Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Delete wait v2 #3

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
edb664f
Add wait and timeout flags to app delete cmd
adamjohnson01 Oct 27, 2019
5070a20
switch to using waitOnApplicationStatus
adamjohnson01 Oct 29, 2019
1f053cb
add examples to app delete
adamjohnson01 Oct 29, 2019
a3d3c3f
remove goroutine
adamjohnson01 Oct 29, 2019
680a1a6
remove println
adamjohnson01 Oct 29, 2019
1dc4052
Merge branch 'master' of https://github.com/argoproj/argo-cd into del…
adamjohnson01 Nov 3, 2019
0a92910
Merge branch 'master' of https://github.com/argoproj/argo-cd into del…
adamjohnson01 Nov 6, 2019
e84de89
Merge branch 'master' of https://github.com/argoproj/argo-cd into del…
adamjohnson01 Nov 11, 2019
c823cee
Merge branch 'master' of https://github.com/argoproj/argo-cd into del…
adamjohnson01 Nov 20, 2019
0f303ea
Merge branch 'master' of https://github.com/argoproj/argo-cd into del…
adamjohnson01 Nov 21, 2019
d972383
Merge branch 'delete-wait' of https://github.com/wintoncode/argo-cd i…
adamjohnson01 Nov 21, 2019
6184f1c
Merge branch 'master' of https://github.com/argoproj/argo-cd into del…
adamjohnson01 Nov 22, 2019
3c18b49
Merge branch 'delete-wait' of https://github.com/wintoncode/argo-cd i…
adamjohnson01 Nov 22, 2019
cd7c943
add e2e test for app delete wait
adamjohnson01 Nov 23, 2019
9ccfa64
fix linting issue
adamjohnson01 Nov 24, 2019
02c7372
Merge branch 'master' of https://github.com/argoproj/argo-cd into del…
adamjohnson01 Dec 25, 2019
0226668
delete wait refreshing pr
pasha-codefresh Feb 2, 2022
0038806
feat: Expose ARGOCD_APP_NAME to the `kustomize build` command (#8096)
wd Jan 26, 2022
69536bb
feat: Use encrypted cookie to store OAuth2 state nonce (instead of re…
Jan 26, 2022
5f8fb65
chore: upgrade gitops engine (#8288)
Jan 27, 2022
b1490e5
Revert "chore: Run dex reverse proxy only when dex is configured (#79…
Jan 27, 2022
c169b8a
docs: update ambassador mappings to avoid --grpc-web-root-path (#8028)
Snaipe Jan 27, 2022
bd2e461
chore: fix docs formatting (#8293)
crenshaw-dev Jan 27, 2022
c99c238
chore: update slack version (#8299)
pasha-codefresh Jan 27, 2022
cb60c49
feat: Zoom in and out on resource view (#7183) (#8290)
keithchong Jan 27, 2022
313a62e
docs: Add ZOZO to list of users (#8300)
wadason Jan 28, 2022
422db25
chore(bug_report): put instructions in comment (#8283)
guilhem Jan 28, 2022
855930c
docs: Add link to new blog (#8308)
terrytangyuan Jan 28, 2022
7c75fc2
fix: fetch revision only then fallback to default refspec (#5605)
yujunz Jan 28, 2022
c492130
fix: add missing steps in release workflow to setup docker buildx (#8…
Jan 28, 2022
99d3e36
feat: favourite ui feature (#8210)
saumeya Jan 28, 2022
804edd8
fix: argocd build fails on windows (#8319)
Jan 30, 2022
78e6cce
docs: mention argocd notifications and applicationset changes in upgr…
Feb 1, 2022
99abb36
add e2e test for app delete wait
adamjohnson01 Nov 23, 2019
6aada27
fix linting issue
adamjohnson01 Nov 24, 2019
16bb7c2
Merge remote-tracking branch 'origin/delete-wait' into delete-wait
pasha-codefresh Feb 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 115 additions & 48 deletions cmd/argocd/commands/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,22 @@ func NewApplicationCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
return command
}

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
}

// NewApplicationCreateCommand returns a new instance of an `argocd app create` command
func NewApplicationCreateCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
Expand Down Expand Up @@ -986,6 +1002,8 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
cascade bool
noPrompt bool
propagationPolicy string
wait bool
timeout uint
)
var command = &cobra.Command{
Use: "delete APPNAME",
Expand All @@ -1011,6 +1029,7 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
if c.Flag("cascade").Changed {
appDeleteReq.Cascade = &cascade
}

if c.Flag("propagation-policy").Changed {
appDeleteReq.PropagationPolicy = &propagationPolicy
}
Expand All @@ -1037,6 +1056,17 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
if lowercaseAnswer == "y" || lowercaseAnswer == "yes" {
_, err := appIf.Delete(context.Background(), &appDeleteReq)
errors.CheckError(err)

if wait {
appStatusOpts := applicationStatusOpts{
client: argocdclient.NewClientOrDie(clientOpts),
appName: appName,
timeout: timeout,
watch: watchOpts{delete: true},
}
_, err := waitOnApplicationStatus(appStatusOpts)
errors.CheckError(err)
}
} else {
fmt.Println("The command to delete '" + appName + "' was cancelled.")
}
Expand All @@ -1050,6 +1080,8 @@ func NewApplicationDeleteCommand(clientOpts *argocdclient.ClientOptions) *cobra.
command.Flags().BoolVar(&cascade, "cascade", true, "Perform a cascaded deletion of all application resources")
command.Flags().StringVarP(&propagationPolicy, "propagation-policy", "p", "foreground", "Specify propagation policy for deletion of application's resources. One of: foreground|background")
command.Flags().BoolVarP(&noPrompt, "yes", "y", false, "Turn off prompting to confirm cascaded deletion of application resources")
command.Flags().BoolVar(&wait, "wait", false, "Will cause the command to only return once the application has been deleted")
command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time to spend waiting for application to delete, default is no timeout")
return command
}

Expand Down Expand Up @@ -1216,13 +1248,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]",
Expand All @@ -1240,16 +1269,24 @@ 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 && !watch.delete {
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 argoio.Close(closer)
if selector != "" {
list, err := appIf.List(context.Background(), &applicationpkg.ApplicationQuery{Selector: selector})
Expand All @@ -1259,17 +1296,19 @@ 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().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
}
Expand Down Expand Up @@ -1456,7 +1495,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)

if !dryRun {
Expand Down Expand Up @@ -1603,25 +1649,25 @@ 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 == string(health.HealthStatusHealthy) ||
healthStatus == string(health.HealthStatusSuspended)
} else if watchSuspended {
} else if watch.suspended {
healthCheckPassed = healthStatus == string(health.HealthStatusSuspended)
} else if watchHealth {
} else if watch.health {
healthCheckPassed = healthStatus == string(health.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) {
func waitOnApplicationStatus(opts applicationStatusOpts) (*argoappv1.Application, error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

Expand All @@ -1633,17 +1679,17 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout
printFinalStatus := func(app *argoappv1.Application) *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)
}

Expand All @@ -1656,8 +1702,8 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout
return app
}

if timeout != 0 {
time.AfterFunc(time.Duration(timeout)*time.Second, func() {
if opts.timeout != 0 {
time.AfterFunc(time.Duration(opts.timeout)*time.Second, func() {
cancel()
})
}
Expand All @@ -1666,11 +1712,11 @@ 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)
conn, appClient := acdClient.NewApplicationClientOrDie()
conn, appClient := opts.client.NewApplicationClientOrDie()
defer argoio.Close(conn)
app, err := appClient.Get(ctx, &applicationpkg.ApplicationQuery{Name: &appName})
app, err := appClient.Get(ctx, &applicationpkg.ApplicationQuery{Name: &opts.appName})
errors.CheckError(err)
appEventCh := acdClient.WatchApplicationWithRetry(ctx, appName, app.ResourceVersion)
appEventCh := opts.client.WatchApplicationWithRetry(ctx, opts.appName, app.ResourceVersion)
for appEvent := range appEventCh {
app = &appEvent.Application

Expand All @@ -1695,35 +1741,42 @@ 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
}
}
} else {
// Wait on the application as a whole
selectedResourcesAreReady = checkResourceStatus(watchSync, watchHealth, watchOperation, watchSuspended, string(app.Status.Health.Status), string(app.Status.Sync.Status), appEvent.Application.Operation)
selectedResourcesAreReady = checkResourceStatus(opts.watch, string(app.Status.Health.Status), string(app.Status.Sync.Status), appEvent.Application.Operation)
}

if selectedResourcesAreReady && (!operationInProgress || !watchOperation) {
if selectedResourcesAreReady && (!operationInProgress || !opts.watch.operation) && !opts.watch.delete {
app = 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 != string(health.HealthStatusUnknown) && prevState.Health != string(health.HealthStatusDegraded) && newState.Health == string(health.HealthStatusDegraded) {
if opts.watch.health && prevState.Health != string(health.HealthStatusUnknown) && prevState.Health != string(health.HealthStatusDegraded) && newState.Health == string(health.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 == string(health.HealthStatusMissing) {
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
Expand All @@ -1733,9 +1786,18 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout
}
}
_ = w.Flush()
if opts.watch.delete {
_, err := appClient.Get(context.Background(), &applicationpkg.ApplicationQuery{Name: &opts.appName})
if err != nil {
if strings.HasSuffix(err.Error(), "not found") {
return nil, nil
}
}
}

}
_ = printFinalStatus(app)
return nil, fmt.Errorf("timed out (%ds) waiting for app %q match desired state", timeout, appName)
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
Expand Down Expand Up @@ -1913,7 +1975,12 @@ func NewApplicationRollbackCommand(clientOpts *argocdclient.ClientOptions) *cobr
})
errors.CheckError(err)

_, err = waitOnApplicationStatus(acdClient, appName, timeout, false, false, true, false, nil)
_, err = waitOnApplicationStatus(applicationStatusOpts{
client: acdClient,
appName: appName,
timeout: timeout,
watch: watchOpts{operation: true},
})
errors.CheckError(err)
},
}
Expand Down
15 changes: 15 additions & 0 deletions test/e2e/app_deletion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
6 changes: 6 additions & 0 deletions test/e2e/fixture/app/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down