github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/clusterapi/capi/helpers.go (about)

     1  // Copyright (c) 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package capi
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"github.com/pkg/errors"
    11  	"github.com/verrazzano/verrazzano/pkg/k8s/resource"
    12  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    13  	"github.com/verrazzano/verrazzano/tests/e2e/backup/helpers"
    14  	"go.uber.org/zap"
    15  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    18  	"k8s.io/apimachinery/pkg/runtime/schema"
    19  	"k8s.io/apimachinery/pkg/types"
    20  	"k8s.io/client-go/dynamic"
    21  	"k8s.io/client-go/kubernetes"
    22  	"os"
    23  	"path/filepath"
    24  	clusterapi "sigs.k8s.io/cluster-api/cmd/clusterctl/client"
    25  	"strings"
    26  	"text/tabwriter"
    27  	"time"
    28  )
    29  
    30  const (
    31  	nodeLabel         = "node-role.kubernetes.io/node"
    32  	controlPlaneLabel = "node-role.kubernetes.io/control-plane"
    33  
    34  	// env var names
    35  	CAPINodeSSHKey               = "OCI_SSH_KEY"
    36  	OCICredsKey                  = "OCI_CREDENTIALS_KEY"
    37  	OCIPrivateCredsKeyBase64     = "OCI_CREDENTIALS_KEY_B64"
    38  	OCITenancyIDKeyBase64        = "OCI_TENANCY_ID_B64"
    39  	OCICredsFingerprintKeyBase64 = "OCI_CREDENTIALS_FINGERPRINT_B64"
    40  	OCIUserIDKeyBase64           = "OCI_USER_ID_B64"
    41  	OCIRegionKeyBase64           = "OCI_REGION_B64"
    42  	OCIImageIDKey                = "OCI_IMAGE_ID"
    43  	OCIVCNKey                    = "OCI_VCN_ID"
    44  	OCISubnetKey                 = "OCI_SUBNET_ID"
    45  	ocneServiceLbName            = "ocne-service-lb"
    46  )
    47  
    48  var capiInitFunc = clusterapi.New
    49  
    50  // PrintYamlOutput is used to print yaml templates to stdout or a file
    51  func (c CAPITestImpl) PrintYamlOutput(printer clusterapi.YamlPrinter, outputFile string) error {
    52  	yaml, err := printer.Yaml()
    53  	if err != nil {
    54  		return err
    55  	}
    56  	yaml = append(yaml, '\n')
    57  	outputFile = strings.TrimSpace(outputFile)
    58  	if outputFile == "" || outputFile == "-" {
    59  		if _, err := os.Stdout.Write(yaml); err != nil {
    60  			return errors.Wrap(err, "failed to write yaml to Stdout")
    61  		}
    62  		return nil
    63  	}
    64  	outputFile = filepath.Clean(outputFile)
    65  	if err := os.WriteFile(outputFile, yaml, 0600); err != nil {
    66  		return errors.Wrap(err, "failed to write to destination file")
    67  	}
    68  	return nil
    69  }
    70  
    71  // ClusterTemplateGenerate used for cluster template generation
    72  func (c CAPITestImpl) ClusterTemplateGenerate(clusterName, templatePath string, log *zap.SugaredLogger) (string, error) {
    73  	log.Infof("Generate called for clustername '%s'...", clusterName)
    74  	capiClient, err := capiInitFunc("")
    75  	if err != nil {
    76  		return "", err
    77  	}
    78  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
    79  	if err != nil {
    80  		log.Errorf("Unable to fetch kubeconfig url due to %v", zap.Error(err))
    81  		return "", err
    82  	}
    83  
    84  	templateOptions := clusterapi.GetClusterTemplateOptions{
    85  		Kubeconfig: clusterapi.Kubeconfig{
    86  			Path:    kubeconfigPath,
    87  			Context: ""},
    88  		URLSource: &clusterapi.URLSourceOptions{
    89  			URL: templatePath,
    90  		},
    91  		ClusterName:     clusterName,
    92  		TargetNamespace: OCNENamespace,
    93  	}
    94  
    95  	template, err := capiClient.GetClusterTemplate(templateOptions)
    96  	if err != nil {
    97  		log.Errorf("template '%s' generation error  = %v", templatePath, zap.Error(err))
    98  		return "", err
    99  	}
   100  
   101  	tmpFile, err := os.CreateTemp(os.TempDir(), clusterName)
   102  	if err != nil {
   103  		return "", fmt.Errorf("Failed to create temporary file: %v", err)
   104  	}
   105  
   106  	if err := c.PrintYamlOutput(template, tmpFile.Name()); err != nil {
   107  		return "", err
   108  	}
   109  	return tmpFile.Name(), nil
   110  }
   111  
   112  // GetUnstructuredData common utility to fetch unstructured data
   113  func (c CAPITestImpl) GetUnstructuredData(group, version, resource, resourceName, nameSpaceName string, log *zap.SugaredLogger) (*unstructured.Unstructured, error) {
   114  	var dataFetched *unstructured.Unstructured
   115  	var err error
   116  	config, err := k8sutil.GetKubeConfig()
   117  	if err != nil {
   118  		log.Errorf("Unable to fetch kubeconfig %v", zap.Error(err))
   119  		return nil, err
   120  	}
   121  	dclient, err := dynamic.NewForConfig(config)
   122  	if err != nil {
   123  		log.Errorf("Unable to create dynamic client %v", zap.Error(err))
   124  		return nil, err
   125  	}
   126  
   127  	gvr := schema.GroupVersionResource{
   128  		Group:    group,
   129  		Version:  version,
   130  		Resource: resource,
   131  	}
   132  
   133  	if nameSpaceName != "" {
   134  		log.Infof("fetching '%s' '%s' in namespace '%s'", resource, resourceName, nameSpaceName)
   135  		dataFetched, err = dclient.Resource(gvr).Namespace(nameSpaceName).Get(context.TODO(), resourceName, metav1.GetOptions{})
   136  	} else {
   137  		log.Infof("fetching '%s' '%s'", resource, resourceName)
   138  		dataFetched, err = dclient.Resource(gvr).Get(context.TODO(), resourceName, metav1.GetOptions{})
   139  	}
   140  	if err != nil {
   141  		if apierrors.IsNotFound(err) {
   142  			log.Errorf("resource %s %s not found", resource, resourceName)
   143  			return nil, nil
   144  		}
   145  		log.Errorf("Unable to fetch %s %s due to '%v'", resource, resourceName, zap.Error(err))
   146  		return nil, err
   147  	}
   148  	return dataFetched, nil
   149  }
   150  
   151  // GetUnstructuredData common utility to fetch list of unstructured data
   152  func (c CAPITestImpl) GetUnstructuredDataList(group, version, resource, nameSpaceName string, log *zap.SugaredLogger) (*unstructured.UnstructuredList, error) {
   153  	config, err := k8sutil.GetKubeConfig()
   154  	if err != nil {
   155  		log.Errorf("Unable to fetch kubeconfig %v", zap.Error(err))
   156  		return nil, err
   157  	}
   158  	dclient, err := dynamic.NewForConfig(config)
   159  	if err != nil {
   160  		log.Errorf("Unable to create dynamic client %v", zap.Error(err))
   161  		return nil, err
   162  	}
   163  
   164  	log.Infof("Fetching resource %s", resource)
   165  	gvr := schema.GroupVersionResource{
   166  		Group:    group,
   167  		Version:  version,
   168  		Resource: resource,
   169  	}
   170  
   171  	dataFetched, err := dclient.Resource(gvr).Namespace(nameSpaceName).List(context.TODO(), metav1.ListOptions{})
   172  	if err != nil {
   173  		log.Errorf("Unable to fetch resource %s due to '%v'", resource, zap.Error(err))
   174  		return nil, err
   175  	}
   176  
   177  	return dataFetched, nil
   178  }
   179  
   180  // GetCluster is used to fetch capi cluster info given a cluster name and namespace, if it exists
   181  func (c CAPITestImpl) GetCluster(namespace, clusterName string, log *zap.SugaredLogger) (*Cluster, error) {
   182  	var capiCluster Cluster
   183  	clusterFetched, err := c.GetUnstructuredData("cluster.x-k8s.io", "v1beta1", "clusters", clusterName, namespace, log)
   184  	if err != nil {
   185  		log.Errorf("Unable to fetch CAPI cluster '%s' due to '%v'", clusterName, zap.Error(err))
   186  		return nil, err
   187  	}
   188  
   189  	if clusterFetched == nil {
   190  		log.Infof("No CAPI clusters with name '%s' in namespace '%s' was detected", clusterName, namespace)
   191  		return &capiCluster, nil
   192  	}
   193  
   194  	bdata, err := json.Marshal(clusterFetched)
   195  	if err != nil {
   196  		log.Errorf("Json marshalling error %v", zap.Error(err))
   197  		return nil, err
   198  	}
   199  	err = json.Unmarshal(bdata, &capiCluster)
   200  	if err != nil {
   201  		log.Errorf("Json unmarshall error %v", zap.Error(err))
   202  		return nil, err
   203  	}
   204  
   205  	return &capiCluster, nil
   206  }
   207  
   208  // GetOCNEControlPlane is used to fetch OCNE control plane info given a control plane name and namespace, if it exists
   209  func (c CAPITestImpl) GetOCNEControlPlane(namespace string, log *zap.SugaredLogger) (*OCNEControlPlane, error) {
   210  	ocnecpesFetched, err := c.GetUnstructuredDataList("controlplane.cluster.x-k8s.io", "v1alpha1", "ocnecontrolplanes", namespace, log)
   211  	if err != nil {
   212  		log.Errorf("Unable to fetch machines due to '%v'", zap.Error(err))
   213  		return nil, err
   214  	}
   215  
   216  	if ocnecpesFetched == nil {
   217  		log.Infof("No OCNE control plane in namespace '%s' was detected", namespace)
   218  	}
   219  
   220  	if len(ocnecpesFetched.Items) > 1 {
   221  		log.Errorf("More than one OCNE control plane in namespace '%s' was detected !!", namespace)
   222  	}
   223  
   224  	var ocneControlPlane OCNEControlPlane
   225  	var bdata []byte
   226  	for _, ocnecp := range ocnecpesFetched.Items {
   227  		bdata, err = json.Marshal(ocnecp.Object)
   228  		if err != nil {
   229  			log.Errorf("Json marshalling error %v", zap.Error(err))
   230  			return nil, err
   231  		}
   232  		err = json.Unmarshal(bdata, &ocneControlPlane)
   233  		if err != nil {
   234  			log.Errorf("Json unmarshall error %v", zap.Error(err))
   235  			return nil, err
   236  		}
   237  	}
   238  	os.Setenv("OCNE_CONTROL_PLANE_NAME", ocneControlPlane.Metadata.Name)
   239  	os.Setenv("OCI_MACHINE_TEMPLATE_NAME", ocneControlPlane.Spec.MachineTemplate.InfrastructureRef.Name)
   240  	return &ocneControlPlane, nil
   241  }
   242  
   243  // GetCapiClusterKubeConfig returns the content of the kubeconfig file of an OCNE cluster if it exists.
   244  func (c CAPITestImpl) GetCapiClusterKubeConfig(clusterName string, log *zap.SugaredLogger) ([]byte, error) {
   245  	clientset, err := k8sutil.GetKubernetesClientset()
   246  	if err != nil {
   247  		log.Errorf("Failed to get clientset with error: %v", err)
   248  		return nil, err
   249  	}
   250  
   251  	secret, err := clientset.CoreV1().Secrets(clusterName).Get(context.TODO(), fmt.Sprintf("%s-kubeconfig", clusterName), metav1.GetOptions{})
   252  	if err != nil {
   253  		log.Infof("Error fetching secret ", zap.Error(err))
   254  		return nil, err
   255  	}
   256  
   257  	return secret.Data["value"], nil
   258  }
   259  
   260  // GetCapiClusterK8sClient returns the K8s client of an OCNE cluster if it exists.
   261  func (c CAPITestImpl) GetCapiClusterK8sClient(clusterName string, log *zap.SugaredLogger) (client *kubernetes.Clientset, err error) {
   262  	capiK8sConfig, err := c.GetCapiClusterKubeConfig(clusterName, log)
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  	tmpFile, err := os.CreateTemp(os.TempDir(), clusterName)
   267  	if err != nil {
   268  		return nil, errors.Wrap(err, "Failed to create temporary file")
   269  	}
   270  
   271  	if err := os.WriteFile(tmpFile.Name(), capiK8sConfig, 0600); err != nil {
   272  		return nil, errors.Wrap(err, "failed to write to destination file")
   273  	}
   274  
   275  	k8sRestConfig, err := k8sutil.GetKubeConfigGivenPathAndContext(tmpFile.Name(), fmt.Sprintf("%s-admin@%s", clusterName, clusterName))
   276  	if err != nil {
   277  		return nil, errors.Wrap(err, "Failed to get k8s rest config")
   278  	}
   279  
   280  	return k8sutil.GetKubernetesClientsetWithConfig(k8sRestConfig)
   281  }
   282  
   283  // TriggerCapiClusterCreation starts the OCNE workload cluster creation by applying the template YAML
   284  func (c CAPITestImpl) TriggerCapiClusterCreation(clusterName, templateName string, log *zap.SugaredLogger) error {
   285  	tmpFilePath, err := c.ClusterTemplateGenerate(clusterName, templateName, log)
   286  	if err != nil {
   287  		log.Errorf("unable to generate template for cluster : %v", zap.Error(err))
   288  		return err
   289  	}
   290  	defer os.RemoveAll(tmpFilePath)
   291  	clusterTemplateData, err := os.ReadFile(tmpFilePath)
   292  	if err != nil {
   293  		return nil
   294  	}
   295  	err = resource.CreateOrUpdateResourceFromBytes(clusterTemplateData, log)
   296  	if err != nil {
   297  		log.Errorf("Error creating cluster from template ", zap.Error(err))
   298  		return err
   299  	}
   300  	log.Infof("Wait for 30 seconds before verification")
   301  	time.Sleep(30 * time.Second)
   302  	return nil
   303  }
   304  
   305  // DeployClusterInfraClusterResourceSets deploys the ClusterResourceSets by deploying the addon template YAML
   306  // ClusterResourceSets are used to deploy the following on OCNE workload clusters
   307  // 1. CCM Secrets
   308  // 2. Calico Module
   309  // 3. CCM Module
   310  func (c CAPITestImpl) DeployClusterInfraClusterResourceSets(clusterName, templateName string, log *zap.SugaredLogger) error {
   311  	log.Info("Preparing to deploy Clusterresourcesets...")
   312  	oci, err := NewClient(GetOCIConfigurationProvider(log))
   313  	if err != nil {
   314  		log.Error("Unable to create OCI client %v", zap.Error(err))
   315  		return err
   316  	}
   317  
   318  	OCIVcnID, err = oci.GetVcnIDByName(context.TODO(), OCICompartmentID, clusterName, log)
   319  	if err != nil {
   320  		return err
   321  	}
   322  
   323  	OCISubnetID, err = oci.GetSubnetIDByName(context.TODO(), OCICompartmentID, OCIVcnID, ocneServiceLbName, log)
   324  	if err != nil {
   325  		return err
   326  	}
   327  
   328  	os.Setenv(OCIVCNKey, OCIVcnID)
   329  	os.Setenv(OCISubnetKey, OCISubnetID)
   330  
   331  	tmpFilePath, err := c.ClusterTemplateGenerate(clusterName, templateName, log)
   332  	if err != nil {
   333  		log.Errorf("unable to generate template for clusterresourcesets : %v", zap.Error(err))
   334  		return err
   335  	}
   336  	defer os.RemoveAll(tmpFilePath)
   337  	clusterTemplateData, err := os.ReadFile(tmpFilePath)
   338  	if err != nil {
   339  		log.Errorf("unable to get read file : %v", zap.Error(err))
   340  		return err
   341  	}
   342  
   343  	err = resource.CreateOrUpdateResourceFromBytes(clusterTemplateData, log)
   344  	if err != nil {
   345  		log.Error("unable to get create clusterresourcesets on workload cluster :", zap.Error(err))
   346  		return err
   347  	}
   348  
   349  	log.Infof("Wait for 30 seconds for cluster resourceset resources to deploy")
   350  	time.Sleep(30 * time.Second)
   351  	localTest := getEnvDefault("LOCAL_TEST", "false")
   352  	if strings.ToLower(localTest) == "false" {
   353  		// When running on Jenkins instance
   354  		return c.CreateImagePullSecrets(clusterName, log)
   355  	}
   356  	return nil
   357  }
   358  
   359  // DeployAnyClusterResourceSets deploys the VZ ClusterResourceSets by deploying the addon template YAML
   360  func (c CAPITestImpl) DeployAnyClusterResourceSets(clusterName, templateName string, log *zap.SugaredLogger) error {
   361  	log.Info("Preparing to deploy VZ Clusterresourcesets...")
   362  
   363  	tmpFilePath, err := c.ClusterTemplateGenerate(clusterName, templateName, log)
   364  	if err != nil {
   365  		log.Errorf("unable to generate template for clusterresourcesets : %v", zap.Error(err))
   366  		return err
   367  	}
   368  
   369  	log.Infof("Filename = %v", tmpFilePath)
   370  
   371  	//defer os.RemoveAll(tmpFilePath)
   372  	clusterTemplateData, err := os.ReadFile(tmpFilePath)
   373  	if err != nil {
   374  		log.Errorf("unable to read file : %v", zap.Error(err))
   375  		return err
   376  	}
   377  
   378  	err = resource.CreateOrUpdateResourceFromBytes(clusterTemplateData, log)
   379  	if err != nil {
   380  		log.Error("unable to create clusterresourcesets on workload cluster :", zap.Error(err))
   381  		return err
   382  	}
   383  
   384  	log.Infof("Wait for 5 seconds for controllers to reconcile...")
   385  	time.Sleep(5 * time.Second)
   386  
   387  	return nil
   388  }
   389  
   390  // EnsureMachinesAreProvisioned fetches the machines that are deployed during OCNE cluster creation.
   391  func (c CAPITestImpl) EnsureMachinesAreProvisioned(namespace, clusterName string, log *zap.SugaredLogger) error {
   392  	machinesFetched, err := c.GetUnstructuredDataList("cluster.x-k8s.io", "v1beta1", "machines", namespace, log)
   393  	if err != nil {
   394  		log.Errorf("Unable to fetch machines due to '%v'", zap.Error(err))
   395  		return err
   396  	}
   397  
   398  	if machinesFetched == nil {
   399  		log.Infof("No machines for cluster '%s' in namespace '%s' was detected", clusterName, namespace)
   400  	}
   401  
   402  	log.Infof("OCNE machine details:")
   403  	writer := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight)
   404  	fmt.Fprintln(writer, "Name\tCluster\tNodename\tProviderID\tPhase\tAge\tVersion")
   405  
   406  	var healthTracker []bool
   407  
   408  	for _, ma := range machinesFetched.Items {
   409  		var machine Machine
   410  		bdata, err := json.Marshal(ma.Object)
   411  		if err != nil {
   412  			log.Errorf("Json marshalling error %v", zap.Error(err))
   413  			return err
   414  		}
   415  		err = json.Unmarshal(bdata, &machine)
   416  		if err != nil {
   417  			log.Errorf("Json unmarshall error %v", zap.Error(err))
   418  			return err
   419  		}
   420  		fmt.Fprintf(writer, "%v\n", fmt.Sprintf("%v\t%v\t%v\t%v\t%v\t%v\t%v",
   421  			machine.Metadata.Name, machine.Metadata.Labels.ClusterXK8SIoClusterName, machine.Status.NodeRef.Name,
   422  			machine.Spec.ProviderID, machine.Status.Phase, time.Until(machine.Metadata.CreationTimestamp).Abs(), machine.Spec.Version))
   423  
   424  		if strings.ToLower(machine.Status.Phase) == "running" {
   425  			healthTracker = append(healthTracker, true)
   426  		} else {
   427  			healthTracker = append(healthTracker, false)
   428  		}
   429  	}
   430  	writer.Flush()
   431  
   432  	if checkAll(healthTracker) {
   433  		log.Infof("All machines for cluster '%s' in namesapce '%s' are in 'Running' state", clusterName, namespace)
   434  		return nil
   435  	}
   436  
   437  	msg := fmt.Sprintf("Not all machines for cluster '%s' in namesapce '%s' are in 'Running' state", clusterName, namespace)
   438  	log.Errorf(msg)
   439  	return fmt.Errorf(msg)
   440  }
   441  
   442  func (c CAPITestImpl) MonitorCapiClusterDeletion(clusterName string, log *zap.SugaredLogger) error {
   443  	var err error
   444  	config, err := k8sutil.GetKubeConfig()
   445  	if err != nil {
   446  		log.Errorf("Unable to fetch kubeconfig %v", zap.Error(err))
   447  		return err
   448  	}
   449  	dclient, err := dynamic.NewForConfig(config)
   450  	if err != nil {
   451  		log.Errorf("Unable to create dynamic client %v", zap.Error(err))
   452  		return err
   453  	}
   454  
   455  	gvr := schema.GroupVersionResource{
   456  		Group:    "cluster.x-k8s.io",
   457  		Version:  "v1beta1",
   458  		Resource: "clusters",
   459  	}
   460  	var capiCluster Cluster
   461  
   462  	kluster, err := dclient.Resource(gvr).Namespace(OCNENamespace).Get(context.TODO(), clusterName, metav1.GetOptions{})
   463  	if err != nil {
   464  		if err != nil {
   465  			if apierrors.IsNotFound(err) {
   466  				log.Errorf("cluster resource %s not found", clusterName)
   467  				return nil
   468  			}
   469  			log.Errorf("Unable to fetch %s %s due to '%v'", clusterName, zap.Error(err))
   470  			return err
   471  		}
   472  	}
   473  	bdata, err := json.Marshal(kluster)
   474  	if err != nil {
   475  		log.Errorf("Json marshalling error %v", zap.Error(err))
   476  		return err
   477  	}
   478  	err = json.Unmarshal(bdata, &capiCluster)
   479  	if err != nil {
   480  		log.Errorf("Json unmarshall error %v", zap.Error(err))
   481  		return err
   482  	}
   483  
   484  	return fmt.Errorf("cluster '%s' is in '%s' state", clusterName, capiCluster.Status.Phase)
   485  }
   486  
   487  // MonitorCapiClusterCreation fetches the workload OCNE cluster elements and prints them as a formatted table.
   488  // Returns an error if Cluster is not Ready
   489  func (c CAPITestImpl) MonitorCapiClusterCreation(clusterName string, log *zap.SugaredLogger) error {
   490  	klusterData, err := c.GetCluster(OCNENamespace, clusterName, log)
   491  	if err != nil {
   492  		return err
   493  	}
   494  
   495  	log.Infof("----------- Cluster Events ---------------------")
   496  	err = c.ShowEvents(OCNENamespace, log)
   497  	if err != nil {
   498  		return err
   499  	}
   500  
   501  	log.Infof("----------- OCNE Control Plane ---------------------")
   502  	ocneCP, err := c.GetOCNEControlPlane(OCNENamespace, log)
   503  	if err != nil {
   504  		return err
   505  	}
   506  
   507  	writer := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight)
   508  	fmt.Fprintln(writer, "Name\tCluster\tInitialized\tReplicas\tUpdated\tUnavailable\tReady\tAge")
   509  	fmt.Fprintf(writer, "%v\n", fmt.Sprintf("%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v",
   510  		ocneCP.Metadata.Name, ocneCP.Metadata.Labels.ClusterXK8SIoClusterName, ocneCP.Status.Initialized, ocneCP.Status.Replicas,
   511  		ocneCP.Status.UpdatedReplicas, ocneCP.Status.UnavailableReplicas, ocneCP.Status.ReadyReplicas, time.Until(ocneCP.Metadata.CreationTimestamp).Abs()))
   512  	writer.Flush()
   513  
   514  	log.Infof("----------- CAPI Machines ---------------------")
   515  	err = c.EnsureMachinesAreProvisioned(OCNENamespace, clusterName, log)
   516  	if err != nil {
   517  		return err
   518  	}
   519  
   520  	// OCNE cluster is ready when both control plane and worker nodes are up
   521  	switch OCNEK8sVersion {
   522  	case "1.25.7", "1.25.11":
   523  		if klusterData.Status.ControlPlaneReady && klusterData.Status.InfrastructureReady {
   524  			log.Infof("Cluster '%s' phase is => '%s'. All machines are also in '%s' state.", clusterName, klusterData.Status.Phase, klusterData.Status.Phase)
   525  			return nil
   526  		}
   527  	default:
   528  		if klusterData.Status.InfrastructureReady {
   529  			log.Infof("Cluster '%s' phase is => '%s'. All machines are also in '%s' state.", clusterName, klusterData.Status.Phase, klusterData.Status.Phase)
   530  			return nil
   531  		}
   532  	}
   533  
   534  	msg := fmt.Sprintf("cluster '%s' not ready. cluster phase is in '%s' state.", clusterName, klusterData.Status.Phase)
   535  	log.Errorf(msg)
   536  	return fmt.Errorf(msg)
   537  }
   538  
   539  func (c CAPITestImpl) TriggerCapiClusterDeletion(clusterName, nameSpaceName string, log *zap.SugaredLogger) error {
   540  	var err error
   541  	config, err := k8sutil.GetKubeConfig()
   542  	if err != nil {
   543  		log.Errorf("Unable to fetch kubeconfig %v", zap.Error(err))
   544  		return err
   545  	}
   546  	dclient, err := dynamic.NewForConfig(config)
   547  	if err != nil {
   548  		log.Errorf("Unable to create dynamic client %v", zap.Error(err))
   549  		return err
   550  	}
   551  
   552  	gvr := schema.GroupVersionResource{
   553  		Group:    "cluster.x-k8s.io",
   554  		Version:  "v1beta1",
   555  		Resource: "clusters",
   556  	}
   557  
   558  	err = dclient.Resource(gvr).Namespace(nameSpaceName).Delete(context.TODO(), clusterName, metav1.DeleteOptions{})
   559  	if err != nil {
   560  		log.Errorf("Unable to delete cluster %s due to '%v'", clusterName, zap.Error(err))
   561  		return err
   562  	}
   563  	return nil
   564  }
   565  
   566  // ShowNodeInfo displays the nodes of workload OCNE cluster as a formatted table.
   567  func (c CAPITestImpl) ShowNodeInfo(client *kubernetes.Clientset, clustername string, log *zap.SugaredLogger) error {
   568  	writer := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight)
   569  	fmt.Fprintln(writer, "Name\tRole\tVersion\tInternalIP\tExternalIP\tOSImage\tKernelVersion\tContainerRuntime\tAge")
   570  	nodeList, err := client.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
   571  	if err != nil {
   572  		return errors.Wrap(err, fmt.Sprintf("failed to get list of nodes from cluster '%s'", clustername))
   573  	}
   574  	for _, node := range nodeList.Items {
   575  		labels := node.GetLabels()
   576  		_, nodeOK := labels[nodeLabel]
   577  		_, controlPlaneOK := labels[nodeLabel]
   578  		var role, internalIP string
   579  		if nodeOK {
   580  			role = strings.Split(nodeLabel, "/")[len(strings.Split(nodeLabel, "/"))-1]
   581  		}
   582  		if controlPlaneOK {
   583  			role = strings.Split(controlPlaneLabel, "/")[len(strings.Split(controlPlaneLabel, "/"))-1]
   584  		}
   585  
   586  		addresses := node.Status.Addresses
   587  		for _, address := range addresses {
   588  			if address.Type == "InternalIP" {
   589  				internalIP = address.Address
   590  				break
   591  			}
   592  		}
   593  		fmt.Fprintf(writer, "%v\n", fmt.Sprintf("%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v\t%v",
   594  			node.GetName(), role, node.Status.NodeInfo.KubeletVersion, internalIP, "None", node.Status.NodeInfo.OSImage, node.Status.NodeInfo.KernelVersion,
   595  			node.Status.NodeInfo.ContainerRuntimeVersion, time.Until(node.GetCreationTimestamp().Time).Abs()))
   596  	}
   597  	writer.Flush()
   598  	return nil
   599  }
   600  
   601  // ShowPodInfo displays the pods of workload OCNE cluster as a formatted table.
   602  func (c CAPITestImpl) ShowPodInfo(client *kubernetes.Clientset, clusterName string, log *zap.SugaredLogger) error {
   603  	nsList, err := client.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
   604  	if err != nil {
   605  		return errors.Wrap(err, fmt.Sprintf("failed to get list of namespaces from cluster '%s'", clusterName))
   606  	}
   607  	writer := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight)
   608  	fmt.Fprintln(writer, "Name\tNamespace\tStatus\tIP\tNode\tAge")
   609  	//var dnsPod, ccmPod, calicokubePod *v1.Pod
   610  	for _, ns := range nsList.Items {
   611  		podList, err := client.CoreV1().Pods(ns.Name).List(context.TODO(), metav1.ListOptions{})
   612  		if err != nil {
   613  			return errors.Wrap(err, fmt.Sprintf("failed to get list of pods from cluster '%s'", clusterName))
   614  		}
   615  		for _, pod := range podList.Items {
   616  			podData, err := client.CoreV1().Pods(ns.Name).Get(context.TODO(), pod.Name, metav1.GetOptions{})
   617  			if err != nil {
   618  				if apierrors.IsNotFound(err) {
   619  					log.Infof("No pods in namespace '%s'", ns.Name)
   620  				} else {
   621  					return errors.Wrap(err, fmt.Sprintf("failed to get pod '%s' from cluster '%s'", pod.Name, clusterName))
   622  				}
   623  			}
   624  
   625  			fmt.Fprintf(writer, "%v\n", fmt.Sprintf("%v\t%v\t%v\t%v\t%v\t%v",
   626  				podData.GetName(), podData.GetNamespace(), podData.Status.Phase, podData.Status.PodIP, podData.Spec.NodeName,
   627  				time.Until(podData.GetCreationTimestamp().Time).Abs()))
   628  		}
   629  	}
   630  	writer.Flush()
   631  	return nil
   632  }
   633  
   634  // ShowEvents displays the events from a specific namespace
   635  func (c CAPITestImpl) ShowEvents(namespace string, log *zap.SugaredLogger) error {
   636  	log.Infof("Showing events for namespace '%s'", namespace)
   637  	k8sclient, err := k8sutil.GetKubernetesClientset()
   638  	if err != nil {
   639  		log.Errorf("Failed to get clientset with error: %v", err)
   640  		return err
   641  	}
   642  
   643  	events, err := k8sclient.CoreV1().Events(namespace).List(context.TODO(), metav1.ListOptions{})
   644  	if err != nil {
   645  		log.Info("Failed to get events from namespace: %v", zap.Error(err))
   646  		return err
   647  	}
   648  
   649  	writer := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight)
   650  	fmt.Fprintln(writer, "Namespace\tLast Seen\tType\tReason\tObject\tMessage")
   651  
   652  	for _, event := range events.Items {
   653  		fmt.Fprintf(writer, "%v\n", fmt.Sprintf("%v\t%v\t%v\t%v\t%v\t%v",
   654  			event.Namespace, event.LastTimestamp, event.Type, event.Reason, fmt.Sprintf("%s/%s", event.InvolvedObject.Kind, event.InvolvedObject.Name),
   655  			event.Message))
   656  
   657  	}
   658  	writer.Flush()
   659  	return nil
   660  }
   661  
   662  // DisplayWorkloadClusterResources displays the pods of workload OCNE cluster as a formatted table.
   663  func (c CAPITestImpl) DisplayWorkloadClusterResources(clusterName string, log *zap.SugaredLogger) error {
   664  	client, err := c.GetCapiClusterK8sClient(clusterName, log)
   665  	if err != nil {
   666  		return errors.Wrap(err, "Failed to get k8s client for workload cluster")
   667  	}
   668  
   669  	log.Infof("----------- Node in workload cluster ---------------------")
   670  	err = c.ShowNodeInfo(client, clusterName, log)
   671  	if err != nil {
   672  		return err
   673  	}
   674  
   675  	log.Infof("----------- Pods running on workload cluster ---------------------")
   676  	return c.ShowPodInfo(client, clusterName, log)
   677  }
   678  
   679  // UpdateOCINSG allows updating nsg based on source subnet cidr using the source subnet name
   680  func (c CAPITestImpl) UpdateOCINSG(clusterName, nsgDisplayNameToUpdate, nsgDisplayNameInRule, info string, rule *SecurityRuleDetails, log *zap.SugaredLogger) error {
   681  	log.Infof("Updating NSG rules for cluster '%s' and nsg '%s' for '%s'", clusterName, nsgDisplayNameToUpdate, info)
   682  	oci, err := NewClient(GetOCIConfigurationProvider(log))
   683  	if err != nil {
   684  		log.Error("Unable to create OCI client %v", zap.Error(err))
   685  		return err
   686  	}
   687  
   688  	vcnID, err := oci.GetVcnIDByName(context.TODO(), OCICompartmentID, clusterName, log)
   689  	if err != nil {
   690  		return err
   691  	}
   692  
   693  	nsgID, err := oci.GetNsgIDByName(context.TODO(), OCICompartmentID, vcnID, nsgDisplayNameToUpdate, log)
   694  	if err != nil {
   695  		return err
   696  	}
   697  
   698  	ruleCIDR, err := oci.GetSubnetCIDRByName(context.TODO(), OCICompartmentID, vcnID, nsgDisplayNameInRule, log)
   699  	if err != nil {
   700  		return err
   701  	}
   702  	rule.Source = ruleCIDR
   703  
   704  	return oci.UpdateNSG(context.TODO(), nsgID, rule, log)
   705  }
   706  
   707  // UpdateOCINSGEW allows updating nsg based on vcn cidr
   708  func (c CAPITestImpl) UpdateOCINSGEW(clusterName, nsgDisplayNameToUpdate, info string, rule *SecurityRuleDetails, log *zap.SugaredLogger) error {
   709  	log.Infof("Updating NSG rules for cluster '%s' and nsg '%s' for '%s'", clusterName, nsgDisplayNameToUpdate, info)
   710  	oci, err := NewClient(GetOCIConfigurationProvider(log))
   711  	if err != nil {
   712  		log.Error("Unable to create OCI client %v", zap.Error(err))
   713  		return err
   714  	}
   715  
   716  	vcnID, err := oci.GetVcnIDByName(context.TODO(), OCICompartmentID, clusterName, log)
   717  	if err != nil {
   718  		return err
   719  	}
   720  
   721  	nsgID, err := oci.GetNsgIDByName(context.TODO(), OCICompartmentID, vcnID, nsgDisplayNameToUpdate, log)
   722  	if err != nil {
   723  		return err
   724  	}
   725  
   726  	ruleCIDR, err := oci.GetVcnCIDRByName(context.TODO(), OCICompartmentID, clusterName, log)
   727  	if err != nil {
   728  		return err
   729  	}
   730  	rule.Source = ruleCIDR
   731  
   732  	return oci.UpdateNSG(context.TODO(), nsgID, rule, log)
   733  }
   734  
   735  func (c CAPITestImpl) GetCapiClusterDynamicClient(clusterName string, log *zap.SugaredLogger) (dynamic.Interface, error) {
   736  	capiK8sConfig, err := c.GetCapiClusterKubeConfig(clusterName, log)
   737  	if err != nil {
   738  		return nil, err
   739  	}
   740  	tmpFile, err := os.CreateTemp(os.TempDir(), fmt.Sprintf("%s-kubeconfig", clusterName))
   741  	if err != nil {
   742  		log.Errorf("Failed to create temporary file : %v", zap.Error(err))
   743  		return nil, err
   744  	}
   745  
   746  	if err := os.WriteFile(tmpFile.Name(), capiK8sConfig, 0600); err != nil {
   747  		log.Errorf("failed to write to destination file : %v", zap.Error(err))
   748  		return nil, err
   749  	}
   750  
   751  	k8sRestConfig, err := k8sutil.GetKubeConfigGivenPathAndContext(tmpFile.Name(), fmt.Sprintf("%s-admin@%s", clusterName, clusterName))
   752  	if err != nil {
   753  		log.Errorf("failed to obtain k8s rest config : %v", zap.Error(err))
   754  		return nil, err
   755  	}
   756  
   757  	dclient, err := dynamic.NewForConfig(k8sRestConfig)
   758  	if err != nil {
   759  		log.Errorf("unable to create dynamic client for workload cluster %v", zap.Error(err))
   760  		return nil, err
   761  	}
   762  	return dclient, nil
   763  
   764  }
   765  
   766  func (c CAPITestImpl) GetVerrazzano(clusterName, namespace, vzinstallname string, log *zap.SugaredLogger) (*unstructured.Unstructured, error) {
   767  	dclient, err := c.GetCapiClusterDynamicClient(clusterName, log)
   768  	if err != nil {
   769  		log.Errorf("unable to get workload kubeconfig ", zap.Error(err))
   770  		return nil, err
   771  	}
   772  
   773  	gvr := schema.GroupVersionResource{
   774  		Group:    "install.verrazzano.io",
   775  		Version:  "v1beta1",
   776  		Resource: "verrazzanos",
   777  	}
   778  
   779  	return dclient.Resource(gvr).Namespace(namespace).Get(context.TODO(), vzinstallname, metav1.GetOptions{})
   780  }
   781  
   782  func (c CAPITestImpl) EnsureVerrazzano(clusterName string, log *zap.SugaredLogger) error {
   783  
   784  	vzFetched, err := c.GetVerrazzano(clusterName, "default", "workload-verrazzano", log)
   785  	if err != nil {
   786  		log.Errorf("unable to fetch vz resource from %s due to '%v'", clusterName, zap.Error(err))
   787  		return err
   788  	}
   789  	var vz Verrazzano
   790  	modBinaryData, err := json.Marshal(vzFetched)
   791  	if err != nil {
   792  		log.Error("json marshalling error ", zap.Error(err))
   793  		return err
   794  	}
   795  
   796  	err = json.Unmarshal(modBinaryData, &vz)
   797  	if err != nil {
   798  		log.Error("json unmarshalling error ", zap.Error(err))
   799  		return err
   800  	}
   801  
   802  	curState := "InstallStarted"
   803  	for _, cond := range vz.Status.Conditions {
   804  		if cond.Type == "InstallComplete" {
   805  			curState = cond.Type
   806  		}
   807  	}
   808  
   809  	writer := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight)
   810  	fmt.Fprintln(writer, "Name\tAvailable\tStatus\tVersion")
   811  	fmt.Fprintf(writer, "%v\n", fmt.Sprintf("%v\t%v\t%v\t%v",
   812  		vz.Metadata.Name, vz.Status.Available, curState, vz.Status.Version))
   813  	writer.Flush()
   814  
   815  	if curState == "InstallComplete" {
   816  		return nil
   817  	}
   818  	return fmt.Errorf("All components are not ready: Current State = %v", curState)
   819  }
   820  
   821  func (c CAPITestImpl) DebugSVCOutput(clusterName string, log *zap.SugaredLogger) error {
   822  
   823  	capiK8sConfig, err := c.GetCapiClusterKubeConfig(clusterName, log)
   824  	if err != nil {
   825  		return err
   826  	}
   827  	tmpFile, err := os.CreateTemp(os.TempDir(), clusterName)
   828  	if err != nil {
   829  		log.Error("Failed to create temporary file ", zap.Error(err))
   830  		return err
   831  	}
   832  
   833  	if err = os.WriteFile(tmpFile.Name(), capiK8sConfig, 0600); err != nil {
   834  		log.Error("failed to write to destination file ", zap.Error(err))
   835  		return err
   836  	}
   837  
   838  	var cmdArgs []string
   839  	var bcmd helpers.BashCommand
   840  	dockerSecretCommand := fmt.Sprintf("kubectl --kubeconfig %s get svc -A", tmpFile.Name())
   841  	cmdArgs = append(cmdArgs, "/bin/bash", "-c", dockerSecretCommand)
   842  	bcmd.CommandArgs = cmdArgs
   843  	debugCmdResponse := helpers.Runner(&bcmd, log)
   844  	if debugCmdResponse.CommandError != nil {
   845  		return debugCmdResponse.CommandError
   846  	}
   847  
   848  	cmdArgs = []string{}
   849  	dockerSecretCommand = fmt.Sprintf("kubectl --kubeconfig %s get pod -A", tmpFile.Name())
   850  	cmdArgs = append(cmdArgs, "/bin/bash", "-c", dockerSecretCommand)
   851  	bcmd.CommandArgs = cmdArgs
   852  	debugCmdResponse = helpers.Runner(&bcmd, log)
   853  	if debugCmdResponse.CommandError != nil {
   854  		return debugCmdResponse.CommandError
   855  	}
   856  
   857  	cmdArgs = []string{}
   858  	dockerSecretCommand = fmt.Sprintf("kubectl --kubeconfig %s get vz", tmpFile.Name())
   859  	cmdArgs = append(cmdArgs, "/bin/bash", "-c", dockerSecretCommand)
   860  	bcmd.CommandArgs = cmdArgs
   861  	debugCmdResponse = helpers.Runner(&bcmd, log)
   862  	if debugCmdResponse.CommandError != nil {
   863  		return debugCmdResponse.CommandError
   864  	}
   865  
   866  	return nil
   867  }
   868  
   869  func (c CAPITestImpl) CreateImagePullSecrets(clusterName string, log *zap.SugaredLogger) error {
   870  	log.Infof("Creating image pull secrets on workload cluster ...")
   871  
   872  	capiK8sConfig, err := c.GetCapiClusterKubeConfig(clusterName, log)
   873  	if err != nil {
   874  		return err
   875  	}
   876  	tmpFile, err := os.CreateTemp(os.TempDir(), clusterName)
   877  	if err != nil {
   878  		log.Error("Failed to create temporary file ", zap.Error(err))
   879  		return err
   880  	}
   881  
   882  	if err = os.WriteFile(tmpFile.Name(), capiK8sConfig, 0600); err != nil {
   883  		log.Error("failed to write to destination file ", zap.Error(err))
   884  		return err
   885  	}
   886  
   887  	var cmdArgs []string
   888  	var bcmd helpers.BashCommand
   889  	dockerSecretCommand := fmt.Sprintf("kubectl --kubeconfig %s create secret docker-registry %s --docker-server=%s --docker-username=%s --docker-password=%s", tmpFile.Name(), ImagePullSecret, DockerRepo, DockerCredsUser, DockerCredsPassword)
   890  	cmdArgs = append(cmdArgs, "/bin/bash", "-c", dockerSecretCommand)
   891  	bcmd.CommandArgs = cmdArgs
   892  	secretCreateResponse := helpers.Runner(&bcmd, log)
   893  	if secretCreateResponse.CommandError != nil {
   894  		return secretCreateResponse.CommandError
   895  	}
   896  
   897  	cmdArgs = []string{}
   898  	dockerSecretCommand = fmt.Sprintf("kubectl --kubeconfig %s create secret docker-registry %s --docker-server=%s --docker-username=%s --docker-password=%s -n verrazzano-install", tmpFile.Name(), ImagePullSecret, DockerRepo, DockerCredsUser, DockerCredsPassword)
   899  	cmdArgs = append(cmdArgs, "/bin/bash", "-c", dockerSecretCommand)
   900  	bcmd.CommandArgs = cmdArgs
   901  	secretCreateResponse = helpers.Runner(&bcmd, log)
   902  	if secretCreateResponse.CommandError != nil {
   903  		return secretCreateResponse.CommandError
   904  	}
   905  
   906  	cmdArgs = []string{}
   907  	dockerSecretCommand = fmt.Sprintf("kubectl --kubeconfig %s create secret docker-registry github-packages --docker-server=%s --docker-username=%s --docker-password=%s", tmpFile.Name(), DockerRepo, DockerCredsUser, DockerCredsPassword)
   908  	cmdArgs = append(cmdArgs, "/bin/bash", "-c", dockerSecretCommand)
   909  	bcmd.CommandArgs = cmdArgs
   910  	secretCreateResponse = helpers.Runner(&bcmd, log)
   911  	if secretCreateResponse.CommandError != nil {
   912  		return secretCreateResponse.CommandError
   913  	}
   914  
   915  	cmdArgs = []string{}
   916  	dockerSecretCommand = fmt.Sprintf("kubectl --kubeconfig %s create secret docker-registry ocr --docker-server=%s --docker-username=%s --docker-password=%s", tmpFile.Name(), DockerRepo, DockerCredsUser, DockerCredsPassword)
   917  	cmdArgs = append(cmdArgs, "/bin/bash", "-c", dockerSecretCommand)
   918  	bcmd.CommandArgs = cmdArgs
   919  	secretCreateResponse = helpers.Runner(&bcmd, log)
   920  	if secretCreateResponse.CommandError != nil {
   921  		return secretCreateResponse.CommandError
   922  	}
   923  
   924  	return nil
   925  
   926  }
   927  
   928  func (c CAPITestImpl) CheckOCNEControlPlaneStatus(clusterName, expectedStatusType, expectedStatus, expectedReason string, log *zap.SugaredLogger) bool {
   929  	ocneCP, err := c.GetOCNEControlPlane(OCNENamespace, log)
   930  	if err != nil {
   931  		log.Error("unable to fetch OCNE control plane : ", zap.Error(err))
   932  		return false
   933  	}
   934  
   935  	for _, cond := range ocneCP.Status.Conditions {
   936  		if cond.Type == expectedStatusType {
   937  			if cond.Status == expectedStatus {
   938  				switch cond.Status {
   939  				case "False":
   940  					if cond.Reason == expectedReason {
   941  						// all matched
   942  						return true
   943  					}
   944  				case "True":
   945  					// reason is irrelevant
   946  					return true
   947  				}
   948  			}
   949  			log.Errorf("HAVE status type = %s, value = %s, reason = %s. WANT status type = %s, value= %s.", cond.Type, cond.Status, cond.Reason, expectedStatusType, expectedStatus)
   950  			return false
   951  		}
   952  	}
   953  
   954  	log.Errorf("OCNE controlplane check failure. All conditions %+v", ocneCP.Status.Conditions)
   955  	return false
   956  }
   957  
   958  // ToggleModules toggles module operator or VPO
   959  func (c CAPITestImpl) ToggleModules(group, version, resource, clusterName, nameSpaceName string, toggle bool, log *zap.SugaredLogger) error {
   960  
   961  	config, err := k8sutil.GetKubeConfig()
   962  	if err != nil {
   963  		log.Errorf("Unable to fetch kubeconfig %v", zap.Error(err))
   964  		return err
   965  	}
   966  	dclient, err := dynamic.NewForConfig(config)
   967  	if err != nil {
   968  		log.Errorf("Unable to create dynamic client %v", zap.Error(err))
   969  		return err
   970  	}
   971  
   972  	log.Infof("Fetching resource %s", resource)
   973  	gvr := schema.GroupVersionResource{
   974  		Group:    group,
   975  		Version:  version,
   976  		Resource: resource,
   977  	}
   978  
   979  	var patch []interface{}
   980  	if !toggle {
   981  		patch = []interface{}{
   982  			map[string]interface{}{
   983  				"op":   "replace",
   984  				"path": "/spec/topology/variables/0",
   985  				"value": map[string]interface{}{
   986  					"name":  "moduleOperatorEnabled",
   987  					"value": toggle,
   988  				},
   989  			},
   990  			map[string]interface{}{
   991  				"op":   "replace",
   992  				"path": "/spec/topology/variables/1",
   993  				"value": map[string]interface{}{
   994  					"name":  "verrazzanoPlatformOperatorEnabled",
   995  					"value": toggle,
   996  				},
   997  			},
   998  		}
   999  	} else {
  1000  		patch = []interface{}{
  1001  			map[string]interface{}{
  1002  				"op":   "replace",
  1003  				"path": "/spec/topology/variables/0",
  1004  				"value": map[string]interface{}{
  1005  					"name":  "moduleOperatorEnabled",
  1006  					"value": toggle,
  1007  				},
  1008  			},
  1009  			map[string]interface{}{
  1010  				"op":   "replace",
  1011  				"path": "/spec/topology/variables/1",
  1012  				"value": map[string]interface{}{
  1013  					"name":  "verrazzanoPlatformOperatorEnabled",
  1014  					"value": toggle,
  1015  				},
  1016  			},
  1017  			map[string]interface{}{
  1018  				"op":   "replace",
  1019  				"path": "/spec/topology/variables/2",
  1020  				"value": map[string]interface{}{
  1021  					"name":  "imagePullSecret",
  1022  					"value": ImagePullSecret,
  1023  				},
  1024  			},
  1025  			map[string]interface{}{
  1026  				"op":   "replace",
  1027  				"path": "/spec/topology/variables/3",
  1028  				"value": map[string]interface{}{
  1029  					"name":  "imageName",
  1030  					"value": ImageName,
  1031  				},
  1032  			},
  1033  			map[string]interface{}{
  1034  				"op":   "replace",
  1035  				"path": "/spec/topology/variables/4",
  1036  				"value": map[string]interface{}{
  1037  					"name":  "imageTag",
  1038  					"value": ImageTag,
  1039  				},
  1040  			},
  1041  		}
  1042  	}
  1043  
  1044  	payload, err := json.Marshal(patch)
  1045  	if err != nil {
  1046  		return err
  1047  	}
  1048  
  1049  	_, err = dclient.Resource(gvr).Namespace(nameSpaceName).Patch(context.TODO(), clusterName, types.JSONPatchType, payload, metav1.PatchOptions{})
  1050  	if err != nil {
  1051  		log.Errorf("unable to patch object %v", zap.Error(err))
  1052  		return err
  1053  	}
  1054  	return nil
  1055  }