Create kubeconfig files in kubeconfig dir 63/86863/1
authorKiran Kamineni <kiran.k.kamineni@intel.com>
Wed, 1 May 2019 19:31:14 +0000 (12:31 -0700)
committerKiran Kamineni <kiran.k.kamineni@intel.com>
Thu, 2 May 2019 22:25:21 +0000 (15:25 -0700)
The connectivity api should allow the creation
of kubeconfig files in the kubeconfig dir.

Issue-ID: MULTICLOUD-292
Change-Id: I5ecc92622648c6c90b71ffad433a132e191cf4b3
Signed-off-by: Kiran Kamineni <kiran.k.kamineni@intel.com>
src/k8splugin/internal/app/client.go
src/k8splugin/internal/app/client_test.go
src/k8splugin/internal/app/instance.go
src/k8splugin/internal/app/instance_test.go
src/k8splugin/internal/connection/connection.go
src/k8splugin/internal/connection/connectionhandler.go

index 7024420..158d21d 100644 (file)
@@ -19,6 +19,8 @@ import (
        "strings"
 
        utils "k8splugin/internal"
+       "k8splugin/internal/config"
+       "k8splugin/internal/connection"
        "k8splugin/internal/helm"
 
        pkgerrors "github.com/pkg/errors"
@@ -43,12 +45,32 @@ type KubernetesClient struct {
        restMapper     meta.RESTMapper
 }
 
-// GetKubeClient loads the Kubernetes configuation values stored into the local configuration file
-func (k *KubernetesClient) init(configPath string) error {
-       if configPath == "" {
-               return pkgerrors.New("config not passed and is not found in ~/.kube. ")
+// getKubeConfig uses the connectivity client to get the kubeconfig based on the name
+// of the cloudregion. This is written out to a file.
+func (k *KubernetesClient) getKubeConfig(cloudregion string) (string, error) {
+       conn := connection.NewConnectionClient()
+       kubeConfigPath, err := conn.Download(cloudregion, config.GetConfiguration().KubeConfigDir)
+       if err != nil {
+               return "", pkgerrors.Wrap(err, "Downloading kubeconfig")
+       }
+
+       return kubeConfigPath, nil
+}
+
+// init loads the Kubernetes configuation values stored into the local configuration file
+func (k *KubernetesClient) init(cloudregion string) error {
+       if cloudregion == "" {
+               return pkgerrors.New("Cloudregion is empty")
        }
 
+       configPath, err := k.getKubeConfig(cloudregion)
+       if err != nil {
+               return pkgerrors.Wrap(err, "Get kubeconfig file")
+       }
+
+       //Remove kubeconfigfile after the clients are created
+       defer os.Remove(configPath)
+
        config, err := clientcmd.BuildConfigFromFlags("", configPath)
        if err != nil {
                return pkgerrors.Wrap(err, "setConfig: Build config from flags raised an error")
index 4cc533e..4bfbcb1 100644 (file)
@@ -14,12 +14,16 @@ limitations under the License.
 package app
 
 import (
+       "encoding/base64"
+       "io/ioutil"
        "os"
        "plugin"
        "reflect"
        "testing"
 
        utils "k8splugin/internal"
+       "k8splugin/internal/connection"
+       "k8splugin/internal/db"
        "k8splugin/internal/helm"
 
        pkgerrors "github.com/pkg/errors"
@@ -46,9 +50,29 @@ func LoadMockPlugins(krdLoadedPlugins map[string]*plugin.Plugin) error {
 
 func TestInit(t *testing.T) {
        t.Run("Successfully create Kube Client", func(t *testing.T) {
+               // Load the mock kube config file into memory
+               fd, err := ioutil.ReadFile("../../mock_files/mock_configs/mock_kube_config")
+               if err != nil {
+                       t.Fatal("Unable to read mock_kube_config")
+               }
+
+               fdbase64 := base64.StdEncoding.EncodeToString(fd)
+
+               // Create mock db with connectivity information in it
+               db.DBconn = &db.MockDB{
+                       Items: map[string]map[string][]byte{
+                               connection.ConnectionKey{CloudRegion: "mock_connection"}.String(): {
+                                       "metadata": []byte(
+                                               "{\"cloud-region\":\"mock_connection\"," +
+                                                       "\"cloud-owner\":\"mock_owner\"," +
+                                                       "\"kubeconfig\": \"" + fdbase64 + "\"}"),
+                               },
+                       },
+               }
 
                kubeClient := KubernetesClient{}
-               err := kubeClient.init("../../mock_files/mock_configs/mock_kube_config")
+               // Refer to the connection via its name
+               err = kubeClient.init("mock_connection")
                if err != nil {
                        t.Fatalf("TestGetKubeClient returned an error (%s)", err)
                }
index 8d289d8..6d0910d 100644 (file)
@@ -21,7 +21,6 @@ import (
        "encoding/json"
        "math/rand"
 
-       "k8splugin/internal/config"
        "k8splugin/internal/db"
        "k8splugin/internal/helm"
        "k8splugin/internal/rb"
@@ -120,7 +119,7 @@ func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) {
        }
 
        k8sClient := KubernetesClient{}
-       err = k8sClient.init(config.GetConfiguration().KubeConfigDir + "/" + i.CloudRegion)
+       err = k8sClient.init(i.CloudRegion)
        if err != nil {
                return InstanceResponse{}, pkgerrors.Wrap(err, "Getting CloudRegion Information")
        }
@@ -185,7 +184,7 @@ func (v *InstanceClient) Delete(id string) error {
        }
 
        k8sClient := KubernetesClient{}
-       err = k8sClient.init(config.GetConfiguration().KubeConfigDir + "/" + inst.CloudRegion)
+       err = k8sClient.init(inst.CloudRegion)
        if err != nil {
                return pkgerrors.Wrap(err, "Getting CloudRegion Information")
        }
index ab39dfb..6ab14a3 100644 (file)
@@ -14,12 +14,14 @@ limitations under the License.
 package app
 
 import (
+       "encoding/base64"
+       "io/ioutil"
        "log"
        "reflect"
        "testing"
 
        utils "k8splugin/internal"
-       "k8splugin/internal/config"
+       "k8splugin/internal/connection"
        "k8splugin/internal/db"
        "k8splugin/internal/helm"
        "k8splugin/internal/rb"
@@ -37,6 +39,12 @@ func TestInstanceCreate(t *testing.T) {
                t.Fatalf("LoadMockPlugins returned an error (%s)", err)
        }
 
+       // Load the mock kube config file into memory
+       fd, err := ioutil.ReadFile("../../mock_files/mock_configs/mock_kube_config")
+       if err != nil {
+               t.Fatal("Unable to read mock_kube_config")
+       }
+
        t.Run("Successfully create Instance", func(t *testing.T) {
                db.DBconn = &db.MockDB{
                        Items: map[string]map[string][]byte{
@@ -145,6 +153,12 @@ func TestInstanceCreate(t *testing.T) {
                                                "RZQl9kOgrk+XoOzX68tJ3wYJb0N/RJ0NzPUr5y4YEDBw4cOHDgwIEDBw4cOHDgwIEDBw4" +
                                                "cOHDgwIEDB18K/AcxEDJDAHgAAA=="),
                                },
+                               connection.ConnectionKey{CloudRegion: "mock_connection"}.String(): {
+                                       "metadata": []byte(
+                                               "{\"cloud-region\":\"mock_connection\"," +
+                                                       "\"cloud-owner\":\"mock_owner\"," +
+                                                       "\"kubeconfig\": \"" + base64.StdEncoding.EncodeToString(fd) + "\"}"),
+                               },
                        },
                }
 
@@ -153,10 +167,9 @@ func TestInstanceCreate(t *testing.T) {
                        RBName:      "test-rbdef",
                        RBVersion:   "v1",
                        ProfileName: "profile1",
-                       CloudRegion: "mock_kube_config",
+                       CloudRegion: "mock_connection",
                }
 
-               config.SetConfigValue("KubeConfigDir", "../../mock_files/mock_configs")
                ir, err := ic.Create(input)
                if err != nil {
                        t.Fatalf("TestInstanceCreate returned an error (%s)", err)
@@ -311,6 +324,12 @@ func TestInstanceDelete(t *testing.T) {
                t.Fatalf("TestInstanceDelete returned an error (%s)", err)
        }
 
+       // Load the mock kube config file into memory
+       fd, err := ioutil.ReadFile("../../mock_files/mock_configs/mock_kube_config")
+       if err != nil {
+               t.Fatal("Unable to read mock_kube_config")
+       }
+
        t.Run("Successfully delete Instance", func(t *testing.T) {
                db.DBconn = &db.MockDB{
                        Items: map[string]map[string][]byte{
@@ -322,7 +341,7 @@ func TestInstanceDelete(t *testing.T) {
                                                        "namespace":"testnamespace",
                                                        "rb-name":"test-rbdef",
                                                        "rb-version":"v1",
-                                                       "cloud-region":"mock_kube_config",
+                                                       "cloud-region":"mock_connection",
                                                        "resources": [
                                                                {
                                                                        "GVK": {
@@ -343,6 +362,12 @@ func TestInstanceDelete(t *testing.T) {
                                                        ]
                                                }`),
                                },
+                               connection.ConnectionKey{CloudRegion: "mock_connection"}.String(): {
+                                       "metadata": []byte(
+                                               "{\"cloud-region\":\"mock_connection\"," +
+                                                       "\"cloud-owner\":\"mock_owner\"," +
+                                                       "\"kubeconfig\": \"" + base64.StdEncoding.EncodeToString(fd) + "\"}"),
+                               },
                        },
                }
 
index dbd1c2e..b2bdca3 100644 (file)
 package connection
 
 import (
+       "encoding/base64"
        "encoding/json"
+       "io/ioutil"
+       "path/filepath"
+
        "k8splugin/internal/db"
 
        pkgerrors "github.com/pkg/errors"
@@ -27,7 +31,7 @@ import (
 type Connection struct {
        CloudRegion           string                 `json:"cloud-region"`
        CloudOwner            string                 `json:"cloud-owner"`
-       Kubeconfig            map[string]interface{} `json:"kubeconfig"`
+       Kubeconfig            string                 `json:"kubeconfig"`
        OtherConnectivityList map[string]interface{} `json:"other-connectivity-list"`
 }
 
@@ -47,14 +51,14 @@ func (dk ConnectionKey) String() string {
        return string(out)
 }
 
-//  ConnectionManager is an interface exposes the Connection functionality
+// ConnectionManager is an interface exposes the Connection functionality
 type ConnectionManager interface {
        Create(c Connection) (Connection, error)
        Get(name string) (Connection, error)
        Delete(name string) error
 }
 
-//  ConnectionClient implements the  ConnectionManager
+// ConnectionClient implements the  ConnectionManager
 // It will also be used to maintain some localized state
 type ConnectionClient struct {
        storeName string
@@ -113,7 +117,7 @@ func (v *ConnectionClient) Get(name string) (Connection, error) {
        return Connection{}, pkgerrors.New("Error getting Connection")
 }
 
-// Delete the  Connection from database
+// Delete the Connection from database
 func (v *ConnectionClient) Delete(name string) error {
 
        //Construct the composite key to select the entry
@@ -124,3 +128,28 @@ func (v *ConnectionClient) Delete(name string) error {
        }
        return nil
 }
+
+// Download the connection information onto a kubeconfig file
+// The file is named after the name of the connection and will
+// be placed in the provided parent directory
+func (v *ConnectionClient) Download(name string, parentdir string) (string, error) {
+
+       conn, err := v.Get(name)
+       if err != nil {
+               return "", pkgerrors.Wrap(err, "Getting Connection info")
+       }
+
+       //Decode the kubeconfig from base64 to string
+       kubeContent, err := base64.StdEncoding.DecodeString(conn.Kubeconfig)
+       if err != nil {
+               return "", pkgerrors.Wrap(err, "Converting from base64")
+       }
+
+       target := filepath.Join(parentdir, conn.CloudRegion)
+       err = ioutil.WriteFile(target, kubeContent, 0644)
+       if err != nil {
+               return "", pkgerrors.Wrap(err, "Writing kubeconfig to file")
+       }
+
+       return target, nil
+}
index 0f8014f..8c860d3 100644 (file)
 package connection
 
 import (
+       "bytes"
+       "encoding/base64"
        "encoding/json"
        "io"
+       "io/ioutil"
        "net/http"
 
        "github.com/gorilla/mux"
@@ -32,11 +35,25 @@ type ConnectionHandler struct {
        Client ConnectionManager
 }
 
-// createHandler handles creation of the connectivity entry in the database
+// CreateHandler handles creation of the connectivity entry in the database
+// This is a multipart handler. See following example curl request
+// curl -i -F "metadata={\"cloud-region\":\"kud\",\"cloud-owner\":\"me\"};type=application/json" \
+//         -F file=@/home/user/.kube/config \
+//         -X POST http://localhost:8081/v1/connectivity-info
 func (h ConnectionHandler) CreateHandler(w http.ResponseWriter, r *http.Request) {
        var v Connection
 
-       err := json.NewDecoder(r.Body).Decode(&v)
+       // Implemenation using multipart form
+       // Review and enable/remove at a later date
+       // Set Max size to 16mb here
+       err := r.ParseMultipartForm(16777216)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+               return
+       }
+
+       jsn := bytes.NewBuffer([]byte(r.FormValue("metadata")))
+       err = json.NewDecoder(jsn).Decode(&v)
        switch {
        case err == io.EOF:
                http.Error(w, "Empty body", http.StatusBadRequest)
@@ -58,11 +75,24 @@ func (h ConnectionHandler) CreateHandler(w http.ResponseWriter, r *http.Request)
                return
        }
 
-       // Kubeconfig is required.
-       if v.Kubeconfig == nil {
-               http.Error(w, "Missing Kubeconfig in POST request", http.StatusBadRequest)
+       //Read the file section and ignore the header
+       file, _, err := r.FormFile("file")
+       if err != nil {
+               http.Error(w, "Unable to process file", http.StatusUnprocessableEntity)
                return
        }
+
+       defer file.Close()
+
+       //Convert the file content to base64 for storage
+       content, err := ioutil.ReadAll(file)
+       if err != nil {
+               http.Error(w, "Unable to read file", http.StatusUnprocessableEntity)
+               return
+       }
+
+       v.Kubeconfig = base64.StdEncoding.EncodeToString(content)
+
        ret, err := h.Client.Create(v)
        if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)