From bf49d552b003072c6bc64ae838a4699c1f4028bd Mon Sep 17 00:00:00 2001 From: Kiran Kamineni Date: Tue, 16 Apr 2019 18:09:13 -0700 Subject: Replace Kind with GroupVersionKind Kind is not unique to track resources in Kubernetes GroupVersionKind is unique. We are just using that to track our data. It is abstracted behind a couple of new types for templates and resources. This change makes a lot of the old kind based operations redundant and simplified. Issue-ID: MULTICLOUD-573 Change-Id: I8f4ded2ba6a0821a8fbd679dc99ce3a44d805524 Signed-off-by: Kiran Kamineni --- src/k8splugin/api/brokerhandler.go | 7 +- src/k8splugin/api/brokerhandler_test.go | 56 ++++++-- src/k8splugin/api/instancehandler_test.go | 74 +++++++++-- src/k8splugin/internal/app/client.go | 190 +++++++++++++--------------- src/k8splugin/internal/app/client_test.go | 52 +++++++- src/k8splugin/internal/app/instance.go | 19 +-- src/k8splugin/internal/app/instance_test.go | 168 +++++++++++++++++------- src/k8splugin/internal/helm/helm.go | 38 ++++-- src/k8splugin/internal/helm/helm_test.go | 35 +++-- src/k8splugin/internal/helm/types.go | 41 ++++++ src/k8splugin/internal/rb/config_backend.go | 30 ++--- src/k8splugin/internal/rb/profile.go | 32 ++--- src/k8splugin/plugins/generic/plugin.go | 28 ++-- 13 files changed, 509 insertions(+), 261 deletions(-) create mode 100644 src/k8splugin/internal/helm/types.go (limited to 'src') diff --git a/src/k8splugin/api/brokerhandler.go b/src/k8splugin/api/brokerhandler.go index 28e44231..80ab643c 100644 --- a/src/k8splugin/api/brokerhandler.go +++ b/src/k8splugin/api/brokerhandler.go @@ -19,6 +19,7 @@ import ( "net/http" "k8splugin/internal/app" + "k8splugin/internal/helm" "github.com/gorilla/mux" ) @@ -44,9 +45,9 @@ type brokerRequest struct { } type brokerPOSTResponse struct { - TemplateType string `json:"template_type"` - WorkloadID string `json:"workload_id"` - TemplateResponse map[string][]string `json:"template_response"` + TemplateType string `json:"template_type"` + WorkloadID string `json:"workload_id"` + TemplateResponse []helm.KubernetesResource `json:"template_response"` } type brokerGETResponse struct { diff --git a/src/k8splugin/api/brokerhandler_test.go b/src/k8splugin/api/brokerhandler_test.go index 57557ac8..d9991e68 100644 --- a/src/k8splugin/api/brokerhandler_test.go +++ b/src/k8splugin/api/brokerhandler_test.go @@ -24,8 +24,10 @@ import ( "testing" "k8splugin/internal/app" + "k8splugin/internal/helm" pkgerrors "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/runtime/schema" ) func TestBrokerCreateHandler(t *testing.T) { @@ -68,9 +70,21 @@ func TestBrokerCreateHandler(t *testing.T) { expected: brokerPOSTResponse{ WorkloadID: "HaKpys8e", TemplateType: "heat", - TemplateResponse: map[string][]string{ - "deployment": []string{"test-deployment"}, - "service": []string{"test-service"}, + TemplateResponse: []helm.KubernetesResource{ + { + GVK: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment"}, + Name: "test-deployment", + }, + { + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Service"}, + Name: "test-service", + }, }, }, expectedCode: http.StatusCreated, @@ -83,9 +97,21 @@ func TestBrokerCreateHandler(t *testing.T) { ProfileName: "profile1", CloudRegion: "region1", Namespace: "testnamespace", - Resources: map[string][]string{ - "deployment": []string{"test-deployment"}, - "service": []string{"test-service"}, + Resources: []helm.KubernetesResource{ + { + GVK: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment"}, + Name: "test-deployment", + }, + { + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Service"}, + Name: "test-service", + }, }, }, }, @@ -154,9 +180,21 @@ func TestBrokerGetHandler(t *testing.T) { ProfileName: "profile1", CloudRegion: "region1", Namespace: "testnamespace", - Resources: map[string][]string{ - "deployment": []string{"test-deployment"}, - "service": []string{"test-service"}, + Resources: []helm.KubernetesResource{ + { + GVK: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment"}, + Name: "test-deployment", + }, + { + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Service"}, + Name: "test-service", + }, }, }, }, diff --git a/src/k8splugin/api/instancehandler_test.go b/src/k8splugin/api/instancehandler_test.go index ed7135a6..6d2abf6f 100644 --- a/src/k8splugin/api/instancehandler_test.go +++ b/src/k8splugin/api/instancehandler_test.go @@ -24,9 +24,11 @@ import ( "testing" "k8splugin/internal/app" + "k8splugin/internal/helm" "github.com/gorilla/mux" pkgerrors "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/runtime/schema" ) //Creating an embedded interface via anonymous variable @@ -108,9 +110,21 @@ func TestInstanceCreateHandler(t *testing.T) { ProfileName: "profile1", CloudRegion: "region1", Namespace: "testnamespace", - Resources: map[string][]string{ - "deployment": []string{"test-deployment"}, - "service": []string{"test-service"}, + Resources: []helm.KubernetesResource{ + { + GVK: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment"}, + Name: "test-deployment", + }, + { + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Service"}, + Name: "test-service", + }, }, }, expectedCode: http.StatusCreated, @@ -123,9 +137,21 @@ func TestInstanceCreateHandler(t *testing.T) { ProfileName: "profile1", CloudRegion: "region1", Namespace: "testnamespace", - Resources: map[string][]string{ - "deployment": []string{"test-deployment"}, - "service": []string{"test-service"}, + Resources: []helm.KubernetesResource{ + { + GVK: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment"}, + Name: "test-deployment", + }, + { + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Service"}, + Name: "test-service", + }, }, }, }, @@ -183,9 +209,21 @@ func TestInstanceGetHandler(t *testing.T) { ProfileName: "profile1", CloudRegion: "region1", Namespace: "testnamespace", - Resources: map[string][]string{ - "deployment": []string{"test-deployment"}, - "service": []string{"test-service"}, + Resources: []helm.KubernetesResource{ + { + GVK: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment"}, + Name: "test-deployment", + }, + { + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Service"}, + Name: "test-service", + }, }, }, instClient: &mockInstanceClient{ @@ -197,9 +235,21 @@ func TestInstanceGetHandler(t *testing.T) { ProfileName: "profile1", CloudRegion: "region1", Namespace: "testnamespace", - Resources: map[string][]string{ - "deployment": []string{"test-deployment"}, - "service": []string{"test-service"}, + Resources: []helm.KubernetesResource{ + { + GVK: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment"}, + Name: "test-deployment", + }, + { + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Service"}, + Name: "test-service", + }, }, }, }, diff --git a/src/k8splugin/internal/app/client.go b/src/k8splugin/internal/app/client.go index 9b8873cc..7024420c 100644 --- a/src/k8splugin/internal/app/client.go +++ b/src/k8splugin/internal/app/client.go @@ -19,6 +19,7 @@ import ( "strings" utils "k8splugin/internal" + "k8splugin/internal/helm" pkgerrors "github.com/pkg/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -27,13 +28,12 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/restmapper" "k8s.io/client-go/tools/clientcmd" - "k8s.io/helm/pkg/tiller" ) // PluginReference is the interface that is implemented type PluginReference interface { Create(yamlFilePath string, namespace string, client *KubernetesClient) (string, error) - Delete(kind string, name string, namespace string, client *KubernetesClient) error + Delete(resource helm.KubernetesResource, namespace string, client *KubernetesClient) error } type KubernetesClient struct { @@ -105,141 +105,124 @@ func (k *KubernetesClient) ensureNamespace(namespace string) error { return nil } -func (k *KubernetesClient) createGeneric(kind string, files []string, namespace string) ([]string, error) { +func (k *KubernetesClient) createGeneric(resTempl helm.KubernetesResourceTemplate, + namespace string) (helm.KubernetesResource, error) { - log.Println("Processing items of Kind: " + kind) + log.Println("Processing Kind: " + resTempl.GVK.Kind) //Check if have the mapper before loading the plugin err := k.updateMapper() if err != nil { - return nil, pkgerrors.Wrap(err, "Unable to create RESTMapper") + return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Unable to create RESTMapper") } pluginObject, ok := utils.LoadedPlugins["generic"] if !ok { - return nil, pkgerrors.New("No generic plugin found") + return helm.KubernetesResource{}, pkgerrors.New("No generic plugin found") } symbol, err := pluginObject.Lookup("ExportedVariable") if err != nil { - return nil, pkgerrors.Wrap(err, "No ExportedVariable symbol found") + return helm.KubernetesResource{}, pkgerrors.Wrap(err, "No ExportedVariable symbol found") } //Assert if it implements the PluginReference interface genericPlugin, ok := symbol.(PluginReference) if !ok { - return nil, pkgerrors.New("ExportedVariable is not PluginReference type") + return helm.KubernetesResource{}, pkgerrors.New("ExportedVariable is not PluginReference type") } - //Iterate over each file of a particular kind here - var resourcesCreated []string - for _, f := range files { - if _, err := os.Stat(f); os.IsNotExist(err) { - return nil, pkgerrors.New("File " + f + "does not exists") - } - - log.Println("Processing file: " + f) + if _, err := os.Stat(resTempl.FilePath); os.IsNotExist(err) { + return helm.KubernetesResource{}, pkgerrors.New("File " + resTempl.FilePath + "does not exists") + } - name, err := genericPlugin.Create(f, namespace, k) - if err != nil { - return nil, pkgerrors.Wrap(err, "Error in generic plugin") - } + log.Println("Processing file: " + resTempl.FilePath) - resourcesCreated = append(resourcesCreated, name) + name, err := genericPlugin.Create(resTempl.FilePath, namespace, k) + if err != nil { + return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error in generic plugin") } - return resourcesCreated, nil -} -func (k *KubernetesClient) createKind(kind string, files []string, namespace string) ([]string, error) { + return helm.KubernetesResource{ + GVK: resTempl.GVK, + Name: name, + }, nil +} - log.Println("Processing items of Kind: " + kind) +func (k *KubernetesClient) createKind(resTempl helm.KubernetesResourceTemplate, + namespace string) (helm.KubernetesResource, error) { - //Iterate over each file of a particular kind here - var resourcesCreated []string - for _, f := range files { - if _, err := os.Stat(f); os.IsNotExist(err) { - return nil, pkgerrors.New("File " + f + "does not exists") - } + log.Println("Processing Kind: " + resTempl.GVK.Kind) - log.Println("Processing file: " + f) + if _, err := os.Stat(resTempl.FilePath); os.IsNotExist(err) { + return helm.KubernetesResource{}, pkgerrors.New("File " + resTempl.FilePath + "does not exists") + } - //Populate the namespace from profile instead of instance body - genericKubeData := &utils.ResourceData{ - YamlFilePath: f, - Namespace: namespace, - } + log.Println("Processing file: " + resTempl.FilePath) - typePlugin, ok := utils.LoadedPlugins[strings.ToLower(kind)] - if !ok { - log.Println("No plugin for kind " + kind + " found. Using generic Plugin") - return k.createGeneric(kind, files, namespace) - } + //Populate the namespace from profile instead of instance body + genericKubeData := &utils.ResourceData{ + YamlFilePath: resTempl.FilePath, + Namespace: namespace, + } - symCreateResourceFunc, err := typePlugin.Lookup("Create") - if err != nil { - return nil, pkgerrors.Wrap(err, "Error fetching "+kind+" plugin") - } + typePlugin, ok := utils.LoadedPlugins[strings.ToLower(resTempl.GVK.Kind)] + if !ok { + log.Println("No plugin for kind " + resTempl.GVK.Kind + " found. Using generic Plugin") + return k.createGeneric(resTempl, namespace) + } - createdResourceName, err := symCreateResourceFunc.(func(*utils.ResourceData, kubernetes.Interface) (string, error))( - genericKubeData, k.clientSet) - if err != nil { - return nil, pkgerrors.Wrap(err, "Error in plugin "+kind+" plugin") - } - log.Print(createdResourceName + " created") - resourcesCreated = append(resourcesCreated, createdResourceName) + symCreateResourceFunc, err := typePlugin.Lookup("Create") + if err != nil { + return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error fetching "+resTempl.GVK.Kind+" plugin") } - return resourcesCreated, nil + createdResourceName, err := symCreateResourceFunc.(func(*utils.ResourceData, kubernetes.Interface) (string, error))( + genericKubeData, k.clientSet) + if err != nil { + return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error in plugin "+resTempl.GVK.Kind+" plugin") + } + log.Print(createdResourceName + " created") + return helm.KubernetesResource{ + GVK: resTempl.GVK, + Name: createdResourceName, + }, nil } -func (k *KubernetesClient) createResources(resMap map[string][]string, - namespace string) (map[string][]string, error) { +func (k *KubernetesClient) createResources(sortedTemplates []helm.KubernetesResourceTemplate, + namespace string) ([]helm.KubernetesResource, error) { err := k.ensureNamespace(namespace) if err != nil { return nil, pkgerrors.Wrap(err, "Creating Namespace") } - createdResourceMap := make(map[string][]string) - // Create all the known kinds in the InstallOrder - for _, kind := range tiller.InstallOrder { - files, ok := resMap[kind] - if !ok { - log.Println("Kind " + kind + " not found. Skipping...") - continue - } - - resourcesCreated, err := k.createKind(kind, files, namespace) + var createdResources []helm.KubernetesResource + for _, resTempl := range sortedTemplates { + resCreated, err := k.createKind(resTempl, namespace) if err != nil { - return nil, pkgerrors.Wrap(err, "Error creating kind: "+kind) + return nil, pkgerrors.Wrapf(err, "Error creating kind: %+v", resTempl.GVK) } - - createdResourceMap[kind] = resourcesCreated - delete(resMap, kind) + createdResources = append(createdResources, resCreated) } - //Create the remaining kinds from the resMap - for kind, files := range resMap { - resourcesCreated, err := k.createKind(kind, files, namespace) - if err != nil { - return nil, pkgerrors.Wrap(err, "Error creating kind: "+kind) - } - - createdResourceMap[kind] = resourcesCreated - delete(resMap, kind) - } - - return createdResourceMap, nil + return createdResources, nil } -func (k *KubernetesClient) deleteGeneric(kind string, resources []string, namespace string) error { - log.Println("Deleting items of Kind: " + kind) +func (k *KubernetesClient) deleteGeneric(resource helm.KubernetesResource, namespace string) error { + log.Println("Deleting Kind: " + resource.GVK.Kind) pluginObject, ok := utils.LoadedPlugins["generic"] if !ok { return pkgerrors.New("No generic plugin found") } + //Check if have the mapper before loading the plugin + err := k.updateMapper() + if err != nil { + return pkgerrors.Wrap(err, "Unable to create RESTMapper") + } + symbol, err := pluginObject.Lookup("ExportedVariable") if err != nil { return pkgerrors.Wrap(err, "No ExportedVariable symbol found") @@ -251,45 +234,42 @@ func (k *KubernetesClient) deleteGeneric(kind string, resources []string, namesp return pkgerrors.New("ExportedVariable is not PluginReference type") } - for _, res := range resources { - err = genericPlugin.Delete(kind, res, namespace, k) - if err != nil { - return pkgerrors.Wrap(err, "Error in generic plugin") - } + err = genericPlugin.Delete(resource, namespace, k) + if err != nil { + return pkgerrors.Wrap(err, "Error in generic plugin") } return nil } -func (k *KubernetesClient) deleteKind(kind string, resources []string, namespace string) error { - log.Println("Deleting items of Kind: " + kind) +func (k *KubernetesClient) deleteKind(resource helm.KubernetesResource, namespace string) error { + log.Println("Deleting Kind: " + resource.GVK.Kind) - typePlugin, ok := utils.LoadedPlugins[strings.ToLower(kind)] + typePlugin, ok := utils.LoadedPlugins[strings.ToLower(resource.GVK.Kind)] if !ok { - log.Println("No plugin for kind " + kind + " found. Using generic Plugin") - return k.deleteGeneric(kind, resources, namespace) + log.Println("No plugin for kind " + resource.GVK.Kind + " found. Using generic Plugin") + return k.deleteGeneric(resource, namespace) } symDeleteResourceFunc, err := typePlugin.Lookup("Delete") if err != nil { - return pkgerrors.Wrap(err, "Error findinf Delete symbol in plugin") + return pkgerrors.Wrap(err, "Error finding Delete symbol in plugin") } - for _, res := range resources { - log.Println("Deleting resource: " + res) - err = symDeleteResourceFunc.(func(string, string, kubernetes.Interface) error)( - res, namespace, k.clientSet) - if err != nil { - return pkgerrors.Wrap(err, "Error destroying "+res) - } + log.Println("Deleting resource: " + resource.Name) + err = symDeleteResourceFunc.(func(string, string, kubernetes.Interface) error)( + resource.Name, namespace, k.clientSet) + if err != nil { + return pkgerrors.Wrap(err, "Error destroying "+resource.Name) } + return nil } -func (k *KubernetesClient) deleteResources(resMap map[string][]string, namespace string) error { +func (k *KubernetesClient) deleteResources(resources []helm.KubernetesResource, namespace string) error { //TODO: Investigate if deletion should be in a particular order - for kind, resourceNames := range resMap { - err := k.deleteKind(kind, resourceNames, namespace) + for _, res := range resources { + err := k.deleteKind(res, namespace) if err != nil { return pkgerrors.Wrap(err, "Deleting resources") } diff --git a/src/k8splugin/internal/app/client_test.go b/src/k8splugin/internal/app/client_test.go index b3436431..d023fcff 100644 --- a/src/k8splugin/internal/app/client_test.go +++ b/src/k8splugin/internal/app/client_test.go @@ -20,8 +20,10 @@ import ( "testing" utils "k8splugin/internal" + "k8splugin/internal/helm" pkgerrors "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes" ) @@ -76,9 +78,21 @@ func TestCreateResources(t *testing.T) { } t.Run("Successfully delete resources", func(t *testing.T) { - data := map[string][]string{ - "Deployment": []string{"../../mock_files/mock_yamls/deployment.yaml"}, - "Service": []string{"../../mock_files/mock_yamls/service.yaml"}, + data := []helm.KubernetesResourceTemplate{ + { + GVK: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment"}, + FilePath: "../../mock_files/mock_yamls/deployment.yaml", + }, + { + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Service"}, + FilePath: "../../mock_files/mock_yamls/service.yaml", + }, } _, err := k8.createResources(data, "testnamespace") @@ -105,9 +119,35 @@ func TestDeleteResources(t *testing.T) { } t.Run("Successfully delete resources", func(t *testing.T) { - data := map[string][]string{ - "Deployment": []string{"deployment-1", "deployment-2"}, - "Service": []string{"service-1", "service-2"}, + data := []helm.KubernetesResource{ + { + GVK: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment"}, + Name: "deployment-1", + }, + { + GVK: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment"}, + Name: "deployment-2", + }, + { + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Service"}, + Name: "service-1", + }, + { + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Service"}, + Name: "service-2", + }, } err := k8.deleteResources(data, "test") diff --git a/src/k8splugin/internal/app/instance.go b/src/k8splugin/internal/app/instance.go index 93305c30..8e9a2b7a 100644 --- a/src/k8splugin/internal/app/instance.go +++ b/src/k8splugin/internal/app/instance.go @@ -23,6 +23,7 @@ import ( "os" "k8splugin/internal/db" + "k8splugin/internal/helm" "k8splugin/internal/rb" pkgerrors "github.com/pkg/errors" @@ -40,13 +41,13 @@ type InstanceRequest struct { // InstanceResponse contains the response from instantiation type InstanceResponse struct { - ID string `json:"id"` - RBName string `json:"rb-name"` - RBVersion string `json:"rb-version"` - ProfileName string `json:"profile-name"` - CloudRegion string `json:"cloud-region"` - Namespace string `json:"namespace"` - Resources map[string][]string `json:"resources"` + ID string `json:"id"` + RBName string `json:"rb-name"` + RBVersion string `json:"rb-version"` + ProfileName string `json:"profile-name"` + CloudRegion string `json:"cloud-region"` + Namespace string `json:"namespace"` + Resources []helm.KubernetesResource `json:"resources"` } // InstanceManager is an interface exposes the instantiation functionality @@ -113,7 +114,7 @@ func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) { overrideValues := []string{} //Execute the kubernetes create command - resMap, err := rb.NewProfileClient().Resolve(i.RBName, i.RBVersion, i.ProfileName, overrideValues) + sortedTemplates, err := rb.NewProfileClient().Resolve(i.RBName, i.RBVersion, i.ProfileName, overrideValues) if err != nil { return InstanceResponse{}, pkgerrors.Wrap(err, "Error resolving helm charts") } @@ -124,7 +125,7 @@ func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) { return InstanceResponse{}, pkgerrors.Wrap(err, "Getting CloudRegion Information") } - createdResources, err := k8sClient.createResources(resMap, profile.Namespace) + createdResources, err := k8sClient.createResources(sortedTemplates, profile.Namespace) if err != nil { return InstanceResponse{}, pkgerrors.Wrap(err, "Create Kubernetes Resources") } diff --git a/src/k8splugin/internal/app/instance_test.go b/src/k8splugin/internal/app/instance_test.go index effd5c99..3828ed38 100644 --- a/src/k8splugin/internal/app/instance_test.go +++ b/src/k8splugin/internal/app/instance_test.go @@ -21,7 +21,10 @@ import ( utils "k8splugin/internal" "k8splugin/internal/db" + "k8splugin/internal/helm" "k8splugin/internal/rb" + + "k8s.io/apimachinery/pkg/runtime/schema" ) func TestInstanceCreate(t *testing.T) { @@ -189,16 +192,32 @@ func TestInstanceGet(t *testing.T) { Items: map[string]map[string][]byte{ InstanceKey{ID: "HaKpys8e"}.String(): { "instance": []byte( - "{\"profile-name\":\"profile1\"," + - "\"id\":\"HaKpys8e\"," + - "\"namespace\":\"testnamespace\"," + - "\"rb-name\":\"test-rbdef\"," + - "\"rb-version\":\"v1\"," + - "\"cloud-region\":\"region1\"," + - "\"resources\": {" + - "\"deployment\": [\"test-deployment\"]," + - "\"service\": [\"test-service\"]" + - "}}"), + `{ + "profile-name":"profile1", + "id":"HaKpys8e", + "namespace":"testnamespace", + "rb-name":"test-rbdef", + "rb-version":"v1", + "cloud-region":"region1", + "resources": [ + { + "GVK": { + "Group":"apps", + "Version":"v1", + "Kind":"Deployment" + }, + "Name": "deployment-1" + }, + { + "GVK": { + "Group":"", + "Version":"v1", + "Kind":"Service" + }, + "Name": "service-1" + } + ] + }`), }, }, } @@ -210,16 +229,29 @@ func TestInstanceGet(t *testing.T) { ProfileName: "profile1", CloudRegion: "region1", Namespace: "testnamespace", - Resources: map[string][]string{ - "deployment": []string{"test-deployment"}, - "service": []string{"test-service"}, + + Resources: []helm.KubernetesResource{ + { + GVK: schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: "Deployment"}, + Name: "deployment-1", + }, + { + GVK: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Service"}, + Name: "service-1", + }, }, } ic := NewInstanceClient() id := "HaKpys8e" data, err := ic.Get(id) if err != nil { - t.Fatalf("TestInstanceDelete returned an error (%s)", err) + t.Fatalf("TestInstanceGet returned an error (%s)", err) } if !reflect.DeepEqual(expected, data) { t.Fatalf("TestInstanceGet returned:\n result=%v\n expected=%v", @@ -232,16 +264,32 @@ func TestInstanceGet(t *testing.T) { Items: map[string]map[string][]byte{ InstanceKey{ID: "HaKpys8e"}.String(): { "instance": []byte( - "{\"profile-name\":\"profile1\"," + - "\"id\":\"HaKpys8e\"," + - "\"namespace\":\"testnamespace\"," + - "\"rb-name\":\"test-rbdef\"," + - "\"rb-version\":\"v1\"," + - "\"cloud-region\":\"mock_config\"," + - "\"resources\": {" + - "\"deployment\": [\"deployment-1\",\"deployment-2\"]," + - "\"service\": [\"service-1\",\"service-2\"]" + - "}}"), + `{ + "profile-name":"profile1", + "id":"HaKpys8e", + "namespace":"testnamespace", + "rb-name":"test-rbdef", + "rb-version":"v1", + "cloud-region":"region1", + "resources": [ + { + "GVK": { + "Group":"apps", + "Version":"v1", + "Kind":"Deployment" + }, + "Name": "deployment-1" + }, + { + "GVK": { + "Group":"", + "Version":"v1", + "Kind":"Service" + }, + "Name": "service-1" + } + ] + }`), }, }, } @@ -272,16 +320,32 @@ func TestInstanceDelete(t *testing.T) { Items: map[string]map[string][]byte{ InstanceKey{ID: "HaKpys8e"}.String(): { "instance": []byte( - "{\"profile-name\":\"profile1\"," + - "\"id\":\"HaKpys8e\"," + - "\"namespace\":\"testnamespace\"," + - "\"rb-name\":\"test-rbdef\"," + - "\"rb-version\":\"v1\"," + - "\"cloud-region\":\"mock_config\"," + - "\"resources\": {" + - "\"deployment\": [\"deployment-1\",\"deployment-2\"]," + - "\"service\": [\"service-1\",\"service-2\"]" + - "}}"), + `{ + "profile-name":"profile1", + "id":"HaKpys8e", + "namespace":"testnamespace", + "rb-name":"test-rbdef", + "rb-version":"v1", + "cloud-region":"mock_config", + "resources": [ + { + "GVK": { + "Group":"apps", + "Version":"v1", + "Kind":"Deployment" + }, + "Name": "deployment-1" + }, + { + "GVK": { + "Group":"", + "Version":"v1", + "Kind":"Service" + }, + "Name": "service-1" + } + ] + }`), }, }, } @@ -299,16 +363,32 @@ func TestInstanceDelete(t *testing.T) { Items: map[string]map[string][]byte{ InstanceKey{ID: "HaKpys8e"}.String(): { "instance": []byte( - "{\"profile-name\":\"profile1\"," + - "\"id\":\"HaKpys8e\"," + - "\"namespace\":\"testnamespace\"," + - "\"rb-name\":\"test-rbdef\"," + - "\"rb-version\":\"v1\"," + - "\"cloud-region\":\"mock_config\"," + - "\"resources\": {" + - "\"deployment\": [\"deployment-1\",\"deployment-2\"]," + - "\"service\": [\"service-1\",\"service-2\"]" + - "}}"), + `{ + "profile-name":"profile1", + "id":"HaKpys8e", + "namespace":"testnamespace", + "rb-name":"test-rbdef", + "rb-version":"v1", + "cloud-region":"mock_config", + "resources": [ + { + "GVK": { + "Group":"apps", + "Version":"v1", + "Kind":"Deployment" + }, + "Name": "deployment-1" + }, + { + "GVK": { + "Group":"", + "Version":"v1", + "Kind":"Service" + }, + "Name": "service-1" + } + ] + }`), }, }, } diff --git a/src/k8splugin/internal/helm/helm.go b/src/k8splugin/internal/helm/helm.go index 65a36d6b..1ab701ae 100644 --- a/src/k8splugin/internal/helm/helm.go +++ b/src/k8splugin/internal/helm/helm.go @@ -28,8 +28,10 @@ import ( "github.com/ghodss/yaml" pkgerrors "github.com/pkg/errors" - + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/apimachinery/pkg/util/validation" + k8syaml "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/manifest" "k8s.io/helm/pkg/proto/hapi/chart" @@ -143,10 +145,11 @@ func (h *TemplateClient) ensureDirectory(f string) error { } // GenerateKubernetesArtifacts a mapping of type to fully evaluated helm template -func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFiles []string, values []string) (map[string][]string, error) { +func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFiles []string, + values []string) ([]KubernetesResourceTemplate, error) { var outputDir, chartPath, namespace, releaseName string - var retData map[string][]string + var retData []KubernetesResourceTemplate releaseName = h.releaseName namespace = h.kubeNameSpace @@ -226,7 +229,6 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile var manifestsToRender []manifest.Manifest //render all manifests in the chart manifestsToRender = listManifests - retData = make(map[string][]string) for _, m := range tiller.SortByKind(manifestsToRender) { data := m.Content b := filepath.Base(m.Name) @@ -249,11 +251,31 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile return retData, err } - if val, ok := retData[m.Head.Kind]; ok { - retData[m.Head.Kind] = append(val, mfilePath) - } else { - retData[m.Head.Kind] = []string{mfilePath} + gvk, err := getGroupVersionKind(data) + if err != nil { + return retData, err } + + kres := KubernetesResourceTemplate{ + GVK: gvk, + FilePath: mfilePath, + } + retData = append(retData, kres) } return retData, nil } + +func getGroupVersionKind(data string) (schema.GroupVersionKind, error) { + out, err := k8syaml.ToJSON([]byte(data)) + if err != nil { + return schema.GroupVersionKind{}, pkgerrors.Wrap(err, "Converting yaml to json") + } + + simpleMeta := json.SimpleMetaFactory{} + gvk, err := simpleMeta.Interpret(out) + if err != nil { + return schema.GroupVersionKind{}, pkgerrors.Wrap(err, "Parsing apiversion and kind") + } + + return *gvk, nil +} diff --git a/src/k8splugin/internal/helm/helm_test.go b/src/k8splugin/internal/helm/helm_test.go index 27bb9d79..a13c67ba 100644 --- a/src/k8splugin/internal/helm/helm_test.go +++ b/src/k8splugin/internal/helm/helm_test.go @@ -165,27 +165,26 @@ func TestGenerateKubernetesArtifacts(t *testing.T) { } else { //Compute the hash of returned data and compare for _, v := range out { - for _, f := range v { - data, err := ioutil.ReadFile(f) - if err != nil { - t.Errorf("Unable to read file %s", v) - } - h.Write(data) - gotHash := fmt.Sprintf("%x", h.Sum(nil)) - h.Reset() + f := v.FilePath + data, err := ioutil.ReadFile(f) + if err != nil { + t.Errorf("Unable to read file %s", v) + } + h.Write(data) + gotHash := fmt.Sprintf("%x", h.Sum(nil)) + h.Reset() - //Find the right hash from expectedHashMap - expectedHash := "" - for k1, v1 := range testCase.expectedHashMap { - if strings.Contains(f, k1) == true { - expectedHash = v1 - break - } - } - if gotHash != expectedHash { - t.Fatalf("Got unexpected hash for %s", f) + //Find the right hash from expectedHashMap + expectedHash := "" + for k1, v1 := range testCase.expectedHashMap { + if strings.Contains(f, k1) == true { + expectedHash = v1 + break } } + if gotHash != expectedHash { + t.Fatalf("Got unexpected hash for %s", f) + } } } }) diff --git a/src/k8splugin/internal/helm/types.go b/src/k8splugin/internal/helm/types.go new file mode 100644 index 00000000..2c8badb8 --- /dev/null +++ b/src/k8splugin/internal/helm/types.go @@ -0,0 +1,41 @@ +/* + * Copyright 2018 Intel Corporation, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package helm + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// Represents the template that is used to create a particular +// resource in Kubernetes +type KubernetesResourceTemplate struct { + // Tracks the apiVersion and Kind of the resource + GVK schema.GroupVersionKind + // Path to the file that contains the resource info + FilePath string +} + +// KubernetesResource is the resource that is created in Kubernetes +// It is the type that will be used for tracking a resource. +// Any future information such as status, time can be added here +// for tracking. +type KubernetesResource struct { + // Tracks the apiVersion and Kind of the resource + GVK schema.GroupVersionKind + // Name of resource in Kubernetes + Name string +} diff --git a/src/k8splugin/internal/rb/config_backend.go b/src/k8splugin/internal/rb/config_backend.go index b61fc493..e2fa5b3c 100644 --- a/src/k8splugin/internal/rb/config_backend.go +++ b/src/k8splugin/internal/rb/config_backend.go @@ -19,16 +19,16 @@ package rb import ( "bytes" "encoding/json" - "k8splugin/internal/db" - "k8splugin/internal/helm" + "io/ioutil" "log" + "path/filepath" "strconv" "strings" - - "io/ioutil" - "path/filepath" "sync" + "k8splugin/internal/db" + "k8splugin/internal/helm" + "github.com/ghodss/yaml" pkgerrors "github.com/pkg/errors" ) @@ -56,9 +56,9 @@ type ConfigVersionStore struct { } type configResourceList struct { - retmap map[string][]string - profile Profile - action string + resourceTemplates []helm.KubernetesResourceTemplate + profile Profile + action string } type profileDataManager struct { @@ -341,13 +341,13 @@ func scheduleResources(c chan configResourceList) { //TODO: ADD Check to see if Application running switch { case data.action == "POST": - log.Printf("[scheduleResources]: POST %v %v", data.profile, data.retmap) + log.Printf("[scheduleResources]: POST %v %v", data.profile, data.resourceTemplates) //TODO: Needs to add code to call Kubectl create case data.action == "PUT": - log.Printf("[scheduleResources]: PUT %v %v", data.profile, data.retmap) + log.Printf("[scheduleResources]: PUT %v %v", data.profile, data.resourceTemplates) //TODO: Needs to add code to call Kubectl apply case data.action == "DELETE": - log.Printf("[scheduleResources]: DELETE %v %v", data.profile, data.retmap) + log.Printf("[scheduleResources]: DELETE %v %v", data.profile, data.resourceTemplates) //TODO: Needs to add code to call Kubectl delete } @@ -358,7 +358,7 @@ func scheduleResources(c chan configResourceList) { //configuration overrides resides. var resolve = func(rbName, rbVersion, profileName string, p Config) (configResourceList, error) { - var retMap map[string][]string + var resTemplates []helm.KubernetesResourceTemplate profile, err := NewProfileClient().Get(rbName, rbVersion, profileName) if err != nil { @@ -408,15 +408,15 @@ var resolve = func(rbName, rbVersion, profileName string, p Config) (configResou profile.ReleaseName) chartPath := filepath.Join(chartBasePath, t.ChartName) - retMap, err = helmClient.GenerateKubernetesArtifacts(chartPath, + resTemplates, err = helmClient.GenerateKubernetesArtifacts(chartPath, []string{outputfile.Name()}, nil) if err != nil { return configResourceList{}, pkgerrors.Wrap(err, "Generate final k8s yaml") } crl := configResourceList{ - retmap: retMap, - profile: profile, + resourceTemplates: resTemplates, + profile: profile, } return crl, nil diff --git a/src/k8splugin/internal/rb/profile.go b/src/k8splugin/internal/rb/profile.go index 679815ac..7d3902f2 100644 --- a/src/k8splugin/internal/rb/profile.go +++ b/src/k8splugin/internal/rb/profile.go @@ -20,12 +20,12 @@ import ( "bytes" "encoding/base64" "encoding/json" - "k8splugin/internal/db" "path/filepath" - pkgerrors "github.com/pkg/errors" - + "k8splugin/internal/db" "k8splugin/internal/helm" + + pkgerrors "github.com/pkg/errors" ) // Profile contains the parameters needed for resource bundle (rb) profiles @@ -236,48 +236,48 @@ func (v *ProfileClient) Download(rbName, rbVersion, prName string) ([]byte, erro //Resolve returns the path where the helm chart merged with //configuration overrides resides. func (v *ProfileClient) Resolve(rbName string, rbVersion string, - profileName string, values []string) (map[string][]string, error) { + profileName string, values []string) ([]helm.KubernetesResourceTemplate, error) { - var retMap map[string][]string + var sortedTemplates []helm.KubernetesResourceTemplate //Download and process the profile first //If everything seems okay, then download the definition prData, err := v.Download(rbName, rbVersion, profileName) if err != nil { - return retMap, pkgerrors.Wrap(err, "Downloading Profile") + return sortedTemplates, pkgerrors.Wrap(err, "Downloading Profile") } prPath, err := ExtractTarBall(bytes.NewBuffer(prData)) if err != nil { - return retMap, pkgerrors.Wrap(err, "Extracting Profile Content") + return sortedTemplates, pkgerrors.Wrap(err, "Extracting Profile Content") } prYamlClient, err := ProcessProfileYaml(prPath, v.manifestName) if err != nil { - return retMap, pkgerrors.Wrap(err, "Processing Profile Manifest") + return sortedTemplates, pkgerrors.Wrap(err, "Processing Profile Manifest") } definitionClient := NewDefinitionClient() definition, err := definitionClient.Get(rbName, rbVersion) if err != nil { - return retMap, pkgerrors.Wrap(err, "Getting Definition Metadata") + return sortedTemplates, pkgerrors.Wrap(err, "Getting Definition Metadata") } defData, err := definitionClient.Download(rbName, rbVersion) if err != nil { - return retMap, pkgerrors.Wrap(err, "Downloading Definition") + return sortedTemplates, pkgerrors.Wrap(err, "Downloading Definition") } chartBasePath, err := ExtractTarBall(bytes.NewBuffer(defData)) if err != nil { - return retMap, pkgerrors.Wrap(err, "Extracting Definition Charts") + return sortedTemplates, pkgerrors.Wrap(err, "Extracting Definition Charts") } //Get the definition ID and download its contents profile, err := v.Get(rbName, rbVersion, profileName) if err != nil { - return retMap, pkgerrors.Wrap(err, "Getting Profile") + return sortedTemplates, pkgerrors.Wrap(err, "Getting Profile") } //Copy the profile configresources to the chart locations @@ -287,7 +287,7 @@ func (v *ProfileClient) Resolve(rbName string, rbVersion string, // chartpath: chart/config/resources/config.yaml err = prYamlClient.CopyConfigurationOverrides(chartBasePath) if err != nil { - return retMap, pkgerrors.Wrap(err, "Copying configresources to chart") + return sortedTemplates, pkgerrors.Wrap(err, "Copying configresources to chart") } helmClient := helm.NewTemplateClient(profile.KubernetesVersion, @@ -295,12 +295,12 @@ func (v *ProfileClient) Resolve(rbName string, rbVersion string, profile.ReleaseName) chartPath := filepath.Join(chartBasePath, definition.ChartName) - retMap, err = helmClient.GenerateKubernetesArtifacts(chartPath, + sortedTemplates, err = helmClient.GenerateKubernetesArtifacts(chartPath, []string{prYamlClient.GetValues()}, values) if err != nil { - return retMap, pkgerrors.Wrap(err, "Generate final k8s yaml") + return sortedTemplates, pkgerrors.Wrap(err, "Generate final k8s yaml") } - return retMap, nil + return sortedTemplates, nil } diff --git a/src/k8splugin/plugins/generic/plugin.go b/src/k8splugin/plugins/generic/plugin.go index b0cf609c..9073535c 100644 --- a/src/k8splugin/plugins/generic/plugin.go +++ b/src/k8splugin/plugins/generic/plugin.go @@ -24,22 +24,12 @@ import ( utils "k8splugin/internal" "k8splugin/internal/app" + "k8splugin/internal/helm" ) type genericPlugin struct { } -var kindToGVRMap = map[string]schema.GroupVersionResource{ - "ConfigMap": schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}, - "StatefulSet": schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulsets"}, - "Job": schema.GroupVersionResource{Group: "batch", Version: "v1", Resource: "jobs"}, - "Pod": schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}, - "DaemonSet": schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "daemonsets"}, - "CustomResourceDefinition": schema.GroupVersionResource{ - Group: "apiextensions.k8s.io", Version: "v1beta1", Resource: "customresourcedefinitions", - }, -} - // Create deployment object in a specific Kubernetes cluster func (g genericPlugin) Create(yamlFilePath string, namespace string, client *app.KubernetesClient) (string, error) { if namespace == "" { @@ -79,7 +69,7 @@ func (g genericPlugin) Create(yamlFilePath string, namespace string, client *app } // Delete an existing deployment hosted in a specific Kubernetes cluster -func (g genericPlugin) Delete(kind string, name string, namespace string, client *app.KubernetesClient) error { +func (g genericPlugin) Delete(resource helm.KubernetesResource, namespace string, client *app.KubernetesClient) error { if namespace == "" { namespace = "default" } @@ -90,14 +80,20 @@ func (g genericPlugin) Delete(kind string, name string, namespace string, client } dynClient := client.GetDynamicClient() - gvr, ok := kindToGVRMap[kind] - if !ok { - return pkgerrors.New("GVR not found for: " + kind) + mapper := client.GetMapper() + + mapping, err := mapper.RESTMapping(schema.GroupKind{ + Group: resource.GVK.Group, + Kind: resource.GVK.Kind, + }, resource.GVK.Version) + if err != nil { + return pkgerrors.Wrap(err, "Mapping kind to resource error") } + gvr := mapping.Resource log.Printf("Using gvr: %s, %s, %s", gvr.Group, gvr.Version, gvr.Resource) - err := dynClient.Resource(gvr).Namespace(namespace).Delete(name, opts) + err = dynClient.Resource(gvr).Namespace(namespace).Delete(resource.Name, opts) if err != nil { return pkgerrors.Wrap(err, "Delete object error") } -- cgit 1.2.3-korg