From 42607aadc3b1d2454cdaa66aa129facb512fdaa7 Mon Sep 17 00:00:00 2001 From: Rishikesh Nair Date: Mon, 3 Apr 2023 15:17:20 +0530 Subject: [PATCH 1/2] Add support for passing multiple objects to suspend/resume commands This change adds support for running `suspend/resume` on multiple supported resources at the same time. This improves the user experience by converting ``` flux suspend ks operator && \ flux suspend ks database && \ flux suspend ks app ``` to ``` flux suspend ks operator database app ``` This works for all types of resources (Kustomizations, Sources, etc.) since it has been implemented at the `suspend.go` and `resume.go` level. When the `--wait` flag is passed to the `resume` command, then Flux will wait for all resources in parallel within a goroutine each. Each object is only processed once, even if user provided its name more than once. If suspension or resuming fails for one object, it is still carried out for the remaining objects. As a special case, the old behaviour of `resume` is retained, i.e. when only one object name is provided, `resume` waits for the object to become ready even if the `--wait` flag is not provided. In all other cases the `--wait` flag is always considered. closes #3746 closes #3793 Co-Authored-By: Max Jonas Werner Signed-off-by: Rishikesh Nair Signed-off-by: Max Jonas Werner --- cmd/flux/kustomization_test.go | 15 ++ cmd/flux/main_test.go | 6 + cmd/flux/object.go | 2 +- cmd/flux/resume.go | 179 +++++++++++++++--- cmd/flux/resume_alert.go | 1 - cmd/flux/resume_helmrelease.go | 1 - cmd/flux/resume_image_repository.go | 1 - cmd/flux/resume_image_updateauto.go | 1 - cmd/flux/resume_kustomization.go | 1 - cmd/flux/resume_receiver.go | 1 - cmd/flux/resume_source_bucket.go | 1 - cmd/flux/resume_source_chart.go | 1 - cmd/flux/resume_source_git.go | 1 - cmd/flux/resume_source_helm.go | 1 - cmd/flux/resume_source_oci.go | 1 - cmd/flux/suspend.go | 54 ++++-- .../resume_helmrelease_from_git.golden | 2 +- .../resume_kustomization_from_git.golden | 2 +- ...ustomization_from_git_multiple_args.golden | 2 + ...ization_from_git_multiple_args_wait.golden | 6 + ...ustomization_from_git_multiple_args.golden | 4 + cmd/flux/testdata/oci/resume_oci.golden | 2 +- 22 files changed, 229 insertions(+), 56 deletions(-) create mode 100644 cmd/flux/testdata/kustomization/resume_kustomization_from_git_multiple_args.golden create mode 100644 cmd/flux/testdata/kustomization/resume_kustomization_from_git_multiple_args_wait.golden create mode 100644 cmd/flux/testdata/kustomization/suspend_kustomization_from_git_multiple_args.golden diff --git a/cmd/flux/kustomization_test.go b/cmd/flux/kustomization_test.go index e49aba46..2f6176ac 100644 --- a/cmd/flux/kustomization_test.go +++ b/cmd/flux/kustomization_test.go @@ -61,11 +61,26 @@ func TestKustomizationFromGit(t *testing.T) { "testdata/kustomization/suspend_kustomization_from_git.golden", tmpl, }, + { + "suspend kustomization tkfg foo tkfg bar", + "testdata/kustomization/suspend_kustomization_from_git_multiple_args.golden", + tmpl, + }, + { + "resume kustomization tkfg foo --wait", + "testdata/kustomization/resume_kustomization_from_git_multiple_args_wait.golden", + tmpl, + }, { "resume kustomization tkfg", "testdata/kustomization/resume_kustomization_from_git.golden", tmpl, }, + { + "resume kustomization tkfg tkfg", + "testdata/kustomization/resume_kustomization_from_git_multiple_args.golden", + tmpl, + }, { "delete kustomization tkfg --silent", "testdata/kustomization/delete_kustomization_from_git.golden", diff --git a/cmd/flux/main_test.go b/cmd/flux/main_test.go index dd9a6c28..37398309 100644 --- a/cmd/flux/main_test.go +++ b/cmd/flux/main_test.go @@ -365,6 +365,12 @@ func executeTemplate(content string, templateValues map[string]string) (string, // Run the command and return the captured output. func executeCommand(cmd string) (string, error) { defer resetCmdArgs() + defer func() { + // need to set this explicitly because apparently its value isn't changed + // in subsequent executions which causes tests to fail that rely on the value + // of "Changed". + resumeCmd.PersistentFlags().Lookup("wait").Changed = false + }() args, err := shellwords.Parse(cmd) if err != nil { return "", err diff --git a/cmd/flux/object.go b/cmd/flux/object.go index 26b3a040..681d86e0 100644 --- a/cmd/flux/object.go +++ b/cmd/flux/object.go @@ -47,7 +47,7 @@ type copyable interface { deepCopyClientObject() client.Object } -// listAdapater is the analogue to adapter, but for lists; the +// listAdapter is the analogue to adapter, but for lists; the // controller runtime distinguishes between methods dealing with // objects and lists. type listAdapter interface { diff --git a/cmd/flux/resume.go b/cmd/flux/resume.go index 9c592d6c..bc27c4eb 100644 --- a/cmd/flux/resume.go +++ b/cmd/flux/resume.go @@ -19,6 +19,8 @@ package main import ( "context" "fmt" + "sort" + "sync" "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/types" @@ -59,8 +61,10 @@ type resumable interface { type resumeCommand struct { apiType - object resumable - list listResumable + client client.WithWatch + list listResumable + namespace string + shouldReconcile bool } type listResumable interface { @@ -68,6 +72,11 @@ type listResumable interface { resumeItem(i int) resumable } +type reconcileResponse struct { + resumable + err error +} + func (resume resumeCommand) run(cmd *cobra.Command, args []string) error { if len(args) < 1 && !resumeArgs.all { return fmt.Errorf("%s name is required", resume.humanKind) @@ -80,52 +89,162 @@ func (resume resumeCommand) run(cmd *cobra.Command, args []string) error { if err != nil { return err } + resume.client = kubeClient + resume.namespace = *kubeconfigArgs.Namespace - var listOpts []client.ListOption - listOpts = append(listOpts, client.InNamespace(*kubeconfigArgs.Namespace)) - if len(args) > 0 { - listOpts = append(listOpts, client.MatchingFields{ - "metadata.name": args[0], - }) - } + // require waiting for the object(s) if the user has not provided the --wait flag and gave exactly + // one object to resume. This is necessary to maintain backwards compatibility with prior versions + // of this command. Otherwise just follow the value of the --wait flag (including its default). + resume.shouldReconcile = !resumeCmd.PersistentFlags().Changed("wait") && len(args) == 1 || resumeArgs.wait - err = kubeClient.List(ctx, resume.list.asClientList(), listOpts...) + resumables, err := resume.getPatchedResumables(ctx, args) if err != nil { return err } + var wg sync.WaitGroup + wg.Add(len(resumables)) + + resultChan := make(chan reconcileResponse, len(resumables)) + for _, r := range resumables { + go func(res resumable) { + defer wg.Done() + resultChan <- resume.reconcile(ctx, res) + }(r) + } + + go func() { + defer close(resultChan) + wg.Wait() + }() + + reconcileResps := make([]reconcileResponse, 0, len(resumables)) + for c := range resultChan { + reconcileResps = append(reconcileResps, c) + } + + resume.printMessage(reconcileResps) + + return nil +} + +// getPatchedResumables returns a list of the given resumable objects that have been patched to be resumed. +// If the args slice is empty, it patches all resumable objects in the given namespace. +func (resume *resumeCommand) getPatchedResumables(ctx context.Context, args []string) ([]resumable, error) { + if len(args) < 1 { + objs, err := resume.patch(ctx, []client.ListOption{ + client.InNamespace(resume.namespace), + }) + if err != nil { + return nil, fmt.Errorf("failed patching objects: %w", err) + } + + return objs, nil + } + + var resumables []resumable + processed := make(map[string]struct{}, len(args)) + for _, arg := range args { + if _, has := processed[arg]; has { + continue // skip object that user might have provided more than once + } + processed[arg] = struct{}{} + + objs, err := resume.patch(ctx, []client.ListOption{ + client.InNamespace(resume.namespace), + client.MatchingFields{ + "metadata.name": arg, + }, + }) + if err != nil { + return nil, err + } + + resumables = append(resumables, objs...) + } + + return resumables, nil +} + +// Patches resumable objects by setting their status to unsuspended. +// Returns a slice of resumables that have been patched and any error encountered during patching. +func (resume resumeCommand) patch(ctx context.Context, listOpts []client.ListOption) ([]resumable, error) { + if err := resume.client.List(ctx, resume.list.asClientList(), listOpts...); err != nil { + return nil, err + } + if resume.list.len() == 0 { - logger.Failuref("no %s objects found in %s namespace", resume.kind, *kubeconfigArgs.Namespace) - return nil + logger.Failuref("no %s objects found in %s namespace", resume.kind, resume.namespace) + return nil, nil } + var resumables []resumable + for i := 0; i < resume.list.len(); i++ { - logger.Actionf("resuming %s %s in %s namespace", resume.humanKind, resume.list.resumeItem(i).asClientObject().GetName(), *kubeconfigArgs.Namespace) obj := resume.list.resumeItem(i) + logger.Actionf("resuming %s %s in %s namespace", resume.humanKind, obj.asClientObject().GetName(), resume.namespace) + patch := client.MergeFrom(obj.deepCopyClientObject()) obj.setUnsuspended() - if err := kubeClient.Patch(ctx, obj.asClientObject(), patch); err != nil { - return err + if err := resume.client.Patch(ctx, obj.asClientObject(), patch); err != nil { + return nil, err } + resumables = append(resumables, obj) + logger.Successf("%s resumed", resume.humanKind) + } + + return resumables, nil +} + +// Waits for resumable object to be reconciled and returns the object and any error encountered while waiting. +// Returns an empty reconcileResponse, if shouldReconcile is false. +func (resume resumeCommand) reconcile(ctx context.Context, res resumable) reconcileResponse { + if !resume.shouldReconcile { + return reconcileResponse{} + } - if resumeArgs.wait || !resumeArgs.all { - namespacedName := types.NamespacedName{ - Name: resume.list.resumeItem(i).asClientObject().GetName(), - Namespace: *kubeconfigArgs.Namespace, - } - - logger.Waitingf("waiting for %s reconciliation", resume.kind) - if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, - isReady(ctx, kubeClient, namespacedName, resume.list.resumeItem(i))); err != nil { - logger.Failuref(err.Error()) - continue - } - logger.Successf("%s reconciliation completed", resume.kind) - logger.Successf(resume.list.resumeItem(i).successMessage()) + namespacedName := types.NamespacedName{ + Name: res.asClientObject().GetName(), + Namespace: resume.namespace, + } + + logger.Waitingf("waiting for %s reconciliation", resume.kind) + + if err := wait.PollImmediate(rootArgs.pollInterval, rootArgs.timeout, + isReady(ctx, resume.client, namespacedName, res)); err != nil { + return reconcileResponse{ + resumable: res, + err: err, } } - return nil + return reconcileResponse{ + resumable: res, + err: nil, + } +} + +// Sorts the given reconcileResponses by resumable name and prints the success/error message for each response. +func (resume resumeCommand) printMessage(responses []reconcileResponse) { + sort.Slice(responses, func(i, j int) bool { + r1, r2 := responses[i], responses[j] + if r1.resumable == nil || r2.resumable == nil { + return false + } + return r1.asClientObject().GetName() <= r2.asClientObject().GetName() + }) + + // Print success/error message. + for _, r := range responses { + if r.resumable == nil { + continue + } + if r.err != nil { + logger.Failuref(r.err.Error()) + } + logger.Successf("%s %s reconciliation completed", resume.kind, r.asClientObject().GetName()) + logger.Successf(r.successMessage()) + } } diff --git a/cmd/flux/resume_alert.go b/cmd/flux/resume_alert.go index 5b86fb45..eee79658 100644 --- a/cmd/flux/resume_alert.go +++ b/cmd/flux/resume_alert.go @@ -32,7 +32,6 @@ finish the apply.`, ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)), RunE: resumeCommand{ apiType: alertType, - object: alertAdapter{¬ificationv1.Alert{}}, list: &alertListAdapter{¬ificationv1.AlertList{}}, }.run, } diff --git a/cmd/flux/resume_helmrelease.go b/cmd/flux/resume_helmrelease.go index 0de4fb90..aadf7bd0 100644 --- a/cmd/flux/resume_helmrelease.go +++ b/cmd/flux/resume_helmrelease.go @@ -35,7 +35,6 @@ finish the apply.`, ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)), RunE: resumeCommand{ apiType: helmReleaseType, - object: helmReleaseAdapter{&helmv2.HelmRelease{}}, list: helmReleaseListAdapter{&helmv2.HelmReleaseList{}}, }.run, } diff --git a/cmd/flux/resume_image_repository.go b/cmd/flux/resume_image_repository.go index a5e69c26..44bae7e0 100644 --- a/cmd/flux/resume_image_repository.go +++ b/cmd/flux/resume_image_repository.go @@ -31,7 +31,6 @@ var resumeImageRepositoryCmd = &cobra.Command{ ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)), RunE: resumeCommand{ apiType: imageRepositoryType, - object: imageRepositoryAdapter{&imagev1.ImageRepository{}}, list: imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}}, }.run, } diff --git a/cmd/flux/resume_image_updateauto.go b/cmd/flux/resume_image_updateauto.go index 8cc40bfc..f24a4833 100644 --- a/cmd/flux/resume_image_updateauto.go +++ b/cmd/flux/resume_image_updateauto.go @@ -31,7 +31,6 @@ var resumeImageUpdateCmd = &cobra.Command{ ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)), RunE: resumeCommand{ apiType: imageUpdateAutomationType, - object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}}, list: imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}}, }.run, } diff --git a/cmd/flux/resume_kustomization.go b/cmd/flux/resume_kustomization.go index fb8da244..a7cc07e2 100644 --- a/cmd/flux/resume_kustomization.go +++ b/cmd/flux/resume_kustomization.go @@ -35,7 +35,6 @@ finish the apply.`, ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)), RunE: resumeCommand{ apiType: kustomizationType, - object: kustomizationAdapter{&kustomizev1.Kustomization{}}, list: kustomizationListAdapter{&kustomizev1.KustomizationList{}}, }.run, } diff --git a/cmd/flux/resume_receiver.go b/cmd/flux/resume_receiver.go index eef0d1e0..e8c3fefa 100644 --- a/cmd/flux/resume_receiver.go +++ b/cmd/flux/resume_receiver.go @@ -32,7 +32,6 @@ finish the apply.`, ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)), RunE: resumeCommand{ apiType: receiverType, - object: receiverAdapter{¬ificationv1.Receiver{}}, list: receiverListAdapter{¬ificationv1.ReceiverList{}}, }.run, } diff --git a/cmd/flux/resume_source_bucket.go b/cmd/flux/resume_source_bucket.go index 4fd32bc2..51188cfe 100644 --- a/cmd/flux/resume_source_bucket.go +++ b/cmd/flux/resume_source_bucket.go @@ -31,7 +31,6 @@ var resumeSourceBucketCmd = &cobra.Command{ ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)), RunE: resumeCommand{ apiType: bucketType, - object: bucketAdapter{&sourcev1.Bucket{}}, list: bucketListAdapter{&sourcev1.BucketList{}}, }.run, } diff --git a/cmd/flux/resume_source_chart.go b/cmd/flux/resume_source_chart.go index bbe895c1..2fe50f9d 100644 --- a/cmd/flux/resume_source_chart.go +++ b/cmd/flux/resume_source_chart.go @@ -33,7 +33,6 @@ var resumeSourceHelmChartCmd = &cobra.Command{ ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)), RunE: resumeCommand{ apiType: helmChartType, - object: &helmChartAdapter{&sourcev1.HelmChart{}}, list: &helmChartListAdapter{&sourcev1.HelmChartList{}}, }.run, } diff --git a/cmd/flux/resume_source_git.go b/cmd/flux/resume_source_git.go index 00655285..6918eec0 100644 --- a/cmd/flux/resume_source_git.go +++ b/cmd/flux/resume_source_git.go @@ -31,7 +31,6 @@ var resumeSourceGitCmd = &cobra.Command{ ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)), RunE: resumeCommand{ apiType: gitRepositoryType, - object: gitRepositoryAdapter{&sourcev1.GitRepository{}}, list: gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}}, }.run, } diff --git a/cmd/flux/resume_source_helm.go b/cmd/flux/resume_source_helm.go index 6e80895f..65670fbc 100644 --- a/cmd/flux/resume_source_helm.go +++ b/cmd/flux/resume_source_helm.go @@ -31,7 +31,6 @@ var resumeSourceHelmCmd = &cobra.Command{ ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)), RunE: resumeCommand{ apiType: helmRepositoryType, - object: helmRepositoryAdapter{&sourcev1.HelmRepository{}}, list: helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}}, }.run, } diff --git a/cmd/flux/resume_source_oci.go b/cmd/flux/resume_source_oci.go index 3c121c47..979d30bd 100644 --- a/cmd/flux/resume_source_oci.go +++ b/cmd/flux/resume_source_oci.go @@ -31,7 +31,6 @@ var resumeSourceOCIRepositoryCmd = &cobra.Command{ ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)), RunE: resumeCommand{ apiType: ociRepositoryType, - object: ociRepositoryAdapter{&sourcev1.OCIRepository{}}, list: ociRepositoryListAdapter{&sourcev1.OCIRepositoryList{}}, }.run, } diff --git a/cmd/flux/suspend.go b/cmd/flux/suspend.go index a7abe356..e0023c7e 100644 --- a/cmd/flux/suspend.go +++ b/cmd/flux/suspend.go @@ -18,6 +18,7 @@ package main import ( "context" + "errors" "fmt" "github.com/spf13/cobra" @@ -75,22 +76,53 @@ func (suspend suspendCommand) run(cmd *cobra.Command, args []string) error { return err } - var listOpts []client.ListOption - listOpts = append(listOpts, client.InNamespace(*kubeconfigArgs.Namespace)) - if len(args) > 0 { - listOpts = append(listOpts, client.MatchingFields{ - "metadata.name": args[0], - }) + if len(args) < 1 && suspendArgs.all { + listOpts := []client.ListOption{ + client.InNamespace(*kubeconfigArgs.Namespace), + } + + if err := suspend.patch(ctx, kubeClient, listOpts); err != nil { + return err + } + + return nil } - err = kubeClient.List(ctx, suspend.list.asClientList(), listOpts...) - if err != nil { + processed := make(map[string]struct{}, len(args)) + for _, arg := range args { + if _, has := processed[arg]; has { + continue // skip object that user might have provided more than once + } + processed[arg] = struct{}{} + + listOpts := []client.ListOption{ + client.InNamespace(*kubeconfigArgs.Namespace), + client.MatchingFields{ + "metadata.name": arg, + }, + } + + if err := suspend.patch(ctx, kubeClient, listOpts); err != nil { + if err == ErrNoObjectsFound { + logger.Failuref("%s %s not found in %s namespace", suspend.kind, arg, *kubeconfigArgs.Namespace) + } else { + logger.Failuref("failed suspending %s %s in %s namespace: %s", suspend.kind, arg, *kubeconfigArgs.Namespace, err.Error()) + } + } + } + + return nil +} + +var ErrNoObjectsFound = errors.New("no objects found") + +func (suspend suspendCommand) patch(ctx context.Context, kubeClient client.WithWatch, listOpts []client.ListOption) error { + if err := kubeClient.List(ctx, suspend.list.asClientList(), listOpts...); err != nil { return err } if suspend.list.len() == 0 { - logger.Failuref("no %s objects found in %s namespace", suspend.kind, *kubeconfigArgs.Namespace) - return nil + return ErrNoObjectsFound } for i := 0; i < suspend.list.len(); i++ { @@ -102,8 +134,8 @@ func (suspend suspendCommand) run(cmd *cobra.Command, args []string) error { if err := kubeClient.Patch(ctx, obj.asClientObject(), patch); err != nil { return err } - logger.Successf("%s suspended", suspend.humanKind) + logger.Successf("%s suspended", suspend.humanKind) } return nil diff --git a/cmd/flux/testdata/helmrelease/resume_helmrelease_from_git.golden b/cmd/flux/testdata/helmrelease/resume_helmrelease_from_git.golden index 4369d16a..0ceaf0d4 100644 --- a/cmd/flux/testdata/helmrelease/resume_helmrelease_from_git.golden +++ b/cmd/flux/testdata/helmrelease/resume_helmrelease_from_git.golden @@ -1,5 +1,5 @@ ► resuming helmrelease thrfg in {{ .ns }} namespace ✔ helmrelease resumed ◎ waiting for HelmRelease reconciliation -✔ HelmRelease reconciliation completed +✔ HelmRelease thrfg reconciliation completed ✔ applied revision 6.3.5 diff --git a/cmd/flux/testdata/kustomization/resume_kustomization_from_git.golden b/cmd/flux/testdata/kustomization/resume_kustomization_from_git.golden index 93d8a012..f8492110 100644 --- a/cmd/flux/testdata/kustomization/resume_kustomization_from_git.golden +++ b/cmd/flux/testdata/kustomization/resume_kustomization_from_git.golden @@ -1,5 +1,5 @@ ► resuming kustomization tkfg in {{ .ns }} namespace ✔ kustomization resumed ◎ waiting for Kustomization reconciliation -✔ Kustomization reconciliation completed +✔ Kustomization tkfg reconciliation completed ✔ applied revision 6.3.5@sha1:67e2c98a60dc92283531412a9e604dd4bae005a9 diff --git a/cmd/flux/testdata/kustomization/resume_kustomization_from_git_multiple_args.golden b/cmd/flux/testdata/kustomization/resume_kustomization_from_git_multiple_args.golden new file mode 100644 index 00000000..318f29f2 --- /dev/null +++ b/cmd/flux/testdata/kustomization/resume_kustomization_from_git_multiple_args.golden @@ -0,0 +1,2 @@ +► resuming kustomization tkfg in {{ .ns }} namespace +✔ kustomization resumed diff --git a/cmd/flux/testdata/kustomization/resume_kustomization_from_git_multiple_args_wait.golden b/cmd/flux/testdata/kustomization/resume_kustomization_from_git_multiple_args_wait.golden new file mode 100644 index 00000000..e0775140 --- /dev/null +++ b/cmd/flux/testdata/kustomization/resume_kustomization_from_git_multiple_args_wait.golden @@ -0,0 +1,6 @@ +► resuming kustomization tkfg in {{ .ns }} namespace +✔ kustomization resumed +✗ no Kustomization objects found in {{ .ns }} namespace +◎ waiting for Kustomization reconciliation +✔ Kustomization tkfg reconciliation completed +✔ applied revision 6.3.5@sha1:67e2c98a60dc92283531412a9e604dd4bae005a9 diff --git a/cmd/flux/testdata/kustomization/suspend_kustomization_from_git_multiple_args.golden b/cmd/flux/testdata/kustomization/suspend_kustomization_from_git_multiple_args.golden new file mode 100644 index 00000000..42abd122 --- /dev/null +++ b/cmd/flux/testdata/kustomization/suspend_kustomization_from_git_multiple_args.golden @@ -0,0 +1,4 @@ +► suspending kustomization tkfg in {{ .ns }} namespace +✔ kustomization suspended +✗ Kustomization foo not found in {{ .ns }} namespace +✗ Kustomization bar not found in {{ .ns }} namespace diff --git a/cmd/flux/testdata/oci/resume_oci.golden b/cmd/flux/testdata/oci/resume_oci.golden index 00e628d5..34a0deba 100644 --- a/cmd/flux/testdata/oci/resume_oci.golden +++ b/cmd/flux/testdata/oci/resume_oci.golden @@ -1,5 +1,5 @@ ► resuming source oci thrfg in {{ .ns }} namespace ✔ source oci resumed ◎ waiting for OCIRepository reconciliation -✔ OCIRepository reconciliation completed +✔ OCIRepository thrfg reconciliation completed ✔ fetched revision 6.3.5@sha256:6c959c51ccbb952e5fe4737563338a0aaf975675dcf812912cf09e5463181871 From 3580d4ff85d7e4f97fd84c7a07d0ee026b3bf8ff Mon Sep 17 00:00:00 2001 From: Rishikesh Nair Date: Thu, 29 Jun 2023 12:50:38 +0530 Subject: [PATCH 2/2] Add examples for resuming/suspending multiple objects Signed-off-by: Rishikesh Nair --- cmd/flux/resume_alert.go | 5 ++++- cmd/flux/resume_helmrelease.go | 5 ++++- cmd/flux/resume_image_repository.go | 5 ++++- cmd/flux/resume_image_updateauto.go | 5 ++++- cmd/flux/resume_kustomization.go | 5 ++++- cmd/flux/resume_receiver.go | 5 ++++- cmd/flux/resume_source_bucket.go | 5 ++++- cmd/flux/resume_source_chart.go | 5 ++++- cmd/flux/resume_source_git.go | 5 ++++- cmd/flux/resume_source_helm.go | 5 ++++- cmd/flux/resume_source_oci.go | 5 ++++- cmd/flux/suspend_alert.go | 5 ++++- cmd/flux/suspend_helmrelease.go | 5 ++++- cmd/flux/suspend_image_repository.go | 5 ++++- cmd/flux/suspend_image_updateauto.go | 5 ++++- cmd/flux/suspend_kustomization.go | 5 ++++- cmd/flux/suspend_receiver.go | 5 ++++- cmd/flux/suspend_source_bucket.go | 5 ++++- cmd/flux/suspend_source_chart.go | 5 ++++- cmd/flux/suspend_source_git.go | 5 ++++- cmd/flux/suspend_source_helm.go | 5 ++++- cmd/flux/suspend_source_oci.go | 5 ++++- 22 files changed, 88 insertions(+), 22 deletions(-) diff --git a/cmd/flux/resume_alert.go b/cmd/flux/resume_alert.go index eee79658..713aebb3 100644 --- a/cmd/flux/resume_alert.go +++ b/cmd/flux/resume_alert.go @@ -28,7 +28,10 @@ var resumeAlertCmd = &cobra.Command{ Long: `The resume command marks a previously suspended Alert resource for reconciliation and waits for it to finish the apply.`, Example: ` # Resume reconciliation for an existing Alert - flux resume alert main`, + flux resume alert main + + # Resume reconciliation for multiple Alerts + flux resume alert main-1 main-2`, ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)), RunE: resumeCommand{ apiType: alertType, diff --git a/cmd/flux/resume_helmrelease.go b/cmd/flux/resume_helmrelease.go index aadf7bd0..b058d273 100644 --- a/cmd/flux/resume_helmrelease.go +++ b/cmd/flux/resume_helmrelease.go @@ -31,7 +31,10 @@ var resumeHrCmd = &cobra.Command{ Long: `The resume command marks a previously suspended HelmRelease resource for reconciliation and waits for it to finish the apply.`, Example: ` # Resume reconciliation for an existing Helm release - flux resume hr podinfo`, + flux resume hr podinfo + + # Resume reconciliation for multiple Helm releases + flux resume hr podinfo-1 podinfo-2`, ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)), RunE: resumeCommand{ apiType: helmReleaseType, diff --git a/cmd/flux/resume_image_repository.go b/cmd/flux/resume_image_repository.go index 44bae7e0..a9ab36cd 100644 --- a/cmd/flux/resume_image_repository.go +++ b/cmd/flux/resume_image_repository.go @@ -27,7 +27,10 @@ var resumeImageRepositoryCmd = &cobra.Command{ Short: "Resume a suspended ImageRepository", Long: `The resume command marks a previously suspended ImageRepository resource for reconciliation and waits for it to finish.`, Example: ` # Resume reconciliation for an existing ImageRepository - flux resume image repository alpine`, + flux resume image repository alpine + + # Resume reconciliation for multiple ImageRepositories + flux resume image repository alpine-1 alpine-2`, ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)), RunE: resumeCommand{ apiType: imageRepositoryType, diff --git a/cmd/flux/resume_image_updateauto.go b/cmd/flux/resume_image_updateauto.go index f24a4833..d50fddf7 100644 --- a/cmd/flux/resume_image_updateauto.go +++ b/cmd/flux/resume_image_updateauto.go @@ -27,7 +27,10 @@ var resumeImageUpdateCmd = &cobra.Command{ Short: "Resume a suspended ImageUpdateAutomation", Long: `The resume command marks a previously suspended ImageUpdateAutomation resource for reconciliation and waits for it to finish.`, Example: ` # Resume reconciliation for an existing ImageUpdateAutomation - flux resume image update latest-images`, + flux resume image update latest-images + + # Resume reconciliation for multiple ImageUpdateAutomations + flux resume image update latest-images-1 latest-images-2`, ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)), RunE: resumeCommand{ apiType: imageUpdateAutomationType, diff --git a/cmd/flux/resume_kustomization.go b/cmd/flux/resume_kustomization.go index a7cc07e2..a03aed62 100644 --- a/cmd/flux/resume_kustomization.go +++ b/cmd/flux/resume_kustomization.go @@ -31,7 +31,10 @@ var resumeKsCmd = &cobra.Command{ Long: `The resume command marks a previously suspended Kustomization resource for reconciliation and waits for it to finish the apply.`, Example: ` # Resume reconciliation for an existing Kustomization - flux resume ks podinfo`, + flux resume ks podinfo + + # Resume reconciliation for multiple Kustomizations + flux resume ks podinfo-1 podinfo-2`, ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)), RunE: resumeCommand{ apiType: kustomizationType, diff --git a/cmd/flux/resume_receiver.go b/cmd/flux/resume_receiver.go index e8c3fefa..c99bd755 100644 --- a/cmd/flux/resume_receiver.go +++ b/cmd/flux/resume_receiver.go @@ -28,7 +28,10 @@ var resumeReceiverCmd = &cobra.Command{ Long: `The resume command marks a previously suspended Receiver resource for reconciliation and waits for it to finish the apply.`, Example: ` # Resume reconciliation for an existing Receiver - flux resume receiver main`, + flux resume receiver main + + # Resume reconciliation for multiple Receivers + flux resume receiver main-1 main-2`, ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)), RunE: resumeCommand{ apiType: receiverType, diff --git a/cmd/flux/resume_source_bucket.go b/cmd/flux/resume_source_bucket.go index 51188cfe..ea1fe37d 100644 --- a/cmd/flux/resume_source_bucket.go +++ b/cmd/flux/resume_source_bucket.go @@ -27,7 +27,10 @@ var resumeSourceBucketCmd = &cobra.Command{ Short: "Resume a suspended Bucket", Long: `The resume command marks a previously suspended Bucket resource for reconciliation and waits for it to finish.`, Example: ` # Resume reconciliation for an existing Bucket - flux resume source bucket podinfo`, + flux resume source bucket podinfo + + # Resume reconciliation for multiple Buckets + flux resume source bucket podinfo-1 podinfo-2`, ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)), RunE: resumeCommand{ apiType: bucketType, diff --git a/cmd/flux/resume_source_chart.go b/cmd/flux/resume_source_chart.go index 2fe50f9d..6322f06f 100644 --- a/cmd/flux/resume_source_chart.go +++ b/cmd/flux/resume_source_chart.go @@ -29,7 +29,10 @@ var resumeSourceHelmChartCmd = &cobra.Command{ Short: "Resume a suspended HelmChart", Long: `The resume command marks a previously suspended HelmChart resource for reconciliation and waits for it to finish.`, Example: ` # Resume reconciliation for an existing HelmChart - flux resume source chart podinfo`, + flux resume source chart podinfo + + # Resume reconciliation for multiple HelmCharts + flux resume source chart podinfo-1 podinfo-2`, ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)), RunE: resumeCommand{ apiType: helmChartType, diff --git a/cmd/flux/resume_source_git.go b/cmd/flux/resume_source_git.go index 6918eec0..751714a4 100644 --- a/cmd/flux/resume_source_git.go +++ b/cmd/flux/resume_source_git.go @@ -27,7 +27,10 @@ var resumeSourceGitCmd = &cobra.Command{ Short: "Resume a suspended GitRepository", Long: `The resume command marks a previously suspended GitRepository resource for reconciliation and waits for it to finish.`, Example: ` # Resume reconciliation for an existing GitRepository - flux resume source git podinfo`, + flux resume source git podinfo + + # Resume reconciliation for multiple GitRepositories + flux resume source git podinfo-1 podinfo-2`, ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)), RunE: resumeCommand{ apiType: gitRepositoryType, diff --git a/cmd/flux/resume_source_helm.go b/cmd/flux/resume_source_helm.go index 65670fbc..0ac641eb 100644 --- a/cmd/flux/resume_source_helm.go +++ b/cmd/flux/resume_source_helm.go @@ -27,7 +27,10 @@ var resumeSourceHelmCmd = &cobra.Command{ Short: "Resume a suspended HelmRepository", Long: `The resume command marks a previously suspended HelmRepository resource for reconciliation and waits for it to finish.`, Example: ` # Resume reconciliation for an existing HelmRepository - flux resume source helm bitnami`, + flux resume source helm bitnami + + # Resume reconciliation for multiple HelmRepositories + flux resume source helm bitnami-1 bitnami-2`, ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)), RunE: resumeCommand{ apiType: helmRepositoryType, diff --git a/cmd/flux/resume_source_oci.go b/cmd/flux/resume_source_oci.go index 979d30bd..04b20a4b 100644 --- a/cmd/flux/resume_source_oci.go +++ b/cmd/flux/resume_source_oci.go @@ -27,7 +27,10 @@ var resumeSourceOCIRepositoryCmd = &cobra.Command{ Short: "Resume a suspended OCIRepository", Long: `The resume command marks a previously suspended OCIRepository resource for reconciliation and waits for it to finish.`, Example: ` # Resume reconciliation for an existing OCIRepository - flux resume source oci podinfo`, + flux resume source oci podinfo + + # Resume reconciliation for multiple OCIRepositories + flux resume source oci podinfo-1 podinfo-2`, ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)), RunE: resumeCommand{ apiType: ociRepositoryType, diff --git a/cmd/flux/suspend_alert.go b/cmd/flux/suspend_alert.go index 63231018..17cdd801 100644 --- a/cmd/flux/suspend_alert.go +++ b/cmd/flux/suspend_alert.go @@ -27,7 +27,10 @@ var suspendAlertCmd = &cobra.Command{ Short: "Suspend reconciliation of Alert", Long: `The suspend command disables the reconciliation of a Alert resource.`, Example: ` # Suspend reconciliation for an existing Alert - flux suspend alert main`, + flux suspend alert main + + # Suspend reconciliation for multiple Alerts + flux suspend alert main-1 main-2`, ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)), RunE: suspendCommand{ apiType: alertType, diff --git a/cmd/flux/suspend_helmrelease.go b/cmd/flux/suspend_helmrelease.go index 9e2fccca..5872c8b4 100644 --- a/cmd/flux/suspend_helmrelease.go +++ b/cmd/flux/suspend_helmrelease.go @@ -28,7 +28,10 @@ var suspendHrCmd = &cobra.Command{ Short: "Suspend reconciliation of HelmRelease", Long: `The suspend command disables the reconciliation of a HelmRelease resource.`, Example: ` # Suspend reconciliation for an existing Helm release - flux suspend hr podinfo`, + flux suspend hr podinfo + + # Suspend reconciliation for multiple Helm releases + flux suspend hr podinfo-1 podinfo-2 `, ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)), RunE: suspendCommand{ apiType: helmReleaseType, diff --git a/cmd/flux/suspend_image_repository.go b/cmd/flux/suspend_image_repository.go index 47288c35..c6e562d1 100644 --- a/cmd/flux/suspend_image_repository.go +++ b/cmd/flux/suspend_image_repository.go @@ -27,7 +27,10 @@ var suspendImageRepositoryCmd = &cobra.Command{ Short: "Suspend reconciliation of an ImageRepository", Long: `The suspend image repository command disables the reconciliation of a ImageRepository resource.`, Example: ` # Suspend reconciliation for an existing ImageRepository - flux suspend image repository alpine`, + flux suspend image repository alpine + + # Suspend reconciliation for multiple ImageRepositories + flux suspend image repository alpine-1 alpine-2`, ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)), RunE: suspendCommand{ apiType: imageRepositoryType, diff --git a/cmd/flux/suspend_image_updateauto.go b/cmd/flux/suspend_image_updateauto.go index c198171e..1c8f11ef 100644 --- a/cmd/flux/suspend_image_updateauto.go +++ b/cmd/flux/suspend_image_updateauto.go @@ -27,7 +27,10 @@ var suspendImageUpdateCmd = &cobra.Command{ Short: "Suspend reconciliation of an ImageUpdateAutomation", Long: `The suspend image update command disables the reconciliation of a ImageUpdateAutomation resource.`, Example: ` # Suspend reconciliation for an existing ImageUpdateAutomation - flux suspend image update latest-images`, + flux suspend image update latest-images + + # Suspend reconciliation for multiple ImageUpdateAutomations + flux suspend image update latest-images-1 latest-images-2`, ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)), RunE: suspendCommand{ apiType: imageUpdateAutomationType, diff --git a/cmd/flux/suspend_kustomization.go b/cmd/flux/suspend_kustomization.go index d9d77e8c..ac6c72e1 100644 --- a/cmd/flux/suspend_kustomization.go +++ b/cmd/flux/suspend_kustomization.go @@ -28,7 +28,10 @@ var suspendKsCmd = &cobra.Command{ Short: "Suspend reconciliation of Kustomization", Long: `The suspend command disables the reconciliation of a Kustomization resource.`, Example: ` # Suspend reconciliation for an existing Kustomization - flux suspend ks podinfo`, + flux suspend ks podinfo + + # Suspend reconciliation for multiple Kustomizations + flux suspend ks podinfo-1 podinfo-2`, ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)), RunE: suspendCommand{ apiType: kustomizationType, diff --git a/cmd/flux/suspend_receiver.go b/cmd/flux/suspend_receiver.go index e0110be1..7f2e6dd7 100644 --- a/cmd/flux/suspend_receiver.go +++ b/cmd/flux/suspend_receiver.go @@ -27,7 +27,10 @@ var suspendReceiverCmd = &cobra.Command{ Short: "Suspend reconciliation of Receiver", Long: `The suspend command disables the reconciliation of a Receiver resource.`, Example: ` # Suspend reconciliation for an existing Receiver - flux suspend receiver main`, + flux suspend receiver main + + # Suspend reconciliation for multiple Receivers + flux suspend receiver main-1 main-2`, ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)), RunE: suspendCommand{ apiType: receiverType, diff --git a/cmd/flux/suspend_source_bucket.go b/cmd/flux/suspend_source_bucket.go index e38d5d47..7fdcfced 100644 --- a/cmd/flux/suspend_source_bucket.go +++ b/cmd/flux/suspend_source_bucket.go @@ -27,7 +27,10 @@ var suspendSourceBucketCmd = &cobra.Command{ Short: "Suspend reconciliation of a Bucket", Long: `The suspend command disables the reconciliation of a Bucket resource.`, Example: ` # Suspend reconciliation for an existing Bucket - flux suspend source bucket podinfo`, + flux suspend source bucket podinfo + + # Suspend reconciliation for multiple Buckets + flux suspend source bucket podinfo-1 podinfo-2`, ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)), RunE: suspendCommand{ apiType: bucketType, diff --git a/cmd/flux/suspend_source_chart.go b/cmd/flux/suspend_source_chart.go index e99defcb..e91327e8 100644 --- a/cmd/flux/suspend_source_chart.go +++ b/cmd/flux/suspend_source_chart.go @@ -27,7 +27,10 @@ var suspendSourceHelmChartCmd = &cobra.Command{ Short: "Suspend reconciliation of a HelmChart", Long: `The suspend command disables the reconciliation of a HelmChart resource.`, Example: ` # Suspend reconciliation for an existing HelmChart - flux suspend source chart podinfo`, + flux suspend source chart podinfo + + # Suspend reconciliation for multiple HelmCharts + flux suspend source chart podinfo-1 podinfo-2`, ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)), RunE: suspendCommand{ apiType: helmChartType, diff --git a/cmd/flux/suspend_source_git.go b/cmd/flux/suspend_source_git.go index bb1281fd..4decf796 100644 --- a/cmd/flux/suspend_source_git.go +++ b/cmd/flux/suspend_source_git.go @@ -27,7 +27,10 @@ var suspendSourceGitCmd = &cobra.Command{ Short: "Suspend reconciliation of a GitRepository", Long: `The suspend command disables the reconciliation of a GitRepository resource.`, Example: ` # Suspend reconciliation for an existing GitRepository - flux suspend source git podinfo`, + flux suspend source git podinfo + + # Suspend reconciliation for multiple GitRepositories + flux suspend source git podinfo-1 podinfo-2`, ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)), RunE: suspendCommand{ apiType: gitRepositoryType, diff --git a/cmd/flux/suspend_source_helm.go b/cmd/flux/suspend_source_helm.go index 6d49040d..22598c64 100644 --- a/cmd/flux/suspend_source_helm.go +++ b/cmd/flux/suspend_source_helm.go @@ -27,7 +27,10 @@ var suspendSourceHelmCmd = &cobra.Command{ Short: "Suspend reconciliation of a HelmRepository", Long: `The suspend command disables the reconciliation of a HelmRepository resource.`, Example: ` # Suspend reconciliation for an existing HelmRepository - flux suspend source helm bitnami`, + flux suspend source helm bitnami + + # Suspend reconciliation for multiple HelmRepositories + flux suspend source helm bitnami-1 bitnami-2 `, ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)), RunE: suspendCommand{ apiType: helmRepositoryType, diff --git a/cmd/flux/suspend_source_oci.go b/cmd/flux/suspend_source_oci.go index 10dd6fff..3b8e36d6 100644 --- a/cmd/flux/suspend_source_oci.go +++ b/cmd/flux/suspend_source_oci.go @@ -27,7 +27,10 @@ var suspendSourceOCIRepositoryCmd = &cobra.Command{ Short: "Suspend reconciliation of an OCIRepository", Long: `The suspend command disables the reconciliation of an OCIRepository resource.`, Example: ` # Suspend reconciliation for an existing OCIRepository - flux suspend source oci podinfo`, + flux suspend source oci podinfo + + # Suspend reconciliation for multiple OCIRepositories + flux suspend source oci podinfo-1 podinfo-2`, ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.OCIRepositoryKind)), RunE: suspendCommand{ apiType: ociRepositoryType,