Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add cli commands to create/set/unset/edit sources for multi-source application #17425

Merged
merged 16 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 80 additions & 23 deletions cmd/argocd/commands/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,15 @@ func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.
# Create a Kustomize app
argocd app create kustomize-guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path kustomize-guestbook --dest-namespace default --dest-server https://kubernetes.default.svc --kustomize-image gcr.io/heptio-images/ks-guestbook-demo:0.1

# Create a MultiSource app while yaml file contains an application with multiple sources
argocd app create guestbook --file <path-to-yaml-file>

# Create a app using a custom tool:
argocd app create kasane --repo https://github.com/argoproj/argocd-example-apps.git --path plugins/kasane --dest-namespace default --dest-server https://kubernetes.default.svc --config-management-plugin kasane`,
Run: func(c *cobra.Command, args []string) {
ctx := c.Context()

argocdClient := headless.NewClientOrDie(clientOpts, c)

apps, err := cmdutil.ConstructApps(fileURL, appName, labels, annotations, args, appOpts, c.Flags())
errors.CheckError(err)

Expand Down Expand Up @@ -730,6 +732,7 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
var (
appOpts cmdutil.AppOptions
appNamespace string
sourceIndex int
)
var command = &cobra.Command{
Use: "set APPNAME",
Expand All @@ -747,6 +750,9 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
# Set and override application parameters with a parameter file
argocd app set my-app --parameter-file path/to/parameter-file.yaml

# Set and override application parameters for a source at index 1 under spec.sources of app my-app. source-index starts at 1.
argocd app set my-app --source-index 1 --repo https://github.com/argoproj/argocd-example-apps.git

# Set application parameters and specify the namespace
argocd app set my-app --parameter key1=value1 --parameter key2=value2 --namespace my-namespace
`),
Expand All @@ -765,14 +771,25 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
app, err := appIf.Get(ctx, &application.ApplicationQuery{Name: &appName, AppNamespace: &appNs})
errors.CheckError(err)

visited := cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
if app.Spec.HasMultipleSources() {
if sourceIndex <= 0 {
errors.CheckError(fmt.Errorf("Source index should be specified and greater than 0 for applications with multiple sources"))
}
if len(app.Spec.GetSources()) < sourceIndex {
errors.CheckError(fmt.Errorf("Source index should be less than the number of sources in the application"))
}
}

// sourceIndex startes with 1, thus, it needs to be decreased by 1 to find the correct index in the list of sources
sourceIndex = sourceIndex - 1
visited := cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts, sourceIndex)
if visited == 0 {
log.Error("Please set at least one option to update")
c.HelpFunc()(c, args)
os.Exit(1)
}

setParameterOverrides(app, appOpts.Parameters)
setParameterOverrides(app, appOpts.Parameters, sourceIndex)
_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
Name: &app.Name,
Spec: &app.Spec,
Expand All @@ -782,6 +799,7 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
errors.CheckError(err)
},
}
command.Flags().IntVar(&sourceIndex, "source-index", -1, "Index of the source from the list of sources of the app. Index starts at 1.")
cmdutil.AddAppFlags(command, &appOpts)
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Set application parameters in namespace")
return command
Expand All @@ -801,6 +819,7 @@ type unsetOpts struct {
ignoreMissingValueFiles bool
pluginEnvs []string
passCredentials bool
ref bool
}

// IsZero returns true when the Application options for kustomize are considered empty
Expand All @@ -816,6 +835,9 @@ func (o *unsetOpts) KustomizeIsZero() bool {

// NewApplicationUnsetCommand returns a new instance of an `argocd app unset` command
func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
sourceIndex int
)
appOpts := cmdutil.AppOptions{}
opts := unsetOpts{}
var appNamespace string
Expand All @@ -825,9 +847,12 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
Example: ` # Unset kustomize override kustomize image
argocd app unset my-app --kustomize-image=alpine

# Unset kustomize override prefix
# Unset kustomize override suffix
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

argocd app unset my-app --namesuffix

# Unset kustomize override suffix for source at index 1 under spec.sources of app my-app. source-index starts at 1.
argocd app unset my-app --source-index 1 --namesuffix

# Unset parameter override
argocd app unset my-app -p COMPONENT=PARAM`,

Expand All @@ -838,14 +863,25 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
c.HelpFunc()(c, args)
os.Exit(1)
}

appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
defer argoio.Close(conn)
app, err := appIf.Get(ctx, &application.ApplicationQuery{Name: &appName, AppNamespace: &appNs})
errors.CheckError(err)

source := app.Spec.GetSource()
updated, nothingToUnset := unset(&source, opts)
if app.Spec.HasMultipleSources() {
if sourceIndex <= 0 {
errors.CheckError(fmt.Errorf("Source index should be specified and greater than 0 for applications with multiple sources"))
}
if len(app.Spec.GetSources()) < sourceIndex {
errors.CheckError(fmt.Errorf("Source index should be less than the number of sources in the application"))
}
}

source := app.Spec.GetSourcePtr(sourceIndex)

updated, nothingToUnset := unset(source, opts)
if nothingToUnset {
c.HelpFunc()(c, args)
os.Exit(1)
Expand All @@ -854,7 +890,7 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
return
}

cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts)
cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts, sourceIndex)
_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
Name: &app.Name,
Spec: &app.Spec,
Expand All @@ -877,13 +913,22 @@ func NewApplicationUnsetCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
command.Flags().StringArrayVar(&opts.kustomizeReplicas, "kustomize-replica", []string{}, "Kustomize replicas name (e.g. --kustomize-replica my-deployment --kustomize-replica my-statefulset)")
command.Flags().StringArrayVar(&opts.pluginEnvs, "plugin-env", []string{}, "Unset plugin env variables (e.g --plugin-env name)")
command.Flags().BoolVar(&opts.passCredentials, "pass-credentials", false, "Unset passCredentials")
command.Flags().BoolVar(&opts.ref, "ref", false, "Unset ref on the source")
command.Flags().IntVar(&sourceIndex, "source-index", -1, "Index of the source from the list of sources of the app. Index starts at 1.")
return command
}

func unset(source *argoappv1.ApplicationSource, opts unsetOpts) (updated bool, nothingToUnset bool) {
needToUnsetRef := false
if opts.ref && source.Ref != "" {
source.Ref = ""
updated = true
needToUnsetRef = true
}

if source.Kustomize != nil {
if opts.KustomizeIsZero() {
return false, true
return updated, !needToUnsetRef
}

if opts.namePrefix && source.Kustomize.NamePrefix != "" {
Expand Down Expand Up @@ -933,7 +978,7 @@ func unset(source *argoappv1.ApplicationSource, opts unsetOpts) (updated bool, n
}
if source.Helm != nil {
if len(opts.parameters) == 0 && len(opts.valuesFiles) == 0 && !opts.valuesLiteral && !opts.ignoreMissingValueFiles && !opts.passCredentials {
return false, true
return updated, !needToUnsetRef
}
for _, paramStr := range opts.parameters {
helmParams := source.Helm.Parameters
Expand Down Expand Up @@ -970,9 +1015,10 @@ func unset(source *argoappv1.ApplicationSource, opts unsetOpts) (updated bool, n
updated = true
}
}

if source.Plugin != nil {
if len(opts.pluginEnvs) == 0 {
return false, true
return false, !needToUnsetRef
}
for _, env := range opts.pluginEnvs {
err := source.Plugin.RemoveEnvEntry(env)
Expand Down Expand Up @@ -2419,11 +2465,11 @@ func waitOnApplicationStatus(ctx context.Context, acdClient argocdclient.Client,
// setParameterOverrides updates an existing or appends a new parameter override in the application
// the app is assumed to be a helm app and is expected to be in the form:
// param=value
func setParameterOverrides(app *argoappv1.Application, parameters []string) {
func setParameterOverrides(app *argoappv1.Application, parameters []string, index int) {
if len(parameters) == 0 {
return
}
source := app.Spec.GetSource()
source := app.Spec.GetSourcePtr(index)
var sourceType argoappv1.ApplicationSourceType
if st, _ := source.ExplicitType(); st != nil {
sourceType = *st
Expand Down Expand Up @@ -2731,7 +2777,9 @@ func NewApplicationTerminateOpCommand(clientOpts *argocdclient.ClientOptions) *c
}

func NewApplicationEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var appNamespace string
var (
appNamespace string
)
var command = &cobra.Command{
Use: "edit APPNAME",
Short: "Edit application",
Expand All @@ -2742,6 +2790,7 @@ func NewApplicationEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
c.HelpFunc()(c, args)
os.Exit(1)
}

appName, appNs := argo.ParseFromQualifiedName(args[0], appNamespace)
conn, appIf := headless.NewClientOrDie(clientOpts, c).NewApplicationClientOrDie()
defer argoio.Close(conn)
Expand All @@ -2768,7 +2817,11 @@ func NewApplicationEditCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
}

var appOpts cmdutil.AppOptions
cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts)

// do not allow overrides for applications with multiple sources
if !app.Spec.HasMultipleSources() {
cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts, 0)
}
_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
Name: &appName,
Spec: &updatedSpec,
Expand Down Expand Up @@ -2871,8 +2924,12 @@ func NewApplicationAddSourceCommand(clientOpts *argocdclient.ClientOptions) *cob
if len(app.Spec.Sources) > 0 {
appSource, _ := cmdutil.ConstructSource(&argoappv1.ApplicationSource{}, appOpts, c.Flags())

// sourceIndex is the index at which new source will be appended to spec.Sources
sourceIndex := len(app.Spec.GetSources())
app.Spec.Sources = append(app.Spec.Sources, *appSource)

setParameterOverrides(app, appOpts.Parameters, sourceIndex)

_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
Name: &app.Name,
Spec: &app.Spec,
Expand All @@ -2895,13 +2952,13 @@ func NewApplicationAddSourceCommand(clientOpts *argocdclient.ClientOptions) *cob
// NewApplicationRemoveSourceCommand returns a new instance of an `argocd app remove-source` command
func NewApplicationRemoveSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
source_index int
sourceIndex int
appNamespace string
)
command := &cobra.Command{
Use: "remove-source APPNAME",
Short: "Remove a source from multiple sources application. Index starts with 0.",
Example: ` # Remove the source at index 1 from application's sources
Short: "Remove a source from multiple sources application. Index starts with 1. Default value is -1.",
Example: ` # Remove the source at index 1 from application's sources. Index starts at 1.
argocd app remove-source myapplication --source-index 1`,
Run: func(c *cobra.Command, args []string) {
ctx := c.Context()
Expand All @@ -2911,8 +2968,8 @@ func NewApplicationRemoveSourceCommand(clientOpts *argocdclient.ClientOptions) *
os.Exit(1)
}

if source_index < 0 {
errors.CheckError(fmt.Errorf("Index value of source cannot be less than 0"))
if sourceIndex <= 0 {
errors.CheckError(fmt.Errorf("Index value of source must be greater than 0"))
}

argocdClient := headless.NewClientOrDie(clientOpts, c)
Expand All @@ -2936,11 +2993,11 @@ func NewApplicationRemoveSourceCommand(clientOpts *argocdclient.ClientOptions) *
errors.CheckError(fmt.Errorf("Cannot remove the only source remaining in the app"))
}

if len(app.Spec.GetSources()) <= source_index {
errors.CheckError(fmt.Errorf("Application does not have source at %d\n", source_index))
if len(app.Spec.GetSources()) < sourceIndex {
errors.CheckError(fmt.Errorf("Application does not have source at %d\n", sourceIndex))
}

app.Spec.Sources = append(app.Spec.Sources[:source_index], app.Spec.Sources[source_index+1:]...)
app.Spec.Sources = append(app.Spec.Sources[:sourceIndex-1], app.Spec.Sources[sourceIndex:]...)

_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
Name: &app.Name,
Expand All @@ -2953,6 +3010,6 @@ func NewApplicationRemoveSourceCommand(clientOpts *argocdclient.ClientOptions) *
},
}
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Namespace of the target application where the source will be appended")
command.Flags().IntVar(&source_index, "source-index", -1, "Index of the source from the list of sources of the app. Index starts from 0.")
command.Flags().IntVar(&sourceIndex, "source-index", -1, "Index of the source from the list of sources of the app. Index starts from 1.")
return command
}
32 changes: 23 additions & 9 deletions cmd/util/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,16 +139,27 @@ func AddAppFlags(command *cobra.Command, opts *AppOptions) {
command.Flags().StringVar(&opts.ref, "ref", "", "Ref is reference to another source within sources field")
}

func SetAppSpecOptions(flags *pflag.FlagSet, spec *argoappv1.ApplicationSpec, appOpts *AppOptions) int {
func SetAppSpecOptions(flags *pflag.FlagSet, spec *argoappv1.ApplicationSpec, appOpts *AppOptions, index int) int {
visited := 0
if flags == nil {
return visited
}
source := spec.GetSourcePtr()
source := spec.GetSourcePtr(index)
if source == nil {
source = &argoappv1.ApplicationSource{}
}
source, visited = ConstructSource(source, *appOpts, flags)
if spec.HasMultipleSources() {
if index == 0 {
spec.Sources[index] = *source
} else if index > 0 {
spec.Sources[index-1] = *source
} else {
spec.Sources = append(spec.Sources, *source)
}
} else {
spec.Source = source
}
flags.Visit(func(f *pflag.Flag) {
visited++

Expand Down Expand Up @@ -220,7 +231,6 @@ func SetAppSpecOptions(flags *pflag.FlagSet, spec *argoappv1.ApplicationSpec, ap
log.Fatalf("Invalid sync-retry-limit [%d]", appOpts.retryLimit)
}
}
spec.Source = source
})
if flags.Changed("auto-prune") {
if spec.SyncPolicy == nil || spec.SyncPolicy.Automated == nil {
Expand Down Expand Up @@ -414,11 +424,11 @@ func setJsonnetOptLibs(src *argoappv1.ApplicationSource, libs []string) {
// SetParameterOverrides updates an existing or appends a new parameter override in the application
// The app is assumed to be a helm app and is expected to be in the form:
// param=value
func SetParameterOverrides(app *argoappv1.Application, parameters []string) {
func SetParameterOverrides(app *argoappv1.Application, parameters []string, index int) {
if len(parameters) == 0 {
return
}
source := app.Spec.GetSource()
source := app.Spec.GetSourcePtr(index)
var sourceType argoappv1.ApplicationSourceType
if st, _ := source.ExplicitType(); st != nil {
sourceType = *st
Expand Down Expand Up @@ -530,8 +540,8 @@ func constructAppsBaseOnName(appName string, labels, annotations, args []string,
Source: &argoappv1.ApplicationSource{},
},
}
SetAppSpecOptions(flags, &app.Spec, &appOpts)
SetParameterOverrides(app, appOpts.Parameters)
SetAppSpecOptions(flags, &app.Spec, &appOpts, 0)
SetParameterOverrides(app, appOpts.Parameters, 0)
mergeLabels(app, labels)
setAnnotations(app, annotations)
return []*argoappv1.Application{
Expand All @@ -557,10 +567,14 @@ func constructAppsFromFileUrl(fileURL, appName string, labels, annotations, args
return nil, fmt.Errorf("app.Name is empty. --name argument can be used to provide app.Name")
}

SetAppSpecOptions(flags, &app.Spec, &appOpts)
SetParameterOverrides(app, appOpts.Parameters)
mergeLabels(app, labels)
setAnnotations(app, annotations)

// do not allow overrides for applications with multiple sources
if !app.Spec.HasMultipleSources() {
SetAppSpecOptions(flags, &app.Spec, &appOpts, 0)
SetParameterOverrides(app, appOpts.Parameters, 0)
}
}
return apps, nil
}
Expand Down
Loading
Loading