diff --git a/cmd/gotk/bootstrap.go b/cmd/gotk/bootstrap.go
index 2dce249a..06af444e 100644
--- a/cmd/gotk/bootstrap.go
+++ b/cmd/gotk/bootstrap.go
@@ -38,6 +38,8 @@ import (
 	kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
 
+	"github.com/fluxcd/toolkit/internal/flags"
+	"github.com/fluxcd/toolkit/internal/utils"
 	"github.com/fluxcd/toolkit/pkg/install"
 )
 
@@ -52,11 +54,11 @@ var (
 	bootstrapComponents         []string
 	bootstrapRegistry           string
 	bootstrapImagePullSecret    string
-	bootstrapArch               string
+	bootstrapArch               flags.Arch = "amd64"
 	bootstrapBranch             string
 	bootstrapWatchAllNamespaces bool
 	bootstrapNetworkPolicy      bool
-	bootstrapLogLevel           string
+	bootstrapLogLevel           flags.LogLevel = "info"
 	bootstrapManifestsPath      string
 	bootstrapRequiredComponents = []string{"source-controller", "kustomize-controller"}
 )
@@ -77,8 +79,7 @@ func init() {
 		"container registry where the toolkit images are published")
 	bootstrapCmd.PersistentFlags().StringVar(&bootstrapImagePullSecret, "image-pull-secret", "",
 		"Kubernetes secret name used for pulling the toolkit images from a private registry")
-	bootstrapCmd.PersistentFlags().StringVar(&bootstrapArch, "arch", "amd64",
-		"arch can be amd64 or arm64")
+	bootstrapCmd.PersistentFlags().Var(&bootstrapArch, "arch", bootstrapArch.Description())
 	bootstrapCmd.PersistentFlags().StringVar(&bootstrapBranch, "branch", bootstrapDefaultBranch,
 		"default branch (for GitHub this must match the default branch setting for the organization)")
 	rootCmd.AddCommand(bootstrapCmd)
@@ -86,22 +87,14 @@ func init() {
 		"watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed")
 	bootstrapCmd.PersistentFlags().BoolVar(&bootstrapNetworkPolicy, "network-policy", true,
 		"deny ingress access to the toolkit controllers from other namespaces using network policies")
-	bootstrapCmd.PersistentFlags().StringVar(&bootstrapLogLevel, "log-level", "info", "set the controllers log level")
+	bootstrapCmd.PersistentFlags().Var(&bootstrapLogLevel, "log-level", bootstrapLogLevel.Description())
 	bootstrapCmd.PersistentFlags().StringVar(&bootstrapManifestsPath, "manifests", "", "path to the manifest directory")
 	bootstrapCmd.PersistentFlags().MarkHidden("manifests")
 }
 
 func bootstrapValidate() error {
-	if !utils.containsItemString(supportedArch, bootstrapArch) {
-		return fmt.Errorf("arch %s is not supported, can be %v", bootstrapArch, supportedArch)
-	}
-
-	if !utils.containsItemString(supportedLogLevels, bootstrapLogLevel) {
-		return fmt.Errorf("log level %s is not supported, can be %v", bootstrapLogLevel, supportedLogLevels)
-	}
-
 	for _, component := range bootstrapRequiredComponents {
-		if !utils.containsItemString(bootstrapComponents, component) {
+		if !utils.ContainsItemString(bootstrapComponents, component) {
 			return fmt.Errorf("component %s is required", component)
 		}
 	}
@@ -124,10 +117,10 @@ func generateInstallManifests(targetPath, namespace, tmpDir string, localManifes
 		Components:             bootstrapComponents,
 		Registry:               bootstrapRegistry,
 		ImagePullSecret:        bootstrapImagePullSecret,
-		Arch:                   bootstrapArch,
+		Arch:                   bootstrapArch.String(),
 		WatchAllNamespaces:     bootstrapWatchAllNamespaces,
 		NetworkPolicy:          bootstrapNetworkPolicy,
-		LogLevel:               bootstrapLogLevel,
+		LogLevel:               bootstrapLogLevel.String(),
 		NotificationController: defaultNotification,
 		ManifestsFile:          fmt.Sprintf("%s.yaml", namespace),
 		Timeout:                timeout,
@@ -151,13 +144,13 @@ func generateInstallManifests(targetPath, namespace, tmpDir string, localManifes
 
 func applyInstallManifests(ctx context.Context, manifestPath string, components []string) error {
 	kubectlArgs := []string{"apply", "-f", manifestPath}
-	if _, err := utils.execKubectlCommand(ctx, ModeOS, kubectlArgs...); err != nil {
+	if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, kubectlArgs...); err != nil {
 		return fmt.Errorf("install failed")
 	}
 
 	for _, deployment := range components {
 		kubectlArgs = []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()}
-		if _, err := utils.execKubectlCommand(ctx, ModeOS, kubectlArgs...); err != nil {
+		if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, kubectlArgs...); err != nil {
 			return fmt.Errorf("install failed")
 		}
 	}
@@ -194,7 +187,7 @@ func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir stri
 		return err
 	}
 
-	if err := utils.writeFile(string(gitData), filepath.Join(tmpDir, targetPath, namespace, bootstrapSourceManifest)); err != nil {
+	if err := utils.WriteFile(string(gitData), filepath.Join(tmpDir, targetPath, namespace, bootstrapSourceManifest)); err != nil {
 		return err
 	}
 
@@ -227,11 +220,11 @@ func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir stri
 		return err
 	}
 
-	if err := utils.writeFile(string(ksData), filepath.Join(tmpDir, targetPath, namespace, bootstrapKustomizationManifest)); err != nil {
+	if err := utils.WriteFile(string(ksData), filepath.Join(tmpDir, targetPath, namespace, bootstrapKustomizationManifest)); err != nil {
 		return err
 	}
 
-	if err := utils.generateKustomizationYaml(filepath.Join(tmpDir, targetPath, namespace)); err != nil {
+	if err := utils.GenerateKustomizationYaml(filepath.Join(tmpDir, targetPath, namespace)); err != nil {
 		return err
 	}
 
@@ -240,7 +233,7 @@ func generateSyncManifests(url, branch, name, namespace, targetPath, tmpDir stri
 
 func applySyncManifests(ctx context.Context, kubeClient client.Client, name, namespace, targetPath, tmpDir string) error {
 	kubectlArgs := []string{"apply", "-k", filepath.Join(tmpDir, targetPath, namespace)}
-	if _, err := utils.execKubectlCommand(ctx, ModeStderrOS, kubectlArgs...); err != nil {
+	if _, err := utils.ExecKubectlCommand(ctx, utils.ModeStderrOS, kubectlArgs...); err != nil {
 		return err
 	}
 
diff --git a/cmd/gotk/bootstrap_github.go b/cmd/gotk/bootstrap_github.go
index eca646ea..c111269e 100644
--- a/cmd/gotk/bootstrap_github.go
+++ b/cmd/gotk/bootstrap_github.go
@@ -28,6 +28,7 @@ import (
 	"github.com/spf13/cobra"
 
 	"github.com/fluxcd/pkg/git"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var bootstrapGitHubCmd = &cobra.Command{
@@ -183,7 +184,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error {
 		logger.Successf("components are up to date")
 	}
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/bootstrap_gitlab.go b/cmd/gotk/bootstrap_gitlab.go
index 3426b7f4..35893f47 100644
--- a/cmd/gotk/bootstrap_gitlab.go
+++ b/cmd/gotk/bootstrap_gitlab.go
@@ -30,6 +30,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	"github.com/fluxcd/pkg/git"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var bootstrapGitLabCmd = &cobra.Command{
@@ -112,7 +113,7 @@ func bootstrapGitLabCmdRun(cmd *cobra.Command, args []string) error {
 		IsPersonal: glPersonal,
 	}
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/check.go b/cmd/gotk/check.go
index 64b86b5c..9ec51398 100644
--- a/cmd/gotk/check.go
+++ b/cmd/gotk/check.go
@@ -24,6 +24,7 @@ import (
 	"strings"
 
 	"github.com/blang/semver/v4"
+	"github.com/fluxcd/toolkit/internal/utils"
 	"github.com/spf13/cobra"
 	apimachineryversion "k8s.io/apimachinery/pkg/version"
 	"k8s.io/client-go/kubernetes"
@@ -103,7 +104,7 @@ func kubectlCheck(ctx context.Context, version string) bool {
 	}
 
 	kubectlArgs := []string{"version", "--client", "--output", "json"}
-	output, err := utils.execKubectlCommand(ctx, ModeCapture, kubectlArgs...)
+	output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, kubectlArgs...)
 	if err != nil {
 		logger.Failuref("kubectl version can't be determined")
 		return false
@@ -173,7 +174,7 @@ func componentsCheck() bool {
 	ok := true
 	for _, deployment := range checkComponents {
 		kubectlArgs := []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()}
-		if output, err := utils.execKubectlCommand(ctx, ModeCapture, kubectlArgs...); err != nil {
+		if output, err := utils.ExecKubectlCommand(ctx, utils.ModeCapture, kubectlArgs...); err != nil {
 			logger.Failuref("%s: %s", deployment, strings.TrimSuffix(output, "\n"))
 			ok = false
 		} else {
diff --git a/cmd/gotk/create_alert.go b/cmd/gotk/create_alert.go
index 9a943b21..cae14bc7 100644
--- a/cmd/gotk/create_alert.go
+++ b/cmd/gotk/create_alert.go
@@ -19,7 +19,9 @@ package main
 import (
 	"context"
 	"fmt"
+
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 
 	"github.com/spf13/cobra"
 	corev1 "k8s.io/api/core/v1"
@@ -71,7 +73,7 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
 
 	eventSources := []notificationv1.CrossNamespaceObjectReference{}
 	for _, eventSource := range aEventSources {
-		kind, name := utils.parseObjectKindName(eventSource)
+		kind, name := utils.ParseObjectKindName(eventSource)
 		if kind == "" {
 			return fmt.Errorf("invalid event source '%s', must be in format <kind>/<name>", eventSource)
 		}
@@ -118,7 +120,7 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/create_alertprovider.go b/cmd/gotk/create_alertprovider.go
index 9c28782e..f1ab8c6a 100644
--- a/cmd/gotk/create_alertprovider.go
+++ b/cmd/gotk/create_alertprovider.go
@@ -30,6 +30,7 @@ import (
 
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var createAlertProviderCmd = &cobra.Command{
@@ -115,7 +116,7 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/create_helmrelease.go b/cmd/gotk/create_helmrelease.go
index 8d77ad31..566e8825 100644
--- a/cmd/gotk/create_helmrelease.go
+++ b/cmd/gotk/create_helmrelease.go
@@ -22,6 +22,8 @@ import (
 	"io/ioutil"
 
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/flags"
+	"github.com/fluxcd/toolkit/internal/utils"
 
 	"github.com/spf13/cobra"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -89,7 +91,7 @@ var createHelmReleaseCmd = &cobra.Command{
 
 var (
 	hrName            string
-	hrSource          string
+	hrSource          flags.HelmChartSource
 	hrDependsOn       []string
 	hrChart           string
 	hrChartVersion    string
@@ -99,7 +101,7 @@ var (
 
 func init() {
 	createHelmReleaseCmd.Flags().StringVar(&hrName, "release-name", "", "name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>'")
-	createHelmReleaseCmd.Flags().StringVar(&hrSource, "source", "", "source that contains the chart (<kind>/<name>)")
+	createHelmReleaseCmd.Flags().Var(&hrSource, "source", hrSource.Description())
 	createHelmReleaseCmd.Flags().StringVar(&hrChart, "chart", "", "Helm chart name or path")
 	createHelmReleaseCmd.Flags().StringVar(&hrChartVersion, "chart-version", "", "Helm chart version, accepts a semver range (ignored for charts from GitRepository sources)")
 	createHelmReleaseCmd.Flags().StringArrayVar(&hrDependsOn, "depends-on", nil, "HelmReleases that must be ready before this release can be installed, supported formats '<name>' and '<namespace>/<name>'")
@@ -114,17 +116,6 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
 	}
 	name := args[0]
 
-	if hrSource == "" {
-		return fmt.Errorf("source is required")
-	}
-	sourceKind, sourceName := utils.parseObjectKindName(hrSource)
-	if sourceKind == "" {
-		return fmt.Errorf("invalid source '%s', must be in format <kind>/<name>", hrSource)
-	}
-	if !utils.containsItemString(supportedHelmChartSourceKinds, sourceKind) {
-		return fmt.Errorf("source kind %s is not supported, can be %v",
-			sourceKind, supportedHelmChartSourceKinds)
-	}
 	if hrChart == "" {
 		return fmt.Errorf("chart name or path is required")
 	}
@@ -146,7 +137,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
 		},
 		Spec: helmv2.HelmReleaseSpec{
 			ReleaseName: hrName,
-			DependsOn:   utils.makeDependsOn(hrDependsOn),
+			DependsOn:   utils.MakeDependsOn(hrDependsOn),
 			Interval: metav1.Duration{
 				Duration: interval,
 			},
@@ -156,8 +147,8 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
 					Chart:   hrChart,
 					Version: hrChartVersion,
 					SourceRef: helmv2.CrossNamespaceObjectReference{
-						Kind: sourceKind,
-						Name: sourceName,
+						Kind: hrSource.Kind,
+						Name: hrSource.Name,
 					},
 				},
 			},
@@ -186,7 +177,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/create_kustomization.go b/cmd/gotk/create_kustomization.go
index d3fdd0c6..e19d9731 100644
--- a/cmd/gotk/create_kustomization.go
+++ b/cmd/gotk/create_kustomization.go
@@ -33,7 +33,8 @@ import (
 	helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
 	kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
 	"github.com/fluxcd/pkg/apis/meta"
-	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/flags"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var createKsCmd = &cobra.Command{
@@ -71,7 +72,7 @@ var createKsCmd = &cobra.Command{
 }
 
 var (
-	ksSource             string
+	ksSource             flags.KustomizationSource
 	ksPath               string
 	ksPrune              bool
 	ksDependsOn          []string
@@ -80,13 +81,12 @@ var (
 	ksHealthTimeout      time.Duration
 	ksSAName             string
 	ksSANamespace        string
-	ksDecryptionProvider string
+	ksDecryptionProvider flags.DecryptionProvider
 	ksDecryptionSecret   string
 )
 
 func init() {
-	createKsCmd.Flags().StringVar(&ksSource, "source", "",
-		"source that contains the Kubernetes manifests in the format '[<kind>/]<name>', where kind can be GitRepository or Bucket, if kind is not specified it defaults to GitRepository")
+	createKsCmd.Flags().Var(&ksSource, "source", ksSource.Description())
 	createKsCmd.Flags().StringVar(&ksPath, "path", "./", "path to the directory containing the Kustomization file")
 	createKsCmd.Flags().BoolVar(&ksPrune, "prune", false, "enable garbage collection")
 	createKsCmd.Flags().StringArrayVar(&ksHealthCheck, "health-check", nil, "workload to be included in the health assessment, in the format '<kind>/<name>.<namespace>'")
@@ -95,7 +95,7 @@ func init() {
 	createKsCmd.Flags().StringArrayVar(&ksDependsOn, "depends-on", nil, "Kustomization that must be ready before this Kustomization can be applied, supported formats '<name>' and '<namespace>/<name>'")
 	createKsCmd.Flags().StringVar(&ksSAName, "sa-name", "", "service account name")
 	createKsCmd.Flags().StringVar(&ksSANamespace, "sa-namespace", "", "service account namespace")
-	createKsCmd.Flags().StringVar(&ksDecryptionProvider, "decryption-provider", "", "enables secrets decryption, provider can be 'sops'")
+	createKsCmd.Flags().Var(&ksDecryptionProvider, "decryption-provider", ksDecryptionProvider.Description())
 	createKsCmd.Flags().StringVar(&ksDecryptionSecret, "decryption-secret", "", "set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption")
 	createCmd.AddCommand(createKsCmd)
 }
@@ -106,19 +106,6 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
 	}
 	name := args[0]
 
-	if ksSource == "" {
-		return fmt.Errorf("source is required")
-	}
-
-	sourceKind, sourceName := utils.parseObjectKindName(ksSource)
-	if sourceKind == "" {
-		sourceKind = sourcev1.GitRepositoryKind
-	}
-	if !utils.containsItemString(supportedKustomizationSourceKinds, sourceKind) {
-		return fmt.Errorf("source kind %s is not supported, can be %v",
-			sourceKind, supportedKustomizationSourceKinds)
-	}
-
 	if ksPath == "" {
 		return fmt.Errorf("path is required")
 	}
@@ -142,15 +129,15 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
 			Labels:    ksLabels,
 		},
 		Spec: kustomizev1.KustomizationSpec{
-			DependsOn: utils.makeDependsOn(ksDependsOn),
+			DependsOn: utils.MakeDependsOn(ksDependsOn),
 			Interval: metav1.Duration{
 				Duration: interval,
 			},
 			Path:  ksPath,
 			Prune: ksPrune,
 			SourceRef: kustomizev1.CrossNamespaceSourceReference{
-				Kind: sourceKind,
-				Name: sourceName,
+				Kind: ksSource.Kind,
+				Name: ksSource.Name,
 			},
 			Suspend:    false,
 			Validation: ksValidation,
@@ -206,13 +193,8 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
 	}
 
 	if ksDecryptionProvider != "" {
-		if !utils.containsItemString(supportedDecryptionProviders, ksDecryptionProvider) {
-			return fmt.Errorf("decryption provider %s is not supported, can be %v",
-				ksDecryptionProvider, supportedDecryptionProviders)
-		}
-
 		kustomization.Spec.Decryption = &kustomizev1.Decryption{
-			Provider: ksDecryptionProvider,
+			Provider: ksDecryptionProvider.String(),
 		}
 
 		if ksDecryptionSecret != "" {
@@ -227,7 +209,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/create_receiver.go b/cmd/gotk/create_receiver.go
index 08595bfe..697860ea 100644
--- a/cmd/gotk/create_receiver.go
+++ b/cmd/gotk/create_receiver.go
@@ -30,6 +30,7 @@ import (
 
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var createReceiverCmd = &cobra.Command{
@@ -79,7 +80,7 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
 
 	resources := []notificationv1.CrossNamespaceObjectReference{}
 	for _, resource := range rcvResources {
-		kind, name := utils.parseObjectKindName(resource)
+		kind, name := utils.ParseObjectKindName(resource)
 		if kind == "" {
 			return fmt.Errorf("invalid event source '%s', must be in format <kind>/<name>", resource)
 		}
@@ -127,7 +128,7 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/create_source_bucket.go b/cmd/gotk/create_source_bucket.go
index 32a06ea1..9511ab54 100644
--- a/cmd/gotk/create_source_bucket.go
+++ b/cmd/gotk/create_source_bucket.go
@@ -31,6 +31,8 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/flags"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var createSourceBucketCmd = &cobra.Command{
@@ -61,7 +63,7 @@ For Buckets with static authentication, the credentials are stored in a Kubernet
 
 var (
 	sourceBucketName      string
-	sourceBucketProvider  string
+	sourceBucketProvider  = flags.SourceBucketProvider(sourcev1.GenericBucketProvider)
 	sourceBucketEndpoint  string
 	sourceBucketAccessKey string
 	sourceBucketSecretKey string
@@ -70,7 +72,7 @@ var (
 )
 
 func init() {
-	createSourceBucketCmd.Flags().StringVar(&sourceBucketProvider, "provider", sourcev1.GenericBucketProvider, "the S3 compatible storage provider name, can be 'generic' or 'aws'")
+	createSourceBucketCmd.Flags().Var(&sourceBucketProvider, "provider", sourceBucketProvider.Description())
 	createSourceBucketCmd.Flags().StringVar(&sourceBucketName, "bucket-name", "", "the bucket name")
 	createSourceBucketCmd.Flags().StringVar(&sourceBucketEndpoint, "endpoint", "", "the bucket endpoint address")
 	createSourceBucketCmd.Flags().StringVar(&sourceBucketAccessKey, "access-key", "", "the bucket access key")
@@ -88,11 +90,6 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
 	name := args[0]
 	secretName := fmt.Sprintf("bucket-%s", name)
 
-	if !utils.containsItemString(supportedSourceBucketProviders, sourceBucketProvider) {
-		return fmt.Errorf("Bucket provider %s is not supported, can be %v",
-			sourceBucketProvider, supportedSourceBucketProviders)
-	}
-
 	if sourceBucketName == "" {
 		return fmt.Errorf("bucket-name is required")
 	}
@@ -120,7 +117,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
 		},
 		Spec: sourcev1.BucketSpec{
 			BucketName: sourceBucketName,
-			Provider:   sourceBucketProvider,
+			Provider:   sourceBucketProvider.String(),
 			Insecure:   sourceBucketInsecure,
 			Endpoint:   sourceBucketEndpoint,
 			Region:     sourceBucketRegion,
@@ -137,7 +134,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/create_source_git.go b/cmd/gotk/create_source_git.go
index ad784841..5330b5ad 100644
--- a/cmd/gotk/create_source_git.go
+++ b/cmd/gotk/create_source_git.go
@@ -20,12 +20,15 @@ import (
 	"context"
 	"crypto/elliptic"
 	"fmt"
-	"github.com/fluxcd/pkg/apis/meta"
 	"io/ioutil"
 	"net/url"
 	"os"
 	"time"
 
+	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/flags"
+	"github.com/fluxcd/toolkit/internal/utils"
+
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
 	"github.com/manifoldco/promptui"
 	"github.com/spf13/cobra"
@@ -90,9 +93,9 @@ var (
 	sourceGitSemver       string
 	sourceGitUsername     string
 	sourceGitPassword     string
-	sourceGitKeyAlgorithm PublicKeyAlgorithm = "rsa"
-	sourceGitRSABits      RSAKeyBits         = 2048
-	sourceGitECDSACurve                      = ECDSACurve{elliptic.P384()}
+	sourceGitKeyAlgorithm flags.PublicKeyAlgorithm = "rsa"
+	sourceGitRSABits      flags.RSAKeyBits         = 2048
+	sourceGitECDSACurve                            = flags.ECDSACurve{Curve: elliptic.P384()}
 )
 
 func init() {
@@ -165,7 +168,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/create_source_helm.go b/cmd/gotk/create_source_helm.go
index bc6c9a08..e8e61f41 100644
--- a/cmd/gotk/create_source_helm.go
+++ b/cmd/gotk/create_source_helm.go
@@ -32,6 +32,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var createSourceHelmCmd = &cobra.Command{
@@ -128,7 +129,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/create_tenant.go b/cmd/gotk/create_tenant.go
index 8119c7fa..8abf61e4 100644
--- a/cmd/gotk/create_tenant.go
+++ b/cmd/gotk/create_tenant.go
@@ -21,6 +21,7 @@ import (
 	"context"
 	"fmt"
 
+	"github.com/fluxcd/toolkit/internal/utils"
 	"github.com/spf13/cobra"
 	corev1 "k8s.io/api/core/v1"
 	rbacv1 "k8s.io/api/rbac/v1"
@@ -144,7 +145,7 @@ func createTenantCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/delete_alert.go b/cmd/gotk/delete_alert.go
index b918f2b7..242969d5 100644
--- a/cmd/gotk/delete_alert.go
+++ b/cmd/gotk/delete_alert.go
@@ -25,6 +25,7 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var deleteAlertCmd = &cobra.Command{
@@ -50,7 +51,7 @@ func deleteAlertCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/delete_alertprovider.go b/cmd/gotk/delete_alertprovider.go
index 1b641529..9cd7e153 100644
--- a/cmd/gotk/delete_alertprovider.go
+++ b/cmd/gotk/delete_alertprovider.go
@@ -25,6 +25,7 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var deleteAlertProviderCmd = &cobra.Command{
@@ -50,7 +51,7 @@ func deleteAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/delete_helmrelease.go b/cmd/gotk/delete_helmrelease.go
index 86ec663c..efc7fe87 100644
--- a/cmd/gotk/delete_helmrelease.go
+++ b/cmd/gotk/delete_helmrelease.go
@@ -25,6 +25,7 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 
 	helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var deleteHelmReleaseCmd = &cobra.Command{
@@ -51,7 +52,7 @@ func deleteHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/delete_kustomization.go b/cmd/gotk/delete_kustomization.go
index 743827a2..07b0acaa 100644
--- a/cmd/gotk/delete_kustomization.go
+++ b/cmd/gotk/delete_kustomization.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 
 	kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 	"github.com/manifoldco/promptui"
 	"github.com/spf13/cobra"
 	"k8s.io/apimachinery/pkg/types"
@@ -50,7 +51,7 @@ func deleteKsCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/delete_receiver.go b/cmd/gotk/delete_receiver.go
index 253308e0..d4c0bd1d 100644
--- a/cmd/gotk/delete_receiver.go
+++ b/cmd/gotk/delete_receiver.go
@@ -25,6 +25,7 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var deleteReceiverCmd = &cobra.Command{
@@ -50,7 +51,7 @@ func deleteReceiverCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/delete_source_bucket.go b/cmd/gotk/delete_source_bucket.go
index 7614e483..a2c2fd29 100644
--- a/cmd/gotk/delete_source_bucket.go
+++ b/cmd/gotk/delete_source_bucket.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 	"github.com/manifoldco/promptui"
 	"github.com/spf13/cobra"
 	"k8s.io/apimachinery/pkg/types"
@@ -49,7 +50,7 @@ func deleteSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/delete_source_git.go b/cmd/gotk/delete_source_git.go
index 73000fbf..7d393bb9 100644
--- a/cmd/gotk/delete_source_git.go
+++ b/cmd/gotk/delete_source_git.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 	"github.com/manifoldco/promptui"
 	"github.com/spf13/cobra"
 	"k8s.io/apimachinery/pkg/types"
@@ -49,7 +50,7 @@ func deleteSourceGitCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/delete_source_helm.go b/cmd/gotk/delete_source_helm.go
index 5a9427ca..05887696 100644
--- a/cmd/gotk/delete_source_helm.go
+++ b/cmd/gotk/delete_source_helm.go
@@ -21,6 +21,7 @@ import (
 	"fmt"
 
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 	"github.com/manifoldco/promptui"
 	"github.com/spf13/cobra"
 	"k8s.io/apimachinery/pkg/types"
@@ -49,7 +50,7 @@ func deleteSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/export_alert.go b/cmd/gotk/export_alert.go
index e5a69f07..c837473f 100644
--- a/cmd/gotk/export_alert.go
+++ b/cmd/gotk/export_alert.go
@@ -27,6 +27,7 @@ import (
 	"sigs.k8s.io/yaml"
 
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var exportAlertCmd = &cobra.Command{
@@ -54,7 +55,7 @@ func exportAlertCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/export_alertprovider.go b/cmd/gotk/export_alertprovider.go
index 88a61182..3d212d63 100644
--- a/cmd/gotk/export_alertprovider.go
+++ b/cmd/gotk/export_alertprovider.go
@@ -27,6 +27,7 @@ import (
 	"sigs.k8s.io/yaml"
 
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var exportAlertProviderCmd = &cobra.Command{
@@ -54,7 +55,7 @@ func exportAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/export_helmrelease.go b/cmd/gotk/export_helmrelease.go
index 8911c52e..54bce9e3 100644
--- a/cmd/gotk/export_helmrelease.go
+++ b/cmd/gotk/export_helmrelease.go
@@ -27,6 +27,7 @@ import (
 	"sigs.k8s.io/yaml"
 
 	helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var exportHelmReleaseCmd = &cobra.Command{
@@ -55,7 +56,7 @@ func exportHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/export_kustomization.go b/cmd/gotk/export_kustomization.go
index b47a7076..a059e3a9 100644
--- a/cmd/gotk/export_kustomization.go
+++ b/cmd/gotk/export_kustomization.go
@@ -27,6 +27,7 @@ import (
 	"sigs.k8s.io/yaml"
 
 	kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var exportKsCmd = &cobra.Command{
@@ -55,7 +56,7 @@ func exportKsCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/export_receiver.go b/cmd/gotk/export_receiver.go
index 1acfb164..e8e35e01 100644
--- a/cmd/gotk/export_receiver.go
+++ b/cmd/gotk/export_receiver.go
@@ -27,6 +27,7 @@ import (
 	"sigs.k8s.io/yaml"
 
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var exportReceiverCmd = &cobra.Command{
@@ -54,7 +55,7 @@ func exportReceiverCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/export_source_bucket.go b/cmd/gotk/export_source_bucket.go
index af9f78d0..3350cdb7 100644
--- a/cmd/gotk/export_source_bucket.go
+++ b/cmd/gotk/export_source_bucket.go
@@ -28,6 +28,7 @@ import (
 	"sigs.k8s.io/yaml"
 
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var exportSourceBucketCmd = &cobra.Command{
@@ -55,7 +56,7 @@ func exportSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/export_source_git.go b/cmd/gotk/export_source_git.go
index 1aeff810..2cf023a6 100644
--- a/cmd/gotk/export_source_git.go
+++ b/cmd/gotk/export_source_git.go
@@ -28,6 +28,7 @@ import (
 	"sigs.k8s.io/yaml"
 
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var exportSourceGitCmd = &cobra.Command{
@@ -55,7 +56,7 @@ func exportSourceGitCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/export_source_helm.go b/cmd/gotk/export_source_helm.go
index 53df16e4..1b313942 100644
--- a/cmd/gotk/export_source_helm.go
+++ b/cmd/gotk/export_source_helm.go
@@ -28,6 +28,7 @@ import (
 	"sigs.k8s.io/yaml"
 
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var exportSourceHelmCmd = &cobra.Command{
@@ -55,7 +56,7 @@ func exportSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/get_alert.go b/cmd/gotk/get_alert.go
index c30b3226..20c188de 100644
--- a/cmd/gotk/get_alert.go
+++ b/cmd/gotk/get_alert.go
@@ -28,6 +28,7 @@ import (
 
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var getAlertCmd = &cobra.Command{
@@ -48,7 +49,7 @@ func getAlertCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
@@ -97,6 +98,6 @@ func getAlertCmdRun(cmd *cobra.Command, args []string) error {
 		}
 		rows = append(rows, row)
 	}
-	utils.printTable(os.Stdout, header, rows)
+	utils.PrintTable(os.Stdout, header, rows)
 	return nil
 }
diff --git a/cmd/gotk/get_alertprovider.go b/cmd/gotk/get_alertprovider.go
index a9421d81..6871e445 100644
--- a/cmd/gotk/get_alertprovider.go
+++ b/cmd/gotk/get_alertprovider.go
@@ -26,6 +26,7 @@ import (
 
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var getAlertProviderCmd = &cobra.Command{
@@ -46,7 +47,7 @@ func getAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
@@ -91,6 +92,6 @@ func getAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
 		}
 		rows = append(rows, row)
 	}
-	utils.printTable(os.Stdout, header, rows)
+	utils.PrintTable(os.Stdout, header, rows)
 	return nil
 }
diff --git a/cmd/gotk/get_helmrelease.go b/cmd/gotk/get_helmrelease.go
index d810e598..b838f57b 100644
--- a/cmd/gotk/get_helmrelease.go
+++ b/cmd/gotk/get_helmrelease.go
@@ -23,6 +23,7 @@ import (
 	"strings"
 
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 
 	"github.com/spf13/cobra"
 	corev1 "k8s.io/api/core/v1"
@@ -50,7 +51,7 @@ func getHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
@@ -99,6 +100,6 @@ func getHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
 		}
 		rows = append(rows, row)
 	}
-	utils.printTable(os.Stdout, header, rows)
+	utils.PrintTable(os.Stdout, header, rows)
 	return nil
 }
diff --git a/cmd/gotk/get_kustomization.go b/cmd/gotk/get_kustomization.go
index ac8374cd..9bee84c1 100644
--- a/cmd/gotk/get_kustomization.go
+++ b/cmd/gotk/get_kustomization.go
@@ -23,6 +23,7 @@ import (
 	"strings"
 
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 
 	kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
 	"github.com/spf13/cobra"
@@ -49,7 +50,7 @@ func getKsCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
@@ -98,6 +99,6 @@ func getKsCmdRun(cmd *cobra.Command, args []string) error {
 		}
 		rows = append(rows, row)
 	}
-	utils.printTable(os.Stdout, header, rows)
+	utils.PrintTable(os.Stdout, header, rows)
 	return nil
 }
diff --git a/cmd/gotk/get_receiver.go b/cmd/gotk/get_receiver.go
index c41bdce0..8f53d96c 100644
--- a/cmd/gotk/get_receiver.go
+++ b/cmd/gotk/get_receiver.go
@@ -28,6 +28,7 @@ import (
 
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var getReceiverCmd = &cobra.Command{
@@ -48,7 +49,7 @@ func getReceiverCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
@@ -92,6 +93,6 @@ func getReceiverCmdRun(cmd *cobra.Command, args []string) error {
 		}
 		rows = append(rows, row)
 	}
-	utils.printTable(os.Stdout, header, rows)
+	utils.PrintTable(os.Stdout, header, rows)
 	return nil
 }
diff --git a/cmd/gotk/get_source_bucket.go b/cmd/gotk/get_source_bucket.go
index e4323285..76eb7dde 100644
--- a/cmd/gotk/get_source_bucket.go
+++ b/cmd/gotk/get_source_bucket.go
@@ -21,6 +21,7 @@ import (
 	"os"
 
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
 	"github.com/spf13/cobra"
@@ -46,7 +47,7 @@ func getSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
@@ -97,6 +98,6 @@ func getSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
 		}
 		rows = append(rows, row)
 	}
-	utils.printTable(os.Stdout, header, rows)
+	utils.PrintTable(os.Stdout, header, rows)
 	return nil
 }
diff --git a/cmd/gotk/get_source_git.go b/cmd/gotk/get_source_git.go
index 4afe14fb..f32a7c62 100644
--- a/cmd/gotk/get_source_git.go
+++ b/cmd/gotk/get_source_git.go
@@ -21,6 +21,7 @@ import (
 	"os"
 
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
 	"github.com/spf13/cobra"
@@ -46,7 +47,7 @@ func getSourceGitCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
@@ -97,6 +98,6 @@ func getSourceGitCmdRun(cmd *cobra.Command, args []string) error {
 		}
 		rows = append(rows, row)
 	}
-	utils.printTable(os.Stdout, header, rows)
+	utils.PrintTable(os.Stdout, header, rows)
 	return nil
 }
diff --git a/cmd/gotk/get_source_helm.go b/cmd/gotk/get_source_helm.go
index 10e3b2b1..b3c67fd4 100644
--- a/cmd/gotk/get_source_helm.go
+++ b/cmd/gotk/get_source_helm.go
@@ -21,6 +21,7 @@ import (
 	"os"
 
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
 	"github.com/spf13/cobra"
@@ -46,7 +47,7 @@ func getSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
@@ -97,6 +98,6 @@ func getSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
 		}
 		rows = append(rows, row)
 	}
-	utils.printTable(os.Stdout, header, rows)
+	utils.PrintTable(os.Stdout, header, rows)
 	return nil
 }
diff --git a/cmd/gotk/install.go b/cmd/gotk/install.go
index 72a87be5..fdd6426f 100644
--- a/cmd/gotk/install.go
+++ b/cmd/gotk/install.go
@@ -26,6 +26,8 @@ import (
 
 	"github.com/spf13/cobra"
 
+	"github.com/fluxcd/toolkit/internal/flags"
+	"github.com/fluxcd/toolkit/internal/utils"
 	"github.com/fluxcd/toolkit/pkg/install"
 )
 
@@ -57,10 +59,10 @@ var (
 	installComponents         []string
 	installRegistry           string
 	installImagePullSecret    string
-	installArch               string
+	installArch               flags.Arch = "amd64"
 	installWatchAllNamespaces bool
 	installNetworkPolicy      bool
-	installLogLevel           string
+	installLogLevel           flags.LogLevel = "info"
 )
 
 func init() {
@@ -78,25 +80,16 @@ func init() {
 		"container registry where the toolkit images are published")
 	installCmd.Flags().StringVar(&installImagePullSecret, "image-pull-secret", "",
 		"Kubernetes secret name used for pulling the toolkit images from a private registry")
-	installCmd.Flags().StringVar(&installArch, "arch", "amd64",
-		"arch can be amd64 or arm64")
+	installCmd.Flags().Var(&installArch, "arch", installArch.Description())
 	installCmd.Flags().BoolVar(&installWatchAllNamespaces, "watch-all-namespaces", true,
 		"watch for custom resources in all namespaces, if set to false it will only watch the namespace where the toolkit is installed")
-	installCmd.Flags().StringVar(&installLogLevel, "log-level", "info", "set the controllers log level")
+	installCmd.Flags().Var(&installLogLevel, "log-level", installLogLevel.Description())
 	installCmd.Flags().BoolVar(&installNetworkPolicy, "network-policy", true,
 		"deny ingress access to the toolkit controllers from other namespaces using network policies")
 	rootCmd.AddCommand(installCmd)
 }
 
 func installCmdRun(cmd *cobra.Command, args []string) error {
-	if !utils.containsItemString(supportedArch, installArch) {
-		return fmt.Errorf("arch %s is not supported, can be %v", installArch, supportedArch)
-	}
-
-	if !utils.containsItemString(supportedLogLevels, installLogLevel) {
-		return fmt.Errorf("log level %s is not supported, can be %v", bootstrapLogLevel, installLogLevel)
-	}
-
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
@@ -117,10 +110,10 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
 		Components:             installComponents,
 		Registry:               installRegistry,
 		ImagePullSecret:        installImagePullSecret,
-		Arch:                   installArch,
+		Arch:                   installArch.String(),
 		WatchAllNamespaces:     installWatchAllNamespaces,
 		NetworkPolicy:          installNetworkPolicy,
-		LogLevel:               installLogLevel,
+		LogLevel:               installLogLevel.String(),
 		NotificationController: defaultNotification,
 		ManifestsFile:          fmt.Sprintf("%s.yaml", namespace),
 		Timeout:                timeout,
@@ -154,17 +147,17 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
 
 	logger.Successf("manifests build completed")
 	logger.Actionf("installing components in %s namespace", namespace)
-	applyOutput := ModeStderrOS
+	applyOutput := utils.ModeStderrOS
 	if verbose {
-		applyOutput = ModeOS
+		applyOutput = utils.ModeOS
 	}
 
 	kubectlArgs := []string{"apply", "-f", manifest}
 	if installDryRun {
 		args = append(args, "--dry-run=client")
-		applyOutput = ModeOS
+		applyOutput = utils.ModeOS
 	}
-	if _, err := utils.execKubectlCommand(ctx, applyOutput, kubectlArgs...); err != nil {
+	if _, err := utils.ExecKubectlCommand(ctx, applyOutput, kubectlArgs...); err != nil {
 		return fmt.Errorf("install failed")
 	}
 
@@ -178,7 +171,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
 	logger.Waitingf("verifying installation")
 	for _, deployment := range installComponents {
 		kubectlArgs = []string{"-n", namespace, "rollout", "status", "deployment", deployment, "--timeout", timeout.String()}
-		if _, err := utils.execKubectlCommand(ctx, applyOutput, kubectlArgs...); err != nil {
+		if _, err := utils.ExecKubectlCommand(ctx, applyOutput, kubectlArgs...); err != nil {
 			return fmt.Errorf("install failed")
 		} else {
 			logger.Successf("%s ready", deployment)
diff --git a/cmd/gotk/main.go b/cmd/gotk/main.go
index 491c31fb..851e2fdb 100644
--- a/cmd/gotk/main.go
+++ b/cmd/gotk/main.go
@@ -26,8 +26,6 @@ import (
 	"github.com/spf13/cobra/doc"
 	_ "k8s.io/client-go/plugin/pkg/client/auth"
 
-	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
-
 	gotklog "github.com/fluxcd/toolkit/pkg/log"
 )
 
@@ -100,22 +98,15 @@ var (
 	namespace    string
 	timeout      time.Duration
 	verbose      bool
-	utils        Utils
 	pollInterval                = 2 * time.Second
 	logger       gotklog.Logger = printLogger{}
 )
 
 var (
-	defaultComponents                 = []string{"source-controller", "kustomize-controller", "helm-controller", "notification-controller"}
-	defaultVersion                    = "latest"
-	defaultNamespace                  = "gotk-system"
-	defaultNotification               = "notification-controller"
-	supportedLogLevels                = []string{"debug", "info", "error"}
-	supportedArch                     = []string{"amd64", "arm", "arm64"}
-	supportedDecryptionProviders      = []string{"sops"}
-	supportedKustomizationSourceKinds = []string{sourcev1.GitRepositoryKind, sourcev1.BucketKind}
-	supportedHelmChartSourceKinds     = []string{sourcev1.HelmRepositoryKind, sourcev1.GitRepositoryKind, sourcev1.BucketKind}
-	supportedSourceBucketProviders    = []string{sourcev1.GenericBucketProvider, sourcev1.AmazonBucketProvider}
+	defaultComponents   = []string{"source-controller", "kustomize-controller", "helm-controller", "notification-controller"}
+	defaultVersion      = "latest"
+	defaultNamespace    = "gotk-system"
+	defaultNotification = "notification-controller"
 )
 
 func init() {
diff --git a/cmd/gotk/reconcile_alert.go b/cmd/gotk/reconcile_alert.go
index fc6e56db..4b5b75d6 100644
--- a/cmd/gotk/reconcile_alert.go
+++ b/cmd/gotk/reconcile_alert.go
@@ -19,9 +19,11 @@ package main
 import (
 	"context"
 	"fmt"
-	"github.com/fluxcd/pkg/apis/meta"
 	"time"
 
+	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
+
 	"github.com/spf13/cobra"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/wait"
@@ -52,7 +54,7 @@ func reconcileAlertCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/reconcile_alertprovider.go b/cmd/gotk/reconcile_alertprovider.go
index c8f4e9ef..b85a374f 100644
--- a/cmd/gotk/reconcile_alertprovider.go
+++ b/cmd/gotk/reconcile_alertprovider.go
@@ -19,9 +19,11 @@ package main
 import (
 	"context"
 	"fmt"
-	"github.com/fluxcd/pkg/apis/meta"
 	"time"
 
+	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
+
 	"github.com/spf13/cobra"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/wait"
@@ -52,7 +54,7 @@ func reconcileAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/reconcile_helmrelease.go b/cmd/gotk/reconcile_helmrelease.go
index af4273ce..b4a8cb5f 100644
--- a/cmd/gotk/reconcile_helmrelease.go
+++ b/cmd/gotk/reconcile_helmrelease.go
@@ -29,6 +29,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 
 	helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
@@ -68,7 +69,7 @@ func reconcileHrCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/reconcile_kustomization.go b/cmd/gotk/reconcile_kustomization.go
index ff4e5586..81e4e9d1 100644
--- a/cmd/gotk/reconcile_kustomization.go
+++ b/cmd/gotk/reconcile_kustomization.go
@@ -26,6 +26,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 	"github.com/spf13/cobra"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/wait"
@@ -68,7 +69,7 @@ func reconcileKsCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/reconcile_receiver.go b/cmd/gotk/reconcile_receiver.go
index 9b151ab7..3022edba 100644
--- a/cmd/gotk/reconcile_receiver.go
+++ b/cmd/gotk/reconcile_receiver.go
@@ -19,9 +19,11 @@ package main
 import (
 	"context"
 	"fmt"
-	"github.com/fluxcd/pkg/apis/meta"
 	"time"
 
+	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
+
 	"github.com/spf13/cobra"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/wait"
@@ -52,7 +54,7 @@ func reconcileReceiverCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/reconcile_source_bucket.go b/cmd/gotk/reconcile_source_bucket.go
index c823806f..70adc7ec 100644
--- a/cmd/gotk/reconcile_source_bucket.go
+++ b/cmd/gotk/reconcile_source_bucket.go
@@ -19,9 +19,11 @@ package main
 import (
 	"context"
 	"fmt"
-	"github.com/fluxcd/pkg/apis/meta"
 	"time"
 
+	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
+
 	"github.com/spf13/cobra"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
@@ -54,7 +56,7 @@ func reconcileSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/reconcile_source_git.go b/cmd/gotk/reconcile_source_git.go
index b586861f..91632ab8 100644
--- a/cmd/gotk/reconcile_source_git.go
+++ b/cmd/gotk/reconcile_source_git.go
@@ -19,9 +19,11 @@ package main
 import (
 	"context"
 	"fmt"
-	"github.com/fluxcd/pkg/apis/meta"
 	"time"
 
+	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
+
 	"github.com/spf13/cobra"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/wait"
@@ -52,7 +54,7 @@ func reconcileSourceGitCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/reconcile_source_helm.go b/cmd/gotk/reconcile_source_helm.go
index 3f1fe13a..ea2ed6e7 100644
--- a/cmd/gotk/reconcile_source_helm.go
+++ b/cmd/gotk/reconcile_source_helm.go
@@ -19,9 +19,11 @@ package main
 import (
 	"context"
 	"fmt"
-	"github.com/fluxcd/pkg/apis/meta"
 	"time"
 
+	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
+
 	"github.com/spf13/cobra"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
@@ -54,7 +56,7 @@ func reconcileSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/resume_alert.go b/cmd/gotk/resume_alert.go
index 7624a45c..34f05418 100644
--- a/cmd/gotk/resume_alert.go
+++ b/cmd/gotk/resume_alert.go
@@ -19,7 +19,9 @@ package main
 import (
 	"context"
 	"fmt"
+
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 
 	"github.com/spf13/cobra"
 	corev1 "k8s.io/api/core/v1"
@@ -54,7 +56,7 @@ func resumeAlertCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/resume_helmrelease.go b/cmd/gotk/resume_helmrelease.go
index 46f9c4ba..7a959560 100644
--- a/cmd/gotk/resume_helmrelease.go
+++ b/cmd/gotk/resume_helmrelease.go
@@ -19,7 +19,9 @@ package main
 import (
 	"context"
 	"fmt"
+
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 
 	"github.com/spf13/cobra"
 	corev1 "k8s.io/api/core/v1"
@@ -55,7 +57,7 @@ func resumeHrCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/resume_kustomization.go b/cmd/gotk/resume_kustomization.go
index b683defb..2ac3bc3c 100644
--- a/cmd/gotk/resume_kustomization.go
+++ b/cmd/gotk/resume_kustomization.go
@@ -19,7 +19,9 @@ package main
 import (
 	"context"
 	"fmt"
+
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 
 	kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
 	"github.com/spf13/cobra"
@@ -54,7 +56,7 @@ func resumeKsCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/resume_receiver.go b/cmd/gotk/resume_receiver.go
index 25c40daa..f2f4ff74 100644
--- a/cmd/gotk/resume_receiver.go
+++ b/cmd/gotk/resume_receiver.go
@@ -19,7 +19,9 @@ package main
 import (
 	"context"
 	"fmt"
+
 	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/fluxcd/toolkit/internal/utils"
 
 	"github.com/spf13/cobra"
 	corev1 "k8s.io/api/core/v1"
@@ -54,7 +56,7 @@ func resumeReceiverCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/suspend_alert.go b/cmd/gotk/suspend_alert.go
index 9e7a2388..cfbe03f9 100644
--- a/cmd/gotk/suspend_alert.go
+++ b/cmd/gotk/suspend_alert.go
@@ -24,6 +24,7 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var suspendAlertCmd = &cobra.Command{
@@ -49,7 +50,7 @@ func suspendAlertCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/suspend_helmrelease.go b/cmd/gotk/suspend_helmrelease.go
index 0763b74d..be6dedf8 100644
--- a/cmd/gotk/suspend_helmrelease.go
+++ b/cmd/gotk/suspend_helmrelease.go
@@ -24,6 +24,7 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 
 	helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var suspendHrCmd = &cobra.Command{
@@ -50,7 +51,7 @@ func suspendHrCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/suspend_kustomization.go b/cmd/gotk/suspend_kustomization.go
index 3b0c0d9a..7b416fdc 100644
--- a/cmd/gotk/suspend_kustomization.go
+++ b/cmd/gotk/suspend_kustomization.go
@@ -19,7 +19,9 @@ package main
 import (
 	"context"
 	"fmt"
+
 	kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 	"github.com/spf13/cobra"
 	"k8s.io/apimachinery/pkg/types"
 )
@@ -48,7 +50,7 @@ func suspendKsCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/suspend_receiver.go b/cmd/gotk/suspend_receiver.go
index bb9d0c0b..893c8db3 100644
--- a/cmd/gotk/suspend_receiver.go
+++ b/cmd/gotk/suspend_receiver.go
@@ -24,6 +24,7 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var suspendReceiverCmd = &cobra.Command{
@@ -49,7 +50,7 @@ func suspendReceiverCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/gotk/uninstall.go b/cmd/gotk/uninstall.go
index 89e1aa97..789c966c 100644
--- a/cmd/gotk/uninstall.go
+++ b/cmd/gotk/uninstall.go
@@ -27,6 +27,7 @@ import (
 	helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
 	kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
 )
 
 var uninstallCmd = &cobra.Command{
@@ -66,7 +67,7 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
-	kubeClient, err := utils.kubeClient(kubeconfig)
+	kubeClient, err := utils.KubeClient(kubeconfig)
 	if err != nil {
 		return err
 	}
@@ -111,7 +112,7 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
 			if uninstallDryRun {
 				kubectlArgs = append(kubectlArgs, dryRun)
 			}
-			if _, err := utils.execKubectlCommand(ctx, ModeOS, kubectlArgs...); err != nil {
+			if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, kubectlArgs...); err != nil {
 				return fmt.Errorf("uninstall failed: %w", err)
 			}
 		}
@@ -135,7 +136,7 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
 		if uninstallDryRun {
 			kubectlArgs = append(kubectlArgs, dryRun)
 		}
-		if _, err := utils.execKubectlCommand(ctx, ModeOS, kubectlArgs...); err != nil {
+		if _, err := utils.ExecKubectlCommand(ctx, utils.ModeOS, kubectlArgs...); err != nil {
 			return fmt.Errorf("uninstall failed: %w", err)
 		}
 	}
diff --git a/docs/cmd/gotk_bootstrap.md b/docs/cmd/gotk_bootstrap.md
index c4780265..c171595e 100644
--- a/docs/cmd/gotk_bootstrap.md
+++ b/docs/cmd/gotk_bootstrap.md
@@ -9,12 +9,12 @@ The bootstrap sub-commands bootstrap the toolkit components on the targeted Git
 ### Options
 
 ```
-      --arch string                arch can be amd64 or arm64 (default "amd64")
+      --arch arch                  cluster architecture, available options are: (amd64, arm, arm64) (default amd64)
       --branch string              default branch (for GitHub this must match the default branch setting for the organization) (default "main")
       --components strings         list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller])
   -h, --help                       help for bootstrap
       --image-pull-secret string   Kubernetes secret name used for pulling the toolkit images from a private registry
-      --log-level string           set the controllers log level (default "info")
+      --log-level logLevel         log level, available options are: (debug, info, error) (default info)
       --network-policy             deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
       --registry string            container registry where the toolkit images are published (default "ghcr.io/fluxcd")
   -v, --version string             toolkit version (default "latest")
diff --git a/docs/cmd/gotk_bootstrap_github.md b/docs/cmd/gotk_bootstrap_github.md
index bce9c09b..f2675cfc 100644
--- a/docs/cmd/gotk_bootstrap_github.md
+++ b/docs/cmd/gotk_bootstrap_github.md
@@ -57,12 +57,12 @@ gotk bootstrap github [flags]
 ### Options inherited from parent commands
 
 ```
-      --arch string                arch can be amd64 or arm64 (default "amd64")
+      --arch arch                  cluster architecture, available options are: (amd64, arm, arm64) (default amd64)
       --branch string              default branch (for GitHub this must match the default branch setting for the organization) (default "main")
       --components strings         list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller])
       --image-pull-secret string   Kubernetes secret name used for pulling the toolkit images from a private registry
       --kubeconfig string          path to the kubeconfig file (default "~/.kube/config")
-      --log-level string           set the controllers log level (default "info")
+      --log-level logLevel         log level, available options are: (debug, info, error) (default info)
   -n, --namespace string           the namespace scope for this operation (default "gotk-system")
       --network-policy             deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
       --registry string            container registry where the toolkit images are published (default "ghcr.io/fluxcd")
diff --git a/docs/cmd/gotk_bootstrap_gitlab.md b/docs/cmd/gotk_bootstrap_gitlab.md
index 68f066a0..e9a1108c 100644
--- a/docs/cmd/gotk_bootstrap_gitlab.md
+++ b/docs/cmd/gotk_bootstrap_gitlab.md
@@ -57,12 +57,12 @@ gotk bootstrap gitlab [flags]
 ### Options inherited from parent commands
 
 ```
-      --arch string                arch can be amd64 or arm64 (default "amd64")
+      --arch arch                  cluster architecture, available options are: (amd64, arm, arm64) (default amd64)
       --branch string              default branch (for GitHub this must match the default branch setting for the organization) (default "main")
       --components strings         list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller])
       --image-pull-secret string   Kubernetes secret name used for pulling the toolkit images from a private registry
       --kubeconfig string          path to the kubeconfig file (default "~/.kube/config")
-      --log-level string           set the controllers log level (default "info")
+      --log-level logLevel         log level, available options are: (debug, info, error) (default info)
   -n, --namespace string           the namespace scope for this operation (default "gotk-system")
       --network-policy             deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
       --registry string            container registry where the toolkit images are published (default "ghcr.io/fluxcd")
diff --git a/docs/cmd/gotk_create_helmrelease.md b/docs/cmd/gotk_create_helmrelease.md
index 9a831bec..cbd86586 100644
--- a/docs/cmd/gotk_create_helmrelease.md
+++ b/docs/cmd/gotk_create_helmrelease.md
@@ -67,7 +67,7 @@ gotk create helmrelease [name] [flags]
       --depends-on stringArray    HelmReleases that must be ready before this release can be installed, supported formats '<name>' and '<namespace>/<name>'
   -h, --help                      help for helmrelease
       --release-name string       name used for the Helm release, defaults to a composition of '[<target-namespace>-]<HelmRelease-name>'
-      --source string             source that contains the chart (<kind>/<name>)
+      --source helmChartSource    source that contains the chart in the format '<kind>/<name>',where kind can be one of: (HelmRepository, GitRepository, Bucket)
       --target-namespace string   namespace to install this release, defaults to the HelmRelease namespace
       --values string             local path to the values.yaml file
 ```
diff --git a/docs/cmd/gotk_create_kustomization.md b/docs/cmd/gotk_create_kustomization.md
index d9fc2cd0..8df99f40 100644
--- a/docs/cmd/gotk_create_kustomization.md
+++ b/docs/cmd/gotk_create_kustomization.md
@@ -44,18 +44,18 @@ gotk create kustomization [name] [flags]
 ### Options
 
 ```
-      --decryption-provider string      enables secrets decryption, provider can be 'sops'
-      --decryption-secret string        set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption
-      --depends-on stringArray          Kustomization that must be ready before this Kustomization can be applied, supported formats '<name>' and '<namespace>/<name>'
-      --health-check stringArray        workload to be included in the health assessment, in the format '<kind>/<name>.<namespace>'
-      --health-check-timeout duration   timeout of health checking operations (default 2m0s)
-  -h, --help                            help for kustomization
-      --path string                     path to the directory containing the Kustomization file (default "./")
-      --prune                           enable garbage collection
-      --sa-name string                  service account name
-      --sa-namespace string             service account namespace
-      --source string                   source that contains the Kubernetes manifests in the format '[<kind>/]<name>', where kind can be GitRepository or Bucket, if kind is not specified it defaults to GitRepository
-      --validation string               validate the manifests before applying them on the cluster, can be 'client' or 'server'
+      --decryption-provider decryptionProvider   decryption provider, available options are: (sops)
+      --decryption-secret string                 set the Kubernetes secret name that contains the OpenPGP private keys used for sops decryption
+      --depends-on stringArray                   Kustomization that must be ready before this Kustomization can be applied, supported formats '<name>' and '<namespace>/<name>'
+      --health-check stringArray                 workload to be included in the health assessment, in the format '<kind>/<name>.<namespace>'
+      --health-check-timeout duration            timeout of health checking operations (default 2m0s)
+  -h, --help                                     help for kustomization
+      --path string                              path to the directory containing the Kustomization file (default "./")
+      --prune                                    enable garbage collection
+      --sa-name string                           service account name
+      --sa-namespace string                      service account namespace
+      --source kustomizationSource               source that contains the Kubernetes manifests in the format '[<kind>/]<name>',where kind can be one of: (GitRepository, Bucket), if kind is not specified it defaults to GitRepository
+      --validation string                        validate the manifests before applying them on the cluster, can be 'client' or 'server'
 ```
 
 ### Options inherited from parent commands
diff --git a/docs/cmd/gotk_create_source_bucket.md b/docs/cmd/gotk_create_source_bucket.md
index 6eb56c78..ded84619 100644
--- a/docs/cmd/gotk_create_source_bucket.md
+++ b/docs/cmd/gotk_create_source_bucket.md
@@ -37,14 +37,14 @@ gotk create source bucket [name] [flags]
 ### Options
 
 ```
-      --access-key string    the bucket access key
-      --bucket-name string   the bucket name
-      --endpoint string      the bucket endpoint address
-  -h, --help                 help for bucket
-      --insecure             for when connecting to a non-TLS S3 HTTP endpoint
-      --provider string      the S3 compatible storage provider name, can be 'generic' or 'aws' (default "generic")
-      --region string        the bucket region
-      --secret-key string    the bucket secret key
+      --access-key string               the bucket access key
+      --bucket-name string              the bucket name
+      --endpoint string                 the bucket endpoint address
+  -h, --help                            help for bucket
+      --insecure                        for when connecting to a non-TLS S3 HTTP endpoint
+      --provider sourceBucketProvider   the S3 compatible storage provider name, available options are: (generic, aws) (default generic)
+      --region string                   the bucket region
+      --secret-key string               the bucket secret key
 ```
 
 ### Options inherited from parent commands
diff --git a/docs/cmd/gotk_install.md b/docs/cmd/gotk_install.md
index afc6c2c6..36dbd014 100644
--- a/docs/cmd/gotk_install.md
+++ b/docs/cmd/gotk_install.md
@@ -31,13 +31,13 @@ gotk install [flags]
 ### Options
 
 ```
-      --arch string                arch can be amd64 or arm64 (default "amd64")
+      --arch arch                  cluster architecture, available options are: (amd64, arm, arm64) (default amd64)
       --components strings         list of components, accepts comma-separated values (default [source-controller,kustomize-controller,helm-controller,notification-controller])
       --dry-run                    only print the object that would be applied
       --export                     write the install manifests to stdout and exit
   -h, --help                       help for install
       --image-pull-secret string   Kubernetes secret name used for pulling the toolkit images from a private registry
-      --log-level string           set the controllers log level (default "info")
+      --log-level logLevel         log level, available options are: (debug, info, error) (default info)
       --network-policy             deny ingress access to the toolkit controllers from other namespaces using network policies (default true)
       --registry string            container registry where the toolkit images are published (default "ghcr.io/fluxcd")
   -v, --version string             toolkit version (default "latest")
diff --git a/internal/flags/arch.go b/internal/flags/arch.go
new file mode 100644
index 00000000..543ef444
--- /dev/null
+++ b/internal/flags/arch.go
@@ -0,0 +1,54 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package flags
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/fluxcd/toolkit/internal/utils"
+)
+
+var supportedArchs = []string{"amd64", "arm", "arm64"}
+
+type Arch string
+
+func (a *Arch) String() string {
+	return string(*a)
+}
+
+func (a *Arch) Set(str string) error {
+	if strings.TrimSpace(str) == "" {
+		return fmt.Errorf("no arch given, must be one of: %s",
+			strings.Join(supportedArchs, ", "))
+	}
+	if !utils.ContainsItemString(supportedArchs, str) {
+		return fmt.Errorf("unsupported arch '%s', must be one of: %s",
+			str, strings.Join(supportedArchs, ", "))
+
+	}
+	*a = Arch(str)
+	return nil
+}
+
+func (a *Arch) Type() string {
+	return "arch"
+}
+
+func (a *Arch) Description() string {
+	return fmt.Sprintf("cluster architecture, available options are: (%s)", strings.Join(supportedArchs, ", "))
+}
diff --git a/internal/flags/decryption_provider.go b/internal/flags/decryption_provider.go
new file mode 100644
index 00000000..99593ebb
--- /dev/null
+++ b/internal/flags/decryption_provider.go
@@ -0,0 +1,50 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package flags
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/fluxcd/toolkit/internal/utils"
+)
+
+var supportedDecryptionProviders = []string{"sops"}
+
+type DecryptionProvider string
+
+func (d *DecryptionProvider) String() string {
+	return string(*d)
+}
+
+func (d *DecryptionProvider) Set(str string) error {
+	if !utils.ContainsItemString(supportedDecryptionProviders, str) {
+		return fmt.Errorf("unsupported decryption provider '%s', must be one of: %s",
+			str, strings.Join(supportedDecryptionProviders, ", "))
+
+	}
+	*d = DecryptionProvider(str)
+	return nil
+}
+
+func (d *DecryptionProvider) Type() string {
+	return "decryptionProvider"
+}
+
+func (d *DecryptionProvider) Description() string {
+	return fmt.Sprintf("decryption provider, available options are: (%s)", strings.Join(supportedDecryptionProviders, ", "))
+}
diff --git a/cmd/gotk/flags.go b/internal/flags/ecdsa_curve.go
similarity index 51%
rename from cmd/gotk/flags.go
rename to internal/flags/ecdsa_curve.go
index 991aa730..a67e55ef 100644
--- a/cmd/gotk/flags.go
+++ b/internal/flags/ecdsa_curve.go
@@ -14,79 +14,15 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package main
+package flags
 
 import (
 	"crypto/elliptic"
 	"fmt"
 	"sort"
-	"strconv"
 	"strings"
 )
 
-var supportedPublicKeyAlgorithms = []string{"rsa", "ecdsa", "ed25519"}
-
-type PublicKeyAlgorithm string
-
-func (a *PublicKeyAlgorithm) String() string {
-	return string(*a)
-}
-
-func (a *PublicKeyAlgorithm) Set(str string) error {
-	if strings.TrimSpace(str) == "" {
-		return fmt.Errorf("no public key algorithm given, must be one of: %s",
-			strings.Join(supportedPublicKeyAlgorithms, ", "))
-	}
-	for _, v := range supportedPublicKeyAlgorithms {
-		if str == v {
-			*a = PublicKeyAlgorithm(str)
-			return nil
-		}
-	}
-	return fmt.Errorf("unsupported public key algorithm '%s', must be one of: %s",
-		str, strings.Join(supportedPublicKeyAlgorithms, ", "))
-}
-
-func (a *PublicKeyAlgorithm) Type() string {
-	return "publicKeyAlgorithm"
-}
-
-func (a *PublicKeyAlgorithm) Description() string {
-	return fmt.Sprintf("SSH public key algorithm (%s)", strings.Join(supportedPublicKeyAlgorithms, ", "))
-}
-
-var defaultRSAKeyBits = 2048
-
-type RSAKeyBits int
-
-func (b *RSAKeyBits) String() string {
-	return strconv.Itoa(int(*b))
-}
-
-func (b *RSAKeyBits) Set(str string) error {
-	if strings.TrimSpace(str) == "" {
-		*b = RSAKeyBits(defaultRSAKeyBits)
-		return nil
-	}
-	bits, err := strconv.Atoi(str)
-	if err != nil {
-		return err
-	}
-	if bits%8 != 0 {
-		return fmt.Errorf("RSA key bit size should be a multiples of 8")
-	}
-	*b = RSAKeyBits(bits)
-	return nil
-}
-
-func (b *RSAKeyBits) Type() string {
-	return "rsaKeyBits"
-}
-
-func (b *RSAKeyBits) Description() string {
-	return "SSH RSA public key bit size (multiplies of 8)"
-}
-
 type ECDSACurve struct {
 	elliptic.Curve
 }
diff --git a/internal/flags/helm_chart_source.go b/internal/flags/helm_chart_source.go
new file mode 100644
index 00000000..702f4cd9
--- /dev/null
+++ b/internal/flags/helm_chart_source.go
@@ -0,0 +1,72 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package flags
+
+import (
+	"fmt"
+	"strings"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
+)
+
+var supportedHelmChartSourceKinds = []string{sourcev1.HelmRepositoryKind, sourcev1.GitRepositoryKind, sourcev1.BucketKind}
+
+type HelmChartSource struct {
+	Kind string
+	Name string
+}
+
+func (h *HelmChartSource) String() string {
+	if h.Name == "" {
+		return ""
+	}
+	return fmt.Sprintf("%s/%s", h.Kind, h.Name)
+}
+
+func (h *HelmChartSource) Set(str string) error {
+	if strings.TrimSpace(str) == "" {
+		return fmt.Errorf("no helm chart source given, please specify %s",
+			h.Description())
+	}
+
+	sourceKind, sourceName := utils.ParseObjectKindName(str)
+	if sourceKind == "" {
+		return fmt.Errorf("invalid helm chart source '%s', must be in format <kind>/<name>", str)
+	}
+	if !utils.ContainsItemString(supportedHelmChartSourceKinds, sourceKind) {
+		return fmt.Errorf("source kind '%s' is not supported, can be one of: %s",
+			sourceKind, strings.Join(supportedHelmChartSourceKinds, ", "))
+	}
+
+	h.Name = sourceName
+	h.Kind = sourceKind
+
+	return nil
+}
+
+func (h *HelmChartSource) Type() string {
+	return "helmChartSource"
+}
+
+func (h *HelmChartSource) Description() string {
+	return fmt.Sprintf(
+		"source that contains the chart in the format '<kind>/<name>',"+
+			"where kind can be one of: (%s)",
+		strings.Join(supportedHelmChartSourceKinds, ", "),
+	)
+}
diff --git a/internal/flags/kustomization_source.go b/internal/flags/kustomization_source.go
new file mode 100644
index 00000000..b581f92f
--- /dev/null
+++ b/internal/flags/kustomization_source.go
@@ -0,0 +1,72 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package flags
+
+import (
+	"fmt"
+	"strings"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
+)
+
+var supportedKustomizationSourceKinds = []string{sourcev1.GitRepositoryKind, sourcev1.BucketKind}
+
+type KustomizationSource struct {
+	Kind string
+	Name string
+}
+
+func (k *KustomizationSource) String() string {
+	if k.Name == "" {
+		return ""
+	}
+	return fmt.Sprintf("%s/%s", k.Kind, k.Name)
+}
+
+func (k *KustomizationSource) Set(str string) error {
+	if strings.TrimSpace(str) == "" {
+		return fmt.Errorf("no kustomization source given, please specify %s",
+			k.Description())
+	}
+
+	sourceKind, sourceName := utils.ParseObjectKindName(str)
+	if sourceKind == "" {
+		sourceKind = sourcev1.GitRepositoryKind
+	}
+	if !utils.ContainsItemString(supportedKustomizationSourceKinds, sourceKind) {
+		return fmt.Errorf("source kind '%s' is not supported, can be one of: %s",
+			sourceKind, strings.Join(supportedKustomizationSourceKinds, ", "))
+	}
+
+	k.Name = sourceName
+	k.Kind = sourceKind
+
+	return nil
+}
+
+func (k *KustomizationSource) Type() string {
+	return "kustomizationSource"
+}
+
+func (k *KustomizationSource) Description() string {
+	return fmt.Sprintf(
+		"source that contains the Kubernetes manifests in the format '[<kind>/]<name>',"+
+			"where kind can be one of: (%s), if kind is not specified it defaults to GitRepository",
+		strings.Join(supportedKustomizationSourceKinds, ", "),
+	)
+}
diff --git a/internal/flags/log_level.go b/internal/flags/log_level.go
new file mode 100644
index 00000000..f2014a59
--- /dev/null
+++ b/internal/flags/log_level.go
@@ -0,0 +1,54 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package flags
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/fluxcd/toolkit/internal/utils"
+)
+
+var supportedLogLevels = []string{"debug", "info", "error"}
+
+type LogLevel string
+
+func (l *LogLevel) String() string {
+	return string(*l)
+}
+
+func (l *LogLevel) Set(str string) error {
+	if strings.TrimSpace(str) == "" {
+		return fmt.Errorf("no log level given, must be one of: %s",
+			strings.Join(supportedLogLevels, ", "))
+	}
+	if !utils.ContainsItemString(supportedLogLevels, str) {
+		return fmt.Errorf("unsupported log level '%s', must be one of: %s",
+			str, strings.Join(supportedLogLevels, ", "))
+
+	}
+	*l = LogLevel(str)
+	return nil
+}
+
+func (l *LogLevel) Type() string {
+	return "logLevel"
+}
+
+func (l *LogLevel) Description() string {
+	return fmt.Sprintf("log level, available options are: (%s)", strings.Join(supportedLogLevels, ", "))
+}
diff --git a/internal/flags/public_key_algorithm.go b/internal/flags/public_key_algorithm.go
new file mode 100644
index 00000000..545a627b
--- /dev/null
+++ b/internal/flags/public_key_algorithm.go
@@ -0,0 +1,53 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package flags
+
+import (
+	"fmt"
+	"strings"
+)
+
+var supportedPublicKeyAlgorithms = []string{"rsa", "ecdsa", "ed25519"}
+
+type PublicKeyAlgorithm string
+
+func (a *PublicKeyAlgorithm) String() string {
+	return string(*a)
+}
+
+func (a *PublicKeyAlgorithm) Set(str string) error {
+	if strings.TrimSpace(str) == "" {
+		return fmt.Errorf("no public key algorithm given, must be one of: %s",
+			strings.Join(supportedPublicKeyAlgorithms, ", "))
+	}
+	for _, v := range supportedPublicKeyAlgorithms {
+		if str == v {
+			*a = PublicKeyAlgorithm(str)
+			return nil
+		}
+	}
+	return fmt.Errorf("unsupported public key algorithm '%s', must be one of: %s",
+		str, strings.Join(supportedPublicKeyAlgorithms, ", "))
+}
+
+func (a *PublicKeyAlgorithm) Type() string {
+	return "publicKeyAlgorithm"
+}
+
+func (a *PublicKeyAlgorithm) Description() string {
+	return fmt.Sprintf("SSH public key algorithm (%s)", strings.Join(supportedPublicKeyAlgorithms, ", "))
+}
diff --git a/internal/flags/rsa_key_bits.go b/internal/flags/rsa_key_bits.go
new file mode 100644
index 00000000..6ba14290
--- /dev/null
+++ b/internal/flags/rsa_key_bits.go
@@ -0,0 +1,55 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package flags
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+var defaultRSAKeyBits = 2048
+
+type RSAKeyBits int
+
+func (b *RSAKeyBits) String() string {
+	return strconv.Itoa(int(*b))
+}
+
+func (b *RSAKeyBits) Set(str string) error {
+	if strings.TrimSpace(str) == "" {
+		*b = RSAKeyBits(defaultRSAKeyBits)
+		return nil
+	}
+	bits, err := strconv.Atoi(str)
+	if err != nil {
+		return err
+	}
+	if bits%8 != 0 {
+		return fmt.Errorf("RSA key bit size should be a multiples of 8")
+	}
+	*b = RSAKeyBits(bits)
+	return nil
+}
+
+func (b *RSAKeyBits) Type() string {
+	return "rsaKeyBits"
+}
+
+func (b *RSAKeyBits) Description() string {
+	return "SSH RSA public key bit size (multiplies of 8)"
+}
diff --git a/internal/flags/source_bucket_provider.go b/internal/flags/source_bucket_provider.go
new file mode 100644
index 00000000..63fa2f17
--- /dev/null
+++ b/internal/flags/source_bucket_provider.go
@@ -0,0 +1,58 @@
+/*
+Copyright 2020 The Flux CD contributors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package flags
+
+import (
+	"fmt"
+	"strings"
+
+	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
+	"github.com/fluxcd/toolkit/internal/utils"
+)
+
+var supportedSourceBucketProviders = []string{sourcev1.GenericBucketProvider, sourcev1.AmazonBucketProvider}
+
+type SourceBucketProvider string
+
+func (s *SourceBucketProvider) String() string {
+	return string(*s)
+}
+
+func (s *SourceBucketProvider) Set(str string) error {
+	if strings.TrimSpace(str) == "" {
+		return fmt.Errorf("no source bucket provider given, please specify %s",
+			s.Description())
+	}
+
+	if !utils.ContainsItemString(supportedSourceBucketProviders, str) {
+		return fmt.Errorf("source bucket provider '%s' is not supported, can be one of: %v",
+			str, strings.Join(supportedSourceBucketProviders, ", "))
+	}
+
+	return nil
+}
+
+func (s *SourceBucketProvider) Type() string {
+	return "sourceBucketProvider"
+}
+
+func (s *SourceBucketProvider) Description() string {
+	return fmt.Sprintf(
+		"the S3 compatible storage provider name, available options are: (%s)",
+		strings.Join(supportedSourceBucketProviders, ", "),
+	)
+}
diff --git a/cmd/gotk/utils.go b/internal/utils/utils.go
similarity index 87%
rename from cmd/gotk/utils.go
rename to internal/utils/utils.go
index 45c01f3a..7b53de9c 100644
--- a/cmd/gotk/utils.go
+++ b/internal/utils/utils.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package main
+package utils
 
 import (
 	"bufio"
@@ -60,7 +60,7 @@ const (
 	ModeCapture  ExecMode = "capture.stderr|stdout"
 )
 
-func (*Utils) execKubectlCommand(ctx context.Context, mode ExecMode, args ...string) (string, error) {
+func ExecKubectlCommand(ctx context.Context, mode ExecMode, args ...string) (string, error) {
 	var stdoutBuf, stderrBuf bytes.Buffer
 
 	c := exec.CommandContext(ctx, "kubectl", args...)
@@ -94,7 +94,7 @@ func (*Utils) execKubectlCommand(ctx context.Context, mode ExecMode, args ...str
 	return "", nil
 }
 
-func (*Utils) execTemplate(obj interface{}, tmpl, filename string) error {
+func ExecTemplate(obj interface{}, tmpl, filename string) error {
 	t, err := template.New("tmpl").Parse(tmpl)
 	if err != nil {
 		return err
@@ -124,8 +124,8 @@ func (*Utils) execTemplate(obj interface{}, tmpl, filename string) error {
 	return file.Sync()
 }
 
-func (*Utils) kubeClient(kubeConfigPath string) (client.Client, error) {
-	configFiles := utils.splitKubeConfigPath(kubeConfigPath)
+func KubeClient(kubeConfigPath string) (client.Client, error) {
+	configFiles := SplitKubeConfigPath(kubeConfigPath)
 	cfg, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
 		&clientcmd.ClientConfigLoadingRules{Precedence: configFiles},
 		&clientcmd.ConfigOverrides{}).ClientConfig()
@@ -151,11 +151,11 @@ func (*Utils) kubeClient(kubeConfigPath string) (client.Client, error) {
 	return kubeClient, nil
 }
 
-// splitKubeConfigPath splits the given KUBECONFIG path based on the runtime OS
+// SplitKubeConfigPath splits the given KUBECONFIG path based on the runtime OS
 // target.
 //
 // Ref: https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable
-func (*Utils) splitKubeConfigPath(path string) []string {
+func SplitKubeConfigPath(path string) []string {
 	var sep string
 	switch runtime.GOOS {
 	case "windows":
@@ -166,7 +166,7 @@ func (*Utils) splitKubeConfigPath(path string) []string {
 	return strings.Split(path, sep)
 }
 
-func (*Utils) writeFile(content, filename string) error {
+func WriteFile(content, filename string) error {
 	file, err := os.Create(filename)
 	if err != nil {
 		return err
@@ -181,7 +181,7 @@ func (*Utils) writeFile(content, filename string) error {
 	return file.Sync()
 }
 
-func (*Utils) copyFile(src, dst string) error {
+func CopyFile(src, dst string) error {
 	in, err := os.Open(src)
 	if err != nil {
 		return err
@@ -201,7 +201,7 @@ func (*Utils) copyFile(src, dst string) error {
 	return out.Close()
 }
 
-func (*Utils) containsItemString(s []string, e string) bool {
+func ContainsItemString(s []string, e string) bool {
 	for _, a := range s {
 		if a == e {
 			return true
@@ -210,7 +210,7 @@ func (*Utils) containsItemString(s []string, e string) bool {
 	return false
 }
 
-func (*Utils) parseObjectKindName(input string) (string, string) {
+func ParseObjectKindName(input string) (string, string) {
 	kind := ""
 	name := input
 	parts := strings.Split(input, "/")
@@ -221,7 +221,7 @@ func (*Utils) parseObjectKindName(input string) (string, string) {
 	return kind, name
 }
 
-func (*Utils) makeDependsOn(deps []string) []dependency.CrossNamespaceDependencyReference {
+func MakeDependsOn(deps []string) []dependency.CrossNamespaceDependencyReference {
 	refs := []dependency.CrossNamespaceDependencyReference{}
 	for _, dep := range deps {
 		parts := strings.Split(dep, "/")
@@ -241,9 +241,9 @@ func (*Utils) makeDependsOn(deps []string) []dependency.CrossNamespaceDependency
 	return refs
 }
 
-// generateKustomizationYaml is the equivalent of running
+// GenerateKustomizationYaml is the equivalent of running
 // 'kustomize create --autodetect' in the specified dir
-func (*Utils) generateKustomizationYaml(dirPath string) error {
+func GenerateKustomizationYaml(dirPath string) error {
 	fs := filesys.MakeFsOnDisk()
 	kfile := filepath.Join(dirPath, "kustomization.yaml")
 
@@ -321,7 +321,7 @@ func (*Utils) generateKustomizationYaml(dirPath string) error {
 	return nil
 }
 
-func (*Utils) printTable(writer io.Writer, header []string, rows [][]string) {
+func PrintTable(writer io.Writer, header []string, rows [][]string) {
 	table := tablewriter.NewWriter(writer)
 	table.SetHeader(header)
 	table.SetAutoWrapText(false)