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  }