Skip to content

Commit

Permalink
feat: add cli commands to add/remove sources for multi-source applica…
Browse files Browse the repository at this point in the history
…tions (argoproj#17310)

* Initial commit

Signed-off-by: ishitasequeira <ishiseq29@gmail.com>

* add cli commands to add/remove sources for multi-source app

Signed-off-by: ishitasequeira <ishiseq29@gmail.com>

* add checks

Signed-off-by: ishitasequeira <ishiseq29@gmail.com>

* add docs

Signed-off-by: ishitasequeira <ishiseq29@gmail.com>

* refactor code and update tests

Signed-off-by: ishitasequeira <ishiseq29@gmail.com>

* add removed additional switch case

Signed-off-by: ishitasequeira <ishiseq29@gmail.com>

* fix suggested nits

Signed-off-by: ishitasequeira <ishiseq29@gmail.com>

---------

Signed-off-by: ishitasequeira <ishiseq29@gmail.com>
  • Loading branch information
ishitasequeira authored and Hariharasuthan99 committed Jun 16, 2024
1 parent bd313cb commit 01b4b88
Show file tree
Hide file tree
Showing 11 changed files with 548 additions and 124 deletions.
156 changes: 147 additions & 9 deletions cmd/argocd/commands/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ func NewApplicationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
command.AddCommand(NewApplicationResourceActionsCommand(clientOpts))
command.AddCommand(NewApplicationListResourcesCommand(clientOpts))
command.AddCommand(NewApplicationLogsCommand(clientOpts))
command.AddCommand(NewApplicationAddSourceCommand(clientOpts))
command.AddCommand(NewApplicationRemoveSourceCommand(clientOpts))
return command
}

Expand Down Expand Up @@ -303,7 +305,7 @@ func printHeader(acdClient argocdclient.Client, app *argoappv1.Application, ctx
fmt.Println()
printOperationResult(app.Status.OperationState)
}
if showParams {
if !app.Spec.HasMultipleSources() && showParams {
printParams(app)
}
}
Expand Down Expand Up @@ -547,16 +549,19 @@ func NewApplicationLogsCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
}

func printAppSummaryTable(app *argoappv1.Application, appURL string, windows *argoappv1.SyncWindows) {
source := app.Spec.GetSource()
fmt.Printf(printOpFmtStr, "Name:", app.QualifiedName())
fmt.Printf(printOpFmtStr, "Project:", app.Spec.GetProject())
fmt.Printf(printOpFmtStr, "Server:", getServer(app))
fmt.Printf(printOpFmtStr, "Namespace:", app.Spec.Destination.Namespace)
fmt.Printf(printOpFmtStr, "URL:", appURL)
fmt.Printf(printOpFmtStr, "Repo:", source.RepoURL)
fmt.Printf(printOpFmtStr, "Target:", source.TargetRevision)
fmt.Printf(printOpFmtStr, "Path:", source.Path)
printAppSourceDetails(&source)
if !app.Spec.HasMultipleSources() {
fmt.Println("Source:")
} else {
fmt.Println("Sources:")
}
for _, source := range app.Spec.GetSources() {
printAppSourceDetails(&source)
}
var wds []string
var status string
var allow, deny, inactiveAllows bool
Expand Down Expand Up @@ -626,11 +631,19 @@ func printAppSummaryTable(app *argoappv1.Application, appURL string, windows *ar
}

func printAppSourceDetails(appSrc *argoappv1.ApplicationSource) {
fmt.Printf(printOpFmtStr, "- Repo:", appSrc.RepoURL)
fmt.Printf(printOpFmtStr, " Target:", appSrc.TargetRevision)
if appSrc.Path != "" {
fmt.Printf(printOpFmtStr, " Path:", appSrc.Path)
}
if appSrc.Ref != "" {
fmt.Printf(printOpFmtStr, " Ref:", appSrc.Ref)
}
if appSrc.Helm != nil && len(appSrc.Helm.ValueFiles) > 0 {
fmt.Printf(printOpFmtStr, "Helm Values:", strings.Join(appSrc.Helm.ValueFiles, ","))
fmt.Printf(printOpFmtStr, " Helm Values:", strings.Join(appSrc.Helm.ValueFiles, ","))
}
if appSrc.Kustomize != nil && appSrc.Kustomize.NamePrefix != "" {
fmt.Printf(printOpFmtStr, "Name Prefix:", appSrc.Kustomize.NamePrefix)
fmt.Printf(printOpFmtStr, " Name Prefix:", appSrc.Kustomize.NamePrefix)
}
}

Expand Down Expand Up @@ -2552,7 +2565,11 @@ func printOperationResult(opState *argoappv1.OperationState) {
}
if opState.SyncResult != nil {
fmt.Printf(printOpFmtStr, "Operation:", "Sync")
fmt.Printf(printOpFmtStr, "Sync Revision:", opState.SyncResult.Revision)
if opState.SyncResult.Sources != nil && opState.SyncResult.Revisions != nil {
fmt.Printf(printOpFmtStr, "Sync Revision:", strings.Join(opState.SyncResult.Revisions, ", "))
} else {
fmt.Printf(printOpFmtStr, "Sync Revision:", opState.SyncResult.Revision)
}
}
fmt.Printf(printOpFmtStr, "Phase:", opState.Phase)
fmt.Printf(printOpFmtStr, "Start:", opState.StartedAt)
Expand Down Expand Up @@ -2780,3 +2797,124 @@ func NewApplicationPatchCommand(clientOpts *argocdclient.ClientOptions) *cobra.C
command.Flags().StringVar(&patchType, "type", "json", "The type of patch being provided; one of [json merge]")
return &command
}

// NewApplicationAddSourceCommand returns a new instance of an `argocd app add-source` command
func NewApplicationAddSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
appOpts cmdutil.AppOptions
)
var command = &cobra.Command{
Use: "add-source APPNAME",
Short: "Adds a source to the list of sources in the application",
Example: ` # Append a source to the list of sources in the application
argocd app add-source guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook`,
Run: func(c *cobra.Command, args []string) {
ctx := c.Context()
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}

argocdClient := headless.NewClientOrDie(clientOpts, c)
conn, appIf := argocdClient.NewApplicationClientOrDie()
defer argoio.Close(conn)

appName, appNs := argo.ParseFromQualifiedName(args[0], "")

app, err := appIf.Get(ctx, &application.ApplicationQuery{
Name: &appName,
Refresh: getRefreshType(false, false),
AppNamespace: &appNs,
})

errors.CheckError(err)

if c.Flags() == nil {
errors.CheckError(fmt.Errorf("ApplicationSource needs atleast repoUrl, path or chart or ref field. No source to add."))
}

if len(app.Spec.Sources) > 0 {
appSource, _ := cmdutil.ConstructSource(&argoappv1.ApplicationSource{}, appOpts, c.Flags())

app.Spec.Sources = append(app.Spec.Sources, *appSource)

_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
Name: &app.Name,
Spec: &app.Spec,
Validate: &appOpts.Validate,
AppNamespace: &appNs,
})
errors.CheckError(err)

fmt.Printf("Application '%s' updated successfully\n", app.ObjectMeta.Name)
} else {
errors.CheckError(fmt.Errorf("Cannot add source: application %s does not have spec.sources defined", appName))
}
},
}
cmdutil.AddAppFlags(command, &appOpts)
return command
}

// NewApplicationRemoveSourceCommand returns a new instance of an `argocd app remove-source` command
func NewApplicationRemoveSourceCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
source_index int
)
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
argocd app remove-source myapplication --source-index 1`,
Run: func(c *cobra.Command, args []string) {
ctx := c.Context()

if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}

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

argocdClient := headless.NewClientOrDie(clientOpts, c)
conn, appIf := argocdClient.NewApplicationClientOrDie()
defer argoio.Close(conn)

appName, appNs := argo.ParseFromQualifiedName(args[0], "")

app, err := appIf.Get(ctx, &application.ApplicationQuery{
Name: &appName,
Refresh: getRefreshType(false, false),
AppNamespace: &appNs,
})
errors.CheckError(err)

if !app.Spec.HasMultipleSources() {
errors.CheckError(fmt.Errorf("Application does not have multiple sources configured"))
}

if len(app.Spec.GetSources()) == 1 {
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))
}

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

_, err = appIf.UpdateSpec(ctx, &application.ApplicationUpdateSpecRequest{
Name: &app.Name,
Spec: &app.Spec,
AppNamespace: &appNs,
})
errors.CheckError(err)

fmt.Printf("Application '%s' updated successfully\n", app.ObjectMeta.Name)
},
}
command.Flags().IntVar(&source_index, "source-index", -1, "Index of the source from the list of sources of the app. Index starts from 0.")
return command
}
109 changes: 104 additions & 5 deletions cmd/argocd/commands/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -659,11 +659,110 @@ Project: default
Server: local
Namespace: argocd
URL: url
Repo: test
Target: master
Path: /test
Helm Values: path1,path2
Name Prefix: prefix
Source:
- Repo: test
Target: master
Path: /test
Helm Values: path1,path2
Name Prefix: prefix
SyncWindow: Sync Denied
Assigned Windows: allow:0 0 * * *:24h,deny:0 0 * * *:24h,allow:0 0 * * *:24h
Sync Policy: Automated (Prune)
Sync Status: OutOfSync from master
Health Status: Progressing (health-message)
`
assert.Equalf(t, expectation, output, "Incorrect print app summary output %q, should be %q", output, expectation)
}

func TestPrintAppSummaryTable_MultipleSources(t *testing.T) {
output, _ := captureOutput(func() error {
app := &v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSpec{
SyncPolicy: &v1alpha1.SyncPolicy{
Automated: &v1alpha1.SyncPolicyAutomated{
Prune: true,
},
},
Project: "default",
Destination: v1alpha1.ApplicationDestination{Server: "local", Namespace: "argocd"},
Sources: v1alpha1.ApplicationSources{
{
RepoURL: "test",
TargetRevision: "master",
Path: "/test",
Helm: &v1alpha1.ApplicationSourceHelm{
ValueFiles: []string{"path1", "path2"},
},
Kustomize: &v1alpha1.ApplicationSourceKustomize{NamePrefix: "prefix"},
}, {
RepoURL: "test2",
TargetRevision: "master2",
Path: "/test2",
},
},
},
Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{
Status: v1alpha1.SyncStatusCodeOutOfSync,
},
Health: v1alpha1.HealthStatus{
Status: health.HealthStatusProgressing,
Message: "health-message",
},
},
}

windows := &v1alpha1.SyncWindows{
{
Kind: "allow",
Schedule: "0 0 * * *",
Duration: "24h",
Applications: []string{
"*-prod",
},
ManualSync: true,
},
{
Kind: "deny",
Schedule: "0 0 * * *",
Duration: "24h",
Namespaces: []string{
"default",
},
},
{
Kind: "allow",
Schedule: "0 0 * * *",
Duration: "24h",
Clusters: []string{
"in-cluster",
"cluster1",
},
},
}

printAppSummaryTable(app, "url", windows)
return nil
})

expectation := `Name: argocd/test
Project: default
Server: local
Namespace: argocd
URL: url
Sources:
- Repo: test
Target: master
Path: /test
Helm Values: path1,path2
Name Prefix: prefix
- Repo: test2
Target: master2
Path: /test2
SyncWindow: Sync Denied
Assigned Windows: allow:0 0 * * *:24h,deny:0 0 * * *:24h,allow:0 0 * * *:24h
Sync Policy: Automated (Prune)
Expand Down
8 changes: 5 additions & 3 deletions cmd/argocd/commands/applicationset.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,9 +350,11 @@ func printAppSetSummaryTable(appSet *arogappsetv1.ApplicationSet) {
fmt.Printf(printOpFmtStr, "Project:", appSet.Spec.Template.Spec.GetProject())
fmt.Printf(printOpFmtStr, "Server:", getServerForAppSet(appSet))
fmt.Printf(printOpFmtStr, "Namespace:", appSet.Spec.Template.Spec.Destination.Namespace)
fmt.Printf(printOpFmtStr, "Repo:", source.RepoURL)
fmt.Printf(printOpFmtStr, "Target:", source.TargetRevision)
fmt.Printf(printOpFmtStr, "Path:", source.Path)
if !appSet.Spec.Template.Spec.HasMultipleSources() {
fmt.Println("Source:")
} else {
fmt.Println("Sources:")
}
printAppSourceDetails(&source)

var (
Expand Down
18 changes: 9 additions & 9 deletions cmd/argocd/commands/applicationset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,9 @@ func TestPrintAppSetSummaryTable(t *testing.T) {
Project: default
Server:
Namespace:
Repo:
Target:
Path:
Source:
- Repo:
Target:
SyncPolicy: <none>
`,
},
Expand All @@ -193,9 +193,9 @@ SyncPolicy: <none>
Project: default
Server:
Namespace:
Repo:
Target:
Path:
Source:
- Repo:
Target:
SyncPolicy: Automated
`,
},
Expand All @@ -206,9 +206,9 @@ SyncPolicy: Automated
Project: default
Server:
Namespace:
Repo:
Target:
Path:
Source:
- Repo:
Target:
SyncPolicy: Automated
`,
},
Expand Down
Loading

0 comments on commit 01b4b88

Please sign in to comment.