Skip to content

Commit e3fb92e

Browse files
authored
feat(v3): customize install command help output based on targets (#2371)
* feat(v3): customize install command help output based on targets * f * feedback
1 parent 5e18495 commit e3fb92e

File tree

5 files changed

+239
-34
lines changed

5 files changed

+239
-34
lines changed

cmd/installer/cli/cidr.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@ import (
77
ecv1beta1 "github.com/replicatedhq/embedded-cluster/kinds/apis/v1beta1"
88
newconfig "github.com/replicatedhq/embedded-cluster/pkg-new/config"
99
"github.com/spf13/cobra"
10+
"github.com/spf13/pflag"
1011
)
1112

12-
func addCIDRFlags(cmd *cobra.Command) error {
13-
cmd.Flags().String("pod-cidr", k0sv1beta1.DefaultNetwork().PodCIDR, "IP address range for Pods")
14-
if err := cmd.Flags().MarkHidden("pod-cidr"); err != nil {
13+
func addCIDRFlags(flagSet *pflag.FlagSet) error {
14+
flagSet.String("pod-cidr", k0sv1beta1.DefaultNetwork().PodCIDR, "IP address range for Pods")
15+
if err := flagSet.MarkHidden("pod-cidr"); err != nil {
1516
return err
1617
}
17-
cmd.Flags().String("service-cidr", k0sv1beta1.DefaultNetwork().ServiceCIDR, "IP address range for Services")
18-
if err := cmd.Flags().MarkHidden("service-cidr"); err != nil {
18+
flagSet.String("service-cidr", k0sv1beta1.DefaultNetwork().ServiceCIDR, "IP address range for Services")
19+
if err := flagSet.MarkHidden("service-cidr"); err != nil {
1920
return err
2021
}
21-
cmd.Flags().String("cidr", ecv1beta1.DefaultNetworkCIDR, "CIDR block of available private IP addresses (/16 or larger)")
22+
flagSet.String("cidr", ecv1beta1.DefaultNetworkCIDR, "CIDR block of available private IP addresses (/16 or larger)")
2223

2324
return nil
2425
}

cmd/installer/cli/cidr_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func Test_getCIDRConfig(t *testing.T) {
8383
req := require.New(t)
8484

8585
cmd := &cobra.Command{}
86-
addCIDRFlags(cmd)
86+
addCIDRFlags(cmd.Flags())
8787

8888
test.setFlags(cmd.Flags())
8989

cmd/installer/cli/flags.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package cli
2+
3+
import (
4+
"os"
5+
"text/template"
6+
7+
"github.com/spf13/cobra"
8+
"github.com/spf13/pflag"
9+
)
10+
11+
const (
12+
flagAnnotationTarget = "replicated.com/target"
13+
flagAnnotationTargetValueLinux = "linux"
14+
flagAnnotationTargetValueKubernetes = "kubernetes"
15+
)
16+
17+
const (
18+
defaultUsageTemplateV3 = `Usage:{{if .Runnable}}
19+
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
20+
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
21+
22+
Aliases:
23+
{{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}}{{$cmds := .Commands}}{{if eq (len .Groups) 0}}
24+
25+
Available Commands:{{range $cmds}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
26+
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{else}}{{range $group := .Groups}}
27+
28+
{{.Title}}{{range $cmds}}{{if (and (eq .GroupID $group.ID) (or .IsAvailableCommand (eq .Name "help")))}}
29+
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if not .AllChildCommandsHaveGroup}}
30+
31+
Additional Commands:{{range $cmds}}{{if (and (eq .GroupID "") (or .IsAvailableCommand (eq .Name "help")))}}
32+
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}{{if (usesTargetFlagMenu .LocalFlags)}}
33+
34+
Common Flags:
35+
36+
{{(commonFlags .LocalFlags).FlagUsages | trimTrailingWhitespaces}}
37+
38+
Linux‑Specific Flags:
39+
(Valid only with --target=linux)
40+
41+
{{(linuxFlags .LocalFlags).FlagUsages | trimTrailingWhitespaces}}
42+
43+
Kubernetes‑Specific Flags:
44+
(Valid only with --target=kubernetes)
45+
46+
{{(kubernetesFlags .LocalFlags).FlagUsages | trimTrailingWhitespaces}}{{else}}
47+
48+
Flags:
49+
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{end}}{{if .HasAvailableInheritedFlags}}
50+
51+
Global Flags:
52+
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasExample}}
53+
54+
Examples:
55+
{{.Example}}{{end}}{{if .HasHelpSubCommands}}
56+
57+
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
58+
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
59+
60+
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
61+
`
62+
)
63+
64+
func init() {
65+
cobra.AddTemplateFuncs(template.FuncMap{
66+
// usesTargetFlagMenu returns true if the target flag is present and the ENABLE_V3 environment variable is set.
67+
"usesTargetFlagMenu": func(flagSet *pflag.FlagSet) bool {
68+
if os.Getenv("ENABLE_V3") == "1" {
69+
return flagSet.Lookup("target") != nil
70+
}
71+
return false
72+
},
73+
// commonFlags returns a flag set with all flags that do not have a target annotation.
74+
"commonFlags": func(flagSet *pflag.FlagSet) *pflag.FlagSet {
75+
return filterFlagSetNoTarget(flagSet)
76+
},
77+
// linuxFlags returns a flag set with all flags that have the target annotation set to linux.
78+
"linuxFlags": func(flagSet *pflag.FlagSet) *pflag.FlagSet {
79+
return filterFlagSetByTarget(flagSet, flagAnnotationTargetValueLinux)
80+
},
81+
// kubernetesFlags returns a flag set with all flags that have the target annotation set to kubernetes.
82+
"kubernetesFlags": func(flagSet *pflag.FlagSet) *pflag.FlagSet {
83+
return filterFlagSetByTarget(flagSet, flagAnnotationTargetValueKubernetes)
84+
},
85+
})
86+
}
87+
88+
func mustSetFlagTargetLinux(flags *pflag.FlagSet, name string) {
89+
mustSetFlagTarget(flags, name, flagAnnotationTargetValueLinux)
90+
}
91+
92+
func mustSetFlagTargetKubernetes(flags *pflag.FlagSet, name string) {
93+
mustSetFlagTarget(flags, name, flagAnnotationTargetValueKubernetes)
94+
}
95+
96+
func mustSetFlagTarget(flags *pflag.FlagSet, name string, target string) {
97+
err := flags.SetAnnotation(name, flagAnnotationTarget, []string{target})
98+
if err != nil {
99+
panic(err)
100+
}
101+
}
102+
103+
func filterFlagSetByTarget(flags *pflag.FlagSet, target string) *pflag.FlagSet {
104+
if flags == nil {
105+
return nil
106+
}
107+
next := pflag.NewFlagSet(flags.Name(), pflag.ContinueOnError)
108+
flags.VisitAll(func(flag *pflag.Flag) {
109+
for _, t := range flag.Annotations[flagAnnotationTarget] {
110+
if t == target {
111+
next.AddFlag(flag)
112+
break
113+
}
114+
}
115+
})
116+
return next
117+
}
118+
119+
func filterFlagSetNoTarget(flags *pflag.FlagSet) *pflag.FlagSet {
120+
if flags == nil {
121+
return nil
122+
}
123+
next := pflag.NewFlagSet(flags.Name(), pflag.ContinueOnError)
124+
flags.VisitAll(func(flag *pflag.Flag) {
125+
if len(flag.Annotations[flagAnnotationTarget]) == 0 {
126+
next.AddFlag(flag)
127+
}
128+
})
129+
return next
130+
}

cmd/installer/cli/install.go

Lines changed: 100 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,15 @@ func InstallCmd(ctx context.Context, name string) *cobra.Command {
9090
ctx, cancel := context.WithCancel(ctx)
9191
rc := runtimeconfig.New(nil)
9292

93+
short := fmt.Sprintf("Install %s", name)
94+
if os.Getenv("ENABLE_V3") == "1" {
95+
short = fmt.Sprintf("Install %s onto Linux or Kubernetes", name)
96+
}
97+
9398
cmd := &cobra.Command{
94-
Use: "install",
95-
Short: fmt.Sprintf("Install %s", name),
99+
Use: "install",
100+
Short: short,
101+
Example: installCmdExample(name),
96102
PostRun: func(cmd *cobra.Command, args []string) {
97103
rc.Cleanup()
98104
cancel() // Cancel context when command completes
@@ -138,6 +144,8 @@ func InstallCmd(ctx context.Context, name string) *cobra.Command {
138144
},
139145
}
140146

147+
cmd.SetUsageTemplate(defaultUsageTemplateV3)
148+
141149
if err := addInstallFlags(cmd, &flags); err != nil {
142150
panic(err)
143151
}
@@ -153,6 +161,88 @@ func InstallCmd(ctx context.Context, name string) *cobra.Command {
153161
return cmd
154162
}
155163

164+
const (
165+
installCmdExampleText = `
166+
# Install on a Linux host
167+
%s install \
168+
--target linux \
169+
--data-dir /opt/embedded-cluster \
170+
--license ./license.yaml \
171+
--yes
172+
173+
# Install in a Kubernetes cluster
174+
%s install \
175+
--target kubernetes \
176+
--kubeconfig ./kubeconfig \
177+
--airgap-bundle ./replicated.airgap \
178+
--license ./license.yaml
179+
`
180+
)
181+
182+
func installCmdExample(name string) string {
183+
if os.Getenv("ENABLE_V3") != "1" {
184+
return ""
185+
}
186+
187+
return fmt.Sprintf(installCmdExampleText, name, name)
188+
}
189+
190+
func newLinuxInstallFlags(flags *InstallCmdFlags) *pflag.FlagSet {
191+
flagSet := pflag.NewFlagSet("linux", pflag.ExitOnError)
192+
193+
flagSet.StringVar(&flags.dataDir, "data-dir", ecv1beta1.DefaultDataDir, "Path to the data directory")
194+
flagSet.IntVar(&flags.localArtifactMirrorPort, "local-artifact-mirror-port", ecv1beta1.DefaultLocalArtifactMirrorPort, "Port on which the Local Artifact Mirror will be served")
195+
flagSet.StringVar(&flags.networkInterface, "network-interface", "", "The network interface to use for the cluster")
196+
197+
flagSet.StringSlice("private-ca", []string{}, "Path to a trusted private CA certificate file")
198+
if err := flagSet.MarkHidden("private-ca"); err != nil {
199+
panic(err)
200+
}
201+
if err := flagSet.MarkDeprecated("private-ca", "This flag is no longer used and will be removed in a future version. The CA bundle will be automatically detected from the host."); err != nil {
202+
panic(err)
203+
}
204+
205+
flagSet.BoolVar(&flags.skipHostPreflights, "skip-host-preflights", false, "Skip host preflight checks. This is not recommended and has been deprecated.")
206+
if err := flagSet.MarkHidden("skip-host-preflights"); err != nil {
207+
panic(err)
208+
}
209+
if err := flagSet.MarkDeprecated("skip-host-preflights", "This flag is deprecated and will be removed in a future version. Use --ignore-host-preflights instead."); err != nil {
210+
panic(err)
211+
}
212+
flagSet.BoolVar(&flags.ignoreHostPreflights, "ignore-host-preflights", false, "Allow bypassing host preflight failures")
213+
214+
if err := addCIDRFlags(flagSet); err != nil {
215+
panic(err)
216+
}
217+
218+
flagSet.VisitAll(func(flag *pflag.Flag) {
219+
mustSetFlagTargetLinux(flagSet, flag.Name)
220+
})
221+
222+
return flagSet
223+
}
224+
225+
func newKubernetesInstallFlags(flags *InstallCmdFlags) *pflag.FlagSet {
226+
// If the ENABLE_V3 environment variable is set, do not hide the new flags.
227+
enableV3 := os.Getenv("ENABLE_V3") == "1"
228+
229+
flagSet := pflag.NewFlagSet("kubernetes", pflag.ExitOnError)
230+
231+
flagSet.String("kubeconfig", "", "Path to the kubeconfig file")
232+
233+
if !enableV3 {
234+
if err := flagSet.MarkHidden("kubeconfig"); err != nil {
235+
panic(err)
236+
}
237+
}
238+
239+
flagSet.VisitAll(func(flag *pflag.Flag) {
240+
mustSetFlagTargetKubernetes(flagSet, flag.Name)
241+
})
242+
243+
return flagSet
244+
}
245+
156246
func addInstallFlags(cmd *cobra.Command, flags *InstallCmdFlags) error {
157247
cmd.Flags().StringVar(&flags.target, "target", "linux", "The target platform to install to. Valid options are 'linux' or 'kubernetes'.")
158248
if os.Getenv("ENABLE_V3") != "1" {
@@ -162,40 +252,24 @@ func addInstallFlags(cmd *cobra.Command, flags *InstallCmdFlags) error {
162252
}
163253

164254
cmd.Flags().StringVar(&flags.airgapBundle, "airgap-bundle", "", "Path to the air gap bundle. If set, the installation will complete without internet access.")
165-
cmd.Flags().StringVar(&flags.dataDir, "data-dir", ecv1beta1.DefaultDataDir, "Path to the data directory")
166-
cmd.Flags().IntVar(&flags.localArtifactMirrorPort, "local-artifact-mirror-port", ecv1beta1.DefaultLocalArtifactMirrorPort, "Port on which the Local Artifact Mirror will be served")
167-
cmd.Flags().StringVar(&flags.networkInterface, "network-interface", "", "The network interface to use for the cluster")
168-
cmd.Flags().BoolVarP(&flags.assumeYes, "yes", "y", false, "Assume yes to all prompts.")
169-
cmd.Flags().SetNormalizeFunc(normalizeNoPromptToYes)
170255

171256
cmd.Flags().StringVar(&flags.overrides, "overrides", "", "File with an EmbeddedClusterConfig object to override the default configuration")
172257
if err := cmd.Flags().MarkHidden("overrides"); err != nil {
173258
return err
174259
}
175260

176-
cmd.Flags().StringSlice("private-ca", []string{}, "Path to a trusted private CA certificate file")
177-
if err := cmd.Flags().MarkHidden("private-ca"); err != nil {
178-
return err
179-
}
180-
if err := cmd.Flags().MarkDeprecated("private-ca", "This flag is no longer used and will be removed in a future version. The CA bundle will be automatically detected from the host."); err != nil {
181-
return err
182-
}
183-
184261
if err := addProxyFlags(cmd); err != nil {
185262
return err
186263
}
187-
if err := addCIDRFlags(cmd); err != nil {
188-
return err
189-
}
190264

191-
cmd.Flags().BoolVar(&flags.skipHostPreflights, "skip-host-preflights", false, "Skip host preflight checks. This is not recommended and has been deprecated.")
192-
if err := cmd.Flags().MarkHidden("skip-host-preflights"); err != nil {
193-
return err
194-
}
195-
if err := cmd.Flags().MarkDeprecated("skip-host-preflights", "This flag is deprecated and will be removed in a future version. Use --ignore-host-preflights instead."); err != nil {
196-
return err
197-
}
198-
cmd.Flags().BoolVar(&flags.ignoreHostPreflights, "ignore-host-preflights", false, "Allow bypassing host preflight failures")
265+
cmd.Flags().BoolVarP(&flags.assumeYes, "yes", "y", false, "Assume yes to all prompts.")
266+
cmd.Flags().SetNormalizeFunc(normalizeNoPromptToYes)
267+
268+
linuxFlagSet := newLinuxInstallFlags(flags)
269+
cmd.Flags().AddFlagSet(linuxFlagSet)
270+
271+
kubernetesFlagSet := newKubernetesInstallFlags(flags)
272+
cmd.Flags().AddFlagSet(kubernetesFlagSet)
199273

200274
return nil
201275
}

cmd/installer/cli/proxy_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func Test_getProxySpecFromFlags(t *testing.T) {
151151
for _, tt := range tests {
152152
t.Run(tt.name, func(t *testing.T) {
153153
cmd := &cobra.Command{}
154-
addCIDRFlags(cmd)
154+
addCIDRFlags(cmd.Flags())
155155
addProxyFlags(cmd)
156156
cmd.Flags().String("network-interface", "", "The network interface to use for the cluster")
157157

0 commit comments

Comments
 (0)