github.com/argoproj/argo-cd/v2@v2.10.9/applicationset/utils/clusterUtils.go (about) 1 package utils 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 corev1 "k8s.io/api/core/v1" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 16 "github.com/argoproj/argo-cd/v2/common" 17 appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" 18 19 "k8s.io/client-go/kubernetes" 20 "k8s.io/utils/pointer" 21 ) 22 23 // The contents of this file are from 24 // github.com/argoproj/argo-cd/util/db/cluster.go 25 // 26 // The main difference is that ListClusters(...) calls the kubeclient directly, 27 // via `g.clientset.CoreV1().Secrets`, rather than using the `db.listClusterSecrets()`` 28 // which appears to have a race condition on when it is called. 29 // 30 // I was reminded of this issue that I opened, which might be related: 31 // https://github.com/argoproj/argo-cd/issues/4755 32 // 33 // I hope to upstream this change in some form, so that we do not need to worry about 34 // Argo CD changing the logic on us. 35 36 var ( 37 localCluster = appv1.Cluster{ 38 Name: "in-cluster", 39 Server: appv1.KubernetesInternalAPIServerAddr, 40 ConnectionState: appv1.ConnectionState{Status: appv1.ConnectionStatusSuccessful}, 41 } 42 initLocalCluster sync.Once 43 ) 44 45 const ( 46 ArgoCDSecretTypeLabel = "argocd.argoproj.io/secret-type" 47 ArgoCDSecretTypeCluster = "cluster" 48 ) 49 50 // ValidateDestination checks: 51 // if we used destination name we infer the server url 52 // if we used both name and server then we return an invalid spec error 53 func ValidateDestination(ctx context.Context, dest *appv1.ApplicationDestination, clientset kubernetes.Interface, argoCDNamespace string) error { 54 if dest.Name != "" { 55 if dest.Server == "" { 56 server, err := getDestinationServer(ctx, dest.Name, clientset, argoCDNamespace) 57 if err != nil { 58 return fmt.Errorf("unable to find destination server: %v", err) 59 } 60 if server == "" { 61 return fmt.Errorf("application references destination cluster %s which does not exist", dest.Name) 62 } 63 dest.SetInferredServer(server) 64 } else { 65 if !dest.IsServerInferred() { 66 return fmt.Errorf("application destination can't have both name and server defined: %s %s", dest.Name, dest.Server) 67 } 68 } 69 } 70 return nil 71 } 72 73 func getDestinationServer(ctx context.Context, clusterName string, clientset kubernetes.Interface, argoCDNamespace string) (string, error) { 74 // settingsMgr := settings.NewSettingsManager(context.TODO(), clientset, namespace) 75 // argoDB := db.NewDB(namespace, settingsMgr, clientset) 76 // clusterList, err := argoDB.ListClusters(ctx) 77 clusterList, err := ListClusters(ctx, clientset, argoCDNamespace) 78 if err != nil { 79 return "", err 80 } 81 var servers []string 82 for _, c := range clusterList.Items { 83 if c.Name == clusterName { 84 servers = append(servers, c.Server) 85 } 86 } 87 if len(servers) > 1 { 88 return "", fmt.Errorf("there are %d clusters with the same name: %v", len(servers), servers) 89 } else if len(servers) == 0 { 90 return "", fmt.Errorf("there are no clusters with this name: %s", clusterName) 91 } 92 return servers[0], nil 93 } 94 95 func ListClusters(ctx context.Context, clientset kubernetes.Interface, namespace string) (*appv1.ClusterList, error) { 96 97 clusterSecretsList, err := clientset.CoreV1().Secrets(namespace).List(ctx, 98 metav1.ListOptions{LabelSelector: common.LabelKeySecretType + "=" + common.LabelValueSecretTypeCluster}) 99 if err != nil { 100 return nil, err 101 } 102 103 if clusterSecretsList == nil { 104 return nil, nil 105 } 106 107 clusterSecrets := clusterSecretsList.Items 108 109 clusterList := appv1.ClusterList{ 110 Items: make([]appv1.Cluster, len(clusterSecrets)), 111 } 112 hasInClusterCredentials := false 113 for i, clusterSecret := range clusterSecrets { 114 // This line has changed from the original Argo CD code: now receives an error, and handles it 115 cluster, err := secretToCluster(&clusterSecret) 116 if err != nil || cluster == nil { 117 return nil, fmt.Errorf("unable to convert cluster secret to cluster object '%s': %v", clusterSecret.Name, err) 118 } 119 120 clusterList.Items[i] = *cluster 121 if cluster.Server == appv1.KubernetesInternalAPIServerAddr { 122 hasInClusterCredentials = true 123 } 124 } 125 if !hasInClusterCredentials { 126 localCluster := getLocalCluster(clientset) 127 if localCluster != nil { 128 clusterList.Items = append(clusterList.Items, *localCluster) 129 } 130 } 131 return &clusterList, nil 132 } 133 134 func getLocalCluster(clientset kubernetes.Interface) *appv1.Cluster { 135 initLocalCluster.Do(func() { 136 info, err := clientset.Discovery().ServerVersion() 137 if err == nil { 138 localCluster.ServerVersion = fmt.Sprintf("%s.%s", info.Major, info.Minor) 139 localCluster.ConnectionState = appv1.ConnectionState{Status: appv1.ConnectionStatusSuccessful} 140 } else { 141 localCluster.ConnectionState = appv1.ConnectionState{ 142 Status: appv1.ConnectionStatusFailed, 143 Message: err.Error(), 144 } 145 } 146 }) 147 cluster := localCluster.DeepCopy() 148 now := metav1.Now() 149 cluster.ConnectionState.ModifiedAt = &now 150 return cluster 151 } 152 153 // secretToCluster converts a secret into a Cluster object 154 func secretToCluster(s *corev1.Secret) (*appv1.Cluster, error) { 155 var config appv1.ClusterConfig 156 if len(s.Data["config"]) > 0 { 157 if err := json.Unmarshal(s.Data["config"], &config); err != nil { 158 // This line has changed from the original Argo CD: now returns an error rather than panicing. 159 return nil, err 160 } 161 } 162 163 var namespaces []string 164 for _, ns := range strings.Split(string(s.Data["namespaces"]), ",") { 165 if ns = strings.TrimSpace(ns); ns != "" { 166 namespaces = append(namespaces, ns) 167 } 168 } 169 var refreshRequestedAt *metav1.Time 170 if v, found := s.Annotations[appv1.AnnotationKeyRefresh]; found { 171 requestedAt, err := time.Parse(time.RFC3339, v) 172 if err != nil { 173 log.Warnf("Error while parsing date in cluster secret '%s': %v", s.Name, err) 174 } else { 175 refreshRequestedAt = &metav1.Time{Time: requestedAt} 176 } 177 } 178 var shard *int64 179 if shardStr := s.Data["shard"]; shardStr != nil { 180 if val, err := strconv.Atoi(string(shardStr)); err != nil { 181 log.Warnf("Error while parsing shard in cluster secret '%s': %v", s.Name, err) 182 } else { 183 shard = pointer.Int64(int64(val)) 184 } 185 } 186 cluster := appv1.Cluster{ 187 ID: string(s.UID), 188 Server: strings.TrimRight(string(s.Data["server"]), "/"), 189 Name: string(s.Data["name"]), 190 Namespaces: namespaces, 191 Config: config, 192 RefreshRequestedAt: refreshRequestedAt, 193 Shard: shard, 194 } 195 return &cluster, nil 196 }