From f2f60a0e8a96b5cdd46620206d7922b333ee9097 Mon Sep 17 00:00:00 2001 From: "Brad P. Crochet" Date: Tue, 11 May 2021 12:10:38 -0400 Subject: [PATCH] Migrate NMState to use kubernetes-nmstate-operator if available (#815) This patch make CNAO use the kubernetes-nmstate-operator if it is running and available. The only prereq for this is to have the operator pod running. If that's detected via the app=kubernetes-nmstate-operator label on a Deployment, then CNAO will only render an nmstates.nmstate.io CR to kick off the handler and webhook from the operator. If the operator is added, it also has upgrade logic to move to using the operator instead of installing kubernetes-nmstate directly. It will remove the old instance. Signed-off-by: Brad P. Crochet --- .gitignore | 2 + data/nmstate/{ => operand}/namespace.yaml | 0 ...io_nodenetworkconfigurationenactments.yaml | 0 ...e.io_nodenetworkconfigurationpolicies.yaml | 0 .../nmstate.io_nodenetworkstates.yaml | 0 data/nmstate/{ => operand}/operator.yaml | 0 data/nmstate/{ => operand}/role.yaml | 0 data/nmstate/{ => operand}/role_binding.yaml | 0 data/nmstate/{ => operand}/scc.yaml | 0 .../{ => operand}/service_account.yaml | 0 .../nmstate.io_v1beta1_nmstate_cr.yaml | 4 + hack/components/bump-nmstate.sh | 15 +- .../networkaddonsconfig_controller.go | 29 +++- pkg/network/cluster-info.go | 5 +- pkg/network/network.go | 4 +- pkg/network/nmstate.go | 63 +++++++- test/check/check.go | 29 +++- test/check/components.go | 62 ++++++++ test/e2e/workflow/deployment_test.go | 140 ++++++++++++++++++ 19 files changed, 333 insertions(+), 20 deletions(-) rename data/nmstate/{ => operand}/namespace.yaml (100%) rename data/nmstate/{ => operand}/nmstate.io_nodenetworkconfigurationenactments.yaml (100%) rename data/nmstate/{ => operand}/nmstate.io_nodenetworkconfigurationpolicies.yaml (100%) rename data/nmstate/{ => operand}/nmstate.io_nodenetworkstates.yaml (100%) rename data/nmstate/{ => operand}/operator.yaml (100%) rename data/nmstate/{ => operand}/role.yaml (100%) rename data/nmstate/{ => operand}/role_binding.yaml (100%) rename data/nmstate/{ => operand}/scc.yaml (100%) rename data/nmstate/{ => operand}/service_account.yaml (100%) create mode 100644 data/nmstate/operator/nmstate.io_v1beta1_nmstate_cr.yaml diff --git a/.gitignore b/.gitignore index 9c5c76403..5ab1eddd2 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ whitespace-check # Goland IDE folder .idea/ +# VS Code IDE folder +.vscode/ diff --git a/data/nmstate/namespace.yaml b/data/nmstate/operand/namespace.yaml similarity index 100% rename from data/nmstate/namespace.yaml rename to data/nmstate/operand/namespace.yaml diff --git a/data/nmstate/nmstate.io_nodenetworkconfigurationenactments.yaml b/data/nmstate/operand/nmstate.io_nodenetworkconfigurationenactments.yaml similarity index 100% rename from data/nmstate/nmstate.io_nodenetworkconfigurationenactments.yaml rename to data/nmstate/operand/nmstate.io_nodenetworkconfigurationenactments.yaml diff --git a/data/nmstate/nmstate.io_nodenetworkconfigurationpolicies.yaml b/data/nmstate/operand/nmstate.io_nodenetworkconfigurationpolicies.yaml similarity index 100% rename from data/nmstate/nmstate.io_nodenetworkconfigurationpolicies.yaml rename to data/nmstate/operand/nmstate.io_nodenetworkconfigurationpolicies.yaml diff --git a/data/nmstate/nmstate.io_nodenetworkstates.yaml b/data/nmstate/operand/nmstate.io_nodenetworkstates.yaml similarity index 100% rename from data/nmstate/nmstate.io_nodenetworkstates.yaml rename to data/nmstate/operand/nmstate.io_nodenetworkstates.yaml diff --git a/data/nmstate/operator.yaml b/data/nmstate/operand/operator.yaml similarity index 100% rename from data/nmstate/operator.yaml rename to data/nmstate/operand/operator.yaml diff --git a/data/nmstate/role.yaml b/data/nmstate/operand/role.yaml similarity index 100% rename from data/nmstate/role.yaml rename to data/nmstate/operand/role.yaml diff --git a/data/nmstate/role_binding.yaml b/data/nmstate/operand/role_binding.yaml similarity index 100% rename from data/nmstate/role_binding.yaml rename to data/nmstate/operand/role_binding.yaml diff --git a/data/nmstate/scc.yaml b/data/nmstate/operand/scc.yaml similarity index 100% rename from data/nmstate/scc.yaml rename to data/nmstate/operand/scc.yaml diff --git a/data/nmstate/service_account.yaml b/data/nmstate/operand/service_account.yaml similarity index 100% rename from data/nmstate/service_account.yaml rename to data/nmstate/operand/service_account.yaml diff --git a/data/nmstate/operator/nmstate.io_v1beta1_nmstate_cr.yaml b/data/nmstate/operator/nmstate.io_v1beta1_nmstate_cr.yaml new file mode 100644 index 000000000..49e371725 --- /dev/null +++ b/data/nmstate/operator/nmstate.io_v1beta1_nmstate_cr.yaml @@ -0,0 +1,4 @@ +apiVersion: nmstate.io/v1beta1 +kind: NMState +metadata: + name: nmstate diff --git a/hack/components/bump-nmstate.sh b/hack/components/bump-nmstate.sh index 5e9d6ef7e..f16f3858c 100755 --- a/hack/components/bump-nmstate.sh +++ b/hack/components/bump-nmstate.sh @@ -46,11 +46,14 @@ echo 'Configure nmstate-webhook and nmstate-handler templates and save the rende echo 'Copy kubernetes-nmstate manifests' rm -rf data/nmstate/* -cp $NMSTATE_PATH/config/cnao/handler/* data/nmstate/ -cp $NMSTATE_PATH/deploy/crds/nmstate.io_nodenetwork*.yaml data/nmstate/ -cp $NMSTATE_PATH/deploy/openshift/scc.yaml data/nmstate/scc.yaml -sed -i "s/---/{{ if .EnableSCC }}\n---/" data/nmstate/scc.yaml -echo "{{ end }}" >> data/nmstate/scc.yaml +mkdir -p data/nmstate/{operator,operand}/ +cp $NMSTATE_PATH/config/cnao/handler/* data/nmstate/operand/ +cp $NMSTATE_PATH/deploy/crds/nmstate.io_nodenetwork*.yaml data/nmstate/operand/ +cp $NMSTATE_PATH/deploy/openshift/scc.yaml data/nmstate/operand/scc.yaml +sed -i "s/---/{{ if .EnableSCC }}\n---/" data/nmstate/operand/scc.yaml +echo "{{ end }}" >> data/nmstate/operand/scc.yaml + +cp $NMSTATE_PATH/deploy/crds/nmstate.io_v1beta1_nmstate_cr.yaml data/nmstate/operator/ echo 'Apply custom CNAO patches on kubernetes-nmstate manifests' -sed -i -z 's#kind: Secret\nmetadata:#kind: Secret\nmetadata:\n annotations:\n networkaddonsoperator.network.kubevirt.io\/rejectOwner: ""#' data/nmstate/operator.yaml +sed -i -z 's#kind: Secret\nmetadata:#kind: Secret\nmetadata:\n annotations:\n networkaddonsoperator.network.kubevirt.io\/rejectOwner: ""#' data/nmstate/operand/operator.yaml diff --git a/pkg/controller/networkaddonsconfig/networkaddonsconfig_controller.go b/pkg/controller/networkaddonsconfig/networkaddonsconfig_controller.go index 8c8a2b432..a1c873d35 100644 --- a/pkg/controller/networkaddonsconfig/networkaddonsconfig_controller.go +++ b/pkg/controller/networkaddonsconfig/networkaddonsconfig_controller.go @@ -191,9 +191,19 @@ func (r *ReconcileNetworkAddonsConfig) Reconcile(request reconcile.Request) (rec return reconcile.Result{}, nil } + // Check for NMState Operator + nmstateOperator, err := isRunningKubernetesNMStateOperator(r.client) + if err != nil { + return reconcile.Result{}, errors.Wrap(err, "failed to check whether running Kubernetes NMState Operator") + } + if nmstateOperator { + log.Printf("Kubernetes NMState Operator is running") + } + r.clusterInfo.NmstateOperator = nmstateOperator + // Fetch the NetworkAddonsConfig instance networkAddonsConfigStorageVersion := &cnaov1.NetworkAddonsConfig{} - err := r.client.Get(context.TODO(), request.NamespacedName, networkAddonsConfigStorageVersion) + err = r.client.Get(context.TODO(), request.NamespacedName, networkAddonsConfigStorageVersion) if err != nil { if apierrors.IsNotFound(err) { // Request object not found, could have been deleted after reconcile request. @@ -316,7 +326,7 @@ func (r *ReconcileNetworkAddonsConfig) renderObjectsV1(networkAddonsConfig *cnao // Perform any special object changes that are impossible to do with regular Apply. e.g. Remove outdated objects // and objects that cannot be modified by Apply method due to incompatible changes. - if err := network.SpecialCleanUp(&networkAddonsConfig.Spec, r.client); err != nil { + if err := network.SpecialCleanUp(&networkAddonsConfig.Spec, r.client, r.clusterInfo); err != nil { log.Printf("failed to Clean Up outdated objects: %v", err) return objs, err } @@ -529,6 +539,21 @@ func isSCCAvailable(c kubernetes.Interface) (bool, error) { return isResourceAvailable(c, "securitycontextconstraints", "security.openshift.io", "v1") } +func isRunningKubernetesNMStateOperator(c k8sclient.Client) (bool, error) { + deployments := &appsv1.DeploymentList{} + err := c.List(context.TODO(), deployments, k8sclient.MatchingLabels{"app": "kubernetes-nmstate-operator"}) + if err != nil { + if apierrors.IsNotFound(err) { + return false, nil + } + return false, err + } + if len(deployments.Items) == 0 { + return false, nil + } + return true, nil +} + func isResourceAvailable(kubeClient kubernetes.Interface, name string, group string, version string) (bool, error) { result := kubeClient.ExtensionsV1beta1().RESTClient().Get().RequestURI("/apis/" + group + "/" + version + "/" + name).Do(context.TODO()) if result.Error() != nil { diff --git a/pkg/network/cluster-info.go b/pkg/network/cluster-info.go index 38eeea1ea..b35e614d7 100644 --- a/pkg/network/cluster-info.go +++ b/pkg/network/cluster-info.go @@ -1,6 +1,7 @@ package network type ClusterInfo struct { - SCCAvailable bool - OpenShift4 bool + SCCAvailable bool + OpenShift4 bool + NmstateOperator bool } diff --git a/pkg/network/network.go b/pkg/network/network.go index 59cb145f8..3fc6759b1 100644 --- a/pkg/network/network.go +++ b/pkg/network/network.go @@ -57,12 +57,12 @@ func FillDefaults(conf, previous *cnao.NetworkAddonsConfigSpec) error { } // specialCleanUp checks if there are any specific outdated objects or ones that are no longer compatible and deletes them. -func SpecialCleanUp(conf *cnao.NetworkAddonsConfigSpec, client k8sclient.Client) error { +func SpecialCleanUp(conf *cnao.NetworkAddonsConfigSpec, client k8sclient.Client, clusterInfo *ClusterInfo) error { errs := []error{} ctx := context.TODO() errs = append(errs, cleanUpMultus(conf, ctx, client)...) - errs = append(errs, cleanUpNMState(conf, ctx, client)...) + errs = append(errs, cleanUpNMState(conf, ctx, client, clusterInfo)...) if len(errs) > 0 { return errors.Errorf("invalid configuration:\n%v", errorListToMultiLineString(errs)) diff --git a/pkg/network/nmstate.go b/pkg/network/nmstate.go index 982812bd4..779e9febe 100644 --- a/pkg/network/nmstate.go +++ b/pkg/network/nmstate.go @@ -38,7 +38,14 @@ func renderNMState(conf *cnao.NetworkAddonsConfigSpec, manifestDir string, clust data.Data["CertOverlapInterval"] = conf.SelfSignConfiguration.CertOverlapInterval data.Data["PlacementConfiguration"] = conf.PlacementConfiguration - objs, err := render.RenderDir(filepath.Join(manifestDir, "nmstate"), &data) + log.Printf("NMStateOperator == %t", clusterInfo.NmstateOperator) + fullManifestDir := filepath.Join(manifestDir, "nmstate", "operand") + if clusterInfo.NmstateOperator { + fullManifestDir = filepath.Join(manifestDir, "nmstate", "operator") + } + log.Printf("Rendering NMState directory: %s", fullManifestDir) + + objs, err := render.RenderDir(fullManifestDir, &data) if err != nil { return nil, errors.Wrap(err, "failed to render nmstate state handler manifests") } @@ -46,6 +53,29 @@ func renderNMState(conf *cnao.NetworkAddonsConfigSpec, manifestDir string, clust return objs, nil } +func removeAppsV1Resource(ctx context.Context, client k8sclient.Client, name, namespace, kind string) []error { + // Get existing + existing := &unstructured.Unstructured{} + gvk := schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: kind} + existing.SetGroupVersionKind(gvk) + + err := client.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, existing) + + // if we found the object + if err == nil { + objDesc := fmt.Sprintf("(%s) %s/%s", gvk.String(), namespace, name) + log.Printf("Cleanup up %s Object", objDesc) + + // Delete the object + err = client.Delete(ctx, existing) + if err != nil { + log.Printf("Failed Cleaning up %s Object", objDesc) + return []error{err} + } + } + return []error{} +} + func removeDaemonSetHandlerWorker(ctx context.Context, client k8sclient.Client) []error { // Get existing existing := &unstructured.Unstructured{} @@ -74,13 +104,42 @@ func removeDaemonSetHandlerWorker(ctx context.Context, client k8sclient.Client) return []error{} } -func cleanUpNMState(conf *cnao.NetworkAddonsConfigSpec, ctx context.Context, client k8sclient.Client) []error { +func removeStandaloneHandler(ctx context.Context, client k8sclient.Client) []error { + namespace := os.Getenv("OPERAND_NAMESPACE") + name := "nmstate-handler" + kind := "DaemonSet" + + return removeAppsV1Resource(ctx, client, name, namespace, kind) +} + +func removeStandaloneWebhook(ctx context.Context, client k8sclient.Client) []error { + namespace := os.Getenv("OPERAND_NAMESPACE") + name := "nmstate-webhook" + kind := "Deployment" + + return removeAppsV1Resource(ctx, client, name, namespace, kind) +} + +func removeStandaloneCertManager(ctx context.Context, client k8sclient.Client) []error { + namespace := os.Getenv("OPERAND_NAMESPACE") + name := "nmstate-cert-manager" + kind := "Deployment" + + return removeAppsV1Resource(ctx, client, name, namespace, kind) +} + +func cleanUpNMState(conf *cnao.NetworkAddonsConfigSpec, ctx context.Context, client k8sclient.Client, clusterInfo *ClusterInfo) []error { if conf.NMState == nil { return []error{} } errList := []error{} errList = append(errList, removeDaemonSetHandlerWorker(ctx, client)...) + if clusterInfo.NmstateOperator { + errList = append(errList, removeStandaloneHandler(ctx, client)...) + errList = append(errList, removeStandaloneWebhook(ctx, client)...) + errList = append(errList, removeStandaloneCertManager(ctx, client)...) + } return errList } diff --git a/test/check/check.go b/test/check/check.go index 2fc469c47..501f97f47 100644 --- a/test/check/check.go +++ b/test/check/check.go @@ -183,6 +183,17 @@ func CheckOperatorIsReady(timeout time.Duration) { } } +func CheckNMStateOperatorIsReady(timeout time.Duration) { + By("Checking that the operator is up and running") + if timeout != CheckImmediately { + Eventually(func() error { + return checkForGenericDeployment("nmstate-operator", "nmstate", false) + }, timeout, time.Second).ShouldNot(HaveOccurred(), fmt.Sprintf("Timed out waiting for the operator to become ready")) + } else { + Expect(checkForGenericDeployment("nmstate-operator", "nmstate", false)).ShouldNot(HaveOccurred(), "Operator is not ready") + } +} + func CheckForLeftoverObjects(currentVersion string) { listOptions := client.ListOptions{} key := cnaov1.SchemeGroupVersion.Group + "/version" @@ -358,17 +369,23 @@ func checkForSecurityContextConstraints(name string) error { } func checkForDeployment(name string) error { + return checkForGenericDeployment(name, components.Namespace, true) +} + +func checkForGenericDeployment(name, namespace string, checkLabels bool) error { deployment := appsv1.Deployment{} - err := framework.Global.Client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: components.Namespace}, &deployment) + err := framework.Global.Client.Get(context.Background(), types.NamespacedName{Name: name, Namespace: namespace}, &deployment) if err != nil { return err } - labels := deployment.GetLabels() - if labels != nil { - if _, operatorLabelSet := labels[cnaov1.SchemeGroupVersion.Group+"/version"]; !operatorLabelSet { - return fmt.Errorf("Deployment %s/%s is missing operator label", components.Namespace, name) + if checkLabels { + labels := deployment.GetLabels() + if labels != nil { + if _, operatorLabelSet := labels[cnaov1.SchemeGroupVersion.Group+"/version"]; !operatorLabelSet { + return fmt.Errorf("Deployment %s/%s is missing operator label", components.Namespace, name) + } } } @@ -377,7 +394,7 @@ func checkForDeployment(name string) error { if err != nil { panic(err) } - return fmt.Errorf("Deployment %s/%s is not ready, current state:\n%v\ncluster Info:\n%v", components.Namespace, name, string(manifest), gatherClusterInfo()) + return fmt.Errorf("Deployment %s/%s is not ready, current state:\n%v\ncluster Info:\n%v", namespace, name, string(manifest), gatherClusterInfo()) } return nil diff --git a/test/check/components.go b/test/check/components.go index 141206805..5ce0a9aa9 100644 --- a/test/check/components.go +++ b/test/check/components.go @@ -1,5 +1,13 @@ package check +import ( + "fmt" + "io/ioutil" + + "github.com/pkg/errors" + "gopkg.in/yaml.v2" +) + type Component struct { ComponentName string ClusterRole string @@ -77,3 +85,57 @@ var ( MacvtapComponent, } ) + +type ComponentUpdatePolicy string + +const ( + Tagged ComponentUpdatePolicy = "tagged" + Static ComponentUpdatePolicy = "static" + Latest ComponentUpdatePolicy = "latest" +) + +type ComponentsConfig struct { + Components map[string]ComponentSource `yaml:"components"` +} + +type ComponentSource struct { + Url string `yaml:"url"` + Commit string `yaml:"commit"` + Branch string `yaml:"branch"` + UpdatePolicy ComponentUpdatePolicy `yaml:"update-policy"` + Metadata string `yaml:"metadata"` +} + +func GetComponentSource(component string) (ComponentSource, error) { + componentsConfig, err := parseComponentsYaml("components.yaml") + if err != nil { + return ComponentSource{}, errors.Wrapf(err, "Failed to get components config") + } + + componentSource, ok := componentsConfig.Components[component] + if !ok { + return ComponentSource{}, errors.Wrapf(err, "Failed to get component %s", component) + } + + return componentSource, nil +} + +func parseComponentsYaml(componentsConfigPath string) (ComponentsConfig, error) { + config := ComponentsConfig{} + + componentsData, err := ioutil.ReadFile(componentsConfigPath) + if err != nil { + return ComponentsConfig{}, errors.Wrapf(err, "Failed to open file %s", componentsConfigPath) + } + + err = yaml.Unmarshal(componentsData, &config) + if err != nil { + return ComponentsConfig{}, errors.Wrapf(err, "Failed to Unmarshal %s", componentsConfigPath) + } + + if len(config.Components) == 0 { + return ComponentsConfig{}, fmt.Errorf("Failed to Unmarshal %s. Output is empty", componentsConfigPath) + } + + return config, nil +} diff --git a/test/e2e/workflow/deployment_test.go b/test/e2e/workflow/deployment_test.go index 0083b730f..a3b40674b 100644 --- a/test/e2e/workflow/deployment_test.go +++ b/test/e2e/workflow/deployment_test.go @@ -1,11 +1,17 @@ package test import ( + "bytes" "context" "fmt" + "io/ioutil" + "net/http" + "path/filepath" + "text/template" "time" framework "github.com/operator-framework/operator-sdk/pkg/test" + "github.com/pkg/errors" v1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -20,6 +26,7 @@ import ( "github.com/kubevirt/cluster-network-addons-operator/pkg/components" "github.com/kubevirt/cluster-network-addons-operator/pkg/network" . "github.com/kubevirt/cluster-network-addons-operator/test/check" + "github.com/kubevirt/cluster-network-addons-operator/test/kubectl" . "github.com/kubevirt/cluster-network-addons-operator/test/okd" . "github.com/kubevirt/cluster-network-addons-operator/test/operations" ) @@ -336,8 +343,141 @@ var _ = Describe("NetworkAddonsConfig", func() { }) }) }) + Describe("NMState", func() { + var ( + configSpec cnao.NetworkAddonsConfigSpec + ) + BeforeEach(func() { + configSpec = cnao.NetworkAddonsConfigSpec{ + NMState: &cnao.NMState{}, + } + }) + JustBeforeEach(func() { + // Install nmstate-operator here + installNMStateOperator() + CheckNMStateOperatorIsReady(5 * time.Minute) + + CreateConfig(gvk, configSpec) + CheckConfigCondition(gvk, ConditionAvailable, ConditionTrue, 15*time.Minute, CheckDoNotRepeat) + }) + JustAfterEach(func() { + uninstallNMStateOperator() + }) + Context("when it is already deployed", func() { + Context("and the nmstate-operator is installed", func() { + It("should run nmstate from the operator", func() { + By("checking for NMState in nmstate namespace") + Eventually(func() error { + nmstateHandlerDaemonSet := &v1.DaemonSet{} + return framework.Global.Client.Get(context.TODO(), types.NamespacedName{Name: NMStateComponent.DaemonSets[0], Namespace: "nmstate"}, nmstateHandlerDaemonSet) + }, 5*time.Minute, time.Second).Should(BeNil(), fmt.Sprintf("Timed out waiting for nmstate-operator daemonset")) + }) + }) + }) + Context("when it is not already deployed", func() { + BeforeEach(func() { + configSpec = cnao.NetworkAddonsConfigSpec{} + }) + Context("and the nmstate-operator is installed", func() { + It("should run nmstate from the operator", func() { + configSpec = cnao.NetworkAddonsConfigSpec{ + NMState: &cnao.NMState{}, + } + UpdateConfig(gvk, configSpec) + CheckConfigCondition(gvk, ConditionAvailable, ConditionTrue, 15*time.Minute, CheckDoNotRepeat) + By("checking for NMState in nmstate namespace") + Eventually(func() error { + nmstateHandlerDaemonSet := &v1.DaemonSet{} + return framework.Global.Client.Get(context.TODO(), types.NamespacedName{Name: NMStateComponent.DaemonSets[0], Namespace: "nmstate"}, nmstateHandlerDaemonSet) + }, 5*time.Minute, time.Second).Should(BeNil(), fmt.Sprintf("Timed out waiting for nmstate-operator daemonset")) + }) + }) + }) + }) }) +func installNMStateOperator() { + By("Installing kubernetes-nmstate-operator") + componentSource, err := GetComponentSource("nmstate") + Expect(err).ToNot(HaveOccurred(), "Error getting the component source") + + result, err := kubectl.Kubectl("apply", "-f", fmt.Sprintf("https://raw.githubusercontent.com/nmstate/kubernetes-nmstate/%s/deploy/crds/nmstate.io_nmstates.yaml", componentSource.Metadata)) + Expect(err).ToNot(HaveOccurred(), "Error applying CRD: %s", result) + + // Create temp directory + tmpdir, err := ioutil.TempDir("", "operator-test") + Expect(err).ToNot(HaveOccurred(), "Error creating temporary dir") + manifests := []string{"namespace", "service_account", "role", "role_binding", "operator"} + for _, manifest := range manifests { + yamlString, err := parseManifest(fmt.Sprintf("https://raw.githubusercontent.com/nmstate/kubernetes-nmstate/%s/deploy/operator/%s.yaml", componentSource.Metadata, manifest), componentSource.Metadata) + Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("Error parsing manifest file to string: %s", manifest)) + + yamlFile := filepath.Join(tmpdir, fmt.Sprintf("%s.yaml", manifest)) + ioutil.WriteFile(yamlFile, []byte(yamlString), 0666) + result, err = kubectl.Kubectl("apply", "-f", yamlFile) + Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("Error when running kubectl: %s", result)) + } +} + +func uninstallNMStateOperator() { + By("Uninstalling kubernetes-nmstate-operator") + componentSource, err := GetComponentSource("nmstate") + Expect(err).ToNot(HaveOccurred(), "Error getting the component source") + + // Create temp directory + tmpdir, err := ioutil.TempDir("", "operator-test") + Expect(err).ToNot(HaveOccurred(), "Error creating temporary dir") + manifests := []string{"operator", "role_binding", "role", "service_account", "namespace"} + for _, manifest := range manifests { + yamlString, err := parseManifest(fmt.Sprintf("https://raw.githubusercontent.com/nmstate/kubernetes-nmstate/%s/deploy/operator/%s.yaml", componentSource.Metadata, manifest), componentSource.Metadata) + Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("Error parsing manifest file to string: %s", manifest)) + + yamlFile := filepath.Join(tmpdir, fmt.Sprintf("%s.yaml", manifest)) + ioutil.WriteFile(yamlFile, []byte(yamlString), 0666) + result, err := kubectl.Kubectl("delete", "-f", yamlFile) + Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("Error when running kubectl: %s", result)) + } + result, err := kubectl.Kubectl("delete", "-f", fmt.Sprintf("https://raw.githubusercontent.com/nmstate/kubernetes-nmstate/%s/deploy/crds/nmstate.io_nmstates.yaml", componentSource.Metadata)) + Expect(err).ToNot(HaveOccurred(), "Error deleting CRD: %s", result) +} + +func parseManifest(url string, tag string) (string, error) { + resp, err := http.Get(url) + if err != nil { + return "", errors.Wrapf(err, "Could not get url: %s", url) + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", errors.Wrapf(err, "Error reading body of url: %s", url) + } + var tmpl *template.Template + tmpl = template.Must(template.New("manifest").Parse(string(body))) + + data := struct { + OperatorNamespace string + OperatorImage string + OperatorPullPolicy string + HandlerNamespace string + HandlerImage string + HandlerPullPolicy string + }{ + OperatorNamespace: "nmstate", + OperatorImage: fmt.Sprintf("quay.io/nmstate/kubernetes-nmstate-operator:%s", tag), + OperatorPullPolicy: "Always", + HandlerNamespace: "nmstate", + HandlerImage: fmt.Sprintf("quay.io/nmstate/kubernetes-nmstate-handler:%s", tag), + HandlerPullPolicy: "Always", + } + var out bytes.Buffer + err = tmpl.Execute(&out, data) + if err != nil { + return "", errors.Wrapf(err, "Error parsing template") + } + return out.String(), nil +} + func testConfigCreate(gvk schema.GroupVersionKind, configSpec cnao.NetworkAddonsConfigSpec, components []Component) { checkConfigChange(gvk, components, func() { CreateConfig(gvk, configSpec)