github.com/argoproj/argo-cd/v3@v3.2.1/controller/clusterinfoupdater.go (about) 1 package controller 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/argoproj/argo-cd/v3/common" 9 10 "github.com/argoproj/gitops-engine/pkg/cache" 11 "github.com/argoproj/gitops-engine/pkg/utils/kube" 12 log "github.com/sirupsen/logrus" 13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 "k8s.io/apimachinery/pkg/labels" 15 16 "github.com/argoproj/argo-cd/v3/util/env" 17 18 "github.com/argoproj/argo-cd/v3/controller/metrics" 19 appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 20 "github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1" 21 "github.com/argoproj/argo-cd/v3/util/argo" 22 appstatecache "github.com/argoproj/argo-cd/v3/util/cache/appstate" 23 "github.com/argoproj/argo-cd/v3/util/db" 24 ) 25 26 const ( 27 defaultSecretUpdateInterval = 10 * time.Second 28 29 EnvClusterInfoTimeout = "ARGO_CD_UPDATE_CLUSTER_INFO_TIMEOUT" 30 ) 31 32 var clusterInfoTimeout = env.ParseDurationFromEnv(EnvClusterInfoTimeout, defaultSecretUpdateInterval, defaultSecretUpdateInterval, 1*time.Minute) 33 34 type clusterInfoUpdater struct { 35 infoSource metrics.HasClustersInfo 36 db db.ArgoDB 37 appLister v1alpha1.ApplicationNamespaceLister 38 cache *appstatecache.Cache 39 clusterFilter func(cluster *appv1.Cluster) bool 40 projGetter func(app *appv1.Application) (*appv1.AppProject, error) 41 namespace string 42 lastUpdated time.Time 43 } 44 45 func NewClusterInfoUpdater( 46 infoSource metrics.HasClustersInfo, 47 db db.ArgoDB, 48 appLister v1alpha1.ApplicationNamespaceLister, 49 cache *appstatecache.Cache, 50 clusterFilter func(cluster *appv1.Cluster) bool, 51 projGetter func(app *appv1.Application) (*appv1.AppProject, error), 52 namespace string, 53 ) *clusterInfoUpdater { 54 return &clusterInfoUpdater{infoSource, db, appLister, cache, clusterFilter, projGetter, namespace, time.Time{}} 55 } 56 57 func (c *clusterInfoUpdater) Run(ctx context.Context) { 58 c.updateClusters() 59 ticker := time.NewTicker(clusterInfoTimeout) 60 for { 61 select { 62 case <-ctx.Done(): 63 ticker.Stop() 64 return 65 case <-ticker.C: 66 c.updateClusters() 67 } 68 } 69 } 70 71 func (c *clusterInfoUpdater) updateClusters() { 72 if time.Since(c.lastUpdated) < clusterInfoTimeout { 73 return 74 } 75 76 ctx, cancel := context.WithTimeout(context.Background(), clusterInfoTimeout) 77 defer func() { 78 cancel() 79 c.lastUpdated = time.Now() 80 }() 81 82 infoByServer := make(map[string]*cache.ClusterInfo) 83 clustersInfo := c.infoSource.GetClustersInfo() 84 for i := range clustersInfo { 85 info := clustersInfo[i] 86 infoByServer[info.Server] = &info 87 } 88 clusters, err := c.db.ListClusters(ctx) 89 if err != nil { 90 log.Warnf("Failed to save clusters info: %v", err) 91 return 92 } 93 var clustersFiltered []appv1.Cluster 94 if c.clusterFilter == nil { 95 clustersFiltered = clusters.Items 96 } else { 97 for i := range clusters.Items { 98 if c.clusterFilter(&clusters.Items[i]) { 99 clustersFiltered = append(clustersFiltered, clusters.Items[i]) 100 } 101 } 102 } 103 _ = kube.RunAllAsync(len(clustersFiltered), func(i int) error { 104 cluster := clustersFiltered[i] 105 clusterInfo := infoByServer[cluster.Server] 106 if err := c.updateClusterInfo(ctx, cluster, clusterInfo); err != nil { 107 log.Warnf("Failed to save cluster info: %v", err) 108 } else if err := updateClusterLabels(ctx, clusterInfo, cluster, c.db.UpdateCluster); err != nil { 109 log.Warnf("Failed to update cluster labels: %v", err) 110 } 111 return nil 112 }) 113 log.Debugf("Successfully saved info of %d clusters", len(clustersFiltered)) 114 } 115 116 func (c *clusterInfoUpdater) updateClusterInfo(ctx context.Context, cluster appv1.Cluster, info *cache.ClusterInfo) error { 117 apps, err := c.appLister.List(labels.Everything()) 118 if err != nil { 119 return fmt.Errorf("error while fetching the apps list: %w", err) 120 } 121 122 updated := c.getUpdatedClusterInfo(ctx, apps, cluster, info, metav1.Now()) 123 return c.cache.SetClusterInfo(cluster.Server, &updated) 124 } 125 126 func (c *clusterInfoUpdater) getUpdatedClusterInfo(ctx context.Context, apps []*appv1.Application, cluster appv1.Cluster, info *cache.ClusterInfo, now metav1.Time) appv1.ClusterInfo { 127 var appCount int64 128 for _, a := range apps { 129 if c.projGetter != nil { 130 proj, err := c.projGetter(a) 131 if err != nil || !proj.IsAppNamespacePermitted(a, c.namespace) { 132 continue 133 } 134 } 135 destCluster, err := argo.GetDestinationCluster(ctx, a.Spec.Destination, c.db) 136 if err != nil { 137 continue 138 } 139 if destCluster.Server == cluster.Server { 140 appCount++ 141 } 142 } 143 clusterInfo := appv1.ClusterInfo{ 144 ConnectionState: appv1.ConnectionState{ModifiedAt: &now}, 145 ApplicationsCount: appCount, 146 } 147 if info != nil { 148 clusterInfo.ServerVersion = info.K8SVersion 149 clusterInfo.APIVersions = argo.APIResourcesToStrings(info.APIResources, true) 150 switch { 151 case info.LastCacheSyncTime == nil: 152 clusterInfo.ConnectionState.Status = appv1.ConnectionStatusUnknown 153 case info.SyncError == nil: 154 clusterInfo.ConnectionState.Status = appv1.ConnectionStatusSuccessful 155 syncTime := metav1.NewTime(*info.LastCacheSyncTime) 156 clusterInfo.CacheInfo.LastCacheSyncTime = &syncTime 157 clusterInfo.CacheInfo.APIsCount = int64(info.APIsCount) 158 clusterInfo.CacheInfo.ResourcesCount = int64(info.ResourcesCount) 159 default: 160 clusterInfo.ConnectionState.Status = appv1.ConnectionStatusFailed 161 clusterInfo.ConnectionState.Message = info.SyncError.Error() 162 } 163 } else { 164 clusterInfo.ConnectionState.Status = appv1.ConnectionStatusUnknown 165 if appCount == 0 { 166 clusterInfo.ConnectionState.Message = "Cluster has no applications and is not being monitored." 167 } 168 } 169 170 return clusterInfo 171 } 172 173 func updateClusterLabels(ctx context.Context, clusterInfo *cache.ClusterInfo, cluster appv1.Cluster, updateCluster func(context.Context, *appv1.Cluster) (*appv1.Cluster, error)) error { 174 if clusterInfo != nil && cluster.Labels[common.LabelKeyAutoLabelClusterInfo] == "true" && cluster.Labels[common.LabelKeyClusterKubernetesVersion] != clusterInfo.K8SVersion { 175 cluster.Labels[common.LabelKeyClusterKubernetesVersion] = clusterInfo.K8SVersion 176 _, err := updateCluster(ctx, &cluster) 177 return err 178 } 179 180 return nil 181 }