From edb664f4cea301f554a5b8060d2e86b3dd904a05 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Sun, 27 Oct 2019 20:05:26 +0000 Subject: [PATCH 01/25] Add wait and timeout flags to app delete cmd --- cmd/argocd/commands/app.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/cmd/argocd/commands/app.go b/cmd/argocd/commands/app.go index e84becd895dfb..848e78a31c8d7 100644 --- a/cmd/argocd/commands/app.go +++ b/cmd/argocd/commands/app.go @@ -1040,6 +1040,8 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { var ( cascade bool + wait bool + timeout int ) var command = &cobra.Command{ Use: "delete APPNAME", @@ -1049,8 +1051,10 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra. c.HelpFunc()(c, args) os.Exit(1) } + conn, appIf := argocdclient.NewClientOrDie(clientOpts).NewApplicationClientOrDie() defer util.Close(conn) + for _, appName := range args { appDeleteReq := applicationpkg.ApplicationDeleteRequest{ Name: &appName, @@ -1058,15 +1062,44 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra. if c.Flag("cascade").Changed { appDeleteReq.Cascade = &cascade } + _, err := appIf.Delete(context.Background(), &appDeleteReq) errors.CheckError(err) + + if c.Flag("wait").Changed { + err = waitForAppDeletion(appIf, appName, timeout) + errors.CheckError(err) + } } }, } command.Flags().BoolVar(&cascade, "cascade", true, "Perform a cascaded deletion of all application resources") + command.Flags().BoolVar(&wait, "wait", false, "Will cause the command to only return once the application has been deleted") + command.Flags().IntVar(&timeout, "timeout", 60, "Time to spend waiting for application to delete, default 60s") + return command } +func waitForAppDeletion(c applicationpkg.ApplicationServiceClient, appName string, timeout int) error { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + for { + _, err := c.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &appName}) + if err != nil { + if strings.HasSuffix(err.Error(), "not found") { + return nil + } + } + select { + case <-ctx.Done(): + return fmt.Errorf("wait timed out after %ds waiting for app %s to be deleted", timeout, appName) + default: + time.Sleep(1 * time.Second) + } + } +} + // Print simple list of application names func printApplicationNames(apps []argoappv1.Application) { for _, app := range apps { From 5070a20d54cc8b2a475c3fc3d6db9044535e69fa Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Tue, 29 Oct 2019 17:57:58 +0000 Subject: [PATCH 02/25] switch to using waitOnApplicationStatus --- cmd/argocd/commands/app.go | 201 +++++++++++++++++++++++-------------- 1 file changed, 127 insertions(+), 74 deletions(-) diff --git a/cmd/argocd/commands/app.go b/cmd/argocd/commands/app.go index 848e78a31c8d7..68b873c3f5ee7 100644 --- a/cmd/argocd/commands/app.go +++ b/cmd/argocd/commands/app.go @@ -1041,7 +1041,7 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra. var ( cascade bool wait bool - timeout int + timeout uint ) var command = &cobra.Command{ Use: "delete APPNAME", @@ -1059,15 +1059,21 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra. appDeleteReq := applicationpkg.ApplicationDeleteRequest{ Name: &appName, } - if c.Flag("cascade").Changed { + if cascade { appDeleteReq.Cascade = &cascade } _, err := appIf.Delete(context.Background(), &appDeleteReq) errors.CheckError(err) - if c.Flag("wait").Changed { - err = waitForAppDeletion(appIf, appName, timeout) + if wait { + appStatusOpts := applicationStatusOpts{ + client: argocdclient.NewClientOrDie(clientOpts), + appName: appName, + timeout: timeout, + watch: watchOpts{delete: true}, + } + _, err := waitOnApplicationStatus(appStatusOpts) errors.CheckError(err) } } @@ -1075,31 +1081,11 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra. } command.Flags().BoolVar(&cascade, "cascade", true, "Perform a cascaded deletion of all application resources") command.Flags().BoolVar(&wait, "wait", false, "Will cause the command to only return once the application has been deleted") - command.Flags().IntVar(&timeout, "timeout", 60, "Time to spend waiting for application to delete, default 60s") + command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time to spend waiting for application to delete, default is no timeout") return command } -func waitForAppDeletion(c applicationpkg.ApplicationServiceClient, appName string, timeout int) error { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) - defer cancel() - - for { - _, err := c.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &appName}) - if err != nil { - if strings.HasSuffix(err.Error(), "not found") { - return nil - } - } - select { - case <-ctx.Done(): - return fmt.Errorf("wait timed out after %ds waiting for app %s to be deleted", timeout, appName) - default: - time.Sleep(1 * time.Second) - } - } -} - // Print simple list of application names func printApplicationNames(apps []argoappv1.Application) { for _, app := range apps { @@ -1248,13 +1234,10 @@ func parseSelectedResources(resources []string) []argoappv1.SyncOperationResourc // NewApplicationWaitCommand returns a new instance of an `argocd app wait` command func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command { var ( - watchSync bool - watchHealth bool - watchSuspended bool - watchOperations bool - timeout uint - selector string - resources []string + watch watchOpts + timeout uint + selector string + resources []string ) var command = &cobra.Command{ Use: "wait [APPNAME.. | -l selector]", @@ -1272,16 +1255,22 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co c.HelpFunc()(c, args) os.Exit(1) } - if !watchSync && !watchHealth && !watchOperations && !watchSuspended { - watchSync = true - watchHealth = true - watchOperations = true - watchSuspended = false + appStatusOpts := applicationStatusOpts{ + client: argocdclient.NewClientOrDie(clientOpts), + timeout: timeout, + selectedResources: parseSelectedResources(resources), + } + if !watch.sync && !watch.health && !watch.operation && !watch.suspended { + appStatusOpts.watch = watchOpts{ + sync: true, + health: true, + operation: true, + } + } else { + appStatusOpts.watch = watch } - selectedResources := parseSelectedResources(resources) appNames := args - acdClient := argocdclient.NewClientOrDie(clientOpts) - closer, appIf := acdClient.NewApplicationClientOrDie() + closer, appIf := appStatusOpts.client.NewApplicationClientOrDie() defer util.Close(closer) if selector != "" { list, err := appIf.List(context.Background(), &applicationpkg.ApplicationQuery{Selector: selector}) @@ -1291,17 +1280,18 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co } } for _, appName := range appNames { - _, err := waitOnApplicationStatus(acdClient, appName, timeout, watchSync, watchHealth, watchOperations, watchSuspended, selectedResources) + appStatusOpts.appName = appName + _, err := waitOnApplicationStatus(appStatusOpts) errors.CheckError(err) } }, } - command.Flags().BoolVar(&watchSync, "sync", false, "Wait for sync") - command.Flags().BoolVar(&watchHealth, "health", false, "Wait for health") - command.Flags().BoolVar(&watchSuspended, "suspended", false, "Wait for suspended") + command.Flags().BoolVar(&watch.sync, "sync", false, "Wait for sync") + command.Flags().BoolVar(&watch.health, "health", false, "Wait for health") + command.Flags().BoolVar(&watch.suspended, "suspended", false, "Wait for suspended") command.Flags().StringVarP(&selector, "selector", "l", "", "Wait for apps by label") command.Flags().StringArrayVar(&resources, "resource", []string{}, fmt.Sprintf("Sync only specific resources as GROUP%sKIND%sNAME. Fields may be blank. This option may be specified repeatedly", resourceFieldDelimiter, resourceFieldDelimiter)) - command.Flags().BoolVar(&watchOperations, "operation", false, "Wait for pending operations") + command.Flags().BoolVar(&watch.operation, "operation", false, "Wait for pending operations") command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds") return command } @@ -1451,7 +1441,14 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co errors.CheckError(err) if !async { - app, err := waitOnApplicationStatus(acdClient, appName, timeout, false, false, true, false, selectedResources) + appStatusOpts := applicationStatusOpts{ + client: acdClient, + appName: appName, + timeout: timeout, + watch: watchOpts{operation: true}, + selectedResources: selectedResources, + } + app, err := waitOnApplicationStatus(appStatusOpts) errors.CheckError(err) // Only get resources to be pruned if sync was application-wide @@ -1591,25 +1588,42 @@ func groupResourceStates(app *argoappv1.Application, selectedResources []argoapp return resStates } -func checkResourceStatus(watchSync bool, watchHealth bool, watchOperation bool, watchSuspended bool, healthStatus string, syncStatus string, operationStatus *argoappv1.Operation) bool { +func checkResourceStatus(watch watchOpts, healthStatus string, syncStatus string, operationStatus *argoappv1.Operation) bool { healthCheckPassed := true - if watchSuspended && watchHealth { + if watch.suspended && watch.health { healthCheckPassed = healthStatus == argoappv1.HealthStatusHealthy || healthStatus == argoappv1.HealthStatusSuspended - } else if watchSuspended { + } else if watch.suspended { healthCheckPassed = healthStatus == argoappv1.HealthStatusSuspended - } else if watchHealth { + } else if watch.health { healthCheckPassed = healthStatus == argoappv1.HealthStatusHealthy } - synced := !watchSync || syncStatus == string(argoappv1.SyncStatusCodeSynced) - operational := !watchOperation || operationStatus == nil + synced := !watch.sync || syncStatus == string(argoappv1.SyncStatusCodeSynced) + operational := !watch.operation || operationStatus == nil return synced && healthCheckPassed && operational } const waitFormatString = "%s\t%5s\t%10s\t%10s\t%20s\t%8s\t%7s\t%10s\t%s\n" -func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout uint, watchSync bool, watchHealth bool, watchOperation bool, watchSuspended bool, selectedResources []argoappv1.SyncOperationResource) (*argoappv1.Application, error) { +type applicationStatusOpts struct { + client apiclient.Client + appName string + timeout uint + watch watchOpts + selectedResources []argoappv1.SyncOperationResource +} + +type watchOpts struct { + sync bool + health bool + operation bool + suspended bool + delete bool +} + +func waitOnApplicationStatus(opts applicationStatusOpts) (*argoappv1.Application, error) { + ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -1621,17 +1635,17 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout printFinalStatus := func(app *argoappv1.Application) { var err error if refresh { - conn, appClient := acdClient.NewApplicationClientOrDie() + conn, appClient := opts.client.NewApplicationClientOrDie() refreshType := string(argoappv1.RefreshTypeNormal) - app, err = appClient.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &appName, Refresh: &refreshType}) + app, err = appClient.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &opts.appName, Refresh: &refreshType}) errors.CheckError(err) _ = conn.Close() } fmt.Println() - printAppSummaryTable(app, appURL(acdClient, appName), nil) + printAppSummaryTable(app, appURL(opts.client, opts.appName), nil) fmt.Println() - if watchOperation { + if opts.watch.operation { printOperationResult(app.Status.OperationState) } @@ -1643,8 +1657,10 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout } } - if timeout != 0 { - time.AfterFunc(time.Duration(timeout)*time.Second, func() { + delete := make(chan bool, 1) + if opts.timeout != 0 { + time.AfterFunc(time.Duration(opts.timeout)*time.Second, func() { + delete <- false cancel() }) } @@ -1653,12 +1669,27 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout _, _ = fmt.Fprintf(w, waitFormatString, "TIMESTAMP", "GROUP", "KIND", "NAMESPACE", "NAME", "STATUS", "HEALTH", "HOOK", "MESSAGE") prevStates := make(map[string]*resourceState) - appEventCh := acdClient.WatchApplicationWithRetry(ctx, appName) - conn, appClient := acdClient.NewApplicationClientOrDie() + appEventCh := opts.client.WatchApplicationWithRetry(ctx, opts.appName) + conn, appClient := opts.client.NewApplicationClientOrDie() defer util.Close(conn) - app, err := appClient.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName}) + app, err := appClient.Get(ctx, &applicationpkg.ApplicationQuery{Name: &opts.appName}) errors.CheckError(err) + if opts.watch.delete { + go func() { + for { + time.Sleep(3 * time.Second) + _, err := appClient.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &opts.appName}) + if err != nil { + if strings.HasSuffix(err.Error(), "not found") { + delete <- true + cancel() + } + } + } + }() + } + for appEvent := range appEventCh { app = &appEvent.Application if app.Operation != nil { @@ -1668,10 +1699,10 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout var selectedResourcesAreReady bool // If selected resources are included, wait only on those resources, otherwise wait on the application as a whole. - if len(selectedResources) > 0 { + if len(opts.selectedResources) > 0 { selectedResourcesAreReady = true - for _, state := range getResourceStates(app, selectedResources) { - resourceIsReady := checkResourceStatus(watchSync, watchHealth, watchOperation, watchSuspended, state.Health, state.Status, appEvent.Application.Operation) + for _, state := range getResourceStates(app, opts.selectedResources) { + resourceIsReady := checkResourceStatus(opts.watch, state.Health, state.Status, appEvent.Application.Operation) if !resourceIsReady { selectedResourcesAreReady = false break @@ -1679,24 +1710,31 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout } } else { // Wait on the application as a whole - selectedResourcesAreReady = checkResourceStatus(watchSync, watchHealth, watchOperation, watchSuspended, app.Status.Health.Status, string(app.Status.Sync.Status), appEvent.Application.Operation) + selectedResourcesAreReady = checkResourceStatus(opts.watch, app.Status.Health.Status, string(app.Status.Sync.Status), appEvent.Application.Operation) } - if selectedResourcesAreReady { + if selectedResourcesAreReady && !opts.watch.delete { printFinalStatus(app) return app, nil } - newStates := groupResourceStates(app, selectedResources) + newStates := groupResourceStates(app, opts.selectedResources) for _, newState := range newStates { var doPrint bool stateKey := newState.Key() if prevState, found := prevStates[stateKey]; found { - if watchHealth && prevState.Health != argoappv1.HealthStatusUnknown && prevState.Health != argoappv1.HealthStatusDegraded && newState.Health == argoappv1.HealthStatusDegraded { + if opts.watch.health && prevState.Health != argoappv1.HealthStatusUnknown && prevState.Health != argoappv1.HealthStatusDegraded && newState.Health == argoappv1.HealthStatusDegraded { printFinalStatus(app) - return nil, fmt.Errorf("Application '%s' health state has transitioned from %s to %s", appName, prevState.Health, newState.Health) + return nil, fmt.Errorf("Application '%s' health state has transitioned from %s to %s", opts.appName, prevState.Health, newState.Health) + } + if opts.watch.delete { + if newState.Health == "Missing" { + newState.Message = strings.Replace(newState.Message, "created", "deleted", 1) + doPrint = prevState.Merge(newState) + } + } else { + doPrint = prevState.Merge(newState) } - doPrint = prevState.Merge(newState) } else { prevStates[stateKey] = newState doPrint = true @@ -1707,8 +1745,17 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout } _ = w.Flush() } - printFinalStatus(app) - return nil, fmt.Errorf("Timed out (%ds) waiting for app %q match desired state", timeout, appName) + select { + case complete := <-delete: + if complete { + return nil, nil + } else { + return nil, fmt.Errorf("Timed out (%ds) waiting for app %q to be deleted", opts.timeout, opts.appName) + } + default: + printFinalStatus(app) + return nil, fmt.Errorf("Timed out (%ds) waiting for app %q match desired state", opts.timeout, opts.appName) + } } // setParameterOverrides updates an existing or appends a new parameter override in the application @@ -1872,7 +1919,13 @@ func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobr }) errors.CheckError(err) - _, err = waitOnApplicationStatus(acdClient, appName, timeout, false, false, true, false, nil) + appStatusOpts := applicationStatusOpts{ + client: acdClient, + appName: appName, + timeout: timeout, + watch: watchOpts{operation: true}, + } + _, err = waitOnApplicationStatus(appStatusOpts) errors.CheckError(err) }, } From 1f053cbf3bfd6cf4dfcdc71891e8d013e80d4f06 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Tue, 29 Oct 2019 20:43:59 +0000 Subject: [PATCH 03/25] add examples to app delete --- cmd/argocd/commands/app.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/cmd/argocd/commands/app.go b/cmd/argocd/commands/app.go index 68b873c3f5ee7..2b00ab1dc683c 100644 --- a/cmd/argocd/commands/app.go +++ b/cmd/argocd/commands/app.go @@ -1046,6 +1046,18 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra. var command = &cobra.Command{ Use: "delete APPNAME", Short: "Delete an application", + Example: ` + # Delete an app and all resources created by the app + argocd app delete app1 + + # Delete only the app manifest and leave all resources untouched + argocd app delete app2 --cascade false + + # Wait for app to be deleted before returning + argocd app delete app3 --wait + + # Wait for 60s for app to be deleted before returning + argocd app delete app4 --wait --timeout 60`, Run: func(c *cobra.Command, args []string) { if len(args) == 0 { c.HelpFunc()(c, args) @@ -1660,7 +1672,9 @@ func waitOnApplicationStatus(opts applicationStatusOpts) (*argoappv1.Application delete := make(chan bool, 1) if opts.timeout != 0 { time.AfterFunc(time.Duration(opts.timeout)*time.Second, func() { - delete <- false + if opts.watch.delete { + delete <- false + } cancel() }) } From a3d3c3f074e32f2eebffc605f4640fbcb731bc3c Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Tue, 29 Oct 2019 23:05:12 +0000 Subject: [PATCH 04/25] remove goroutine --- cmd/argocd/commands/app.go | 44 +++++++++++--------------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/cmd/argocd/commands/app.go b/cmd/argocd/commands/app.go index 2b00ab1dc683c..bf7e2c8a3e68b 100644 --- a/cmd/argocd/commands/app.go +++ b/cmd/argocd/commands/app.go @@ -1272,7 +1272,7 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co timeout: timeout, selectedResources: parseSelectedResources(resources), } - if !watch.sync && !watch.health && !watch.operation && !watch.suspended { + if !watch.sync && !watch.health && !watch.operation && !watch.suspended && !watch.delete { appStatusOpts.watch = watchOpts{ sync: true, health: true, @@ -1304,6 +1304,7 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co command.Flags().StringVarP(&selector, "selector", "l", "", "Wait for apps by label") command.Flags().StringArrayVar(&resources, "resource", []string{}, fmt.Sprintf("Sync only specific resources as GROUP%sKIND%sNAME. Fields may be blank. This option may be specified repeatedly", resourceFieldDelimiter, resourceFieldDelimiter)) command.Flags().BoolVar(&watch.operation, "operation", false, "Wait for pending operations") + command.Flags().BoolVar(&watch.delete, "delete", false, "wait for application to be deleted") command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds") return command } @@ -1669,12 +1670,8 @@ func waitOnApplicationStatus(opts applicationStatusOpts) (*argoappv1.Application } } - delete := make(chan bool, 1) if opts.timeout != 0 { time.AfterFunc(time.Duration(opts.timeout)*time.Second, func() { - if opts.watch.delete { - delete <- false - } cancel() }) } @@ -1689,21 +1686,6 @@ func waitOnApplicationStatus(opts applicationStatusOpts) (*argoappv1.Application app, err := appClient.Get(ctx, &applicationpkg.ApplicationQuery{Name: &opts.appName}) errors.CheckError(err) - if opts.watch.delete { - go func() { - for { - time.Sleep(3 * time.Second) - _, err := appClient.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &opts.appName}) - if err != nil { - if strings.HasSuffix(err.Error(), "not found") { - delete <- true - cancel() - } - } - } - }() - } - for appEvent := range appEventCh { app = &appEvent.Application if app.Operation != nil { @@ -1742,7 +1724,7 @@ func waitOnApplicationStatus(opts applicationStatusOpts) (*argoappv1.Application return nil, fmt.Errorf("Application '%s' health state has transitioned from %s to %s", opts.appName, prevState.Health, newState.Health) } if opts.watch.delete { - if newState.Health == "Missing" { + if newState.Health == argoappv1.HealthStatusMissing { newState.Message = strings.Replace(newState.Message, "created", "deleted", 1) doPrint = prevState.Merge(newState) } @@ -1758,18 +1740,18 @@ func waitOnApplicationStatus(opts applicationStatusOpts) (*argoappv1.Application } } _ = w.Flush() - } - select { - case complete := <-delete: - if complete { - return nil, nil - } else { - return nil, fmt.Errorf("Timed out (%ds) waiting for app %q to be deleted", opts.timeout, opts.appName) + if opts.watch.delete { + fmt.Println("inside check") + _, err := appClient.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &opts.appName}) + if err != nil { + if strings.HasSuffix(err.Error(), "not found") { + return nil, nil + } + } } - default: - printFinalStatus(app) - return nil, fmt.Errorf("Timed out (%ds) waiting for app %q match desired state", opts.timeout, opts.appName) } + printFinalStatus(app) + return nil, fmt.Errorf("Timed out (%ds) waiting for app %q match desired state", opts.timeout, opts.appName) } // setParameterOverrides updates an existing or appends a new parameter override in the application From 680a1a69989a742b6e46280dc9e1fd9bfee2f8ad Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Tue, 29 Oct 2019 23:07:14 +0000 Subject: [PATCH 05/25] remove println --- cmd/argocd/commands/app.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/argocd/commands/app.go b/cmd/argocd/commands/app.go index bf7e2c8a3e68b..dc1ed0a40de03 100644 --- a/cmd/argocd/commands/app.go +++ b/cmd/argocd/commands/app.go @@ -1741,7 +1741,6 @@ func waitOnApplicationStatus(opts applicationStatusOpts) (*argoappv1.Application } _ = w.Flush() if opts.watch.delete { - fmt.Println("inside check") _, err := appClient.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &opts.appName}) if err != nil { if strings.HasSuffix(err.Error(), "not found") { From cd7c943b6bc0c692697181a1d62a9fd58b421f85 Mon Sep 17 00:00:00 2001 From: adamjohnson01 Date: Sat, 23 Nov 2019 22:16:34 +0000 Subject: [PATCH 06/25] add e2e test for app delete wait --- test/e2e/app_deletion_test.go | 15 +++++++++++++++ test/e2e/fixture/app/actions.go | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/test/e2e/app_deletion_test.go b/test/e2e/app_deletion_test.go index 3f20e31d0e05e..061c92434cd85 100644 --- a/test/e2e/app_deletion_test.go +++ b/test/e2e/app_deletion_test.go @@ -38,3 +38,18 @@ func TestDeletingAppStuckInSync(t *testing.T) { // delete is successful Expect(DoesNotExist()) } + +func TestApplicationDeleteWait(t *testing.T) { + Given(t). + Path(guestbookPath). + When(). + CreateFromFile(func(app *Application) { + app.Spec.SyncPolicy = &SyncPolicy{Automated: &SyncPolicyAutomated{}} + }). + Then(). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + When(). + DeleteWait(). + Then(). + Expect(DoesNotExist()) +} diff --git a/test/e2e/fixture/app/actions.go b/test/e2e/fixture/app/actions.go index e02e27e42f18e..bf268eb06ab44 100644 --- a/test/e2e/fixture/app/actions.go +++ b/test/e2e/fixture/app/actions.go @@ -249,6 +249,12 @@ func (a *Actions) Delete(cascade bool) *Actions { return a } +func (a *Actions) DeleteWait() *Actions { + a.context.t.Helper() + a.runCli("app", "delete", a.context.name, "--cascade=true","--wait") + return a +} + func (a *Actions) And(block func()) *Actions { a.context.t.Helper() block() From 9ccfa642aeccb001b190c2439c83fa8d230864c2 Mon Sep 17 00:00:00 2001 From: adamjohnson01 Date: Sun, 24 Nov 2019 12:39:08 +0000 Subject: [PATCH 07/25] fix linting issue --- test/e2e/fixture/app/actions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/fixture/app/actions.go b/test/e2e/fixture/app/actions.go index bf268eb06ab44..c843b02461523 100644 --- a/test/e2e/fixture/app/actions.go +++ b/test/e2e/fixture/app/actions.go @@ -251,7 +251,7 @@ func (a *Actions) Delete(cascade bool) *Actions { func (a *Actions) DeleteWait() *Actions { a.context.t.Helper() - a.runCli("app", "delete", a.context.name, "--cascade=true","--wait") + a.runCli("app", "delete", a.context.name, "--cascade=true", "--wait") return a } From 0038806c2658bcd7dd99aab09a53659029de549a Mon Sep 17 00:00:00 2001 From: Dong Wang Date: Thu, 27 Jan 2022 00:18:14 +0800 Subject: [PATCH 08/25] feat: Expose ARGOCD_APP_NAME to the `kustomize build` command (#8096) Signed-off-by: Dong Wang Signed-off-by: pashavictorovich --- docs/user-guide/build-environment.md | 4 +-- reposerver/repository/repository.go | 15 ++++++--- util/kustomize/kustomize.go | 10 ++++-- util/kustomize/kustomize_test.go | 33 +++++++++++++++++-- .../testdata/custom_version/kustomize.special | 8 +++++ 5 files changed, 57 insertions(+), 13 deletions(-) create mode 100755 util/kustomize/testdata/custom_version/kustomize.special diff --git a/docs/user-guide/build-environment.md b/docs/user-guide/build-environment.md index 6e99477e539df..c5219aa9fe0d1 100644 --- a/docs/user-guide/build-environment.md +++ b/docs/user-guide/build-environment.md @@ -1,8 +1,6 @@ # Build Environment -> v1.4 - -[Custom tools](config-management-plugins.md), [Helm](helm.md), and [Jsonnet](jsonnet.md) support the following build env vars: +[Custom tools](config-management-plugins.md), [Helm](helm.md), [Jsonnet](jsonnet.md), and [Kustomize](kustomize.md) support the following build env vars: * `ARGOCD_APP_NAME` - name of application * `ARGOCD_APP_NAMESPACE` - destination application namespace. diff --git a/reposerver/repository/repository.go b/reposerver/repository/repository.go index e880476420bad..7399a15475e41 100644 --- a/reposerver/repository/repository.go +++ b/reposerver/repository/repository.go @@ -757,7 +757,7 @@ func GenerateManifests(ctx context.Context, appPath, repoRoot, revision string, kustomizeBinary = q.KustomizeOptions.BinaryPath } k := kustomize.NewKustomizeApp(appPath, q.Repo.GetGitCreds(), repoURL, kustomizeBinary) - targetObjs, _, err = k.Build(q.ApplicationSource.Kustomize, q.KustomizeOptions) + targetObjs, _, err = k.Build(q.ApplicationSource.Kustomize, q.KustomizeOptions, env) case v1alpha1.ApplicationSourceTypePlugin: if q.ApplicationSource.Plugin != nil && q.ApplicationSource.Plugin.Name != "" { targetObjs, err = runConfigManagementPlugin(appPath, repoRoot, env, q, q.Repo.GetGitCreds()) @@ -1276,7 +1276,7 @@ func (s *Service) GetAppDetails(ctx context.Context, q *apiclient.RepoServerAppD return err } case v1alpha1.ApplicationSourceTypeKustomize: - if err := populateKustomizeAppDetails(res, q, opContext.appPath); err != nil { + if err := populateKustomizeAppDetails(res, q, opContext.appPath, commitSHA); err != nil { return err } } @@ -1423,14 +1423,21 @@ func findHelmValueFilesInPath(path string) ([]string, error) { return result, nil } -func populateKustomizeAppDetails(res *apiclient.RepoAppDetailsResponse, q *apiclient.RepoServerAppDetailsQuery, appPath string) error { +func populateKustomizeAppDetails(res *apiclient.RepoAppDetailsResponse, q *apiclient.RepoServerAppDetailsQuery, appPath string, reversion string) error { res.Kustomize = &apiclient.KustomizeAppSpec{} kustomizeBinary := "" if q.KustomizeOptions != nil { kustomizeBinary = q.KustomizeOptions.BinaryPath } k := kustomize.NewKustomizeApp(appPath, q.Repo.GetGitCreds(), q.Repo.Repo, kustomizeBinary) - _, images, err := k.Build(q.Source.Kustomize, q.KustomizeOptions) + fakeManifestRequest := apiclient.ManifestRequest{ + AppName: q.AppName, + Namespace: "", // FIXME: omit it for now + Repo: q.Repo, + ApplicationSource: q.Source, + } + env := newEnv(&fakeManifestRequest, reversion) + _, images, err := k.Build(q.Source.Kustomize, q.KustomizeOptions, env) if err != nil { return err } diff --git a/util/kustomize/kustomize.go b/util/kustomize/kustomize.go index 58b3fe95a8738..cff45b88f61a0 100644 --- a/util/kustomize/kustomize.go +++ b/util/kustomize/kustomize.go @@ -30,7 +30,7 @@ type Image = string // Kustomize provides wrapper functionality around the `kustomize` command. type Kustomize interface { // Build returns a list of unstructured objects from a `kustomize build` command and extract supported parameters - Build(opts *v1alpha1.ApplicationSourceKustomize, kustomizeOptions *v1alpha1.KustomizeOptions) ([]*unstructured.Unstructured, []Image, error) + Build(opts *v1alpha1.ApplicationSourceKustomize, kustomizeOptions *v1alpha1.KustomizeOptions, envVars *v1alpha1.Env) ([]*unstructured.Unstructured, []Image, error) } // NewKustomizeApp create a new wrapper to run commands on the `kustomize` command-line tool. @@ -84,7 +84,7 @@ func mapToEditAddArgs(val map[string]string) []string { return args } -func (k *kustomize) Build(opts *v1alpha1.ApplicationSourceKustomize, kustomizeOptions *v1alpha1.KustomizeOptions) ([]*unstructured.Unstructured, []Image, error) { +func (k *kustomize) Build(opts *v1alpha1.ApplicationSourceKustomize, kustomizeOptions *v1alpha1.KustomizeOptions, envVars *v1alpha1.Env) ([]*unstructured.Unstructured, []Image, error) { if opts != nil { if opts.NamePrefix != "" { @@ -155,7 +155,11 @@ func (k *kustomize) Build(opts *v1alpha1.ApplicationSourceKustomize, kustomizeOp cmd = exec.Command(k.getBinaryPath(), "build", k.path) } - cmd.Env = os.Environ() + env := os.Environ() + if envVars != nil { + env = append(env, envVars.Environ()...) + } + cmd.Env = env closer, environ, err := k.creds.Environ() if err != nil { return nil, nil, err diff --git a/util/kustomize/kustomize_test.go b/util/kustomize/kustomize_test.go index 56ad93dd19029..5ebaed83fda6e 100644 --- a/util/kustomize/kustomize_test.go +++ b/util/kustomize/kustomize_test.go @@ -3,6 +3,7 @@ package kustomize import ( "fmt" "io/ioutil" + "os" "path" "path/filepath" "testing" @@ -18,6 +19,7 @@ const kustomization1 = "kustomization_yaml" const kustomization2a = "kustomization_yml" const kustomization2b = "Kustomization" const kustomization3 = "force_common" +const kustomization4 = "custom_version" func testDataDir(testData string) (string, error) { res, err := ioutil.TempDir("", "kustomize-test") @@ -50,7 +52,7 @@ func TestKustomizeBuild(t *testing.T) { "app.kubernetes.io/part-of": "argo-cd-tests", }, } - objs, images, err := kustomize.Build(&kustomizeSource, nil) + objs, images, err := kustomize.Build(&kustomizeSource, nil, nil) assert.Nil(t, err) if err != nil { assert.Equal(t, len(objs), 2) @@ -163,7 +165,7 @@ func TestKustomizeBuildForceCommonLabels(t *testing.T) { appPath, err := testDataDir(tc.TestData) assert.Nil(t, err) kustomize := NewKustomizeApp(appPath, git.NopCreds{}, "", "") - objs, _, err := kustomize.Build(&tc.KustomizeSource, nil) + objs, _, err := kustomize.Build(&tc.KustomizeSource, nil, nil) switch tc.ExpectErr { case true: assert.Error(t, err) @@ -212,7 +214,7 @@ func TestKustomizeBuildForceCommonAnnotations(t *testing.T) { appPath, err := testDataDir(tc.TestData) assert.Nil(t, err) kustomize := NewKustomizeApp(appPath, git.NopCreds{}, "", "") - objs, _, err := kustomize.Build(&tc.KustomizeSource, nil) + objs, _, err := kustomize.Build(&tc.KustomizeSource, nil, nil) switch tc.ExpectErr { case true: assert.Error(t, err) @@ -224,3 +226,28 @@ func TestKustomizeBuildForceCommonAnnotations(t *testing.T) { } } } + +func TestKustomizeCustomVersion(t *testing.T) { + appPath, err := testDataDir(kustomization1) + assert.Nil(t, err) + kustomizePath, err := testDataDir(kustomization4) + assert.Nil(t, err) + envOutputFile := kustomizePath + "/env_output" + kustomize := NewKustomizeApp(appPath, git.NopCreds{}, "", kustomizePath+"/kustomize.special") + kustomizeSource := v1alpha1.ApplicationSourceKustomize{ + Version: "special", + } + env := &v1alpha1.Env{ + &v1alpha1.EnvEntry{Name: "ARGOCD_APP_NAME", Value: "argo-cd-tests"}, + } + objs, images, err := kustomize.Build(&kustomizeSource, nil, env) + assert.Nil(t, err) + if err != nil { + assert.Equal(t, len(objs), 2) + assert.Equal(t, len(images), 2) + } + + content, err := os.ReadFile(envOutputFile) + assert.Nil(t, err) + assert.Equal(t, "ARGOCD_APP_NAME=argo-cd-tests\n", string(content)) +} diff --git a/util/kustomize/testdata/custom_version/kustomize.special b/util/kustomize/testdata/custom_version/kustomize.special new file mode 100755 index 0000000000000..1a1c01b74fa4f --- /dev/null +++ b/util/kustomize/testdata/custom_version/kustomize.special @@ -0,0 +1,8 @@ +#!/bin/bash + +current_dir=$(dirname $0) +output="$current_dir/env_output" + +env | grep "ARGOCD_APP_NAME" > $output + +kustomize $@ From 69536bb1f32ef4886d71c8d590693268f12ad158 Mon Sep 17 00:00:00 2001 From: Alexander Matyushentsev Date: Wed, 26 Jan 2022 10:59:50 -0800 Subject: [PATCH 09/25] feat: Use encrypted cookie to store OAuth2 state nonce (instead of redis) (#8241) feat: Use encrypted cookie to store OAuth2 state nonce (instead of redis) (#8241) Signed-off-by: Alexander Matyushentsev Signed-off-by: pashavictorovich --- common/common.go | 4 ++ server/cache/cache.go | 17 ------ server/cache/cache_test.go | 18 ------ server/server.go | 2 +- util/crypto/crypto.go | 62 ++++++++++++++++++++ util/crypto/crypto_test.go | 43 ++++++++++++++ util/oidc/oidc.go | 116 +++++++++++++++++++++++-------------- util/oidc/oidc_test.go | 59 +++++++++++++++++++ util/settings/settings.go | 6 ++ 9 files changed, 247 insertions(+), 80 deletions(-) create mode 100644 util/crypto/crypto.go create mode 100644 util/crypto/crypto_test.go diff --git a/common/common.go b/common/common.go index 79bcceb1c1647..5df7091330d7c 100644 --- a/common/common.go +++ b/common/common.go @@ -76,6 +76,10 @@ const ( ArgoCDUserAgentName = "argocd-client" // AuthCookieName is the HTTP cookie name where we store our auth token AuthCookieName = "argocd.token" + // StateCookieName is the HTTP cookie name that holds temporary nonce tokens for CSRF protection + StateCookieName = "argocd.oauthstate" + // StateCookieMaxAge is the maximum age of the oauth state cookie + StateCookieMaxAge = time.Minute * 5 // ChangePasswordSSOTokenMaxAge is the max token age for password change operation ChangePasswordSSOTokenMaxAge = time.Minute * 5 diff --git a/server/cache/cache.go b/server/cache/cache.go index dbbeb37833d7d..9fe6d3f890620 100644 --- a/server/cache/cache.go +++ b/server/cache/cache.go @@ -13,7 +13,6 @@ import ( cacheutil "github.com/argoproj/argo-cd/v2/util/cache" appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate" "github.com/argoproj/argo-cd/v2/util/env" - "github.com/argoproj/argo-cd/v2/util/oidc" ) var ErrCacheMiss = appstatecache.ErrCacheMiss @@ -25,8 +24,6 @@ type Cache struct { loginAttemptsExpiration time.Duration } -var _ oidc.OIDCStateStorage = &Cache{} - func NewCache( cache *appstatecache.Cache, connectionStatusCacheExpiration time.Duration, @@ -91,20 +88,6 @@ func (c *Cache) SetClusterInfo(server string, res *appv1.ClusterInfo) error { return c.cache.SetClusterInfo(server, res) } -func oidcStateKey(key string) string { - return fmt.Sprintf("oidc|%s", key) -} - -func (c *Cache) GetOIDCState(key string) (*oidc.OIDCState, error) { - res := oidc.OIDCState{} - err := c.cache.GetItem(oidcStateKey(key), &res) - return &res, err -} - -func (c *Cache) SetOIDCState(key string, state *oidc.OIDCState) error { - return c.cache.SetItem(oidcStateKey(key), state, c.oidcCacheExpiration, state == nil) -} - func (c *Cache) GetCache() *cacheutil.Cache { return c.cache.Cache } diff --git a/server/cache/cache_test.go b/server/cache/cache_test.go index e92c966788fc8..6e173035aa33a 100644 --- a/server/cache/cache_test.go +++ b/server/cache/cache_test.go @@ -10,7 +10,6 @@ import ( . "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" cacheutil "github.com/argoproj/argo-cd/v2/util/cache" appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate" - "github.com/argoproj/argo-cd/v2/util/oidc" ) type fixtures struct { @@ -46,23 +45,6 @@ func TestCache_GetRepoConnectionState(t *testing.T) { assert.Equal(t, ConnectionState{Status: "my-state"}, value) } -func TestCache_GetOIDCState(t *testing.T) { - cache := newFixtures().Cache - // cache miss - _, err := cache.GetOIDCState("my-key") - assert.Equal(t, ErrCacheMiss, err) - // populate cache - err = cache.SetOIDCState("my-key", &oidc.OIDCState{ReturnURL: "my-return-url"}) - assert.NoError(t, err) - //cache miss - _, err = cache.GetOIDCState("other-key") - assert.Equal(t, ErrCacheMiss, err) - // cache hit - value, err := cache.GetOIDCState("my-key") - assert.NoError(t, err) - assert.Equal(t, &oidc.OIDCState{ReturnURL: "my-return-url"}, value) -} - func TestAddCacheFlagsToCmd(t *testing.T) { cache, err := AddCacheFlagsToCmd(&cobra.Command{})() assert.NoError(t, err) diff --git a/server/server.go b/server/server.go index 207d09c1b56f1..a468825613876 100644 --- a/server/server.go +++ b/server/server.go @@ -782,7 +782,7 @@ func (a *ArgoCDServer) registerDexHandlers(mux *http.ServeMux) { tlsConfig := a.settings.TLSConfig() tlsConfig.InsecureSkipVerify = true } - a.ssoClientApp, err = oidc.NewClientApp(a.settings, a.Cache, a.DexServerAddr, a.BaseHRef) + a.ssoClientApp, err = oidc.NewClientApp(a.settings, a.DexServerAddr, a.BaseHRef) errors.CheckError(err) mux.HandleFunc(common.LoginEndpoint, a.ssoClientApp.HandleLogin) mux.HandleFunc(common.CallbackEndpoint, a.ssoClientApp.HandleCallback) diff --git a/util/crypto/crypto.go b/util/crypto/crypto.go new file mode 100644 index 0000000000000..1b902951f22a3 --- /dev/null +++ b/util/crypto/crypto.go @@ -0,0 +1,62 @@ +package crypto + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "errors" + "io" + + "golang.org/x/crypto/scrypt" +) + +// KeyFromPassphrase generates 32 byte key from the passphrase +func KeyFromPassphrase(passphrase string) ([]byte, error) { + // salt is just a hash of a passphrase (effectively no salt) + salt := sha256.Sum256([]byte(passphrase)) + // These defaults will consume approximately 16MB of memory (128 * r * N) + const N = 16384 + const r = 8 + return scrypt.Key([]byte(passphrase), salt[:], N, r, 1, 32) +} + +// Encrypt encrypts the given data with the given passphrase. +func Encrypt(data []byte, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + nonce := make([]byte, gcm.NonceSize()) + if _, err = io.ReadFull(rand.Reader, nonce); err != nil { + return nil, err + } + ciphertext := gcm.Seal(nonce, nonce, data, nil) + return ciphertext, nil +} + +// Decrypt decrypts the given data using the given passphrase. +func Decrypt(data []byte, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + nonceSize := gcm.NonceSize() + if len(data) < nonceSize { + return nil, errors.New("data length is less than nonce size") + } + nonce, ciphertext := data[:nonceSize], data[nonceSize:] + plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, err + } + return plaintext, nil +} diff --git a/util/crypto/crypto_test.go b/util/crypto/crypto_test.go new file mode 100644 index 0000000000000..2d982ad718295 --- /dev/null +++ b/util/crypto/crypto_test.go @@ -0,0 +1,43 @@ +package crypto + +import ( + "crypto/rand" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func newKey() ([]byte, error) { + b := make([]byte, 32) + _, err := rand.Read(b) + if err != nil { + b = nil + } + return b, err +} + +func TestEncryptDecrypt_Successful(t *testing.T) { + key, err := newKey() + require.NoError(t, err) + encrypted, err := Encrypt([]byte("test"), key) + require.NoError(t, err) + + decrypted, err := Decrypt(encrypted, key) + require.NoError(t, err) + + assert.Equal(t, "test", string(decrypted)) +} + +func TestEncryptDecrypt_Failed(t *testing.T) { + key, err := newKey() + require.NoError(t, err) + encrypted, err := Encrypt([]byte("test"), key) + require.NoError(t, err) + + wrongKey, err := newKey() + require.NoError(t, err) + + _, err = Decrypt(encrypted, wrongKey) + assert.Error(t, err) +} diff --git a/util/oidc/oidc.go b/util/oidc/oidc.go index 7f7d9623601ca..1727331ec1189 100644 --- a/util/oidc/oidc.go +++ b/util/oidc/oidc.go @@ -1,6 +1,7 @@ package oidc import ( + "encoding/hex" "encoding/json" "fmt" "html" @@ -20,7 +21,7 @@ import ( "github.com/argoproj/argo-cd/v2/common" "github.com/argoproj/argo-cd/v2/server/settings/oidc" - appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate" + "github.com/argoproj/argo-cd/v2/util/crypto" "github.com/argoproj/argo-cd/v2/util/dex" httputil "github.com/argoproj/argo-cd/v2/util/http" "github.com/argoproj/argo-cd/v2/util/rand" @@ -45,16 +46,6 @@ type ClaimsRequest struct { IDToken map[string]*oidc.Claim `json:"id_token"` } -type OIDCState struct { - // ReturnURL is the URL in which to redirect a user back to after completing an OAuth2 login - ReturnURL string `json:"returnURL"` -} - -type OIDCStateStorage interface { - GetOIDCState(key string) (*OIDCState, error) - SetOIDCState(key string, state *OIDCState) error -} - type ClientApp struct { // OAuth2 client ID of this application (e.g. argo-cd) clientID string @@ -73,11 +64,10 @@ type ClientApp struct { secureCookie bool // settings holds Argo CD settings settings *settings.ArgoCDSettings + // encryptionKey holds server encryption key + encryptionKey []byte // provider is the OIDC provider provider Provider - // cache holds temporary nonce tokens to which hold application state values - // See http://tools.ietf.org/html/rfc6749#section-10.12 for more info. - cache OIDCStateStorage } func GetScopesOrDefault(scopes []string) []string { @@ -89,18 +79,22 @@ func GetScopesOrDefault(scopes []string) []string { // NewClientApp will register the Argo CD client app (either via Dex or external OIDC) and return an // object which has HTTP handlers for handling the HTTP responses for login and callback -func NewClientApp(settings *settings.ArgoCDSettings, cache OIDCStateStorage, dexServerAddr, baseHRef string) (*ClientApp, error) { +func NewClientApp(settings *settings.ArgoCDSettings, dexServerAddr, baseHRef string) (*ClientApp, error) { redirectURL, err := settings.RedirectURL() if err != nil { return nil, err } + encryptionKey, err := settings.GetServerEncryptionKey() + if err != nil { + return nil, err + } a := ClientApp{ - clientID: settings.OAuth2ClientID(), - clientSecret: settings.OAuth2ClientSecret(), - redirectURI: redirectURL, - issuerURL: settings.IssuerURL(), - baseHRef: baseHRef, - cache: cache, + clientID: settings.OAuth2ClientID(), + clientSecret: settings.OAuth2ClientSecret(), + redirectURI: redirectURL, + issuerURL: settings.IssuerURL(), + baseHRef: baseHRef, + encryptionKey: encryptionKey, } log.Infof("Creating client app (%s)", a.clientID) u, err := url.Parse(settings.URL) @@ -149,31 +143,60 @@ func (a *ClientApp) oauth2Config(scopes []string) (*oauth2.Config, error) { } // generateAppState creates an app state nonce -func (a *ClientApp) generateAppState(returnURL string) string { +func (a *ClientApp) generateAppState(returnURL string, w http.ResponseWriter) (string, error) { randStr := rand.RandString(10) if returnURL == "" { returnURL = a.baseHRef } - err := a.cache.SetOIDCState(randStr, &OIDCState{ReturnURL: returnURL}) - if err != nil { - // This should never happen with the in-memory cache - log.Errorf("Failed to set app state: %v", err) + cookieValue := fmt.Sprintf("%s:%s", randStr, returnURL) + if encrypted, err := crypto.Encrypt([]byte(cookieValue), a.encryptionKey); err != nil { + return "", err + } else { + cookieValue = hex.EncodeToString(encrypted) } - return randStr + + http.SetCookie(w, &http.Cookie{ + Name: common.StateCookieName, + Value: cookieValue, + Expires: time.Now().Add(common.StateCookieMaxAge), + HttpOnly: true, + SameSite: http.SameSiteLaxMode, + Secure: a.secureCookie, + }) + return randStr, nil } -func (a *ClientApp) verifyAppState(state string) (*OIDCState, error) { - res, err := a.cache.GetOIDCState(state) +func (a *ClientApp) verifyAppState(r *http.Request, w http.ResponseWriter, state string) (string, error) { + c, err := r.Cookie(common.StateCookieName) if err != nil { - if err == appstatecache.ErrCacheMiss { - return nil, fmt.Errorf("unknown app state %s", state) - } else { - return nil, fmt.Errorf("failed to verify app state %s: %v", state, err) - } + return "", err } - - _ = a.cache.SetOIDCState(state, nil) - return res, nil + val, err := hex.DecodeString(c.Value) + if err != nil { + return "", err + } + val, err = crypto.Decrypt(val, a.encryptionKey) + if err != nil { + return "", err + } + cookieVal := string(val) + returnURL := a.baseHRef + parts := strings.SplitN(cookieVal, ":", 2) + if len(parts) == 2 && parts[1] != "" { + returnURL = parts[1] + } + if parts[0] != state { + return "", fmt.Errorf("invalid state in '%s' cookie", common.AuthCookieName) + } + // set empty cookie to clear it + http.SetCookie(w, &http.Cookie{ + Name: common.StateCookieName, + Value: "", + HttpOnly: true, + SameSite: http.SameSiteLaxMode, + Secure: a.secureCookie, + }) + return returnURL, nil } // isValidRedirectURL checks whether the given redirectURL matches on of the @@ -248,7 +271,12 @@ func (a *ClientApp) HandleLogin(w http.ResponseWriter, r *http.Request) { http.Error(w, "Invalid redirect URL: the protocol and host (including port) must match and the path must be within allowed URLs if provided", http.StatusBadRequest) return } - stateNonce := a.generateAppState(returnURL) + stateNonce, err := a.generateAppState(returnURL, w) + if err != nil { + log.Errorf("Failed to initiate login flow: %v", err) + http.Error(w, "Failed to initiate login flow", http.StatusInternalServerError) + return + } grantType := InferGrantType(oidcConf) var url string switch grantType { @@ -281,10 +309,10 @@ func (a *ClientApp) HandleCallback(w http.ResponseWriter, r *http.Request) { state := r.FormValue("state") if code == "" { // If code was not given, it implies implicit flow - a.handleImplicitFlow(w, state) + a.handleImplicitFlow(r, w, state) return } - appState, err := a.verifyAppState(state) + returnURL, err := a.verifyAppState(r, w, state) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return @@ -339,7 +367,7 @@ func (a *ClientApp) HandleCallback(w http.ResponseWriter, r *http.Request) { claimsJSON, _ := json.MarshalIndent(claims, "", " ") renderToken(w, a.redirectURI, idTokenRAW, token.RefreshToken, claimsJSON) } else { - http.Redirect(w, r, appState.ReturnURL, http.StatusSeeOther) + http.Redirect(w, r, returnURL, http.StatusSeeOther) } } @@ -366,7 +394,7 @@ if (state != "" && returnURL == "") { // state nonce for verification, as well as looking up the return URL. Once verified, the client // stores the id_token from the fragment as a cookie. Finally it performs the final redirect back to // the return URL. -func (a *ClientApp) handleImplicitFlow(w http.ResponseWriter, state string) { +func (a *ClientApp) handleImplicitFlow(r *http.Request, w http.ResponseWriter, state string) { type implicitFlowValues struct { CookieName string ReturnURL string @@ -375,12 +403,12 @@ func (a *ClientApp) handleImplicitFlow(w http.ResponseWriter, state string) { CookieName: common.AuthCookieName, } if state != "" { - appState, err := a.verifyAppState(state) + returnURL, err := a.verifyAppState(r, w, state) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - vals.ReturnURL = appState.ReturnURL + vals.ReturnURL = returnURL } renderTemplate(w, implicitFlowTmpl, vals) } diff --git a/util/oidc/oidc_test.go b/util/oidc/oidc_test.go index 179a575f56be2..5c94a661c4161 100644 --- a/util/oidc/oidc_test.go +++ b/util/oidc/oidc_test.go @@ -1,17 +1,24 @@ package oidc import ( + "encoding/hex" "encoding/json" "io/ioutil" + "net/http" "net/http/httptest" "net/url" "testing" gooidc "github.com/coreos/go-oidc" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/oauth2" + "github.com/argoproj/argo-cd/v2/common" "github.com/argoproj/argo-cd/v2/server/settings/oidc" + "github.com/argoproj/argo-cd/v2/util" + "github.com/argoproj/argo-cd/v2/util/crypto" + "github.com/argoproj/argo-cd/v2/util/settings" ) func TestInferGrantType(t *testing.T) { @@ -179,3 +186,55 @@ func TestIsValidRedirect(t *testing.T) { }) } } + +func TestGenerateAppState(t *testing.T) { + signature, err := util.MakeSignature(32) + require.NoError(t, err) + expectedReturnURL := "http://argocd.example.com/" + app, err := NewClientApp(&settings.ArgoCDSettings{ServerSignature: signature}, "", "") + require.NoError(t, err) + generateResponse := httptest.NewRecorder() + state, err := app.generateAppState(expectedReturnURL, generateResponse) + require.NoError(t, err) + + t.Run("VerifyAppState_Successful", func(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + for _, cookie := range generateResponse.Result().Cookies() { + req.AddCookie(cookie) + } + + returnURL, err := app.verifyAppState(req, httptest.NewRecorder(), state) + assert.NoError(t, err) + assert.Equal(t, expectedReturnURL, returnURL) + }) + + t.Run("VerifyAppState_Failed", func(t *testing.T) { + req := httptest.NewRequest("GET", "/", nil) + for _, cookie := range generateResponse.Result().Cookies() { + req.AddCookie(cookie) + } + + _, err := app.verifyAppState(req, httptest.NewRecorder(), "wrong state") + assert.Error(t, err) + }) +} + +func TestGenerateAppState_NoReturnURL(t *testing.T) { + signature, err := util.MakeSignature(32) + require.NoError(t, err) + cdSettings := &settings.ArgoCDSettings{ServerSignature: signature} + key, err := cdSettings.GetServerEncryptionKey() + require.NoError(t, err) + + req := httptest.NewRequest("GET", "/", nil) + encrypted, err := crypto.Encrypt([]byte("123"), key) + require.NoError(t, err) + + app, err := NewClientApp(cdSettings, "", "/argo-cd") + require.NoError(t, err) + + req.AddCookie(&http.Cookie{Name: common.StateCookieName, Value: hex.EncodeToString(encrypted)}) + returnURL, err := app.verifyAppState(req, httptest.NewRecorder(), "123") + assert.NoError(t, err) + assert.Equal(t, "/argo-cd", returnURL) +} diff --git a/util/settings/settings.go b/util/settings/settings.go index e05183d564008..71e731ac216ad 100644 --- a/util/settings/settings.go +++ b/util/settings/settings.go @@ -35,6 +35,7 @@ import ( "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v2/server/settings/oidc" "github.com/argoproj/argo-cd/v2/util" + "github.com/argoproj/argo-cd/v2/util/crypto" "github.com/argoproj/argo-cd/v2/util/kube" "github.com/argoproj/argo-cd/v2/util/password" tlsutil "github.com/argoproj/argo-cd/v2/util/tls" @@ -1481,6 +1482,11 @@ func (a *ArgoCDSettings) IsDexConfigured() bool { return len(dexCfg) > 0 } +// GetServerEncryptionKey generates a new server encryption key using the server signature as a passphrase +func (a *ArgoCDSettings) GetServerEncryptionKey() ([]byte, error) { + return crypto.KeyFromPassphrase(string(a.ServerSignature)) +} + func UnmarshalDexConfig(config string) (map[string]interface{}, error) { var dexCfg map[string]interface{} err := yaml.Unmarshal([]byte(config), &dexCfg) From 5f8fb650a05da9b187437eb3cae4c314cdaa1604 Mon Sep 17 00:00:00 2001 From: Alexander Matyushentsev Date: Wed, 26 Jan 2022 19:04:41 -0800 Subject: [PATCH 10/25] chore: upgrade gitops engine (#8288) Signed-off-by: Alexander Matyushentsev Signed-off-by: pashavictorovich --- go.mod | 4 ++-- go.sum | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index d0cb3d607129d..4dca3b4fe5263 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d github.com/alicebob/miniredis v2.5.0+incompatible github.com/alicebob/miniredis/v2 v2.14.2 - github.com/argoproj/gitops-engine v0.5.1-0.20220119211147-b5600162862f + github.com/argoproj/gitops-engine v0.5.1-0.20220126184517-b0c5e00ccfa5 github.com/argoproj/notifications-engine v0.3.1-0.20220124172652-14e7f52eb33e github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 github.com/bombsimon/logrusr/v2 v2.0.1 @@ -21,7 +21,7 @@ require ( github.com/fsnotify/fsnotify v1.5.1 github.com/ghodss/yaml v1.0.0 github.com/go-git/go-git/v5 v5.2.0 - github.com/go-logr/logr v1.2.0 + github.com/go-logr/logr v1.2.2 github.com/go-openapi/loads v0.19.4 github.com/go-openapi/runtime v0.19.4 github.com/go-openapi/spec v0.19.5 // indirect diff --git a/go.sum b/go.sum index a54549b76a12c..85c23f0df49a6 100644 --- a/go.sum +++ b/go.sum @@ -125,8 +125,8 @@ github.com/antonmedv/expr v1.8.9/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmH github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/appscode/go v0.0.0-20190808133642-1d4ef1f1c1e0/go.mod h1:iy07dV61Z7QQdCKJCIvUoDL21u6AIceRhZzyleh2ymc= -github.com/argoproj/gitops-engine v0.5.1-0.20220119211147-b5600162862f h1:9ZEZZ5vgvaWLeAZnrZEpBh9UdW+RD19odD6YziYc5/8= -github.com/argoproj/gitops-engine v0.5.1-0.20220119211147-b5600162862f/go.mod h1:t/X9eVdopmPIYO0LTCqZirEXCQn1tzXxxQpEgMtTwWI= +github.com/argoproj/gitops-engine v0.5.1-0.20220126184517-b0c5e00ccfa5 h1:oMRXPoMzlonjHMUE/dcdimFLiWUTieitanXlCIQf+a8= +github.com/argoproj/gitops-engine v0.5.1-0.20220126184517-b0c5e00ccfa5/go.mod h1:UJrK2YMBUbwJZue68mXhSDw+T52egdZWAU1F5cK34ko= github.com/argoproj/notifications-engine v0.3.1-0.20220124172652-14e7f52eb33e h1:px7jeBJNoRF84tcik7Iw7MtXOUiqqNhYLf3UapYhJBM= github.com/argoproj/notifications-engine v0.3.1-0.20220124172652-14e7f52eb33e/go.mod h1:fONJdKbHnb3uhczfCXfJhlk87RPKCqt489KX+AaXurA= github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 h1:Cfp7rO/HpVxnwlRqJe0jHiBbZ77ZgXhB6HWlYD02Xdc= @@ -335,8 +335,9 @@ github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg github.com/go-logr/logr v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.0.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= From b1490e52fa7da515a613f78eb33fb42f0c9bfd29 Mon Sep 17 00:00:00 2001 From: Alexander Matyushentsev Date: Wed, 26 Jan 2022 22:09:10 -0800 Subject: [PATCH 11/25] Revert "chore: Run dex reverse proxy only when dex is configured (#7999)" (#8291) This reverts commit 303cc4613b488186fd221a72d38b32b825102080. Signed-off-by: Alexander Matyushentsev Signed-off-by: pashavictorovich --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index a468825613876..46ac83299ef1e 100644 --- a/server/server.go +++ b/server/server.go @@ -772,7 +772,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl // registerDexHandlers will register dex HTTP handlers, creating the the OAuth client app func (a *ArgoCDServer) registerDexHandlers(mux *http.ServeMux) { - if !a.settings.IsDexConfigured() { + if !a.settings.IsSSOConfigured() { return } // Run dex OpenID Connect Identity Provider behind a reverse proxy (served at /api/dex) From c169b8a567bf1fd81dc843f1926ee0ea5b141442 Mon Sep 17 00:00:00 2001 From: Snaipe Date: Thu, 27 Jan 2022 17:18:46 +0100 Subject: [PATCH 12/25] docs: update ambassador mappings to avoid --grpc-web-root-path (#8028) I found it non-intuitive to have to tell our users to use `--grpc-web-root-path /` when logging in when the defaults should have just worked. This commit updates the Host-based ambassador mappings to avoid that, making plain `argocd login ` calls work. Signed-off-by: Franklin "Snaipe" Mathieu Signed-off-by: pashavictorovich --- docs/operator-manual/ingress.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/operator-manual/ingress.md b/docs/operator-manual/ingress.md index fb3acfbe6de5d..91a8b8edd8861 100644 --- a/docs/operator-manual/ingress.md +++ b/docs/operator-manual/ingress.md @@ -32,15 +32,19 @@ metadata: name: argocd-server-cli namespace: argocd spec: + # NOTE: the port must be ignored if you have strip_matching_host_port enabled on envoy host: argocd.example.com:443 prefix: / - service: argocd-server:443 + service: argocd-server:80 + regex_headers: + Content-Type: "^application/grpc.*$" + grpc: true ``` -Login with the `argocd` CLI using the extra `--grpc-web-root-path` flag for gRPC-web. +Login with the `argocd` CLI: ```shell -argocd login : --grpc-web-root-path / +argocd login ``` ### Option 2: Mapping CRD for Path-based Routing From bd2e461665e33d455c653be533f063d477e2242b Mon Sep 17 00:00:00 2001 From: Michael Crenshaw Date: Thu, 27 Jan 2022 13:01:48 -0500 Subject: [PATCH 13/25] chore: fix docs formatting (#8293) Signed-off-by: Michael Crenshaw Signed-off-by: pashavictorovich --- docs/user-guide/config-management-plugins.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/user-guide/config-management-plugins.md b/docs/user-guide/config-management-plugins.md index 024f73f9b81c0..82c12ab2eddad 100644 --- a/docs/user-guide/config-management-plugins.md +++ b/docs/user-guide/config-management-plugins.md @@ -67,7 +67,8 @@ spec: lockRepo: false ``` -!!! note While the ConfigManagementPlugin _looks like_ a Kubernetes object, it is not actually a custom resource. +!!! note + While the ConfigManagementPlugin _looks like_ a Kubernetes object, it is not actually a custom resource. It only follows kubernetes-style spec conventions. The `generate` command must print a valid YAML stream to stdout. Both `init` and `generate` commands are executed inside the application source directory. From c99c238845311b139c9d64c5dfbd104e99eb5f64 Mon Sep 17 00:00:00 2001 From: pasha-codefresh Date: Thu, 27 Jan 2022 22:09:14 +0200 Subject: [PATCH 14/25] chore: update slack version (#8299) chore: update slack version (#8299) Signed-off-by: pashavictorovich Signed-off-by: pashavictorovich --- go.mod | 4 ++-- go.sum | 8 ++++---- hack/gen-resources/util/gen_options_parser.go | 5 ++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 4dca3b4fe5263..580ae9febed17 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/alicebob/miniredis v2.5.0+incompatible github.com/alicebob/miniredis/v2 v2.14.2 github.com/argoproj/gitops-engine v0.5.1-0.20220126184517-b0c5e00ccfa5 - github.com/argoproj/notifications-engine v0.3.1-0.20220124172652-14e7f52eb33e + github.com/argoproj/notifications-engine v0.3.1-0.20220127183449-91deed20b998 github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 github.com/bombsimon/logrusr/v2 v2.0.1 github.com/bradleyfalzon/ghinstallation/v2 v2.0.4 @@ -180,7 +180,7 @@ require ( github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/sergi/go-diff v1.1.0 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect - github.com/slack-go/slack v0.6.6 // indirect + github.com/slack-go/slack v0.10.1 // indirect github.com/stretchr/objx v0.2.0 // indirect github.com/vmihailenco/go-tinylfu v0.2.1 // indirect github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect diff --git a/go.sum b/go.sum index 85c23f0df49a6..54cbb0f37fb48 100644 --- a/go.sum +++ b/go.sum @@ -127,8 +127,8 @@ github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb github.com/appscode/go v0.0.0-20190808133642-1d4ef1f1c1e0/go.mod h1:iy07dV61Z7QQdCKJCIvUoDL21u6AIceRhZzyleh2ymc= github.com/argoproj/gitops-engine v0.5.1-0.20220126184517-b0c5e00ccfa5 h1:oMRXPoMzlonjHMUE/dcdimFLiWUTieitanXlCIQf+a8= github.com/argoproj/gitops-engine v0.5.1-0.20220126184517-b0c5e00ccfa5/go.mod h1:UJrK2YMBUbwJZue68mXhSDw+T52egdZWAU1F5cK34ko= -github.com/argoproj/notifications-engine v0.3.1-0.20220124172652-14e7f52eb33e h1:px7jeBJNoRF84tcik7Iw7MtXOUiqqNhYLf3UapYhJBM= -github.com/argoproj/notifications-engine v0.3.1-0.20220124172652-14e7f52eb33e/go.mod h1:fONJdKbHnb3uhczfCXfJhlk87RPKCqt489KX+AaXurA= +github.com/argoproj/notifications-engine v0.3.1-0.20220127183449-91deed20b998 h1:V9RDg+IZeebnm3XjkfkbN07VM21Fu1Cy/RJNoHO++VM= +github.com/argoproj/notifications-engine v0.3.1-0.20220127183449-91deed20b998/go.mod h1:5mKv7zEgI3NO0L+fsuRSwBSY9EIXSuyIsDND8O8TTIw= github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 h1:Cfp7rO/HpVxnwlRqJe0jHiBbZ77ZgXhB6HWlYD02Xdc= github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0/go.mod h1:ra+bQPmbVAoEL+gYSKesuigt4m49i3Qa3mE/xQcjCiA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -902,8 +902,8 @@ github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c h1:fyKiXKO1/I/B6Y2U8T7WdQGWzwehOuGIrljPtt7YTTI= github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= -github.com/slack-go/slack v0.6.6 h1:ln0fO794CudStSJEfhZ08Ok5JanMjvW6/k2xBuHqedU= -github.com/slack-go/slack v0.6.6/go.mod h1:FGqNzJBmxIsZURAxh2a8D21AnOVvvXZvGligs4npPUM= +github.com/slack-go/slack v0.10.1 h1:BGbxa0kMsGEvLOEoZmYs8T1wWfoZXwmQFBb6FgYCXUA= +github.com/slack-go/slack v0.10.1/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= diff --git a/hack/gen-resources/util/gen_options_parser.go b/hack/gen-resources/util/gen_options_parser.go index 0a43194733d88..d2938f4520325 100644 --- a/hack/gen-resources/util/gen_options_parser.go +++ b/hack/gen-resources/util/gen_options_parser.go @@ -41,9 +41,8 @@ type GenerateOpts struct { ClusterOpts ClusterOpts `yaml:"cluster"` RepositoryOpts RepositoryOpts `yaml:"repository"` ProjectOpts ProjectOpts `yaml:"project"` - - GithubToken string - Namespace string `yaml:"namespace"` + GithubToken string + Namespace string `yaml:"namespace"` } func Parse(opts *GenerateOpts, file string) error { From cb60c496b566a6b537557803923651df5ed61d7d Mon Sep 17 00:00:00 2001 From: Keith Chong Date: Thu, 27 Jan 2022 18:59:53 -0500 Subject: [PATCH 15/25] feat: Zoom in and out on resource view (#7183) (#8290) Signed-off-by: Keith Chong Signed-off-by: pashavictorovich --- .../application-details.scss | 61 +++++++++++++++++++ .../application-details.tsx | 40 +++++++++--- .../application-resource-tree.tsx | 3 +- 3 files changed, 93 insertions(+), 11 deletions(-) diff --git a/ui/src/app/applications/components/application-details/application-details.scss b/ui/src/app/applications/components/application-details/application-details.scss index 71eaf2f545d5d..c73fd9537f52d 100644 --- a/ui/src/app/applications/components/application-details/application-details.scss +++ b/ui/src/app/applications/components/application-details/application-details.scss @@ -206,4 +206,65 @@ $header: 120px; top: 280px; } } + + .graph-options-panel { + margin-left: 10px; + z-index: 10; + padding: 5px; + display: inline-block; + background-color: $argo-color-gray-1; + box-shadow: 1px 1px 3px $argo-color-gray-5; + position: fixed; + + a { + padding: 5px; + margin: 2px; + color: $argo-color-gray-6; + border: 1px solid transparent; + border-radius: 5px; + + &.group-nodes-button { + cursor: pointer; + position: relative; + display: inline-block; + vertical-align: middle; + font-size: 13px; + font-weight: 500; + line-height: 1.4; + text-align: center; + user-select: none; + transition: background-color .2s, border .2s, color .2s; + text-transform: uppercase; + &:hover { + background-color: #d1d5d9; + } + &:active { + transition: background-color .2s, border .2s, color .2s; + border: 1px $argo-color-teal-5 solid; + } + } + + &.group-nodes-button-on { + color: $argo-color-gray-1; + background-color: $argo-color-gray-6; + &:hover { + background-color: $argo-color-gray-5; + } + } + } + + .zoom-value { + user-select: none; + margin-top: 3px; + margin-right: 6px; + margin-left: 4px; + font-size: 14px; + text-align-last: right; + float: right; + width: 40px; + border: 1px $argo-color-gray-5 solid; + background-color: $argo-color-gray-3; + padding: 2px; + } + } } diff --git a/ui/src/app/applications/components/application-details/application-details.tsx b/ui/src/app/applications/components/application-details/application-details.tsx index 0ff119030af32..430149b8cba9f 100644 --- a/ui/src/app/applications/components/application-details/application-details.tsx +++ b/ui/src/app/applications/components/application-details/application-details.tsx @@ -34,6 +34,7 @@ interface ApplicationDetailsState { revision?: string; groupedResources?: ResourceStatus[]; slidingPanelPage?: number; + zoom?: number; } interface FilterInput { @@ -68,7 +69,7 @@ export class ApplicationDetails extends React.Component) { super(props); - this.state = {page: 0, groupedResources: [], slidingPanelPage: 0}; + this.state = {page: 0, groupedResources: [], slidingPanelPage: 0, zoom: 1.0}; } private get showOperationState() { @@ -211,6 +212,16 @@ export class ApplicationDetails extends React.Component { + let targetZoom: number = this.state.zoom + s; + if (targetZoom <= 0.05) { + targetZoom = 0.1; + } else if (targetZoom > 2.0) { + targetZoom = 2.0; + } + this.setState({zoom: targetZoom}); + }; return (
Refreshing

} {((pref.view === 'tree' || pref.view === 'network') && ( - {pref.view === 'tree' && ( - - )} + this.filterTreeNode(node, treeFilter)} selectedNodeFullName={this.selectedNodeKey} @@ -299,6 +318,7 @@ export class ApplicationDetails extends React.Component openGroupNodeDetails(groupdedNodeIds)} + zoom={this.state.zoom} /> )) || diff --git a/ui/src/app/applications/components/application-resource-tree/application-resource-tree.tsx b/ui/src/app/applications/components/application-resource-tree/application-resource-tree.tsx index 53d1abfa64dab..d298eac28f8d8 100644 --- a/ui/src/app/applications/components/application-resource-tree/application-resource-tree.tsx +++ b/ui/src/app/applications/components/application-resource-tree/application-resource-tree.tsx @@ -43,6 +43,7 @@ export interface ApplicationResourceTreeProps { onClearFilter: () => any; showOrphanedResources: boolean; showCompactNodes: boolean; + zoom: number; } interface Line { @@ -644,7 +645,7 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) => )) || (
+ style={{width: size.width + 150, height: size.height + 250, transformOrigin: '0% 0%', transform: `scale(${props.zoom})`}}> {graphNodes.map(key => { const node = graph.node(key); const nodeType = node.type; From 313a62ec4dc975f5e4961a59b05ea6e11dc6cd5c Mon Sep 17 00:00:00 2001 From: Keisuke Wada Date: Fri, 28 Jan 2022 18:05:12 +0900 Subject: [PATCH 16/25] docs: Add ZOZO to list of users (#8300) Signed-off-by: keisuke.wada Signed-off-by: pashavictorovich --- USERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/USERS.md b/USERS.md index cbe22b8c3d9f1..cd907d02d7456 100644 --- a/USERS.md +++ b/USERS.md @@ -194,3 +194,4 @@ Currently, the following organizations are **officially** using Argo CD: 1. [Skyscanner](https://www.skyscanner.net/) 1. [Casavo](https://casavo.com) 1. [Majid Al Futtaim](https://www.majidalfuttaim.com/) +1. [ZOZO](https://corp.zozo.com/) From 422db25caf6ff5398f39fbf6ac1ac7165f3054e1 Mon Sep 17 00:00:00 2001 From: Guilhem Lettron Date: Fri, 28 Jan 2022 10:08:31 +0100 Subject: [PATCH 17/25] chore(bug_report): put instructions in comment (#8283) Users reporting a bug will see instructions, can keep it, it will be ignored in final issue Signed-off-by: Guilhem Lettron Signed-off-by: pashavictorovich --- .github/ISSUE_TEMPLATE/bug_report.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f5344da2b86b7..41a1b4ae95ec7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,7 +6,7 @@ labels: 'bug' assignees: '' --- -If you are trying to resolve an environment-specific issue or have a one-off question about the edge case that does not require a feature then please consider asking a question in argocd slack [channel](https://argoproj.github.io/community/join-slack). + Checklist: @@ -16,19 +16,19 @@ Checklist: **Describe the bug** -A clear and concise description of what the bug is. + **To Reproduce** -A list of the steps required to reproduce the issue. Best of all, give us the URL to a repository that exhibits this issue. + **Expected behavior** -A clear and concise description of what you expected to happen. + **Screenshots** -If applicable, add screenshots to help explain your problem. + **Version** From 855930ce22d1686a4b4d22626cf486a319f702ad Mon Sep 17 00:00:00 2001 From: Yuan Tang Date: Fri, 28 Jan 2022 12:06:05 -0500 Subject: [PATCH 18/25] docs: Add link to new blog (#8308) Signed-off-by: Yuan Tang Signed-off-by: pashavictorovich --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7baea97ed35c4..2dc1116220609 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ Participation in the Argo CD project is governed by the [CNCF Code of Conduct](h ### Blogs and Presentations 1. [Awesome-Argo: A Curated List of Awesome Projects and Resources Related to Argo](https://github.com/terrytangyuan/awesome-argo) +1. [Unveil the Secret Ingredients of Continuous Delivery at Enterprise Scale with Argo CD](https://blog.akuity.io/unveil-the-secret-ingredients-of-continuous-delivery-at-enterprise-scale-with-argo-cd-7c5b4057ee49) 1. [GitOps Without Pipelines With ArgoCD Image Updater](https://youtu.be/avPUQin9kzU) 1. [Combining Argo CD (GitOps), Crossplane (Control Plane), And KubeVela (OAM)](https://youtu.be/eEcgn_gU3SM) 1. [How to Apply GitOps to Everything - Combining Argo CD and Crossplane](https://youtu.be/yrj4lmScKHQ) From 7c75fc288090a866f359cdee8868672356c892b1 Mon Sep 17 00:00:00 2001 From: Yujun Zhang Date: Sat, 29 Jan 2022 02:58:36 +0800 Subject: [PATCH 19/25] fix: fetch revision only then fallback to default refspec (#5605) * fix: fallback to fetch default only on error Ignoring commit SHA breaks gerrit when the commit is not merged Signed-off-by: Yujun Zhang * revert util/git/client.go changes Signed-off-by: Alexander Matyushentsev Co-authored-by: Alexander Matyushentsev Signed-off-by: pashavictorovich --- reposerver/repository/repository.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/reposerver/repository/repository.go b/reposerver/repository/repository.go index 7399a15475e41..212ef5424fe49 100644 --- a/reposerver/repository/repository.go +++ b/reposerver/repository/repository.go @@ -1583,25 +1583,25 @@ func checkoutRevision(gitClient git.Client, revision string) error { return status.Errorf(codes.Internal, "Failed to initialize git repo: %v", err) } - // Some git providers don't support fetching commit sha - if revision != "" && !git.IsCommitSHA(revision) && !git.IsTruncatedCommitSHA(revision) { - err = gitClient.Fetch(revision) - if err != nil { - return status.Errorf(codes.Internal, "Failed to fetch %s: %v", revision, err) - } - err = gitClient.Checkout("FETCH_HEAD") - if err != nil { - return status.Errorf(codes.Internal, "Failed to checkout FETCH_HEAD: %v", err) - } - } else { + err = gitClient.Fetch(revision) + + if err != nil { + log.Infof("Failed to fetch revision %s: %v", revision, err) + log.Infof("Fallback to fetch default") err = gitClient.Fetch("") if err != nil { - return status.Errorf(codes.Internal, "Failed to fetch %s: %v", revision, err) + return status.Errorf(codes.Internal, "Failed to fetch default: %v", err) } err = gitClient.Checkout(revision) if err != nil { - return status.Errorf(codes.Internal, "Failed to checkout %s: %v", revision, err) + return status.Errorf(codes.Internal, "Failed to checkout revision %s: %v", revision, err) } + return err + } + + err = gitClient.Checkout("FETCH_HEAD") + if err != nil { + return status.Errorf(codes.Internal, "Failed to checkout FETCH_HEAD: %v", err) } return err From c49213061ba2b0f294b736055fe0abe9c735e33f Mon Sep 17 00:00:00 2001 From: Alexander Matyushentsev Date: Fri, 28 Jan 2022 12:04:04 -0800 Subject: [PATCH 20/25] fix: add missing steps in release workflow to setup docker buildx (#8311) Signed-off-by: Alexander Matyushentsev Signed-off-by: pashavictorovich --- .github/workflows/release.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8afa0f468e933..0976bf60ff159 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -197,6 +197,8 @@ jobs: if: ${{ env.DRY_RUN != 'true' }} + - uses: docker/setup-qemu-action@v1 + - uses: docker/setup-buildx-action@v1 - name: Build and push Docker image for release run: | set -ue From 99d3e36cffc4d1f3660d79391178d8cbc2ec695b Mon Sep 17 00:00:00 2001 From: Saumeya Katyal Date: Sat, 29 Jan 2022 01:49:30 +0530 Subject: [PATCH 21/25] feat: favourite ui feature (#8210) * feat: favourite ui feature (#8210) Signed-off-by: saumeya Signed-off-by: pashavictorovich --- .../applications-list/applications-filter.tsx | 22 ++ .../applications-list/applications-list.scss | 1 - .../applications-list/applications-list.tsx | 3 + .../applications-list/applications-table.tsx | 143 +++++---- .../applications-list/applications-tiles.tsx | 286 ++++++++++-------- .../services/view-preferences-service.ts | 5 + 6 files changed, 271 insertions(+), 189 deletions(-) diff --git a/ui/src/app/applications/components/applications-list/applications-filter.tsx b/ui/src/app/applications/components/applications-list/applications-filter.tsx index 6efbd6062a988..e0ee2b95e7d9c 100644 --- a/ui/src/app/applications/components/applications-list/applications-filter.tsx +++ b/ui/src/app/applications/components/applications-list/applications-filter.tsx @@ -1,6 +1,8 @@ +import {Checkbox} from 'argo-ui'; import {useData} from 'argo-ui/v2'; import * as minimatch from 'minimatch'; import * as React from 'react'; +import {Context} from '../../../shared/context'; import {Application, ApplicationDestination, Cluster, HealthStatusCode, HealthStatuses, SyncStatusCode, SyncStatuses} from '../../../shared/models'; import {AppsListPreferences, services} from '../../../shared/services'; import {Filter, FiltersGroup} from '../filter/filter'; @@ -13,6 +15,7 @@ export interface FilterResult { health: boolean; namespaces: boolean; clusters: boolean; + favourite: boolean; labels: boolean; } @@ -28,6 +31,7 @@ export function getFilterResults(applications: Application[], pref: AppsListPref sync: pref.syncFilter.length === 0 || pref.syncFilter.includes(app.status.sync.status), health: pref.healthFilter.length === 0 || pref.healthFilter.includes(app.status.health.status), namespaces: pref.namespacesFilter.length === 0 || pref.namespacesFilter.some(ns => app.spec.destination.namespace && minimatch(app.spec.destination.namespace, ns)), + favourite: !pref.showFavorites || pref.favoritesAppList.includes(app.metadata.name), clusters: pref.clustersFilter.length === 0 || pref.clustersFilter.some(filterString => { @@ -211,6 +215,23 @@ const NamespaceFilter = (props: AppFilterProps) => { ); }; +const FavoriteFilter = (props: AppFilterProps) => { + const ctx = React.useContext(Context); + return ( +
+ { + ctx.navigation.goto('.', {showFavorites: val}, {replace: true}); + services.viewPreferences.updatePreferences({appList: {...props.pref, showFavorites: val}}); + }} + />{' '} + +
+ ); +}; + export const ApplicationsFilter = (props: AppFilterProps) => { const setShown = (val: boolean) => { services.viewPreferences.updatePreferences({appList: {...props.pref, hideFilters: !val}}); @@ -218,6 +239,7 @@ export const ApplicationsFilter = (props: AppFilterProps) => { return ( + diff --git a/ui/src/app/applications/components/applications-list/applications-list.scss b/ui/src/app/applications/components/applications-list/applications-list.scss index 6d34822f9ab33..e731619d51ce8 100644 --- a/ui/src/app/applications/components/applications-list/applications-list.scss +++ b/ui/src/app/applications/components/applications-list/applications-list.scss @@ -172,7 +172,6 @@ &__external-links-icon-container { position: relative; display: inline-block; - width: 28px; } .filters-group__panel { diff --git a/ui/src/app/applications/components/applications-list/applications-list.tsx b/ui/src/app/applications/components/applications-list/applications-list.tsx index 5303780c22334..133695d08436a 100644 --- a/ui/src/app/applications/components/applications-list/applications-list.tsx +++ b/ui/src/app/applications/components/applications-list/applications-list.tsx @@ -122,6 +122,9 @@ const ViewPref = ({children}: {children: (pref: AppsListPreferences & {page: num .split(',') .filter(item => !!item); } + if (params.get('showFavorites') != null) { + viewPref.showFavorites = params.get('showFavorites') === 'true'; + } if (params.get('view') != null) { viewPref.view = params.get('view') as AppsListViewType; } else { diff --git a/ui/src/app/applications/components/applications-list/applications-table.tsx b/ui/src/app/applications/components/applications-list/applications-table.tsx index cf8e4e80dbf46..1f3c14bbe34bc 100644 --- a/ui/src/app/applications/components/applications-list/applications-table.tsx +++ b/ui/src/app/applications/components/applications-list/applications-table.tsx @@ -1,4 +1,4 @@ -import {DropDownMenu} from 'argo-ui'; +import {DataLoader, DropDownMenu, Tooltip} from 'argo-ui'; import * as React from 'react'; import {Key, KeybindingContext, useNav} from 'argo-ui/v2'; import {Cluster} from '../../../shared/components'; @@ -9,6 +9,7 @@ import * as AppUtils from '../utils'; import {OperationState} from '../utils'; import {ApplicationsLabels} from './applications-labels'; import {ApplicationsSource} from './applications-source'; +import {services} from '../../../shared/services'; require('./applications-table.scss'); export const ApplicationsTable = (props: { @@ -34,66 +35,98 @@ export const ApplicationsTable = (props: { return ( {ctx => ( -
- {props.applications.map((app, i) => ( -
services.viewPreferences.getPreferences()}> + {pref => { + const favList = pref.appList.favoritesAppList || []; + return ( +
+ {props.applications.map((app, i) => ( +
-
ctx.navigation.goto(`/applications/${app.metadata.name}`, {}, {event: e})}> -
-
-
Project:
-
{app.spec.project}
-
-
-
Name:
-
- {app.metadata.name} -
-
-
-
-
-
Source:
-
-
- +
ctx.navigation.goto(`/applications/${app.metadata.name}`, {}, {event: e})}> +
+
+
+
+ + + + +
+
+
Project:
+
{app.spec.project}
+
+
+
+
Name:
+
{app.metadata.name}
+
-
- + +
+
+
Source:
+
+
+ +
+
+ +
+
+
+
+
Destination:
+
+ /{app.spec.destination.namespace} +
+
+
+
+ {app.status.health.status}
+ + {app.status.sync.status} + ( + + )} + items={[ + {title: 'Sync', action: () => props.syncApplication(app.metadata.name)}, + {title: 'Refresh', action: () => props.refreshApplication(app.metadata.name)}, + {title: 'Delete', action: () => props.deleteApplication(app.metadata.name)} + ]} + />
-
-
Destination:
-
- /{app.spec.destination.namespace} -
-
-
-
- {app.status.health.status} -
- - {app.status.sync.status} - ( - - )} - items={[ - {title: 'Sync', action: () => props.syncApplication(app.metadata.name)}, - {title: 'Refresh', action: () => props.refreshApplication(app.metadata.name)}, - {title: 'Delete', action: () => props.deleteApplication(app.metadata.name)} - ]} - /> -
+ ))}
-
- ))} -
+ ); + }} + )} ); diff --git a/ui/src/app/applications/components/applications-list/applications-tiles.tsx b/ui/src/app/applications/components/applications-list/applications-tiles.tsx index b22158a679ac7..6f7509f9ef4ee 100644 --- a/ui/src/app/applications/components/applications-list/applications-tiles.tsx +++ b/ui/src/app/applications/components/applications-list/applications-tiles.tsx @@ -102,158 +102,178 @@ export const ApplicationTiles = ({applications, syncApplication, refreshApplicat {ctx => ( services.viewPreferences.getPreferences()}> - {pref => ( -
- {applications.map((app, i) => ( -
-
-
ctx.navigation.goto(`/applications/${app.metadata.name}`, {view: pref.appDetails.view}, {event: e})}> -
-
- -
-
-
- - {app.metadata.name} -
-
-
-
- Project: -
-
{app.spec.project}
-
-
-
- Labels: -
-
- - {Object.keys(app.metadata.labels || {}) - .map(label => ({label, value: app.metadata.labels[label]})) - .map(item => ( -
- {item.label}={item.value} -
- ))} -
- }> - - {Object.keys(app.metadata.labels || {}) - .map(label => `${label}=${app.metadata.labels[label]}`) - .join(', ')} - + {pref => { + const favList = pref.appList.favoritesAppList || []; + return ( +
+ {applications.map((app, i) => ( +
+
+
ctx.navigation.goto(`/applications/${app.metadata.name}`, {view: pref.appDetails.view}, {event: e})}> +
+
+ + +
-
-
-
- Status: -
-
- {app.status.health.status} -   - {app.status.sync.status} -   - -
-
-
-
- Repository: +
+
+ + {app.metadata.name} +
-
- - {app.spec.source.repoURL} - +
+
+ Project: +
+
{app.spec.project}
-
-
-
- Target Revision: +
+
+ Labels: +
+
+ + {Object.keys(app.metadata.labels || {}) + .map(label => ({label, value: app.metadata.labels[label]})) + .map(item => ( +
+ {item.label}={item.value} +
+ ))} +
+ }> + + {Object.keys(app.metadata.labels || {}) + .map(label => `${label}=${app.metadata.labels[label]}`) + .join(', ')} + + +
-
{app.spec.source.targetRevision}
-
- {app.spec.source.path && (
-
- Path: +
+ Status: +
+
+ {app.status.health.status} +   + {app.status.sync.status} +   +
-
{app.spec.source.path}
- )} - {app.spec.source.chart && (
-
- Chart: +
+ Repository: +
+
+ + {app.spec.source.repoURL} +
-
{app.spec.source.chart}
- )} -
-
- Destination: +
+
+ Target Revision: +
+
{app.spec.source.targetRevision}
-
- + {app.spec.source.path && ( +
+
+ Path: +
+
{app.spec.source.path}
+
+ )} + {app.spec.source.chart && ( +
+
+ Chart: +
+
{app.spec.source.chart}
+
+ )} +
+
+ Destination: +
+
+ +
-
-
-
- ))} -
- )} + ))} +
+ ); + }} )} diff --git a/ui/src/app/shared/services/view-preferences-service.ts b/ui/src/app/shared/services/view-preferences-service.ts index 7251305913b13..6a8f2dfcff763 100644 --- a/ui/src/app/shared/services/view-preferences-service.ts +++ b/ui/src/app/shared/services/view-preferences-service.ts @@ -63,6 +63,7 @@ export class AppsListPreferences { pref.projectsFilter = []; pref.reposFilter = []; pref.syncFilter = []; + pref.showFavorites = false; } public labelsFilter: string[]; @@ -75,6 +76,8 @@ export class AppsListPreferences { public view: AppsListViewType; public hideFilters: boolean; public statusBarView: HealthStatusBarPreferences; + public showFavorites: boolean; + public favoritesAppList: string[]; } export interface ViewPreferences { @@ -117,6 +120,8 @@ const DEFAULT_PREFERENCES: ViewPreferences = { syncFilter: new Array(), healthFilter: new Array(), hideFilters: false, + showFavorites: false, + favoritesAppList: new Array(), statusBarView: { showHealthStatusBar: true } From 804edd887e93b9862de169c3b1ff4eac3661a574 Mon Sep 17 00:00:00 2001 From: Alexander Matyushentsev Date: Sun, 30 Jan 2022 13:01:13 -0800 Subject: [PATCH 22/25] fix: argocd build fails on windows (#8319) Signed-off-by: Alexander Matyushentsev Signed-off-by: pashavictorovich --- cmpserver/plugin/plugin.go | 5 ++--- cmpserver/plugin/plugin_unix.go | 16 ++++++++++++++++ cmpserver/plugin/plugin_windows.go | 16 ++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 cmpserver/plugin/plugin_unix.go create mode 100644 cmpserver/plugin/plugin_windows.go diff --git a/cmpserver/plugin/plugin.go b/cmpserver/plugin/plugin.go index 99f1727927247..2f150bd532939 100644 --- a/cmpserver/plugin/plugin.go +++ b/cmpserver/plugin/plugin.go @@ -9,7 +9,6 @@ import ( "os/exec" "path/filepath" "strings" - "syscall" "time" "github.com/argoproj/pkg/rand" @@ -68,7 +67,7 @@ func runCommand(ctx context.Context, command Command, path string, env []string) cmd.Stderr = &stderr // Make sure the command is killed immediately on timeout. https://stackoverflow.com/a/38133948/684776 - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + cmd.SysProcAttr = newSysProcAttr(true) start := time.Now() err = cmd.Start() @@ -80,7 +79,7 @@ func runCommand(ctx context.Context, command Command, path string, env []string) <-ctx.Done() // Kill by group ID to make sure child processes are killed. The - tells `kill` that it's a group ID. // Since we didn't set Pgid in SysProcAttr, the group ID is the same as the process ID. https://pkg.go.dev/syscall#SysProcAttr - _ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) + _ = sysCallKill(-cmd.Process.Pid) }() err = cmd.Wait() diff --git a/cmpserver/plugin/plugin_unix.go b/cmpserver/plugin/plugin_unix.go new file mode 100644 index 0000000000000..a9dc157bc7ef8 --- /dev/null +++ b/cmpserver/plugin/plugin_unix.go @@ -0,0 +1,16 @@ +//go:build !windows +// +build !windows + +package plugin + +import ( + "syscall" +) + +func newSysProcAttr(setpgid bool) *syscall.SysProcAttr { + return &syscall.SysProcAttr{Setpgid: setpgid} +} + +func sysCallKill(pid int) error { + return syscall.Kill(pid, syscall.SIGKILL) +} diff --git a/cmpserver/plugin/plugin_windows.go b/cmpserver/plugin/plugin_windows.go new file mode 100644 index 0000000000000..2a188e61f6f9e --- /dev/null +++ b/cmpserver/plugin/plugin_windows.go @@ -0,0 +1,16 @@ +//go:build windows +// +build windows + +package plugin + +import ( + "syscall" +) + +func newSysProcAttr(setpgid bool) *syscall.SysProcAttr { + return &syscall.SysProcAttr{} +} + +func sysCallKill(pid int) error { + return nil +} From 78e6cced6c903b852490052be022d929c1c86990 Mon Sep 17 00:00:00 2001 From: Alexander Matyushentsev Date: Mon, 31 Jan 2022 17:26:46 -0800 Subject: [PATCH 23/25] docs: mention argocd notifications and applicationset changes in upgrade instructions (#8312) Signed-off-by: Alexander Matyushentsev Signed-off-by: pashavictorovich --- docs/operator-manual/upgrading/2.2-2.3.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/operator-manual/upgrading/2.2-2.3.md b/docs/operator-manual/upgrading/2.2-2.3.md index 58efc578bca34..25bb5bdf9beb4 100644 --- a/docs/operator-manual/upgrading/2.2-2.3.md +++ b/docs/operator-manual/upgrading/2.2-2.3.md @@ -1,5 +1,14 @@ # v2.2 to 2.3 +## Argo CD Notifications and ApplicationSet Are Bundled into Argo CD + +The Argo CD Notifications and ApplicationSet are part of Argo CD now. You no longer need to install them separately. +The Notifications and ApplicationSet components are bundled into default Argo CD installation manifests. + +The bundled manifests are drop-in replacements for the previous versions. If you are using Kustomize to bundle the manifests together then just +remove references to https://github.com/argoproj-labs/argocd-notifications and https://github.com/argoproj-labs/applicationset. No action is required +if you are using `kubectl apply`. + ## Configure Additional ArgoCD Binaries We have removed non-Linux ArgoCD binaries (Darwin amd64 and Windows amd64) from the image ([#7668](https://github.com/argoproj/argo-cd/pull/7668)) and the associated download buttons in the help page in the UI. From 99abb36727b39c86d741b589e65f72527d7176a9 Mon Sep 17 00:00:00 2001 From: adamjohnson01 Date: Sat, 23 Nov 2019 22:16:34 +0000 Subject: [PATCH 24/25] add e2e test for app delete wait Signed-off-by: pashavictorovich --- test/e2e/app_deletion_test.go | 15 +++++++++++++++ test/e2e/fixture/app/actions.go | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/test/e2e/app_deletion_test.go b/test/e2e/app_deletion_test.go index 3e85fb7afbe9a..634cf92d64a07 100644 --- a/test/e2e/app_deletion_test.go +++ b/test/e2e/app_deletion_test.go @@ -41,3 +41,18 @@ func TestDeletingAppStuckInSync(t *testing.T) { // delete is successful Expect(DoesNotExist()) } + +func TestApplicationDeleteWait(t *testing.T) { + Given(t). + Path(guestbookPath). + When(). + CreateFromFile(func(app *Application) { + app.Spec.SyncPolicy = &SyncPolicy{Automated: &SyncPolicyAutomated{}} + }). + Then(). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + When(). + DeleteWait(). + Then(). + Expect(DoesNotExist()) +} diff --git a/test/e2e/fixture/app/actions.go b/test/e2e/fixture/app/actions.go index 59376f1069015..6aa9cde566fc1 100644 --- a/test/e2e/fixture/app/actions.go +++ b/test/e2e/fixture/app/actions.go @@ -318,6 +318,12 @@ func (a *Actions) Delete(cascade bool) *Actions { return a } +func (a *Actions) DeleteWait() *Actions { + a.context.t.Helper() + a.runCli("app", "delete", a.context.name, "--cascade=true","--wait") + return a +} + func (a *Actions) And(block func()) *Actions { a.context.t.Helper() block() From 6aada27a8bb843bec78124b07a3bd73b4d54d487 Mon Sep 17 00:00:00 2001 From: adamjohnson01 Date: Sun, 24 Nov 2019 12:39:08 +0000 Subject: [PATCH 25/25] fix linting issue Signed-off-by: pashavictorovich --- test/e2e/fixture/app/actions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/fixture/app/actions.go b/test/e2e/fixture/app/actions.go index 6aa9cde566fc1..51ca1dc4e62d6 100644 --- a/test/e2e/fixture/app/actions.go +++ b/test/e2e/fixture/app/actions.go @@ -320,7 +320,7 @@ func (a *Actions) Delete(cascade bool) *Actions { func (a *Actions) DeleteWait() *Actions { a.context.t.Helper() - a.runCli("app", "delete", a.context.name, "--cascade=true","--wait") + a.runCli("app", "delete", a.context.name, "--cascade=true", "--wait") return a }