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 }