diff --git a/pkg/cmd/hgctl/configBootstrap.go b/pkg/cmd/hgctl/config_bootstrap.go similarity index 100% rename from pkg/cmd/hgctl/configBootstrap.go rename to pkg/cmd/hgctl/config_bootstrap.go diff --git a/pkg/cmd/hgctl/configCluster.go b/pkg/cmd/hgctl/config_cluster.go similarity index 100% rename from pkg/cmd/hgctl/configCluster.go rename to pkg/cmd/hgctl/config_cluster.go diff --git a/pkg/cmd/hgctl/configCmd.go b/pkg/cmd/hgctl/config_cmd.go similarity index 100% rename from pkg/cmd/hgctl/configCmd.go rename to pkg/cmd/hgctl/config_cmd.go diff --git a/pkg/cmd/hgctl/configEndpoint.go b/pkg/cmd/hgctl/config_endpoint.go similarity index 100% rename from pkg/cmd/hgctl/configEndpoint.go rename to pkg/cmd/hgctl/config_endpoint.go diff --git a/pkg/cmd/hgctl/configListener.go b/pkg/cmd/hgctl/config_listener.go similarity index 100% rename from pkg/cmd/hgctl/configListener.go rename to pkg/cmd/hgctl/config_listener.go diff --git a/pkg/cmd/hgctl/configRetriever.go b/pkg/cmd/hgctl/config_retriever.go similarity index 100% rename from pkg/cmd/hgctl/configRetriever.go rename to pkg/cmd/hgctl/config_retriever.go diff --git a/pkg/cmd/hgctl/configRoute.go b/pkg/cmd/hgctl/config_route.go similarity index 100% rename from pkg/cmd/hgctl/configRoute.go rename to pkg/cmd/hgctl/config_route.go diff --git a/pkg/cmd/hgctl/install.go b/pkg/cmd/hgctl/install.go index ab983ce4f..15c9b96ea 100644 --- a/pkg/cmd/hgctl/install.go +++ b/pkg/cmd/hgctl/install.go @@ -180,7 +180,7 @@ func InstallManifests(profile *helm.Profile, writer io.Writer) error { return fmt.Errorf("failed to build kubernetes client: %w", err) } - op, err := installer.NewInstaller(profile, cliClient, writer) + op, err := installer.NewInstaller(profile, cliClient, writer, false) if err != nil { return err } diff --git a/pkg/cmd/hgctl/installer/component.go b/pkg/cmd/hgctl/installer/component.go index 790b78c80..5f03973e9 100644 --- a/pkg/cmd/hgctl/installer/component.go +++ b/pkg/cmd/hgctl/installer/component.go @@ -15,10 +15,6 @@ package installer import ( - "fmt" - "io" - "os" - "github.com/alibaba/higress/pkg/cmd/hgctl/helm" "github.com/alibaba/higress/pkg/cmd/hgctl/util" "sigs.k8s.io/yaml" @@ -26,14 +22,9 @@ import ( type ComponentName string -const ( - Higress ComponentName = "higress" - Istio ComponentName = "istio" -) - -var ComponentMap = map[string]ComponentName{ - "higress": Higress, - "istio": Istio, +var ComponentMap = map[ComponentName]struct{}{ + Higress: {}, + Istio: {}, } type Component interface { @@ -57,6 +48,7 @@ type ComponentOptions struct { RepoURL string ChartName string Version string + Quiet bool } type ComponentOption func(*ComponentOptions) @@ -91,186 +83,10 @@ func WithComponentVersion(version string) ComponentOption { } } -type HigressComponent struct { - profile *helm.Profile - started bool - opts *ComponentOptions - renderer helm.Renderer - writer io.Writer -} - -func (h *HigressComponent) ComponentName() ComponentName { - return Higress -} - -func (h *HigressComponent) Namespace() string { - return h.opts.Namespace -} - -func (h *HigressComponent) Enabled() bool { - return true -} - -func (h *HigressComponent) Run() error { - // Parse latest version - if h.opts.Version == helm.RepoLatestVersion { - - latestVersion, err := helm.ParseLatestVersion(h.opts.RepoURL, h.opts.Version) - if err != nil { - return err - } - fmt.Fprintf(h.writer, "⚡️ Fetching Higress Helm Chart latest version \"%s\" \n", latestVersion) - - // Reset Helm Chart version - h.opts.Version = latestVersion - h.renderer.SetVersion(latestVersion) +func WithQuiet() ComponentOption { + return func(opts *ComponentOptions) { + opts.Quiet = true } - - fmt.Fprintf(h.writer, "🏄 Downloading Higress Helm Chart version: %s, url: %s\n", h.opts.Version, h.opts.RepoURL) - - if err := h.renderer.Init(); err != nil { - return err - } - h.started = true - return nil -} - -func (h *HigressComponent) RenderManifest() (string, error) { - if !h.started { - return "", nil - } - fmt.Fprintf(h.writer, "📦 Rendering Higress Helm Chart\n") - valsYaml, err := h.profile.ValuesYaml() - if err != nil { - return "", err - } - manifest, err2 := renderComponentManifest(valsYaml, h.renderer, true, h.ComponentName(), h.opts.Namespace) - if err2 != nil { - return "", err - } - return manifest, nil -} - -func NewHigressComponent(profile *helm.Profile, writer io.Writer, opts ...ComponentOption) (Component, error) { - newOpts := &ComponentOptions{} - for _, opt := range opts { - opt(newOpts) - } - - var renderer helm.Renderer - var err error - if newOpts.RepoURL != "" { - renderer, err = helm.NewRemoteRenderer( - helm.WithName(newOpts.ChartName), - helm.WithNamespace(newOpts.Namespace), - helm.WithRepoURL(newOpts.RepoURL), - helm.WithVersion(newOpts.Version), - ) - if err != nil { - return nil, err - } - } else { - renderer, err = helm.NewLocalRenderer( - helm.WithName(newOpts.ChartName), - helm.WithNamespace(newOpts.Namespace), - helm.WithVersion(newOpts.Version), - helm.WithFS(os.DirFS(newOpts.ChartPath)), - helm.WithDir(string(Higress)), - ) - if err != nil { - return nil, err - } - } - - higressComponent := &HigressComponent{ - profile: profile, - renderer: renderer, - opts: newOpts, - writer: writer, - } - return higressComponent, nil -} - -type IstioCRDComponent struct { - profile *helm.Profile - started bool - opts *ComponentOptions - renderer helm.Renderer - writer io.Writer -} - -func NewIstioCRDComponent(profile *helm.Profile, writer io.Writer, opts ...ComponentOption) (Component, error) { - newOpts := &ComponentOptions{} - for _, opt := range opts { - opt(newOpts) - } - - var renderer helm.Renderer - var err error - if newOpts.RepoURL != "" { - renderer, err = helm.NewRemoteRenderer( - helm.WithName(newOpts.ChartName), - helm.WithNamespace(newOpts.Namespace), - helm.WithRepoURL(newOpts.RepoURL), - helm.WithVersion(newOpts.Version), - ) - if err != nil { - return nil, err - } - } else { - renderer, err = helm.NewLocalRenderer( - helm.WithName(newOpts.ChartName), - helm.WithNamespace(newOpts.Namespace), - helm.WithVersion(newOpts.Version), - helm.WithFS(os.DirFS(newOpts.ChartPath)), - helm.WithDir(string(Istio)), - ) - if err != nil { - return nil, err - } - } - - istioComponent := &IstioCRDComponent{ - profile: profile, - renderer: renderer, - opts: newOpts, - writer: writer, - } - return istioComponent, nil -} - -func (i *IstioCRDComponent) ComponentName() ComponentName { - return Istio -} - -func (i *IstioCRDComponent) Namespace() string { - return i.opts.Namespace -} - -func (i *IstioCRDComponent) Enabled() bool { - return true -} - -func (i *IstioCRDComponent) Run() error { - fmt.Fprintf(i.writer, "🏄 Downloading Istio Helm Chart version: %s, url: %s\n", i.opts.Version, i.opts.RepoURL) - if err := i.renderer.Init(); err != nil { - return err - } - i.started = true - return nil -} - -func (i *IstioCRDComponent) RenderManifest() (string, error) { - if !i.started { - return "", nil - } - fmt.Fprintf(i.writer, "📦 Rendering Istio Helm Chart\n") - values := make(map[string]any) - manifest, err := renderComponentManifest(values, i.renderer, false, i.ComponentName(), i.opts.Namespace) - if err != nil { - return "", err - } - return manifest, nil } func renderComponentManifest(spec any, renderer helm.Renderer, addOn bool, name ComponentName, namespace string) (string, error) { diff --git a/pkg/cmd/hgctl/installer/higress.go b/pkg/cmd/hgctl/installer/higress.go new file mode 100644 index 000000000..1a2bddaaf --- /dev/null +++ b/pkg/cmd/hgctl/installer/higress.go @@ -0,0 +1,131 @@ +// Copyright (c) 2022 Alibaba Group Holding Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package installer + +import ( + "fmt" + "io" + "os" + + "github.com/alibaba/higress/pkg/cmd/hgctl/helm" +) + +const ( + Higress ComponentName = "higress" +) + +type HigressComponent struct { + profile *helm.Profile + started bool + opts *ComponentOptions + renderer helm.Renderer + writer io.Writer +} + +func (h *HigressComponent) ComponentName() ComponentName { + return Higress +} + +func (h *HigressComponent) Namespace() string { + return h.opts.Namespace +} + +func (h *HigressComponent) Enabled() bool { + return true +} + +func (h *HigressComponent) Run() error { + // Parse latest version + if h.opts.Version == helm.RepoLatestVersion { + + latestVersion, err := helm.ParseLatestVersion(h.opts.RepoURL, h.opts.Version) + if err != nil { + return err + } + if !h.opts.Quiet { + fmt.Fprintf(h.writer, "⚡️ Fetching Higress Helm Chart latest version \"%s\" \n", latestVersion) + } + + // Reset Helm Chart version + h.opts.Version = latestVersion + h.renderer.SetVersion(latestVersion) + } + if !h.opts.Quiet { + fmt.Fprintf(h.writer, "🏄 Downloading Higress Helm Chart version: %s, url: %s\n", h.opts.Version, h.opts.RepoURL) + } + if err := h.renderer.Init(); err != nil { + return err + } + h.started = true + return nil +} + +func (h *HigressComponent) RenderManifest() (string, error) { + if !h.started { + return "", nil + } + if !h.opts.Quiet { + fmt.Fprintf(h.writer, "📦 Rendering Higress Helm Chart\n") + } + valsYaml, err := h.profile.ValuesYaml() + if err != nil { + return "", err + } + manifest, err2 := renderComponentManifest(valsYaml, h.renderer, true, h.ComponentName(), h.opts.Namespace) + if err2 != nil { + return "", err + } + return manifest, nil +} + +func NewHigressComponent(profile *helm.Profile, writer io.Writer, opts ...ComponentOption) (Component, error) { + newOpts := &ComponentOptions{} + for _, opt := range opts { + opt(newOpts) + } + + var renderer helm.Renderer + var err error + if newOpts.RepoURL != "" { + renderer, err = helm.NewRemoteRenderer( + helm.WithName(newOpts.ChartName), + helm.WithNamespace(newOpts.Namespace), + helm.WithRepoURL(newOpts.RepoURL), + helm.WithVersion(newOpts.Version), + ) + if err != nil { + return nil, err + } + } else { + renderer, err = helm.NewLocalRenderer( + helm.WithName(newOpts.ChartName), + helm.WithNamespace(newOpts.Namespace), + helm.WithVersion(newOpts.Version), + helm.WithFS(os.DirFS(newOpts.ChartPath)), + helm.WithDir(string(Higress)), + ) + if err != nil { + return nil, err + } + } + + higressComponent := &HigressComponent{ + profile: profile, + renderer: renderer, + opts: newOpts, + writer: writer, + } + return higressComponent, nil +} diff --git a/pkg/cmd/hgctl/installer/installer.go b/pkg/cmd/hgctl/installer/installer.go index e19ce0b33..8955d92af 100644 --- a/pkg/cmd/hgctl/installer/installer.go +++ b/pkg/cmd/hgctl/installer/installer.go @@ -65,6 +65,17 @@ func (o *Installer) RenderManifests() (map[ComponentName]string, error) { return res, nil } +// GenerateManifests generates component manifests to k8s cluster +func (o *Installer) GenerateManifests(manifestMap map[ComponentName]string) error { + if o.kubeCli == nil { + return errors.New("no injected k8s cli into Installer") + } + for _, manifest := range manifestMap { + fmt.Fprint(o.writer, manifest) + } + return nil +} + // ApplyManifests apply component manifests to k8s cluster func (o *Installer) ApplyManifests(manifestMap map[ComponentName]string) error { if o.kubeCli == nil { @@ -152,32 +163,41 @@ func (o *Installer) isNamespacedObject(obj *object.K8sObject) bool { return false } -func NewInstaller(profile *helm.Profile, cli kubernetes.CLIClient, writer io.Writer) (*Installer, error) { +func NewInstaller(profile *helm.Profile, cli kubernetes.CLIClient, writer io.Writer, quiet bool) (*Installer, error) { if profile == nil { return nil, errors.New("install profile is empty") } // initialize components components := make(map[ComponentName]Component) - higressComponent, err := NewHigressComponent(profile, writer, + opts := []ComponentOption{ WithComponentNamespace(profile.Global.Namespace), WithComponentChartPath(profile.InstallPackagePath), WithComponentVersion(profile.Charts.Higress.Version), WithComponentRepoURL(profile.Charts.Higress.Url), WithComponentChartName(profile.Charts.Higress.Name), - ) + } + if quiet { + opts = append(opts, WithQuiet()) + } + higressComponent, err := NewHigressComponent(profile, writer, opts...) if err != nil { return nil, fmt.Errorf("NewHigressComponent failed, err: %s", err) } components[Higress] = higressComponent if profile.IstioEnabled() { - istioCRDComponent, err := NewIstioCRDComponent(profile, writer, + opts := []ComponentOption{ WithComponentNamespace(profile.Global.IstioNamespace), WithComponentChartPath(profile.InstallPackagePath), WithComponentVersion(profile.Charts.Istio.Version), WithComponentRepoURL(profile.Charts.Istio.Url), WithComponentChartName(profile.Charts.Istio.Name), - ) + } + if quiet { + opts = append(opts, WithQuiet()) + } + + istioCRDComponent, err := NewIstioCRDComponent(profile, writer, opts...) if err != nil { return nil, fmt.Errorf("NewIstioCRDComponent failed, err: %s", err) } diff --git a/pkg/cmd/hgctl/installer/istio.go b/pkg/cmd/hgctl/installer/istio.go new file mode 100644 index 000000000..40ee71141 --- /dev/null +++ b/pkg/cmd/hgctl/installer/istio.go @@ -0,0 +1,113 @@ +// Copyright (c) 2022 Alibaba Group Holding Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package installer + +import ( + "fmt" + "io" + "os" + + "github.com/alibaba/higress/pkg/cmd/hgctl/helm" +) + +const ( + Istio ComponentName = "istio" +) + +type IstioCRDComponent struct { + profile *helm.Profile + started bool + opts *ComponentOptions + renderer helm.Renderer + writer io.Writer +} + +func NewIstioCRDComponent(profile *helm.Profile, writer io.Writer, opts ...ComponentOption) (Component, error) { + newOpts := &ComponentOptions{} + for _, opt := range opts { + opt(newOpts) + } + + var renderer helm.Renderer + var err error + if newOpts.RepoURL != "" { + renderer, err = helm.NewRemoteRenderer( + helm.WithName(newOpts.ChartName), + helm.WithNamespace(newOpts.Namespace), + helm.WithRepoURL(newOpts.RepoURL), + helm.WithVersion(newOpts.Version), + ) + if err != nil { + return nil, err + } + } else { + renderer, err = helm.NewLocalRenderer( + helm.WithName(newOpts.ChartName), + helm.WithNamespace(newOpts.Namespace), + helm.WithVersion(newOpts.Version), + helm.WithFS(os.DirFS(newOpts.ChartPath)), + helm.WithDir(string(Istio)), + ) + if err != nil { + return nil, err + } + } + + istioComponent := &IstioCRDComponent{ + profile: profile, + renderer: renderer, + opts: newOpts, + writer: writer, + } + return istioComponent, nil +} + +func (i *IstioCRDComponent) ComponentName() ComponentName { + return Istio +} + +func (i *IstioCRDComponent) Namespace() string { + return i.opts.Namespace +} + +func (i *IstioCRDComponent) Enabled() bool { + return true +} + +func (i *IstioCRDComponent) Run() error { + if !i.opts.Quiet { + fmt.Fprintf(i.writer, "🏄 Downloading Istio Helm Chart version: %s, url: %s\n", i.opts.Version, i.opts.RepoURL) + } + if err := i.renderer.Init(); err != nil { + return err + } + i.started = true + return nil +} + +func (i *IstioCRDComponent) RenderManifest() (string, error) { + if !i.started { + return "", nil + } + if !i.opts.Quiet { + fmt.Fprintf(i.writer, "📦 Rendering Istio Helm Chart\n") + } + values := make(map[string]any) + manifest, err := renderComponentManifest(values, i.renderer, false, i.ComponentName(), i.opts.Namespace) + if err != nil { + return "", err + } + return manifest, nil +} diff --git a/pkg/cmd/hgctl/manifest.go b/pkg/cmd/hgctl/manifest.go new file mode 100644 index 000000000..44ef656fd --- /dev/null +++ b/pkg/cmd/hgctl/manifest.go @@ -0,0 +1,143 @@ +// Copyright (c) 2022 Alibaba Group Holding Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hgctl + +import ( + "fmt" + "io" + "strings" + + "github.com/alibaba/higress/pkg/cmd/hgctl/helm" + "github.com/alibaba/higress/pkg/cmd/hgctl/installer" + "github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes" + "github.com/alibaba/higress/pkg/cmd/options" + "github.com/spf13/cobra" +) + +type ManifestArgs struct { + InFilenames []string + // KubeConfigPath is the path to kube config file. + KubeConfigPath string + // Context is the cluster context in the kube config + Context string + // Set is a string with element format "path=value" where path is an profile path and the value is a + // value to set the node at that path to. + Set []string + // ManifestsPath is a path to a ManifestsPath and profiles directory in the local filesystem with a release tgz. + ManifestsPath string +} + +func (a *ManifestArgs) String() string { + var b strings.Builder + b.WriteString("KubeConfigPath: " + a.KubeConfigPath + "\n") + b.WriteString("Context: " + a.Context + "\n") + b.WriteString("Set: " + fmt.Sprint(a.Set) + "\n") + b.WriteString("ManifestsPath: " + a.ManifestsPath + "\n") + return b.String() +} + +// newManifestCmd generates a higress install manifest and applies it to a cluster +func newManifestCmd() *cobra.Command { + iArgs := &ManifestArgs{} + manifestCmd := &cobra.Command{ + Use: "manifest", + Short: "Generate higress manifests.", + Long: "The manifest command generates an higress install manifests.", + } + + generate := newManifestGenerateCmd(iArgs) + addManifestFlags(generate, iArgs) + manifestCmd.AddCommand(generate) + + return manifestCmd +} + +func addManifestFlags(cmd *cobra.Command, args *ManifestArgs) { + cmd.PersistentFlags().StringArrayVarP(&args.Set, "set", "s", nil, setFlagHelpStr) + cmd.PersistentFlags().StringVarP(&args.ManifestsPath, "manifests", "d", "", manifestsFlagHelpStr) +} + +// newManifestGenerateCmd generates a higress install manifest and applies it to a cluster +func newManifestGenerateCmd(iArgs *ManifestArgs) *cobra.Command { + installCmd := &cobra.Command{ + Use: "generate", + Short: "Generate higress manifests.", + Long: "The manifest generate command generates higress install manifests.", + // nolint: lll + Example: ` # Generate higress manifests + hgctl manifest generate +`, + Args: cobra.ExactArgs(0), + PreRunE: func(cmd *cobra.Command, args []string) error { + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return generate(cmd.OutOrStdout(), iArgs) + }, + } + + return installCmd +} + +func generate(writer io.Writer, iArgs *ManifestArgs) error { + setFlags := applyFlagAliases(iArgs.Set, iArgs.ManifestsPath) + + // check profileName + psf := helm.GetValueForSetFlag(setFlags, "profile") + if len(psf) == 0 { + setFlags = append(setFlags, fmt.Sprintf("profile=%s", helm.InstallLocalK8s)) + } + + _, profile, _, err := helm.GenerateConfig(iArgs.InFilenames, setFlags) + if err != nil { + return fmt.Errorf("generate config: %v", err) + } + + err = profile.Validate() + if err != nil { + return err + } + + err = genManifests(profile, writer) + if err != nil { + return fmt.Errorf("failed to install manifests: %v", err) + } + return nil +} + +func genManifests(profile *helm.Profile, writer io.Writer) error { + cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader()) + if err != nil { + return fmt.Errorf("failed to build kubernetes client: %w", err) + } + + op, err := installer.NewInstaller(profile, cliClient, writer, true) + if err != nil { + return err + } + if err := op.Run(); err != nil { + return err + } + + manifestMap, err := op.RenderManifests() + if err != nil { + return err + } + + if err := op.GenerateManifests(manifestMap); err != nil { + return err + } + return nil +} diff --git a/pkg/cmd/hgctl/profileDump.go b/pkg/cmd/hgctl/profile_dump.go similarity index 99% rename from pkg/cmd/hgctl/profileDump.go rename to pkg/cmd/hgctl/profile_dump.go index 2caec5bb0..1a6f7c89e 100644 --- a/pkg/cmd/hgctl/profileDump.go +++ b/pkg/cmd/hgctl/profile_dump.go @@ -16,9 +16,10 @@ package hgctl import ( "fmt" + "os" + "github.com/alibaba/higress/pkg/cmd/hgctl/helm" "github.com/spf13/cobra" - "os" ) type profileDumpArgs struct { diff --git a/pkg/cmd/hgctl/profileList.go b/pkg/cmd/hgctl/profile_list.go similarity index 100% rename from pkg/cmd/hgctl/profileList.go rename to pkg/cmd/hgctl/profile_list.go diff --git a/pkg/cmd/hgctl/root.go b/pkg/cmd/hgctl/root.go index 33284300e..9dd58c095 100644 --- a/pkg/cmd/hgctl/root.go +++ b/pkg/cmd/hgctl/root.go @@ -33,6 +33,7 @@ func GetRootCommand() *cobra.Command { rootCmd.AddCommand(newUpgradeCmd()) rootCmd.AddCommand(newProfileCmd()) rootCmd.AddCommand(newDashboardCmd()) + rootCmd.AddCommand(newManifestCmd()) return rootCmd } diff --git a/pkg/cmd/hgctl/uninstall.go b/pkg/cmd/hgctl/uninstall.go index c49dd71cf..fb9d0726a 100644 --- a/pkg/cmd/hgctl/uninstall.go +++ b/pkg/cmd/hgctl/uninstall.go @@ -114,7 +114,7 @@ func UnInstallManifests(profile *helm.Profile, writer io.Writer) error { return fmt.Errorf("failed to build kubernetes client: %w", err) } - op, err := installer.NewInstaller(profile, cliClient, writer) + op, err := installer.NewInstaller(profile, cliClient, writer, false) if err != nil { return err }