Plugin code refactoring 51/89551/2
authorKiran Kamineni <kiran.k.kamineni@intel.com>
Thu, 30 May 2019 21:43:06 +0000 (14:43 -0700)
committerKiran Kamineni <kiran.k.kamineni@intel.com>
Fri, 7 Jun 2019 00:32:41 +0000 (17:32 -0700)
The plugin code has been refactored to
implement a common interface.
This will allow us to do plugin validation
at loadtime of the plugin instead of at runtime.
This also makes the code calling the plugins cleaner
and easier to read.

Issue-ID: MULTICLOUD-557
Change-Id: Ice2bcc9b850d7c0e1707dcc42132c63dd77472a7
Signed-off-by: Kiran Kamineni <kiran.k.kamineni@intel.com>
12 files changed:
src/k8splugin/go.sum
src/k8splugin/internal/app/client.go
src/k8splugin/internal/app/client_test.go
src/k8splugin/internal/plugin/helpers.go [new file with mode: 0644]
src/k8splugin/mock_files/mock_plugins/mockplugin.go
src/k8splugin/plugins/generic/plugin.go
src/k8splugin/plugins/namespace/plugin.go
src/k8splugin/plugins/namespace/plugin_test.go
src/k8splugin/plugins/network/plugin.go
src/k8splugin/plugins/network/plugin_test.go
src/k8splugin/plugins/service/plugin.go
src/k8splugin/plugins/service/plugin_test.go

index cf605d2..0047e33 100644 (file)
@@ -129,6 +129,7 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
 github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
 github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
@@ -147,6 +148,7 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
 github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
 github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245 h1:DNVk+NIkGS0RbLkjQOLCJb/759yfCysThkMbl7EXxyY=
 github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245/go.mod h1:O1c8HleITsZqzNZDjSNzirUGsMT0oGu9LhHKoJrqO+A=
index 158d21d..9a5aa9e 100644 (file)
@@ -16,15 +16,16 @@ package app
 import (
        "log"
        "os"
-       "strings"
+       "time"
 
-       utils "k8splugin/internal"
        "k8splugin/internal/config"
        "k8splugin/internal/connection"
        "k8splugin/internal/helm"
+       "k8splugin/internal/plugin"
 
        pkgerrors "github.com/pkg/errors"
        "k8s.io/apimachinery/pkg/api/meta"
+       "k8s.io/apimachinery/pkg/runtime/schema"
        "k8s.io/client-go/discovery"
        "k8s.io/client-go/dynamic"
        "k8s.io/client-go/kubernetes"
@@ -32,16 +33,12 @@ import (
        "k8s.io/client-go/tools/clientcmd"
 )
 
-// PluginReference is the interface that is implemented
-type PluginReference interface {
-       Create(yamlFilePath string, namespace string, client *KubernetesClient) (string, error)
-       Delete(resource helm.KubernetesResource, namespace string, client *KubernetesClient) error
-}
-
+// KubernetesClient encapsulates the different clients' interfaces
+// we need when interacting with a Kubernetes cluster
 type KubernetesClient struct {
-       clientSet      *kubernetes.Clientset
+       clientSet      kubernetes.Interface
        dynamicClient  dynamic.Interface
-       discoverClient *discovery.DiscoveryClient
+       discoverClient discovery.CachedDiscoveryInterface
        restMapper     meta.RESTMapper
 }
 
@@ -86,40 +83,35 @@ func (k *KubernetesClient) init(cloudregion string) error {
                return pkgerrors.Wrap(err, "Creating dynamic client")
        }
 
-       k.discoverClient, err = discovery.NewDiscoveryClientForConfig(config)
+       k.discoverClient, err = discovery.NewCachedDiscoveryClientForConfig(config, os.TempDir(), "", 10*time.Minute)
        if err != nil {
                return pkgerrors.Wrap(err, "Creating discovery client")
        }
 
+       k.restMapper = restmapper.NewDeferredDiscoveryRESTMapper(k.discoverClient)
        return nil
 }
 
 func (k *KubernetesClient) ensureNamespace(namespace string) error {
-       namespacePlugin, ok := utils.LoadedPlugins["namespace"]
-       if !ok {
-               return pkgerrors.New("No plugin for namespace resource found")
-       }
 
-       symGetNamespaceFunc, err := namespacePlugin.Lookup("Get")
+       pluginImpl, err := plugin.GetPluginByKind("Namespace")
        if err != nil {
-               return pkgerrors.Wrap(err, "Error fetching get namespace function")
+               return pkgerrors.Wrap(err, "Loading Namespace Plugin")
        }
 
-       ns, _ := symGetNamespaceFunc.(func(string, string, kubernetes.Interface) (string, error))(
-               namespace, namespace, k.clientSet)
+       ns, err := pluginImpl.Get(helm.KubernetesResource{
+               Name: namespace,
+               GVK: schema.GroupVersionKind{
+                       Group:   "",
+                       Version: "v1",
+                       Kind:    "Namespace",
+               },
+       }, namespace, k)
 
        if ns == "" {
                log.Println("Creating " + namespace + " namespace")
-               symGetNamespaceFunc, err := namespacePlugin.Lookup("Create")
-               if err != nil {
-                       return pkgerrors.Wrap(err, "Error fetching create namespace plugin")
-               }
-               namespaceResource := &utils.ResourceData{
-                       Namespace: namespace,
-               }
 
-               _, err = symGetNamespaceFunc.(func(*utils.ResourceData, kubernetes.Interface) (string, error))(
-                       namespaceResource, k.clientSet)
+               _, err = pluginImpl.Create("", namespace, k)
                if err != nil {
                        return pkgerrors.Wrap(err, "Error creating "+namespace+" namespace")
                }
@@ -127,50 +119,6 @@ func (k *KubernetesClient) ensureNamespace(namespace string) error {
        return nil
 }
 
-func (k *KubernetesClient) createGeneric(resTempl helm.KubernetesResourceTemplate,
-       namespace string) (helm.KubernetesResource, error) {
-
-       log.Println("Processing Kind: " + resTempl.GVK.Kind)
-
-       //Check if have the mapper before loading the plugin
-       err := k.updateMapper()
-       if err != nil {
-               return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Unable to create RESTMapper")
-       }
-
-       pluginObject, ok := utils.LoadedPlugins["generic"]
-       if !ok {
-               return helm.KubernetesResource{}, pkgerrors.New("No generic plugin found")
-       }
-
-       symbol, err := pluginObject.Lookup("ExportedVariable")
-       if err != nil {
-               return helm.KubernetesResource{}, pkgerrors.Wrap(err, "No ExportedVariable symbol found")
-       }
-
-       //Assert if it implements the PluginReference interface
-       genericPlugin, ok := symbol.(PluginReference)
-       if !ok {
-               return helm.KubernetesResource{}, pkgerrors.New("ExportedVariable is not PluginReference type")
-       }
-
-       if _, err := os.Stat(resTempl.FilePath); os.IsNotExist(err) {
-               return helm.KubernetesResource{}, pkgerrors.New("File " + resTempl.FilePath + "does not exists")
-       }
-
-       log.Println("Processing file: " + resTempl.FilePath)
-
-       name, err := genericPlugin.Create(resTempl.FilePath, namespace, k)
-       if err != nil {
-               return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error in generic plugin")
-       }
-
-       return helm.KubernetesResource{
-               GVK:  resTempl.GVK,
-               Name: name,
-       }, nil
-}
-
 func (k *KubernetesClient) createKind(resTempl helm.KubernetesResourceTemplate,
        namespace string) (helm.KubernetesResource, error) {
 
@@ -182,28 +130,16 @@ func (k *KubernetesClient) createKind(resTempl helm.KubernetesResourceTemplate,
 
        log.Println("Processing file: " + resTempl.FilePath)
 
-       //Populate the namespace from profile instead of instance body
-       genericKubeData := &utils.ResourceData{
-               YamlFilePath: resTempl.FilePath,
-               Namespace:    namespace,
-       }
-
-       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)
-       }
-
-       symCreateResourceFunc, err := typePlugin.Lookup("Create")
+       pluginImpl, err := plugin.GetPluginByKind(resTempl.GVK.Kind)
        if err != nil {
-               return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error fetching "+resTempl.GVK.Kind+" plugin")
+               return helm.KubernetesResource{}, pkgerrors.Wrap(err, "Error loading plugin")
        }
 
-       createdResourceName, err := symCreateResourceFunc.(func(*utils.ResourceData, kubernetes.Interface) (string, error))(
-               genericKubeData, k.clientSet)
+       createdResourceName, err := pluginImpl.Create(resTempl.FilePath, namespace, k)
        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,
@@ -231,58 +167,18 @@ func (k *KubernetesClient) createResources(sortedTemplates []helm.KubernetesReso
        return createdResources, nil
 }
 
-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")
-       }
-
-       //Assert that it implements the PluginReference interface
-       genericPlugin, ok := symbol.(PluginReference)
-       if !ok {
-               return pkgerrors.New("ExportedVariable is not PluginReference type")
-       }
-
-       err = genericPlugin.Delete(resource, namespace, k)
-       if err != nil {
-               return pkgerrors.Wrap(err, "Error in generic plugin")
-       }
-
-       return nil
-}
-
 func (k *KubernetesClient) deleteKind(resource helm.KubernetesResource, namespace string) error {
        log.Println("Deleting Kind: " + resource.GVK.Kind)
 
-       typePlugin, ok := utils.LoadedPlugins[strings.ToLower(resource.GVK.Kind)]
-       if !ok {
-               log.Println("No plugin for kind " + resource.GVK.Kind + " found. Using generic Plugin")
-               return k.deleteGeneric(resource, namespace)
-       }
-
-       symDeleteResourceFunc, err := typePlugin.Lookup("Delete")
+       pluginImpl, err := plugin.GetPluginByKind(resource.GVK.Kind)
        if err != nil {
-               return pkgerrors.Wrap(err, "Error finding Delete symbol in plugin")
+               return pkgerrors.Wrap(err, "Error loading plugin")
        }
 
        log.Println("Deleting resource: " + resource.Name)
-       err = symDeleteResourceFunc.(func(string, string, kubernetes.Interface) error)(
-               resource.Name, namespace, k.clientSet)
+       err = pluginImpl.Delete(resource, namespace, k)
        if err != nil {
-               return pkgerrors.Wrap(err, "Error destroying "+resource.Name)
+               return pkgerrors.Wrap(err, "Error deleting "+resource.Name)
        }
 
        return nil
@@ -300,21 +196,6 @@ func (k *KubernetesClient) deleteResources(resources []helm.KubernetesResource,
        return nil
 }
 
-func (k *KubernetesClient) updateMapper() error {
-       //Create restMapper if not already done
-       if k.restMapper != nil {
-               return nil
-       }
-
-       groupResources, err := restmapper.GetAPIGroupResources(k.discoverClient)
-       if err != nil {
-               return pkgerrors.Wrap(err, "Get GroupResources")
-       }
-
-       k.restMapper = restmapper.NewDiscoveryRESTMapper(groupResources)
-       return nil
-}
-
 //GetMapper returns the RESTMapper that was created for this client
 func (k *KubernetesClient) GetMapper() meta.RESTMapper {
        return k.restMapper
@@ -325,3 +206,9 @@ func (k *KubernetesClient) GetMapper() meta.RESTMapper {
 func (k *KubernetesClient) GetDynamicClient() dynamic.Interface {
        return k.dynamicClient
 }
+
+// GetStandardClient returns the standard client that can be used to handle
+// standard kubernetes kinds
+func (k *KubernetesClient) GetStandardClient() kubernetes.Interface {
+       return k.clientSet
+}
index 4bfbcb1..e52aa7f 100644 (file)
@@ -42,7 +42,7 @@ func LoadMockPlugins(krdLoadedPlugins map[string]*plugin.Plugin) error {
        }
 
        krdLoadedPlugins["namespace"] = mockPlugin
-       krdLoadedPlugins["deployment"] = mockPlugin
+       krdLoadedPlugins["generic"] = mockPlugin
        krdLoadedPlugins["service"] = mockPlugin
 
        return nil
diff --git a/src/k8splugin/internal/plugin/helpers.go b/src/k8splugin/internal/plugin/helpers.go
new file mode 100644 (file)
index 0000000..efcd18c
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2019 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 plugin
+
+import (
+       "log"
+       "strings"
+
+       utils "k8splugin/internal"
+       "k8splugin/internal/helm"
+
+       pkgerrors "github.com/pkg/errors"
+       "k8s.io/apimachinery/pkg/api/meta"
+       "k8s.io/apimachinery/pkg/runtime/schema"
+       "k8s.io/client-go/dynamic"
+       "k8s.io/client-go/kubernetes"
+)
+
+// KubernetesConnector is an interface that is expected to be implemented
+// by any code that calls the plugin framework functions.
+// It implements methods that are needed by the plugins to get Kubernetes
+// clients and other information needed to interface with Kubernetes
+type KubernetesConnector interface {
+       //GetMapper returns the RESTMapper that was created for this client
+       GetMapper() meta.RESTMapper
+
+       //GetDynamicClient returns the dynamic client that is needed for
+       //unstructured REST calls to the apiserver
+       GetDynamicClient() dynamic.Interface
+
+       // GetStandardClient returns the standard client that can be used to handle
+       // standard kubernetes kinds
+       GetStandardClient() kubernetes.Interface
+}
+
+// Reference is the interface that is implemented
+type Reference interface {
+       //Create a kubernetes resource described by the yaml in yamlFilePath
+       Create(yamlFilePath string, namespace string, client KubernetesConnector) (string, error)
+
+       //Get a kubernetes resource based on the groupVersionKind and resourceName provided in resource
+       Get(resource helm.KubernetesResource, namespace string, client KubernetesConnector) (string, error)
+
+       //List all resources of the specified GroupVersionKind in the given namespace
+       //If gvk is empty, the plugin will return all supported objects in the namespace
+       List(gvk schema.GroupVersionKind, namespace string, client KubernetesConnector) ([]helm.KubernetesResource, error)
+
+       //Delete a kubernetes resource described in the provided namespace
+       Delete(resource helm.KubernetesResource, namespace string, client KubernetesConnector) error
+}
+
+// GetPluginByKind returns a plugin by the kind name
+// If plugin does not exist, it will return the generic plugin
+// TODO: Change this once we have a plugin registration mechanism
+func GetPluginByKind(kind string) (Reference, error) {
+
+       typePlugin, ok := utils.LoadedPlugins[strings.ToLower(kind)]
+       if !ok {
+               log.Println("No plugin for kind " + kind + " found. Using generic Plugin")
+               typePlugin, ok = utils.LoadedPlugins["generic"]
+               if !ok {
+                       return nil, pkgerrors.New("No generic plugin found")
+               }
+       }
+
+       symbol, err := typePlugin.Lookup("ExportedVariable")
+       if err != nil {
+               return nil, pkgerrors.Wrap(err, "No ExportedVariable symbol found")
+       }
+
+       //Assert if it implements the PluginReference interface
+       pluginImpl, ok := symbol.(Reference)
+       if !ok {
+               return nil, pkgerrors.New("ExportedVariable does not implement plugins.Reference interface type")
+       }
+
+       return pluginImpl, nil
+}
index bdc2130..0c3d246 100644 (file)
@@ -14,30 +14,43 @@ limitations under the License.
 package main
 
 import (
-       "k8s.io/client-go/kubernetes"
+       "k8splugin/internal/helm"
+       "k8splugin/internal/plugin"
 
-       utils "k8splugin/internal"
+       "k8s.io/apimachinery/pkg/runtime/schema"
 )
 
-func main() {}
+// ExportedVariable is what we will look for when calling the plugin
+var ExportedVariable mockPlugin
+
+type mockPlugin struct {
+}
 
 // Create object in a specific Kubernetes resource
-func Create(data *utils.ResourceData, client kubernetes.Interface) (string, error) {
+func (p mockPlugin) Create(yamlFilePath string, namespace string, client plugin.KubernetesConnector) (string, error) {
        return "resource-name", nil
 }
 
 // List of existing resources
-func List(namespace string, client kubernetes.Interface) ([]string, error) {
-       returnVal := []string{"resource-name-1", "resource-name-2"}
+func (p mockPlugin) List(gvk schema.GroupVersionKind, namespace string,
+       client plugin.KubernetesConnector) ([]helm.KubernetesResource, error) {
+       returnVal := []helm.KubernetesResource{
+               {
+                       Name: "resource-name-1",
+               },
+               {
+                       Name: "resource-name-2",
+               },
+       }
        return returnVal, nil
 }
 
 // Delete existing resources
-func Delete(name string, namespace string, client kubernetes.Interface) error {
+func (p mockPlugin) Delete(resource helm.KubernetesResource, namespace string, client plugin.KubernetesConnector) error {
        return nil
 }
 
 // Get existing resource host
-func Get(name string, namespace string, client kubernetes.Interface) (string, error) {
-       return name, nil
+func (p mockPlugin) Get(resource helm.KubernetesResource, namespace string, client plugin.KubernetesConnector) (string, error) {
+       return resource.Name, nil
 }
index 9ecaf68..31c65d0 100644 (file)
@@ -23,15 +23,18 @@ import (
        "k8s.io/apimachinery/pkg/runtime/schema"
 
        utils "k8splugin/internal"
-       "k8splugin/internal/app"
+       "k8splugin/internal/plugin"
        "k8splugin/internal/helm"
 )
 
+// ExportedVariable is what we will look for when calling the generic plugin
+var ExportedVariable genericPlugin
+
 type genericPlugin struct {
 }
 
 // Create deployment object in a specific Kubernetes cluster
-func (g genericPlugin) Create(yamlFilePath string, namespace string, client *app.KubernetesClient) (string, error) {
+func (g genericPlugin) Create(yamlFilePath string, namespace string, client plugin.KubernetesConnector) (string, error) {
        if namespace == "" {
                namespace = "default"
        }
@@ -72,15 +75,58 @@ func (g genericPlugin) Create(yamlFilePath string, namespace string, client *app
        return createdObj.GetName(), nil
 }
 
-// Delete an existing deployment hosted in a specific Kubernetes cluster
-func (g genericPlugin) Delete(resource helm.KubernetesResource, namespace string, client *app.KubernetesClient) error {
+// Get an existing resource hosted in a specific Kubernetes cluster
+func (g genericPlugin) Get(resource helm.KubernetesResource,
+       namespace string, client plugin.KubernetesConnector) (string, error) {
        if namespace == "" {
                namespace = "default"
        }
 
-       deletePolicy := metav1.DeletePropagationForeground
-       opts := &metav1.DeleteOptions{
-               PropagationPolicy: &deletePolicy,
+       dynClient := client.GetDynamicClient()
+       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)
+
+       opts := metav1.GetOptions{}
+       var unstruct *unstructured.Unstructured
+       switch mapping.Scope.Name() {
+       case meta.RESTScopeNameNamespace:
+               unstruct, err = dynClient.Resource(gvr).Namespace(namespace).Get(resource.Name, opts)
+       case meta.RESTScopeNameRoot:
+               unstruct, err = dynClient.Resource(gvr).Get(resource.Name, opts)
+       default:
+               return "", pkgerrors.New("Got an unknown RESTSCopeName for mapping: " + resource.GVK.String())
+       }
+
+       if err != nil {
+               return "", pkgerrors.Wrap(err, "Delete object error")
+       }
+
+       return unstruct.GetName(), nil
+}
+
+// List all existing resources of the GroupVersionKind
+// TODO: Implement in seperate patch
+func (g genericPlugin) List(gvk schema.GroupVersionKind, namespace string,
+       client plugin.KubernetesConnector) ([]helm.KubernetesResource, error) {
+
+       var returnData []helm.KubernetesResource
+       return returnData, nil
+}
+
+// Delete an existing resource hosted in a specific Kubernetes cluster
+func (g genericPlugin) Delete(resource helm.KubernetesResource, namespace string, client plugin.KubernetesConnector) error {
+       if namespace == "" {
+               namespace = "default"
        }
 
        dynClient := client.GetDynamicClient()
@@ -97,6 +143,11 @@ func (g genericPlugin) Delete(resource helm.KubernetesResource, namespace string
        gvr := mapping.Resource
        log.Printf("Using gvr: %s, %s, %s", gvr.Group, gvr.Version, gvr.Resource)
 
+       deletePolicy := metav1.DeletePropagationForeground
+       opts := &metav1.DeleteOptions{
+               PropagationPolicy: &deletePolicy,
+       }
+
        switch mapping.Scope.Name() {
        case meta.RESTScopeNameNamespace:
                err = dynClient.Resource(gvr).Namespace(namespace).Delete(resource.Name, opts)
@@ -111,6 +162,3 @@ func (g genericPlugin) Delete(resource helm.KubernetesResource, namespace string
        }
        return nil
 }
-
-// ExportedVariable is what we will look for when calling the generic plugin
-var ExportedVariable genericPlugin
index 6f82391..2d5d2ab 100644 (file)
@@ -16,39 +16,42 @@ package main
 import (
        "log"
 
-       "k8s.io/client-go/kubernetes"
-
        pkgerrors "github.com/pkg/errors"
-
        coreV1 "k8s.io/api/core/v1"
        metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/runtime/schema"
 
        utils "k8splugin/internal"
+       "k8splugin/internal/helm"
+       "k8splugin/internal/plugin"
 )
 
+// ExportedVariable is what we will look for when calling the plugin
+var ExportedVariable namespacePlugin
+
+type namespacePlugin struct {
+}
+
 // Create a namespace object in a specific Kubernetes cluster
-func Create(data *utils.ResourceData, client kubernetes.Interface) (string, error) {
-       namespace := &coreV1.Namespace{
+func (p namespacePlugin) Create(yamlFilePath string, namespace string, client plugin.KubernetesConnector) (string, error) {
+       namespaceObj := &coreV1.Namespace{
                ObjectMeta: metaV1.ObjectMeta{
-                       Name: data.Namespace,
+                       Name: namespace,
                },
        }
-       _, err := client.CoreV1().Namespaces().Create(namespace)
+       _, err := client.GetStandardClient().CoreV1().Namespaces().Create(namespaceObj)
        if err != nil {
                return "", pkgerrors.Wrap(err, "Create Namespace error")
        }
-       log.Printf("Namespace (%s) created", data.Namespace)
+       log.Printf("Namespace (%s) created", namespace)
 
-       return data.Namespace, nil
+       return namespace, nil
 }
 
 // Get an existing namespace hosted in a specific Kubernetes cluster
-func Get(name string, namespace string, client kubernetes.Interface) (string, error) {
+func (p namespacePlugin) Get(resource helm.KubernetesResource, namespace string, client plugin.KubernetesConnector) (string, error) {
        opts := metaV1.GetOptions{}
-       opts.APIVersion = "apps/v1"
-       opts.Kind = "Deployment"
-
-       ns, err := client.CoreV1().Namespaces().Get(name, opts)
+       ns, err := client.GetStandardClient().CoreV1().Namespaces().Get(resource.Name, opts)
        if err != nil {
                return "", pkgerrors.Wrap(err, "Get Namespace error")
        }
@@ -57,14 +60,14 @@ func Get(name string, namespace string, client kubernetes.Interface) (string, er
 }
 
 // Delete an existing namespace hosted in a specific Kubernetes cluster
-func Delete(name string, namespace string, client kubernetes.Interface) error {
+func (p namespacePlugin) Delete(resource helm.KubernetesResource, namespace string, client plugin.KubernetesConnector) error {
        deletePolicy := metaV1.DeletePropagationForeground
        opts := &metaV1.DeleteOptions{
                PropagationPolicy: &deletePolicy,
        }
 
-       log.Println("Deleting namespace: " + name)
-       if err := client.CoreV1().Namespaces().Delete(name, opts); err != nil {
+       log.Println("Deleting namespace: " + resource.Name)
+       if err := client.GetStandardClient().CoreV1().Namespaces().Delete(resource.Name, opts); err != nil {
                return pkgerrors.Wrap(err, "Delete namespace error")
        }
 
@@ -72,23 +75,30 @@ func Delete(name string, namespace string, client kubernetes.Interface) error {
 }
 
 // List of existing namespaces hosted in a specific Kubernetes cluster
-func List(namespace string, client kubernetes.Interface) ([]string, error) {
+// This plugin ignores both gvk and namespace arguments
+func (p namespacePlugin) List(gvk schema.GroupVersionKind, namespace string, client plugin.KubernetesConnector) ([]helm.KubernetesResource, error) {
        opts := metaV1.ListOptions{
                Limit: utils.ResourcesListLimit,
        }
-       opts.APIVersion = "apps/v1"
-       opts.Kind = "Namespace"
 
-       list, err := client.CoreV1().Namespaces().List(opts)
+       list, err := client.GetStandardClient().CoreV1().Namespaces().List(opts)
        if err != nil {
                return nil, pkgerrors.Wrap(err, "Get Namespace list error")
        }
 
-       result := make([]string, 0, utils.ResourcesListLimit)
+       result := make([]helm.KubernetesResource, 0, utils.ResourcesListLimit)
        if list != nil {
-               for _, deployment := range list.Items {
-                       log.Printf("%v", deployment.Name)
-                       result = append(result, deployment.Name)
+               for _, ns := range list.Items {
+                       log.Printf("%v", ns.Name)
+                       result = append(result,
+                               helm.KubernetesResource{
+                                       GVK: schema.GroupVersionKind{
+                                               Group:   "",
+                                               Version: "v1",
+                                               Kind:    "Namespace",
+                                       },
+                                       Name: ns.Name,
+                               })
                }
        }
 
index 0019df1..9e57b97 100644 (file)
@@ -18,36 +18,54 @@ import (
        "strings"
        "testing"
 
-       utils "k8splugin/internal"
+       "k8splugin/internal/helm"
 
        coreV1 "k8s.io/api/core/v1"
+       "k8s.io/apimachinery/pkg/api/meta"
        metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-       testclient "k8s.io/client-go/kubernetes/fake"
+       "k8s.io/apimachinery/pkg/runtime"
+       "k8s.io/apimachinery/pkg/runtime/schema"
+       "k8s.io/client-go/dynamic"
+       "k8s.io/client-go/kubernetes"
+       "k8s.io/client-go/kubernetes/fake"
 )
 
+type TestKubernetesConnector struct {
+       object runtime.Object
+}
+
+func (t TestKubernetesConnector) GetMapper() meta.RESTMapper {
+       return nil
+}
+
+func (t TestKubernetesConnector) GetDynamicClient() dynamic.Interface {
+       return nil
+}
+
+func (t TestKubernetesConnector) GetStandardClient() kubernetes.Interface {
+       return fake.NewSimpleClientset(t.object)
+}
+
 func TestCreateNamespace(t *testing.T) {
-       namespace := "test1"
        testCases := []struct {
                label          string
-               input          *utils.ResourceData
-               clientOutput   *coreV1.Namespace
+               input          string
+               object         *coreV1.Namespace
                expectedResult string
                expectedError  string
        }{
                {
-                       label: "Successfully create a namespace",
-                       input: &utils.ResourceData{
-                               Namespace: namespace,
-                       },
-                       clientOutput:   &coreV1.Namespace{},
-                       expectedResult: namespace,
+                       label:          "Successfully create a namespace",
+                       input:          "test1",
+                       object:         &coreV1.Namespace{},
+                       expectedResult: "test1",
                },
        }
 
        for _, testCase := range testCases {
-               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               client := TestKubernetesConnector{testCase.object}
                t.Run(testCase.label, func(t *testing.T) {
-                       result, err := Create(testCase.input, client)
+                       result, err := namespacePlugin{}.Create("", testCase.input, client)
                        if err != nil {
                                if testCase.expectedError == "" {
                                        t.Fatalf("Create method return an un-expected (%s)", err)
@@ -72,39 +90,47 @@ func TestCreateNamespace(t *testing.T) {
 }
 
 func TestListNamespace(t *testing.T) {
-       namespace := "test1"
        testCases := []struct {
                label          string
                input          string
-               clientOutput   *coreV1.NamespaceList
-               expectedResult []string
+               object         *coreV1.NamespaceList
+               expectedResult []helm.KubernetesResource
        }{
                {
                        label:          "Sucessfully to display an empty namespace list",
-                       input:          namespace,
-                       clientOutput:   &coreV1.NamespaceList{},
-                       expectedResult: []string{},
+                       input:          "",
+                       object:         &coreV1.NamespaceList{},
+                       expectedResult: []helm.KubernetesResource{},
                },
                {
                        label: "Sucessfully to display a list of existing namespaces",
-                       input: namespace,
-                       clientOutput: &coreV1.NamespaceList{
+                       input: "test1",
+                       object: &coreV1.NamespaceList{
                                Items: []coreV1.Namespace{
                                        coreV1.Namespace{
                                                ObjectMeta: metaV1.ObjectMeta{
-                                                       Name: namespace,
+                                                       Name: "test1",
                                                },
                                        },
                                },
                        },
-                       expectedResult: []string{namespace},
+                       expectedResult: []helm.KubernetesResource{
+                               {
+                                       Name: "test1",
+                                       GVK:  schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"},
+                               },
+                       },
                },
        }
 
        for _, testCase := range testCases {
-               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               client := TestKubernetesConnector{testCase.object}
                t.Run(testCase.label, func(t *testing.T) {
-                       result, err := List(testCase.input, client)
+                       result, err := namespacePlugin{}.List(schema.GroupVersionKind{
+                               Group:   "",
+                               Version: "v1",
+                               Kind:    "Namespace",
+                       }, testCase.input, client)
                        if err != nil {
                                t.Fatalf("List method returned an error (%s)", err)
                        } else {
@@ -113,7 +139,7 @@ func TestListNamespace(t *testing.T) {
                                }
                                if !reflect.DeepEqual(testCase.expectedResult, result) {
 
-                                       t.Fatalf("List method returned: \n%v\n and it was expected: \n%v", result, testCase.expectedResult)
+                                       t.Fatalf("List method returned: \n%+v\n and it was expected: \n%+v", result, testCase.expectedResult)
                                }
                        }
                })
@@ -122,14 +148,14 @@ func TestListNamespace(t *testing.T) {
 
 func TestDeleteNamespace(t *testing.T) {
        testCases := []struct {
-               label        string
-               input        map[string]string
-               clientOutput *coreV1.Namespace
+               label  string
+               input  map[string]string
+               object *coreV1.Namespace
        }{
                {
                        label: "Sucessfully to delete an existing namespace",
                        input: map[string]string{"name": "test-name", "namespace": "test-namespace"},
-                       clientOutput: &coreV1.Namespace{
+                       object: &coreV1.Namespace{
                                ObjectMeta: metaV1.ObjectMeta{
                                        Name: "test-name",
                                },
@@ -138,9 +164,12 @@ func TestDeleteNamespace(t *testing.T) {
        }
 
        for _, testCase := range testCases {
-               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               client := TestKubernetesConnector{testCase.object}
                t.Run(testCase.label, func(t *testing.T) {
-                       err := Delete(testCase.input["name"], testCase.input["namespace"], client)
+                       err := namespacePlugin{}.Delete(helm.KubernetesResource{
+                               GVK:  schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"},
+                               Name: testCase.input["name"],
+                       }, testCase.input["namespace"], client)
                        if err != nil {
                                t.Fatalf("Delete method returned an error (%s)", err)
                        }
@@ -152,14 +181,14 @@ func TestGetNamespace(t *testing.T) {
        testCases := []struct {
                label          string
                input          map[string]string
-               clientOutput   *coreV1.Namespace
+               object         *coreV1.Namespace
                expectedResult string
                expectedError  string
        }{
                {
                        label: "Sucessfully to get an existing namespace",
                        input: map[string]string{"name": "test-name", "namespace": "test-namespace"},
-                       clientOutput: &coreV1.Namespace{
+                       object: &coreV1.Namespace{
                                ObjectMeta: metaV1.ObjectMeta{
                                        Name: "test-name",
                                },
@@ -169,7 +198,7 @@ func TestGetNamespace(t *testing.T) {
                {
                        label: "Fail to get an non-existing namespace",
                        input: map[string]string{"name": "test-name", "namespace": "test-namespace"},
-                       clientOutput: &coreV1.Namespace{
+                       object: &coreV1.Namespace{
                                ObjectMeta: metaV1.ObjectMeta{
                                        Name: "test-name2",
                                },
@@ -179,9 +208,12 @@ func TestGetNamespace(t *testing.T) {
        }
 
        for _, testCase := range testCases {
-               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               client := TestKubernetesConnector{testCase.object}
                t.Run(testCase.label, func(t *testing.T) {
-                       result, err := Get(testCase.input["name"], testCase.input["namespace"], client)
+                       result, err := namespacePlugin{}.Get(helm.KubernetesResource{
+                               GVK:  schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"},
+                               Name: testCase.input["name"],
+                       }, testCase.input["namespace"], client)
                        if err != nil {
                                if testCase.expectedError == "" {
                                        t.Fatalf("Get method return an un-expected (%s)", err)
index 74ac347..5cc57e8 100644 (file)
@@ -14,31 +14,38 @@ limitations under the License.
 package main
 
 import (
-       "k8splugin/plugins/network/v1"
+       v1 "k8splugin/plugins/network/v1"
        "regexp"
 
        utils "k8splugin/internal"
+       "k8splugin/internal/app"
+       "k8splugin/internal/helm"
 
        pkgerrors "github.com/pkg/errors"
-       "k8s.io/client-go/kubernetes"
+       "k8s.io/apimachinery/pkg/runtime/schema"
 )
 
-func extractData(data string) (vnfID, cniType, networkName string) {
+// ExportedVariable is what we will look for when calling the plugin
+var ExportedVariable networkPlugin
+
+type networkPlugin struct {
+}
+
+func extractData(data string) (cniType, networkName string) {
        re := regexp.MustCompile("_")
        split := re.Split(data, -1)
        if len(split) != 3 {
                return
        }
-       vnfID = split[0]
        cniType = split[1]
        networkName = split[2]
        return
 }
 
 // Create an ONAP Network object
-func Create(data *utils.ResourceData, client kubernetes.Interface) (string, error) {
+func (p networkPlugin) Create(yamlFilePath string, namespace string, client *app.KubernetesClient) (string, error) {
        network := &v1.OnapNetwork{}
-       if _, err := utils.DecodeYAML(data.YamlFilePath, network); err != nil {
+       if _, err := utils.DecodeYAML(yamlFilePath, network); err != nil {
                return "", pkgerrors.Wrap(err, "Decode network object error")
        }
 
@@ -58,17 +65,24 @@ func Create(data *utils.ResourceData, client kubernetes.Interface) (string, erro
                return "", pkgerrors.Wrap(err, "Error during the creation for "+cniType+" plugin")
        }
 
-       return data.VnfId + "_" + cniType + "_" + name, nil
+       return cniType + "_" + name, nil
+}
+
+// Get a Network
+func (p networkPlugin) Get(resource helm.KubernetesResource, namespace string, client *app.KubernetesClient) (string, error) {
+       return "", nil
 }
 
 // List of Networks
-func List(namespace string, kubeclient kubernetes.Interface) ([]string, error) {
+func (p networkPlugin) List(gvk schema.GroupVersionKind, namespace string,
+       client *app.KubernetesClient) ([]helm.KubernetesResource, error) {
+
        return nil, nil
 }
 
 // Delete an existing Network
-func Delete(name string, namespace string, kubeclient kubernetes.Interface) error {
-       _, cniType, networkName := extractData(name)
+func (p networkPlugin) Delete(resource helm.KubernetesResource, namespace string, client *app.KubernetesClient) error {
+       cniType, networkName := extractData(resource.Name)
        typePlugin, ok := utils.LoadedPlugins[cniType+"-network"]
        if !ok {
                return pkgerrors.New("No plugin for resource " + cniType + " found")
@@ -85,8 +99,3 @@ func Delete(name string, namespace string, kubeclient kubernetes.Interface) erro
 
        return nil
 }
-
-// Get an existing Network
-func Get(name string, namespace string, kubeclient kubernetes.Interface) (string, error) {
-       return "", nil
-}
index e8e113b..5a8ce4d 100644 (file)
@@ -15,6 +15,7 @@ package main
 
 import (
        utils "k8splugin/internal"
+       "k8splugin/internal/helm"
        "os"
        "plugin"
        "reflect"
@@ -22,6 +23,7 @@ import (
        "testing"
 
        pkgerrors "github.com/pkg/errors"
+       "k8s.io/apimachinery/pkg/runtime/schema"
 )
 
 func LoadMockNetworkPlugins(krdLoadedPlugins *map[string]*plugin.Plugin, networkName, errMsg string) error {
@@ -51,7 +53,6 @@ func LoadMockNetworkPlugins(krdLoadedPlugins *map[string]*plugin.Plugin, network
 }
 
 func TestCreateNetwork(t *testing.T) {
-       internalVNFID := "1"
        oldkrdPluginData := utils.LoadedPlugins
 
        defer func() {
@@ -60,34 +61,27 @@ func TestCreateNetwork(t *testing.T) {
 
        testCases := []struct {
                label          string
-               input          *utils.ResourceData
+               input          string
                mockError      string
                mockOutput     string
                expectedResult string
                expectedError  string
        }{
                {
-                       label: "Fail to decode a network object",
-                       input: &utils.ResourceData{
-                               YamlFilePath: "../../mock_files/mock_yamls/service.yaml",
-                       },
+                       label:         "Fail to decode a network object",
+                       input:         "../../mock_files/mock_yamls/service.yaml",
                        expectedError: "No plugin for resource",
                },
                {
-                       label: "Fail to create a network",
-                       input: &utils.ResourceData{
-                               YamlFilePath: "../../mock_files/mock_yamls/ovn4nfvk8s.yaml",
-                       },
+                       label:         "Fail to create a network",
+                       input:         "../../mock_files/mock_yamls/ovn4nfvk8s.yaml",
                        mockError:     "Internal error",
                        expectedError: "Error during the creation for ovn4nfvk8s plugin: Internal error",
                },
                {
-                       label: "Successfully create a ovn4nfv network",
-                       input: &utils.ResourceData{
-                               VnfId:        internalVNFID,
-                               YamlFilePath: "../../mock_files/mock_yamls/ovn4nfvk8s.yaml",
-                       },
-                       expectedResult: internalVNFID + "_ovn4nfvk8s_myNetwork",
+                       label:          "Successfully create a ovn4nfv network",
+                       input:          "../../mock_files/mock_yamls/ovn4nfvk8s.yaml",
+                       expectedResult: "ovn4nfvk8s_myNetwork",
                        mockOutput:     "myNetwork",
                },
        }
@@ -98,7 +92,7 @@ func TestCreateNetwork(t *testing.T) {
                        if err != nil {
                                t.Fatalf("TestCreateNetwork returned an error (%s)", err)
                        }
-                       result, err := Create(testCase.input, nil)
+                       result, err := networkPlugin{}.Create(testCase.input, "", nil)
                        if err != nil {
                                if testCase.expectedError == "" {
                                        t.Fatalf("Create method return an un-expected (%s)", err)
@@ -157,7 +151,10 @@ func TestDeleteNetwork(t *testing.T) {
                        if err != nil {
                                t.Fatalf("TestDeleteNetwork returned an error (%s)", err)
                        }
-                       err = Delete(testCase.input, "", nil)
+                       err = networkPlugin{}.Delete(helm.KubernetesResource{
+                               GVK:  schema.GroupVersionKind{Group: "", Version: "", Kind: "Network"},
+                               Name: testCase.input,
+                       }, "", nil)
                        if err != nil {
                                if testCase.expectedError == "" {
                                        t.Fatalf("Create method return an un-expected (%s)", err)
index ea5aeca..2957c44 100644 (file)
@@ -16,23 +16,29 @@ package main
 import (
        "log"
 
-       "k8s.io/client-go/kubernetes"
-
        pkgerrors "github.com/pkg/errors"
-
        coreV1 "k8s.io/api/core/v1"
        metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+       "k8s.io/apimachinery/pkg/runtime/schema"
 
        utils "k8splugin/internal"
+       "k8splugin/internal/helm"
+       "k8splugin/internal/plugin"
 )
 
+// ExportedVariable is what we will look for when calling the plugin
+var ExportedVariable servicePlugin
+
+type servicePlugin struct {
+}
+
 // Create a service object in a specific Kubernetes cluster
-func Create(data *utils.ResourceData, client kubernetes.Interface) (string, error) {
-       namespace := data.Namespace
+func (p servicePlugin) Create(yamlFilePath string, namespace string, client plugin.KubernetesConnector) (string, error) {
        if namespace == "" {
                namespace = "default"
        }
-       obj, err := utils.DecodeYAML(data.YamlFilePath, nil)
+
+       obj, err := utils.DecodeYAML(yamlFilePath, nil)
        if err != nil {
                return "", pkgerrors.Wrap(err, "Decode service object error")
        }
@@ -43,7 +49,7 @@ func Create(data *utils.ResourceData, client kubernetes.Interface) (string, erro
        }
        service.Namespace = namespace
 
-       result, err := client.CoreV1().Services(namespace).Create(service)
+       result, err := client.GetStandardClient().CoreV1().Services(namespace).Create(service)
        if err != nil {
                return "", pkgerrors.Wrap(err, "Create Service error")
        }
@@ -52,7 +58,8 @@ func Create(data *utils.ResourceData, client kubernetes.Interface) (string, erro
 }
 
 // List of existing services hosted in a specific Kubernetes cluster
-func List(namespace string, kubeclient kubernetes.Interface) ([]string, error) {
+// gvk parameter is not used as this plugin is specific to services only
+func (p servicePlugin) List(gvk schema.GroupVersionKind, namespace string, client plugin.KubernetesConnector) ([]helm.KubernetesResource, error) {
        if namespace == "" {
                namespace = "default"
        }
@@ -60,19 +67,25 @@ func List(namespace string, kubeclient kubernetes.Interface) ([]string, error) {
        opts := metaV1.ListOptions{
                Limit: utils.ResourcesListLimit,
        }
-       opts.APIVersion = "apps/v1"
-       opts.Kind = "Service"
 
-       list, err := kubeclient.CoreV1().Services(namespace).List(opts)
+       list, err := client.GetStandardClient().CoreV1().Services(namespace).List(opts)
        if err != nil {
                return nil, pkgerrors.Wrap(err, "Get Service list error")
        }
 
-       result := make([]string, 0, utils.ResourcesListLimit)
+       result := make([]helm.KubernetesResource, 0, utils.ResourcesListLimit)
        if list != nil {
-               for _, deployment := range list.Items {
-                       log.Printf("%v", deployment.Name)
-                       result = append(result, deployment.Name)
+               for _, service := range list.Items {
+                       log.Printf("%v", service.Name)
+                       result = append(result,
+                               helm.KubernetesResource{
+                                       GVK: schema.GroupVersionKind{
+                                               Group:   "",
+                                               Version: "v1",
+                                               Kind:    "Service",
+                                       },
+                                       Name: service.GetName(),
+                               })
                }
        }
 
@@ -80,7 +93,7 @@ func List(namespace string, kubeclient kubernetes.Interface) ([]string, error) {
 }
 
 // Delete an existing service hosted in a specific Kubernetes cluster
-func Delete(name string, namespace string, kubeclient kubernetes.Interface) error {
+func (p servicePlugin) Delete(resource helm.KubernetesResource, namespace string, client plugin.KubernetesConnector) error {
        if namespace == "" {
                namespace = "default"
        }
@@ -90,8 +103,8 @@ func Delete(name string, namespace string, kubeclient kubernetes.Interface) erro
                PropagationPolicy: &deletePolicy,
        }
 
-       log.Println("Deleting service: " + name)
-       if err := kubeclient.CoreV1().Services(namespace).Delete(name, opts); err != nil {
+       log.Println("Deleting service: " + resource.Name)
+       if err := client.GetStandardClient().CoreV1().Services(namespace).Delete(resource.Name, opts); err != nil {
                return pkgerrors.Wrap(err, "Delete service error")
        }
 
@@ -99,18 +112,15 @@ func Delete(name string, namespace string, kubeclient kubernetes.Interface) erro
 }
 
 // Get an existing service hosted in a specific Kubernetes cluster
-func Get(name string, namespace string, kubeclient kubernetes.Interface) (string, error) {
+func (p servicePlugin) Get(resource helm.KubernetesResource, namespace string, client plugin.KubernetesConnector) (string, error) {
        if namespace == "" {
                namespace = "default"
        }
 
        opts := metaV1.GetOptions{}
-       opts.APIVersion = "apps/v1"
-       opts.Kind = "Service"
-
-       service, err := kubeclient.CoreV1().Services(namespace).Get(name, opts)
+       service, err := client.GetStandardClient().CoreV1().Services(namespace).Get(resource.Name, opts)
        if err != nil {
-               return "", pkgerrors.Wrap(err, "Get Deployment error")
+               return "", pkgerrors.Wrap(err, "Get Service error")
        }
 
        return service.Name, nil
index d361486..6670308 100644 (file)
@@ -14,54 +14,67 @@ limitations under the License.
 package main
 
 import (
+       "k8splugin/internal/helm"
        "reflect"
        "strings"
        "testing"
 
-       utils "k8splugin/internal"
-
        coreV1 "k8s.io/api/core/v1"
+       "k8s.io/apimachinery/pkg/api/meta"
        metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-       testclient "k8s.io/client-go/kubernetes/fake"
+       "k8s.io/apimachinery/pkg/runtime"
+       "k8s.io/apimachinery/pkg/runtime/schema"
+       "k8s.io/client-go/dynamic"
+       "k8s.io/client-go/kubernetes"
+       "k8s.io/client-go/kubernetes/fake"
 )
 
+type TestKubernetesConnector struct {
+       object runtime.Object
+}
+
+func (t TestKubernetesConnector) GetMapper() meta.RESTMapper {
+       return nil
+}
+
+func (t TestKubernetesConnector) GetDynamicClient() dynamic.Interface {
+       return nil
+}
+
+func (t TestKubernetesConnector) GetStandardClient() kubernetes.Interface {
+       return fake.NewSimpleClientset(t.object)
+}
+
 func TestCreateService(t *testing.T) {
-       namespace := "test1"
        name := "mock-service"
        testCases := []struct {
                label          string
-               input          *utils.ResourceData
-               clientOutput   *coreV1.Service
+               input          string
+               namespace      string
+               object         *coreV1.Service
                expectedResult string
                expectedError  string
        }{
                {
-                       label: "Fail to create a service with invalid type",
-                       input: &utils.ResourceData{
-                               YamlFilePath: "../../mock_files/mock_yamls/deployment.yaml",
-                       },
-                       clientOutput:  &coreV1.Service{},
+                       label:         "Fail to create a service with invalid type",
+                       input:         "../../mock_files/mock_yamls/deployment.yaml",
+                       namespace:     "test1",
+                       object:        &coreV1.Service{},
                        expectedError: "contains another resource different than Service",
                },
                {
-                       label: "Successfully create a service",
-                       input: &utils.ResourceData{
-                               YamlFilePath: "../../mock_files/mock_yamls/service.yaml",
-                       },
-                       clientOutput: &coreV1.Service{
-                               ObjectMeta: metaV1.ObjectMeta{
-                                       Name:      name,
-                                       Namespace: namespace,
-                               },
-                       },
+                       label:          "Successfully create a service",
+                       input:          "../../mock_files/mock_yamls/service.yaml",
+                       namespace:      "test1",
+                       object:         &coreV1.Service{},
                        expectedResult: name,
                },
        }
 
        for _, testCase := range testCases {
-               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               client := TestKubernetesConnector{testCase.object}
                t.Run(testCase.label, func(t *testing.T) {
-                       result, err := Create(testCase.input, client)
+                       result, err := servicePlugin{}.Create(testCase.input, testCase.namespace, client)
                        if err != nil {
                                if testCase.expectedError == "" {
                                        t.Fatalf("Create method return an un-expected (%s)", err)
@@ -86,38 +99,42 @@ func TestCreateService(t *testing.T) {
 }
 
 func TestListService(t *testing.T) {
-       namespace := "test1"
        testCases := []struct {
                label          string
-               input          string
-               clientOutput   *coreV1.ServiceList
-               expectedResult []string
+               namespace      string
+               object         *coreV1.ServiceList
+               expectedResult []helm.KubernetesResource
        }{
                {
                        label:          "Sucessfully to display an empty service list",
-                       input:          namespace,
-                       clientOutput:   &coreV1.ServiceList{},
-                       expectedResult: []string{},
+                       namespace:      "test1",
+                       object:         &coreV1.ServiceList{},
+                       expectedResult: []helm.KubernetesResource{},
                },
                {
-                       label: "Sucessfully to display a list of existing services",
-                       input: namespace,
-                       clientOutput: &coreV1.ServiceList{
+                       label:     "Sucessfully to display a list of existing services",
+                       namespace: "test1",
+                       object: &coreV1.ServiceList{
                                Items: []coreV1.Service{
                                        coreV1.Service{
                                                ObjectMeta: metaV1.ObjectMeta{
                                                        Name:      "test",
-                                                       Namespace: namespace,
+                                                       Namespace: "test1",
                                                },
                                        },
                                },
                        },
-                       expectedResult: []string{"test"},
+                       expectedResult: []helm.KubernetesResource{
+                               {
+                                       Name: "test",
+                                       GVK:  schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"},
+                               },
+                       },
                },
                {
-                       label: "Sucessfully display a list of existing services in default namespace",
-                       input: "",
-                       clientOutput: &coreV1.ServiceList{
+                       label:     "Sucessfully display a list of existing services in default namespace",
+                       namespace: "",
+                       object: &coreV1.ServiceList{
                                Items: []coreV1.Service{
                                        coreV1.Service{
                                                ObjectMeta: metaV1.ObjectMeta{
@@ -128,19 +145,27 @@ func TestListService(t *testing.T) {
                                        coreV1.Service{
                                                ObjectMeta: metaV1.ObjectMeta{
                                                        Name:      "test2",
-                                                       Namespace: namespace,
+                                                       Namespace: "test1",
                                                },
                                        },
                                },
                        },
-                       expectedResult: []string{"test"},
+                       expectedResult: []helm.KubernetesResource{
+                               {
+                                       Name: "test",
+                                       GVK:  schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"},
+                               },
+                       },
                },
        }
 
        for _, testCase := range testCases {
-               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               client := TestKubernetesConnector{testCase.object}
                t.Run(testCase.label, func(t *testing.T) {
-                       result, err := List(testCase.input, client)
+                       result, err := servicePlugin{}.List(schema.GroupVersionKind{
+                               Group:   "",
+                               Version: "v1",
+                               Kind:    "Service"}, testCase.namespace, client)
                        if err != nil {
                                t.Fatalf("List method returned an error (%s)", err)
                        } else {
@@ -158,14 +183,14 @@ func TestListService(t *testing.T) {
 
 func TestDeleteService(t *testing.T) {
        testCases := []struct {
-               label        string
-               input        map[string]string
-               clientOutput *coreV1.Service
+               label  string
+               input  map[string]string
+               object *coreV1.Service
        }{
                {
                        label: "Sucessfully to delete an existing service",
                        input: map[string]string{"name": "test-service", "namespace": "test-namespace"},
-                       clientOutput: &coreV1.Service{
+                       object: &coreV1.Service{
                                ObjectMeta: metaV1.ObjectMeta{
                                        Name:      "test-service",
                                        Namespace: "test-namespace",
@@ -175,7 +200,7 @@ func TestDeleteService(t *testing.T) {
                {
                        label: "Sucessfully delete an existing service in default namespace",
                        input: map[string]string{"name": "test-service", "namespace": ""},
-                       clientOutput: &coreV1.Service{
+                       object: &coreV1.Service{
                                ObjectMeta: metaV1.ObjectMeta{
                                        Name:      "test-service",
                                        Namespace: "default",
@@ -185,9 +210,12 @@ func TestDeleteService(t *testing.T) {
        }
 
        for _, testCase := range testCases {
-               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               client := TestKubernetesConnector{testCase.object}
                t.Run(testCase.label, func(t *testing.T) {
-                       err := Delete(testCase.input["name"], testCase.input["namespace"], client)
+                       err := servicePlugin{}.Delete(helm.KubernetesResource{
+                               GVK:  schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"},
+                               Name: testCase.input["name"],
+                       }, testCase.input["namespace"], client)
                        if err != nil {
                                t.Fatalf("Delete method returned an error (%s)", err)
                        }
@@ -199,14 +227,14 @@ func TestGetService(t *testing.T) {
        testCases := []struct {
                label          string
                input          map[string]string
-               clientOutput   *coreV1.Service
+               object         *coreV1.Service
                expectedResult string
                expectedError  string
        }{
                {
                        label: "Sucessfully to get an existing service",
                        input: map[string]string{"name": "test-service", "namespace": "test-namespace"},
-                       clientOutput: &coreV1.Service{
+                       object: &coreV1.Service{
                                ObjectMeta: metaV1.ObjectMeta{
                                        Name:      "test-service",
                                        Namespace: "test-namespace",
@@ -217,7 +245,7 @@ func TestGetService(t *testing.T) {
                {
                        label: "Sucessfully get an existing service from default namespaces",
                        input: map[string]string{"name": "test-service", "namespace": ""},
-                       clientOutput: &coreV1.Service{
+                       object: &coreV1.Service{
                                ObjectMeta: metaV1.ObjectMeta{
                                        Name:      "test-service",
                                        Namespace: "default",
@@ -228,7 +256,7 @@ func TestGetService(t *testing.T) {
                {
                        label: "Fail to get an non-existing namespace",
                        input: map[string]string{"name": "test-name", "namespace": "test-namespace"},
-                       clientOutput: &coreV1.Service{
+                       object: &coreV1.Service{
                                ObjectMeta: metaV1.ObjectMeta{
                                        Name:      "test-service",
                                        Namespace: "default",
@@ -239,9 +267,12 @@ func TestGetService(t *testing.T) {
        }
 
        for _, testCase := range testCases {
-               client := testclient.NewSimpleClientset(testCase.clientOutput)
+               client := TestKubernetesConnector{testCase.object}
                t.Run(testCase.label, func(t *testing.T) {
-                       result, err := Get(testCase.input["name"], testCase.input["namespace"], client)
+                       result, err := servicePlugin{}.Get(helm.KubernetesResource{
+                               GVK:  schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"},
+                               Name: testCase.input["name"],
+                       }, testCase.input["namespace"], client)
                        if err != nil {
                                if testCase.expectedError == "" {
                                        t.Fatalf("Get method return an un-expected (%s)", err)