summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKiran Kamineni <kiran.k.kamineni@intel.com>2019-05-01 12:31:14 -0700
committerKiran Kamineni <kiran.k.kamineni@intel.com>2019-05-02 15:25:21 -0700
commita2ae972e814f80184033ee75e9715d1d76323410 (patch)
tree0916ce2ef051b455a76756ba8bd4d9c6d8efd72e
parent2eec43edc8b9a438865422fcf6ae340c2aec036d (diff)
Create kubeconfig files in kubeconfig dir
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>
-rw-r--r--src/k8splugin/internal/app/client.go30
-rw-r--r--src/k8splugin/internal/app/client_test.go26
-rw-r--r--src/k8splugin/internal/app/instance.go5
-rw-r--r--src/k8splugin/internal/app/instance_test.go33
-rw-r--r--src/k8splugin/internal/connection/connection.go37
-rw-r--r--src/k8splugin/internal/connection/connectionhandler.go40
6 files changed, 150 insertions, 21 deletions
diff --git a/src/k8splugin/internal/app/client.go b/src/k8splugin/internal/app/client.go
index 7024420c..158d21de 100644
--- a/src/k8splugin/internal/app/client.go
+++ b/src/k8splugin/internal/app/client.go
@@ -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")
diff --git a/src/k8splugin/internal/app/client_test.go b/src/k8splugin/internal/app/client_test.go
index 4cc533e2..4bfbcb18 100644
--- a/src/k8splugin/internal/app/client_test.go
+++ b/src/k8splugin/internal/app/client_test.go
@@ -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)
}
diff --git a/src/k8splugin/internal/app/instance.go b/src/k8splugin/internal/app/instance.go
index 8d289d85..6d0910d0 100644
--- a/src/k8splugin/internal/app/instance.go
+++ b/src/k8splugin/internal/app/instance.go
@@ -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")
}
diff --git a/src/k8splugin/internal/app/instance_test.go b/src/k8splugin/internal/app/instance_test.go
index ab39dfb7..6ab14a34 100644
--- a/src/k8splugin/internal/app/instance_test.go
+++ b/src/k8splugin/internal/app/instance_test.go
@@ -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) + "\"}"),
+ },
},
}
diff --git a/src/k8splugin/internal/connection/connection.go b/src/k8splugin/internal/connection/connection.go
index dbd1c2e1..b2bdca32 100644
--- a/src/k8splugin/internal/connection/connection.go
+++ b/src/k8splugin/internal/connection/connection.go
@@ -17,7 +17,11 @@
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
+}
diff --git a/src/k8splugin/internal/connection/connectionhandler.go b/src/k8splugin/internal/connection/connectionhandler.go
index 0f8014fa..8c860d31 100644
--- a/src/k8splugin/internal/connection/connectionhandler.go
+++ b/src/k8splugin/internal/connection/connectionhandler.go
@@ -17,8 +17,11 @@
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)