diff --git a/docs/command-reference/tanzu_apps_workload_apply.md b/docs/command-reference/tanzu_apps_workload_apply.md index 3a038db01..149e3fabe 100644 --- a/docs/command-reference/tanzu_apps_workload_apply.md +++ b/docs/command-reference/tanzu_apps_workload_apply.md @@ -29,6 +29,7 @@ tanzu apps workload apply --file workload.yaml -a, --app name application name the workload is a part of --build-env "key=value" pair build environment variables represented as a "key=value" pair ("key-" to remove, flag can be used multiple times) --debug put the workload in debug mode (--debug=false to deactivate) + --delay duration delay set to prevent premature exit before supply chain step completion when waiting/tailing (default 30s) --dry-run print kubernetes resources to stdout rather than apply them to the cluster, messages normally on stdout will be sent to stderr -e, --env "key=value" pair environment variables represented as a "key=value" pair ("key-" to remove, flag can be used multiple times) -f, --file file path file path containing the description of a single workload, other flags are layered on top of this resource. Use value "-" to read from stdin diff --git a/docs/command-reference/tanzu_apps_workload_create.md b/docs/command-reference/tanzu_apps_workload_create.md index 32e2268f4..c944fb2b5 100644 --- a/docs/command-reference/tanzu_apps_workload_create.md +++ b/docs/command-reference/tanzu_apps_workload_create.md @@ -31,6 +31,7 @@ tanzu apps workload create --file workload.yaml -a, --app name application name the workload is a part of --build-env "key=value" pair build environment variables represented as a "key=value" pair ("key-" to remove, flag can be used multiple times) --debug put the workload in debug mode (--debug=false to deactivate) + --delay duration delay set to prevent premature exit before supply chain step completion when waiting/tailing (default 30s) --dry-run print kubernetes resources to stdout rather than apply them to the cluster, messages normally on stdout will be sent to stderr -e, --env "key=value" pair environment variables represented as a "key=value" pair ("key-" to remove, flag can be used multiple times) -f, --file file path file path containing the description of a single workload, other flags are layered on top of this resource. Use value "-" to read from stdin diff --git a/pkg/cli-runtime/wait/wait.go b/pkg/cli-runtime/wait/wait.go index 344149fd8..6be8fccb0 100644 --- a/pkg/cli-runtime/wait/wait.go +++ b/pkg/cli-runtime/wait/wait.go @@ -32,12 +32,15 @@ var ( type ConditionFunc = func(client.Object) (bool, error) -func UntilCondition(ctx context.Context, watchClient client.WithWatch, target types.NamespacedName, listType client.ObjectList, condition ConditionFunc) error { +func UntilCondition(ctx context.Context, watchClient client.WithWatch, target types.NamespacedName, listType client.ObjectList, condition ConditionFunc, delayTime time.Duration) error { + readyStatus := false + timer := time.NewTimer(delayTime) eventWatcher, err := watchClient.Watch(ctx, listType, &client.ListOptions{Namespace: target.Namespace}) if err != nil { return err } defer eventWatcher.Stop() + defer timer.Stop() for { select { case event := <-eventWatcher.ResultChan(): @@ -53,10 +56,25 @@ func UntilCondition(ctx context.Context, watchClient client.WithWatch, target ty return err } if cond { - return nil + // Timer is started/reset to track ready status change. + timer.Reset(delayTime) + readyStatus = true + } else { + // This is to capture 'unknown' state to avoid incorrect exit from tailing + readyStatus = false } } } + case <-timer.C: + // Wait until the delay time is met before stopping the tail. This is done to address the use case where + // the workload apply flows through supply chain steps to rerun, which may result in the workload status + // switching between Ready - unknown - Ready - unknown, and so on. + // The delay timer provides an option to allow the supply chain to parse through the steps before exiting the tail. + if readyStatus { + return nil + } else { + timer.Reset(delayTime) + } case <-ctx.Done(): return ctx.Err() } diff --git a/pkg/cli-runtime/wait/wait_test.go b/pkg/cli-runtime/wait/wait_test.go index 5e8c1aae1..d7a0cf606 100644 --- a/pkg/cli-runtime/wait/wait_test.go +++ b/pkg/cli-runtime/wait/wait_test.go @@ -133,7 +133,7 @@ func TestUntilReady(t *testing.T) { done := make(chan error, 1) defer close(done) go func() { - done <- UntilCondition(ctx, fakeWithWatcher, types.NamespacedName{Name: test.resource.Name, Namespace: test.resource.Namespace}, &cartov1alpha1.WorkloadList{}, test.condFunc) + done <- UntilCondition(ctx, fakeWithWatcher, types.NamespacedName{Name: test.resource.Name, Namespace: test.resource.Namespace}, &cartov1alpha1.WorkloadList{}, test.condFunc, 0*time.Second) }() for _, r := range objs { diff --git a/pkg/commands/workload.go b/pkg/commands/workload.go index 7cac98e76..b9fca6c86 100644 --- a/pkg/commands/workload.go +++ b/pkg/commands/workload.go @@ -147,6 +147,7 @@ type WorkloadOptions struct { Wait bool WaitTimeout time.Duration + DelayTime time.Duration Tail bool TailTimestamps bool DryRun bool @@ -896,18 +897,18 @@ func getStatusChangeWorker(c *cli.Config, workload *cartov1alpha1.Workload) wait } } return false, nil - }) + }, 0*time.Second) }) return worker } -func getReadyConditionWorker(c *cli.Config, workload *cartov1alpha1.Workload) wait.Worker { +func getReadyConditionWorker(c *cli.Config, workload *cartov1alpha1.Workload, delayTime time.Duration) wait.Worker { worker := wait.Worker(func(ctx context.Context) error { clientWithWatch, err := watch.GetWatcher(ctx, c) if err != nil { return err } - return wait.UntilCondition(ctx, clientWithWatch, types.NamespacedName{Name: workload.Name, Namespace: workload.Namespace}, &cartov1alpha1.WorkloadList{}, cartov1alpha1.WorkloadReadyConditionFunc) + return wait.UntilCondition(ctx, clientWithWatch, types.NamespacedName{Name: workload.Name, Namespace: workload.Namespace}, &cartov1alpha1.WorkloadList{}, cartov1alpha1.WorkloadReadyConditionFunc, delayTime) }) return worker @@ -1018,6 +1019,8 @@ func (opts *WorkloadOptions) DefineFlags(ctx context.Context, c *cli.Config, cmd cmd.MarkFlagFilename(cli.StripDash(flags.FilePathFlagName), ".yaml", ".yml") cmd.Flags().BoolVar(&opts.DryRun, cli.StripDash(flags.DryRunFlagName), false, "print kubernetes resources to stdout rather than apply them to the cluster, messages normally on stdout will be sent to stderr") cmd.Flags().BoolVarP(&opts.Yes, cli.StripDash(flags.YesFlagName), "y", false, "accept all prompts") + cmd.Flags().DurationVar(&opts.DelayTime, cli.StripDash(flags.DelayTimeFlagName), 30*time.Second, "delay set to prevent premature exit before supply chain step completion when waiting/tailing") + cmd.RegisterFlagCompletionFunc(cli.StripDash(flags.DelayTimeFlagName), completion.SuggestDurationUnits(ctx, completion.CommonDurationUnits)) } func (opts *WorkloadOptions) DefineEnvVars(ctx context.Context, c *cli.Config, cmd *cobra.Command) { diff --git a/pkg/commands/workload_apply.go b/pkg/commands/workload_apply.go index 169fc9a03..cd3489435 100644 --- a/pkg/commands/workload_apply.go +++ b/pkg/commands/workload_apply.go @@ -225,7 +225,7 @@ func (opts *WorkloadApplyOptions) Exec(ctx context.Context, c *cli.Config) error } } - workers = append(workers, getReadyConditionWorker(c, workload)) + workers = append(workers, getReadyConditionWorker(c, workload, opts.DelayTime)) if anyTail { workers = append(workers, getTailWorker(c, workload, opts.TailTimestamps)) diff --git a/pkg/commands/workload_apply_test.go b/pkg/commands/workload_apply_test.go index 74d94a478..20f4c6a39 100644 --- a/pkg/commands/workload_apply_test.go +++ b/pkg/commands/workload_apply_test.go @@ -350,7 +350,7 @@ status: { Name: "create - output yaml with wait", Args: []string{workloadName, flags.GitRepoFlagName, gitRepo, flags.GitBranchFlagName, gitBranch, - flags.OutputFlagName, printer.OutputFormatYaml, flags.WaitFlagName, flags.YesFlagName}, + flags.OutputFlagName, printer.OutputFormatYaml, flags.WaitFlagName, flags.YesFlagName, flags.DelayTimeFlagName, "0ms"}, GivenObjects: givenNamespaceDefault, Prepare: func(t *testing.T, ctx context.Context, config *cli.Config, tc *clitesting.CommandTestCase) (context.Context, error) { workload := &cartov1alpha1.Workload{ @@ -5356,7 +5356,7 @@ status: Name: "output workload after update in yaml format with wait error", Args: []string{workloadName, flags.ServiceRefFlagName, "database=services.tanzu.vmware.com/v1alpha1:PostgreSQL:my-prod-db", - flags.OutputFlagName, printer.OutputFormatYml, flags.WaitFlagName}, + flags.OutputFlagName, printer.OutputFormatYml, flags.WaitFlagName, flags.DelayTimeFlagName, "0ms"}, Prepare: func(t *testing.T, ctx context.Context, config *cli.Config, tc *clitesting.CommandTestCase) (context.Context, error) { workload := &cartov1alpha1.Workload{ ObjectMeta: metav1.ObjectMeta{ @@ -5499,7 +5499,7 @@ status: Name: "console interaction - output workload after update in yaml format with wait", Args: []string{workloadName, flags.ServiceRefFlagName, "database=services.tanzu.vmware.com/v1alpha1:PostgreSQL:my-prod-db", - flags.OutputFlagName, printer.OutputFormatYml, flags.WaitFlagName}, + flags.OutputFlagName, printer.OutputFormatYml, flags.WaitFlagName, flags.DelayTimeFlagName, "0ms"}, Prepare: func(t *testing.T, ctx context.Context, config *cli.Config, tc *clitesting.CommandTestCase) (context.Context, error) { workload := &cartov1alpha1.Workload{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/commands/workload_create.go b/pkg/commands/workload_create.go index 8fb81b377..65afc9403 100644 --- a/pkg/commands/workload_create.go +++ b/pkg/commands/workload_create.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "strings" + "time" "github.com/spf13/cobra" apierrs "k8s.io/apimachinery/pkg/api/errors" @@ -144,7 +145,7 @@ func (opts *WorkloadCreateOptions) Exec(ctx context.Context, c *cli.Config) erro if opts.Wait || anyTail { cli.PrintPrompt(shouldPrint, c.Infof, "Waiting for workload %q to become ready...\n", opts.Name) - workers = append(workers, getReadyConditionWorker(c, workload)) + workers = append(workers, getReadyConditionWorker(c, workload, 0*time.Second)) if anyTail { workers = append(workers, getTailWorker(c, workload, opts.TailTimestamps)) diff --git a/pkg/flags/flags.go b/pkg/flags/flags.go index cdb6ef1ee..94930ac23 100644 --- a/pkg/flags/flags.go +++ b/pkg/flags/flags.go @@ -30,6 +30,7 @@ const ( ConfigFlagName = "--config" ContextFlagName = cli.ContextFlagName DebugFlagName = "--debug" + DelayTimeFlagName = "--delay" DryRunFlagName = "--dry-run" EnvFlagName = "--env" ExportFlagName = "--export"