github.com/verrazzano/verrazzano@v1.7.0/application-operator/mcagent/mcagent_cattle_agent.go (about)

     1  // Copyright (c) 2022, 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 mcagent
     5  
     6  import (
     7  	"bytes"
     8  	"crypto/sha256"
     9  	"fmt"
    10  	"strings"
    11  
    12  	"github.com/verrazzano/verrazzano/pkg/constants"
    13  	"github.com/verrazzano/verrazzano/pkg/k8s/resource"
    14  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    15  
    16  	"github.com/Jeffail/gabs/v2"
    17  	"go.uber.org/zap"
    18  	corev1 "k8s.io/api/core/v1"
    19  	"k8s.io/apimachinery/pkg/runtime/schema"
    20  	"k8s.io/apimachinery/pkg/types"
    21  	"k8s.io/apimachinery/pkg/util/yaml"
    22  	"sigs.k8s.io/controller-runtime/pkg/client"
    23  )
    24  
    25  const cattleAgent = "cattle-cluster-agent"
    26  
    27  // syncCattleClusterAgent syncs the Rancher cattle-cluster-agent deployment
    28  // and the cattle-credentials secret from the admin cluster to the managed cluster
    29  // if they have changed in the registration-manifest
    30  func (s *Syncer) syncCattleClusterAgent(currentCattleAgentHash string, kubeconfigPath string) (string, error) {
    31  	manifestSecret := corev1.Secret{}
    32  	err := s.AdminClient.Get(s.Context, client.ObjectKey{
    33  		Namespace: constants.VerrazzanoMultiClusterNamespace,
    34  		Name:      getManifestSecretName(s.ManagedClusterName),
    35  	}, &manifestSecret)
    36  
    37  	if err != nil {
    38  		return currentCattleAgentHash, fmt.Errorf("failed to fetch manifest secret for %s cluster: %v", s.ManagedClusterName, err)
    39  	}
    40  	s.Log.Debugf(fmt.Sprintf("Found manifest secret for %s cluster: %s", s.ManagedClusterName, manifestSecret.Name))
    41  
    42  	manifestData := manifestSecret.Data["yaml"]
    43  	yamlSections := bytes.Split(manifestData, []byte("---\n"))
    44  
    45  	cattleAgentResource, cattleCredentialResource := checkForCattleResources(yamlSections)
    46  	if cattleAgentResource == nil || cattleCredentialResource == nil {
    47  		s.Log.Debugf("The registration manifest doesn't contain the required resources. Will try to update the cattle-cluster-agent in the next iteration")
    48  		return currentCattleAgentHash, nil
    49  	}
    50  
    51  	newCattleAgentHash := createHash(cattleAgentResource)
    52  
    53  	// We have a previous hash to compare to
    54  	if len(currentCattleAgentHash) > 0 {
    55  		// If they are the same, do nothing
    56  		if currentCattleAgentHash == newCattleAgentHash {
    57  			return currentCattleAgentHash, nil
    58  		}
    59  	}
    60  
    61  	// No previous hash or the hash has changed
    62  	// Sync the cattle-agent and update the hash for next iterations
    63  	s.Log.Info("No previous cattle hash found or cattle hash has changed. Updating the cattle-cluster-agent")
    64  	err = updateCattleResources(cattleAgentResource, cattleCredentialResource, s.Log, kubeconfigPath)
    65  	if err != nil {
    66  		return currentCattleAgentHash, fmt.Errorf("failed to update the cattle-cluster-agent on %s cluster: %v", s.ManagedClusterName, err)
    67  	}
    68  	s.Log.Infof("Successfully synched cattle-cluster-agent")
    69  
    70  	return newCattleAgentHash, nil
    71  }
    72  
    73  // checkForCattleResources iterates through the list of resources in the manifest yaml
    74  // and returns the cattle-cluster-agent deployment and cattle-credentials secret if found
    75  func checkForCattleResources(yamlData [][]byte) (*gabs.Container, *gabs.Container) {
    76  	var cattleAgentResource, cattleCredentialResource *gabs.Container
    77  	for _, eachResource := range yamlData {
    78  		json, _ := yaml.ToJSON(eachResource)
    79  		container, _ := gabs.ParseJSON(json)
    80  
    81  		name := strings.Trim(container.Path("metadata.name").String(), "\"")
    82  		namespace := strings.Trim(container.Path("metadata.namespace").String(), "\"")
    83  		kind := strings.Trim(container.Path("kind").String(), "\"")
    84  
    85  		if name == cattleAgent && namespace == constants.RancherSystemNamespace && kind == "Deployment" {
    86  			cattleAgentResource = container
    87  		} else if strings.Contains(name, "cattle-credentials-") && namespace == constants.RancherSystemNamespace && kind == "Secret" {
    88  			cattleCredentialResource = container
    89  		}
    90  	}
    91  
    92  	return cattleAgentResource, cattleCredentialResource
    93  }
    94  
    95  // updateCattleResources patches the cattle-cluster-agent and creates the cattle-credentials secret
    96  func updateCattleResources(cattleAgentResource *gabs.Container, cattleCredentialResource *gabs.Container, log *zap.SugaredLogger, kubeconfigPath string) error {
    97  
    98  	config, err := k8sutil.BuildKubeConfig(kubeconfigPath)
    99  	if err != nil {
   100  		log.Errorf("failed to create incluster config: %v", err)
   101  		return err
   102  	}
   103  	log.Debugf("Built kubeconfig: %s, now updating resources", config.Host)
   104  
   105  	patch := cattleAgentResource.Bytes()
   106  	gvr := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}
   107  	err = resource.PatchResourceFromBytes(gvr, types.StrategicMergePatchType, constants.RancherSystemNamespace, cattleAgent, patch, config)
   108  	if err != nil {
   109  		log.Errorf("failed to patch cattle-cluster-agent: %v", err)
   110  		return err
   111  	}
   112  
   113  	err = resource.CreateOrUpdateResourceFromBytesUsingConfig(cattleCredentialResource.Bytes(), config)
   114  	if err != nil {
   115  		log.Errorf("failed to create new cattle-credential: %v", err)
   116  		return err
   117  	}
   118  	log.Debugf("Successfully patched cattle-cluster-agent and created a new cattle-credential secret")
   119  
   120  	return nil
   121  }
   122  
   123  // createHash returns a hash of the cattle-cluster-agent deployment
   124  func createHash(cattleAgent *gabs.Container) string {
   125  	data := cattleAgent.Path("spec.template.spec.containers.0").Bytes()
   126  	sha := sha256.New()
   127  	sha.Write(data)
   128  
   129  	return string(sha.Sum(nil))
   130  }
   131  
   132  // getManifestSecretName returns the manifest secret name for a managed cluster on the admin cluster
   133  func getManifestSecretName(clusterName string) string {
   134  	manifestSecretSuffix := "-manifest"
   135  	return generateManagedResourceName(clusterName) + manifestSecretSuffix
   136  }