diff --git a/cmd/flux/alert.go b/cmd/flux/alert.go
new file mode 100644
index 0000000000000000000000000000000000000000..e0bc28879314974bdab3c30b4160c67ba3573a47
--- /dev/null
+++ b/cmd/flux/alert.go
@@ -0,0 +1,51 @@
+/*
+Copyright 2021 The Flux authors
+
+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 main
+
+import (
+	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// notificationv1.Alert
+
+var alertType = apiType{
+	kind:      notificationv1.AlertKind,
+	humanKind: "alert",
+}
+
+type alertAdapter struct {
+	*notificationv1.Alert
+}
+
+func (a alertAdapter) asClientObject() client.Object {
+	return a.Alert
+}
+
+// notificationv1.Alert
+
+type alertListAdapter struct {
+	*notificationv1.AlertList
+}
+
+func (a alertListAdapter) asClientList() client.ObjectList {
+	return a.AlertList
+}
+
+func (a alertListAdapter) len() int {
+	return len(a.AlertList.Items)
+}
diff --git a/cmd/flux/alert_provider.go b/cmd/flux/alert_provider.go
new file mode 100644
index 0000000000000000000000000000000000000000..c2722ff009206dd355a3885e912773756f975237
--- /dev/null
+++ b/cmd/flux/alert_provider.go
@@ -0,0 +1,51 @@
+/*
+Copyright 2021 The Flux authors
+
+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 main
+
+import (
+	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// notificationv1.Provider
+
+var alertProviderType = apiType{
+	kind:      notificationv1.ProviderKind,
+	humanKind: "alert provider",
+}
+
+type alertProviderAdapter struct {
+	*notificationv1.Provider
+}
+
+func (a alertProviderAdapter) asClientObject() client.Object {
+	return a.Provider
+}
+
+// notificationv1.Provider
+
+type alertProviderListAdapter struct {
+	*notificationv1.ProviderList
+}
+
+func (a alertProviderListAdapter) asClientList() client.ObjectList {
+	return a.ProviderList
+}
+
+func (a alertProviderListAdapter) len() int {
+	return len(a.ProviderList.Items)
+}
diff --git a/cmd/flux/create_alert.go b/cmd/flux/create_alert.go
index 8dbeb80713cc1da0f833f0ccd2215532ddd44974..0718d03ab2a2941115fa1e6d35cb8cd8884d9134 100644
--- a/cmd/flux/create_alert.go
+++ b/cmd/flux/create_alert.go
@@ -116,7 +116,7 @@ func createAlertCmdRun(cmd *cobra.Command, args []string) error {
 	}
 
 	if createArgs.export {
-		return exportAlert(alert)
+		return printExport(exportAlert(&alert))
 	}
 
 	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
diff --git a/cmd/flux/create_alertprovider.go b/cmd/flux/create_alertprovider.go
index dc7164cfe3dc90a526bb7b0b9b1be7bfea1cd289..e9099ed956c7bc9652d50ee228dc20d2a0405ac9 100644
--- a/cmd/flux/create_alertprovider.go
+++ b/cmd/flux/create_alertprovider.go
@@ -113,7 +113,7 @@ func createAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
 	}
 
 	if createArgs.export {
-		return exportAlertProvider(provider)
+		return printExport(exportAlertProvider(&provider))
 	}
 
 	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
diff --git a/cmd/flux/create_helmrelease.go b/cmd/flux/create_helmrelease.go
index d316cfcf4bbff47058439d7bfe98a9a7c7fec98f..11886b7136872713a833ca17cae35d7db04ef743 100644
--- a/cmd/flux/create_helmrelease.go
+++ b/cmd/flux/create_helmrelease.go
@@ -219,7 +219,7 @@ func createHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
 	}
 
 	if createArgs.export {
-		return exportHelmRelease(helmRelease)
+		return printExport(exportHelmRelease(&helmRelease))
 	}
 
 	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
diff --git a/cmd/flux/create_kustomization.go b/cmd/flux/create_kustomization.go
index f5ca359389d8fb8ef303014832f0093464303868..650c2854148883e9cfbc87279d9f3428d1730e2a 100644
--- a/cmd/flux/create_kustomization.go
+++ b/cmd/flux/create_kustomization.go
@@ -211,7 +211,7 @@ func createKsCmdRun(cmd *cobra.Command, args []string) error {
 	}
 
 	if createArgs.export {
-		return exportKs(kustomization)
+		return printExport(exportKs(&kustomization))
 	}
 
 	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
diff --git a/cmd/flux/create_receiver.go b/cmd/flux/create_receiver.go
index 49ab3b807a2c0bbc969696c2adf3fe12bdef9488..9568fd6c5c6ab25b8bdee6c59be2c8ae4d985fe7 100644
--- a/cmd/flux/create_receiver.go
+++ b/cmd/flux/create_receiver.go
@@ -125,7 +125,7 @@ func createReceiverCmdRun(cmd *cobra.Command, args []string) error {
 	}
 
 	if createArgs.export {
-		return exportReceiver(receiver)
+		return printExport(exportReceiver(&receiver))
 	}
 
 	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
diff --git a/cmd/flux/create_source_bucket.go b/cmd/flux/create_source_bucket.go
index 212dc6854d11ac4ff789086a1e5dc7ccf463b924..20063017bb7bd7cd72ac5d8faad36707c6990862 100644
--- a/cmd/flux/create_source_bucket.go
+++ b/cmd/flux/create_source_bucket.go
@@ -144,7 +144,7 @@ func createSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
 	}
 
 	if createArgs.export {
-		return exportBucket(*bucket)
+		return printExport(exportBucket(bucket))
 	}
 
 	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
diff --git a/cmd/flux/create_source_git.go b/cmd/flux/create_source_git.go
index e245af6ba1341b8d5c7715b1aa8898cb68a5e161..0741b968f17adf7c85ccce5361e626f952b2c034 100644
--- a/cmd/flux/create_source_git.go
+++ b/cmd/flux/create_source_git.go
@@ -195,7 +195,7 @@ func createSourceGitCmdRun(cmd *cobra.Command, args []string) error {
 	}
 
 	if createArgs.export {
-		return exportGit(gitRepository)
+		return printExport(exportGit(&gitRepository))
 	}
 
 	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
diff --git a/cmd/flux/create_source_helm.go b/cmd/flux/create_source_helm.go
index c9940b22769b957089faaaff91baf155234d9c29..174e7ed996b8654e6d7dc30bbb63d3446a73cfc1 100644
--- a/cmd/flux/create_source_helm.go
+++ b/cmd/flux/create_source_helm.go
@@ -137,7 +137,7 @@ func createSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
 	}
 
 	if createArgs.export {
-		return exportHelmRepository(*helmRepository)
+		return printExport(exportHelmRepository(helmRepository))
 	}
 
 	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
diff --git a/cmd/flux/delete_alert.go b/cmd/flux/delete_alert.go
index 0f9ce12ede6a0de507a821cce2196d0f6f29a419..6788c28b8f8820237373dc8ff1d05b6428953efe 100644
--- a/cmd/flux/delete_alert.go
+++ b/cmd/flux/delete_alert.go
@@ -17,15 +17,8 @@ limitations under the License.
 package main
 
 import (
-	"context"
-	"fmt"
-
-	"github.com/manifoldco/promptui"
-	"github.com/spf13/cobra"
-	"k8s.io/apimachinery/pkg/types"
-
-	"github.com/fluxcd/flux2/internal/utils"
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
+	"github.com/spf13/cobra"
 )
 
 var deleteAlertCmd = &cobra.Command{
@@ -35,54 +28,12 @@ var deleteAlertCmd = &cobra.Command{
 	Example: `  # Delete an Alert and the Kubernetes resources created by it
   flux delete alert main
 `,
-	RunE: deleteAlertCmdRun,
+	RunE: deleteCommand{
+		apiType: alertType,
+		object:  universalAdapter{&notificationv1.Alert{}},
+	}.run,
 }
 
 func init() {
 	deleteCmd.AddCommand(deleteAlertCmd)
 }
-
-func deleteAlertCmdRun(cmd *cobra.Command, args []string) error {
-	if len(args) < 1 {
-		return fmt.Errorf("alert name is required")
-	}
-	name := args[0]
-
-	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
-	defer cancel()
-
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
-	if err != nil {
-		return err
-	}
-
-	namespacedName := types.NamespacedName{
-		Namespace: rootArgs.namespace,
-		Name:      name,
-	}
-
-	var alert notificationv1.Alert
-	err = kubeClient.Get(ctx, namespacedName, &alert)
-	if err != nil {
-		return err
-	}
-
-	if !deleteArgs.silent {
-		prompt := promptui.Prompt{
-			Label:     "Are you sure you want to delete this Alert",
-			IsConfirm: true,
-		}
-		if _, err := prompt.Run(); err != nil {
-			return fmt.Errorf("aborting")
-		}
-	}
-
-	logger.Actionf("deleting alert %s in %s namespace", name, rootArgs.namespace)
-	err = kubeClient.Delete(ctx, &alert)
-	if err != nil {
-		return err
-	}
-	logger.Successf("alert deleted")
-
-	return nil
-}
diff --git a/cmd/flux/delete_alertprovider.go b/cmd/flux/delete_alertprovider.go
index 3e58e963350e833ba995eee4febe6eaa9c537f6c..8a2e03771e58080ef757f119158ae727e607a9e0 100644
--- a/cmd/flux/delete_alertprovider.go
+++ b/cmd/flux/delete_alertprovider.go
@@ -17,15 +17,8 @@ limitations under the License.
 package main
 
 import (
-	"context"
-	"fmt"
-
-	"github.com/manifoldco/promptui"
-	"github.com/spf13/cobra"
-	"k8s.io/apimachinery/pkg/types"
-
-	"github.com/fluxcd/flux2/internal/utils"
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
+	"github.com/spf13/cobra"
 )
 
 var deleteAlertProviderCmd = &cobra.Command{
@@ -35,54 +28,12 @@ var deleteAlertProviderCmd = &cobra.Command{
 	Example: `  # Delete a Provider and the Kubernetes resources created by it
   flux delete alert-provider slack
 `,
-	RunE: deleteAlertProviderCmdRun,
+	RunE: deleteCommand{
+		apiType: alertProviderType,
+		object:  universalAdapter{&notificationv1.Provider{}},
+	}.run,
 }
 
 func init() {
 	deleteCmd.AddCommand(deleteAlertProviderCmd)
 }
-
-func deleteAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
-	if len(args) < 1 {
-		return fmt.Errorf("provider name is required")
-	}
-	name := args[0]
-
-	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
-	defer cancel()
-
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
-	if err != nil {
-		return err
-	}
-
-	namespacedName := types.NamespacedName{
-		Namespace: rootArgs.namespace,
-		Name:      name,
-	}
-
-	var alertProvider notificationv1.Provider
-	err = kubeClient.Get(ctx, namespacedName, &alertProvider)
-	if err != nil {
-		return err
-	}
-
-	if !deleteArgs.silent {
-		prompt := promptui.Prompt{
-			Label:     "Are you sure you want to delete this Provider",
-			IsConfirm: true,
-		}
-		if _, err := prompt.Run(); err != nil {
-			return fmt.Errorf("aborting")
-		}
-	}
-
-	logger.Actionf("deleting provider %s in %s namespace", name, rootArgs.namespace)
-	err = kubeClient.Delete(ctx, &alertProvider)
-	if err != nil {
-		return err
-	}
-	logger.Successf("provider deleted")
-
-	return nil
-}
diff --git a/cmd/flux/delete_receiver.go b/cmd/flux/delete_receiver.go
index deef850ed1467ba6fe69b0772de84c05b75661fe..489640ba6aa5a7a9c7d545d7b963b205dabab497 100644
--- a/cmd/flux/delete_receiver.go
+++ b/cmd/flux/delete_receiver.go
@@ -17,15 +17,8 @@ limitations under the License.
 package main
 
 import (
-	"context"
-	"fmt"
-
-	"github.com/manifoldco/promptui"
-	"github.com/spf13/cobra"
-	"k8s.io/apimachinery/pkg/types"
-
-	"github.com/fluxcd/flux2/internal/utils"
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
+	"github.com/spf13/cobra"
 )
 
 var deleteReceiverCmd = &cobra.Command{
@@ -35,54 +28,12 @@ var deleteReceiverCmd = &cobra.Command{
 	Example: `  # Delete an Receiver and the Kubernetes resources created by it
   flux delete receiver main
 `,
-	RunE: deleteReceiverCmdRun,
+	RunE: deleteCommand{
+		apiType: receiverType,
+		object:  universalAdapter{&notificationv1.Receiver{}},
+	}.run,
 }
 
 func init() {
 	deleteCmd.AddCommand(deleteReceiverCmd)
 }
-
-func deleteReceiverCmdRun(cmd *cobra.Command, args []string) error {
-	if len(args) < 1 {
-		return fmt.Errorf("receiver name is required")
-	}
-	name := args[0]
-
-	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
-	defer cancel()
-
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
-	if err != nil {
-		return err
-	}
-
-	namespacedName := types.NamespacedName{
-		Namespace: rootArgs.namespace,
-		Name:      name,
-	}
-
-	var receiver notificationv1.Receiver
-	err = kubeClient.Get(ctx, namespacedName, &receiver)
-	if err != nil {
-		return err
-	}
-
-	if !deleteArgs.silent {
-		prompt := promptui.Prompt{
-			Label:     "Are you sure you want to delete this Receiver",
-			IsConfirm: true,
-		}
-		if _, err := prompt.Run(); err != nil {
-			return fmt.Errorf("aborting")
-		}
-	}
-
-	logger.Actionf("deleting receiver %s in %s namespace", name, rootArgs.namespace)
-	err = kubeClient.Delete(ctx, &receiver)
-	if err != nil {
-		return err
-	}
-	logger.Successf("receiver deleted")
-
-	return nil
-}
diff --git a/cmd/flux/delete_source_helm.go b/cmd/flux/delete_source_helm.go
index cbe3f86c30e8092ebdc11b4d3faeb7b8ded4edcb..a744eaae7e7592619644e1dd6904b686501ccdb5 100644
--- a/cmd/flux/delete_source_helm.go
+++ b/cmd/flux/delete_source_helm.go
@@ -17,14 +17,8 @@ limitations under the License.
 package main
 
 import (
-	"context"
-	"fmt"
-
-	"github.com/fluxcd/flux2/internal/utils"
 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
-	"github.com/manifoldco/promptui"
 	"github.com/spf13/cobra"
-	"k8s.io/apimachinery/pkg/types"
 )
 
 var deleteSourceHelmCmd = &cobra.Command{
@@ -43,48 +37,3 @@ var deleteSourceHelmCmd = &cobra.Command{
 func init() {
 	deleteSourceCmd.AddCommand(deleteSourceHelmCmd)
 }
-
-func deleteSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
-	if len(args) < 1 {
-		return fmt.Errorf("name is required")
-	}
-	name := args[0]
-
-	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
-	defer cancel()
-
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
-	if err != nil {
-		return err
-	}
-
-	namespacedName := types.NamespacedName{
-		Namespace: rootArgs.namespace,
-		Name:      name,
-	}
-
-	var helmRepository sourcev1.HelmRepository
-	err = kubeClient.Get(ctx, namespacedName, &helmRepository)
-	if err != nil {
-		return err
-	}
-
-	if !deleteArgs.silent {
-		prompt := promptui.Prompt{
-			Label:     "Are you sure you want to delete this source",
-			IsConfirm: true,
-		}
-		if _, err := prompt.Run(); err != nil {
-			return fmt.Errorf("aborting")
-		}
-	}
-
-	logger.Actionf("deleting source %s in %s namespace", name, rootArgs.namespace)
-	err = kubeClient.Delete(ctx, &helmRepository)
-	if err != nil {
-		return err
-	}
-	logger.Successf("source deleted")
-
-	return nil
-}
diff --git a/cmd/flux/export.go b/cmd/flux/export.go
index 0b60fba97e2dfd309026a9a26e8636c5be02606e..99c11db193e7ad974467e8821543eda13b930a7c 100644
--- a/cmd/flux/export.go
+++ b/cmd/flux/export.go
@@ -20,7 +20,6 @@ import (
 	"bytes"
 	"context"
 	"fmt"
-
 	"github.com/spf13/cobra"
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -86,8 +85,7 @@ func (export exportCommand) run(cmd *cobra.Command, args []string) error {
 		}
 
 		if export.list.len() == 0 {
-			logger.Failuref("no objects found in %s namespace", rootArgs.namespace)
-			return nil
+			return fmt.Errorf("no objects found in %s namespace", rootArgs.namespace)
 		}
 
 		for i := 0; i < export.list.len(); i++ {
diff --git a/cmd/flux/export_alert.go b/cmd/flux/export_alert.go
index b6c7cf80021c50766a4d7a2103d7f9a259e9f6bb..cbb45fa7edaedd1aaec6fb0da7469e289da702cb 100644
--- a/cmd/flux/export_alert.go
+++ b/cmd/flux/export_alert.go
@@ -17,17 +17,9 @@ limitations under the License.
 package main
 
 import (
-	"context"
-	"fmt"
-
+	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
 	"github.com/spf13/cobra"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/yaml"
-
-	"github.com/fluxcd/flux2/internal/utils"
-	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
 )
 
 var exportAlertCmd = &cobra.Command{
@@ -40,60 +32,17 @@ var exportAlertCmd = &cobra.Command{
   # Export a Alert
   flux export alert main > main.yaml
 `,
-	RunE: exportAlertCmdRun,
+	RunE: exportCommand{
+		object: alertAdapter{&notificationv1.Alert{}},
+		list:   alertListAdapter{&notificationv1.AlertList{}},
+	}.run,
 }
 
 func init() {
 	exportCmd.AddCommand(exportAlertCmd)
 }
 
-func exportAlertCmdRun(cmd *cobra.Command, args []string) error {
-	if !exportArgs.all && len(args) < 1 {
-		return fmt.Errorf("name is required")
-	}
-
-	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
-	defer cancel()
-
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
-	if err != nil {
-		return err
-	}
-
-	if exportArgs.all {
-		var list notificationv1.AlertList
-		err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
-		if err != nil {
-			return err
-		}
-
-		if len(list.Items) == 0 {
-			logger.Failuref("no alerts found in %s namespace", rootArgs.namespace)
-			return nil
-		}
-
-		for _, alert := range list.Items {
-			if err := exportAlert(alert); err != nil {
-				return err
-			}
-		}
-	} else {
-		name := args[0]
-		namespacedName := types.NamespacedName{
-			Namespace: rootArgs.namespace,
-			Name:      name,
-		}
-		var alert notificationv1.Alert
-		err = kubeClient.Get(ctx, namespacedName, &alert)
-		if err != nil {
-			return err
-		}
-		return exportAlert(alert)
-	}
-	return nil
-}
-
-func exportAlert(alert notificationv1.Alert) error {
+func exportAlert(alert *notificationv1.Alert) interface{} {
 	gvk := notificationv1.GroupVersion.WithKind("Alert")
 	export := notificationv1.Alert{
 		TypeMeta: metav1.TypeMeta{
@@ -109,12 +58,13 @@ func exportAlert(alert notificationv1.Alert) error {
 		Spec: alert.Spec,
 	}
 
-	data, err := yaml.Marshal(export)
-	if err != nil {
-		return err
-	}
+	return export
+}
+
+func (ex alertAdapter) export() interface{} {
+	return exportAlert(ex.Alert)
+}
 
-	fmt.Println("---")
-	fmt.Println(resourceToString(data))
-	return nil
+func (ex alertListAdapter) exportItem(i int) interface{} {
+	return exportAlert(&ex.AlertList.Items[i])
 }
diff --git a/cmd/flux/export_alertprovider.go b/cmd/flux/export_alertprovider.go
index f3404e0383470dd894ff7f610946c4331ed04807..f144dd93afa7c0445c1a6bfd3548fa272fbd3cc5 100644
--- a/cmd/flux/export_alertprovider.go
+++ b/cmd/flux/export_alertprovider.go
@@ -17,17 +17,9 @@ limitations under the License.
 package main
 
 import (
-	"context"
-	"fmt"
-
+	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
 	"github.com/spf13/cobra"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/yaml"
-
-	"github.com/fluxcd/flux2/internal/utils"
-	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
 )
 
 var exportAlertProviderCmd = &cobra.Command{
@@ -40,60 +32,17 @@ var exportAlertProviderCmd = &cobra.Command{
   # Export a Provider
   flux export alert-provider slack > slack.yaml
 `,
-	RunE: exportAlertProviderCmdRun,
+	RunE: exportCommand{
+		object: alertProviderAdapter{&notificationv1.Provider{}},
+		list:   alertProviderListAdapter{&notificationv1.ProviderList{}},
+	}.run,
 }
 
 func init() {
 	exportCmd.AddCommand(exportAlertProviderCmd)
 }
 
-func exportAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
-	if !exportArgs.all && len(args) < 1 {
-		return fmt.Errorf("name is required")
-	}
-
-	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
-	defer cancel()
-
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
-	if err != nil {
-		return err
-	}
-
-	if exportArgs.all {
-		var list notificationv1.ProviderList
-		err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
-		if err != nil {
-			return err
-		}
-
-		if len(list.Items) == 0 {
-			logger.Failuref("no alertproviders found in %s namespace", rootArgs.namespace)
-			return nil
-		}
-
-		for _, alertProvider := range list.Items {
-			if err := exportAlertProvider(alertProvider); err != nil {
-				return err
-			}
-		}
-	} else {
-		name := args[0]
-		namespacedName := types.NamespacedName{
-			Namespace: rootArgs.namespace,
-			Name:      name,
-		}
-		var alertProvider notificationv1.Provider
-		err = kubeClient.Get(ctx, namespacedName, &alertProvider)
-		if err != nil {
-			return err
-		}
-		return exportAlertProvider(alertProvider)
-	}
-	return nil
-}
-
-func exportAlertProvider(alertProvider notificationv1.Provider) error {
+func exportAlertProvider(alertProvider *notificationv1.Provider) interface{} {
 	gvk := notificationv1.GroupVersion.WithKind("Provider")
 	export := notificationv1.Provider{
 		TypeMeta: metav1.TypeMeta{
@@ -108,13 +57,13 @@ func exportAlertProvider(alertProvider notificationv1.Provider) error {
 		},
 		Spec: alertProvider.Spec,
 	}
+	return export
+}
 
-	data, err := yaml.Marshal(export)
-	if err != nil {
-		return err
-	}
+func (ex alertProviderAdapter) export() interface{} {
+	return exportAlertProvider(ex.Provider)
+}
 
-	fmt.Println("---")
-	fmt.Println(resourceToString(data))
-	return nil
+func (ex alertProviderListAdapter) exportItem(i int) interface{} {
+	return exportAlertProvider(&ex.ProviderList.Items[i])
 }
diff --git a/cmd/flux/export_helmrelease.go b/cmd/flux/export_helmrelease.go
index 8266ddc83152c151ae0670db8a3c0d5d0c9ad3aa..fb19b6594cc616acef20dfb2a2674d680d2f5676 100644
--- a/cmd/flux/export_helmrelease.go
+++ b/cmd/flux/export_helmrelease.go
@@ -17,17 +17,9 @@ limitations under the License.
 package main
 
 import (
-	"context"
-	"fmt"
-
+	helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
 	"github.com/spf13/cobra"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/yaml"
-
-	"github.com/fluxcd/flux2/internal/utils"
-	helmv2 "github.com/fluxcd/helm-controller/api/v2beta1"
 )
 
 var exportHelmReleaseCmd = &cobra.Command{
@@ -41,60 +33,17 @@ var exportHelmReleaseCmd = &cobra.Command{
   # Export a HelmRelease
   flux export hr my-app > app-release.yaml
 `,
-	RunE: exportHelmReleaseCmdRun,
+	RunE: exportCommand{
+		object: helmReleaseAdapter{&helmv2.HelmRelease{}},
+		list:   helmReleaseListAdapter{&helmv2.HelmReleaseList{}},
+	}.run,
 }
 
 func init() {
 	exportCmd.AddCommand(exportHelmReleaseCmd)
 }
 
-func exportHelmReleaseCmdRun(cmd *cobra.Command, args []string) error {
-	if !exportArgs.all && len(args) < 1 {
-		return fmt.Errorf("name is required")
-	}
-
-	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
-	defer cancel()
-
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
-	if err != nil {
-		return err
-	}
-
-	if exportArgs.all {
-		var list helmv2.HelmReleaseList
-		err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
-		if err != nil {
-			return err
-		}
-
-		if len(list.Items) == 0 {
-			logger.Failuref("no helmrelease found in %s namespace", rootArgs.namespace)
-			return nil
-		}
-
-		for _, helmRelease := range list.Items {
-			if err := exportHelmRelease(helmRelease); err != nil {
-				return err
-			}
-		}
-	} else {
-		name := args[0]
-		namespacedName := types.NamespacedName{
-			Namespace: rootArgs.namespace,
-			Name:      name,
-		}
-		var helmRelease helmv2.HelmRelease
-		err = kubeClient.Get(ctx, namespacedName, &helmRelease)
-		if err != nil {
-			return err
-		}
-		return exportHelmRelease(helmRelease)
-	}
-	return nil
-}
-
-func exportHelmRelease(helmRelease helmv2.HelmRelease) error {
+func exportHelmRelease(helmRelease *helmv2.HelmRelease) interface{} {
 	gvk := helmv2.GroupVersion.WithKind(helmv2.HelmReleaseKind)
 	export := helmv2.HelmRelease{
 		TypeMeta: metav1.TypeMeta{
@@ -109,13 +58,13 @@ func exportHelmRelease(helmRelease helmv2.HelmRelease) error {
 		},
 		Spec: helmRelease.Spec,
 	}
+	return export
+}
 
-	data, err := yaml.Marshal(export)
-	if err != nil {
-		return err
-	}
+func (ex helmReleaseAdapter) export() interface{} {
+	return exportHelmRelease(ex.HelmRelease)
+}
 
-	fmt.Println("---")
-	fmt.Println(resourceToString(data))
-	return nil
+func (ex helmReleaseListAdapter) exportItem(i int) interface{} {
+	return exportHelmRelease(&ex.HelmReleaseList.Items[i])
 }
diff --git a/cmd/flux/export_kustomization.go b/cmd/flux/export_kustomization.go
index e90002e6886426c7841b3b75bf29020a45d8b6a2..393244431057c04223dded93ff1ff3977b0bb366 100644
--- a/cmd/flux/export_kustomization.go
+++ b/cmd/flux/export_kustomization.go
@@ -17,17 +17,9 @@ limitations under the License.
 package main
 
 import (
-	"context"
-	"fmt"
-
+	kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
 	"github.com/spf13/cobra"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/yaml"
-
-	"github.com/fluxcd/flux2/internal/utils"
-	kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
 )
 
 var exportKsCmd = &cobra.Command{
@@ -41,60 +33,17 @@ var exportKsCmd = &cobra.Command{
   # Export a Kustomization
   flux export kustomization my-app > kustomization.yaml
 `,
-	RunE: exportKsCmdRun,
+	RunE: exportCommand{
+		object: kustomizationAdapter{&kustomizev1.Kustomization{}},
+		list:   kustomizationListAdapter{&kustomizev1.KustomizationList{}},
+	}.run,
 }
 
 func init() {
 	exportCmd.AddCommand(exportKsCmd)
 }
 
-func exportKsCmdRun(cmd *cobra.Command, args []string) error {
-	if !exportArgs.all && len(args) < 1 {
-		return fmt.Errorf("kustomization name is required")
-	}
-
-	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
-	defer cancel()
-
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
-	if err != nil {
-		return err
-	}
-
-	if exportArgs.all {
-		var list kustomizev1.KustomizationList
-		err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
-		if err != nil {
-			return err
-		}
-
-		if len(list.Items) == 0 {
-			logger.Failuref("no kustomizations found in %s namespace", rootArgs.namespace)
-			return nil
-		}
-
-		for _, kustomization := range list.Items {
-			if err := exportKs(kustomization); err != nil {
-				return err
-			}
-		}
-	} else {
-		name := args[0]
-		namespacedName := types.NamespacedName{
-			Namespace: rootArgs.namespace,
-			Name:      name,
-		}
-		var kustomization kustomizev1.Kustomization
-		err = kubeClient.Get(ctx, namespacedName, &kustomization)
-		if err != nil {
-			return err
-		}
-		return exportKs(kustomization)
-	}
-	return nil
-}
-
-func exportKs(kustomization kustomizev1.Kustomization) error {
+func exportKs(kustomization *kustomizev1.Kustomization) interface{} {
 	gvk := kustomizev1.GroupVersion.WithKind("Kustomization")
 	export := kustomizev1.Kustomization{
 		TypeMeta: metav1.TypeMeta{
@@ -110,12 +59,13 @@ func exportKs(kustomization kustomizev1.Kustomization) error {
 		Spec: kustomization.Spec,
 	}
 
-	data, err := yaml.Marshal(export)
-	if err != nil {
-		return err
-	}
+	return export
+}
+
+func (ex kustomizationAdapter) export() interface{} {
+	return exportKs(ex.Kustomization)
+}
 
-	fmt.Println("---")
-	fmt.Println(resourceToString(data))
-	return nil
+func (ex kustomizationListAdapter) exportItem(i int) interface{} {
+	return exportKs(&ex.KustomizationList.Items[i])
 }
diff --git a/cmd/flux/export_receiver.go b/cmd/flux/export_receiver.go
index 700d87564cca585948fc36ddcab546624bf26e10..771ceb587a43085a16b9e114c6d4e35499c53d89 100644
--- a/cmd/flux/export_receiver.go
+++ b/cmd/flux/export_receiver.go
@@ -17,17 +17,9 @@ limitations under the License.
 package main
 
 import (
-	"context"
-	"fmt"
-
+	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
 	"github.com/spf13/cobra"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/yaml"
-
-	"github.com/fluxcd/flux2/internal/utils"
-	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
 )
 
 var exportReceiverCmd = &cobra.Command{
@@ -40,60 +32,17 @@ var exportReceiverCmd = &cobra.Command{
   # Export a Receiver
   flux export receiver main > main.yaml
 `,
-	RunE: exportReceiverCmdRun,
+	RunE: exportCommand{
+		list:   receiverListAdapter{&notificationv1.ReceiverList{}},
+		object: receiverAdapter{&notificationv1.Receiver{}},
+	}.run,
 }
 
 func init() {
 	exportCmd.AddCommand(exportReceiverCmd)
 }
 
-func exportReceiverCmdRun(cmd *cobra.Command, args []string) error {
-	if !exportArgs.all && len(args) < 1 {
-		return fmt.Errorf("name is required")
-	}
-
-	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
-	defer cancel()
-
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
-	if err != nil {
-		return err
-	}
-
-	if exportArgs.all {
-		var list notificationv1.ReceiverList
-		err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
-		if err != nil {
-			return err
-		}
-
-		if len(list.Items) == 0 {
-			logger.Failuref("no receivers found in %s namespace", rootArgs.namespace)
-			return nil
-		}
-
-		for _, receiver := range list.Items {
-			if err := exportReceiver(receiver); err != nil {
-				return err
-			}
-		}
-	} else {
-		name := args[0]
-		namespacedName := types.NamespacedName{
-			Namespace: rootArgs.namespace,
-			Name:      name,
-		}
-		var receiver notificationv1.Receiver
-		err = kubeClient.Get(ctx, namespacedName, &receiver)
-		if err != nil {
-			return err
-		}
-		return exportReceiver(receiver)
-	}
-	return nil
-}
-
-func exportReceiver(receiver notificationv1.Receiver) error {
+func exportReceiver(receiver *notificationv1.Receiver) interface{} {
 	gvk := notificationv1.GroupVersion.WithKind("Receiver")
 	export := notificationv1.Receiver{
 		TypeMeta: metav1.TypeMeta{
@@ -109,12 +58,13 @@ func exportReceiver(receiver notificationv1.Receiver) error {
 		Spec: receiver.Spec,
 	}
 
-	data, err := yaml.Marshal(export)
-	if err != nil {
-		return err
-	}
+	return export
+}
+
+func (ex receiverAdapter) export() interface{} {
+	return exportReceiver(ex.Receiver)
+}
 
-	fmt.Println("---")
-	fmt.Println(resourceToString(data))
-	return nil
+func (ex receiverListAdapter) exportItem(i int) interface{} {
+	return exportReceiver(&ex.ReceiverList.Items[i])
 }
diff --git a/cmd/flux/export_secret.go b/cmd/flux/export_secret.go
new file mode 100644
index 0000000000000000000000000000000000000000..ab75a156386b9b430ecfe35f4532ceedd97f03d6
--- /dev/null
+++ b/cmd/flux/export_secret.go
@@ -0,0 +1,133 @@
+/*
+Copyright 2021 The Flux authors
+
+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 main
+
+import (
+	"context"
+	"fmt"
+	"github.com/fluxcd/flux2/internal/utils"
+	"github.com/spf13/cobra"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// exportableWithSecret represents a type that you can fetch from the Kubernetes
+// API, get a secretRef from the spec, then tidy up for serialising.
+type exportableWithSecret interface {
+	adapter
+	exportable
+	secret() *types.NamespacedName
+}
+
+// exportableWithSecretList represents a type that has a list of values, each of
+// which is exportableWithSecret.
+type exportableWithSecretList interface {
+	listAdapter
+	exportableList
+	secretItem(i int) *types.NamespacedName
+}
+
+type exportWithSecretCommand struct {
+	apiType
+	object exportableWithSecret
+	list   exportableWithSecretList
+}
+
+func (export exportWithSecretCommand) run(cmd *cobra.Command, args []string) error {
+	if !exportArgs.all && len(args) < 1 {
+		return fmt.Errorf("name is required")
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
+	defer cancel()
+
+	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
+	if err != nil {
+		return err
+	}
+
+	if exportArgs.all {
+		err = kubeClient.List(ctx, export.list.asClientList(), client.InNamespace(rootArgs.namespace))
+		if err != nil {
+			return err
+		}
+
+		if export.list.len() == 0 {
+			return fmt.Errorf("no objects found in %s namespace", rootArgs.namespace)
+		}
+
+		for i := 0; i < export.list.len(); i++ {
+			if err = printExport(export.list.exportItem(i)); err != nil {
+				return err
+			}
+
+			if exportSourceWithCred {
+				if export.list.secretItem(i) != nil {
+					namespacedName := *export.list.secretItem(i)
+					return printSecretCredentials(ctx, kubeClient, namespacedName)
+				}
+			}
+		}
+	} else {
+		name := args[0]
+		namespacedName := types.NamespacedName{
+			Namespace: rootArgs.namespace,
+			Name:      name,
+		}
+		err = kubeClient.Get(ctx, namespacedName, export.object.asClientObject())
+		if err != nil {
+			return err
+		}
+
+		if err := printExport(export.object.export()); err != nil {
+			return err
+		}
+
+		if exportSourceWithCred {
+			if export.object.secret() != nil {
+				namespacedName := *export.object.secret()
+				return printSecretCredentials(ctx, kubeClient, namespacedName)
+			}
+		}
+
+	}
+	return nil
+}
+
+func printSecretCredentials(ctx context.Context, kubeClient client.Client, nsName types.NamespacedName) error {
+	var cred corev1.Secret
+	err := kubeClient.Get(ctx, nsName, &cred)
+	if err != nil {
+		return fmt.Errorf("failed to retrieve secret %s, error: %w", nsName.Name, err)
+	}
+
+	exported := corev1.Secret{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "v1",
+			Kind:       "Secret",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      nsName.Name,
+			Namespace: nsName.Namespace,
+		},
+		Data: cred.Data,
+		Type: cred.Type,
+	}
+	return printExport(exported)
+}
diff --git a/cmd/flux/export_source_bucket.go b/cmd/flux/export_source_bucket.go
index 2e0e8cdc6a69283177cdc9576d66f03dcb73486e..8e8c245bf490cc86210d69f8c373fcdc222e44fe 100644
--- a/cmd/flux/export_source_bucket.go
+++ b/cmd/flux/export_source_bucket.go
@@ -17,18 +17,10 @@ limitations under the License.
 package main
 
 import (
-	"context"
-	"fmt"
-
+	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
 	"github.com/spf13/cobra"
-	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/yaml"
-
-	"github.com/fluxcd/flux2/internal/utils"
-	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
 )
 
 var exportSourceBucketCmd = &cobra.Command{
@@ -41,70 +33,17 @@ var exportSourceBucketCmd = &cobra.Command{
   # Export a Bucket source including the static credentials
   flux export source bucket my-bucket --with-credentials > source.yaml
 `,
-	RunE: exportSourceBucketCmdRun,
+	RunE: exportWithSecretCommand{
+		list:   bucketListAdapter{&sourcev1.BucketList{}},
+		object: bucketAdapter{&sourcev1.Bucket{}},
+	}.run,
 }
 
 func init() {
 	exportSourceCmd.AddCommand(exportSourceBucketCmd)
 }
 
-func exportSourceBucketCmdRun(cmd *cobra.Command, args []string) error {
-	if !exportArgs.all && len(args) < 1 {
-		return fmt.Errorf("name is required")
-	}
-
-	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
-	defer cancel()
-
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
-	if err != nil {
-		return err
-	}
-
-	if exportArgs.all {
-		var list sourcev1.BucketList
-		err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
-		if err != nil {
-			return err
-		}
-
-		if len(list.Items) == 0 {
-			logger.Failuref("no source found in %s namespace", rootArgs.namespace)
-			return nil
-		}
-
-		for _, bucket := range list.Items {
-			if err := exportBucket(bucket); err != nil {
-				return err
-			}
-			if exportSourceWithCred {
-				if err := exportBucketCredentials(ctx, kubeClient, bucket); err != nil {
-					return err
-				}
-			}
-		}
-	} else {
-		name := args[0]
-		namespacedName := types.NamespacedName{
-			Namespace: rootArgs.namespace,
-			Name:      name,
-		}
-		var bucket sourcev1.Bucket
-		err = kubeClient.Get(ctx, namespacedName, &bucket)
-		if err != nil {
-			return err
-		}
-		if err := exportBucket(bucket); err != nil {
-			return err
-		}
-		if exportSourceWithCred {
-			return exportBucketCredentials(ctx, kubeClient, bucket)
-		}
-	}
-	return nil
-}
-
-func exportBucket(source sourcev1.Bucket) error {
+func exportBucket(source *sourcev1.Bucket) interface{} {
 	gvk := sourcev1.GroupVersion.WithKind(sourcev1.BucketKind)
 	export := sourcev1.Bucket{
 		TypeMeta: metav1.TypeMeta{
@@ -119,49 +58,34 @@ func exportBucket(source sourcev1.Bucket) error {
 		},
 		Spec: source.Spec,
 	}
-
-	data, err := yaml.Marshal(export)
-	if err != nil {
-		return err
-	}
-
-	fmt.Println("---")
-	fmt.Println(resourceToString(data))
-	return nil
+	return export
 }
 
-func exportBucketCredentials(ctx context.Context, kubeClient client.Client, source sourcev1.Bucket) error {
+func getBucketSecret(source *sourcev1.Bucket) *types.NamespacedName {
 	if source.Spec.SecretRef != nil {
 		namespacedName := types.NamespacedName{
 			Namespace: source.Namespace,
 			Name:      source.Spec.SecretRef.Name,
 		}
-		var cred corev1.Secret
-		err := kubeClient.Get(ctx, namespacedName, &cred)
-		if err != nil {
-			return fmt.Errorf("failed to retrieve secret %s, error: %w", namespacedName.Name, err)
-		}
-
-		exported := corev1.Secret{
-			TypeMeta: metav1.TypeMeta{
-				APIVersion: "v1",
-				Kind:       "Secret",
-			},
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      namespacedName.Name,
-				Namespace: namespacedName.Namespace,
-			},
-			Data: cred.Data,
-			Type: cred.Type,
-		}
-
-		data, err := yaml.Marshal(exported)
-		if err != nil {
-			return err
-		}
 
-		fmt.Println("---")
-		fmt.Println(resourceToString(data))
+		return &namespacedName
 	}
+
 	return nil
 }
+
+func (ex bucketAdapter) secret() *types.NamespacedName {
+	return getBucketSecret(ex.Bucket)
+}
+
+func (ex bucketListAdapter) secretItem(i int) *types.NamespacedName {
+	return getBucketSecret(&ex.BucketList.Items[i])
+}
+
+func (ex bucketAdapter) export() interface{} {
+	return exportBucket(ex.Bucket)
+}
+
+func (ex bucketListAdapter) exportItem(i int) interface{} {
+	return exportBucket(&ex.BucketList.Items[i])
+}
diff --git a/cmd/flux/export_source_git.go b/cmd/flux/export_source_git.go
index e8062fac970b7df2e5b276e00bf699d546f6f155..ebe2352d82d69a33e497b10d3c552cbb942afbd3 100644
--- a/cmd/flux/export_source_git.go
+++ b/cmd/flux/export_source_git.go
@@ -17,18 +17,10 @@ limitations under the License.
 package main
 
 import (
-	"context"
-	"fmt"
-
+	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
 	"github.com/spf13/cobra"
-	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/yaml"
-
-	"github.com/fluxcd/flux2/internal/utils"
-	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
 )
 
 var exportSourceGitCmd = &cobra.Command{
@@ -41,70 +33,17 @@ var exportSourceGitCmd = &cobra.Command{
   # Export a GitRepository source including the SSH key pair or basic auth credentials
   flux export source git my-private-repo --with-credentials > source.yaml
 `,
-	RunE: exportSourceGitCmdRun,
+	RunE: exportWithSecretCommand{
+		object: gitRepositoryAdapter{&sourcev1.GitRepository{}},
+		list:   gitRepositoryListAdapter{&sourcev1.GitRepositoryList{}},
+	}.run,
 }
 
 func init() {
 	exportSourceCmd.AddCommand(exportSourceGitCmd)
 }
 
-func exportSourceGitCmdRun(cmd *cobra.Command, args []string) error {
-	if !exportArgs.all && len(args) < 1 {
-		return fmt.Errorf("name is required")
-	}
-
-	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
-	defer cancel()
-
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
-	if err != nil {
-		return err
-	}
-
-	if exportArgs.all {
-		var list sourcev1.GitRepositoryList
-		err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
-		if err != nil {
-			return err
-		}
-
-		if len(list.Items) == 0 {
-			logger.Failuref("no source found in %s namespace", rootArgs.namespace)
-			return nil
-		}
-
-		for _, repository := range list.Items {
-			if err := exportGit(repository); err != nil {
-				return err
-			}
-			if exportSourceWithCred {
-				if err := exportGitCredentials(ctx, kubeClient, repository); err != nil {
-					return err
-				}
-			}
-		}
-	} else {
-		name := args[0]
-		namespacedName := types.NamespacedName{
-			Namespace: rootArgs.namespace,
-			Name:      name,
-		}
-		var repository sourcev1.GitRepository
-		err = kubeClient.Get(ctx, namespacedName, &repository)
-		if err != nil {
-			return err
-		}
-		if err := exportGit(repository); err != nil {
-			return err
-		}
-		if exportSourceWithCred {
-			return exportGitCredentials(ctx, kubeClient, repository)
-		}
-	}
-	return nil
-}
-
-func exportGit(source sourcev1.GitRepository) error {
+func exportGit(source *sourcev1.GitRepository) interface{} {
 	gvk := sourcev1.GroupVersion.WithKind(sourcev1.GitRepositoryKind)
 	export := sourcev1.GitRepository{
 		TypeMeta: metav1.TypeMeta{
@@ -120,48 +59,33 @@ func exportGit(source sourcev1.GitRepository) error {
 		Spec: source.Spec,
 	}
 
-	data, err := yaml.Marshal(export)
-	if err != nil {
-		return err
-	}
-
-	fmt.Println("---")
-	fmt.Println(resourceToString(data))
-	return nil
+	return export
 }
 
-func exportGitCredentials(ctx context.Context, kubeClient client.Client, source sourcev1.GitRepository) error {
+func getGitSecret(source *sourcev1.GitRepository) *types.NamespacedName {
 	if source.Spec.SecretRef != nil {
 		namespacedName := types.NamespacedName{
 			Namespace: source.Namespace,
 			Name:      source.Spec.SecretRef.Name,
 		}
-		var cred corev1.Secret
-		err := kubeClient.Get(ctx, namespacedName, &cred)
-		if err != nil {
-			return fmt.Errorf("failed to retrieve secret %s, error: %w", namespacedName.Name, err)
-		}
+		return &namespacedName
+	}
 
-		exported := corev1.Secret{
-			TypeMeta: metav1.TypeMeta{
-				APIVersion: "v1",
-				Kind:       "Secret",
-			},
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      namespacedName.Name,
-				Namespace: namespacedName.Namespace,
-			},
-			Data: cred.Data,
-			Type: cred.Type,
-		}
+	return nil
+}
 
-		data, err := yaml.Marshal(exported)
-		if err != nil {
-			return err
-		}
+func (ex gitRepositoryAdapter) secret() *types.NamespacedName {
+	return getGitSecret(ex.GitRepository)
+}
 
-		fmt.Println("---")
-		fmt.Println(resourceToString(data))
-	}
-	return nil
+func (ex gitRepositoryListAdapter) secretItem(i int) *types.NamespacedName {
+	return getGitSecret(&ex.GitRepositoryList.Items[i])
+}
+
+func (ex gitRepositoryAdapter) export() interface{} {
+	return exportGit(ex.GitRepository)
+}
+
+func (ex gitRepositoryListAdapter) exportItem(i int) interface{} {
+	return exportGit(&ex.GitRepositoryList.Items[i])
 }
diff --git a/cmd/flux/export_source_helm.go b/cmd/flux/export_source_helm.go
index e8e51f932d6de4b0a8632b8f3924ead4365cb761..53f79ae77181465fdfc5daf588de3039f729817c 100644
--- a/cmd/flux/export_source_helm.go
+++ b/cmd/flux/export_source_helm.go
@@ -17,18 +17,10 @@ limitations under the License.
 package main
 
 import (
-	"context"
-	"fmt"
-
+	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
 	"github.com/spf13/cobra"
-	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/yaml"
-
-	"github.com/fluxcd/flux2/internal/utils"
-	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
 )
 
 var exportSourceHelmCmd = &cobra.Command{
@@ -41,70 +33,17 @@ var exportSourceHelmCmd = &cobra.Command{
   # Export a HelmRepository source including the basic auth credentials
   flux export source helm my-private-repo --with-credentials > source.yaml
 `,
-	RunE: exportSourceHelmCmdRun,
+	RunE: exportWithSecretCommand{
+		list:   helmRepositoryListAdapter{&sourcev1.HelmRepositoryList{}},
+		object: helmRepositoryAdapter{&sourcev1.HelmRepository{}},
+	}.run,
 }
 
 func init() {
 	exportSourceCmd.AddCommand(exportSourceHelmCmd)
 }
 
-func exportSourceHelmCmdRun(cmd *cobra.Command, args []string) error {
-	if !exportArgs.all && len(args) < 1 {
-		return fmt.Errorf("name is required")
-	}
-
-	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
-	defer cancel()
-
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
-	if err != nil {
-		return err
-	}
-
-	if exportArgs.all {
-		var list sourcev1.HelmRepositoryList
-		err = kubeClient.List(ctx, &list, client.InNamespace(rootArgs.namespace))
-		if err != nil {
-			return err
-		}
-
-		if len(list.Items) == 0 {
-			logger.Failuref("no source found in %s namespace", rootArgs.namespace)
-			return nil
-		}
-
-		for _, repository := range list.Items {
-			if err := exportHelmRepository(repository); err != nil {
-				return err
-			}
-			if exportSourceWithCred {
-				if err := exportHelmCredentials(ctx, kubeClient, repository); err != nil {
-					return err
-				}
-			}
-		}
-	} else {
-		name := args[0]
-		namespacedName := types.NamespacedName{
-			Namespace: rootArgs.namespace,
-			Name:      name,
-		}
-		var repository sourcev1.HelmRepository
-		err = kubeClient.Get(ctx, namespacedName, &repository)
-		if err != nil {
-			return err
-		}
-		if err := exportHelmRepository(repository); err != nil {
-			return err
-		}
-		if exportSourceWithCred {
-			return exportHelmCredentials(ctx, kubeClient, repository)
-		}
-	}
-	return nil
-}
-
-func exportHelmRepository(source sourcev1.HelmRepository) error {
+func exportHelmRepository(source *sourcev1.HelmRepository) interface{} {
 	gvk := sourcev1.GroupVersion.WithKind(sourcev1.HelmRepositoryKind)
 	export := sourcev1.HelmRepository{
 		TypeMeta: metav1.TypeMeta{
@@ -119,49 +58,32 @@ func exportHelmRepository(source sourcev1.HelmRepository) error {
 		},
 		Spec: source.Spec,
 	}
-
-	data, err := yaml.Marshal(export)
-	if err != nil {
-		return err
-	}
-
-	fmt.Println("---")
-	fmt.Println(resourceToString(data))
-	return nil
+	return export
 }
 
-func exportHelmCredentials(ctx context.Context, kubeClient client.Client, source sourcev1.HelmRepository) error {
+func getHelmSecret(source *sourcev1.HelmRepository) *types.NamespacedName {
 	if source.Spec.SecretRef != nil {
 		namespacedName := types.NamespacedName{
 			Namespace: source.Namespace,
 			Name:      source.Spec.SecretRef.Name,
 		}
-		var cred corev1.Secret
-		err := kubeClient.Get(ctx, namespacedName, &cred)
-		if err != nil {
-			return fmt.Errorf("failed to retrieve secret %s, error: %w", namespacedName.Name, err)
-		}
+		return &namespacedName
+	}
+	return nil
+}
 
-		exported := corev1.Secret{
-			TypeMeta: metav1.TypeMeta{
-				APIVersion: "v1",
-				Kind:       "Secret",
-			},
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      namespacedName.Name,
-				Namespace: namespacedName.Namespace,
-			},
-			Data: cred.Data,
-			Type: cred.Type,
-		}
+func (ex helmRepositoryAdapter) secret() *types.NamespacedName {
+	return getHelmSecret(ex.HelmRepository)
+}
 
-		data, err := yaml.Marshal(exported)
-		if err != nil {
-			return err
-		}
+func (ex helmRepositoryListAdapter) secretItem(i int) *types.NamespacedName {
+	return getHelmSecret(&ex.HelmRepositoryList.Items[i])
+}
 
-		fmt.Println("---")
-		fmt.Println(resourceToString(data))
-	}
-	return nil
+func (ex helmRepositoryAdapter) export() interface{} {
+	return exportHelmRepository(ex.HelmRepository)
+}
+
+func (ex helmRepositoryListAdapter) exportItem(i int) interface{} {
+	return exportHelmRepository(&ex.HelmRepositoryList.Items[i])
 }
diff --git a/cmd/flux/get_alert.go b/cmd/flux/get_alert.go
index 5d4ab78dc6ec86fa8099d7f56201d26a1e5cb969..e6c4a615ae15af27e6330b35ef4779898c16f848 100644
--- a/cmd/flux/get_alert.go
+++ b/cmd/flux/get_alert.go
@@ -17,19 +17,11 @@ limitations under the License.
 package main
 
 import (
-	"context"
-	"os"
 	"strconv"
 	"strings"
 
-	"github.com/spf13/cobra"
-	apimeta "k8s.io/apimachinery/pkg/api/meta"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-
-	"github.com/fluxcd/flux2/internal/utils"
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
-	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/spf13/cobra"
 )
 
 var getAlertCmd = &cobra.Command{
@@ -40,64 +32,26 @@ var getAlertCmd = &cobra.Command{
 	Example: `  # List all Alerts and their status
   flux get alerts
 `,
-	RunE: getAlertCmdRun,
+	RunE: getCommand{
+		apiType: alertType,
+		list:    &alertListAdapter{&notificationv1.AlertList{}},
+	}.run,
 }
 
 func init() {
 	getCmd.AddCommand(getAlertCmd)
 }
 
-func getAlertCmdRun(cmd *cobra.Command, args []string) error {
-	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
-	defer cancel()
-
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
-	if err != nil {
-		return err
-	}
-
-	var listOpts []client.ListOption
-	if !getArgs.allNamespaces {
-		listOpts = append(listOpts, client.InNamespace(rootArgs.namespace))
-	}
-	var list notificationv1.AlertList
-	err = kubeClient.List(ctx, &list, listOpts...)
-	if err != nil {
-		return err
-	}
-
-	if len(list.Items) == 0 {
-		logger.Failuref("no alerts found in %s namespace", rootArgs.namespace)
-		return nil
-	}
+func (s alertListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
+	item := s.Items[i]
+	status, msg := statusAndMessage(item.Status.Conditions)
+	return append(nameColumns(&item, includeNamespace, includeKind), status, msg, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
+}
 
-	header := []string{"Name", "Ready", "Message", "Suspended"}
-	if getArgs.allNamespaces {
-		header = append([]string{"Namespace"}, header...)
-	}
-	var rows [][]string
-	for _, alert := range list.Items {
-		row := []string{}
-		if c := apimeta.FindStatusCondition(alert.Status.Conditions, meta.ReadyCondition); c != nil {
-			row = []string{
-				alert.GetName(),
-				string(c.Status),
-				c.Message,
-				strings.Title(strconv.FormatBool(alert.Spec.Suspend)),
-			}
-		} else {
-			row = []string{
-				alert.GetName(),
-				string(metav1.ConditionFalse),
-				"waiting to be reconciled",
-				strings.Title(strconv.FormatBool(alert.Spec.Suspend)),
-			}
-		}
-		if getArgs.allNamespaces {
-			row = append([]string{alert.Namespace}, row...)
-		}
-		rows = append(rows, row)
+func (s alertListAdapter) headers(includeNamespace bool) []string {
+	headers := []string{"Name", "Ready", "Message", "Suspended"}
+	if includeNamespace {
+		return append(namespaceHeader, headers...)
 	}
-	utils.PrintTable(os.Stdout, header, rows)
-	return nil
+	return headers
 }
diff --git a/cmd/flux/get_alertprovider.go b/cmd/flux/get_alertprovider.go
index 3cf58e3cea6b9175466d9cd3b265b0dafc520b93..f0c5b8fed8f2c00efddaf187c2c3d10838ef8d1a 100644
--- a/cmd/flux/get_alertprovider.go
+++ b/cmd/flux/get_alertprovider.go
@@ -17,17 +17,8 @@ limitations under the License.
 package main
 
 import (
-	"context"
-	"os"
-
-	"github.com/spf13/cobra"
-	apimeta "k8s.io/apimachinery/pkg/api/meta"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-
-	"github.com/fluxcd/flux2/internal/utils"
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
-	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/spf13/cobra"
 )
 
 var getAlertProviderCmd = &cobra.Command{
@@ -38,62 +29,26 @@ var getAlertProviderCmd = &cobra.Command{
 	Example: `  # List all Providers and their status
   flux get alert-providers
 `,
-	RunE: getAlertProviderCmdRun,
+	RunE: getCommand{
+		apiType: alertProviderType,
+		list:    alertProviderListAdapter{&notificationv1.ProviderList{}},
+	}.run,
 }
 
 func init() {
 	getCmd.AddCommand(getAlertProviderCmd)
 }
 
-func getAlertProviderCmdRun(cmd *cobra.Command, args []string) error {
-	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
-	defer cancel()
-
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
-	if err != nil {
-		return err
-	}
-
-	var listOpts []client.ListOption
-	if !getArgs.allNamespaces {
-		listOpts = append(listOpts, client.InNamespace(rootArgs.namespace))
-	}
-	var list notificationv1.ProviderList
-	err = kubeClient.List(ctx, &list, listOpts...)
-	if err != nil {
-		return err
-	}
-
-	if len(list.Items) == 0 {
-		logger.Failuref("no providers found in %s namespace", rootArgs.namespace)
-		return nil
-	}
+func (s alertProviderListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
+	item := s.Items[i]
+	status, msg := statusAndMessage(item.Status.Conditions)
+	return append(nameColumns(&item, includeNamespace, includeKind), status, msg)
+}
 
-	header := []string{"Name", "Ready", "Message"}
-	if getArgs.allNamespaces {
-		header = append([]string{"Namespace"}, header...)
-	}
-	var rows [][]string
-	for _, provider := range list.Items {
-		row := []string{}
-		if c := apimeta.FindStatusCondition(provider.Status.Conditions, meta.ReadyCondition); c != nil {
-			row = []string{
-				provider.GetName(),
-				string(c.Status),
-				c.Message,
-			}
-		} else {
-			row = []string{
-				provider.GetName(),
-				string(metav1.ConditionFalse),
-				"waiting to be reconciled",
-			}
-		}
-		if getArgs.allNamespaces {
-			row = append([]string{provider.Namespace}, row...)
-		}
-		rows = append(rows, row)
+func (s alertProviderListAdapter) headers(includeNamespace bool) []string {
+	headers := []string{"Name", "Ready", "Message"}
+	if includeNamespace {
+		return append(namespaceHeader, headers...)
 	}
-	utils.PrintTable(os.Stdout, header, rows)
-	return nil
+	return headers
 }
diff --git a/cmd/flux/get_receiver.go b/cmd/flux/get_receiver.go
index 422bb8ddb1431380b2df18d45a73cb278f2a1e3d..d58ced0cc47415f345a363a6c68b6a4e2362dde6 100644
--- a/cmd/flux/get_receiver.go
+++ b/cmd/flux/get_receiver.go
@@ -17,19 +17,11 @@ limitations under the License.
 package main
 
 import (
-	"context"
-	"os"
 	"strconv"
 	"strings"
 
-	"github.com/spf13/cobra"
-	apimeta "k8s.io/apimachinery/pkg/api/meta"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-
-	"github.com/fluxcd/flux2/internal/utils"
 	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
-	"github.com/fluxcd/pkg/apis/meta"
+	"github.com/spf13/cobra"
 )
 
 var getReceiverCmd = &cobra.Command{
@@ -40,61 +32,26 @@ var getReceiverCmd = &cobra.Command{
 	Example: `  # List all Receiver and their status
   flux get receivers
 `,
-	RunE: getReceiverCmdRun,
+	RunE: getCommand{
+		apiType: receiverType,
+		list:    receiverListAdapter{&notificationv1.ReceiverList{}},
+	}.run,
 }
 
 func init() {
 	getCmd.AddCommand(getReceiverCmd)
 }
 
-func getReceiverCmdRun(cmd *cobra.Command, args []string) error {
-	ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
-	defer cancel()
-
-	kubeClient, err := utils.KubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
-	if err != nil {
-		return err
-	}
-
-	var listOpts []client.ListOption
-	if !getArgs.allNamespaces {
-		listOpts = append(listOpts, client.InNamespace(rootArgs.namespace))
-	}
-	var list notificationv1.ReceiverList
-	err = kubeClient.List(ctx, &list, listOpts...)
-	if err != nil {
-		return err
-	}
-
-	if len(list.Items) == 0 {
-		logger.Failuref("no receivers found in %s namespace", rootArgs.namespace)
-		return nil
-	}
+func (s receiverListAdapter) summariseItem(i int, includeNamespace bool, includeKind bool) []string {
+	item := s.Items[i]
+	status, msg := statusAndMessage(item.Status.Conditions)
+	return append(nameColumns(&item, includeNamespace, includeKind), status, msg, strings.Title(strconv.FormatBool(item.Spec.Suspend)))
+}
 
-	header := []string{"Name", "Ready", "Message", "Suspended"}
-	if getArgs.allNamespaces {
-		header = append([]string{"Namespace"}, header...)
-	}
-	var rows [][]string
-	for _, receiver := range list.Items {
-		var row []string
-		if c := apimeta.FindStatusCondition(receiver.Status.Conditions, meta.ReadyCondition); c != nil {
-			row = []string{
-				receiver.GetName(),
-				string(c.Status),
-				c.Message,
-				strings.Title(strconv.FormatBool(receiver.Spec.Suspend)),
-			}
-		} else {
-			row = []string{
-				receiver.GetName(),
-				string(metav1.ConditionFalse),
-				"waiting to be reconciled",
-				strings.Title(strconv.FormatBool(receiver.Spec.Suspend)),
-			}
-		}
-		rows = append(rows, row)
+func (s receiverListAdapter) headers(includeNamespace bool) []string {
+	headers := []string{"Name", "Ready", "Message", "Suspended"}
+	if includeNamespace {
+		return append(namespaceHeader, headers...)
 	}
-	utils.PrintTable(os.Stdout, header, rows)
-	return nil
+	return headers
 }
diff --git a/cmd/flux/receiver.go b/cmd/flux/receiver.go
new file mode 100644
index 0000000000000000000000000000000000000000..3de4cbb3597ab3255b635ded48d2d92cbdc7c2b2
--- /dev/null
+++ b/cmd/flux/receiver.go
@@ -0,0 +1,51 @@
+/*
+Copyright 2021 The Flux authors
+
+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 main
+
+import (
+	notificationv1 "github.com/fluxcd/notification-controller/api/v1beta1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// notificationv1.Receiver
+
+var receiverType = apiType{
+	kind:      notificationv1.ReceiverKind,
+	humanKind: "receiver",
+}
+
+type receiverAdapter struct {
+	*notificationv1.Receiver
+}
+
+func (a receiverAdapter) asClientObject() client.Object {
+	return a.Receiver
+}
+
+// notificationv1.Receiver
+
+type receiverListAdapter struct {
+	*notificationv1.ReceiverList
+}
+
+func (a receiverListAdapter) asClientList() client.ObjectList {
+	return a.ReceiverList
+}
+
+func (a receiverListAdapter) len() int {
+	return len(a.ReceiverList.Items)
+}