github.com/argoproj/argo-cd/v3@v3.2.1/cmd/util/app.go (about)

     1  package util
     2  
     3  import (
     4  	"bufio"
     5  	stderrors "errors"
     6  	"fmt"
     7  	"io"
     8  	"net/url"
     9  	"os"
    10  	"strings"
    11  	"time"
    12  
    13  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    14  
    15  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
    16  
    17  	log "github.com/sirupsen/logrus"
    18  	"github.com/spf13/cobra"
    19  	"github.com/spf13/pflag"
    20  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    21  	"k8s.io/utils/ptr"
    22  
    23  	"github.com/argoproj/argo-cd/v3/pkg/apis/application"
    24  	argoappv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    25  	"github.com/argoproj/argo-cd/v3/util/argo"
    26  	"github.com/argoproj/argo-cd/v3/util/config"
    27  	"github.com/argoproj/argo-cd/v3/util/errors"
    28  	"github.com/argoproj/argo-cd/v3/util/text/label"
    29  )
    30  
    31  type AppOptions struct {
    32  	repoURL                         string
    33  	appPath                         string
    34  	chart                           string
    35  	env                             string
    36  	revision                        string
    37  	revisionHistoryLimit            int
    38  	destName                        string
    39  	destServer                      string
    40  	destNamespace                   string
    41  	Parameters                      []string
    42  	valuesFiles                     []string
    43  	ignoreMissingValueFiles         bool
    44  	values                          string
    45  	releaseName                     string
    46  	helmSets                        []string
    47  	helmSetStrings                  []string
    48  	helmSetFiles                    []string
    49  	helmVersion                     string
    50  	helmPassCredentials             bool
    51  	helmSkipCrds                    bool
    52  	helmSkipSchemaValidation        bool
    53  	helmSkipTests                   bool
    54  	helmNamespace                   string
    55  	helmKubeVersion                 string
    56  	helmApiVersions                 []string //nolint:revive //FIXME(var-naming)
    57  	project                         string
    58  	syncPolicy                      string
    59  	syncOptions                     []string
    60  	autoPrune                       bool
    61  	selfHeal                        bool
    62  	allowEmpty                      bool
    63  	namePrefix                      string
    64  	nameSuffix                      string
    65  	directoryRecurse                bool
    66  	configManagementPlugin          string
    67  	jsonnetTlaStr                   []string
    68  	jsonnetTlaCode                  []string
    69  	jsonnetExtVarStr                []string
    70  	jsonnetExtVarCode               []string
    71  	jsonnetLibs                     []string
    72  	kustomizeImages                 []string
    73  	kustomizeReplicas               []string
    74  	kustomizeVersion                string
    75  	kustomizeCommonLabels           []string
    76  	kustomizeCommonAnnotations      []string
    77  	kustomizeLabelWithoutSelector   bool
    78  	kustomizeLabelIncludeTemplates  bool
    79  	kustomizeForceCommonLabels      bool
    80  	kustomizeForceCommonAnnotations bool
    81  	kustomizeNamespace              string
    82  	kustomizeKubeVersion            string
    83  	kustomizeApiVersions            []string //nolint:revive //FIXME(var-naming)
    84  	ignoreMissingComponents         bool
    85  	pluginEnvs                      []string
    86  	Validate                        bool
    87  	directoryExclude                string
    88  	directoryInclude                string
    89  	retryLimit                      int64
    90  	retryBackoffDuration            time.Duration
    91  	retryBackoffMaxDuration         time.Duration
    92  	retryBackoffFactor              int64
    93  	retryRefresh                    bool
    94  	ref                             string
    95  	SourceName                      string
    96  	drySourceRepo                   string
    97  	drySourceRevision               string
    98  	drySourcePath                   string
    99  	syncSourceBranch                string
   100  	syncSourcePath                  string
   101  	hydrateToBranch                 string
   102  }
   103  
   104  func AddAppFlags(command *cobra.Command, opts *AppOptions) {
   105  	command.Flags().StringVar(&opts.repoURL, "repo", "", "Repository URL, ignored if a file is set")
   106  	command.Flags().StringVar(&opts.appPath, "path", "", "Path in repository to the app directory, ignored if a file is set")
   107  	command.Flags().StringVar(&opts.chart, "helm-chart", "", "Helm Chart name")
   108  	command.Flags().StringVar(&opts.env, "env", "", "Application environment to monitor")
   109  	command.Flags().StringVar(&opts.revision, "revision", "", "The tracking source branch, tag, commit or Helm chart version the application will sync to")
   110  	command.Flags().StringVar(&opts.drySourceRepo, "dry-source-repo", "", "Repository URL of the app dry source")
   111  	command.Flags().StringVar(&opts.drySourceRevision, "dry-source-revision", "", "Revision of the app dry source")
   112  	command.Flags().StringVar(&opts.drySourcePath, "dry-source-path", "", "Path in repository to the app directory for the dry source")
   113  	command.Flags().StringVar(&opts.syncSourceBranch, "sync-source-branch", "", "The branch from which the app will sync")
   114  	command.Flags().StringVar(&opts.syncSourcePath, "sync-source-path", "", "The path in the repository from which the app will sync")
   115  	command.Flags().StringVar(&opts.hydrateToBranch, "hydrate-to-branch", "", "The branch to hydrate the app to")
   116  	command.Flags().IntVar(&opts.revisionHistoryLimit, "revision-history-limit", argoappv1.RevisionHistoryLimit, "How many items to keep in revision history")
   117  	command.Flags().StringVar(&opts.destServer, "dest-server", "", "K8s cluster URL (e.g. https://kubernetes.default.svc)")
   118  	command.Flags().StringVar(&opts.destName, "dest-name", "", "K8s cluster Name (e.g. minikube)")
   119  	command.Flags().StringVar(&opts.destNamespace, "dest-namespace", "", "K8s target namespace")
   120  	command.Flags().StringArrayVarP(&opts.Parameters, "parameter", "p", []string{}, "set a parameter override (e.g. -p guestbook=image=example/guestbook:latest)")
   121  	command.Flags().StringArrayVar(&opts.valuesFiles, "values", []string{}, "Helm values file(s) to use")
   122  	command.Flags().BoolVar(&opts.ignoreMissingValueFiles, "ignore-missing-value-files", false, "Ignore locally missing valueFiles when setting helm template --values")
   123  	command.Flags().StringVar(&opts.values, "values-literal-file", "", "Filename or URL to import as a literal Helm values block")
   124  	command.Flags().StringVar(&opts.releaseName, "release-name", "", "Helm release-name")
   125  	command.Flags().StringVar(&opts.helmVersion, "helm-version", "", "Helm version")
   126  	command.Flags().BoolVar(&opts.helmPassCredentials, "helm-pass-credentials", false, "Pass credentials to all domain")
   127  	command.Flags().StringArrayVar(&opts.helmSets, "helm-set", []string{}, "Helm set values on the command line (can be repeated to set several values: --helm-set key1=val1 --helm-set key2=val2)")
   128  	command.Flags().StringArrayVar(&opts.helmSetStrings, "helm-set-string", []string{}, "Helm set STRING values on the command line (can be repeated to set several values: --helm-set-string key1=val1 --helm-set-string key2=val2)")
   129  	command.Flags().StringArrayVar(&opts.helmSetFiles, "helm-set-file", []string{}, "Helm set values from respective files specified via the command line (can be repeated to set several values: --helm-set-file key1=path1 --helm-set-file key2=path2)")
   130  	command.Flags().BoolVar(&opts.helmSkipCrds, "helm-skip-crds", false, "Skip helm crd installation step")
   131  	command.Flags().BoolVar(&opts.helmSkipSchemaValidation, "helm-skip-schema-validation", false, "Skip helm schema validation step")
   132  	command.Flags().BoolVar(&opts.helmSkipTests, "helm-skip-tests", false, "Skip helm test manifests installation step")
   133  	command.Flags().StringVar(&opts.helmNamespace, "helm-namespace", "", "Helm namespace to use when running helm template. If not set, use app.spec.destination.namespace")
   134  	command.Flags().StringVar(&opts.helmKubeVersion, "helm-kube-version", "", "Helm kube-version to use when running helm template. If not set, use the kube version from the destination cluster")
   135  	command.Flags().StringArrayVar(&opts.helmApiVersions, "helm-api-versions", []string{}, "Helm api-versions (in format [group/]version/kind) to use when running helm template (Can be repeated to set several values: --helm-api-versions traefik.io/v1alpha1/TLSOption --helm-api-versions v1/Service). If not set, use the api-versions from the destination cluster")
   136  	command.Flags().StringVar(&opts.project, "project", "", "Application project name")
   137  	command.Flags().StringVar(&opts.syncPolicy, "sync-policy", "", "Set the sync policy (one of: manual (aliases of manual: none), automated (aliases of automated: auto, automatic))")
   138  	command.Flags().StringArrayVar(&opts.syncOptions, "sync-option", []string{}, "Add or remove a sync option, e.g add `Prune=false`. Remove using `!` prefix, e.g. `!Prune=false`")
   139  	command.Flags().BoolVar(&opts.autoPrune, "auto-prune", false, "Set automatic pruning when sync is automated")
   140  	command.Flags().BoolVar(&opts.selfHeal, "self-heal", false, "Set self healing when sync is automated")
   141  	command.Flags().BoolVar(&opts.allowEmpty, "allow-empty", false, "Set allow zero live resources when sync is automated")
   142  	command.Flags().StringVar(&opts.namePrefix, "nameprefix", "", "Kustomize nameprefix")
   143  	command.Flags().StringVar(&opts.nameSuffix, "namesuffix", "", "Kustomize namesuffix")
   144  	command.Flags().StringVar(&opts.kustomizeVersion, "kustomize-version", "", "Kustomize version")
   145  	command.Flags().BoolVar(&opts.directoryRecurse, "directory-recurse", false, "Recurse directory")
   146  	command.Flags().StringVar(&opts.configManagementPlugin, "config-management-plugin", "", "Config management plugin name")
   147  	command.Flags().StringArrayVar(&opts.jsonnetTlaStr, "jsonnet-tla-str", []string{}, "Jsonnet top level string arguments")
   148  	command.Flags().StringArrayVar(&opts.jsonnetTlaCode, "jsonnet-tla-code", []string{}, "Jsonnet top level code arguments")
   149  	command.Flags().StringArrayVar(&opts.jsonnetExtVarStr, "jsonnet-ext-var-str", []string{}, "Jsonnet string ext var")
   150  	command.Flags().StringArrayVar(&opts.jsonnetExtVarCode, "jsonnet-ext-var-code", []string{}, "Jsonnet ext var")
   151  	command.Flags().StringArrayVar(&opts.jsonnetLibs, "jsonnet-libs", []string{}, "Additional jsonnet libs (prefixed by repoRoot)")
   152  	command.Flags().StringArrayVar(&opts.kustomizeImages, "kustomize-image", []string{}, "Kustomize images (e.g. --kustomize-image node:8.15.0 --kustomize-image mysql=mariadb,alpine@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d)")
   153  	command.Flags().StringArrayVar(&opts.kustomizeReplicas, "kustomize-replica", []string{}, "Kustomize replicas (e.g. --kustomize-replica my-development=2 --kustomize-replica my-statefulset=4)")
   154  	command.Flags().BoolVar(&opts.ignoreMissingComponents, "ignore-missing-components", false, "Ignore locally missing component directories when setting Kustomize components")
   155  	command.Flags().StringArrayVar(&opts.pluginEnvs, "plugin-env", []string{}, "Additional plugin envs")
   156  	command.Flags().BoolVar(&opts.Validate, "validate", true, "Validation of repo and cluster")
   157  	command.Flags().StringArrayVar(&opts.kustomizeCommonLabels, "kustomize-common-label", []string{}, "Set common labels in Kustomize")
   158  	command.Flags().StringArrayVar(&opts.kustomizeCommonAnnotations, "kustomize-common-annotation", []string{}, "Set common labels in Kustomize")
   159  	command.Flags().BoolVar(&opts.kustomizeLabelWithoutSelector, "kustomize-label-without-selector", false, "Do not apply common label to selectors. Also do not apply label to templates unless --kustomize-label-include-templates is set")
   160  	command.Flags().BoolVar(&opts.kustomizeLabelIncludeTemplates, "kustomize-label-include-templates", false, "Apply common label to resource templates")
   161  	command.Flags().BoolVar(&opts.kustomizeForceCommonLabels, "kustomize-force-common-label", false, "Force common labels in Kustomize")
   162  	command.Flags().BoolVar(&opts.kustomizeForceCommonAnnotations, "kustomize-force-common-annotation", false, "Force common annotations in Kustomize")
   163  	command.Flags().StringVar(&opts.kustomizeNamespace, "kustomize-namespace", "", "Kustomize namespace")
   164  	command.Flags().StringVar(&opts.kustomizeKubeVersion, "kustomize-kube-version", "", "kube-version to use when running helm template. If not set, use the kube version from the destination cluster. Only applicable when Helm is enabled for Kustomize builds")
   165  	command.Flags().StringArrayVar(&opts.kustomizeApiVersions, "kustomize-api-versions", nil, "api-versions (in format [group/]version/kind) to use when running helm template (Can be repeated to set several values: --helm-api-versions traefik.io/v1alpha1/TLSOption --helm-api-versions v1/Service). If not set, use the api-versions from the destination cluster. Only applicable when Helm is enabled for Kustomize builds")
   166  	command.Flags().StringVar(&opts.directoryExclude, "directory-exclude", "", "Set glob expression used to exclude files from application source path")
   167  	command.Flags().StringVar(&opts.directoryInclude, "directory-include", "", "Set glob expression used to include files from application source path")
   168  	command.Flags().Int64Var(&opts.retryLimit, "sync-retry-limit", 0, "Max number of allowed sync retries")
   169  	command.Flags().DurationVar(&opts.retryBackoffDuration, "sync-retry-backoff-duration", argoappv1.DefaultSyncRetryDuration, "Sync retry backoff base duration. Input needs to be a duration (e.g. 2m, 1h)")
   170  	command.Flags().DurationVar(&opts.retryBackoffMaxDuration, "sync-retry-backoff-max-duration", argoappv1.DefaultSyncRetryMaxDuration, "Max sync retry backoff duration. Input needs to be a duration (e.g. 2m, 1h)")
   171  	command.Flags().Int64Var(&opts.retryBackoffFactor, "sync-retry-backoff-factor", argoappv1.DefaultSyncRetryFactor, "Factor multiplies the base duration after each failed sync retry")
   172  	command.Flags().BoolVar(&opts.retryRefresh, "sync-retry-refresh", false, "Indicates if the latest revision should be used on retry instead of the initial one")
   173  	command.Flags().StringVar(&opts.ref, "ref", "", "Ref is reference to another source within sources field")
   174  	command.Flags().StringVar(&opts.SourceName, "source-name", "", "Name of the source from the list of sources of the app.")
   175  }
   176  
   177  func SetAppSpecOptions(flags *pflag.FlagSet, spec *argoappv1.ApplicationSpec, appOpts *AppOptions, sourcePosition int) int {
   178  	visited := 0
   179  	if flags == nil {
   180  		return visited
   181  	}
   182  	var h *argoappv1.SourceHydrator
   183  	h, hasHydratorFlag := constructSourceHydrator(spec.SourceHydrator, *appOpts, flags)
   184  	if hasHydratorFlag {
   185  		spec.SourceHydrator = h
   186  	} else {
   187  		source := spec.GetSourcePtrByPosition(sourcePosition)
   188  		if source == nil {
   189  			source = &argoappv1.ApplicationSource{}
   190  		}
   191  		source, visited = ConstructSource(source, *appOpts, flags)
   192  		if spec.HasMultipleSources() {
   193  			switch {
   194  			case sourcePosition == 0:
   195  				spec.Sources[sourcePosition] = *source
   196  			case sourcePosition > 0:
   197  				spec.Sources[sourcePosition-1] = *source
   198  			default:
   199  				spec.Sources = append(spec.Sources, *source)
   200  			}
   201  		} else {
   202  			spec.Source = source
   203  		}
   204  	}
   205  	flags.Visit(func(f *pflag.Flag) {
   206  		visited++
   207  
   208  		switch f.Name {
   209  		case "revision-history-limit":
   210  			i := int64(appOpts.revisionHistoryLimit)
   211  			spec.RevisionHistoryLimit = &i
   212  		case "dest-name":
   213  			spec.Destination.Name = appOpts.destName
   214  		case "dest-server":
   215  			spec.Destination.Server = appOpts.destServer
   216  		case "dest-namespace":
   217  			spec.Destination.Namespace = appOpts.destNamespace
   218  		case "project":
   219  			spec.Project = appOpts.project
   220  		case "sync-policy":
   221  			switch appOpts.syncPolicy {
   222  			case "none", "manual":
   223  				if spec.SyncPolicy != nil {
   224  					spec.SyncPolicy.Automated = nil
   225  				}
   226  				if spec.SyncPolicy.IsZero() {
   227  					spec.SyncPolicy = nil
   228  				}
   229  			case "automated", "automatic", "auto":
   230  				if spec.SyncPolicy == nil {
   231  					spec.SyncPolicy = &argoappv1.SyncPolicy{}
   232  				}
   233  				spec.SyncPolicy.Automated = &argoappv1.SyncPolicyAutomated{}
   234  			default:
   235  				log.Fatalf("Invalid sync-policy: %s", appOpts.syncPolicy)
   236  			}
   237  		case "sync-option":
   238  			if spec.SyncPolicy == nil {
   239  				spec.SyncPolicy = &argoappv1.SyncPolicy{}
   240  			}
   241  			for _, option := range appOpts.syncOptions {
   242  				// `!` means remove the option
   243  				if strings.HasPrefix(option, "!") {
   244  					option = strings.TrimPrefix(option, "!")
   245  					spec.SyncPolicy.SyncOptions = spec.SyncPolicy.SyncOptions.RemoveOption(option)
   246  				} else {
   247  					spec.SyncPolicy.SyncOptions = spec.SyncPolicy.SyncOptions.AddOption(option)
   248  				}
   249  			}
   250  			if spec.SyncPolicy.IsZero() {
   251  				spec.SyncPolicy = nil
   252  			}
   253  		case "sync-retry-limit":
   254  			switch {
   255  			case appOpts.retryLimit > 0:
   256  				if spec.SyncPolicy == nil {
   257  					spec.SyncPolicy = &argoappv1.SyncPolicy{}
   258  				}
   259  				spec.SyncPolicy.Retry = &argoappv1.RetryStrategy{
   260  					Limit: appOpts.retryLimit,
   261  					Backoff: &argoappv1.Backoff{
   262  						Duration:    appOpts.retryBackoffDuration.String(),
   263  						MaxDuration: appOpts.retryBackoffMaxDuration.String(),
   264  						Factor:      ptr.To(appOpts.retryBackoffFactor),
   265  					},
   266  					Refresh: appOpts.retryRefresh,
   267  				}
   268  			case appOpts.retryLimit == 0:
   269  				if spec.SyncPolicy.IsZero() {
   270  					spec.SyncPolicy = nil
   271  				} else {
   272  					spec.SyncPolicy.Retry = nil
   273  				}
   274  			default:
   275  				log.Fatalf("Invalid sync-retry-limit [%d]", appOpts.retryLimit)
   276  			}
   277  		case "sync-retry-refresh":
   278  			if spec.SyncPolicy == nil {
   279  				spec.SyncPolicy = &argoappv1.SyncPolicy{}
   280  			}
   281  			if spec.SyncPolicy.Retry == nil {
   282  				spec.SyncPolicy.Retry = &argoappv1.RetryStrategy{}
   283  			}
   284  			spec.SyncPolicy.Retry.Refresh = appOpts.retryRefresh
   285  		}
   286  	})
   287  	if flags.Changed("auto-prune") {
   288  		if spec.SyncPolicy == nil || !spec.SyncPolicy.IsAutomatedSyncEnabled() {
   289  			log.Fatal("Cannot set --auto-prune: application not configured with automatic sync")
   290  		}
   291  		spec.SyncPolicy.Automated.Prune = appOpts.autoPrune
   292  	}
   293  	if flags.Changed("self-heal") {
   294  		if spec.SyncPolicy == nil || !spec.SyncPolicy.IsAutomatedSyncEnabled() {
   295  			log.Fatal("Cannot set --self-heal: application not configured with automatic sync")
   296  		}
   297  		spec.SyncPolicy.Automated.SelfHeal = appOpts.selfHeal
   298  	}
   299  	if flags.Changed("allow-empty") {
   300  		if spec.SyncPolicy == nil || !spec.SyncPolicy.IsAutomatedSyncEnabled() {
   301  			log.Fatal("Cannot set --allow-empty: application not configured with automatic sync")
   302  		}
   303  		spec.SyncPolicy.Automated.AllowEmpty = appOpts.allowEmpty
   304  	}
   305  
   306  	return visited
   307  }
   308  
   309  type kustomizeOpts struct {
   310  	namePrefix              string
   311  	nameSuffix              string
   312  	images                  []string
   313  	replicas                []string
   314  	version                 string
   315  	commonLabels            map[string]string
   316  	commonAnnotations       map[string]string
   317  	labelWithoutSelector    bool
   318  	labelIncludeTemplates   bool
   319  	forceCommonLabels       bool
   320  	forceCommonAnnotations  bool
   321  	namespace               string
   322  	kubeVersion             string
   323  	apiVersions             []string
   324  	ignoreMissingComponents bool
   325  }
   326  
   327  func setKustomizeOpt(src *argoappv1.ApplicationSource, opts kustomizeOpts) {
   328  	if src.Kustomize == nil {
   329  		src.Kustomize = &argoappv1.ApplicationSourceKustomize{}
   330  	}
   331  	if opts.version != "" {
   332  		src.Kustomize.Version = opts.version
   333  	}
   334  	if opts.namePrefix != "" {
   335  		src.Kustomize.NamePrefix = opts.namePrefix
   336  	}
   337  	if opts.nameSuffix != "" {
   338  		src.Kustomize.NameSuffix = opts.nameSuffix
   339  	}
   340  	if opts.namespace != "" {
   341  		src.Kustomize.Namespace = opts.namespace
   342  	}
   343  	if opts.kubeVersion != "" {
   344  		src.Kustomize.KubeVersion = opts.kubeVersion
   345  	}
   346  	if len(opts.apiVersions) > 0 {
   347  		src.Kustomize.APIVersions = opts.apiVersions
   348  	}
   349  	if opts.commonLabels != nil {
   350  		src.Kustomize.CommonLabels = opts.commonLabels
   351  	}
   352  	if opts.commonAnnotations != nil {
   353  		src.Kustomize.CommonAnnotations = opts.commonAnnotations
   354  	}
   355  	if opts.labelWithoutSelector {
   356  		src.Kustomize.LabelWithoutSelector = opts.labelWithoutSelector
   357  	}
   358  	if opts.labelIncludeTemplates {
   359  		src.Kustomize.LabelIncludeTemplates = opts.labelIncludeTemplates
   360  	}
   361  	if opts.forceCommonLabels {
   362  		src.Kustomize.ForceCommonLabels = opts.forceCommonLabels
   363  	}
   364  	if opts.forceCommonAnnotations {
   365  		src.Kustomize.ForceCommonAnnotations = opts.forceCommonAnnotations
   366  	}
   367  	if opts.ignoreMissingComponents {
   368  		src.Kustomize.IgnoreMissingComponents = opts.ignoreMissingComponents
   369  	}
   370  	for _, image := range opts.images {
   371  		src.Kustomize.MergeImage(argoappv1.KustomizeImage(image))
   372  	}
   373  	for _, replica := range opts.replicas {
   374  		r, err := argoappv1.NewKustomizeReplica(replica)
   375  		if err != nil {
   376  			log.Fatal(err)
   377  		}
   378  		src.Kustomize.MergeReplica(*r)
   379  	}
   380  
   381  	if src.Kustomize.IsZero() {
   382  		src.Kustomize = nil
   383  	}
   384  }
   385  
   386  func setPluginOptEnvs(src *argoappv1.ApplicationSource, envs []string) {
   387  	if src.Plugin == nil {
   388  		src.Plugin = &argoappv1.ApplicationSourcePlugin{}
   389  	}
   390  
   391  	for _, text := range envs {
   392  		e, err := argoappv1.NewEnvEntry(text)
   393  		if err != nil {
   394  			log.Fatal(err)
   395  		}
   396  		src.Plugin.AddEnvEntry(e)
   397  	}
   398  }
   399  
   400  type helmOpts struct {
   401  	valueFiles              []string
   402  	ignoreMissingValueFiles bool
   403  	values                  string
   404  	releaseName             string
   405  	version                 string
   406  	helmSets                []string
   407  	helmSetStrings          []string
   408  	helmSetFiles            []string
   409  	passCredentials         bool
   410  	skipCrds                bool
   411  	skipSchemaValidation    bool
   412  	skipTests               bool
   413  	namespace               string
   414  	kubeVersion             string
   415  	apiVersions             []string
   416  }
   417  
   418  func setHelmOpt(src *argoappv1.ApplicationSource, opts helmOpts) {
   419  	if src.Helm == nil {
   420  		src.Helm = &argoappv1.ApplicationSourceHelm{}
   421  	}
   422  	if len(opts.valueFiles) > 0 {
   423  		src.Helm.ValueFiles = opts.valueFiles
   424  	}
   425  	if opts.ignoreMissingValueFiles {
   426  		src.Helm.IgnoreMissingValueFiles = opts.ignoreMissingValueFiles
   427  	}
   428  	if opts.values != "" {
   429  		err := src.Helm.SetValuesString(opts.values)
   430  		if err != nil {
   431  			log.Fatal(err)
   432  		}
   433  	}
   434  	if opts.releaseName != "" {
   435  		src.Helm.ReleaseName = opts.releaseName
   436  	}
   437  	if opts.version != "" {
   438  		src.Helm.Version = opts.version
   439  	}
   440  	if opts.passCredentials {
   441  		src.Helm.PassCredentials = opts.passCredentials
   442  	}
   443  	if opts.skipCrds {
   444  		src.Helm.SkipCrds = opts.skipCrds
   445  	}
   446  	if opts.skipSchemaValidation {
   447  		src.Helm.SkipSchemaValidation = opts.skipSchemaValidation
   448  	}
   449  	if opts.skipTests {
   450  		src.Helm.SkipTests = opts.skipTests
   451  	}
   452  	if opts.namespace != "" {
   453  		src.Helm.Namespace = opts.namespace
   454  	}
   455  	if opts.kubeVersion != "" {
   456  		src.Helm.KubeVersion = opts.kubeVersion
   457  	}
   458  	if len(opts.apiVersions) > 0 {
   459  		src.Helm.APIVersions = opts.apiVersions
   460  	}
   461  	for _, text := range opts.helmSets {
   462  		p, err := argoappv1.NewHelmParameter(text, false)
   463  		if err != nil {
   464  			log.Fatal(err)
   465  		}
   466  		src.Helm.AddParameter(*p)
   467  	}
   468  	for _, text := range opts.helmSetStrings {
   469  		p, err := argoappv1.NewHelmParameter(text, true)
   470  		if err != nil {
   471  			log.Fatal(err)
   472  		}
   473  		src.Helm.AddParameter(*p)
   474  	}
   475  	for _, text := range opts.helmSetFiles {
   476  		p, err := argoappv1.NewHelmFileParameter(text)
   477  		if err != nil {
   478  			log.Fatal(err)
   479  		}
   480  		src.Helm.AddFileParameter(*p)
   481  	}
   482  	if src.Helm.IsZero() {
   483  		src.Helm = nil
   484  	}
   485  }
   486  
   487  func setJsonnetOpt(src *argoappv1.ApplicationSource, tlaParameters []string, code bool) {
   488  	if src.Directory == nil {
   489  		src.Directory = &argoappv1.ApplicationSourceDirectory{}
   490  	}
   491  	for _, j := range tlaParameters {
   492  		src.Directory.Jsonnet.TLAs = append(src.Directory.Jsonnet.TLAs, argoappv1.NewJsonnetVar(j, code))
   493  	}
   494  }
   495  
   496  func setJsonnetOptExtVar(src *argoappv1.ApplicationSource, jsonnetExtVar []string, code bool) {
   497  	if src.Directory == nil {
   498  		src.Directory = &argoappv1.ApplicationSourceDirectory{}
   499  	}
   500  	for _, j := range jsonnetExtVar {
   501  		src.Directory.Jsonnet.ExtVars = append(src.Directory.Jsonnet.ExtVars, argoappv1.NewJsonnetVar(j, code))
   502  	}
   503  }
   504  
   505  func setJsonnetOptLibs(src *argoappv1.ApplicationSource, libs []string) {
   506  	if src.Directory == nil {
   507  		src.Directory = &argoappv1.ApplicationSourceDirectory{}
   508  	}
   509  	src.Directory.Jsonnet.Libs = append(src.Directory.Jsonnet.Libs, libs...)
   510  }
   511  
   512  // SetParameterOverrides updates an existing or appends a new parameter override in the application
   513  // The app is assumed to be a helm app and is expected to be in the form:
   514  // param=value
   515  func SetParameterOverrides(app *argoappv1.Application, parameters []string, index int) {
   516  	if len(parameters) == 0 {
   517  		return
   518  	}
   519  	source := app.Spec.GetSourcePtrByIndex(index)
   520  	var sourceType argoappv1.ApplicationSourceType
   521  	if st, _ := source.ExplicitType(); st != nil {
   522  		sourceType = *st
   523  	} else if app.Status.SourceType != "" {
   524  		sourceType = app.Status.SourceType
   525  	} else if len(strings.SplitN(parameters[0], "=", 2)) == 2 {
   526  		sourceType = argoappv1.ApplicationSourceTypeHelm
   527  	}
   528  
   529  	switch sourceType {
   530  	case argoappv1.ApplicationSourceTypeHelm:
   531  		if source.Helm == nil {
   532  			source.Helm = &argoappv1.ApplicationSourceHelm{}
   533  		}
   534  		for _, p := range parameters {
   535  			newParam, err := argoappv1.NewHelmParameter(p, false)
   536  			if err != nil {
   537  				log.Error(err)
   538  				continue
   539  			}
   540  			source.Helm.AddParameter(*newParam)
   541  		}
   542  	default:
   543  		log.Fatalf("Parameters can only be set against Helm applications")
   544  	}
   545  }
   546  
   547  func readApps(yml []byte, apps *[]*argoappv1.Application) error {
   548  	yamls, _ := kube.SplitYAMLToString(yml)
   549  
   550  	var err error
   551  
   552  	for _, yml := range yamls {
   553  		var app argoappv1.Application
   554  		err = config.Unmarshal([]byte(yml), &app)
   555  		*apps = append(*apps, &app)
   556  		if err != nil {
   557  			return err
   558  		}
   559  	}
   560  
   561  	return err
   562  }
   563  
   564  func readAppsFromStdin(apps *[]*argoappv1.Application) error {
   565  	reader := bufio.NewReader(os.Stdin)
   566  	data, err := io.ReadAll(reader)
   567  	if err != nil {
   568  		return err
   569  	}
   570  	err = readApps(data, apps)
   571  	if err != nil {
   572  		return fmt.Errorf("unable to read manifest from stdin: %w", err)
   573  	}
   574  	return nil
   575  }
   576  
   577  func readAppsFromURI(fileURL string, apps *[]*argoappv1.Application) error {
   578  	readFilePayload := func() ([]byte, error) {
   579  		parsedURL, err := url.ParseRequestURI(fileURL)
   580  		if err != nil || (parsedURL.Scheme != "http" && parsedURL.Scheme != "https") {
   581  			return os.ReadFile(fileURL)
   582  		}
   583  		return config.ReadRemoteFile(fileURL)
   584  	}
   585  
   586  	yml, err := readFilePayload()
   587  	if err != nil {
   588  		return err
   589  	}
   590  
   591  	return readApps(yml, apps)
   592  }
   593  
   594  func constructAppsFromStdin() ([]*argoappv1.Application, error) {
   595  	apps := make([]*argoappv1.Application, 0)
   596  	// read stdin
   597  	err := readAppsFromStdin(&apps)
   598  	if err != nil {
   599  		return nil, err
   600  	}
   601  	return apps, nil
   602  }
   603  
   604  func constructAppsBaseOnName(appName string, labels, annotations, args []string, appOpts AppOptions, flags *pflag.FlagSet) ([]*argoappv1.Application, error) {
   605  	var app *argoappv1.Application
   606  
   607  	// read arguments
   608  	if len(args) == 1 {
   609  		if appName != "" && appName != args[0] {
   610  			return nil, fmt.Errorf("--name argument '%s' does not match app name %s", appName, args[0])
   611  		}
   612  		appName = args[0]
   613  	}
   614  	appName, appNs := argo.ParseFromQualifiedName(appName, "")
   615  	app = &argoappv1.Application{
   616  		TypeMeta: metav1.TypeMeta{
   617  			Kind:       application.ApplicationKind,
   618  			APIVersion: application.Group + "/v1alpha1",
   619  		},
   620  		ObjectMeta: metav1.ObjectMeta{
   621  			Name:      appName,
   622  			Namespace: appNs,
   623  		},
   624  		Spec: argoappv1.ApplicationSpec{},
   625  	}
   626  	SetAppSpecOptions(flags, &app.Spec, &appOpts, 0)
   627  	SetParameterOverrides(app, appOpts.Parameters, 0)
   628  	mergeLabels(app, labels)
   629  	setAnnotations(app, annotations)
   630  	return []*argoappv1.Application{
   631  		app,
   632  	}, nil
   633  }
   634  
   635  func constructAppsFromFileURL(fileURL, appName string, labels, annotations, args []string, appOpts AppOptions, flags *pflag.FlagSet) ([]*argoappv1.Application, error) {
   636  	apps := make([]*argoappv1.Application, 0)
   637  	// read uri
   638  	err := readAppsFromURI(fileURL, &apps)
   639  	if err != nil {
   640  		return nil, err
   641  	}
   642  	for _, app := range apps {
   643  		if len(args) == 1 && args[0] != app.Name {
   644  			return nil, fmt.Errorf("app name '%s' does not match app spec metadata.name '%s'", args[0], app.Name)
   645  		}
   646  		if appName != "" && appName != app.Name {
   647  			app.Name = appName
   648  		}
   649  		if app.Name == "" {
   650  			return nil, stderrors.New("app.Name is empty. --name argument can be used to provide app.Name")
   651  		}
   652  
   653  		mergeLabels(app, labels)
   654  		setAnnotations(app, annotations)
   655  
   656  		// do not allow overrides for applications with multiple sources
   657  		if !app.Spec.HasMultipleSources() {
   658  			SetAppSpecOptions(flags, &app.Spec, &appOpts, 0)
   659  			SetParameterOverrides(app, appOpts.Parameters, 0)
   660  		}
   661  	}
   662  	return apps, nil
   663  }
   664  
   665  func ConstructApps(fileURL, appName string, labels, annotations, args []string, appOpts AppOptions, flags *pflag.FlagSet) ([]*argoappv1.Application, error) {
   666  	if fileURL == "-" {
   667  		return constructAppsFromStdin()
   668  	} else if fileURL != "" {
   669  		return constructAppsFromFileURL(fileURL, appName, labels, annotations, args, appOpts, flags)
   670  	}
   671  
   672  	return constructAppsBaseOnName(appName, labels, annotations, args, appOpts, flags)
   673  }
   674  
   675  func ConstructSource(source *argoappv1.ApplicationSource, appOpts AppOptions, flags *pflag.FlagSet) (*argoappv1.ApplicationSource, int) {
   676  	visited := 0
   677  	flags.Visit(func(f *pflag.Flag) {
   678  		visited++
   679  		switch f.Name {
   680  		case "repo":
   681  			source.RepoURL = appOpts.repoURL
   682  		case "path":
   683  			source.Path = appOpts.appPath
   684  		case "helm-chart":
   685  			source.Chart = appOpts.chart
   686  		case "revision":
   687  			source.TargetRevision = appOpts.revision
   688  		case "values":
   689  			setHelmOpt(source, helmOpts{valueFiles: appOpts.valuesFiles})
   690  		case "ignore-missing-value-files":
   691  			setHelmOpt(source, helmOpts{ignoreMissingValueFiles: appOpts.ignoreMissingValueFiles})
   692  		case "values-literal-file":
   693  			var data []byte
   694  			// read uri
   695  			parsedURL, err := url.ParseRequestURI(appOpts.values)
   696  			if err != nil || (parsedURL.Scheme != "http" && parsedURL.Scheme != "https") {
   697  				data, err = os.ReadFile(appOpts.values)
   698  			} else {
   699  				data, err = config.ReadRemoteFile(appOpts.values)
   700  			}
   701  			errors.CheckError(err)
   702  			setHelmOpt(source, helmOpts{values: string(data)})
   703  		case "release-name":
   704  			setHelmOpt(source, helmOpts{releaseName: appOpts.releaseName})
   705  		case "helm-version":
   706  			setHelmOpt(source, helmOpts{version: appOpts.helmVersion})
   707  		case "helm-pass-credentials":
   708  			setHelmOpt(source, helmOpts{passCredentials: appOpts.helmPassCredentials})
   709  		case "helm-set":
   710  			setHelmOpt(source, helmOpts{helmSets: appOpts.helmSets})
   711  		case "helm-set-string":
   712  			setHelmOpt(source, helmOpts{helmSetStrings: appOpts.helmSetStrings})
   713  		case "helm-set-file":
   714  			setHelmOpt(source, helmOpts{helmSetFiles: appOpts.helmSetFiles})
   715  		case "helm-skip-crds":
   716  			setHelmOpt(source, helmOpts{skipCrds: appOpts.helmSkipCrds})
   717  		case "helm-skip-schema-validation":
   718  			setHelmOpt(source, helmOpts{skipSchemaValidation: appOpts.helmSkipSchemaValidation})
   719  		case "helm-skip-tests":
   720  			setHelmOpt(source, helmOpts{skipTests: appOpts.helmSkipTests})
   721  		case "helm-namespace":
   722  			setHelmOpt(source, helmOpts{namespace: appOpts.helmNamespace})
   723  		case "helm-kube-version":
   724  			setHelmOpt(source, helmOpts{kubeVersion: appOpts.helmKubeVersion})
   725  		case "helm-api-versions":
   726  			setHelmOpt(source, helmOpts{apiVersions: appOpts.helmApiVersions})
   727  		case "directory-recurse":
   728  			if source.Directory != nil {
   729  				source.Directory.Recurse = appOpts.directoryRecurse
   730  			} else {
   731  				source.Directory = &argoappv1.ApplicationSourceDirectory{Recurse: appOpts.directoryRecurse}
   732  			}
   733  		case "directory-exclude":
   734  			if source.Directory != nil {
   735  				source.Directory.Exclude = appOpts.directoryExclude
   736  			} else {
   737  				source.Directory = &argoappv1.ApplicationSourceDirectory{Exclude: appOpts.directoryExclude}
   738  			}
   739  		case "directory-include":
   740  			if source.Directory != nil {
   741  				source.Directory.Include = appOpts.directoryInclude
   742  			} else {
   743  				source.Directory = &argoappv1.ApplicationSourceDirectory{Include: appOpts.directoryInclude}
   744  			}
   745  		case "config-management-plugin":
   746  			source.Plugin = &argoappv1.ApplicationSourcePlugin{Name: appOpts.configManagementPlugin}
   747  		case "nameprefix":
   748  			setKustomizeOpt(source, kustomizeOpts{namePrefix: appOpts.namePrefix})
   749  		case "namesuffix":
   750  			setKustomizeOpt(source, kustomizeOpts{nameSuffix: appOpts.nameSuffix})
   751  		case "kustomize-image":
   752  			setKustomizeOpt(source, kustomizeOpts{images: appOpts.kustomizeImages})
   753  		case "kustomize-replica":
   754  			setKustomizeOpt(source, kustomizeOpts{replicas: appOpts.kustomizeReplicas})
   755  		case "kustomize-version":
   756  			setKustomizeOpt(source, kustomizeOpts{version: appOpts.kustomizeVersion})
   757  		case "kustomize-namespace":
   758  			setKustomizeOpt(source, kustomizeOpts{namespace: appOpts.kustomizeNamespace})
   759  		case "kustomize-kube-version":
   760  			setKustomizeOpt(source, kustomizeOpts{kubeVersion: appOpts.kustomizeKubeVersion})
   761  		case "kustomize-api-versions":
   762  			setKustomizeOpt(source, kustomizeOpts{apiVersions: appOpts.kustomizeApiVersions})
   763  		case "kustomize-common-label":
   764  			parsedLabels, err := label.Parse(appOpts.kustomizeCommonLabels)
   765  			errors.CheckError(err)
   766  			setKustomizeOpt(source, kustomizeOpts{commonLabels: parsedLabels})
   767  		case "kustomize-common-annotation":
   768  			parsedAnnotations, err := label.Parse(appOpts.kustomizeCommonAnnotations)
   769  			errors.CheckError(err)
   770  			setKustomizeOpt(source, kustomizeOpts{commonAnnotations: parsedAnnotations})
   771  		case "kustomize-label-without-selector":
   772  			setKustomizeOpt(source, kustomizeOpts{labelWithoutSelector: appOpts.kustomizeLabelWithoutSelector})
   773  		case "kustomize-label-include-templates":
   774  			setKustomizeOpt(source, kustomizeOpts{labelIncludeTemplates: appOpts.kustomizeLabelIncludeTemplates})
   775  		case "kustomize-force-common-label":
   776  			setKustomizeOpt(source, kustomizeOpts{forceCommonLabels: appOpts.kustomizeForceCommonLabels})
   777  		case "kustomize-force-common-annotation":
   778  			setKustomizeOpt(source, kustomizeOpts{forceCommonAnnotations: appOpts.kustomizeForceCommonAnnotations})
   779  		case "ignore-missing-components":
   780  			setKustomizeOpt(source, kustomizeOpts{ignoreMissingComponents: appOpts.ignoreMissingComponents})
   781  		case "jsonnet-tla-str":
   782  			setJsonnetOpt(source, appOpts.jsonnetTlaStr, false)
   783  		case "jsonnet-tla-code":
   784  			setJsonnetOpt(source, appOpts.jsonnetTlaCode, true)
   785  		case "jsonnet-ext-var-str":
   786  			setJsonnetOptExtVar(source, appOpts.jsonnetExtVarStr, false)
   787  		case "jsonnet-ext-var-code":
   788  			setJsonnetOptExtVar(source, appOpts.jsonnetExtVarCode, true)
   789  		case "jsonnet-libs":
   790  			setJsonnetOptLibs(source, appOpts.jsonnetLibs)
   791  		case "plugin-env":
   792  			setPluginOptEnvs(source, appOpts.pluginEnvs)
   793  		case "ref":
   794  			source.Ref = appOpts.ref
   795  		case "source-name":
   796  			source.Name = appOpts.SourceName
   797  		}
   798  	})
   799  	return source, visited
   800  }
   801  
   802  // constructSourceHydrator constructs a source hydrator from the command line flags. It returns the modified source
   803  // hydrator and a boolean indicating if any hydrator flags were set. We return instead of just modifying the source
   804  // hydrator in place because the given hydrator `h` might be nil. In that case, we need to create a new source hydrator
   805  // and return it.
   806  func constructSourceHydrator(h *argoappv1.SourceHydrator, appOpts AppOptions, flags *pflag.FlagSet) (*argoappv1.SourceHydrator, bool) {
   807  	hasHydratorFlag := false
   808  	ensureNotNil := func(notEmpty bool) {
   809  		hasHydratorFlag = true
   810  		if notEmpty && h == nil {
   811  			h = &argoappv1.SourceHydrator{}
   812  		}
   813  	}
   814  	flags.Visit(func(f *pflag.Flag) {
   815  		switch f.Name {
   816  		case "dry-source-repo":
   817  			ensureNotNil(appOpts.drySourceRepo != "")
   818  			h.DrySource.RepoURL = appOpts.drySourceRepo
   819  		case "dry-source-path":
   820  			ensureNotNil(appOpts.drySourcePath != "")
   821  			h.DrySource.Path = appOpts.drySourcePath
   822  		case "dry-source-revision":
   823  			ensureNotNil(appOpts.drySourceRevision != "")
   824  			h.DrySource.TargetRevision = appOpts.drySourceRevision
   825  		case "sync-source-branch":
   826  			ensureNotNil(appOpts.syncSourceBranch != "")
   827  			h.SyncSource.TargetBranch = appOpts.syncSourceBranch
   828  		case "sync-source-path":
   829  			ensureNotNil(appOpts.syncSourcePath != "")
   830  			h.SyncSource.Path = appOpts.syncSourcePath
   831  		case "hydrate-to-branch":
   832  			ensureNotNil(appOpts.hydrateToBranch != "")
   833  			if appOpts.hydrateToBranch == "" {
   834  				h.HydrateTo = nil
   835  			} else {
   836  				h.HydrateTo = &argoappv1.HydrateTo{TargetBranch: appOpts.hydrateToBranch}
   837  			}
   838  		}
   839  	})
   840  	return h, hasHydratorFlag
   841  }
   842  
   843  func mergeLabels(app *argoappv1.Application, labels []string) {
   844  	mapLabels, err := label.Parse(labels)
   845  	errors.CheckError(err)
   846  
   847  	mergedLabels := make(map[string]string)
   848  
   849  	for name, value := range app.GetLabels() {
   850  		mergedLabels[name] = value
   851  	}
   852  
   853  	for name, value := range mapLabels {
   854  		mergedLabels[name] = value
   855  	}
   856  
   857  	app.SetLabels(mergedLabels)
   858  }
   859  
   860  func setAnnotations(app *argoappv1.Application, annotations []string) {
   861  	if len(annotations) > 0 && app.Annotations == nil {
   862  		app.Annotations = map[string]string{}
   863  	}
   864  	for _, a := range annotations {
   865  		annotation := strings.SplitN(a, "=", 2)
   866  		if len(annotation) == 2 {
   867  			app.Annotations[annotation[0]] = annotation[1]
   868  		} else {
   869  			app.Annotations[annotation[0]] = ""
   870  		}
   871  	}
   872  }
   873  
   874  // LiveObjects deserializes the list of live states into unstructured objects
   875  func LiveObjects(resources []*argoappv1.ResourceDiff) ([]*unstructured.Unstructured, error) {
   876  	objs := make([]*unstructured.Unstructured, len(resources))
   877  	for i, resState := range resources {
   878  		obj, err := resState.LiveObject()
   879  		if err != nil {
   880  			return nil, err
   881  		}
   882  		objs[i] = obj
   883  	}
   884  	return objs, nil
   885  }
   886  
   887  func FilterResources(groupChanged bool, resources []*argoappv1.ResourceDiff, group, kind, namespace, resourceName string, all bool) ([]*unstructured.Unstructured, error) {
   888  	liveObjs, err := LiveObjects(resources)
   889  	errors.CheckError(err)
   890  	filteredObjects := make([]*unstructured.Unstructured, 0)
   891  	for i := range liveObjs {
   892  		obj := liveObjs[i]
   893  		if obj == nil {
   894  			continue
   895  		}
   896  		gvk := obj.GroupVersionKind()
   897  		if groupChanged && group != gvk.Group {
   898  			continue
   899  		}
   900  		if namespace != "" && namespace != obj.GetNamespace() {
   901  			continue
   902  		}
   903  		if resourceName != "" && resourceName != obj.GetName() {
   904  			continue
   905  		}
   906  		if kind != "" && kind != gvk.Kind {
   907  			continue
   908  		}
   909  		deepCopy := obj.DeepCopy()
   910  		filteredObjects = append(filteredObjects, deepCopy)
   911  	}
   912  	if len(filteredObjects) == 0 {
   913  		return nil, stderrors.New("no matching resource found")
   914  	}
   915  	if len(filteredObjects) > 1 && !all {
   916  		return nil, stderrors.New("multiple resources match inputs, use the --all flag to patch multiple resources")
   917  	}
   918  	return filteredObjects, nil
   919  }