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 }