diff --git a/.gitignore b/.gitignore index 9804be6bd..4b131eb8b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ vendor/ .claude do-not-commit/ pkg/tools/embedded/ +docs/plans/ diff --git a/cli/cmd/app_create.go b/cli/cmd/app_create.go index 9e1f25e3c..a35ad8a6f 100644 --- a/cli/cmd/app_create.go +++ b/cli/cmd/app_create.go @@ -16,8 +16,6 @@ type createAppOpts struct { func (r *runners) InitAppCreate(parent *cobra.Command) *cobra.Command { opts := createAppOpts{} - var outputFormat string - cmd := &cobra.Command{ Use: "create NAME", Short: "Create a new application", @@ -44,17 +42,15 @@ replicated app create "Custom App" --output table`, return errors.New("missing app name") } opts.name = args[0] - return r.createApp(ctx, cmd, opts, outputFormat) + return r.createApp(ctx, cmd, opts) }, SilenceUsage: true, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") - return cmd } -func (r *runners) createApp(ctx context.Context, cmd *cobra.Command, opts createAppOpts, outputFormat string) error { +func (r *runners) createApp(ctx context.Context, cmd *cobra.Command, opts createAppOpts) error { kotsRestClient := kotsclient.VendorV3Client{ HTTPClient: *r.platformAPI, } @@ -75,5 +71,5 @@ func (r *runners) createApp(ctx context.Context, cmd *cobra.Command, opts create }, } - return print.Apps(outputFormat, r.w, apps) + return print.Apps(r.outputFormat, r.w, apps) } diff --git a/cli/cmd/app_hostname_ls.go b/cli/cmd/app_hostname_ls.go index bd881d0c2..53b1a76d9 100644 --- a/cli/cmd/app_hostname_ls.go +++ b/cli/cmd/app_hostname_ls.go @@ -14,8 +14,6 @@ import ( ) func (r *runners) InitAppHostnameListCommand(parent *cobra.Command) *cobra.Command { - var outputFormat string - cmd := &cobra.Command{ Use: "ls", Aliases: []string{"list"}, @@ -65,20 +63,18 @@ replicated app hostname ls --app myapp --output json`, }, RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - return r.listAppHostnames(ctx, outputFormat) + return r.listAppHostnames(ctx) }, SilenceUsage: true, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") - return cmd } -func (r *runners) listAppHostnames(ctx context.Context, outputFormat string) error { +func (r *runners) listAppHostnames(ctx context.Context) error { // Only show spinners for table output - showSpinners := outputFormat == "table" + showSpinners := r.outputFormat == "table" log := logger.NewLogger(r.w).SetIsTerminal(r.stdoutIsTTY) // Resolve app ID from slug or ID @@ -141,7 +137,7 @@ func (r *runners) listAppHostnames(ctx context.Context, outputFormat string) err hostnameStrings := extractHostnameStrings(mergedHostnames) // Output based on format - if outputFormat == "json" { + if r.outputFormat == "json" { jsonBytes, err := json.MarshalIndent(hostnameStrings, "", " ") if err != nil { return errors.Wrap(err, "marshal json") @@ -153,11 +149,11 @@ func (r *runners) listAppHostnames(ctx context.Context, outputFormat string) err return nil } - if outputFormat == "table" { + if r.outputFormat == "table" { return printHostnamesTable(r.w, hostnameStrings) } - return errors.Errorf("unsupported output format: %s", outputFormat) + return errors.Errorf("unsupported output format: %s", r.outputFormat) } // extractHostnameStrings extracts just the hostname strings from the merged hostnames diff --git a/cli/cmd/app_ls.go b/cli/cmd/app_ls.go index 36726a4b5..ac7fc40e1 100644 --- a/cli/cmd/app_ls.go +++ b/cli/cmd/app_ls.go @@ -11,8 +11,6 @@ import ( ) func (r *runners) InitAppList(parent *cobra.Command) *cobra.Command { - var outputFormat string - cmd := &cobra.Command{ Use: "ls [NAME]", Aliases: []string{"list"}, @@ -39,24 +37,22 @@ replicated app ls --output json replicated app ls "App Name" --output table`, RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - return r.listApps(ctx, cmd, args, outputFormat) + return r.listApps(ctx, cmd, args) }, SilenceUsage: true, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") - return cmd } -func (r *runners) listApps(ctx context.Context, cmd *cobra.Command, args []string, outputFormat string) error { +func (r *runners) listApps(ctx context.Context, cmd *cobra.Command, args []string) error { kotsApps, err := r.kotsAPI.ListApps(ctx, false) if err != nil { return errors.Wrap(err, "list apps") } if len(args) == 0 { - return print.Apps(outputFormat, r.w, kotsApps) + return print.Apps(r.outputFormat, r.w, kotsApps) } appSearch := args[0] @@ -66,5 +62,5 @@ func (r *runners) listApps(ctx context.Context, cmd *cobra.Command, args []strin resultApps = append(resultApps, app) } } - return print.Apps(outputFormat, r.w, resultApps) + return print.Apps(r.outputFormat, r.w, resultApps) } diff --git a/cli/cmd/app_rm.go b/cli/cmd/app_rm.go index 219b58be8..bc90808bc 100644 --- a/cli/cmd/app_rm.go +++ b/cli/cmd/app_rm.go @@ -17,8 +17,6 @@ type deleteAppOpts struct { func (r *runners) InitAppRm(parent *cobra.Command) *cobra.Command { opts := deleteAppOpts{} - var outputFormat string - cmd := &cobra.Command{ Use: "rm NAME", Aliases: []string{"delete"}, @@ -42,20 +40,19 @@ replicated app delete "Custom App" --output json`, if len(args) != 1 { return errors.New("missing app slug or id") } - return r.deleteApp(ctx, cmd, args[0], opts, outputFormat) + return r.deleteApp(ctx, cmd, args[0], opts) }, SilenceUsage: true, } parent.AddCommand(cmd) cmd.Flags().BoolVarP(&opts.force, "force", "f", false, "Skip confirmation prompt. There is no undo for this action.") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } -func (r *runners) deleteApp(ctx context.Context, cmd *cobra.Command, appName string, opts deleteAppOpts, outputFormat string) error { +func (r *runners) deleteApp(ctx context.Context, cmd *cobra.Command, appName string, opts deleteAppOpts) error { log := logger.NewLogger(r.w).SetIsTerminal(r.stdoutIsTTY) - showSpinners := outputFormat == "table" + showSpinners := r.outputFormat == "table" if showSpinners { log.ActionWithSpinner("Fetching App") @@ -77,7 +74,7 @@ func (r *runners) deleteApp(ctx context.Context, cmd *cobra.Command, appName str }, } - err = print.Apps(outputFormat, r.w, apps) + err = print.Apps(r.outputFormat, r.w, apps) if err != nil { return errors.Wrap(err, "print app") } diff --git a/cli/cmd/channel_adoption.go b/cli/cmd/channel_adoption.go index 415c5f6fe..004756b5a 100644 --- a/cli/cmd/channel_adoption.go +++ b/cli/cmd/channel_adoption.go @@ -9,9 +9,10 @@ import ( func (r *runners) InitChannelAdoption(parent *cobra.Command) { cmd := &cobra.Command{ - Use: "adoption CHANNEL_ID", - Short: "Print channel adoption statistics by license type", - Long: "Print channel adoption statistics by license type", + Use: "adoption CHANNEL_ID", + Short: "Print channel adoption statistics by license type", + Long: "Print channel adoption statistics by license type", + SilenceUsage: true, } cmd.Hidden = true // Not supported in KOTS parent.AddCommand(cmd) @@ -34,7 +35,7 @@ func (r *runners) channelAdoption(cmd *cobra.Command, args []string) error { return err } - if err = print.ChannelAdoption(r.w, appChan.Adoption); err != nil { + if err = print.ChannelAdoption(r.outputFormat, r.w, appChan.Adoption); err != nil { return err } diff --git a/cli/cmd/channel_counts.go b/cli/cmd/channel_counts.go index bcf7f7a50..138f64962 100644 --- a/cli/cmd/channel_counts.go +++ b/cli/cmd/channel_counts.go @@ -9,9 +9,10 @@ import ( func (r *runners) InitChannelCounts(parent *cobra.Command) { cmd := &cobra.Command{ - Use: "counts CHANNEL_ID", - Short: "Print channel license counts", - Long: "Print channel license counts", + Use: "counts CHANNEL_ID", + Short: "Print channel license counts", + Long: "Print channel license counts", + SilenceUsage: true, } cmd.Hidden = true // Not supported in KOTS parent.AddCommand(cmd) @@ -34,7 +35,7 @@ func (r *runners) channelCounts(cmd *cobra.Command, args []string) error { return err } - if err = print.LicenseCounts(r.w, appChan.LicenseCounts); err != nil { + if err = print.LicenseCounts(r.outputFormat, r.w, appChan.LicenseCounts); err != nil { return err } } else if r.appType == "kots" { diff --git a/cli/cmd/channel_create.go b/cli/cmd/channel_create.go index da65f7611..2a58f93a7 100644 --- a/cli/cmd/channel_create.go +++ b/cli/cmd/channel_create.go @@ -17,7 +17,6 @@ func (r *runners) InitChannelCreate(parent *cobra.Command) { cmd.Flags().StringVar(&r.args.channelCreateName, "name", "", "The name of this channel") cmd.Flags().StringVar(&r.args.channelCreateDescription, "description", "", "A longer description of this channel") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.RunE = r.channelCreate } diff --git a/cli/cmd/channel_inspect.go b/cli/cmd/channel_inspect.go index 24348c4f1..f6d5b27d8 100644 --- a/cli/cmd/channel_inspect.go +++ b/cli/cmd/channel_inspect.go @@ -15,7 +15,6 @@ func (r *runners) InitChannelInspect(parent *cobra.Command) { Long: "Show full details for a channel", } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.RunE = r.channelInspect } diff --git a/cli/cmd/channel_ls.go b/cli/cmd/channel_ls.go index 2144c6a7a..baddd3c63 100644 --- a/cli/cmd/channel_ls.go +++ b/cli/cmd/channel_ls.go @@ -15,7 +15,6 @@ func (r *runners) InitChannelList(parent *cobra.Command) { } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.RunE = r.channelList } diff --git a/cli/cmd/channel_releases.go b/cli/cmd/channel_releases.go index 5acb51bb0..0064ef4a2 100644 --- a/cli/cmd/channel_releases.go +++ b/cli/cmd/channel_releases.go @@ -25,7 +25,7 @@ replicated channel releases Stable --output json replicated channel releases Stable --page 1 --page-size 50`, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") + cmd.Flags().IntVar(&r.args.channelReleasesPage, "page", 0, "The page to fetch (KOTS apps only).") cmd.Flags().IntVar(&r.args.channelReleasesPageSize, "page-size", 0, "The number of releases per page (KOTS apps only).") diff --git a/cli/cmd/cluster_addon_create_objectstore.go b/cli/cmd/cluster_addon_create_objectstore.go index b11e4aaeb..03c134e4d 100644 --- a/cli/cmd/cluster_addon_create_objectstore.go +++ b/cli/cmd/cluster_addon_create_objectstore.go @@ -61,7 +61,6 @@ func (r *runners) clusterAddonCreateObjectStoreFlags(cmd *cobra.Command) error { } cmd.Flags().DurationVar(&r.args.clusterAddonCreateObjectStoreDuration, "wait", 0, "Wait duration for add-on to be ready before exiting (leave empty to not wait)") cmd.Flags().BoolVar(&r.args.clusterAddonCreateObjectStoreDryRun, "dry-run", false, "Simulate creation to verify that your inputs are valid without actually creating an add-on") - cmd.Flags().StringVarP(&r.args.clusterAddonCreateObjectStoreOutput, "output", "o", "table", "The output format to use. One of: json|table|wide") return nil } @@ -86,7 +85,7 @@ func (r *runners) clusterAddonCreateObjectStoreCreateRun() error { return err } - return print.Addon(r.args.clusterAddonCreateObjectStoreOutput, r.w, addon) + return print.Addon(r.outputFormat, r.w, addon) } func (r *runners) createAndWaitForClusterAddonCreateObjectStore(opts kotsclient.CreateClusterAddonObjectStoreOpts, waitDuration time.Duration) (*types.ClusterAddon, error) { diff --git a/cli/cmd/cluster_addon_ls.go b/cli/cmd/cluster_addon_ls.go index 0c76b65cd..579fe6bc4 100644 --- a/cli/cmd/cluster_addon_ls.go +++ b/cli/cmd/cluster_addon_ls.go @@ -6,8 +6,7 @@ import ( ) type clusterAddonLsArgs struct { - clusterID string - outputFormat string + clusterID string } func (r *runners) InitClusterAddonLs(parent *cobra.Command) *cobra.Command { @@ -31,30 +30,20 @@ replicated cluster addon ls CLUSTER_ID_OR_NAME --output wide`, Args: cobra.ExactArgs(1), RunE: func(_ *cobra.Command, cmdArgs []string) error { args.clusterID = cmdArgs[0] - return r.addonClusterLsRun(args) + return r.addonClusterLsRun(args.clusterID) }, ValidArgsFunction: r.completeClusterIDs, } parent.AddCommand(cmd) - err := clusterAddonLsFlags(cmd, &args) - if err != nil { - panic(err) - } - return cmd } -func clusterAddonLsFlags(cmd *cobra.Command, args *clusterAddonLsArgs) error { - cmd.Flags().StringVarP(&args.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") - return nil -} - -func (r *runners) addonClusterLsRun(args clusterAddonLsArgs) error { - addons, err := r.kotsAPI.ListClusterAddons(args.clusterID) +func (r *runners) addonClusterLsRun(clusterID string) error { + addons, err := r.kotsAPI.ListClusterAddons(clusterID) if err != nil { return err } - return print.Addons(args.outputFormat, r.w, addons, true) + return print.Addons(r.outputFormat, r.w, addons, true) } diff --git a/cli/cmd/cluster_create.go b/cli/cmd/cluster_create.go index a32269afe..78301c0dd 100644 --- a/cli/cmd/cluster_create.go +++ b/cli/cmd/cluster_create.go @@ -89,8 +89,6 @@ replicated cluster create --distribution eks --version 1.21 --nodes 3 --addon ob cmd.Flags().BoolVar(&r.args.createClusterDryRun, "dry-run", false, "Dry run") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") - _ = cmd.MarkFlagRequired("distribution") return cmd @@ -205,7 +203,6 @@ func (r *runners) createAndWaitForAddons(clusterID string) error { r.args.clusterAddonCreateObjectStoreClusterID = clusterID r.args.clusterAddonCreateObjectStoreDryRun = r.args.createClusterDryRun r.args.clusterAddonCreateObjectStoreDuration = r.args.createClusterWaitDuration - r.args.clusterAddonCreateObjectStoreOutput = r.outputFormat err := r.clusterAddonCreateObjectStoreCreateRun() if err != nil { diff --git a/cli/cmd/cluster_ls.go b/cli/cmd/cluster_ls.go index 1ebc131c4..f2c463743 100644 --- a/cli/cmd/cluster_ls.go +++ b/cli/cmd/cluster_ls.go @@ -46,7 +46,6 @@ replicated cluster ls --output wide`, cmd.Flags().BoolVar(&r.args.lsClusterShowTerminated, "show-terminated", false, "when set, only show terminated clusters") cmd.Flags().StringVar(&r.args.lsClusterStartTime, "start-time", "", "start time for the query (Format: 2006-01-02T15:04:05Z)") cmd.Flags().StringVar(&r.args.lsClusterEndTime, "end-time", "", "end time for the query (Format: 2006-01-02T15:04:05Z)") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") cmd.Flags().BoolVarP(&r.args.lsClusterWatch, "watch", "w", false, "watch clusters") return cmd diff --git a/cli/cmd/cluster_nodegroup_ls.go b/cli/cmd/cluster_nodegroup_ls.go index 2864dad80..ad1a4590c 100644 --- a/cli/cmd/cluster_nodegroup_ls.go +++ b/cli/cmd/cluster_nodegroup_ls.go @@ -31,8 +31,6 @@ replicated cluster nodegroup ls CLUSTER_ID_OR_NAME --output wide`, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") - return cmd } diff --git a/cli/cmd/cluster_port_expose.go b/cli/cmd/cluster_port_expose.go index 199b9b882..9d0edc089 100644 --- a/cli/cmd/cluster_port_expose.go +++ b/cli/cmd/cluster_port_expose.go @@ -41,7 +41,6 @@ replicated cluster port expose CLUSTER_ID_OR_NAME --port 8080 --protocol https - } cmd.Flags().StringSliceVar(&r.args.clusterExposePortProtocols, "protocol", []string{"http", "https"}, `Protocol to expose (valid values are "http", "https", "ws" and "wss")`) cmd.Flags().BoolVar(&r.args.clusterExposePortIsWildcard, "wildcard", false, "Create a wildcard DNS entry and TLS certificate for this port") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") return cmd } diff --git a/cli/cmd/cluster_port_ls.go b/cli/cmd/cluster_port_ls.go index 80c7b2026..511359a11 100644 --- a/cli/cmd/cluster_port_ls.go +++ b/cli/cmd/cluster_port_ls.go @@ -27,8 +27,6 @@ replicated cluster port ls CLUSTER_ID_OR_NAME --output wide`, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") - return cmd } diff --git a/cli/cmd/cluster_port_rm.go b/cli/cmd/cluster_port_rm.go index e419e7813..e8456819e 100644 --- a/cli/cmd/cluster_port_rm.go +++ b/cli/cmd/cluster_port_rm.go @@ -31,7 +31,6 @@ replicated cluster port rm CLUSTER_ID_OR_NAME --id PORT_ID --output json`, parent.AddCommand(cmd) cmd.Flags().StringVar(&r.args.clusterPortRemoveAddonID, "id", "", "ID of the port to remove (required)") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") // Deprecated flags cmd.Flags().IntVar(&r.args.clusterPortRemovePort, "port", 0, "Port to remove") diff --git a/cli/cmd/cluster_update_nodegroup.go b/cli/cmd/cluster_update_nodegroup.go index 4db002104..9dda94650 100644 --- a/cli/cmd/cluster_update_nodegroup.go +++ b/cli/cmd/cluster_update_nodegroup.go @@ -34,8 +34,6 @@ replicated cluster update nodegroup CLUSTER_ID_OR_NAME --nodegroup-id NODEGROUP_ cmd.Flags().StringVar(&r.args.updateClusterNodeGroupMinCount, "min-nodes", "", "The minimum number of nodes in the nodegroup") cmd.Flags().StringVar(&r.args.updateClusterNodeGroupMaxCount, "max-nodes", "", "The maximum number of nodes in the nodegroup") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") - return cmd } diff --git a/cli/cmd/cluster_update_ttl.go b/cli/cmd/cluster_update_ttl.go index d16c6aa9f..8479e51d4 100644 --- a/cli/cmd/cluster_update_ttl.go +++ b/cli/cmd/cluster_update_ttl.go @@ -22,7 +22,6 @@ replicated cluster update ttl CLUSTER_ID_OR_NAME --ttl 24h`, parent.AddCommand(cmd) cmd.Flags().StringVar(&r.args.updateClusterTTL, "ttl", "", "Update TTL which starts from the moment the cluster is running (duration, max 48h).") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.MarkFlagRequired("ttl") diff --git a/cli/cmd/cluster_upgrade.go b/cli/cmd/cluster_upgrade.go index 2ab8bb826..e1eca343d 100644 --- a/cli/cmd/cluster_upgrade.go +++ b/cli/cmd/cluster_upgrade.go @@ -36,8 +36,6 @@ replicated cluster upgrade CLUSTER_ID_OR_NAME --version 1.31 --wait 30m`, cmd.Flags().BoolVar(&r.args.upgradeClusterDryRun, "dry-run", false, "Dry run") cmd.Flags().DurationVar(&r.args.upgradeClusterWaitDuration, "wait", 0, "Wait duration for cluster to be ready (leave empty to not wait)") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") - _ = cmd.MarkFlagRequired("version") return cmd diff --git a/cli/cmd/cluster_versions.go b/cli/cmd/cluster_versions.go index d19a44ad1..06122fe3e 100644 --- a/cli/cmd/cluster_versions.go +++ b/cli/cmd/cluster_versions.go @@ -26,7 +26,6 @@ replicated cluster versions --output json`, parent.AddCommand(cmd) cmd.Flags().StringVar(&r.args.lsVersionsDistribution, "distribution", "", "Kubernetes distribution to filter by.") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } diff --git a/cli/cmd/collection_addmodel.go b/cli/cmd/collection_addmodel.go index e2fdda651..966a1d035 100644 --- a/cli/cmd/collection_addmodel.go +++ b/cli/cmd/collection_addmodel.go @@ -22,8 +22,6 @@ func (r *runners) InitCollectionAddModel(parent *cobra.Command) *cobra.Command { cmd.Flags().StringVar(&r.args.modelCollectionAddModelCollectionID, "collection-id", "", "The ID of the collection") cmd.MarkFlagRequired("collection-id") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") - return cmd } diff --git a/cli/cmd/collection_create.go b/cli/cmd/collection_create.go index 143b7aa70..876f7f75e 100644 --- a/cli/cmd/collection_create.go +++ b/cli/cmd/collection_create.go @@ -21,8 +21,6 @@ func (r *runners) InitCollectionCreate(parent *cobra.Command) *cobra.Command { cmd.Flags().StringVar(&r.args.modelCollectionCreateName, "name", "", "The name of the collection") cmd.MarkFlagRequired("name") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") - return cmd } diff --git a/cli/cmd/collection_ls.go b/cli/cmd/collection_ls.go index 2513ca629..ae0839e7c 100644 --- a/cli/cmd/collection_ls.go +++ b/cli/cmd/collection_ls.go @@ -16,7 +16,6 @@ func (r *runners) InitCollectionList(parent *cobra.Command) *cobra.Command { SilenceUsage: true, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } diff --git a/cli/cmd/collection_removemodel.go b/cli/cmd/collection_removemodel.go index f926a2895..854b84370 100644 --- a/cli/cmd/collection_removemodel.go +++ b/cli/cmd/collection_removemodel.go @@ -22,8 +22,6 @@ func (r *runners) InitCollectionRemoveModel(parent *cobra.Command) *cobra.Comman cmd.Flags().StringVar(&r.args.modelCollectionRmModelCollectionID, "collection-id", "", "The ID of the collection") cmd.MarkFlagRequired("collection-id") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") - return cmd } diff --git a/cli/cmd/collection_rm.go b/cli/cmd/collection_rm.go index 8aab3eb1a..c78499706 100644 --- a/cli/cmd/collection_rm.go +++ b/cli/cmd/collection_rm.go @@ -18,8 +18,6 @@ func (r *runners) InitCollectionRemove(parent *cobra.Command) *cobra.Command { } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") - return cmd } diff --git a/cli/cmd/customer_create.go b/cli/cmd/customer_create.go index 29ecb58a6..1f84c8381 100644 --- a/cli/cmd/customer_create.go +++ b/cli/cmd/customer_create.go @@ -36,8 +36,6 @@ type createCustomerOpts struct { func (r *runners) InitCustomersCreateCommand(parent *cobra.Command) *cobra.Command { opts := createCustomerOpts{} - var outputFormat string - cmd := &cobra.Command{ Use: "create", Short: "Create a new customer for the current application", @@ -63,7 +61,7 @@ replicated customer create --app myapp --name "Full Options Inc" --custom-id "FU --airgap --snapshot --kots-install --embedded-cluster-download \ --support-bundle-upload --ensure-channel`, RunE: func(cmd *cobra.Command, args []string) error { - return r.createCustomer(cmd, opts, outputFormat) + return r.createCustomer(cmd, opts) }, SilenceUsage: false, SilenceErrors: false, @@ -91,14 +89,13 @@ replicated customer create --app myapp --name "Full Options Inc" --custom-id "FU cmd.Flags().BoolVar(&opts.IsDeveloperModeEnabled, "developer-mode", false, "If set, Replicated SDK installed in dev mode will use mock data.") cmd.Flags().StringVar(&opts.Email, "email", "", "Email address of the customer that is to be created.") cmd.Flags().StringVar(&opts.CustomerType, "type", "dev", "The license type to create. One of: dev|trial|paid|community|test (default: dev)") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.MarkFlagRequired("channel") return cmd } -func (r *runners) createCustomer(cmd *cobra.Command, opts createCustomerOpts, outputFormat string) (err error) { +func (r *runners) createCustomer(cmd *cobra.Command, opts createCustomerOpts) (err error) { defer func() { printIfError(cmd, err) }() @@ -176,7 +173,7 @@ func (r *runners) createCustomer(cmd *cobra.Command, opts createCustomerOpts, ou return errors.Wrap(err, "create customer") } - err = print.Customer(outputFormat, r.w, customer) + err = print.Customer(r.outputFormat, r.w, customer) if err != nil { return errors.Wrap(err, "print customer") } diff --git a/cli/cmd/customer_inspect.go b/cli/cmd/customer_inspect.go index 03693e866..e15c4a9b8 100644 --- a/cli/cmd/customer_inspect.go +++ b/cli/cmd/customer_inspect.go @@ -13,8 +13,7 @@ import ( func (r *runners) InitCustomersInspectCommand(parent *cobra.Command) *cobra.Command { var ( - customer string - outputFormat string + customer string ) cmd := &cobra.Command{ Use: "inspect [flags]", @@ -38,20 +37,19 @@ replicated customer inspect --customer cus_abcdef123456 --output json # Inspect a customer for a specific app (if you have multiple apps) replicated customer inspect --app myapp --customer "Acme Inc"`, RunE: func(cmd *cobra.Command, args []string) error { - return r.inspectCustomer(cmd, customer, outputFormat) + return r.inspectCustomer(cmd, customer) }, SilenceUsage: true, } parent.AddCommand(cmd) cmd.Flags().StringVar(&customer, "customer", "", "The Customer Name or ID") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.MarkFlagRequired("customer") return cmd } -func (r *runners) inspectCustomer(cmd *cobra.Command, customer string, outputFormat string) error { +func (r *runners) inspectCustomer(cmd *cobra.Command, customer string) error { if !r.hasApp() { return errors.New("no app specified") } @@ -75,7 +73,7 @@ func (r *runners) inspectCustomer(cmd *cobra.Command, customer string, outputFor return errors.Wrapf(err, "get registry hostname for customer %q", customer) } - if err = print.CustomerAttrs(outputFormat, r.w, r.appType, r.appSlug, ch, regHost, c); err != nil { + if err = print.CustomerAttrs(r.outputFormat, r.w, r.appType, r.appSlug, ch, regHost, c); err != nil { return errors.Wrap(err, "print customer attrs") } diff --git a/cli/cmd/customer_ls.go b/cli/cmd/customer_ls.go index 70e5464a5..2ce509ec4 100644 --- a/cli/cmd/customer_ls.go +++ b/cli/cmd/customer_ls.go @@ -8,9 +8,8 @@ import ( func (r *runners) InitCustomersLSCommand(parent *cobra.Command) *cobra.Command { var ( - appVersion string - includeTest bool - outputFormat string + appVersion string + includeTest bool ) customersLsCmd := &cobra.Command{ @@ -34,19 +33,18 @@ replicated customer ls --app myapp --output json # Combine multiple flags replicated customer ls --app myapp --output json`, RunE: func(cmd *cobra.Command, args []string) error { - return r.listCustomers(appVersion, includeTest, outputFormat) + return r.listCustomers(appVersion, includeTest) }, } parent.AddCommand(customersLsCmd) customersLsCmd.Flags().StringVar(&appVersion, "app-version", "", "Filter customers by a specific app version") customersLsCmd.Flags().BoolVar(&includeTest, "include-test", false, "Include test customers in the results") - customersLsCmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return customersLsCmd } -func (r *runners) listCustomers(appVersion string, includeTest bool, outputFormat string) error { +func (r *runners) listCustomers(appVersion string, includeTest bool) error { if !r.hasApp() { return errors.New("no app specified") } @@ -56,14 +54,14 @@ func (r *runners) listCustomers(appVersion string, includeTest bool, outputForma if err != nil { return errors.Wrap(err, "list customers") } - return print.Customers(outputFormat, r.w, customers) + return print.Customers(r.outputFormat, r.w, customers) } else { customers, err := r.api.ListCustomersByAppAndVersion(r.appID, appVersion, r.appType) - if err != nil && outputFormat == "json" { - return print.CustomersWithInstances(outputFormat, r.w, customers) + if err != nil && r.outputFormat == "json" { + return print.CustomersWithInstances(r.outputFormat, r.w, customers) } else if err != nil { return errors.Wrap(err, "list customers by app and app version") } - return print.CustomersWithInstances(outputFormat, r.w, customers) + return print.CustomersWithInstances(r.outputFormat, r.w, customers) } } diff --git a/cli/cmd/customer_update.go b/cli/cmd/customer_update.go index bdf9afb1e..b3cec5825 100644 --- a/cli/cmd/customer_update.go +++ b/cli/cmd/customer_update.go @@ -94,7 +94,6 @@ replicated customer update --customer cus_abcdef123456 --name "JSON Corp" --outp cmd.Flags().BoolVar(&opts.IsDeveloperModeEnabled, "developer-mode", false, "If set, Replicated SDK installed in dev mode will use mock data.") cmd.Flags().StringVar(&opts.Email, "email", "", "Email address of the customer that is to be updated.") cmd.Flags().StringVar(&opts.Type, "type", "dev", "The license type to update. One of: dev|trial|paid|community|test (default: dev)") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.MarkFlagRequired("customer") cmd.MarkFlagRequired("channel") diff --git a/cli/cmd/default_set.go b/cli/cmd/default_set.go index db9ffee6c..78f35385d 100644 --- a/cli/cmd/default_set.go +++ b/cli/cmd/default_set.go @@ -8,8 +8,6 @@ import ( ) func (r *runners) InitDefaultSetCommand(parent *cobra.Command) *cobra.Command { - var outputFormat string - cmd := &cobra.Command{ Use: "set KEY VALUE", Short: "Set default value for a key", @@ -26,17 +24,16 @@ either table or JSON format.`, replicated default set app my-app-slug`, Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - return r.setDefault(cmd, args[0], args[1], outputFormat) + return r.setDefault(cmd, args[0], args[1]) }, } - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") parent.AddCommand(cmd) return cmd } -func (r *runners) setDefault(cmd *cobra.Command, defaultType string, defaultValue string, outputFormat string) error { +func (r *runners) setDefault(cmd *cobra.Command, defaultType string, defaultValue string) error { switch defaultType { case "app": app, err := getApp(defaultValue, r.api.KotsClient) @@ -48,7 +45,7 @@ func (r *runners) setDefault(cmd *cobra.Command, defaultType string, defaultValu return errors.Wrap(err, "set default in cache") } - if err := print.Apps(outputFormat, r.w, []types.AppAndChannels{{App: app}}); err != nil { + if err := print.Apps(r.outputFormat, r.w, []types.AppAndChannels{{App: app}}); err != nil { return errors.Wrap(err, "print app") } diff --git a/cli/cmd/default_show.go b/cli/cmd/default_show.go index 5bcc9a110..047ce2fbc 100644 --- a/cli/cmd/default_show.go +++ b/cli/cmd/default_show.go @@ -10,8 +10,6 @@ import ( ) func (r *runners) InitDefaultShowCommand(parent *cobra.Command) *cobra.Command { - var outputFormat string - cmd := &cobra.Command{ Use: "show KEY", Short: "Show default value for a key", @@ -29,24 +27,23 @@ replicated default show app `, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return r.showDefault(cmd, args[0], outputFormat) + return r.showDefault(cmd, args[0]) }, } - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") parent.AddCommand(cmd) return cmd } -func (r *runners) showDefault(cmd *cobra.Command, defaultType string, outputFormat string) error { +func (r *runners) showDefault(cmd *cobra.Command, defaultType string) error { defaultValue, err := cache.GetDefault(defaultType) if err != nil { return errors.Wrap(err, "get default value") } if defaultValue == "" { - if outputFormat == "json" { + if r.outputFormat == "json" { fmt.Println("{}") } else { fmt.Printf("No default set for %s\n", defaultType) @@ -61,7 +58,7 @@ func (r *runners) showDefault(cmd *cobra.Command, defaultType string, outputForm return errors.Wrap(err, "get app") } - if err := print.Apps(outputFormat, r.w, []types.AppAndChannels{{App: app}}); err != nil { + if err := print.Apps(r.outputFormat, r.w, []types.AppAndChannels{{App: app}}); err != nil { return errors.Wrap(err, "print app") } } diff --git a/cli/cmd/enterprise_portal_invite.go b/cli/cmd/enterprise_portal_invite.go index 7edd9ebc4..f9a732c8a 100644 --- a/cli/cmd/enterprise_portal_invite.go +++ b/cli/cmd/enterprise_portal_invite.go @@ -11,7 +11,6 @@ import ( func (r *runners) InitEnterprisePortalInviteCmd(parent *cobra.Command) *cobra.Command { var customer string - var outputFormat string cmd := &cobra.Command{ Use: "invite [EMAIL_ADDRESSES...]", @@ -36,21 +35,20 @@ replicated enterprise-portal invite --customer "ACME Inc" --output json user@exa # Invite users to the Enterprise Portal for a specific app (if you have multiple apps) replicated enterprise-portal invite --app myapp --customer "ACME Inc" user1@example.com user2@example.com`, RunE: func(cmd *cobra.Command, args []string) error { - return r.enterprisePortalInvite(cmd, r.appID, customer, args, outputFormat) + return r.enterprisePortalInvite(cmd, r.appID, customer, args) }, SilenceUsage: true, } parent.AddCommand(cmd) cmd.Flags().StringVar(&customer, "customer", "", "The customer name or ID to invite") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.MarkFlagRequired("customer") return cmd } -func (r *runners) enterprisePortalInvite(cmd *cobra.Command, appID string, customer string, emailAddresses []string, outputFormat string) error { +func (r *runners) enterprisePortalInvite(cmd *cobra.Command, appID string, customer string, emailAddresses []string) error { var c *types.Customer // try to get the customer as if we have an id first diff --git a/cli/cmd/enterprise_portal_user_ls.go b/cli/cmd/enterprise_portal_user_ls.go index ed98b4cbc..309c667ee 100644 --- a/cli/cmd/enterprise_portal_user_ls.go +++ b/cli/cmd/enterprise_portal_user_ls.go @@ -12,7 +12,6 @@ type listEnterprisePortalUsersOpts struct { func (r *runners) InitEnterprisePortalUserLsCmd(parent *cobra.Command) *cobra.Command { opts := listEnterprisePortalUsersOpts{} - var outputFormat string cmd := &cobra.Command{ Use: "ls", @@ -41,19 +40,18 @@ replicated enterprise-portal user ls --output json # List all users, including invites, for a specific app in table format replicated enterprise-portal user ls --app myapp --include-invites --output table`, RunE: func(cmd *cobra.Command, args []string) error { - return r.enterprisePortalUserLs(cmd, r.appID, opts, outputFormat) + return r.enterprisePortalUserLs(cmd, r.appID, opts) }, SilenceUsage: true, } parent.AddCommand(cmd) cmd.Flags().BoolVar(&opts.includeInvites, "include-invites", false, "Include pending invitations in the list") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } -func (r *runners) enterprisePortalUserLs(cmd *cobra.Command, appID string, opts listEnterprisePortalUsersOpts, outputFormat string) error { +func (r *runners) enterprisePortalUserLs(cmd *cobra.Command, appID string, opts listEnterprisePortalUsersOpts) error { users, err := r.kotsAPI.ListEnterprisePortalUsers(appID, opts.includeInvites) if err != nil { return err diff --git a/cli/cmd/enterpriseportal_status_get.go b/cli/cmd/enterpriseportal_status_get.go index cb53c54ba..5120977ea 100644 --- a/cli/cmd/enterpriseportal_status_get.go +++ b/cli/cmd/enterpriseportal_status_get.go @@ -8,8 +8,6 @@ import ( ) func (r *runners) InitEnterprisePortalStatusGetCmd(parent *cobra.Command) *cobra.Command { - var outputFormat string - cmd := &cobra.Command{ Use: "get", Short: "Get the status of the Enterprise Portal", @@ -33,17 +31,16 @@ replicated enterprise-portal status get --output json # Get the Enterprise Portal status and output in table format (default) replicated enterprise-portal status get --output table`, RunE: func(cmd *cobra.Command, args []string) error { - return r.enterprisePortalStatusGet(cmd, r.appID, outputFormat) + return r.enterprisePortalStatusGet(cmd, r.appID) }, SilenceUsage: true, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } -func (r *runners) enterprisePortalStatusGet(cmd *cobra.Command, appID string, outputFormat string) error { +func (r *runners) enterprisePortalStatusGet(cmd *cobra.Command, appID string) error { status, err := r.kotsAPI.GetEnterprisePortalStatus(appID) if err != nil { return errors.Wrap(err, "get enterprise portal status") diff --git a/cli/cmd/enterpriseportal_status_update.go b/cli/cmd/enterpriseportal_status_update.go index b70b253b7..d72d3e4e5 100644 --- a/cli/cmd/enterpriseportal_status_update.go +++ b/cli/cmd/enterpriseportal_status_update.go @@ -12,7 +12,6 @@ type updateEnterprisePortalStatusOpts struct { func (r *runners) InitEnterprisePortalStatusUpdateCmd(parent *cobra.Command) *cobra.Command { opts := updateEnterprisePortalStatusOpts{} - var outputFormat string cmd := &cobra.Command{ Use: "update", @@ -38,20 +37,19 @@ replicated enterprise-portal status update --status pending --output json # Update the Enterprise Portal status and output in table format (default) replicated enterprise-portal status update --status active --output table`, RunE: func(cmd *cobra.Command, args []string) error { - return r.enterprisePortalStatusUpdate(cmd, r.appID, opts, outputFormat) + return r.enterprisePortalStatusUpdate(cmd, r.appID, opts) }, SilenceUsage: true, } parent.AddCommand(cmd) cmd.Flags().StringVar(&opts.status, "status", "", "The status to set for the enterprise portal (active|inactive|pending)") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.MarkFlagRequired("status") return cmd } -func (r *runners) enterprisePortalStatusUpdate(cmd *cobra.Command, appID string, opts updateEnterprisePortalStatusOpts, outputFormat string) error { +func (r *runners) enterprisePortalStatusUpdate(cmd *cobra.Command, appID string, opts updateEnterprisePortalStatusOpts) error { status, err := r.kotsAPI.UpdateEnterprisePortalStatus(appID, opts.status) if err != nil { return err diff --git a/cli/cmd/installer_ls.go b/cli/cmd/installer_ls.go index 0ab8ff411..7b91105ae 100644 --- a/cli/cmd/installer_ls.go +++ b/cli/cmd/installer_ls.go @@ -15,7 +15,6 @@ func (r *runners) InitInstallerList(parent *cobra.Command) { } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.RunE = r.installerList } diff --git a/cli/cmd/instance_inspect.go b/cli/cmd/instance_inspect.go index 27739861f..745092e69 100644 --- a/cli/cmd/instance_inspect.go +++ b/cli/cmd/instance_inspect.go @@ -19,7 +19,6 @@ func (r *runners) InitInstanceInspectCommand(parent *cobra.Command) *cobra.Comma parent.AddCommand(cmd) cmd.Flags().StringVar(&r.args.instanceInspectCustomer, "customer", "", "Customer Name or ID") cmd.Flags().StringVar(&r.args.instanceInspectInstance, "instance", "", "Instance Name or ID") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } diff --git a/cli/cmd/instance_ls.go b/cli/cmd/instance_ls.go index fe12214d6..b8aa85483 100644 --- a/cli/cmd/instance_ls.go +++ b/cli/cmd/instance_ls.go @@ -20,7 +20,6 @@ func (r *runners) InitInstanceLSCommand(parent *cobra.Command) *cobra.Command { parent.AddCommand(cmd) cmd.Flags().StringVar(&r.args.instanceListCustomer, "customer", "", "Customer Name or ID") cmd.Flags().StringArrayVar(&r.args.instanceListTags, "tag", []string{}, "Tags to use to filter instances (key=value format, can be specified multiple times). Only one tag needs to match (an OR operation)") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } diff --git a/cli/cmd/instance_tag.go b/cli/cmd/instance_tag.go index 62b970682..33d6ecb68 100644 --- a/cli/cmd/instance_tag.go +++ b/cli/cmd/instance_tag.go @@ -19,7 +19,6 @@ func (r *runners) InitInstanceTagCommand(parent *cobra.Command) *cobra.Command { cmd.Flags().StringVar(&r.args.instanceTagCustomer, "customer", "", "Customer Name or ID") cmd.Flags().StringVar(&r.args.instanceTagInstacne, "instance", "", "Instance Name or ID") cmd.Flags().StringArrayVar(&r.args.instanceTagTags, "tag", []string{}, "Tags to apply to instance. Leave value empty to remove tag. Tags not specified will not be removed.") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } diff --git a/cli/cmd/model_ls.go b/cli/cmd/model_ls.go index 11dbce428..b3841eb56 100644 --- a/cli/cmd/model_ls.go +++ b/cli/cmd/model_ls.go @@ -15,7 +15,6 @@ func (r *runners) InitModelList(parent *cobra.Command) *cobra.Command { SilenceUsage: true, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } diff --git a/cli/cmd/model_rm.go b/cli/cmd/model_rm.go index d2039ebc3..a55ad9b5f 100644 --- a/cli/cmd/model_rm.go +++ b/cli/cmd/model_rm.go @@ -16,7 +16,6 @@ func (r *runners) InitModelRemove(parent *cobra.Command) *cobra.Command { Args: cobra.ExactArgs(1), } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } diff --git a/cli/cmd/network_create.go b/cli/cmd/network_create.go index 11013129b..84994c054 100644 --- a/cli/cmd/network_create.go +++ b/cli/cmd/network_create.go @@ -32,8 +32,6 @@ func (r *runners) InitNetworkCreate(parent *cobra.Command) *cobra.Command { cmd.Flags().BoolVar(&r.args.createNetworkDryRun, "dry-run", false, "Dry run") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") - return cmd } diff --git a/cli/cmd/network_ls.go b/cli/cmd/network_ls.go index 82b0c4830..244e791b9 100644 --- a/cli/cmd/network_ls.go +++ b/cli/cmd/network_ls.go @@ -26,7 +26,6 @@ func (r *runners) InitNetworkList(parent *cobra.Command) *cobra.Command { cmd.Flags().BoolVar(&r.args.lsNetworkShowReports, "show-reports", false, "when set, only show networks that have reports") cmd.Flags().StringVar(&r.args.lsNetworkStartTime, "start-time", "", "start time for the query (Format: 2006-01-02T15:04:05Z)") cmd.Flags().StringVar(&r.args.lsNetworkEndTime, "end-time", "", "end time for the query (Format: 2006-01-02T15:04:05Z)") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") cmd.Flags().BoolVarP(&r.args.lsNetworkWatch, "watch", "w", false, "watch networks") return cmd diff --git a/cli/cmd/network_update.go b/cli/cmd/network_update.go index 4e149d3a8..4ad3c000d 100644 --- a/cli/cmd/network_update.go +++ b/cli/cmd/network_update.go @@ -48,7 +48,6 @@ replicated network update NETWORK_ID --policy airgap --collect-report cmd.Flags().StringVarP(&r.args.updateNetworkPolicy, "policy", "p", "", "Update network policy setting") cmd.Flags().BoolVarP(&r.args.updateNetworkCollectReport, "collect-report", "r", false, "Enable report collection on this network (use --collect-report=false to disable)") - cmd.Flags().StringVar(&r.outputFormat, "output", "table", "The output format to use. One of: json|table|wide") return cmd } diff --git a/cli/cmd/notification.go b/cli/cmd/notification.go index 8f052696e..2385a8b41 100644 --- a/cli/cmd/notification.go +++ b/cli/cmd/notification.go @@ -69,7 +69,7 @@ func (r *runners) InitNotificationWebhookCommand(parent *cobra.Command) *cobra.C } func (r *runners) InitNotificationSubscriptionList(parent *cobra.Command) *cobra.Command { - var outputFormat, search, subscriptionType string + var search, subscriptionType string cmd := &cobra.Command{ Use: "ls", @@ -81,22 +81,19 @@ func (r *runners) InitNotificationSubscriptionList(parent *cobra.Command) *cobra return errors.Wrap(err, "list notification subscriptions") } if len(resp.Subscriptions) == 0 { - return print.NoNotifications(outputFormat, r.w) + return print.NoNotifications(r.outputFormat, r.w) } - return print.Notifications(outputFormat, r.w, resp.Subscriptions) + return print.Notifications(r.outputFormat, r.w, resp.Subscriptions) }, SilenceUsage: true, } parent.AddCommand(cmd) cmd.Flags().StringVar(&search, "search", "", "Text search filter") cmd.Flags().StringVar(&subscriptionType, "type", "", "Filter by subscription type: personal|team") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } func (r *runners) InitNotificationSubscriptionGet(parent *cobra.Command) *cobra.Command { - var outputFormat string - cmd := &cobra.Command{ Use: "get ID", Short: "Get a notification subscription", @@ -106,17 +103,16 @@ func (r *runners) InitNotificationSubscriptionGet(parent *cobra.Command) *cobra. if err != nil { return errors.Wrap(err, "get notification subscription") } - return print.Notification(outputFormat, r.w, subscription) + return print.Notification(r.outputFormat, r.w, subscription) }, SilenceUsage: true, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } func (r *runners) InitNotificationSubscriptionCreate(parent *cobra.Command) *cobra.Command { - var outputFormat, file string + var file string cmd := &cobra.Command{ Use: "create", @@ -130,19 +126,18 @@ func (r *runners) InitNotificationSubscriptionCreate(parent *cobra.Command) *cob if err != nil { return errors.Wrap(err, "create notification subscription") } - return print.Notification(outputFormat, r.w, subscription) + return print.Notification(r.outputFormat, r.w, subscription) }, SilenceUsage: true, } parent.AddCommand(cmd) cmd.Flags().StringVar(&file, "file", "", "Path to a JSON file containing the subscription definition") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") _ = cmd.MarkFlagRequired("file") return cmd } func (r *runners) InitNotificationSubscriptionUpdate(parent *cobra.Command) *cobra.Command { - var outputFormat, file string + var file string cmd := &cobra.Command{ Use: "update ID", @@ -157,13 +152,12 @@ func (r *runners) InitNotificationSubscriptionUpdate(parent *cobra.Command) *cob if err != nil { return errors.Wrap(err, "update notification subscription") } - return print.Notification(outputFormat, r.w, subscription) + return print.Notification(r.outputFormat, r.w, subscription) }, SilenceUsage: true, } parent.AddCommand(cmd) cmd.Flags().StringVar(&file, "file", "", "Path to a JSON file containing the subscription patch") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") _ = cmd.MarkFlagRequired("file") return cmd } @@ -191,8 +185,6 @@ func (r *runners) InitNotificationSubscriptionRemove(parent *cobra.Command) *cob } func (r *runners) InitNotificationSubscriptionEvents(parent *cobra.Command) *cobra.Command { - var outputFormat string - cmd := &cobra.Command{ Use: "events ID", Short: "List delivery events for a notification subscription", @@ -203,19 +195,17 @@ func (r *runners) InitNotificationSubscriptionEvents(parent *cobra.Command) *cob return errors.Wrap(err, "list notification subscription events") } if len(resp.Events) == 0 { - return print.NoNotificationEvents(outputFormat, r.w) + return print.NoNotificationEvents(r.outputFormat, r.w) } - return print.NotificationEvents(outputFormat, r.w, resp.Events) + return print.NotificationEvents(r.outputFormat, r.w, resp.Events) }, SilenceUsage: true, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } func (r *runners) InitNotificationEventList(parent *cobra.Command) *cobra.Command { - var outputFormat string opts := kotsclient.ListNotificationEventsOpts{} cmd := &cobra.Command{ @@ -238,9 +228,9 @@ func (r *runners) InitNotificationEventList(parent *cobra.Command) *cobra.Comman return errors.Wrap(err, "list notification events") } if len(resp.Events) == 0 { - return print.NoNotificationEvents(outputFormat, r.w) + return print.NoNotificationEvents(r.outputFormat, r.w) } - return print.NotificationEvents(outputFormat, r.w, resp.Events) + return print.NotificationEvents(r.outputFormat, r.w, resp.Events) }, SilenceUsage: true, } @@ -255,13 +245,10 @@ func (r *runners) InitNotificationEventList(parent *cobra.Command) *cobra.Comman cmd.Flags().StringVar(&opts.Status, "status", "", "Filter by status: success|pending|failed") cmd.Flags().IntVar(&opts.CurrentPage, "current-page", 0, "Pagination page index") cmd.Flags().IntVar(&opts.PageSize, "page-size", 20, "Pagination page size") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } func (r *runners) InitNotificationEventRetry(parent *cobra.Command) *cobra.Command { - var outputFormat string - cmd := &cobra.Command{ Use: "retry EVENT_ID", Short: "Retry a notification event", @@ -271,17 +258,16 @@ func (r *runners) InitNotificationEventRetry(parent *cobra.Command) *cobra.Comma if err != nil { return errors.Wrap(err, "retry notification event") } - return print.NotificationRetry(outputFormat, r.w, resp) + return print.NotificationRetry(r.outputFormat, r.w, resp) }, SilenceUsage: true, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } func (r *runners) InitNotificationEventTypeList(parent *cobra.Command) *cobra.Command { - var outputFormat, query string + var query string var limit int cmd := &cobra.Command{ @@ -293,19 +279,18 @@ func (r *runners) InitNotificationEventTypeList(parent *cobra.Command) *cobra.Co if err != nil { return errors.Wrap(err, "list notification event types") } - return print.NotificationEventTypes(outputFormat, r.w, resp) + return print.NotificationEventTypes(r.outputFormat, r.w, resp) }, SilenceUsage: true, } parent.AddCommand(cmd) cmd.Flags().StringVar(&query, "q", "", "Search query") cmd.Flags().IntVar(&limit, "limit", 0, "Maximum results to request from the API (0 means no limit)") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } func (r *runners) InitNotificationEmailResendVerification(parent *cobra.Command) *cobra.Command { - var outputFormat, email string + var email string cmd := &cobra.Command{ Use: "resend-verification", @@ -320,19 +305,18 @@ func (r *runners) InitNotificationEmailResendVerification(parent *cobra.Command) if err != nil { return errors.Wrap(err, "resend verification email") } - return print.NotificationEmailAction(outputFormat, r.w, resp) + return print.NotificationEmailAction(r.outputFormat, r.w, resp) }, SilenceUsage: true, } parent.AddCommand(cmd) cmd.Flags().StringVar(&email, "email", "", "Email address to verify") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") _ = cmd.MarkFlagRequired("email") return cmd } func (r *runners) InitNotificationEmailVerify(parent *cobra.Command) *cobra.Command { - var outputFormat, email, code string + var email, code string cmd := &cobra.Command{ Use: "verify", @@ -351,21 +335,20 @@ func (r *runners) InitNotificationEmailVerify(parent *cobra.Command) *cobra.Comm if err != nil { return errors.Wrap(err, "verify notification email") } - return print.NotificationEmailAction(outputFormat, r.w, resp) + return print.NotificationEmailAction(r.outputFormat, r.w, resp) }, SilenceUsage: true, } parent.AddCommand(cmd) cmd.Flags().StringVar(&email, "email", "", "Email address to verify") cmd.Flags().StringVar(&code, "code", "", "Verification code") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") _ = cmd.MarkFlagRequired("email") _ = cmd.MarkFlagRequired("code") return cmd } func (r *runners) InitNotificationWebhookTest(parent *cobra.Command) *cobra.Command { - var outputFormat, file string + var file string cmd := &cobra.Command{ Use: "test", @@ -379,13 +362,12 @@ func (r *runners) InitNotificationWebhookTest(parent *cobra.Command) *cobra.Comm if err != nil { return errors.Wrap(err, "test notification webhook") } - return print.NotificationWebhookTest(outputFormat, r.w, resp) + return print.NotificationWebhookTest(r.outputFormat, r.w, resp) }, SilenceUsage: true, } parent.AddCommand(cmd) cmd.Flags().StringVar(&file, "file", "", "Path to a JSON file containing the webhook test payload") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") _ = cmd.MarkFlagRequired("file") return cmd } diff --git a/cli/cmd/output_test.go b/cli/cmd/output_test.go new file mode 100644 index 000000000..4a5ee3207 --- /dev/null +++ b/cli/cmd/output_test.go @@ -0,0 +1,41 @@ +package cmd + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/require" +) + +func TestResolveOutputFormat_ExplicitFlagWins(t *testing.T) { + // Set env var + t.Setenv("REPLICATED_OUTPUT", "json") + + r := &runners{outputFormat: "table"} + cmd := &cobra.Command{} + cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "") + cmd.Flags().Set("output", "wide") + + r.resolveOutputFormat(cmd) + require.Equal(t, "wide", r.outputFormat) +} + +func TestResolveOutputFormat_EnvVarWinsOverDefault(t *testing.T) { + t.Setenv("REPLICATED_OUTPUT", "json") + + r := &runners{outputFormat: "table"} + cmd := &cobra.Command{} + cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "") + + r.resolveOutputFormat(cmd) + require.Equal(t, "json", r.outputFormat) +} + +func TestResolveOutputFormat_DefaultTable(t *testing.T) { + r := &runners{outputFormat: "table"} + cmd := &cobra.Command{} + cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "") + + r.resolveOutputFormat(cmd) + require.Equal(t, "table", r.outputFormat) +} diff --git a/cli/cmd/policy_create.go b/cli/cmd/policy_create.go index e3682c0ec..6fe085301 100644 --- a/cli/cmd/policy_create.go +++ b/cli/cmd/policy_create.go @@ -15,7 +15,6 @@ func (r *runners) InitPolicyCreate(parent *cobra.Command) *cobra.Command { name string description string definitionFile string - outputFormat string ) cmd := &cobra.Command{ @@ -43,7 +42,7 @@ Vendors not on an enterprise plan cannot create policies.`, # Create a policy with a description replicated policy create --name "My Policy" --description "Custom access policy" --definition policy.json`, RunE: func(cmd *cobra.Command, args []string) error { - return r.policyCreate(name, description, definitionFile, outputFormat) + return r.policyCreate(name, description, definitionFile) }, SilenceUsage: true, } @@ -51,14 +50,13 @@ Vendors not on an enterprise plan cannot create policies.`, cmd.Flags().StringVar(&name, "name", "", "Name of the policy") cmd.Flags().StringVar(&description, "description", "", "Description of the policy") cmd.Flags().StringVar(&definitionFile, "definition", "", "Path to the JSON file containing the policy definition") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.MarkFlagRequired("name") cmd.MarkFlagRequired("definition") return cmd } -func (r *runners) policyCreate(name, description, definitionFile, outputFormat string) error { +func (r *runners) policyCreate(name, description, definitionFile string) error { definition, err := readPolicyDefinition(definitionFile) if err != nil { return errors.Wrap(err, "read policy definition") @@ -72,7 +70,7 @@ func (r *runners) policyCreate(name, description, definitionFile, outputFormat s return errors.Wrap(err, "create policy") } - return print.Policy(outputFormat, r.w, policy) + return print.Policy(r.outputFormat, r.w, policy) } func readPolicyDefinition(path string) (string, error) { diff --git a/cli/cmd/policy_get.go b/cli/cmd/policy_get.go index 9beac8328..1d44841a9 100644 --- a/cli/cmd/policy_get.go +++ b/cli/cmd/policy_get.go @@ -11,10 +11,7 @@ import ( ) func (r *runners) InitPolicyGet(parent *cobra.Command) *cobra.Command { - var ( - outputFormat string - outputFile string - ) + var outputFile string cmd := &cobra.Command{ Use: "get NAME_OR_ID", @@ -30,18 +27,17 @@ func (r *runners) InitPolicyGet(parent *cobra.Command) *cobra.Command { replicated policy get "My Policy" --output json`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return r.policyGet(args[0], outputFormat, outputFile) + return r.policyGet(args[0], outputFile) }, SilenceUsage: true, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.Flags().StringVar(&outputFile, "output-file", "", "If set, saves the policy definition to the specified file") return cmd } -func (r *runners) policyGet(nameOrID, outputFormat, outputFile string) error { +func (r *runners) policyGet(nameOrID, outputFile string) error { policy, err := r.kotsAPI.GetPolicyByNameOrID(nameOrID) if err != nil { return errors.Wrap(err, "get policy") @@ -59,5 +55,5 @@ func (r *runners) policyGet(nameOrID, outputFormat, outputFile string) error { return r.w.Flush() } - return print.Policy(outputFormat, r.w, policy) + return print.Policy(r.outputFormat, r.w, policy) } diff --git a/cli/cmd/policy_ls.go b/cli/cmd/policy_ls.go index 700b714f1..7fbaf9dd3 100644 --- a/cli/cmd/policy_ls.go +++ b/cli/cmd/policy_ls.go @@ -7,8 +7,6 @@ import ( ) func (r *runners) InitPolicyList(parent *cobra.Command) *cobra.Command { - var outputFormat string - cmd := &cobra.Command{ Use: "ls", Aliases: []string{"list"}, @@ -20,21 +18,20 @@ func (r *runners) InitPolicyList(parent *cobra.Command) *cobra.Command { # List policies in JSON format replicated policy ls --output json`, RunE: func(cmd *cobra.Command, args []string) error { - return r.policyList(outputFormat) + return r.policyList() }, SilenceUsage: true, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } -func (r *runners) policyList(outputFormat string) error { +func (r *runners) policyList() error { policies, err := r.kotsAPI.ListPolicies() if err != nil { return errors.Wrap(err, "list policies") } - return print.Policies(outputFormat, r.w, policies) + return print.Policies(r.outputFormat, r.w, policies) } diff --git a/cli/cmd/policy_update.go b/cli/cmd/policy_update.go index 1acba134f..f4c032d8f 100644 --- a/cli/cmd/policy_update.go +++ b/cli/cmd/policy_update.go @@ -14,7 +14,6 @@ func (r *runners) InitPolicyUpdate(parent *cobra.Command) *cobra.Command { newName string description string definitionFile string - outputFormat string ) cmd := &cobra.Command{ @@ -35,7 +34,7 @@ Vendors not on an enterprise plan cannot update policies.`, replicated policy update "My Policy" --description "Updated description" --definition policy.json`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return r.policyUpdate(cmd, args[0], newName, description, definitionFile, outputFormat) + return r.policyUpdate(cmd, args[0], newName, description, definitionFile) }, SilenceUsage: true, } @@ -43,12 +42,11 @@ Vendors not on an enterprise plan cannot update policies.`, cmd.Flags().StringVar(&newName, "name", "", "New name for the policy") cmd.Flags().StringVar(&description, "description", "", "New description for the policy") cmd.Flags().StringVar(&definitionFile, "definition", "", "Path to the JSON file containing the updated policy definition") - cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } -func (r *runners) policyUpdate(cmd *cobra.Command, nameOrID, newName, description, definitionFile, outputFormat string) error { +func (r *runners) policyUpdate(cmd *cobra.Command, nameOrID, newName, description, definitionFile string) error { if !cmd.Flags().Changed("name") && !cmd.Flags().Changed("description") && !cmd.Flags().Changed("definition") { return errors.New("at least one of --name, --description, or --definition must be specified") } @@ -88,5 +86,5 @@ func (r *runners) policyUpdate(cmd *cobra.Command, nameOrID, newName, descriptio return errors.Wrap(err, "update policy") } - return print.Policy(outputFormat, r.w, policy) + return print.Policy(r.outputFormat, r.w, policy) } diff --git a/cli/cmd/registry_add_dockerhub.go b/cli/cmd/registry_add_dockerhub.go index 0962a2654..57e2b46a8 100644 --- a/cli/cmd/registry_add_dockerhub.go +++ b/cli/cmd/registry_add_dockerhub.go @@ -27,7 +27,6 @@ func (r *runners) InitRegistryAddDockerHub(parent *cobra.Command) *cobra.Command cmd.Flags().BoolVar(&r.args.addRegistryTokenFromStdIn, "token-stdin", false, "Take the token from stdin") cmd.Flags().StringVar(&r.args.addRegistryName, "name", "", "Name for the registry") cmd.Flags().StringVar(&r.args.addRegistryAppIds, "app-ids", "", "Comma-separated list of app IDs to scope this registry to") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.RunE = r.registryAddDockerHub diff --git a/cli/cmd/registry_add_ecr.go b/cli/cmd/registry_add_ecr.go index 91adc69b4..bcb2fd61e 100644 --- a/cli/cmd/registry_add_ecr.go +++ b/cli/cmd/registry_add_ecr.go @@ -25,7 +25,6 @@ func (r *runners) InitRegistryAddECR(parent *cobra.Command) { cmd.Flags().BoolVar(&r.args.addRegistrySecretAccessKeyFromStdIn, "secretaccesskey-stdin", false, "Take the secret access key from stdin") cmd.Flags().StringVar(&r.args.addRegistryName, "name", "", "Name for the registry") cmd.Flags().StringVar(&r.args.addRegistryAppIds, "app-ids", "", "Comma-separated list of app IDs to scope this registry to") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.RunE = r.registryAddECR } diff --git a/cli/cmd/registry_add_gar.go b/cli/cmd/registry_add_gar.go index 61fd5999a..4bde0434e 100644 --- a/cli/cmd/registry_add_gar.go +++ b/cli/cmd/registry_add_gar.go @@ -28,7 +28,6 @@ func (r *runners) InitRegistryAddGAR(parent *cobra.Command) { cmd.Flags().BoolVar(&r.args.addRegistryTokenFromStdIn, "token-stdin", false, "Take the token from stdin") cmd.Flags().StringVar(&r.args.addRegistryName, "name", "", "Name for the registry") cmd.Flags().StringVar(&r.args.addRegistryAppIds, "app-ids", "", "Comma-separated list of app IDs to scope this registry to") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.RunE = r.registryAddGAR } diff --git a/cli/cmd/registry_add_gcr.go b/cli/cmd/registry_add_gcr.go index a391f8286..7eff8a585 100644 --- a/cli/cmd/registry_add_gcr.go +++ b/cli/cmd/registry_add_gcr.go @@ -24,7 +24,6 @@ func (r *runners) InitRegistryAddGCR(parent *cobra.Command) { cmd.Flags().BoolVar(&r.args.addRegistryServiceAccountKeyFromStdIn, "serviceaccountkey-stdin", false, "Take the service account key from stdin") cmd.Flags().StringVar(&r.args.addRegistryName, "name", "", "Name for the registry") cmd.Flags().StringVar(&r.args.addRegistryAppIds, "app-ids", "", "Comma-separated list of app IDs to scope this registry to") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.RunE = r.registryAddGCR } diff --git a/cli/cmd/registry_add_ghcr.go b/cli/cmd/registry_add_ghcr.go index a2562140d..381b2893e 100644 --- a/cli/cmd/registry_add_ghcr.go +++ b/cli/cmd/registry_add_ghcr.go @@ -23,7 +23,6 @@ func (r *runners) InitRegistryAddGHCR(parent *cobra.Command) { cmd.Flags().BoolVar(&r.args.addRegistryTokenFromStdIn, "token-stdin", false, "Take the token from stdin") cmd.Flags().StringVar(&r.args.addRegistryName, "name", "", "Name for the registry") cmd.Flags().StringVar(&r.args.addRegistryAppIds, "app-ids", "", "Comma-separated list of app IDs to scope this registry to") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.RunE = r.registryAddGHCR } diff --git a/cli/cmd/registry_add_other.go b/cli/cmd/registry_add_other.go index b1581c09d..8dcdb98d6 100644 --- a/cli/cmd/registry_add_other.go +++ b/cli/cmd/registry_add_other.go @@ -25,7 +25,6 @@ func (r *runners) InitRegistryAddOther(parent *cobra.Command) *cobra.Command { cmd.Flags().BoolVar(&r.args.addRegistryPasswordFromStdIn, "password-stdin", false, "Take the password from stdin") cmd.Flags().StringVar(&r.args.addRegistryName, "name", "", "Name for the registry") cmd.Flags().StringVar(&r.args.addRegistryAppIds, "app-ids", "", "Comma-separated list of app IDs to scope this registry to") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.RunE = r.registryAddOther diff --git a/cli/cmd/registry_add_quay.go b/cli/cmd/registry_add_quay.go index 538215082..53a00028b 100644 --- a/cli/cmd/registry_add_quay.go +++ b/cli/cmd/registry_add_quay.go @@ -24,7 +24,6 @@ func (r *runners) InitRegistryAddQuay(parent *cobra.Command) *cobra.Command { cmd.Flags().BoolVar(&r.args.addRegistryPasswordFromStdIn, "password-stdin", false, "Take the password from stdin") cmd.Flags().StringVar(&r.args.addRegistryName, "name", "", "Name for the registry") cmd.Flags().StringVar(&r.args.addRegistryAppIds, "app-ids", "", "Comma-separated list of app IDs to scope this registry to") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.RunE = r.registryAddQuay diff --git a/cli/cmd/registry_ls.go b/cli/cmd/registry_ls.go index f69342c54..903d5f98f 100644 --- a/cli/cmd/registry_ls.go +++ b/cli/cmd/registry_ls.go @@ -19,7 +19,6 @@ func (r *runners) InitRegistryList(parent *cobra.Command) *cobra.Command { SilenceUsage: true, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } diff --git a/cli/cmd/release_create.go b/cli/cmd/release_create.go index 5a5e8a3e3..0f5d0713e 100644 --- a/cli/cmd/release_create.go +++ b/cli/cmd/release_create.go @@ -80,9 +80,6 @@ replicated release create --version 1.0.0 --promote Unstable --required`, cmd.Flags().StringVar(&r.args.createReleaseOutputDir, "output-dir", "", "Stage the release artifacts (packaged charts and manifests) to this directory. Existing contents of the directory are removed before each run. The directory is preserved after the command completes.") cmd.Flags().BoolVar(&r.args.createReleaseNoUpload, "no-upload", false, "Build the release locally but do not upload it. Use with --output-dir to inspect or reuse the staged artifacts. Cannot be used with --promote.") - // output format - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") - // not supported for KOTS cmd.Flags().MarkHidden("yaml-file") cmd.Flags().MarkHidden("yaml") diff --git a/cli/cmd/release_image_ls.go b/cli/cmd/release_image_ls.go index c5b5b968c..6513a1e1a 100644 --- a/cli/cmd/release_image_ls.go +++ b/cli/cmd/release_image_ls.go @@ -126,7 +126,7 @@ func (r *runners) releaseImageLS(cmd *cobra.Command, args []string) error { } // Print images - return print.ChannelImages(r.w, images) + return print.ChannelImages(r.outputFormat, r.w, images) } func cleanImageName(image string, proxyRegistryDomain string) string { diff --git a/cli/cmd/release_inspect.go b/cli/cmd/release_inspect.go index 1aad2bba8..cd73ad298 100644 --- a/cli/cmd/release_inspect.go +++ b/cli/cmd/release_inspect.go @@ -28,7 +28,6 @@ replicated release inspect 123 replicated release inspect 123 --output json`, Args: cobra.ExactArgs(1), } - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") parent.AddCommand(cmd) cmd.RunE = r.releaseInspect diff --git a/cli/cmd/release_lint.go b/cli/cmd/release_lint.go index f0ff97f8e..2132eab7a 100644 --- a/cli/cmd/release_lint.go +++ b/cli/cmd/release_lint.go @@ -42,9 +42,6 @@ func (r *runners) InitReleaseLint(parent *cobra.Command) { // New flags (for local lint - when flag=1) cmd.Flags().BoolVarP(&r.args.lintVerbose, "verbose", "v", false, "Show detailed output including extracted container images (local lint only)") - // Output format flag works for both old and new lint - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") - cmd.Flags().MarkHidden("chart") cmd.RunE = r.releaseLint diff --git a/cli/cmd/release_ls.go b/cli/cmd/release_ls.go index de4efca42..7c9b13fa3 100644 --- a/cli/cmd/release_ls.go +++ b/cli/cmd/release_ls.go @@ -15,7 +15,6 @@ func (r *runners) IniReleaseList(parent *cobra.Command) { } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") cmd.RunE = r.releaseList } diff --git a/cli/cmd/release_promote.go b/cli/cmd/release_promote.go index 37798c9a0..2eedb9128 100644 --- a/cli/cmd/release_promote.go +++ b/cli/cmd/release_promote.go @@ -1,6 +1,7 @@ package cmd import ( + "encoding/json" "fmt" "strconv" "time" @@ -102,10 +103,33 @@ func (r *runners) releasePromote(cmd *cobra.Command, args []string) (err error) return errors.Wrapf(err, "failed to promote release") } - fmt.Fprintf(r.w, "Channel %s successfully set to release %d\n", channelName, seq) + if r.outputFormat == "json" { + log := logger.NewLogger(r.w).SetIsTerminal(r.stdoutIsTTY) + log.Silence() + + out := struct { + Channel string `json:"channel"` + ReleaseSequence int64 `json:"release_sequence"` + VersionLabel string `json:"version_label"` + }{ + Channel: newID, + ReleaseSequence: seq, + VersionLabel: r.args.releaseVersion, + } + enc := json.NewEncoder(r.w) + enc.SetIndent("", " ") + if err := enc.Encode(out); err != nil { + return errors.Wrap(err, "encode json output") + } + } else { + fmt.Fprintf(r.w, "Channel %s successfully set to release %d\n", channelName, seq) + } if r.appType == "kots" && r.args.releasePromoteWaitForAirgap { log := logger.NewLogger(r.w).SetIsTerminal(r.stdoutIsTTY) + if r.outputFormat == "json" { + log.Silence() + } if err := r.waitForAirgapBuilds(promoteResp, r.args.releasePromoteWaitForAirgapTimeout, log); err != nil { return err } diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 2c1af2499..fadfc7f88 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -124,6 +124,12 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i runCmds.rootCmd.SetErr(stderr) runCmds.rootCmd.SetOut(stderr) } + + runCmds.rootCmd.PersistentFlags().StringVarP( + &runCmds.outputFormat, "output", "o", "table", + "The output format to use. Supported formats vary by command (json, table, wide). (default 'table', override with REPLICATED_OUTPUT env var)", + ) + if stdout != nil { defaultHelpFunc := runCmds.rootCmd.HelpFunc() runCmds.rootCmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { @@ -329,6 +335,7 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i runCmds.rootCmd.SetUsageTemplate(rootCmdUsageTmpl) preRunSetupAPIs := func(cmd *cobra.Command, args []string) error { + runCmds.resolveOutputFormat(cmd) if apiToken == "" { // Try to load profile from --profile flag, then default profile var profileName string @@ -436,6 +443,7 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i } prerunCommand := func(cmd *cobra.Command, args []string) (err error) { + runCmds.resolveOutputFormat(cmd) if cmd.SilenceErrors { // when SilenceErrors is set, command wants to use custom error printer defer func() { printIfError(cmd, err) @@ -547,7 +555,7 @@ func Execute(rootCmd *cobra.Command, stdin io.Reader, stdout io.Writer, stderr i runCmds.InitInitCommand(configCmd) configCmd.PersistentPreRunE = preRunSetupAPIs - runCmds.rootCmd.AddCommand(Version()) + runCmds.rootCmd.AddCommand(runCmds.Version()) return runCmds.rootCmd.Execute() } diff --git a/cli/cmd/runner.go b/cli/cmd/runner.go index 00dac30b2..536efe653 100644 --- a/cli/cmd/runner.go +++ b/cli/cmd/runner.go @@ -2,6 +2,7 @@ package cmd import ( "io" + "os" "text/tabwriter" "time" @@ -37,6 +38,15 @@ func (r *runners) hasApp() bool { return true } +func (r *runners) resolveOutputFormat(cmd *cobra.Command) { + if cmd.Flags().Changed("output") { + return // explicit flag wins + } + if env := os.Getenv("REPLICATED_OUTPUT"); env != "" { + r.outputFormat = env + } +} + type runnerArgs struct { channelCreateName string channelCreateDescription string @@ -289,7 +299,6 @@ type runnerArgs struct { clusterAddonCreateObjectStoreClusterID string clusterAddonCreateObjectStoreDuration time.Duration clusterAddonCreateObjectStoreDryRun bool - clusterAddonCreateObjectStoreOutput string demoteReleaseSequence int64 demoteChannelSequence int64 diff --git a/cli/cmd/version.go b/cli/cmd/version.go index 9f68ea5da..f2858aa3a 100644 --- a/cli/cmd/version.go +++ b/cli/cmd/version.go @@ -9,14 +9,19 @@ import ( "github.com/replicatedhq/replicated/pkg/version" ) -func Version() *cobra.Command { +func (r *runners) Version() *cobra.Command { var versionJson bool - cmd := &cobra.Command{ Use: "version", Short: "Print the current version and exit", Long: `Print the current version and exit`, RunE: func(cmd *cobra.Command, args []string) error { + r.resolveOutputFormat(cmd) + + if cmd.Flags().Changed("json") { + r.outputFormat = "json" + } + currentVersion := version.Version() // For version command, do a synchronous update check @@ -36,7 +41,7 @@ func Version() *cobra.Command { // Now get the (potentially updated) build info build := version.GetBuild() - if !versionJson { + if r.outputFormat != "json" { // Special handling for development/unknown version when printing if currentVersion == "unknown" || currentVersion == "development" { fmt.Printf("replicated version %s (development build)\n", currentVersion) @@ -64,6 +69,7 @@ func Version() *cobra.Command { } cmd.Flags().BoolVar(&versionJson, "json", false, "output version info in json") + _ = cmd.Flags().MarkDeprecated("json", "use --output json instead") cmd.AddCommand(versionUpgradeCmd()) diff --git a/cli/cmd/vm_create.go b/cli/cmd/vm_create.go index 714e095f9..199e79776 100644 --- a/cli/cmd/vm_create.go +++ b/cli/cmd/vm_create.go @@ -90,8 +90,6 @@ ssh -i /tmp/ci_key $(replicated vm ssh-endpoint my-vm --username ci)`, cmd.Flags().BoolVar(&r.args.createVMDryRun, "dry-run", false, "Dry run") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") - _ = cmd.MarkFlagRequired("distribution") return cmd diff --git a/cli/cmd/vm_ls.go b/cli/cmd/vm_ls.go index 9e6c68978..cfff4c2ef 100644 --- a/cli/cmd/vm_ls.go +++ b/cli/cmd/vm_ls.go @@ -44,7 +44,6 @@ replicated vm ls --watch`, cmd.Flags().BoolVar(&r.args.lsVMShowTerminated, "show-terminated", false, "when set, only show terminated vms") cmd.Flags().StringVar(&r.args.lsVMStartTime, "start-time", "", "start time for the query (Format: 2006-01-02T15:04:05Z)") cmd.Flags().StringVar(&r.args.lsVMEndTime, "end-time", "", "end time for the query (Format: 2006-01-02T15:04:05Z)") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") cmd.Flags().BoolVarP(&r.args.lsVMWatch, "watch", "w", false, "watch vms") return cmd diff --git a/cli/cmd/vm_port_expose.go b/cli/cmd/vm_port_expose.go index 1c36de95b..8295b0715 100644 --- a/cli/cmd/vm_port_expose.go +++ b/cli/cmd/vm_port_expose.go @@ -38,7 +38,6 @@ replicated vm port expose VM_ID_OR_NAME --port 8080 --protocol https --output js panic(err) } cmd.Flags().StringSliceVar(&r.args.vmExposePortProtocols, "protocol", []string{"http", "https"}, `Protocol to expose (valid values are "http", "https", "ws" and "wss")`) - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") return cmd } diff --git a/cli/cmd/vm_port_ls.go b/cli/cmd/vm_port_ls.go index a29eede5b..07b948663 100644 --- a/cli/cmd/vm_port_ls.go +++ b/cli/cmd/vm_port_ls.go @@ -28,8 +28,6 @@ replicated vm port ls VM_ID_OR_NAME --output wide`, } parent.AddCommand(cmd) - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") - return cmd } diff --git a/cli/cmd/vm_port_rm.go b/cli/cmd/vm_port_rm.go index b51ba49e0..4d4069935 100644 --- a/cli/cmd/vm_port_rm.go +++ b/cli/cmd/vm_port_rm.go @@ -30,7 +30,6 @@ replicated vm port rm VM_ID_OR_NAME --id PORT_ID --output json`, if err != nil { panic(err) } - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") return cmd } diff --git a/cli/cmd/vm_update_ttl.go b/cli/cmd/vm_update_ttl.go index a0eedd624..4c5a60ab8 100644 --- a/cli/cmd/vm_update_ttl.go +++ b/cli/cmd/vm_update_ttl.go @@ -33,7 +33,6 @@ replicated vm update ttl my-test-vm --ttl 30m`, parent.AddCommand(cmd) cmd.Flags().StringVar(&r.args.updateVMTTL, "ttl", "", "Update TTL which starts from the moment the vm is running (duration, max 48h).") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table|wide") cmd.MarkFlagRequired("ttl") diff --git a/cli/cmd/vm_versions.go b/cli/cmd/vm_versions.go index d87d33685..242c86939 100644 --- a/cli/cmd/vm_versions.go +++ b/cli/cmd/vm_versions.go @@ -31,7 +31,6 @@ replicated vm versions --output json`, parent.AddCommand(cmd) cmd.Flags().StringVar(&r.args.lsVersionsDistribution, "distribution", "", "Kubernetes distribution to filter by.") - cmd.Flags().StringVarP(&r.outputFormat, "output", "o", "table", "The output format to use. One of: json|table") return cmd } diff --git a/cli/print/channel_adoption.go b/cli/print/channel_adoption.go index fd286cc13..3e52f2806 100644 --- a/cli/print/channel_adoption.go +++ b/cli/print/channel_adoption.go @@ -1,6 +1,7 @@ package print import ( + "encoding/json" "fmt" "text/tabwriter" "text/template" @@ -28,52 +29,66 @@ type licenseAdoption struct { Other allActiveCounts } -func ChannelAdoption(w *tabwriter.Writer, adoption *channels.ChannelAdoption) error { - countsByLicense := make(map[string]*licenseAdoption) +func ChannelAdoption(format string, w *tabwriter.Writer, adoption *channels.ChannelAdoption) error { + switch format { + case "json": + out, err := json.MarshalIndent(adoption, "", " ") + if err != nil { + return err + } + if _, err := fmt.Fprintln(w, string(out)); err != nil { + return err + } + return w.Flush() + case "table": + countsByLicense := make(map[string]*licenseAdoption) - var getOrSetLicenseAdoption = func(licenseType string) *licenseAdoption { - la, ok := countsByLicense[licenseType] - if !ok { - la = &licenseAdoption{} - countsByLicense[licenseType] = la + var getOrSetLicenseAdoption = func(licenseType string) *licenseAdoption { + la, ok := countsByLicense[licenseType] + if !ok { + la = &licenseAdoption{} + countsByLicense[licenseType] = la + } + return la } - return la - } - // current - for licenseType, count := range adoption.CurrentVersionCountActive { - getOrSetLicenseAdoption(licenseType).Current.Active = count - } - for licenseType, count := range adoption.CurrentVersionCountAll { - getOrSetLicenseAdoption(licenseType).Current.All = count - } + // current + for licenseType, count := range adoption.CurrentVersionCountActive { + getOrSetLicenseAdoption(licenseType).Current.Active = count + } + for licenseType, count := range adoption.CurrentVersionCountAll { + getOrSetLicenseAdoption(licenseType).Current.All = count + } - // previous - for licenseType, count := range adoption.PreviousVersionCountActive { - getOrSetLicenseAdoption(licenseType).Previous.Active = count - } - for licenseType, count := range adoption.PreviousVersionCountAll { - getOrSetLicenseAdoption(licenseType).Previous.All = count - } + // previous + for licenseType, count := range adoption.PreviousVersionCountActive { + getOrSetLicenseAdoption(licenseType).Previous.Active = count + } + for licenseType, count := range adoption.PreviousVersionCountAll { + getOrSetLicenseAdoption(licenseType).Previous.All = count + } - // other - for licenseType, count := range adoption.OtherVersionCountActive { - getOrSetLicenseAdoption(licenseType).Other.Active = count - } - for licenseType, count := range adoption.OtherVersionCountAll { - getOrSetLicenseAdoption(licenseType).Other.All = count - } + // other + for licenseType, count := range adoption.OtherVersionCountActive { + getOrSetLicenseAdoption(licenseType).Other.Active = count + } + for licenseType, count := range adoption.OtherVersionCountAll { + getOrSetLicenseAdoption(licenseType).Other.All = count + } - if len(countsByLicense) == 0 { - if _, err := fmt.Fprintln(w, "No active licenses in channel"); err != nil { + if len(countsByLicense) == 0 { + if _, err := fmt.Fprintln(w, "No active licenses in channel"); err != nil { + return err + } + return w.Flush() + } + + if err := channelAdoptionTmpl.Execute(w, countsByLicense); err != nil { return err } - return w.Flush() - } - if err := channelAdoptionTmpl.Execute(w, countsByLicense); err != nil { - return err + return w.Flush() + default: + return fmt.Errorf("unknown format: %s", format) } - - return w.Flush() } diff --git a/cli/print/channel_adoption_test.go b/cli/print/channel_adoption_test.go new file mode 100644 index 000000000..f85610bd4 --- /dev/null +++ b/cli/print/channel_adoption_test.go @@ -0,0 +1,83 @@ +package print + +import ( + "bytes" + "encoding/json" + "testing" + "text/tabwriter" + + channels "github.com/replicatedhq/replicated/gen/go/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestChannelAdoption_Table(t *testing.T) { + adoption := &channels.ChannelAdoption{ + CurrentVersionCountActive: map[string]int64{"paid": 5, "trial": 3}, + CurrentVersionCountAll: map[string]int64{"paid": 10, "trial": 6}, + PreviousVersionCountActive: map[string]int64{"paid": 2}, + PreviousVersionCountAll: map[string]int64{"paid": 4}, + OtherVersionCountActive: map[string]int64{"trial": 1}, + OtherVersionCountAll: map[string]int64{"trial": 2}, + } + + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 8, 4, ' ', tabwriter.TabIndent) + require.NoError(t, ChannelAdoption("table", w, adoption)) + + got := out.String() + assert.Contains(t, got, "LICENSE_TYPE") + assert.Contains(t, got, "CURRENT") + assert.Contains(t, got, "PREVIOUS") + assert.Contains(t, got, "OTHER") + assert.Contains(t, got, "paid") + assert.Contains(t, got, "trial") +} + +func TestChannelAdoption_JSON(t *testing.T) { + adoption := &channels.ChannelAdoption{ + CurrentVersionCountActive: map[string]int64{"paid": 5}, + CurrentVersionCountAll: map[string]int64{"paid": 10}, + PreviousVersionCountActive: map[string]int64{}, + PreviousVersionCountAll: map[string]int64{}, + OtherVersionCountActive: map[string]int64{}, + OtherVersionCountAll: map[string]int64{}, + } + + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 8, 4, ' ', tabwriter.TabIndent) + require.NoError(t, ChannelAdoption("json", w, adoption)) + + var decoded channels.ChannelAdoption + require.NoError(t, json.Unmarshal(out.Bytes(), &decoded)) + assert.Equal(t, int64(5), decoded.CurrentVersionCountActive["paid"]) + assert.Equal(t, int64(10), decoded.CurrentVersionCountAll["paid"]) +} + +func TestChannelAdoption_Empty_Table(t *testing.T) { + adoption := &channels.ChannelAdoption{} + + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 8, 4, ' ', tabwriter.TabIndent) + require.NoError(t, ChannelAdoption("table", w, adoption)) + + assert.Contains(t, out.String(), "No active licenses in channel") +} + +func TestChannelAdoption_Empty_JSON(t *testing.T) { + adoption := &channels.ChannelAdoption{} + + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 8, 4, ' ', tabwriter.TabIndent) + require.NoError(t, ChannelAdoption("json", w, adoption)) + + assert.Equal(t, "{}\n", out.String()) +} + +func TestChannelAdoption_UnknownFormat(t *testing.T) { + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 8, 4, ' ', tabwriter.TabIndent) + err := ChannelAdoption("yaml", w, &channels.ChannelAdoption{}) + require.Error(t, err) + assert.Contains(t, err.Error(), "unknown format: yaml") +} diff --git a/cli/print/channel_images.go b/cli/print/channel_images.go index 4116b76c4..56f08fa95 100644 --- a/cli/print/channel_images.go +++ b/cli/print/channel_images.go @@ -1,22 +1,37 @@ package print import ( + "encoding/json" "fmt" "sort" "text/tabwriter" ) -func ChannelImages(w *tabwriter.Writer, images []string) error { +func ChannelImages(format string, w *tabwriter.Writer, images []string) error { // Sort images for consistent output sort.Strings(images) - // Print header - fmt.Fprintln(w, "IMAGE") + switch format { + case "json": + out, err := json.MarshalIndent(images, "", " ") + if err != nil { + return err + } + if _, err := fmt.Fprintln(w, string(out)); err != nil { + return err + } + return w.Flush() + case "table": + // Print header + fmt.Fprintln(w, "IMAGE") - // Print each image - for _, image := range images { - fmt.Fprintln(w, image) - } + // Print each image + for _, image := range images { + fmt.Fprintln(w, image) + } - return w.Flush() + return w.Flush() + default: + return fmt.Errorf("unknown format: %s", format) + } } diff --git a/cli/print/channel_images_test.go b/cli/print/channel_images_test.go new file mode 100644 index 000000000..b4ce87144 --- /dev/null +++ b/cli/print/channel_images_test.go @@ -0,0 +1,60 @@ +package print + +import ( + "bytes" + "encoding/json" + "testing" + "text/tabwriter" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestChannelImages_Table(t *testing.T) { + images := []string{ + "nginx:1.27", + "postgres:14", + } + + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 8, 4, ' ', tabwriter.TabIndent) + require.NoError(t, ChannelImages("table", w, images)) + + got := out.String() + assert.Contains(t, got, "IMAGE") + assert.Contains(t, got, "nginx:1.27") + assert.Contains(t, got, "postgres:14") +} + +func TestChannelImages_JSON(t *testing.T) { + images := []string{ + "nginx:1.27", + "postgres:14", + } + + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 8, 4, ' ', tabwriter.TabIndent) + require.NoError(t, ChannelImages("json", w, images)) + + var decoded []string + require.NoError(t, json.Unmarshal(out.Bytes(), &decoded)) + assert.Equal(t, images, decoded) +} + +func TestChannelImages_Empty_JSON(t *testing.T) { + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 8, 4, ' ', tabwriter.TabIndent) + require.NoError(t, ChannelImages("json", w, []string{})) + + var decoded []string + require.NoError(t, json.Unmarshal(out.Bytes(), &decoded)) + assert.Empty(t, decoded) +} + +func TestChannelImages_UnknownFormat(t *testing.T) { + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 8, 4, ' ', tabwriter.TabIndent) + err := ChannelImages("yaml", w, []string{"nginx:1.27"}) + require.Error(t, err) + assert.Contains(t, err.Error(), "unknown format: yaml") +} diff --git a/cli/print/channel_license_counts.go b/cli/print/channel_license_counts.go index c2413e75b..a252d169b 100644 --- a/cli/print/channel_license_counts.go +++ b/cli/print/channel_license_counts.go @@ -1,6 +1,7 @@ package print import ( + "encoding/json" "fmt" "text/tabwriter" "text/template" @@ -20,41 +21,55 @@ type licenseTypeCounts struct { Active, Airgap, Inactive, Total int64 } -func LicenseCounts(w *tabwriter.Writer, counts *channels.LicenseCounts) error { - countsByLicenseType := make(map[string]*licenseTypeCounts) +func LicenseCounts(format string, w *tabwriter.Writer, counts *channels.LicenseCounts) error { + switch format { + case "json": + out, err := json.MarshalIndent(counts, "", " ") + if err != nil { + return err + } + if _, err := fmt.Fprintln(w, string(out)); err != nil { + return err + } + return w.Flush() + case "table": + countsByLicenseType := make(map[string]*licenseTypeCounts) - var getOrSetLicenseCounts = func(licenseType string) *licenseTypeCounts { - licenseCounts, ok := countsByLicenseType[licenseType] - if !ok { - licenseCounts = &licenseTypeCounts{} - countsByLicenseType[licenseType] = licenseCounts + var getOrSetLicenseCounts = func(licenseType string) *licenseTypeCounts { + licenseCounts, ok := countsByLicenseType[licenseType] + if !ok { + licenseCounts = &licenseTypeCounts{} + countsByLicenseType[licenseType] = licenseCounts + } + return licenseCounts } - return licenseCounts - } - for licenseType, count := range counts.Active { - getOrSetLicenseCounts(licenseType).Active = count - } - for licenseType, count := range counts.Airgap { - getOrSetLicenseCounts(licenseType).Airgap = count - } - for licenseType, count := range counts.Inactive { - getOrSetLicenseCounts(licenseType).Inactive = count - } - for licenseType, count := range counts.Total { - getOrSetLicenseCounts(licenseType).Total = count - } + for licenseType, count := range counts.Active { + getOrSetLicenseCounts(licenseType).Active = count + } + for licenseType, count := range counts.Airgap { + getOrSetLicenseCounts(licenseType).Airgap = count + } + for licenseType, count := range counts.Inactive { + getOrSetLicenseCounts(licenseType).Inactive = count + } + for licenseType, count := range counts.Total { + getOrSetLicenseCounts(licenseType).Total = count + } + + if len(countsByLicenseType) == 0 { + if _, err := fmt.Fprintln(w, "No active licenses in channel"); err != nil { + return err + } + return w.Flush() + } - if len(countsByLicenseType) == 0 { - if _, err := fmt.Fprintln(w, "No active licenses in channel"); err != nil { + if err := channelLicenseCountsTmpl.Execute(w, countsByLicenseType); err != nil { return err } - return w.Flush() - } - if err := channelLicenseCountsTmpl.Execute(w, countsByLicenseType); err != nil { - return err + return w.Flush() + default: + return fmt.Errorf("unknown format: %s", format) } - - return w.Flush() } diff --git a/cli/print/channel_license_counts_test.go b/cli/print/channel_license_counts_test.go new file mode 100644 index 000000000..20748b4fd --- /dev/null +++ b/cli/print/channel_license_counts_test.go @@ -0,0 +1,81 @@ +package print + +import ( + "bytes" + "encoding/json" + "testing" + "text/tabwriter" + + channels "github.com/replicatedhq/replicated/gen/go/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLicenseCounts_Table(t *testing.T) { + counts := &channels.LicenseCounts{ + Active: map[string]int64{"paid": 5, "trial": 3}, + Airgap: map[string]int64{"paid": 2}, + Inactive: map[string]int64{"trial": 1}, + Total: map[string]int64{"paid": 7, "trial": 4}, + } + + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 8, 4, ' ', tabwriter.TabIndent) + require.NoError(t, LicenseCounts("table", w, counts)) + + got := out.String() + assert.Contains(t, got, "LICENSE_TYPE") + assert.Contains(t, got, "ACTIVE") + assert.Contains(t, got, "AIRGAP") + assert.Contains(t, got, "INACTIVE") + assert.Contains(t, got, "TOTAL") + assert.Contains(t, got, "paid") + assert.Contains(t, got, "trial") +} + +func TestLicenseCounts_JSON(t *testing.T) { + counts := &channels.LicenseCounts{ + Active: map[string]int64{"paid": 5}, + Airgap: map[string]int64{"paid": 2}, + Inactive: map[string]int64{}, + Total: map[string]int64{"paid": 7}, + } + + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 8, 4, ' ', tabwriter.TabIndent) + require.NoError(t, LicenseCounts("json", w, counts)) + + var decoded channels.LicenseCounts + require.NoError(t, json.Unmarshal(out.Bytes(), &decoded)) + assert.Equal(t, int64(5), decoded.Active["paid"]) + assert.Equal(t, int64(2), decoded.Airgap["paid"]) + assert.Equal(t, int64(7), decoded.Total["paid"]) +} + +func TestLicenseCounts_Empty_Table(t *testing.T) { + counts := &channels.LicenseCounts{} + + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 8, 4, ' ', tabwriter.TabIndent) + require.NoError(t, LicenseCounts("table", w, counts)) + + assert.Contains(t, out.String(), "No active licenses in channel") +} + +func TestLicenseCounts_Empty_JSON(t *testing.T) { + counts := &channels.LicenseCounts{} + + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 8, 4, ' ', tabwriter.TabIndent) + require.NoError(t, LicenseCounts("json", w, counts)) + + assert.Equal(t, "{}\n", out.String()) +} + +func TestLicenseCounts_UnknownFormat(t *testing.T) { + var out bytes.Buffer + w := tabwriter.NewWriter(&out, 0, 8, 4, ' ', tabwriter.TabIndent) + err := LicenseCounts("yaml", w, &channels.LicenseCounts{}) + require.Error(t, err) + assert.Contains(t, err.Error(), "unknown format: yaml") +} diff --git a/pkg/platformclient/client.go b/pkg/platformclient/client.go index dbbc9f20a..75f77cc5a 100644 --- a/pkg/platformclient/client.go +++ b/pkg/platformclient/client.go @@ -29,6 +29,7 @@ var ( // This is a singleton that's reused for all requests to avoid leaking connections. var httpClient = &http.Client{ Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { // Check if the address is a .localhost domain host, port, err := net.SplitHostPort(addr)