github.com/argoproj/argo-cd@v1.8.7/cmd/argocd-util/commands/apps.go (about) 1 package commands 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "os" 10 "sort" 11 "time" 12 13 "github.com/ghodss/yaml" 14 "github.com/spf13/cobra" 15 apiv1 "k8s.io/api/core/v1" 16 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 18 "k8s.io/apimachinery/pkg/util/runtime" 19 "k8s.io/client-go/kubernetes" 20 kubecache "k8s.io/client-go/tools/cache" 21 "k8s.io/client-go/tools/clientcmd" 22 23 "github.com/argoproj/argo-cd/common" 24 "github.com/argoproj/argo-cd/controller" 25 "github.com/argoproj/argo-cd/controller/cache" 26 "github.com/argoproj/argo-cd/controller/metrics" 27 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" 28 appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned" 29 appinformers "github.com/argoproj/argo-cd/pkg/client/informers/externalversions" 30 "github.com/argoproj/argo-cd/reposerver/apiclient" 31 "github.com/argoproj/argo-cd/util/cli" 32 "github.com/argoproj/argo-cd/util/config" 33 "github.com/argoproj/argo-cd/util/db" 34 "github.com/argoproj/argo-cd/util/errors" 35 kubeutil "github.com/argoproj/argo-cd/util/kube" 36 "github.com/argoproj/argo-cd/util/settings" 37 ) 38 39 func NewAppsCommand() *cobra.Command { 40 var command = &cobra.Command{ 41 Use: "apps", 42 Short: "Utility commands operate on ArgoCD applications", 43 Run: func(c *cobra.Command, args []string) { 44 c.HelpFunc()(c, args) 45 }, 46 } 47 48 command.AddCommand(NewReconcileCommand()) 49 command.AddCommand(NewDiffReconcileResults()) 50 return command 51 } 52 53 type appReconcileResult struct { 54 Name string `json:"name"` 55 Health *v1alpha1.HealthStatus `json:"health"` 56 Sync *v1alpha1.SyncStatus `json:"sync"` 57 Conditions []v1alpha1.ApplicationCondition `json:"conditions"` 58 } 59 60 type reconcileResults struct { 61 Applications []appReconcileResult `json:"applications"` 62 } 63 64 func (r *reconcileResults) getAppsMap() map[string]appReconcileResult { 65 res := map[string]appReconcileResult{} 66 for i := range r.Applications { 67 res[r.Applications[i].Name] = r.Applications[i] 68 } 69 return res 70 } 71 72 func printLine(format string, a ...interface{}) { 73 _, _ = fmt.Printf(format+"\n", a...) 74 } 75 76 func NewDiffReconcileResults() *cobra.Command { 77 var command = &cobra.Command{ 78 Use: "diff-reconcile-results PATH1 PATH2", 79 Short: "Compare results of two reconciliations and print diff.", 80 Run: func(c *cobra.Command, args []string) { 81 if len(args) != 2 { 82 c.HelpFunc()(c, args) 83 os.Exit(1) 84 } 85 86 path1 := args[0] 87 path2 := args[1] 88 var res1 reconcileResults 89 var res2 reconcileResults 90 errors.CheckError(config.UnmarshalLocalFile(path1, &res1)) 91 errors.CheckError(config.UnmarshalLocalFile(path2, &res2)) 92 errors.CheckError(diffReconcileResults(res1, res2)) 93 }, 94 } 95 96 return command 97 } 98 99 func toUnstructured(val interface{}) (*unstructured.Unstructured, error) { 100 data, err := json.Marshal(val) 101 if err != nil { 102 return nil, err 103 } 104 res := make(map[string]interface{}) 105 err = json.Unmarshal(data, &res) 106 if err != nil { 107 return nil, err 108 } 109 return &unstructured.Unstructured{Object: res}, nil 110 } 111 112 type diffPair struct { 113 name string 114 first *unstructured.Unstructured 115 second *unstructured.Unstructured 116 } 117 118 func diffReconcileResults(res1 reconcileResults, res2 reconcileResults) error { 119 var pairs []diffPair 120 resMap1 := res1.getAppsMap() 121 resMap2 := res2.getAppsMap() 122 for k, v := range resMap1 { 123 firstUn, err := toUnstructured(v) 124 if err != nil { 125 return err 126 } 127 var secondUn *unstructured.Unstructured 128 second, ok := resMap2[k] 129 if ok { 130 secondUn, err = toUnstructured(second) 131 if err != nil { 132 return err 133 } 134 delete(resMap2, k) 135 } 136 pairs = append(pairs, diffPair{name: k, first: firstUn, second: secondUn}) 137 } 138 for k, v := range resMap2 { 139 secondUn, err := toUnstructured(v) 140 if err != nil { 141 return err 142 } 143 pairs = append(pairs, diffPair{name: k, first: nil, second: secondUn}) 144 } 145 sort.Slice(pairs, func(i, j int) bool { 146 return pairs[i].name < pairs[j].name 147 }) 148 for _, item := range pairs { 149 printLine(item.name) 150 _ = cli.PrintDiff(item.name, item.first, item.second) 151 } 152 153 return nil 154 } 155 156 func NewReconcileCommand() *cobra.Command { 157 var ( 158 clientConfig clientcmd.ClientConfig 159 selector string 160 repoServerAddress string 161 outputFormat string 162 refresh bool 163 ) 164 165 var command = &cobra.Command{ 166 Use: "get-reconcile-results PATH", 167 Short: "Reconcile all applications and stores reconciliation summary in the specified file.", 168 Run: func(c *cobra.Command, args []string) { 169 // get rid of logging error handler 170 runtime.ErrorHandlers = runtime.ErrorHandlers[1:] 171 172 if len(args) != 1 { 173 c.HelpFunc()(c, args) 174 os.Exit(1) 175 } 176 outputPath := args[0] 177 178 errors.CheckError(os.Setenv(common.EnvVarFakeInClusterConfig, "true")) 179 cfg, err := clientConfig.ClientConfig() 180 errors.CheckError(err) 181 namespace, _, err := clientConfig.Namespace() 182 errors.CheckError(err) 183 184 var result []appReconcileResult 185 if refresh { 186 if repoServerAddress == "" { 187 printLine("Repo server is not provided, trying to port-forward to argocd-repo-server pod.") 188 repoServerPort, err := kubeutil.PortForward("app.kubernetes.io/name=argocd-repo-server", 8081, namespace) 189 errors.CheckError(err) 190 repoServerAddress = fmt.Sprintf("localhost:%d", repoServerPort) 191 } 192 repoServerClient := apiclient.NewRepoServerClientset(repoServerAddress, 60) 193 194 appClientset := appclientset.NewForConfigOrDie(cfg) 195 kubeClientset := kubernetes.NewForConfigOrDie(cfg) 196 result, err = reconcileApplications(kubeClientset, appClientset, namespace, repoServerClient, selector, newLiveStateCache) 197 errors.CheckError(err) 198 } else { 199 appClientset := appclientset.NewForConfigOrDie(cfg) 200 result, err = getReconcileResults(appClientset, namespace, selector) 201 } 202 203 errors.CheckError(saveToFile(err, outputFormat, reconcileResults{Applications: result}, outputPath)) 204 }, 205 } 206 clientConfig = cli.AddKubectlFlagsToCmd(command) 207 command.Flags().StringVar(&repoServerAddress, "repo-server", "", "Repo server address.") 208 command.Flags().StringVar(&selector, "l", "", "Label selector") 209 command.Flags().StringVar(&outputFormat, "o", "yaml", "Output format (yaml|json)") 210 command.Flags().BoolVar(&refresh, "refresh", false, "If set to true then recalculates apps reconciliation") 211 212 return command 213 } 214 215 func saveToFile(err error, outputFormat string, result reconcileResults, outputPath string) error { 216 errors.CheckError(err) 217 var data []byte 218 switch outputFormat { 219 case "yaml": 220 if data, err = yaml.Marshal(result); err != nil { 221 return err 222 } 223 case "json": 224 if data, err = json.Marshal(result); err != nil { 225 return err 226 } 227 default: 228 return fmt.Errorf("format %s is not supported", outputFormat) 229 } 230 231 return ioutil.WriteFile(outputPath, data, 0644) 232 } 233 234 func getReconcileResults(appClientset appclientset.Interface, namespace string, selector string) ([]appReconcileResult, error) { 235 appsList, err := appClientset.ArgoprojV1alpha1().Applications(namespace).List(context.Background(), v1.ListOptions{LabelSelector: selector}) 236 if err != nil { 237 return nil, err 238 } 239 240 var items []appReconcileResult 241 for _, app := range appsList.Items { 242 items = append(items, appReconcileResult{ 243 Name: app.Name, 244 Conditions: app.Status.Conditions, 245 Health: &app.Status.Health, 246 Sync: &app.Status.Sync, 247 }) 248 } 249 return items, nil 250 } 251 252 func reconcileApplications( 253 kubeClientset kubernetes.Interface, 254 appClientset appclientset.Interface, 255 namespace string, 256 repoServerClient apiclient.Clientset, 257 selector string, 258 createLiveStateCache func(argoDB db.ArgoDB, appInformer kubecache.SharedIndexInformer, settingsMgr *settings.SettingsManager, server *metrics.MetricsServer) cache.LiveStateCache, 259 ) ([]appReconcileResult, error) { 260 261 settingsMgr := settings.NewSettingsManager(context.Background(), kubeClientset, namespace) 262 argoDB := db.NewDB(namespace, settingsMgr, kubeClientset) 263 appInformerFactory := appinformers.NewFilteredSharedInformerFactory( 264 appClientset, 265 1*time.Hour, 266 namespace, 267 func(options *v1.ListOptions) {}, 268 ) 269 270 appInformer := appInformerFactory.Argoproj().V1alpha1().Applications().Informer() 271 projInformer := appInformerFactory.Argoproj().V1alpha1().AppProjects().Informer() 272 go appInformer.Run(context.Background().Done()) 273 go projInformer.Run(context.Background().Done()) 274 if !kubecache.WaitForCacheSync(context.Background().Done(), appInformer.HasSynced, projInformer.HasSynced) { 275 return nil, fmt.Errorf("failed to sync cache") 276 } 277 278 appLister := appInformerFactory.Argoproj().V1alpha1().Applications().Lister() 279 projLister := appInformerFactory.Argoproj().V1alpha1().AppProjects().Lister() 280 server, err := metrics.NewMetricsServer("", appLister, func(obj interface{}) bool { 281 return true 282 }, func(r *http.Request) error { 283 return nil 284 }) 285 286 if err != nil { 287 return nil, err 288 } 289 stateCache := createLiveStateCache(argoDB, appInformer, settingsMgr, server) 290 if err := stateCache.Init(); err != nil { 291 return nil, err 292 } 293 294 appStateManager := controller.NewAppStateManager( 295 argoDB, appClientset, repoServerClient, namespace, kubeutil.NewKubectl(), settingsMgr, stateCache, projInformer, server) 296 297 appsList, err := appClientset.ArgoprojV1alpha1().Applications(namespace).List(context.Background(), v1.ListOptions{LabelSelector: selector}) 298 if err != nil { 299 return nil, err 300 } 301 302 sort.Slice(appsList.Items, func(i, j int) bool { 303 return appsList.Items[i].Spec.Destination.Server < appsList.Items[j].Spec.Destination.Server 304 }) 305 306 var items []appReconcileResult 307 prevServer := "" 308 for _, app := range appsList.Items { 309 if prevServer != app.Spec.Destination.Server { 310 if prevServer != "" { 311 if clusterCache, err := stateCache.GetClusterCache(prevServer); err == nil { 312 clusterCache.Invalidate() 313 } 314 } 315 printLine("Reconciling apps of %s", app.Spec.Destination.Server) 316 prevServer = app.Spec.Destination.Server 317 } 318 printLine(app.Name) 319 320 proj, err := projLister.AppProjects(namespace).Get(app.Spec.Project) 321 if err != nil { 322 return nil, err 323 } 324 325 res := appStateManager.CompareAppState(&app, proj, app.Spec.Source.TargetRevision, app.Spec.Source, false, nil) 326 items = append(items, appReconcileResult{ 327 Name: app.Name, 328 Conditions: app.Status.Conditions, 329 Health: res.GetHealthStatus(), 330 Sync: res.GetSyncStatus(), 331 }) 332 } 333 return items, nil 334 } 335 336 func newLiveStateCache(argoDB db.ArgoDB, appInformer kubecache.SharedIndexInformer, settingsMgr *settings.SettingsManager, server *metrics.MetricsServer) cache.LiveStateCache { 337 return cache.NewLiveStateCache(argoDB, appInformer, settingsMgr, kubeutil.NewKubectl(), server, func(managedByApp map[string]bool, ref apiv1.ObjectReference) {}, nil) 338 }