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

     1  package db
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strconv"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	log "github.com/sirupsen/logrus"
    13  	"google.golang.org/grpc/codes"
    14  	"google.golang.org/grpc/status"
    15  	apiv1 "k8s.io/api/core/v1"
    16  	apierr "k8s.io/apimachinery/pkg/api/errors"
    17  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  	"k8s.io/apimachinery/pkg/watch"
    19  	"k8s.io/utils/pointer"
    20  
    21  	"github.com/argoproj/argo-cd/v2/common"
    22  	appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    23  	"github.com/argoproj/argo-cd/v2/util/collections"
    24  	"github.com/argoproj/argo-cd/v2/util/settings"
    25  )
    26  
    27  var (
    28  	localCluster = appv1.Cluster{
    29  		Name:            "in-cluster",
    30  		Server:          appv1.KubernetesInternalAPIServerAddr,
    31  		ConnectionState: appv1.ConnectionState{Status: appv1.ConnectionStatusSuccessful},
    32  	}
    33  	initLocalCluster sync.Once
    34  )
    35  
    36  func (db *db) getLocalCluster() *appv1.Cluster {
    37  	initLocalCluster.Do(func() {
    38  		info, err := db.kubeclientset.Discovery().ServerVersion()
    39  		if err == nil {
    40  			localCluster.ServerVersion = fmt.Sprintf("%s.%s", info.Major, info.Minor)
    41  			localCluster.ConnectionState = appv1.ConnectionState{Status: appv1.ConnectionStatusSuccessful}
    42  		} else {
    43  			localCluster.ConnectionState = appv1.ConnectionState{
    44  				Status:  appv1.ConnectionStatusFailed,
    45  				Message: err.Error(),
    46  			}
    47  		}
    48  	})
    49  	cluster := localCluster.DeepCopy()
    50  	now := metav1.Now()
    51  	cluster.ConnectionState.ModifiedAt = &now
    52  	return cluster
    53  }
    54  
    55  // ListClusters returns list of clusters
    56  func (db *db) ListClusters(ctx context.Context) (*appv1.ClusterList, error) {
    57  	clusterSecrets, err := db.listSecretsByType(common.LabelValueSecretTypeCluster)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	clusterList := appv1.ClusterList{
    62  		Items: make([]appv1.Cluster, 0),
    63  	}
    64  	settings, err := db.settingsMgr.GetSettings()
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	inClusterEnabled := settings.InClusterEnabled
    69  	hasInClusterCredentials := false
    70  	for _, clusterSecret := range clusterSecrets {
    71  		cluster, err := SecretToCluster(clusterSecret)
    72  		if err != nil {
    73  			log.Errorf("could not unmarshal cluster secret %s", clusterSecret.Name)
    74  			continue
    75  		}
    76  		if cluster.Server == appv1.KubernetesInternalAPIServerAddr {
    77  			if inClusterEnabled {
    78  				hasInClusterCredentials = true
    79  				clusterList.Items = append(clusterList.Items, *cluster)
    80  			}
    81  		} else {
    82  			clusterList.Items = append(clusterList.Items, *cluster)
    83  		}
    84  	}
    85  	if inClusterEnabled && !hasInClusterCredentials {
    86  		clusterList.Items = append(clusterList.Items, *db.getLocalCluster())
    87  	}
    88  	return &clusterList, nil
    89  }
    90  
    91  // CreateCluster creates a cluster
    92  func (db *db) CreateCluster(ctx context.Context, c *appv1.Cluster) (*appv1.Cluster, error) {
    93  	settings, err := db.settingsMgr.GetSettings()
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	if c.Server == appv1.KubernetesInternalAPIServerAddr && !settings.InClusterEnabled {
    98  		return nil, status.Errorf(codes.InvalidArgument, "cannot register cluster: in-cluster has been disabled")
    99  	}
   100  	secName, err := URIToSecretName("cluster", c.Server)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	clusterSecret := &apiv1.Secret{
   106  		ObjectMeta: metav1.ObjectMeta{
   107  			Name: secName,
   108  		},
   109  	}
   110  
   111  	if err = clusterToSecret(c, clusterSecret); err != nil {
   112  		return nil, err
   113  	}
   114  
   115  	clusterSecret, err = db.createSecret(ctx, clusterSecret)
   116  	if err != nil {
   117  		if apierr.IsAlreadyExists(err) {
   118  			return nil, status.Errorf(codes.AlreadyExists, "cluster %q already exists", c.Server)
   119  		}
   120  		return nil, err
   121  	}
   122  
   123  	cluster, err := SecretToCluster(clusterSecret)
   124  	if err != nil {
   125  		return nil, status.Errorf(codes.InvalidArgument, "could not unmarshal cluster secret %s", clusterSecret.Name)
   126  	}
   127  	return cluster, db.settingsMgr.ResyncInformers()
   128  }
   129  
   130  // ClusterEvent contains information about cluster event
   131  type ClusterEvent struct {
   132  	Type    watch.EventType
   133  	Cluster *appv1.Cluster
   134  }
   135  
   136  func (db *db) WatchClusters(ctx context.Context,
   137  	handleAddEvent func(cluster *appv1.Cluster),
   138  	handleModEvent func(oldCluster *appv1.Cluster, newCluster *appv1.Cluster),
   139  	handleDeleteEvent func(clusterServer string)) error {
   140  	localCls, err := db.GetCluster(ctx, appv1.KubernetesInternalAPIServerAddr)
   141  	if err != nil {
   142  		return err
   143  	}
   144  	handleAddEvent(localCls)
   145  
   146  	db.watchSecrets(
   147  		ctx,
   148  		common.LabelValueSecretTypeCluster,
   149  
   150  		func(secret *apiv1.Secret) {
   151  			cluster, err := SecretToCluster(secret)
   152  			if err != nil {
   153  				log.Errorf("could not unmarshal cluster secret %s", secret.Name)
   154  				return
   155  			}
   156  			if cluster.Server == appv1.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  		func(oldSecret *apiv1.Secret, newSecret *apiv1.Secret) {
   166  			oldCluster, err := SecretToCluster(oldSecret)
   167  			if err != nil {
   168  				log.Errorf("could not unmarshal cluster secret %s", oldSecret.Name)
   169  				return
   170  			}
   171  			newCluster, err := SecretToCluster(newSecret)
   172  			if err != nil {
   173  				log.Errorf("could not unmarshal cluster secret %s", newSecret.Name)
   174  				return
   175  			}
   176  			if newCluster.Server == appv1.KubernetesInternalAPIServerAddr {
   177  				localCls = newCluster
   178  			}
   179  			handleModEvent(oldCluster, newCluster)
   180  		},
   181  
   182  		func(secret *apiv1.Secret) {
   183  			if string(secret.Data["server"]) == appv1.KubernetesInternalAPIServerAddr {
   184  				// change local cluster event to modified or deleted, since it cannot be re-added or deleted
   185  				handleModEvent(localCls, db.getLocalCluster())
   186  				localCls = db.getLocalCluster()
   187  			} else {
   188  				handleDeleteEvent(string(secret.Data["server"]))
   189  			}
   190  		},
   191  	)
   192  
   193  	return err
   194  }
   195  
   196  func (db *db) getClusterSecret(server string) (*apiv1.Secret, error) {
   197  	clusterSecrets, err := db.listSecretsByType(common.LabelValueSecretTypeCluster)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  	srv := strings.TrimRight(server, "/")
   202  	for _, clusterSecret := range clusterSecrets {
   203  		if strings.TrimRight(string(clusterSecret.Data["server"]), "/") == srv {
   204  			return clusterSecret, nil
   205  		}
   206  	}
   207  	return nil, status.Errorf(codes.NotFound, "cluster %q not found", server)
   208  }
   209  
   210  // GetCluster returns a cluster from a query
   211  func (db *db) GetCluster(_ context.Context, server string) (*appv1.Cluster, error) {
   212  	informer, err := db.settingsMgr.GetSecretsInformer()
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	res, err := informer.GetIndexer().ByIndex(settings.ByClusterURLIndexer, server)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  	if len(res) > 0 {
   221  		return SecretToCluster(res[0].(*apiv1.Secret))
   222  	}
   223  	if server == appv1.KubernetesInternalAPIServerAddr {
   224  		return db.getLocalCluster(), nil
   225  	}
   226  
   227  	return nil, status.Errorf(codes.NotFound, "cluster %q not found", server)
   228  }
   229  
   230  // GetProjectClusters return project scoped clusters by given project name
   231  func (db *db) GetProjectClusters(ctx context.Context, project string) ([]*appv1.Cluster, error) {
   232  	informer, err := db.settingsMgr.GetSecretsInformer()
   233  	if err != nil {
   234  		return nil, fmt.Errorf("failed to get secrets informer: %w", err)
   235  	}
   236  	secrets, err := informer.GetIndexer().ByIndex(settings.ByProjectClusterIndexer, project)
   237  	if err != nil {
   238  		return nil, fmt.Errorf("failed to get index by project cluster indexer for project %q: %w", project, err)
   239  	}
   240  	var res []*appv1.Cluster
   241  	for i := range secrets {
   242  		cluster, err := SecretToCluster(secrets[i].(*apiv1.Secret))
   243  		if err != nil {
   244  			return nil, fmt.Errorf("failed to convert secret to cluster: %w", err)
   245  		}
   246  		res = append(res, cluster)
   247  	}
   248  	return res, nil
   249  }
   250  
   251  func (db *db) GetClusterServersByName(ctx context.Context, name string) ([]string, error) {
   252  	informer, err := db.settingsMgr.GetSecretsInformer()
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  
   257  	// if local cluster name is not overridden and specified name is local cluster name, return local cluster server
   258  	localClusterSecrets, err := informer.GetIndexer().ByIndex(settings.ByClusterURLIndexer, appv1.KubernetesInternalAPIServerAddr)
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  
   263  	if len(localClusterSecrets) == 0 && db.getLocalCluster().Name == name {
   264  		return []string{appv1.KubernetesInternalAPIServerAddr}, nil
   265  	}
   266  
   267  	secrets, err := informer.GetIndexer().ByIndex(settings.ByClusterNameIndexer, name)
   268  	if err != nil {
   269  		return nil, err
   270  	}
   271  	var res []string
   272  	for i := range secrets {
   273  		s := secrets[i].(*apiv1.Secret)
   274  		res = append(res, strings.TrimRight(string(s.Data["server"]), "/"))
   275  	}
   276  	return res, nil
   277  }
   278  
   279  // UpdateCluster updates a cluster
   280  func (db *db) UpdateCluster(ctx context.Context, c *appv1.Cluster) (*appv1.Cluster, error) {
   281  	clusterSecret, err := db.getClusterSecret(c.Server)
   282  	if err != nil {
   283  		if status.Code(err) == codes.NotFound {
   284  			return db.CreateCluster(ctx, c)
   285  		}
   286  		return nil, err
   287  	}
   288  	if err := clusterToSecret(c, clusterSecret); err != nil {
   289  		return nil, err
   290  	}
   291  
   292  	clusterSecret, err = db.kubeclientset.CoreV1().Secrets(db.ns).Update(ctx, clusterSecret, metav1.UpdateOptions{})
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  	cluster, err := SecretToCluster(clusterSecret)
   297  	if err != nil {
   298  		log.Errorf("could not unmarshal cluster secret %s", clusterSecret.Name)
   299  		return nil, err
   300  	}
   301  	return cluster, db.settingsMgr.ResyncInformers()
   302  }
   303  
   304  // DeleteCluster deletes a cluster by name
   305  func (db *db) DeleteCluster(ctx context.Context, server string) error {
   306  	secret, err := db.getClusterSecret(server)
   307  	if err != nil {
   308  		return err
   309  	}
   310  
   311  	err = db.deleteSecret(ctx, secret)
   312  	if err != nil {
   313  		return err
   314  	}
   315  
   316  	return db.settingsMgr.ResyncInformers()
   317  }
   318  
   319  // clusterToData converts a cluster object to string data for serialization to a secret
   320  func clusterToSecret(c *appv1.Cluster, secret *apiv1.Secret) error {
   321  	data := make(map[string][]byte)
   322  	data["server"] = []byte(strings.TrimRight(c.Server, "/"))
   323  	if c.Name == "" {
   324  		data["name"] = []byte(c.Server)
   325  	} else {
   326  		data["name"] = []byte(c.Name)
   327  	}
   328  	if len(c.Namespaces) != 0 {
   329  		data["namespaces"] = []byte(strings.Join(c.Namespaces, ","))
   330  	}
   331  	configBytes, err := json.Marshal(c.Config)
   332  	if err != nil {
   333  		return err
   334  	}
   335  	data["config"] = configBytes
   336  	if c.Shard != nil {
   337  		data["shard"] = []byte(strconv.Itoa(int(*c.Shard)))
   338  	}
   339  	if c.ClusterResources {
   340  		data["clusterResources"] = []byte("true")
   341  	}
   342  	if c.Project != "" {
   343  		data["project"] = []byte(c.Project)
   344  	}
   345  	secret.Data = data
   346  
   347  	secret.Labels = c.Labels
   348  	if c.Annotations != nil && c.Annotations[apiv1.LastAppliedConfigAnnotation] != "" {
   349  		return status.Errorf(codes.InvalidArgument, "annotation %s cannot be set", apiv1.LastAppliedConfigAnnotation)
   350  	}
   351  	secret.Annotations = c.Annotations
   352  
   353  	if secret.Annotations == nil {
   354  		secret.Annotations = make(map[string]string)
   355  	}
   356  
   357  	if c.RefreshRequestedAt != nil {
   358  		secret.Annotations[appv1.AnnotationKeyRefresh] = c.RefreshRequestedAt.Format(time.RFC3339)
   359  	} else {
   360  		delete(secret.Annotations, appv1.AnnotationKeyRefresh)
   361  	}
   362  	addSecretMetadata(secret, common.LabelValueSecretTypeCluster)
   363  	return nil
   364  }
   365  
   366  // SecretToCluster converts a secret into a Cluster object
   367  func SecretToCluster(s *apiv1.Secret) (*appv1.Cluster, error) {
   368  	var config appv1.ClusterConfig
   369  	if len(s.Data["config"]) > 0 {
   370  		err := json.Unmarshal(s.Data["config"], &config)
   371  		if err != nil {
   372  			return nil, fmt.Errorf("failed to unmarshal cluster config: %w", err)
   373  		}
   374  	}
   375  
   376  	var namespaces []string
   377  	for _, ns := range strings.Split(string(s.Data["namespaces"]), ",") {
   378  		if ns = strings.TrimSpace(ns); ns != "" {
   379  			namespaces = append(namespaces, ns)
   380  		}
   381  	}
   382  	var refreshRequestedAt *metav1.Time
   383  	if v, found := s.Annotations[appv1.AnnotationKeyRefresh]; found {
   384  		requestedAt, err := time.Parse(time.RFC3339, v)
   385  		if err != nil {
   386  			log.Warnf("Error while parsing date in cluster secret '%s': %v", s.Name, err)
   387  		} else {
   388  			refreshRequestedAt = &metav1.Time{Time: requestedAt}
   389  		}
   390  	}
   391  	var shard *int64
   392  	if shardStr := s.Data["shard"]; shardStr != nil {
   393  		if val, err := strconv.Atoi(string(shardStr)); err != nil {
   394  			log.Warnf("Error while parsing shard in cluster secret '%s': %v", s.Name, err)
   395  		} else {
   396  			shard = pointer.Int64(int64(val))
   397  		}
   398  	}
   399  
   400  	// copy labels and annotations excluding system ones
   401  	labels := map[string]string{}
   402  	if s.Labels != nil {
   403  		labels = collections.CopyStringMap(s.Labels)
   404  		delete(labels, common.LabelKeySecretType)
   405  	}
   406  	annotations := map[string]string{}
   407  	if s.Annotations != nil {
   408  		annotations = collections.CopyStringMap(s.Annotations)
   409  		// delete system annotations
   410  		delete(annotations, apiv1.LastAppliedConfigAnnotation)
   411  		delete(annotations, common.AnnotationKeyManagedBy)
   412  	}
   413  
   414  	cluster := appv1.Cluster{
   415  		ID:                 string(s.UID),
   416  		Server:             strings.TrimRight(string(s.Data["server"]), "/"),
   417  		Name:               string(s.Data["name"]),
   418  		Namespaces:         namespaces,
   419  		ClusterResources:   string(s.Data["clusterResources"]) == "true",
   420  		Config:             config,
   421  		RefreshRequestedAt: refreshRequestedAt,
   422  		Shard:              shard,
   423  		Project:            string(s.Data["project"]),
   424  		Labels:             labels,
   425  		Annotations:        annotations,
   426  	}
   427  	return &cluster, nil
   428  }