github.com/verrazzano/verrazzano@v1.7.0/cluster-operator/controllers/vmc/rancher.go (about)

     1  // Copyright (c) 2021, 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 vmc
     5  
     6  import (
     7  	"crypto/md5" //nolint:gosec //#gosec G501 // package used for caching only, not security
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  
    12  	"github.com/Jeffail/gabs/v2"
    13  	cons "github.com/verrazzano/verrazzano/pkg/constants"
    14  	"github.com/verrazzano/verrazzano/pkg/httputil"
    15  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    16  	"github.com/verrazzano/verrazzano/pkg/mcconstants"
    17  	"github.com/verrazzano/verrazzano/pkg/rancherutil"
    18  	"github.com/verrazzano/verrazzano/platform-operator/constants"
    19  	corev1 "k8s.io/api/core/v1"
    20  	"k8s.io/apimachinery/pkg/api/equality"
    21  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    22  	"k8s.io/apimachinery/pkg/runtime/schema"
    23  	"k8s.io/apimachinery/pkg/util/json"
    24  	"k8s.io/apimachinery/pkg/util/yaml"
    25  	"sigs.k8s.io/controller-runtime/pkg/client"
    26  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    27  )
    28  
    29  const (
    30  	rancherNamespace   = "cattle-system"
    31  	rancherIngressName = "rancher"
    32  	rancherTLSSecret   = "tls-rancher-ingress" //nolint:gosec //#gosec G101
    33  
    34  	clusterPath          = "/v3/cluster"
    35  	clustersPath         = "/v3/clusters"
    36  	clustersByNamePath   = "/v3/clusters?name="
    37  	clusterRegTokenPath  = "/v3/clusterregistrationtoken" //nolint:gosec //#gosec G101
    38  	manifestPath         = "/v3/import/"
    39  	loginPath            = "/v3-public/localProviders/local?action=login"
    40  	secretPathTemplate   = "/api/v1/namespaces/%s/secrets/%s" //nolint:gosec //#gosec G101
    41  	secretCreateTemplate = "/api/v1/namespaces/%s/secrets"    //nolint:gosec //#gosec G101
    42  
    43  	k8sClustersPath = "/k8s/clusters/"
    44  
    45  	rancherClusterStateActive = "active"
    46  )
    47  
    48  type RancherCluster struct {
    49  	Name string
    50  	ID   string
    51  }
    52  
    53  // RegisterManagedClusterWithRancher registers a managed cluster with Rancher and returns a chunk of YAML that
    54  // must be applied on the managed cluster to complete the registration.
    55  func RegisterManagedClusterWithRancher(rc *rancherutil.RancherConfig, clusterName string, rancherClusterID string, log vzlog.VerrazzanoLogger) (string, string, error) {
    56  	clusterID := rancherClusterID
    57  	var err error
    58  	if clusterID == "" {
    59  		log.Oncef("Registering managed cluster in Rancher with name: %s", clusterName)
    60  		clusterID, err = ImportClusterToRancher(rc, clusterName, nil, log)
    61  		if err != nil {
    62  			log.Errorf("Failed to import cluster to Rancher: %v", err)
    63  			return "", "", err
    64  		}
    65  	}
    66  
    67  	log.Oncef("Getting registration YAML from Rancher for cluster %s with id %s", clusterName, clusterID)
    68  	regYAML, err := getRegistrationYAMLFromRancher(rc, clusterID, log)
    69  	if err != nil {
    70  		log.Errorf("Failed to get registration YAML from Rancher: %v", err)
    71  		return "", "", err
    72  	}
    73  
    74  	return regYAML, clusterID, nil
    75  }
    76  
    77  // ImportClusterToRancher uses the Rancher API to import the cluster. The cluster will show as "pending" until the registration
    78  // YAML is applied on the managed cluster.
    79  func ImportClusterToRancher(rc *rancherutil.RancherConfig, clusterName string, labels map[string]string, log vzlog.VerrazzanoLogger) (string, error) {
    80  	action := http.MethodPost
    81  
    82  	payload, err := makeClusterPayload(clusterName, labels)
    83  	if err != nil {
    84  		return "", err
    85  	}
    86  
    87  	reqURL := rc.BaseURL + clusterPath
    88  	headers := map[string]string{"Content-Type": "application/json"}
    89  	headers["Authorization"] = "Bearer " + rc.APIAccessToken
    90  
    91  	response, responseBody, err := rancherutil.SendRequest(action, reqURL, headers, payload, rc, log)
    92  
    93  	if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
    94  		// if we've already imported this cluster, we get an HTTP 422, so attempt to fetch the existing cluster
    95  		// and get the cluster ID from the response
    96  		log.Debugf("Cluster %s already registered with Rancher, attempting to fetch it", clusterName)
    97  		clusterID, err := GetClusterIDFromRancher(rc, clusterName, log)
    98  		if err != nil {
    99  			return "", err
   100  		}
   101  		return clusterID, nil
   102  	}
   103  
   104  	if err != nil {
   105  		return "", err
   106  	}
   107  
   108  	err = httputil.ValidateResponseCode(response, http.StatusCreated)
   109  	if err != nil {
   110  		return "", err
   111  	}
   112  	log.Oncef("Successfully registered managed cluster in Rancher with name: %s", clusterName)
   113  
   114  	return httputil.ExtractFieldFromResponseBodyOrReturnError(responseBody, "id", "unable to find cluster id in Rancher response")
   115  }
   116  
   117  // makeClusterPayload returns the payload for Rancher cluster creation, given a cluster name
   118  // and labels to apply to it
   119  func makeClusterPayload(clusterName string, labels map[string]string) (string, error) {
   120  	labelsJSONString, err := makeLabelsJSONString(labels)
   121  	if err != nil {
   122  		return "", err
   123  	}
   124  	payload := `{"type": "cluster",
   125  		"name":"` + clusterName + `",
   126  		"dockerRootDir": "/var/lib/docker",
   127  		"enableClusterAlerting": "false",
   128  		"enableClusterMonitoring": "false",
   129  		"enableNetworkPolicy": "false"`
   130  
   131  	if len(labelsJSONString) > 0 {
   132  		payload = fmt.Sprintf(`%s, "labels": %s }`, payload, labelsJSONString)
   133  	} else {
   134  		payload = fmt.Sprintf("%s}", payload)
   135  	}
   136  	return payload, nil
   137  }
   138  
   139  func makeLabelsJSONString(labels map[string]string) (string, error) {
   140  	if len(labels) == 0 {
   141  		return "", nil
   142  	}
   143  	labelsJSON, err := json.Marshal(labels)
   144  	if err != nil {
   145  		return "", err
   146  	}
   147  	return string(labelsJSON), nil
   148  }
   149  
   150  // DeleteClusterFromRancher uses the Rancher API to delete a cluster in Rancher.
   151  func DeleteClusterFromRancher(rc *rancherutil.RancherConfig, clusterID string, log vzlog.VerrazzanoLogger) (bool, error) {
   152  	action := http.MethodDelete
   153  	reqURL := rc.BaseURL + clustersPath + "/" + clusterID
   154  	headers := map[string]string{"Authorization": "Bearer " + rc.APIAccessToken}
   155  
   156  	response, _, err := rancherutil.SendRequest(action, reqURL, headers, "", rc, log)
   157  
   158  	if response != nil && response.StatusCode != http.StatusOK && response.StatusCode != http.StatusNotFound {
   159  		return false, fmt.Errorf("tried to delete cluster from Rancher but failed, response code: %d", response.StatusCode)
   160  	}
   161  
   162  	if err != nil {
   163  		return false, err
   164  	}
   165  
   166  	log.Oncef("Successfully deleted cluster %s from Rancher", clusterID)
   167  	return true, nil
   168  }
   169  
   170  // GetClusterIDFromRancher attempts to fetch the cluster from Rancher by name and pull out the cluster ID
   171  func GetClusterIDFromRancher(rc *rancherutil.RancherConfig, clusterName string, log vzlog.VerrazzanoLogger) (string, error) {
   172  	action := http.MethodGet
   173  
   174  	reqURL := rc.BaseURL + clustersByNamePath + clusterName
   175  	headers := map[string]string{"Authorization": "Bearer " + rc.APIAccessToken}
   176  
   177  	response, responseBody, err := rancherutil.SendRequest(action, reqURL, headers, "", rc, log)
   178  
   179  	if response != nil && response.StatusCode != http.StatusOK {
   180  		return "", fmt.Errorf("tried to get cluster from Rancher but failed, response code: %d", response.StatusCode)
   181  	}
   182  
   183  	if err != nil {
   184  		return "", err
   185  	}
   186  
   187  	return httputil.ExtractFieldFromResponseBodyOrReturnError(responseBody, "data.0.id", "unable to find clusterId in Rancher response")
   188  }
   189  
   190  // GetAllClustersInRancher returns cluster information for every cluster registered with Rancher
   191  func GetAllClustersInRancher(rc *rancherutil.RancherConfig, log vzlog.VerrazzanoLogger) ([]RancherCluster, []byte, error) {
   192  	reqURL := rc.BaseURL + clustersPath
   193  	headers := map[string]string{"Authorization": "Bearer " + rc.APIAccessToken}
   194  
   195  	hash := md5.New() //nolint:gosec //#gosec G401
   196  	clusters := []RancherCluster{}
   197  	for {
   198  		response, responseBody, err := rancherutil.SendRequest(http.MethodGet, reqURL, headers, "", rc, log)
   199  		if response != nil && response.StatusCode != http.StatusOK {
   200  			return nil, nil, fmt.Errorf("Unable to get clusters from Rancher, response code: %d", response.StatusCode)
   201  		}
   202  
   203  		if err != nil {
   204  			return nil, nil, err
   205  		}
   206  
   207  		// parse the response and iterate over the items
   208  		jsonString, err := gabs.ParseJSON([]byte(responseBody))
   209  		if err != nil {
   210  			return nil, nil, err
   211  		}
   212  
   213  		var items []interface{}
   214  		var ok bool
   215  		if items, ok = jsonString.Path("data").Data().([]interface{}); !ok {
   216  			return nil, nil, fmt.Errorf("Unable to find expected data in Rancher clusters response: %v", jsonString)
   217  		}
   218  
   219  		for _, item := range items {
   220  			var i map[string]interface{}
   221  			var ok bool
   222  			if i, ok = item.(map[string]interface{}); !ok {
   223  				log.Infof("Expected item to be of type 'map[string]interface{}': %s", responseBody)
   224  				continue
   225  			}
   226  			var name, id interface{}
   227  			if name, ok = i["name"]; !ok {
   228  				log.Infof("Expected to find 'name' field in Rancher cluster data: %s", responseBody)
   229  				continue
   230  			}
   231  			if id, ok = i["id"]; !ok {
   232  				log.Infof("Expected to find 'id' field in Rancher cluster data: %s", responseBody)
   233  				continue
   234  			}
   235  			cluster := RancherCluster{Name: name.(string), ID: id.(string)}
   236  			clusters = append(clusters, cluster)
   237  		}
   238  
   239  		// add this response body to the hash
   240  		io.WriteString(hash, responseBody)
   241  
   242  		// if there is a "next page" link then use that to make another request
   243  		if reqURL, err = httputil.ExtractFieldFromResponseBodyOrReturnError(responseBody, "pagination.next", ""); err != nil {
   244  			break
   245  		}
   246  	}
   247  
   248  	// unfortunately Rancher does not support ETags, so we return a hash of the response bodies which allows the caller to know if
   249  	// there were any changes to the clusters
   250  	return clusters, hash.Sum(nil), nil
   251  }
   252  
   253  // isManagedClusterActiveInRancher returns true if the managed cluster is active
   254  func isManagedClusterActiveInRancher(rc *rancherutil.RancherConfig, clusterID string, log vzlog.VerrazzanoLogger) (bool, error) {
   255  	reqURL := rc.BaseURL + clustersPath + "/" + clusterID
   256  	headers := map[string]string{"Authorization": "Bearer " + rc.APIAccessToken}
   257  
   258  	response, responseBody, err := rancherutil.SendRequest(http.MethodGet, reqURL, headers, "", rc, log)
   259  
   260  	if response != nil && response.StatusCode != http.StatusOK {
   261  		return false, fmt.Errorf("tried to get cluster from Rancher but failed, response code: %d", response.StatusCode)
   262  	}
   263  
   264  	if err != nil {
   265  		return false, err
   266  	}
   267  
   268  	state, err := httputil.ExtractFieldFromResponseBodyOrReturnError(responseBody, "state", "unable to find cluster state in Rancher response")
   269  	if err != nil {
   270  		return false, err
   271  	}
   272  	agentImage, err := httputil.ExtractFieldFromResponseBodyOrReturnError(responseBody, "agentImage", "unable to find agent image in Rancher response")
   273  	if err != nil {
   274  		return false, err
   275  	}
   276  
   277  	// Rancher temporarily sets the state of a new cluster to "active" before setting it to "pending", so we also check for the "agentImage" field
   278  	// to know that the cluster is really active
   279  	return state == rancherClusterStateActive && len(agentImage) > 0, nil
   280  }
   281  
   282  // getCACertFromManagedCluster attempts to get the CA cert from the managed cluster using the Rancher API proxy. It first checks for
   283  // the Rancher TLS secret and if that is not found it looks for the Verrazzano system TLS secret.
   284  func getCACertFromManagedCluster(rc *rancherutil.RancherConfig, clusterID string, log vzlog.VerrazzanoLogger) (string, error) {
   285  	// first look for the Rancher TLS secret
   286  	caCert, err := GetCACertFromManagedClusterSecret(rc, clusterID, rancherNamespace, cons.RancherTLSCA, cons.RancherTLSCAKey, log)
   287  	if err != nil {
   288  		return "", err
   289  	}
   290  
   291  	if caCert != "" {
   292  		return caCert, nil
   293  	}
   294  
   295  	// didn't find the Rancher secret so next look for the verrazzano-tls secret
   296  	caCert, err = GetCACertFromManagedClusterSecret(rc, clusterID, cons.VerrazzanoSystemNamespace, constants.VerrazzanoIngressSecret, mcconstants.CaCrtKey, log)
   297  	if err != nil {
   298  		return "", err
   299  	}
   300  
   301  	if caCert != "" {
   302  		return caCert, nil
   303  	}
   304  
   305  	return "", nil
   306  }
   307  
   308  // GetCACertFromManagedClusterSecret attempts to get the CA cert from a secret on the managed cluster using the Rancher API proxy
   309  func GetCACertFromManagedClusterSecret(rc *rancherutil.RancherConfig, clusterID, namespace, secretName, secretKey string, log vzlog.VerrazzanoLogger) (string, error) {
   310  	const k8sAPISecretPattern = "%s/api/v1/namespaces/%s/secrets/%s" //nolint:gosec //#gosec G101
   311  
   312  	// use the Rancher API proxy on the managed cluster to fetch the secret
   313  	baseReqURL := rc.BaseURL + k8sClustersPath + clusterID
   314  	headers := map[string]string{"Authorization": "Bearer " + rc.APIAccessToken}
   315  
   316  	reqURL := fmt.Sprintf(k8sAPISecretPattern, baseReqURL, namespace, secretName)
   317  	response, responseBody, err := rancherutil.SendRequest(http.MethodGet, reqURL, headers, "", rc, log)
   318  
   319  	if response != nil {
   320  		if response.StatusCode == http.StatusNotFound {
   321  			return "", nil
   322  		}
   323  		if response.StatusCode != http.StatusOK {
   324  			return "", fmt.Errorf("tried to get managed cluster CA cert %s/%s from Rancher but failed, response code: %d", namespace, secretName, response.StatusCode)
   325  		}
   326  	}
   327  	if err != nil {
   328  		return "", err
   329  	}
   330  
   331  	// parse the response and pull out the secretKey value from the secret data
   332  	jsonString, err := gabs.ParseJSON([]byte(responseBody))
   333  	if err != nil {
   334  		return "", err
   335  	}
   336  
   337  	if data, ok := jsonString.Path("data").Data().(map[string]interface{}); ok {
   338  		if caCert, ok := data[secretKey].(string); ok {
   339  			return caCert, nil
   340  		}
   341  	}
   342  
   343  	return "", nil
   344  }
   345  
   346  // isNamespaceCreated attempts to ascertain whether the given namesapce is created in the cluster
   347  func isNamespaceCreated(client client.Client, ingressHost, clusterID, namespace string, log vzlog.VerrazzanoLogger) (bool, error) {
   348  	const k8sAPISecretPattern = "%s/api/v1/namespaces/%s" //nolint:gosec //#gosec G101
   349  
   350  	rc, err := rancherutil.NewAdminRancherConfig(client, ingressHost, log)
   351  	if err != nil || rc == nil {
   352  		return false, err
   353  	}
   354  
   355  	// use the Rancher API proxy on the managed cluster to fetch the secret
   356  	baseReqURL := rc.BaseURL + k8sClustersPath + clusterID
   357  	headers := map[string]string{"Authorization": "Bearer " + rc.APIAccessToken}
   358  
   359  	reqURL := fmt.Sprintf(k8sAPISecretPattern, baseReqURL, namespace)
   360  	response, _, err := rancherutil.SendRequest(http.MethodGet, reqURL, headers, "", rc, log)
   361  
   362  	if response != nil {
   363  		if response.StatusCode == http.StatusNotFound {
   364  			return false, nil
   365  		}
   366  		if response.StatusCode != http.StatusOK {
   367  			return false, fmt.Errorf("tried to get namespace %s via Rancher from clsuter %s but failed, response code: %d", namespace, clusterID, response.StatusCode)
   368  		}
   369  	}
   370  	if err != nil {
   371  		return false, err
   372  	}
   373  
   374  	return true, nil
   375  }
   376  
   377  // getRegistrationYAMLFromRancher creates a registration token in Rancher for the managed cluster and uses the
   378  // returned token to fetch the registration (manifest) YAML.
   379  func getRegistrationYAMLFromRancher(rc *rancherutil.RancherConfig, rancherClusterID string, log vzlog.VerrazzanoLogger) (string, error) {
   380  	headers := map[string]string{"Content-Type": "application/json"}
   381  	headers["Authorization"] = "Bearer " + rc.APIAccessToken
   382  
   383  	var token string
   384  	token, err := getRegistrationTokenFromRancher(rc, rancherClusterID, log)
   385  	if err != nil {
   386  		log.Oncef("Unable to fetch existing cluster registration token for cluster: %s with reason: %v, continuing to create a new token", rancherClusterID, err)
   387  	}
   388  
   389  	if token == "" {
   390  		action := http.MethodPost
   391  		payload := `{"type": "clusterRegistrationToken", "clusterId": "` + rancherClusterID + `"}`
   392  		reqURL := rc.BaseURL + clusterRegTokenPath
   393  
   394  		log.Infof("Creating cluster registration token for cluster %s", rancherClusterID)
   395  		response, manifestContent, err := rancherutil.SendRequest(action, reqURL, headers, payload, rc, log)
   396  		if err != nil {
   397  			return "", err
   398  		}
   399  
   400  		err = httputil.ValidateResponseCode(response, http.StatusCreated)
   401  		if err != nil {
   402  			return "", err
   403  		}
   404  
   405  		// get the manifest token from the response, construct a URL, and fetch its contents
   406  		token, err = httputil.ExtractFieldFromResponseBodyOrReturnError(manifestContent, "token", "unable to find manifest token in Rancher response")
   407  		if err != nil {
   408  			return "", err
   409  		}
   410  	}
   411  	// Rancher 2.5.x added the cluster ID to the manifest URL.
   412  	manifestURL := rc.BaseURL + manifestPath + token + "_" + rancherClusterID + ".yaml"
   413  
   414  	action := http.MethodGet
   415  	response, manifestContent, err := rancherutil.SendRequest(action, manifestURL, headers, "", rc, log)
   416  
   417  	if err != nil {
   418  		return "", err
   419  	}
   420  
   421  	err = httputil.ValidateResponseCode(response, http.StatusOK)
   422  	if err != nil {
   423  		return "", err
   424  	}
   425  
   426  	return manifestContent, nil
   427  }
   428  
   429  type ClusterRegistrationTokens struct {
   430  	ClusterID string `json:"clusterId"`
   431  	State     string `json:"state"`
   432  	Token     string `json:"token"`
   433  }
   434  
   435  func getRegistrationTokenFromRancher(rc *rancherutil.RancherConfig, rancherClusterID string, log vzlog.VerrazzanoLogger) (string, error) {
   436  
   437  	action := http.MethodGet
   438  	reqURL := rc.BaseURL + clusterRegTokenPath + "?state=active&clusterId=" + rancherClusterID
   439  	headers := map[string]string{"Content-Type": "application/json"}
   440  	headers["Authorization"] = "Bearer " + rc.APIAccessToken
   441  
   442  	response, manifestContent, err := rancherutil.SendRequest(action, reqURL, headers, "{}", rc, log)
   443  	if err != nil {
   444  		return "", err
   445  	}
   446  
   447  	err = httputil.ValidateResponseCode(response, http.StatusOK)
   448  	if err != nil {
   449  		return "", err
   450  	}
   451  
   452  	data, err := httputil.ExtractFieldFromResponseBodyOrReturnError(manifestContent, "data", "unable to find data token in Rancher response")
   453  	if err != nil {
   454  		return "", err
   455  	}
   456  
   457  	var items []ClusterRegistrationTokens
   458  	json.Unmarshal([]byte(data), &items)
   459  	for _, item := range items {
   460  		if item.ClusterID == rancherClusterID && item.State == "active" {
   461  			log.Oncef("ClusterRegistrationToken exists for the cluster %s", rancherClusterID)
   462  			return item.Token, nil
   463  		}
   464  	}
   465  
   466  	log.Oncef("No existing ClusterRegistrationToken found for cluster %s", rancherClusterID)
   467  	return "", nil
   468  }
   469  
   470  // createOrUpdateSecretRancherProxy simulates the controllerutil create or update function through the Rancher Proxy API for secrets
   471  func createOrUpdateSecretRancherProxy(secret *corev1.Secret, rc *rancherutil.RancherConfig, clusterID string, f controllerutil.MutateFn, log vzlog.VerrazzanoLogger) (controllerutil.OperationResult, error) {
   472  	log.Debugf("Creating or Updating Secret %s/%s", secret.GetNamespace(), secret.GetName())
   473  	if err := rancherSecretGet(secret, rc, clusterID, log); err != nil {
   474  		if !apierrors.IsNotFound(err) {
   475  			return controllerutil.OperationResultNone, err
   476  		}
   477  		if err := rancherSecretMutate(f, secret, log); err != nil {
   478  			return controllerutil.OperationResultNone, err
   479  		}
   480  		if err := rancherSecretCreate(secret, rc, clusterID, log); err != nil {
   481  			return controllerutil.OperationResultNone, err
   482  		}
   483  		return controllerutil.OperationResultCreated, nil
   484  	}
   485  
   486  	existingSec := secret.DeepCopyObject()
   487  	if err := rancherSecretMutate(f, secret, log); err != nil {
   488  		return controllerutil.OperationResultNone, err
   489  	}
   490  	if equality.Semantic.DeepEqual(existingSec, secret) {
   491  		return controllerutil.OperationResultNone, nil
   492  	}
   493  	if err := rancherSecretUpdate(secret, rc, clusterID, log); err != nil {
   494  		return controllerutil.OperationResultNone, err
   495  	}
   496  	return controllerutil.OperationResultUpdated, nil
   497  }
   498  
   499  // rancherSecretMutate mutates the rancher secret from the given Mutate function
   500  func rancherSecretMutate(f controllerutil.MutateFn, secret *corev1.Secret, log vzlog.VerrazzanoLogger) error {
   501  	key := client.ObjectKeyFromObject(secret)
   502  	if err := f(); err != nil {
   503  		return err
   504  	}
   505  	if newKey := client.ObjectKeyFromObject(secret); key != newKey {
   506  		return log.ErrorfNewErr("MutateFn cannot mutate secret name and/or secret namespace")
   507  	}
   508  	return nil
   509  }
   510  
   511  // rancherSecretGet simulates a client get request through the Rancher proxy for secrets
   512  func rancherSecretGet(secret *corev1.Secret, rc *rancherutil.RancherConfig, clusterID string, log vzlog.VerrazzanoLogger) error {
   513  	if secret == nil {
   514  		return log.ErrorNewErr("Failed to get secret, nil value passed to get request")
   515  	}
   516  	reqURL := constructSecretURL(secret, rc.Host, clusterID, false)
   517  	headers := map[string]string{"Authorization": "Bearer " + rc.APIAccessToken}
   518  	resp, body, err := rancherutil.SendRequest(http.MethodGet, reqURL, headers, "", rc, log)
   519  	if err != nil && (resp == nil || resp.StatusCode != 404) {
   520  		return err
   521  	}
   522  	if resp == nil {
   523  		return log.ErrorfNewErr("Failed to find response from GET request %s", secret.GetNamespace(), secret.GetName(), reqURL)
   524  	}
   525  	if resp.StatusCode == http.StatusNotFound {
   526  		return apierrors.NewNotFound(schema.ParseGroupResource("Secret"), secret.GetName())
   527  	}
   528  	if resp.StatusCode != http.StatusOK {
   529  		return log.ErrorfNewErr("Failed to get secret %s/%s from GET request %s with code %d", secret.GetNamespace(), secret.GetName(), reqURL, resp.StatusCode)
   530  	}
   531  
   532  	// Unmarshall the response body into the secret object, simulating a typical Get request
   533  	err = yaml.Unmarshal([]byte(body), secret)
   534  	if err != nil {
   535  		return log.ErrorfNewErr("Failed to unmarshall response body into secret %s/%s from GET request %s: %v", secret.GetNamespace(), secret.GetName(), reqURL, err)
   536  	}
   537  	return nil
   538  }
   539  
   540  // rancherSecretCreate simulates a client create request through the Rancher proxy for secrets
   541  func rancherSecretCreate(secret *corev1.Secret, rc *rancherutil.RancherConfig, clusterID string, log vzlog.VerrazzanoLogger) error {
   542  	if secret == nil {
   543  		return log.ErrorNewErr("Failed to create secret, nil value passed to create request")
   544  	}
   545  	reqURL := constructSecretURL(secret, rc.Host, clusterID, true)
   546  	payload, err := json.Marshal(secret)
   547  	if err != nil {
   548  		return log.ErrorfNewErr("Failed to marshall secret %s/%s: %v", secret.GetNamespace(), secret.GetName(), err)
   549  	}
   550  	headers := map[string]string{
   551  		"Authorization": "Bearer " + rc.APIAccessToken,
   552  		"Content-Type":  "application/json",
   553  	}
   554  	resp, _, err := rancherutil.SendRequest(http.MethodPost, reqURL, headers, string(payload), rc, log)
   555  	if err != nil {
   556  		return err
   557  	}
   558  
   559  	if resp == nil {
   560  		return log.ErrorfNewErr("Failed to find response from POST request %s", secret.GetNamespace(), secret.GetName(), reqURL)
   561  	}
   562  	if resp.StatusCode != http.StatusCreated {
   563  		return log.ErrorfNewErr("Failed to create secret %s/%s from POST request %s with code %d", secret.GetNamespace(), secret.GetName(), reqURL, resp.StatusCode)
   564  	}
   565  	return nil
   566  }
   567  
   568  // rancherSecretUpdate simulates a client update request through the Rancher proxy for secrets
   569  func rancherSecretUpdate(secret *corev1.Secret, rc *rancherutil.RancherConfig, clusterID string, log vzlog.VerrazzanoLogger) error {
   570  	if secret == nil {
   571  		return log.ErrorNewErr("Failed to update secret, nil value passed to update request")
   572  	}
   573  	reqURL := constructSecretURL(secret, rc.Host, clusterID, false)
   574  	payload, err := json.Marshal(secret)
   575  	if err != nil {
   576  		return log.ErrorfNewErr("Failed to marshall secret %s/%s: %v", secret.GetNamespace(), secret.GetName(), err)
   577  	}
   578  	headers := map[string]string{
   579  		"Authorization": "Bearer " + rc.APIAccessToken,
   580  		"Content-Type":  "application/json",
   581  	}
   582  	resp, _, err := rancherutil.SendRequest(http.MethodPut, reqURL, headers, string(payload), rc, log)
   583  	if err != nil {
   584  		return err
   585  	}
   586  
   587  	if resp == nil {
   588  		return log.ErrorfNewErr("Failed to find response from PUT request %s", secret.GetNamespace(), secret.GetName(), reqURL)
   589  	}
   590  	if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
   591  		return log.ErrorfNewErr("Failed to create secret %s/%s from PUT request %s with code %d", secret.GetNamespace(), secret.GetName(), reqURL, resp.StatusCode)
   592  	}
   593  	return nil
   594  }
   595  
   596  // constructSecretURL returns a formatted url string from path requirements and objects
   597  func constructSecretURL(secret *corev1.Secret, host, clusterID string, create bool) string {
   598  	if create {
   599  		return "https://" + host + k8sClustersPath + clusterID + fmt.Sprintf(secretCreateTemplate, secret.GetNamespace())
   600  	}
   601  	return "https://" + host + k8sClustersPath + clusterID + fmt.Sprintf(secretPathTemplate, secret.GetNamespace(), secret.GetName())
   602  }