github.com/argoproj/argo-cd@v1.8.7/util/db/cluster.go (about)

     1  package db
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"hash/fnv"
     7  	"net/url"
     8  	"strconv"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	log "github.com/sirupsen/logrus"
    14  	"golang.org/x/net/context"
    15  	"google.golang.org/grpc/codes"
    16  	"google.golang.org/grpc/status"
    17  	apiv1 "k8s.io/api/core/v1"
    18  	apierr "k8s.io/apimachinery/pkg/api/errors"
    19  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    20  	"k8s.io/apimachinery/pkg/fields"
    21  	"k8s.io/apimachinery/pkg/labels"
    22  	"k8s.io/apimachinery/pkg/selection"
    23  	"k8s.io/apimachinery/pkg/watch"
    24  	informerv1 "k8s.io/client-go/informers/core/v1"
    25  	"k8s.io/client-go/tools/cache"
    26  	"k8s.io/utils/pointer"
    27  
    28  	"github.com/argoproj/argo-cd/common"
    29  	appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
    30  )
    31  
    32  var (
    33  	localCluster = appv1.Cluster{
    34  		Name:            "in-cluster",
    35  		Server:          common.KubernetesInternalAPIServerAddr,
    36  		ConnectionState: appv1.ConnectionState{Status: appv1.ConnectionStatusSuccessful},
    37  	}
    38  	initLocalCluster sync.Once
    39  )
    40  
    41  func (db *db) getLocalCluster() *appv1.Cluster {
    42  	initLocalCluster.Do(func() {
    43  		info, err := db.kubeclientset.Discovery().ServerVersion()
    44  		if err == nil {
    45  			localCluster.ServerVersion = fmt.Sprintf("%s.%s", info.Major, info.Minor)
    46  			localCluster.ConnectionState = appv1.ConnectionState{Status: appv1.ConnectionStatusSuccessful}
    47  		} else {
    48  			localCluster.ConnectionState = appv1.ConnectionState{
    49  				Status:  appv1.ConnectionStatusFailed,
    50  				Message: err.Error(),
    51  			}
    52  		}
    53  	})
    54  	cluster := localCluster.DeepCopy()
    55  	now := metav1.Now()
    56  	cluster.ConnectionState.ModifiedAt = &now
    57  	return cluster
    58  }
    59  
    60  func (db *db) listClusterSecrets() ([]*apiv1.Secret, error) {
    61  	labelSelector := labels.NewSelector()
    62  	req, err := labels.NewRequirement(common.LabelKeySecretType, selection.Equals, []string{common.LabelValueSecretTypeCluster})
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	labelSelector = labelSelector.Add(*req)
    67  
    68  	secretsLister, err := db.settingsMgr.GetSecretsLister()
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	clusterSecrets, err := secretsLister.Secrets(db.ns).List(labelSelector)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	return clusterSecrets, nil
    77  }
    78  
    79  // ListClusters returns list of clusters
    80  func (db *db) ListClusters(ctx context.Context) (*appv1.ClusterList, error) {
    81  	clusterSecrets, err := db.listClusterSecrets()
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	clusterList := appv1.ClusterList{
    86  		Items: make([]appv1.Cluster, len(clusterSecrets)),
    87  	}
    88  	hasInClusterCredentials := false
    89  	for i, clusterSecret := range clusterSecrets {
    90  		cluster := *secretToCluster(clusterSecret)
    91  		clusterList.Items[i] = cluster
    92  		if cluster.Server == common.KubernetesInternalAPIServerAddr {
    93  			hasInClusterCredentials = true
    94  		}
    95  	}
    96  	if !hasInClusterCredentials {
    97  		clusterList.Items = append(clusterList.Items, *db.getLocalCluster())
    98  	}
    99  	return &clusterList, nil
   100  }
   101  
   102  // CreateCluster creates a cluster
   103  func (db *db) CreateCluster(ctx context.Context, c *appv1.Cluster) (*appv1.Cluster, error) {
   104  	secName, err := serverToSecretName(c.Server)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	clusterSecret := &apiv1.Secret{
   109  		ObjectMeta: metav1.ObjectMeta{
   110  			Name: secName,
   111  			Labels: map[string]string{
   112  				common.LabelKeySecretType: common.LabelValueSecretTypeCluster,
   113  			},
   114  			Annotations: map[string]string{
   115  				common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD,
   116  			},
   117  		},
   118  	}
   119  
   120  	if err = clusterToSecret(c, clusterSecret); err != nil {
   121  		return nil, err
   122  	}
   123  	clusterSecret, err = db.kubeclientset.CoreV1().Secrets(db.ns).Create(ctx, clusterSecret, metav1.CreateOptions{})
   124  	if err != nil {
   125  		if apierr.IsAlreadyExists(err) {
   126  			return nil, status.Errorf(codes.AlreadyExists, "cluster %q already exists", c.Server)
   127  		}
   128  		return nil, err
   129  	}
   130  	return secretToCluster(clusterSecret), db.settingsMgr.ResyncInformers()
   131  }
   132  
   133  // ClusterEvent contains information about cluster event
   134  type ClusterEvent struct {
   135  	Type    watch.EventType
   136  	Cluster *appv1.Cluster
   137  }
   138  
   139  func (db *db) WatchClusters(ctx context.Context,
   140  	handleAddEvent func(cluster *appv1.Cluster),
   141  	handleModEvent func(oldCluster *appv1.Cluster, newCluster *appv1.Cluster),
   142  	handleDeleteEvent func(clusterServer string)) error {
   143  	localCls, err := db.GetCluster(ctx, common.KubernetesInternalAPIServerAddr)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	handleAddEvent(localCls)
   148  	clusterSecretListOptions := func(options *metav1.ListOptions) {
   149  		clusterLabelSelector := fields.ParseSelectorOrDie(common.LabelKeySecretType + "=" + common.LabelValueSecretTypeCluster)
   150  		options.LabelSelector = clusterLabelSelector.String()
   151  	}
   152  	clusterEventHandler := cache.ResourceEventHandlerFuncs{
   153  		AddFunc: func(obj interface{}) {
   154  			if secretObj, ok := obj.(*apiv1.Secret); ok {
   155  				cluster := secretToCluster(secretObj)
   156  				if cluster.Server == common.KubernetesInternalAPIServerAddr {
   157  					// change local cluster event to modified or deleted, since it cannot be re-added or deleted
   158  					handleModEvent(localCls, cluster)
   159  					localCls = cluster
   160  					return
   161  				}
   162  				handleAddEvent(cluster)
   163  			}
   164  		},
   165  		DeleteFunc: func(obj interface{}) {
   166  			if secretObj, ok := obj.(*apiv1.Secret); ok {
   167  				if string(secretObj.Data["server"]) == common.KubernetesInternalAPIServerAddr {
   168  					// change local cluster event to modified or deleted, since it cannot be re-added or deleted
   169  					handleModEvent(localCls, db.getLocalCluster())
   170  					localCls = db.getLocalCluster()
   171  				} else {
   172  					handleDeleteEvent(string(secretObj.Data["server"]))
   173  				}
   174  			}
   175  		},
   176  		UpdateFunc: func(oldObj, newObj interface{}) {
   177  			if oldSecretObj, ok := oldObj.(*apiv1.Secret); ok {
   178  				if newSecretObj, ok := newObj.(*apiv1.Secret); ok {
   179  					oldCluster := secretToCluster(oldSecretObj)
   180  					newCluster := secretToCluster(newSecretObj)
   181  					if newCluster.Server == common.KubernetesInternalAPIServerAddr {
   182  						localCls = newCluster
   183  					}
   184  					handleModEvent(oldCluster, newCluster)
   185  				}
   186  			}
   187  		},
   188  	}
   189  	indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
   190  	clusterSecretInformer := informerv1.NewFilteredSecretInformer(db.kubeclientset, db.ns, 3*time.Minute, indexers, clusterSecretListOptions)
   191  	clusterSecretInformer.AddEventHandler(clusterEventHandler)
   192  	log.Info("Starting clusterSecretInformer informers")
   193  	go func() {
   194  		clusterSecretInformer.Run(ctx.Done())
   195  		log.Info("clusterSecretInformer cancelled")
   196  	}()
   197  
   198  	<-ctx.Done()
   199  	return err
   200  }
   201  
   202  func (db *db) getClusterSecret(server string) (*apiv1.Secret, error) {
   203  	clusterSecrets, err := db.listClusterSecrets()
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  	for _, clusterSecret := range clusterSecrets {
   208  		if secretToCluster(clusterSecret).Server == strings.TrimRight(server, "/") {
   209  			return clusterSecret, nil
   210  		}
   211  	}
   212  	return nil, status.Errorf(codes.NotFound, "cluster %q not found", server)
   213  }
   214  
   215  // GetCluster returns a cluster from a query
   216  func (db *db) GetCluster(ctx context.Context, server string) (*appv1.Cluster, error) {
   217  	clusterSecret, err := db.getClusterSecret(server)
   218  	if err != nil {
   219  		if errorStatus, ok := status.FromError(err); ok && errorStatus.Code() == codes.NotFound && server == common.KubernetesInternalAPIServerAddr {
   220  			return db.getLocalCluster(), nil
   221  		} else {
   222  			return nil, err
   223  		}
   224  	}
   225  	return secretToCluster(clusterSecret), nil
   226  }
   227  
   228  // UpdateCluster updates a cluster
   229  func (db *db) UpdateCluster(ctx context.Context, c *appv1.Cluster) (*appv1.Cluster, error) {
   230  	clusterSecret, err := db.getClusterSecret(c.Server)
   231  	if err != nil {
   232  		if status.Code(err) == codes.NotFound {
   233  			return db.CreateCluster(ctx, c)
   234  		}
   235  		return nil, err
   236  	}
   237  	if err := clusterToSecret(c, clusterSecret); err != nil {
   238  		return nil, err
   239  	}
   240  
   241  	clusterSecret, err = db.kubeclientset.CoreV1().Secrets(db.ns).Update(ctx, clusterSecret, metav1.UpdateOptions{})
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	return secretToCluster(clusterSecret), db.settingsMgr.ResyncInformers()
   246  }
   247  
   248  // Delete deletes a cluster by name
   249  func (db *db) DeleteCluster(ctx context.Context, server string) error {
   250  	secret, err := db.getClusterSecret(server)
   251  	if err != nil {
   252  		return err
   253  	}
   254  
   255  	canDelete := secret.Annotations != nil && secret.Annotations[common.AnnotationKeyManagedBy] == common.AnnotationValueManagedByArgoCD
   256  
   257  	if canDelete {
   258  		err = db.kubeclientset.CoreV1().Secrets(db.ns).Delete(ctx, secret.Name, metav1.DeleteOptions{})
   259  	} else {
   260  		delete(secret.Labels, common.LabelKeySecretType)
   261  		_, err = db.kubeclientset.CoreV1().Secrets(db.ns).Update(ctx, secret, metav1.UpdateOptions{})
   262  	}
   263  	if err != nil {
   264  		return err
   265  	}
   266  	return db.settingsMgr.ResyncInformers()
   267  }
   268  
   269  // serverToSecretName hashes server address to the secret name using a formula.
   270  // Part of the server address is incorporated for debugging purposes
   271  func serverToSecretName(server string) (string, error) {
   272  	serverURL, err := url.ParseRequestURI(server)
   273  	if err != nil {
   274  		return "", err
   275  	}
   276  	h := fnv.New32a()
   277  	_, _ = h.Write([]byte(server))
   278  	host := strings.ToLower(strings.Split(serverURL.Host, ":")[0])
   279  	return fmt.Sprintf("cluster-%s-%v", host, h.Sum32()), nil
   280  }
   281  
   282  // clusterToData converts a cluster object to string data for serialization to a secret
   283  func clusterToSecret(c *appv1.Cluster, secret *apiv1.Secret) error {
   284  	data := make(map[string][]byte)
   285  	data["server"] = []byte(strings.TrimRight(c.Server, "/"))
   286  	if c.Name == "" {
   287  		data["name"] = []byte(c.Server)
   288  	} else {
   289  		data["name"] = []byte(c.Name)
   290  	}
   291  	if len(c.Namespaces) != 0 {
   292  		data["namespaces"] = []byte(strings.Join(c.Namespaces, ","))
   293  	}
   294  	configBytes, err := json.Marshal(c.Config)
   295  	if err != nil {
   296  		return err
   297  	}
   298  	data["config"] = configBytes
   299  	if c.Shard != nil {
   300  		data["shard"] = []byte(strconv.Itoa(int(*c.Shard)))
   301  	}
   302  	secret.Data = data
   303  
   304  	if secret.Annotations == nil {
   305  		secret.Annotations = make(map[string]string)
   306  	}
   307  	if c.RefreshRequestedAt != nil {
   308  		secret.Annotations[common.AnnotationKeyRefresh] = c.RefreshRequestedAt.Format(time.RFC3339)
   309  	} else {
   310  		delete(secret.Annotations, common.AnnotationKeyRefresh)
   311  	}
   312  	return nil
   313  }
   314  
   315  // secretToCluster converts a secret into a Cluster object
   316  func secretToCluster(s *apiv1.Secret) *appv1.Cluster {
   317  	var config appv1.ClusterConfig
   318  	if len(s.Data["config"]) > 0 {
   319  		err := json.Unmarshal(s.Data["config"], &config)
   320  		if err != nil {
   321  			panic(err)
   322  		}
   323  	}
   324  
   325  	var namespaces []string
   326  	for _, ns := range strings.Split(string(s.Data["namespaces"]), ",") {
   327  		if ns = strings.TrimSpace(ns); ns != "" {
   328  			namespaces = append(namespaces, ns)
   329  		}
   330  	}
   331  	var refreshRequestedAt *metav1.Time
   332  	if v, found := s.Annotations[common.AnnotationKeyRefresh]; found {
   333  		requestedAt, err := time.Parse(time.RFC3339, v)
   334  		if err != nil {
   335  			log.Warnf("Error while parsing date in cluster secret '%s': %v", s.Name, err)
   336  		} else {
   337  			refreshRequestedAt = &metav1.Time{Time: requestedAt}
   338  		}
   339  	}
   340  	var shard *int64
   341  	if shardStr := s.Data["shard"]; shardStr != nil {
   342  		if val, err := strconv.Atoi(string(shardStr)); err != nil {
   343  			log.Warnf("Error while parsing shard in cluster secret '%s': %v", s.Name, err)
   344  		} else {
   345  			shard = pointer.Int64Ptr(int64(val))
   346  		}
   347  	}
   348  	cluster := appv1.Cluster{
   349  		ID:                 string(s.UID),
   350  		Server:             strings.TrimRight(string(s.Data["server"]), "/"),
   351  		Name:               string(s.Data["name"]),
   352  		Namespaces:         namespaces,
   353  		Config:             config,
   354  		RefreshRequestedAt: refreshRequestedAt,
   355  		Shard:              shard,
   356  	}
   357  	return &cluster
   358  }