From 254cc131aec830caf9c85b2e2aeb8362931b0d5a Mon Sep 17 00:00:00 2001
From: Kazuki Suda <kazuki.suda@gmail.com>
Date: Fri, 17 Sep 2021 18:28:41 +0900
Subject: [PATCH] Add dynamic completion suppport

This commit adds dynamic completion support for the following commands
and flags:

- `flux delete ...` command
- `flux export ...` command
- `flux get ...` command
- `flux reconcile ...` command
- `flux resume ...` command
- `flux suspend ...` command
- `--namespace` flag
- `--context` flag

Signed-off-by: Kazuki Suda <kazuki.suda@gmail.com>
---
 cmd/flux/completion.go                 | 85 ++++++++++++++++++++++++++
 cmd/flux/delete_alert.go               |  1 +
 cmd/flux/delete_alertprovider.go       |  1 +
 cmd/flux/delete_helmrelease.go         |  1 +
 cmd/flux/delete_image_policy.go        |  1 +
 cmd/flux/delete_image_repository.go    |  1 +
 cmd/flux/delete_image_update.go        |  1 +
 cmd/flux/delete_kustomization.go       |  1 +
 cmd/flux/delete_receiver.go            |  1 +
 cmd/flux/delete_source_bucket.go       |  1 +
 cmd/flux/delete_source_git.go          |  1 +
 cmd/flux/delete_source_helm.go         |  1 +
 cmd/flux/export_alert.go               |  1 +
 cmd/flux/export_alertprovider.go       |  1 +
 cmd/flux/export_helmrelease.go         |  1 +
 cmd/flux/export_image_policy.go        |  1 +
 cmd/flux/export_image_repository.go    |  1 +
 cmd/flux/export_image_update.go        |  1 +
 cmd/flux/export_kustomization.go       |  1 +
 cmd/flux/export_receiver.go            |  1 +
 cmd/flux/export_source_bucket.go       |  1 +
 cmd/flux/export_source_git.go          |  1 +
 cmd/flux/export_source_helm.go         |  1 +
 cmd/flux/get_alert.go                  |  1 +
 cmd/flux/get_alertprovider.go          |  1 +
 cmd/flux/get_helmrelease.go            |  1 +
 cmd/flux/get_image_policy.go           |  1 +
 cmd/flux/get_image_repository.go       |  1 +
 cmd/flux/get_image_update.go           |  1 +
 cmd/flux/get_kustomization.go          |  1 +
 cmd/flux/get_receiver.go               |  1 +
 cmd/flux/get_source_bucket.go          |  1 +
 cmd/flux/get_source_chart.go           |  1 +
 cmd/flux/get_source_git.go             |  1 +
 cmd/flux/get_source_helm.go            |  1 +
 cmd/flux/main.go                       |  5 ++
 cmd/flux/reconcile_alert.go            |  1 +
 cmd/flux/reconcile_alertprovider.go    |  3 +-
 cmd/flux/reconcile_helmrelease.go      |  1 +
 cmd/flux/reconcile_image_repository.go |  1 +
 cmd/flux/reconcile_image_updateauto.go |  1 +
 cmd/flux/reconcile_kustomization.go    |  1 +
 cmd/flux/reconcile_receiver.go         |  3 +-
 cmd/flux/reconcile_source_bucket.go    |  1 +
 cmd/flux/reconcile_source_git.go       |  1 +
 cmd/flux/reconcile_source_helm.go      |  1 +
 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/suspend_alert.go              |  1 +
 cmd/flux/suspend_helmrelease.go        |  1 +
 cmd/flux/suspend_image_repository.go   |  1 +
 cmd/flux/suspend_image_updateauto.go   |  1 +
 cmd/flux/suspend_kustomization.go      |  1 +
 cmd/flux/suspend_receiver.go           |  1 +
 cmd/flux/suspend_source_bucket.go      |  1 +
 cmd/flux/suspend_source_chart.go       |  1 +
 cmd/flux/suspend_source_git.go         |  1 +
 cmd/flux/suspend_source_helm.go        |  1 +
 internal/utils/utils.go                |  9 ++-
 67 files changed, 162 insertions(+), 5 deletions(-)

diff --git a/cmd/flux/completion.go b/cmd/flux/completion.go
index 08406ac7..077d768e 100644
--- a/cmd/flux/completion.go
+++ b/cmd/flux/completion.go
@@ -17,7 +17,18 @@ limitations under the License.
 package main
 
 import (
+	"context"
+	"strings"
+
+	"github.com/fluxcd/flux2/internal/utils"
 	"github.com/spf13/cobra"
+	"k8s.io/apimachinery/pkg/api/meta"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/client-go/discovery"
+	memory "k8s.io/client-go/discovery/cached"
+	"k8s.io/client-go/dynamic"
+	"k8s.io/client-go/restmapper"
 )
 
 var completionCmd = &cobra.Command{
@@ -29,3 +40,77 @@ var completionCmd = &cobra.Command{
 func init() {
 	rootCmd.AddCommand(completionCmd)
 }
+
+func contextsCompletionFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	rawConfig, err := utils.ClientConfig(rootArgs.kubeconfig, rootArgs.kubecontext).RawConfig()
+	if err != nil {
+		return completionError(err)
+	}
+
+	var comps []string
+
+	for name := range rawConfig.Contexts {
+		if strings.HasPrefix(name, toComplete) {
+			comps = append(comps, name)
+		}
+	}
+
+	return comps, cobra.ShellCompDirectiveNoFileComp
+}
+
+func resourceNamesCompletionFunc(gvk schema.GroupVersionKind) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
+		defer cancel()
+
+		cfg, err := utils.KubeConfig(rootArgs.kubeconfig, rootArgs.kubecontext)
+		if err != nil {
+			return completionError(err)
+		}
+
+		dc, err := discovery.NewDiscoveryClientForConfig(cfg)
+		if err != nil {
+			return completionError(err)
+		}
+		mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(dc))
+
+		mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
+		if err != nil {
+			return completionError(err)
+		}
+
+		client, err := dynamic.NewForConfig(cfg)
+		if err != nil {
+			return completionError(err)
+		}
+
+		var dr dynamic.ResourceInterface
+		if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
+			dr = client.Resource(mapping.Resource).Namespace(rootArgs.namespace)
+		} else {
+			dr = client.Resource(mapping.Resource)
+		}
+
+		list, err := dr.List(ctx, metav1.ListOptions{})
+		if err != nil {
+			return completionError(err)
+		}
+
+		var comps []string
+
+		for _, item := range list.Items {
+			name := item.GetName()
+
+			if strings.HasPrefix(name, toComplete) {
+				comps = append(comps, name)
+			}
+		}
+
+		return comps, cobra.ShellCompDirectiveNoFileComp
+	}
+}
+
+func completionError(err error) ([]string, cobra.ShellCompDirective) {
+	cobra.CompError(err.Error())
+	return nil, cobra.ShellCompDirectiveError
+}
diff --git a/cmd/flux/delete_alert.go b/cmd/flux/delete_alert.go
index 3b1ac9ce..c9e6f1ab 100644
--- a/cmd/flux/delete_alert.go
+++ b/cmd/flux/delete_alert.go
@@ -28,6 +28,7 @@ var deleteAlertCmd = &cobra.Command{
 	Long:  "The delete alert command removes the given Alert from the cluster.",
 	Example: `  # Delete an Alert and the Kubernetes resources created by it
   flux delete alert main`,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
 	RunE: deleteCommand{
 		apiType: alertType,
 		object:  universalAdapter{&notificationv1.Alert{}},
diff --git a/cmd/flux/delete_alertprovider.go b/cmd/flux/delete_alertprovider.go
index c1de6fea..08f0526c 100644
--- a/cmd/flux/delete_alertprovider.go
+++ b/cmd/flux/delete_alertprovider.go
@@ -28,6 +28,7 @@ var deleteAlertProviderCmd = &cobra.Command{
 	Long:  "The delete alert-provider command removes the given Provider from the cluster.",
 	Example: `  # Delete a Provider and the Kubernetes resources created by it
   flux delete alert-provider slack`,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
 	RunE: deleteCommand{
 		apiType: alertProviderType,
 		object:  universalAdapter{&notificationv1.Provider{}},
diff --git a/cmd/flux/delete_helmrelease.go b/cmd/flux/delete_helmrelease.go
index 05ad157a..c721d3dc 100644
--- a/cmd/flux/delete_helmrelease.go
+++ b/cmd/flux/delete_helmrelease.go
@@ -29,6 +29,7 @@ var deleteHelmReleaseCmd = &cobra.Command{
 	Long:    "The delete helmrelease command removes the given HelmRelease from the cluster.",
 	Example: `  # Delete a Helm release and the Kubernetes resources created by it
   flux delete hr podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
 	RunE: deleteCommand{
 		apiType: helmReleaseType,
 		object:  universalAdapter{&helmv2.HelmRelease{}},
diff --git a/cmd/flux/delete_image_policy.go b/cmd/flux/delete_image_policy.go
index a11b7b59..e86924da 100644
--- a/cmd/flux/delete_image_policy.go
+++ b/cmd/flux/delete_image_policy.go
@@ -28,6 +28,7 @@ var deleteImagePolicyCmd = &cobra.Command{
 	Long:  "The delete image policy command deletes the given ImagePolicy from the cluster.",
 	Example: `  # Delete an image policy
   flux delete image policy alpine3.x`,
+	ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
 	RunE: deleteCommand{
 		apiType: imagePolicyType,
 		object:  universalAdapter{&imagev1.ImagePolicy{}},
diff --git a/cmd/flux/delete_image_repository.go b/cmd/flux/delete_image_repository.go
index 10b85ff9..a8769788 100644
--- a/cmd/flux/delete_image_repository.go
+++ b/cmd/flux/delete_image_repository.go
@@ -28,6 +28,7 @@ var deleteImageRepositoryCmd = &cobra.Command{
 	Long:  "The delete image repository command deletes the given ImageRepository from the cluster.",
 	Example: `  # Delete an image repository
   flux delete image repository alpine`,
+	ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
 	RunE: deleteCommand{
 		apiType: imageRepositoryType,
 		object:  universalAdapter{&imagev1.ImageRepository{}},
diff --git a/cmd/flux/delete_image_update.go b/cmd/flux/delete_image_update.go
index cede3840..3c38f078 100644
--- a/cmd/flux/delete_image_update.go
+++ b/cmd/flux/delete_image_update.go
@@ -28,6 +28,7 @@ var deleteImageUpdateCmd = &cobra.Command{
 	Long:  "The delete image update command deletes the given ImageUpdateAutomation from the cluster.",
 	Example: `  # Delete an image update automation
   flux delete image update latest-images`,
+	ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
 	RunE: deleteCommand{
 		apiType: imageUpdateAutomationType,
 		object:  universalAdapter{&autov1.ImageUpdateAutomation{}},
diff --git a/cmd/flux/delete_kustomization.go b/cmd/flux/delete_kustomization.go
index 5e03ad95..142eed26 100644
--- a/cmd/flux/delete_kustomization.go
+++ b/cmd/flux/delete_kustomization.go
@@ -29,6 +29,7 @@ var deleteKsCmd = &cobra.Command{
 	Long:    "The delete kustomization command deletes the given Kustomization from the cluster.",
 	Example: `  # Delete a kustomization and the Kubernetes resources created by it
   flux delete kustomization podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
 	RunE: deleteCommand{
 		apiType: kustomizationType,
 		object:  universalAdapter{&kustomizev1.Kustomization{}},
diff --git a/cmd/flux/delete_receiver.go b/cmd/flux/delete_receiver.go
index 5806b6df..bfdc5dbc 100644
--- a/cmd/flux/delete_receiver.go
+++ b/cmd/flux/delete_receiver.go
@@ -28,6 +28,7 @@ var deleteReceiverCmd = &cobra.Command{
 	Long:  "The delete receiver command removes the given Receiver from the cluster.",
 	Example: `  # Delete an Receiver and the Kubernetes resources created by it
   flux delete receiver main`,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
 	RunE: deleteCommand{
 		apiType: receiverType,
 		object:  universalAdapter{&notificationv1.Receiver{}},
diff --git a/cmd/flux/delete_source_bucket.go b/cmd/flux/delete_source_bucket.go
index 3549efb9..56d9bdf8 100644
--- a/cmd/flux/delete_source_bucket.go
+++ b/cmd/flux/delete_source_bucket.go
@@ -28,6 +28,7 @@ var deleteSourceBucketCmd = &cobra.Command{
 	Long:  "The delete source bucket command deletes the given Bucket from the cluster.",
 	Example: `  # Delete a Bucket source
   flux delete source bucket podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
 	RunE: deleteCommand{
 		apiType: bucketType,
 		object:  universalAdapter{&sourcev1.Bucket{}},
diff --git a/cmd/flux/delete_source_git.go b/cmd/flux/delete_source_git.go
index 36e4303a..5c521a5b 100644
--- a/cmd/flux/delete_source_git.go
+++ b/cmd/flux/delete_source_git.go
@@ -28,6 +28,7 @@ var deleteSourceGitCmd = &cobra.Command{
 	Long:  "The delete source git command deletes the given GitRepository from the cluster.",
 	Example: `  # Delete a Git repository
   flux delete source git podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
 	RunE: deleteCommand{
 		apiType: gitRepositoryType,
 		object:  universalAdapter{&sourcev1.GitRepository{}},
diff --git a/cmd/flux/delete_source_helm.go b/cmd/flux/delete_source_helm.go
index 35cc2ad0..87b708ad 100644
--- a/cmd/flux/delete_source_helm.go
+++ b/cmd/flux/delete_source_helm.go
@@ -28,6 +28,7 @@ var deleteSourceHelmCmd = &cobra.Command{
 	Long:  "The delete source helm command deletes the given HelmRepository from the cluster.",
 	Example: `  # Delete a Helm repository
   flux delete source helm podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
 	RunE: deleteCommand{
 		apiType: helmRepositoryType,
 		object:  universalAdapter{&sourcev1.HelmRepository{}},
diff --git a/cmd/flux/export_alert.go b/cmd/flux/export_alert.go
index 32b660e2..9c358e0c 100644
--- a/cmd/flux/export_alert.go
+++ b/cmd/flux/export_alert.go
@@ -32,6 +32,7 @@ var exportAlertCmd = &cobra.Command{
 
   # Export a Alert
   flux export alert main > main.yaml`,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
 	RunE: exportCommand{
 		object: alertAdapter{&notificationv1.Alert{}},
 		list:   alertListAdapter{&notificationv1.AlertList{}},
diff --git a/cmd/flux/export_alertprovider.go b/cmd/flux/export_alertprovider.go
index ddb6fe2f..eb47294f 100644
--- a/cmd/flux/export_alertprovider.go
+++ b/cmd/flux/export_alertprovider.go
@@ -32,6 +32,7 @@ var exportAlertProviderCmd = &cobra.Command{
 
   # Export a Provider
   flux export alert-provider slack > slack.yaml`,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
 	RunE: exportCommand{
 		object: alertProviderAdapter{&notificationv1.Provider{}},
 		list:   alertProviderListAdapter{&notificationv1.ProviderList{}},
diff --git a/cmd/flux/export_helmrelease.go b/cmd/flux/export_helmrelease.go
index 961caa3c..8fd8c87f 100644
--- a/cmd/flux/export_helmrelease.go
+++ b/cmd/flux/export_helmrelease.go
@@ -33,6 +33,7 @@ var exportHelmReleaseCmd = &cobra.Command{
 
   # Export a HelmRelease
   flux export hr my-app > app-release.yaml`,
+	ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
 	RunE: exportCommand{
 		object: helmReleaseAdapter{&helmv2.HelmRelease{}},
 		list:   helmReleaseListAdapter{&helmv2.HelmReleaseList{}},
diff --git a/cmd/flux/export_image_policy.go b/cmd/flux/export_image_policy.go
index 6aa64f7b..5302dd63 100644
--- a/cmd/flux/export_image_policy.go
+++ b/cmd/flux/export_image_policy.go
@@ -32,6 +32,7 @@ var exportImagePolicyCmd = &cobra.Command{
 
   # Export a specific policy
   flux export image policy alpine1x > alpine1x.yaml`,
+	ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
 	RunE: exportCommand{
 		object: imagePolicyAdapter{&imagev1.ImagePolicy{}},
 		list:   imagePolicyListAdapter{&imagev1.ImagePolicyList{}},
diff --git a/cmd/flux/export_image_repository.go b/cmd/flux/export_image_repository.go
index 7fad1da6..13f72f7d 100644
--- a/cmd/flux/export_image_repository.go
+++ b/cmd/flux/export_image_repository.go
@@ -32,6 +32,7 @@ var exportImageRepositoryCmd = &cobra.Command{
 
   # Export a specific ImageRepository resource
   flux export image repository alpine > alpine.yaml`,
+	ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
 	RunE: exportCommand{
 		object: imageRepositoryAdapter{&imagev1.ImageRepository{}},
 		list:   imageRepositoryListAdapter{&imagev1.ImageRepositoryList{}},
diff --git a/cmd/flux/export_image_update.go b/cmd/flux/export_image_update.go
index 6d6fcffa..44f4b4a1 100644
--- a/cmd/flux/export_image_update.go
+++ b/cmd/flux/export_image_update.go
@@ -32,6 +32,7 @@ var exportImageUpdateCmd = &cobra.Command{
 
   # Export a specific automation
   flux export image update latest-images > latest.yaml`,
+	ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
 	RunE: exportCommand{
 		object: imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
 		list:   imageUpdateAutomationListAdapter{&autov1.ImageUpdateAutomationList{}},
diff --git a/cmd/flux/export_kustomization.go b/cmd/flux/export_kustomization.go
index d6f4e1db..fcf48819 100644
--- a/cmd/flux/export_kustomization.go
+++ b/cmd/flux/export_kustomization.go
@@ -33,6 +33,7 @@ var exportKsCmd = &cobra.Command{
 
   # Export a Kustomization
   flux export kustomization my-app > kustomization.yaml`,
+	ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
 	RunE: exportCommand{
 		object: kustomizationAdapter{&kustomizev1.Kustomization{}},
 		list:   kustomizationListAdapter{&kustomizev1.KustomizationList{}},
diff --git a/cmd/flux/export_receiver.go b/cmd/flux/export_receiver.go
index 54c8e75d..0a79d853 100644
--- a/cmd/flux/export_receiver.go
+++ b/cmd/flux/export_receiver.go
@@ -32,6 +32,7 @@ var exportReceiverCmd = &cobra.Command{
 
   # Export a Receiver
   flux export receiver main > main.yaml`,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
 	RunE: exportCommand{
 		list:   receiverListAdapter{&notificationv1.ReceiverList{}},
 		object: receiverAdapter{&notificationv1.Receiver{}},
diff --git a/cmd/flux/export_source_bucket.go b/cmd/flux/export_source_bucket.go
index 7a7039c1..33e36ebd 100644
--- a/cmd/flux/export_source_bucket.go
+++ b/cmd/flux/export_source_bucket.go
@@ -33,6 +33,7 @@ var exportSourceBucketCmd = &cobra.Command{
 
   # Export a Bucket source including the static credentials
   flux export source bucket my-bucket --with-credentials > source.yaml`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
 	RunE: exportWithSecretCommand{
 		list:   bucketListAdapter{&sourcev1.BucketList{}},
 		object: bucketAdapter{&sourcev1.Bucket{}},
diff --git a/cmd/flux/export_source_git.go b/cmd/flux/export_source_git.go
index 7f069193..c93e0fd1 100644
--- a/cmd/flux/export_source_git.go
+++ b/cmd/flux/export_source_git.go
@@ -33,6 +33,7 @@ var exportSourceGitCmd = &cobra.Command{
 
   # Export a GitRepository source including the SSH key pair or basic auth credentials
   flux export source git my-private-repo --with-credentials > source.yaml`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
 	RunE: exportWithSecretCommand{
 		object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
 		list:   gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},
diff --git a/cmd/flux/export_source_helm.go b/cmd/flux/export_source_helm.go
index 62499987..d2215335 100644
--- a/cmd/flux/export_source_helm.go
+++ b/cmd/flux/export_source_helm.go
@@ -33,6 +33,7 @@ var exportSourceHelmCmd = &cobra.Command{
 
   # Export a HelmRepository source including the basic auth credentials
   flux export source helm my-private-repo --with-credentials > source.yaml`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
 	RunE: exportWithSecretCommand{
 		list:   helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},
 		object: helmRepositoryAdapter{&sourcev1.HelmRepository{}},
diff --git a/cmd/flux/get_alert.go b/cmd/flux/get_alert.go
index 122d1bc6..126dbe56 100644
--- a/cmd/flux/get_alert.go
+++ b/cmd/flux/get_alert.go
@@ -34,6 +34,7 @@ var getAlertCmd = &cobra.Command{
 	Long:    "The get alert command prints the statuses of the resources.",
 	Example: `  # List all Alerts and their status
   flux get alerts`,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		get := getCommand{
 			apiType: alertType,
diff --git a/cmd/flux/get_alertprovider.go b/cmd/flux/get_alertprovider.go
index 4c7f6105..cb27973f 100644
--- a/cmd/flux/get_alertprovider.go
+++ b/cmd/flux/get_alertprovider.go
@@ -32,6 +32,7 @@ var getAlertProviderCmd = &cobra.Command{
 	Long:    "The get alert-provider command prints the statuses of the resources.",
 	Example: `  # List all Providers and their status
   flux get alert-providers`,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		get := getCommand{
 			apiType: alertProviderType,
diff --git a/cmd/flux/get_helmrelease.go b/cmd/flux/get_helmrelease.go
index f489aa87..5478e29c 100644
--- a/cmd/flux/get_helmrelease.go
+++ b/cmd/flux/get_helmrelease.go
@@ -33,6 +33,7 @@ var getHelmReleaseCmd = &cobra.Command{
 	Long:    "The get helmreleases command prints the statuses of the resources.",
 	Example: `  # List all Helm releases and their status
   flux get helmreleases`,
+	ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		get := getCommand{
 			apiType: helmReleaseType,
diff --git a/cmd/flux/get_image_policy.go b/cmd/flux/get_image_policy.go
index fe73118d..7974c380 100644
--- a/cmd/flux/get_image_policy.go
+++ b/cmd/flux/get_image_policy.go
@@ -34,6 +34,7 @@ var getImagePolicyCmd = &cobra.Command{
 
  # List image policies from all namespaces
   flux get image policy --all-namespaces`,
+	ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		get := getCommand{
 			apiType: imagePolicyType,
diff --git a/cmd/flux/get_image_repository.go b/cmd/flux/get_image_repository.go
index a5b55fea..0878a29c 100644
--- a/cmd/flux/get_image_repository.go
+++ b/cmd/flux/get_image_repository.go
@@ -37,6 +37,7 @@ var getImageRepositoryCmd = &cobra.Command{
 
  # List image repositories from all namespaces
   flux get image repository --all-namespaces`,
+	ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		get := getCommand{
 			apiType: imageRepositoryType,
diff --git a/cmd/flux/get_image_update.go b/cmd/flux/get_image_update.go
index 9c34f95d..da16c576 100644
--- a/cmd/flux/get_image_update.go
+++ b/cmd/flux/get_image_update.go
@@ -37,6 +37,7 @@ var getImageUpdateCmd = &cobra.Command{
 
  # List image update automations from all namespaces
   flux get image update --all-namespaces`,
+	ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		get := getCommand{
 			apiType: imageUpdateAutomationType,
diff --git a/cmd/flux/get_kustomization.go b/cmd/flux/get_kustomization.go
index d507b2a5..3e749b2d 100644
--- a/cmd/flux/get_kustomization.go
+++ b/cmd/flux/get_kustomization.go
@@ -34,6 +34,7 @@ var getKsCmd = &cobra.Command{
 	Long:    "The get kustomizations command prints the statuses of the resources.",
 	Example: `  # List all kustomizations and their status
   flux get kustomizations`,
+	ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		get := getCommand{
 			apiType: kustomizationType,
diff --git a/cmd/flux/get_receiver.go b/cmd/flux/get_receiver.go
index f3da4bb4..d2147d1b 100644
--- a/cmd/flux/get_receiver.go
+++ b/cmd/flux/get_receiver.go
@@ -34,6 +34,7 @@ var getReceiverCmd = &cobra.Command{
 	Long:    "The get receiver command prints the statuses of the resources.",
 	Example: `  # List all Receiver and their status
   flux get receivers`,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		get := getCommand{
 			apiType: receiverType,
diff --git a/cmd/flux/get_source_bucket.go b/cmd/flux/get_source_bucket.go
index c34ef8c2..963a8ed4 100644
--- a/cmd/flux/get_source_bucket.go
+++ b/cmd/flux/get_source_bucket.go
@@ -36,6 +36,7 @@ var getSourceBucketCmd = &cobra.Command{
 
  # List buckets from all namespaces
   flux get sources helm --all-namespaces`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		get := getCommand{
 			apiType: bucketType,
diff --git a/cmd/flux/get_source_chart.go b/cmd/flux/get_source_chart.go
index 3e890a5f..f5401791 100644
--- a/cmd/flux/get_source_chart.go
+++ b/cmd/flux/get_source_chart.go
@@ -36,6 +36,7 @@ var getSourceHelmChartCmd = &cobra.Command{
 
  # List Helm charts from all namespaces
   flux get sources chart --all-namespaces`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		get := getCommand{
 			apiType: helmChartType,
diff --git a/cmd/flux/get_source_git.go b/cmd/flux/get_source_git.go
index 75639903..31a8e5bd 100644
--- a/cmd/flux/get_source_git.go
+++ b/cmd/flux/get_source_git.go
@@ -36,6 +36,7 @@ var getSourceGitCmd = &cobra.Command{
 
  # List Git repositories from all namespaces
   flux get sources git --all-namespaces`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		get := getCommand{
 			apiType: gitRepositoryType,
diff --git a/cmd/flux/get_source_helm.go b/cmd/flux/get_source_helm.go
index e0d0bb4d..cf98246b 100644
--- a/cmd/flux/get_source_helm.go
+++ b/cmd/flux/get_source_helm.go
@@ -36,6 +36,7 @@ var getSourceHelmCmd = &cobra.Command{
 
  # List Helm repositories from all namespaces
   flux get sources helm --all-namespaces`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
 	RunE: func(cmd *cobra.Command, args []string) error {
 		get := getCommand{
 			apiType: helmRepositoryType,
diff --git a/cmd/flux/main.go b/cmd/flux/main.go
index 9295e83e..5bd8fa50 100644
--- a/cmd/flux/main.go
+++ b/cmd/flux/main.go
@@ -23,6 +23,7 @@ import (
 	"time"
 
 	"github.com/spf13/cobra"
+	corev1 "k8s.io/api/core/v1"
 	_ "k8s.io/client-go/plugin/pkg/client/auth"
 
 	"github.com/fluxcd/flux2/pkg/manifestgen/install"
@@ -108,11 +109,15 @@ var rootArgs = NewRootFlags()
 
 func init() {
 	rootCmd.PersistentFlags().StringVarP(&rootArgs.namespace, "namespace", "n", rootArgs.defaults.Namespace, "the namespace scope for this operation")
+	rootCmd.RegisterFlagCompletionFunc("namespace", resourceNamesCompletionFunc(corev1.SchemeGroupVersion.WithKind("Namespace")))
+
 	rootCmd.PersistentFlags().DurationVar(&rootArgs.timeout, "timeout", 5*time.Minute, "timeout for this operation")
 	rootCmd.PersistentFlags().BoolVar(&rootArgs.verbose, "verbose", false, "print generated objects")
 	rootCmd.PersistentFlags().StringVarP(&rootArgs.kubeconfig, "kubeconfig", "", "",
 		"absolute path to the kubeconfig file")
+
 	rootCmd.PersistentFlags().StringVarP(&rootArgs.kubecontext, "context", "", "", "kubernetes context to use")
+	rootCmd.RegisterFlagCompletionFunc("context", contextsCompletionFunc)
 
 	rootCmd.DisableAutoGenTag = true
 }
diff --git a/cmd/flux/reconcile_alert.go b/cmd/flux/reconcile_alert.go
index 2bc79c9c..6027da3e 100644
--- a/cmd/flux/reconcile_alert.go
+++ b/cmd/flux/reconcile_alert.go
@@ -28,6 +28,7 @@ var reconcileAlertCmd = &cobra.Command{
 	Long:  `The reconcile alert command triggers a reconciliation of an Alert resource and waits for it to finish.`,
 	Example: `  # Trigger a reconciliation for an existing alert
   flux reconcile alert main`,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
 	RunE: reconcileCommand{
 		apiType: alertType,
 		object:  alertAdapter{&notificationv1.Alert{}},
diff --git a/cmd/flux/reconcile_alertprovider.go b/cmd/flux/reconcile_alertprovider.go
index ccf68bdb..092e8ac9 100644
--- a/cmd/flux/reconcile_alertprovider.go
+++ b/cmd/flux/reconcile_alertprovider.go
@@ -37,7 +37,8 @@ var reconcileAlertProviderCmd = &cobra.Command{
 	Long:  `The reconcile alert-provider command triggers a reconciliation of a Provider resource and waits for it to finish.`,
 	Example: `  # Trigger a reconciliation for an existing provider
   flux reconcile alert-provider slack`,
-	RunE: reconcileAlertProviderCmdRun,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ProviderKind)),
+	RunE:              reconcileAlertProviderCmdRun,
 }
 
 func init() {
diff --git a/cmd/flux/reconcile_helmrelease.go b/cmd/flux/reconcile_helmrelease.go
index 0262dad9..38558a0f 100644
--- a/cmd/flux/reconcile_helmrelease.go
+++ b/cmd/flux/reconcile_helmrelease.go
@@ -35,6 +35,7 @@ The reconcile kustomization command triggers a reconciliation of a HelmRelease r
 
   # Trigger a reconciliation of the HelmRelease's source and apply changes
   flux reconcile hr podinfo --with-source`,
+	ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
 	RunE: reconcileWithSourceCommand{
 		apiType: helmReleaseType,
 		object:  helmReleaseAdapter{&helmv2.HelmRelease{}},
diff --git a/cmd/flux/reconcile_image_repository.go b/cmd/flux/reconcile_image_repository.go
index d2e4c087..efd89e9c 100644
--- a/cmd/flux/reconcile_image_repository.go
+++ b/cmd/flux/reconcile_image_repository.go
@@ -30,6 +30,7 @@ var reconcileImageRepositoryCmd = &cobra.Command{
 	Long:  `The reconcile image repository command triggers a reconciliation of an ImageRepository resource and waits for it to finish.`,
 	Example: `  # Trigger an scan for an existing image repository
   flux reconcile image repository alpine`,
+	ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImagePolicyKind)),
 	RunE: reconcileCommand{
 		apiType: imageRepositoryType,
 		object:  imageRepositoryAdapter{&imagev1.ImageRepository{}},
diff --git a/cmd/flux/reconcile_image_updateauto.go b/cmd/flux/reconcile_image_updateauto.go
index b1af0d56..8fba13cc 100644
--- a/cmd/flux/reconcile_image_updateauto.go
+++ b/cmd/flux/reconcile_image_updateauto.go
@@ -32,6 +32,7 @@ var reconcileImageUpdateCmd = &cobra.Command{
 	Long:  `The reconcile image update command triggers a reconciliation of an ImageUpdateAutomation resource and waits for it to finish.`,
 	Example: `  # Trigger an automation run for an existing image update automation
   flux reconcile image update latest-images`,
+	ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
 	RunE: reconcileCommand{
 		apiType: imageUpdateAutomationType,
 		object:  imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
diff --git a/cmd/flux/reconcile_kustomization.go b/cmd/flux/reconcile_kustomization.go
index d108bcfa..48b7ec2e 100644
--- a/cmd/flux/reconcile_kustomization.go
+++ b/cmd/flux/reconcile_kustomization.go
@@ -35,6 +35,7 @@ The reconcile kustomization command triggers a reconciliation of a Kustomization
 
   # Trigger a sync of the Kustomization's source and apply changes
   flux reconcile kustomization podinfo --with-source`,
+	ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
 	RunE: reconcileWithSourceCommand{
 		apiType: kustomizationType,
 		object:  kustomizationAdapter{&kustomizev1.Kustomization{}},
diff --git a/cmd/flux/reconcile_receiver.go b/cmd/flux/reconcile_receiver.go
index 8ec849de..9b7e7fd0 100644
--- a/cmd/flux/reconcile_receiver.go
+++ b/cmd/flux/reconcile_receiver.go
@@ -37,7 +37,8 @@ var reconcileReceiverCmd = &cobra.Command{
 	Long:  `The reconcile receiver command triggers a reconciliation of a Receiver resource and waits for it to finish.`,
 	Example: `  # Trigger a reconciliation for an existing receiver
   flux reconcile receiver main`,
-	RunE: reconcileReceiverCmdRun,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
+	RunE:              reconcileReceiverCmdRun,
 }
 
 func init() {
diff --git a/cmd/flux/reconcile_source_bucket.go b/cmd/flux/reconcile_source_bucket.go
index 6fa346ba..768071da 100644
--- a/cmd/flux/reconcile_source_bucket.go
+++ b/cmd/flux/reconcile_source_bucket.go
@@ -37,6 +37,7 @@ var reconcileSourceBucketCmd = &cobra.Command{
 	Long:  `The reconcile source command triggers a reconciliation of a Bucket resource and waits for it to finish.`,
 	Example: `  # Trigger a reconciliation for an existing source
   flux reconcile source bucket podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
 	RunE: reconcileCommand{
 		apiType: bucketType,
 		object:  bucketAdapter{&sourcev1.Bucket{}},
diff --git a/cmd/flux/reconcile_source_git.go b/cmd/flux/reconcile_source_git.go
index 9c01a8fc..9b5a75be 100644
--- a/cmd/flux/reconcile_source_git.go
+++ b/cmd/flux/reconcile_source_git.go
@@ -30,6 +30,7 @@ var reconcileSourceGitCmd = &cobra.Command{
 	Long:  `The reconcile source command triggers a reconciliation of a GitRepository resource and waits for it to finish.`,
 	Example: `  # Trigger a git pull for an existing source
   flux reconcile source git podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
 	RunE: reconcileCommand{
 		apiType: gitRepositoryType,
 		object:  gitRepositoryAdapter{&sourcev1.GitRepository{}},
diff --git a/cmd/flux/reconcile_source_helm.go b/cmd/flux/reconcile_source_helm.go
index 736b9245..e0836259 100644
--- a/cmd/flux/reconcile_source_helm.go
+++ b/cmd/flux/reconcile_source_helm.go
@@ -30,6 +30,7 @@ var reconcileSourceHelmCmd = &cobra.Command{
 	Long:  `The reconcile source command triggers a reconciliation of a HelmRepository resource and waits for it to finish.`,
 	Example: `  # Trigger a reconciliation for an existing source
   flux reconcile source helm podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
 	RunE: reconcileCommand{
 		apiType: helmRepositoryType,
 		object:  helmRepositoryAdapter{&sourcev1.HelmRepository{}},
diff --git a/cmd/flux/resume_alert.go b/cmd/flux/resume_alert.go
index de5a645a..bede1e3e 100644
--- a/cmd/flux/resume_alert.go
+++ b/cmd/flux/resume_alert.go
@@ -29,6 +29,7 @@ var resumeAlertCmd = &cobra.Command{
 finish the apply.`,
 	Example: `  # Resume reconciliation for an existing Alert
   flux resume alert main`,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
 	RunE: resumeCommand{
 		apiType: alertType,
 		object:  alertAdapter{&notificationv1.Alert{}},
diff --git a/cmd/flux/resume_helmrelease.go b/cmd/flux/resume_helmrelease.go
index 24ee8010..0de4fb90 100644
--- a/cmd/flux/resume_helmrelease.go
+++ b/cmd/flux/resume_helmrelease.go
@@ -32,6 +32,7 @@ var resumeHrCmd = &cobra.Command{
 finish the apply.`,
 	Example: `  # Resume reconciliation for an existing Helm release
   flux resume hr podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
 	RunE: resumeCommand{
 		apiType: helmReleaseType,
 		object:  helmReleaseAdapter{&helmv2.HelmRelease{}},
diff --git a/cmd/flux/resume_image_repository.go b/cmd/flux/resume_image_repository.go
index 59349c55..b0c15b18 100644
--- a/cmd/flux/resume_image_repository.go
+++ b/cmd/flux/resume_image_repository.go
@@ -28,6 +28,7 @@ var resumeImageRepositoryCmd = &cobra.Command{
 	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`,
+	ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
 	RunE: resumeCommand{
 		apiType: imageRepositoryType,
 		object:  imageRepositoryAdapter{&imagev1.ImageRepository{}},
diff --git a/cmd/flux/resume_image_updateauto.go b/cmd/flux/resume_image_updateauto.go
index c7e45d74..8cc40bfc 100644
--- a/cmd/flux/resume_image_updateauto.go
+++ b/cmd/flux/resume_image_updateauto.go
@@ -28,6 +28,7 @@ var resumeImageUpdateCmd = &cobra.Command{
 	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`,
+	ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
 	RunE: resumeCommand{
 		apiType: imageUpdateAutomationType,
 		object:  imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
diff --git a/cmd/flux/resume_kustomization.go b/cmd/flux/resume_kustomization.go
index 1555cc9a..78bc5e66 100644
--- a/cmd/flux/resume_kustomization.go
+++ b/cmd/flux/resume_kustomization.go
@@ -32,6 +32,7 @@ var resumeKsCmd = &cobra.Command{
 finish the apply.`,
 	Example: `  # Resume reconciliation for an existing Kustomization
   flux resume ks podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
 	RunE: resumeCommand{
 		apiType: kustomizationType,
 		object:  kustomizationAdapter{&kustomizev1.Kustomization{}},
diff --git a/cmd/flux/resume_receiver.go b/cmd/flux/resume_receiver.go
index eecfe63d..d4d3c64b 100644
--- a/cmd/flux/resume_receiver.go
+++ b/cmd/flux/resume_receiver.go
@@ -29,6 +29,7 @@ var resumeReceiverCmd = &cobra.Command{
 finish the apply.`,
 	Example: `  # Resume reconciliation for an existing Receiver
   flux resume receiver main`,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
 	RunE: resumeCommand{
 		apiType: receiverType,
 		object:  receiverAdapter{&notificationv1.Receiver{}},
diff --git a/cmd/flux/resume_source_bucket.go b/cmd/flux/resume_source_bucket.go
index e40cef34..c16d2090 100644
--- a/cmd/flux/resume_source_bucket.go
+++ b/cmd/flux/resume_source_bucket.go
@@ -28,6 +28,7 @@ var resumeSourceBucketCmd = &cobra.Command{
 	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`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
 	RunE: resumeCommand{
 		apiType: bucketType,
 		object:  &bucketAdapter{&sourcev1.Bucket{}},
diff --git a/cmd/flux/resume_source_chart.go b/cmd/flux/resume_source_chart.go
index d3525eb3..6da61eed 100644
--- a/cmd/flux/resume_source_chart.go
+++ b/cmd/flux/resume_source_chart.go
@@ -30,6 +30,7 @@ var resumeSourceHelmChartCmd = &cobra.Command{
 	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`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),
 	RunE: resumeCommand{
 		apiType: helmChartType,
 		object:  &helmChartAdapter{&sourcev1.HelmChart{}},
diff --git a/cmd/flux/resume_source_git.go b/cmd/flux/resume_source_git.go
index f4aa22ed..31784281 100644
--- a/cmd/flux/resume_source_git.go
+++ b/cmd/flux/resume_source_git.go
@@ -28,6 +28,7 @@ var resumeSourceGitCmd = &cobra.Command{
 	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`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
 	RunE: resumeCommand{
 		apiType: gitRepositoryType,
 		object:  gitRepositoryAdapter{&sourcev1.GitRepository{}},
diff --git a/cmd/flux/resume_source_helm.go b/cmd/flux/resume_source_helm.go
index a22d7327..674a81b1 100644
--- a/cmd/flux/resume_source_helm.go
+++ b/cmd/flux/resume_source_helm.go
@@ -28,6 +28,7 @@ var resumeSourceHelmCmd = &cobra.Command{
 	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`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
 	RunE: resumeCommand{
 		apiType: helmRepositoryType,
 		object:  helmRepositoryAdapter{&sourcev1.HelmRepository{}},
diff --git a/cmd/flux/suspend_alert.go b/cmd/flux/suspend_alert.go
index eb3c184f..42ee60df 100644
--- a/cmd/flux/suspend_alert.go
+++ b/cmd/flux/suspend_alert.go
@@ -28,6 +28,7 @@ var suspendAlertCmd = &cobra.Command{
 	Long:  "The suspend command disables the reconciliation of a Alert resource.",
 	Example: `  # Suspend reconciliation for an existing Alert
   flux suspend alert main`,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.AlertKind)),
 	RunE: suspendCommand{
 		apiType: alertType,
 		object:  &alertAdapter{&notificationv1.Alert{}},
diff --git a/cmd/flux/suspend_helmrelease.go b/cmd/flux/suspend_helmrelease.go
index d222916a..5e552278 100644
--- a/cmd/flux/suspend_helmrelease.go
+++ b/cmd/flux/suspend_helmrelease.go
@@ -29,6 +29,7 @@ var suspendHrCmd = &cobra.Command{
 	Long:    "The suspend command disables the reconciliation of a HelmRelease resource.",
 	Example: `  # Suspend reconciliation for an existing Helm release
   flux suspend hr podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)),
 	RunE: suspendCommand{
 		apiType: helmReleaseType,
 		object:  &helmReleaseAdapter{&helmv2.HelmRelease{}},
diff --git a/cmd/flux/suspend_image_repository.go b/cmd/flux/suspend_image_repository.go
index 5651b799..826c4de4 100644
--- a/cmd/flux/suspend_image_repository.go
+++ b/cmd/flux/suspend_image_repository.go
@@ -28,6 +28,7 @@ var suspendImageRepositoryCmd = &cobra.Command{
 	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`,
+	ValidArgsFunction: resourceNamesCompletionFunc(imagev1.GroupVersion.WithKind(imagev1.ImageRepositoryKind)),
 	RunE: suspendCommand{
 		apiType: imageRepositoryType,
 		object:  imageRepositoryAdapter{&imagev1.ImageRepository{}},
diff --git a/cmd/flux/suspend_image_updateauto.go b/cmd/flux/suspend_image_updateauto.go
index cc6c2ecf..cd6e8f3a 100644
--- a/cmd/flux/suspend_image_updateauto.go
+++ b/cmd/flux/suspend_image_updateauto.go
@@ -28,6 +28,7 @@ var suspendImageUpdateCmd = &cobra.Command{
 	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`,
+	ValidArgsFunction: resourceNamesCompletionFunc(autov1.GroupVersion.WithKind(autov1.ImageUpdateAutomationKind)),
 	RunE: suspendCommand{
 		apiType: imageUpdateAutomationType,
 		object:  imageUpdateAutomationAdapter{&autov1.ImageUpdateAutomation{}},
diff --git a/cmd/flux/suspend_kustomization.go b/cmd/flux/suspend_kustomization.go
index 1d1f208b..18d00764 100644
--- a/cmd/flux/suspend_kustomization.go
+++ b/cmd/flux/suspend_kustomization.go
@@ -29,6 +29,7 @@ var suspendKsCmd = &cobra.Command{
 	Long:    "The suspend command disables the reconciliation of a Kustomization resource.",
 	Example: `  # Suspend reconciliation for an existing Kustomization
   flux suspend ks podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(kustomizev1.GroupVersion.WithKind(kustomizev1.KustomizationKind)),
 	RunE: suspendCommand{
 		apiType: kustomizationType,
 		object:  kustomizationAdapter{&kustomizev1.Kustomization{}},
diff --git a/cmd/flux/suspend_receiver.go b/cmd/flux/suspend_receiver.go
index 90ca5cf0..2edf1572 100644
--- a/cmd/flux/suspend_receiver.go
+++ b/cmd/flux/suspend_receiver.go
@@ -28,6 +28,7 @@ var suspendReceiverCmd = &cobra.Command{
 	Long:  "The suspend command disables the reconciliation of a Receiver resource.",
 	Example: `  # Suspend reconciliation for an existing Receiver
   flux suspend receiver main`,
+	ValidArgsFunction: resourceNamesCompletionFunc(notificationv1.GroupVersion.WithKind(notificationv1.ReceiverKind)),
 	RunE: suspendCommand{
 		apiType: receiverType,
 		object:  &receiverAdapter{&notificationv1.Receiver{}},
diff --git a/cmd/flux/suspend_source_bucket.go b/cmd/flux/suspend_source_bucket.go
index 72f1d0e3..e452b9dc 100644
--- a/cmd/flux/suspend_source_bucket.go
+++ b/cmd/flux/suspend_source_bucket.go
@@ -28,6 +28,7 @@ var suspendSourceBucketCmd = &cobra.Command{
 	Long:  "The suspend command disables the reconciliation of a Bucket resource.",
 	Example: `  # Suspend reconciliation for an existing Bucket
   flux suspend source bucket podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)),
 	RunE: suspendCommand{
 		apiType: bucketType,
 		object:  bucketAdapter{&sourcev1.Bucket{}},
diff --git a/cmd/flux/suspend_source_chart.go b/cmd/flux/suspend_source_chart.go
index 703e820e..750e65ff 100644
--- a/cmd/flux/suspend_source_chart.go
+++ b/cmd/flux/suspend_source_chart.go
@@ -28,6 +28,7 @@ var suspendSourceHelmChartCmd = &cobra.Command{
 	Long:  "The suspend command disables the reconciliation of a HelmChart resource.",
 	Example: `  # Suspend reconciliation for an existing HelmChart
   flux suspend source chart podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmChartKind)),
 	RunE: suspendCommand{
 		apiType: helmChartType,
 		object:  helmChartAdapter{&sourcev1.HelmChart{}},
diff --git a/cmd/flux/suspend_source_git.go b/cmd/flux/suspend_source_git.go
index bc058303..16ebd0a6 100644
--- a/cmd/flux/suspend_source_git.go
+++ b/cmd/flux/suspend_source_git.go
@@ -28,6 +28,7 @@ var suspendSourceGitCmd = &cobra.Command{
 	Long:  "The suspend command disables the reconciliation of a GitRepository resource.",
 	Example: `  # Suspend reconciliation for an existing GitRepository
   flux suspend source git podinfo`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)),
 	RunE: suspendCommand{
 		apiType: gitRepositoryType,
 		object:  gitRepositoryAdapter{&sourcev1.GitRepository{}},
diff --git a/cmd/flux/suspend_source_helm.go b/cmd/flux/suspend_source_helm.go
index a4a5787c..0b29a2dc 100644
--- a/cmd/flux/suspend_source_helm.go
+++ b/cmd/flux/suspend_source_helm.go
@@ -28,6 +28,7 @@ var suspendSourceHelmCmd = &cobra.Command{
 	Long:  "The suspend command disables the reconciliation of a HelmRepository resource.",
 	Example: `  # Suspend reconciliation for an existing HelmRepository
   flux suspend source helm bitnami`,
+	ValidArgsFunction: resourceNamesCompletionFunc(sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)),
 	RunE: suspendCommand{
 		apiType: helmRepositoryType,
 		object:  helmRepositoryAdapter{&sourcev1.HelmRepository{}},
diff --git a/internal/utils/utils.go b/internal/utils/utils.go
index d06629e2..1181cc0b 100644
--- a/internal/utils/utils.go
+++ b/internal/utils/utils.go
@@ -107,7 +107,7 @@ func ExecKubectlCommand(ctx context.Context, mode ExecMode, kubeConfigPath strin
 	return "", nil
 }
 
-func KubeConfig(kubeConfigPath string, kubeContext string) (*rest.Config, error) {
+func ClientConfig(kubeConfigPath string, kubeContext string) clientcmd.ClientConfig {
 	configFiles := SplitKubeConfigPath(kubeConfigPath)
 	configOverrides := clientcmd.ConfigOverrides{}
 
@@ -115,11 +115,14 @@ func KubeConfig(kubeConfigPath string, kubeContext string) (*rest.Config, error)
 		configOverrides.CurrentContext = kubeContext
 	}
 
-	cfg, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
+	return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
 		&clientcmd.ClientConfigLoadingRules{Precedence: configFiles},
 		&configOverrides,
-	).ClientConfig()
+	)
+}
 
+func KubeConfig(kubeConfigPath string, kubeContext string) (*rest.Config, error) {
+	cfg, err := ClientConfig(kubeConfigPath, kubeContext).ClientConfig()
 	if err != nil {
 		return nil, fmt.Errorf("kubernetes configuration load failed: %w", err)
 	}
-- 
GitLab