From 94e57395d8f72e135de6ceaf5d27f972da7f0bc3 Mon Sep 17 00:00:00 2001 From: Maciej Szulik Date: Thu, 12 Dec 2024 14:02:55 +0100 Subject: [PATCH] refactor: Create structs for each command to better isolate commands Signed-off-by: Maciej Szulik --- src/cmd/connect.go | 179 ++++---- src/cmd/destroy.go | 159 +++---- src/cmd/dev.go | 657 ++++++++++++++++------------ src/cmd/initialize.go | 192 +++++---- src/cmd/internal.go | 551 ++++++++++++++---------- src/cmd/package.go | 980 ++++++++++++++++++++++-------------------- src/cmd/root.go | 37 +- 7 files changed, 1527 insertions(+), 1228 deletions(-) diff --git a/src/cmd/connect.go b/src/cmd/connect.go index 9a7a13a241..2b2208692a 100644 --- a/src/cmd/connect.go +++ b/src/cmd/connect.go @@ -16,100 +16,121 @@ import ( "github.com/zarf-dev/zarf/src/pkg/utils/exec" ) -var ( +// ConnectOptions holds the command-line options for 'connect' sub-command. +type ConnectOptions struct { cliOnly bool zt cluster.TunnelInfo -) -var connectCmd = &cobra.Command{ - Use: "connect { REGISTRY | GIT | connect-name }", - Aliases: []string{"c"}, - Short: lang.CmdConnectShort, - Long: lang.CmdConnectLong, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - l := logger.From(ctx) - target := "" - if len(args) > 0 { - target = args[0] - } - - spinner := message.NewProgressSpinner(lang.CmdConnectPreparingTunnel, target) - defer spinner.Stop() +} - c, err := cluster.NewCluster() - if err != nil { - return err - } +// NewConnectCommand creates the `connect` sub-command and its nested children. +func NewConnectCommand() *cobra.Command { + o := &ConnectOptions{} - var tunnel *cluster.Tunnel - if target == "" { - tunnel, err = c.ConnectTunnelInfo(ctx, zt) - } else { - var ti cluster.TunnelInfo - ti, err = c.NewTargetTunnelInfo(ctx, target) - if err != nil { - return fmt.Errorf("unable to create tunnel: %w", err) - } - if zt.LocalPort != 0 { - ti.LocalPort = zt.LocalPort - } - tunnel, err = c.ConnectTunnelInfo(ctx, ti) - } + cmd := &cobra.Command{ + Use: "connect { REGISTRY | GIT | connect-name }", + Aliases: []string{"c"}, + Short: lang.CmdConnectShort, + Long: lang.CmdConnectLong, + RunE: o.Run, + } - if err != nil { - return fmt.Errorf("unable to connect to the service: %w", err) - } + cmd.Flags().StringVar(&o.zt.ResourceName, "name", "", lang.CmdConnectFlagName) + cmd.Flags().StringVar(&o.zt.Namespace, "namespace", cluster.ZarfNamespaceName, lang.CmdConnectFlagNamespace) + cmd.Flags().StringVar(&o.zt.ResourceType, "type", cluster.SvcResource, lang.CmdConnectFlagType) + cmd.Flags().IntVar(&o.zt.LocalPort, "local-port", 0, lang.CmdConnectFlagLocalPort) + cmd.Flags().IntVar(&o.zt.RemotePort, "remote-port", 0, lang.CmdConnectFlagRemotePort) + cmd.Flags().BoolVar(&o.cliOnly, "cli-only", false, lang.CmdConnectFlagCliOnly) - defer tunnel.Close() - - if cliOnly { - spinner.Updatef(lang.CmdConnectEstablishedCLI, tunnel.FullURL()) - l.Info("Tunnel established, waiting for user to interrupt (ctrl-c to end)", "url", tunnel.FullURL()) - } else { - spinner.Updatef(lang.CmdConnectEstablishedWeb, tunnel.FullURL()) - l.Info("Tunnel established, opening your default web browser (ctrl-c to end)", "url", tunnel.FullURL()) - if err := exec.LaunchURL(tunnel.FullURL()); err != nil { - return err - } - } + // TODO(soltysh): consider splitting sub-commands into separate files + cmd.AddCommand(NewConnectListCommand()) - // Wait for the interrupt signal or an error. - select { - case <-ctx.Done(): - spinner.Successf(lang.CmdConnectTunnelClosed, tunnel.FullURL()) - return nil - case err = <-tunnel.ErrChan(): - return fmt.Errorf("lost connection to the service: %w", err) - } - }, + return cmd } -var connectListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"l"}, - Short: lang.CmdConnectListShort, - RunE: func(cmd *cobra.Command, _ []string) error { - c, err := cluster.NewCluster() +// Run performs the execution of 'connect' sub command. +func (o *ConnectOptions) Run(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + l := logger.From(ctx) + target := "" + if len(args) > 0 { + target = args[0] + } + + spinner := message.NewProgressSpinner(lang.CmdConnectPreparingTunnel, target) + defer spinner.Stop() + + c, err := cluster.NewCluster() + if err != nil { + return err + } + + var tunnel *cluster.Tunnel + if target == "" { + tunnel, err = c.ConnectTunnelInfo(ctx, o.zt) + } else { + var ti cluster.TunnelInfo + ti, err = c.NewTargetTunnelInfo(ctx, target) if err != nil { - return err + return fmt.Errorf("unable to create tunnel: %w", err) } - connections, err := c.ListConnections(cmd.Context()) - if err != nil { + if o.zt.LocalPort != 0 { + ti.LocalPort = o.zt.LocalPort + } + tunnel, err = c.ConnectTunnelInfo(ctx, ti) + } + + if err != nil { + return fmt.Errorf("unable to connect to the service: %w", err) + } + + defer tunnel.Close() + + if o.cliOnly { + spinner.Updatef(lang.CmdConnectEstablishedCLI, tunnel.FullURL()) + l.Info("Tunnel established, waiting for user to interrupt (ctrl-c to end)", "url", tunnel.FullURL()) + } else { + spinner.Updatef(lang.CmdConnectEstablishedWeb, tunnel.FullURL()) + l.Info("Tunnel established, opening your default web browser (ctrl-c to end)", "url", tunnel.FullURL()) + if err := exec.LaunchURL(tunnel.FullURL()); err != nil { return err } - message.PrintConnectStringTable(connections) + } + + // Wait for the interrupt signal or an error. + select { + case <-ctx.Done(): + spinner.Successf(lang.CmdConnectTunnelClosed, tunnel.FullURL()) return nil - }, + case err = <-tunnel.ErrChan(): + return fmt.Errorf("lost connection to the service: %w", err) + } } -func init() { - rootCmd.AddCommand(connectCmd) - connectCmd.AddCommand(connectListCmd) +// ConnectOptions holds the command-line options for 'connect list' sub-command. +type ConnectListOptions struct{} + +// NewConnectListCommand creates the `connect list` sub-command. +func NewConnectListCommand() *cobra.Command { + o := &ConnectListOptions{} + cmd := &cobra.Command{ + Use: "list", + Aliases: []string{"l"}, + Short: lang.CmdConnectListShort, + RunE: o.Run, + } + return cmd +} - connectCmd.Flags().StringVar(&zt.ResourceName, "name", "", lang.CmdConnectFlagName) - connectCmd.Flags().StringVar(&zt.Namespace, "namespace", cluster.ZarfNamespaceName, lang.CmdConnectFlagNamespace) - connectCmd.Flags().StringVar(&zt.ResourceType, "type", cluster.SvcResource, lang.CmdConnectFlagType) - connectCmd.Flags().IntVar(&zt.LocalPort, "local-port", 0, lang.CmdConnectFlagLocalPort) - connectCmd.Flags().IntVar(&zt.RemotePort, "remote-port", 0, lang.CmdConnectFlagRemotePort) - connectCmd.Flags().BoolVar(&cliOnly, "cli-only", false, lang.CmdConnectFlagCliOnly) +// Run performs the execution of 'connect list' sub-command. +func (o *ConnectListOptions) Run(cmd *cobra.Command, _ []string) error { + c, err := cluster.NewCluster() + if err != nil { + return err + } + connections, err := c.ListConnections(cmd.Context()) + if err != nil { + return err + } + message.PrintConnectStringTable(connections) + return nil } diff --git a/src/cmd/destroy.go b/src/cmd/destroy.go index eeca2ce3f3..5f3363c302 100644 --- a/src/cmd/destroy.go +++ b/src/cmd/destroy.go @@ -23,91 +23,98 @@ import ( "github.com/spf13/cobra" ) -var confirmDestroy bool -var removeComponents bool - -var destroyCmd = &cobra.Command{ - Use: "destroy --confirm", - Aliases: []string{"d"}, - Short: lang.CmdDestroyShort, - Long: lang.CmdDestroyLong, - // TODO(mkcp): refactor and de-nest this function. - RunE: func(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - l := logger.From(ctx) - - timeoutCtx, cancel := context.WithTimeout(ctx, cluster.DefaultTimeout) - defer cancel() - c, err := cluster.NewClusterWithWait(timeoutCtx) - if err != nil { - return err +// ConnectOptions holds the command-line options for 'destroy' sub-command. +type DestroyOptions struct { + confirmDestroy bool + removeComponents bool +} + +// NewDestroyCommand creates the `destroy` sub-command. +func NewDestroyCommand() *cobra.Command { + o := DestroyOptions{} + cmd := &cobra.Command{ + Use: "destroy --confirm", + Aliases: []string{"d"}, + Short: lang.CmdDestroyShort, + Long: lang.CmdDestroyLong, + RunE: o.Run, + } + + // Still going to require a flag for destroy confirm, no viper oopsies here + cmd.Flags().BoolVar(&o.confirmDestroy, "confirm", false, lang.CmdDestroyFlagConfirm) + cmd.Flags().BoolVar(&o.removeComponents, "remove-components", false, lang.CmdDestroyFlagRemoveComponents) + _ = cmd.MarkFlagRequired("confirm") + + return cmd +} + +// Run performs the execution of 'destroy' sub-command. +func (o *DestroyOptions) Run(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + l := logger.From(ctx) + + timeoutCtx, cancel := context.WithTimeout(ctx, cluster.DefaultTimeout) + defer cancel() + c, err := cluster.NewClusterWithWait(timeoutCtx) + if err != nil { + return err + } + + // NOTE: If 'zarf init' failed to deploy the k3s component (or if we're looking at the wrong kubeconfig) + // there will be no zarf-state to load and the struct will be empty. In these cases, if we can find + // the scripts to remove k3s, we will still try to remove a locally installed k3s cluster + state, err := c.LoadZarfState(ctx) + if err != nil { + message.WarnErr(err, err.Error()) + l.Warn(err.Error()) + } + + // If Zarf deployed the cluster, burn it all down + if state.ZarfAppliance || (state.Distro == "") { + // Check if we have the scripts to destroy everything + fileInfo, err := os.Stat(config.ZarfCleanupScriptsPath) + if errors.Is(err, os.ErrNotExist) || !fileInfo.IsDir() { + return fmt.Errorf("unable to find the folder %s which has the scripts to cleanup the cluster. Please double-check you have the right kube-context", config.ZarfCleanupScriptsPath) } - // NOTE: If 'zarf init' failed to deploy the k3s component (or if we're looking at the wrong kubeconfig) - // there will be no zarf-state to load and the struct will be empty. In these cases, if we can find - // the scripts to remove k3s, we will still try to remove a locally installed k3s cluster - state, err := c.LoadZarfState(ctx) + // Run all the scripts! + pattern := regexp.MustCompile(`(?mi)zarf-clean-.+\.sh$`) + scripts, err := helpers.RecursiveFileList(config.ZarfCleanupScriptsPath, pattern, true) if err != nil { - message.WarnErr(err, err.Error()) - l.Warn(err.Error()) + return err } - - // If Zarf deployed the cluster, burn it all down - if state.ZarfAppliance || (state.Distro == "") { - // Check if we have the scripts to destroy everything - fileInfo, err := os.Stat(config.ZarfCleanupScriptsPath) - if errors.Is(err, os.ErrNotExist) || !fileInfo.IsDir() { - return fmt.Errorf("unable to find the folder %s which has the scripts to cleanup the cluster. Please double-check you have the right kube-context", config.ZarfCleanupScriptsPath) + // Iterate over all matching zarf-clean scripts and exec them + for _, script := range scripts { + // Run the matched script + err := exec.CmdWithPrint(script) + if errors.Is(err, os.ErrPermission) { + message.Warnf(lang.CmdDestroyErrScriptPermissionDenied, script) + l.Warn("received 'permission denied' when trying to execute script. Please double-check you have the correct kube-context.", "script", script) + + // Don't remove scripts we can't execute so the user can try to manually run + continue + } else if err != nil { + return fmt.Errorf("received an error when executing the script %s: %w", script, err) } - // Run all the scripts! - pattern := regexp.MustCompile(`(?mi)zarf-clean-.+\.sh$`) - scripts, err := helpers.RecursiveFileList(config.ZarfCleanupScriptsPath, pattern, true) + // Try to remove the script, but ignore any errors and debug log them + err = os.Remove(script) if err != nil { - return err - } - // Iterate over all matching zarf-clean scripts and exec them - for _, script := range scripts { - // Run the matched script - err := exec.CmdWithPrint(script) - if errors.Is(err, os.ErrPermission) { - message.Warnf(lang.CmdDestroyErrScriptPermissionDenied, script) - l.Warn("received 'permission denied' when trying to execute script. Please double-check you have the correct kube-context.", "script", script) - - // Don't remove scripts we can't execute so the user can try to manually run - continue - } else if err != nil { - return fmt.Errorf("received an error when executing the script %s: %w", script, err) - } - - // Try to remove the script, but ignore any errors and debug log them - err = os.Remove(script) - if err != nil { - message.WarnErr(err, fmt.Sprintf("Unable to remove script. script=%s", script)) - l.Warn("unable to remove script", "script", script, "error", err.Error()) - } + message.WarnErr(err, fmt.Sprintf("Unable to remove script. script=%s", script)) + l.Warn("unable to remove script", "script", script, "error", err.Error()) } - } else { - // Perform chart uninstallation - helm.Destroy(ctx, removeComponents) - - // If Zarf didn't deploy the cluster, only delete the ZarfNamespace - if err := c.DeleteZarfNamespace(ctx); err != nil { - return err - } - - // Remove zarf agent labels and secrets from namespaces Zarf doesn't manage - c.StripZarfLabelsAndSecretsFromNamespaces(ctx) } - return nil - }, -} + } else { + // Perform chart uninstallation + helm.Destroy(ctx, o.removeComponents) -func init() { - rootCmd.AddCommand(destroyCmd) + // If Zarf didn't deploy the cluster, only delete the ZarfNamespace + if err := c.DeleteZarfNamespace(ctx); err != nil { + return err + } - // Still going to require a flag for destroy confirm, no viper oopsies here - destroyCmd.Flags().BoolVar(&confirmDestroy, "confirm", false, lang.CmdDestroyFlagConfirm) - destroyCmd.Flags().BoolVar(&removeComponents, "remove-components", false, lang.CmdDestroyFlagRemoveComponents) - _ = destroyCmd.MarkFlagRequired("confirm") + // Remove zarf agent labels and secrets from namespaces Zarf doesn't manage + c.StripZarfLabelsAndSecretsFromNamespaces(ctx) + } + return nil } diff --git a/src/cmd/dev.go b/src/cmd/dev.go index 3dcfe51fb9..42c7409766 100644 --- a/src/cmd/dev.go +++ b/src/cmd/dev.go @@ -31,378 +31,465 @@ import ( "github.com/zarf-dev/zarf/src/types" ) -var extractPath string - var defaultRegistry = fmt.Sprintf("%s:%d", helpers.IPV4Localhost, types.ZarfInClusterContainerRegistryNodePort) -var devCmd = &cobra.Command{ - Use: "dev", - Aliases: []string{"prepare", "prep"}, - Short: lang.CmdDevShort, +// NewDevCommand creates the `dev` sub-command and its nested children. +func NewDevCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "dev", + Aliases: []string{"prepare", "prep"}, + Short: lang.CmdDevShort, + } + + v := common.GetViper() + + cmd.AddCommand(NewDevDeployCommand(v)) + cmd.AddCommand(NewDevGenerateCommand()) + cmd.AddCommand(NewDevPatchGitCommand()) + cmd.AddCommand(NewDevSha256SumCommand()) + cmd.AddCommand(NewDevFindImagesCommand(v)) + cmd.AddCommand(NewDevGenerateConfigCommand()) + cmd.AddCommand(NewDevLintCommand(v)) + + return cmd } -var devDeployCmd = &cobra.Command{ - Use: "deploy", - Args: cobra.MaximumNArgs(1), - Short: lang.CmdDevDeployShort, - Long: lang.CmdDevDeployLong, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - pkgConfig.CreateOpts.BaseDir = setBaseDirectory(args) +// DevDeployOptions holds the command-line options for 'dev deploy' sub-command. +type DevDeployOptions struct{} - v := common.GetViper() - pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap( - v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) +// NewDevDeployCommand creates the `dev deploy` sub-command. +func NewDevDeployCommand(v *viper.Viper) *cobra.Command { + o := &DevDeployOptions{} - pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( - v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) + cmd := &cobra.Command{ + Use: "deploy", + Args: cobra.MaximumNArgs(1), + Short: lang.CmdDevDeployShort, + Long: lang.CmdDevDeployLong, + RunE: o.Run, + } - pkgClient, err := packager.New(&pkgConfig, packager.WithContext(ctx)) - if err != nil { - return err - } - defer pkgClient.ClearTempPaths() + // TODO(soltysh): get rid of pkgConfig global + cmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "create-set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdPackageCreateFlagSet) + cmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.RegistryOverrides, "registry-override", v.GetStringMapString(common.VPkgCreateRegistryOverride), lang.CmdPackageCreateFlagRegistryOverride) + cmd.Flags().StringVarP(&pkgConfig.CreateOpts.Flavor, "flavor", "f", v.GetString(common.VPkgCreateFlavor), lang.CmdPackageCreateFlagFlavor) - err = pkgClient.DevDeploy(ctx) - var lintErr *lint.LintError - if errors.As(err, &lintErr) { - common.PrintFindings(ctx, lintErr) - } - if err != nil { - return fmt.Errorf("failed to dev deploy: %w", err) - } - return nil - }, + cmd.Flags().StringVar(&pkgConfig.DeployOpts.RegistryURL, "registry-url", defaultRegistry, lang.CmdDevFlagRegistry) + err := cmd.Flags().MarkHidden("registry-url") + if err != nil { + logger.Default().Debug("unable to mark dev-deploy flag as hidden", "error", err) + } + + cmd.Flags().StringToStringVar(&pkgConfig.PkgOpts.SetVariables, "deploy-set", v.GetStringMapString(common.VPkgDeploySet), lang.CmdPackageDeployFlagSet) + + // Always require adopt-existing-resources flag (no viper) + cmd.Flags().BoolVar(&pkgConfig.DeployOpts.AdoptExistingResources, "adopt-existing-resources", false, lang.CmdPackageDeployFlagAdoptExistingResources) + cmd.Flags().DurationVar(&pkgConfig.DeployOpts.Timeout, "timeout", v.GetDuration(common.VPkgDeployTimeout), lang.CmdPackageDeployFlagTimeout) + + cmd.Flags().IntVar(&pkgConfig.PkgOpts.Retries, "retries", v.GetInt(common.VPkgRetries), lang.CmdPackageFlagRetries) + cmd.Flags().StringVar(&pkgConfig.PkgOpts.OptionalComponents, "components", v.GetString(common.VPkgDeployComponents), lang.CmdPackageDeployFlagComponents) + + cmd.Flags().BoolVar(&pkgConfig.CreateOpts.NoYOLO, "no-yolo", v.GetBool(common.VDevDeployNoYolo), lang.CmdDevDeployFlagNoYolo) + + return cmd } -var devGenerateCmd = &cobra.Command{ - Use: "generate NAME", - Aliases: []string{"g"}, - Args: cobra.ExactArgs(1), - Short: lang.CmdDevGenerateShort, - Example: lang.CmdDevGenerateExample, - RunE: func(cmd *cobra.Command, args []string) error { - pkgConfig.GenerateOpts.Name = args[0] +// Run performs the execution of 'dev deploy' sub-command. +func (o *DevDeployOptions) Run(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + pkgConfig.CreateOpts.BaseDir = setBaseDirectory(args) - pkgConfig.CreateOpts.BaseDir = "." - pkgConfig.FindImagesOpts.RepoHelmChartPath = pkgConfig.GenerateOpts.GitPath + v := common.GetViper() + pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap( + v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) - pkgClient, err := packager.New(&pkgConfig, packager.WithContext(cmd.Context())) - if err != nil { - return err - } - defer pkgClient.ClearTempPaths() + pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( + v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) + + pkgClient, err := packager.New(&pkgConfig, packager.WithContext(ctx)) + if err != nil { + return err + } + defer pkgClient.ClearTempPaths() + + err = pkgClient.DevDeploy(ctx) + var lintErr *lint.LintError + if errors.As(err, &lintErr) { + common.PrintFindings(ctx, lintErr) + } + if err != nil { + return fmt.Errorf("failed to dev deploy: %w", err) + } + return nil - err = pkgClient.Generate(cmd.Context()) - if err != nil { - return err - } - return nil - }, } -var devTransformGitLinksCmd = &cobra.Command{ - Use: "patch-git HOST FILE", - Aliases: []string{"p"}, - Short: lang.CmdDevPatchGitShort, - Args: cobra.ExactArgs(2), - RunE: func(_ *cobra.Command, args []string) error { - host, fileName := args[0], args[1] +// DevGenerateOptions holds the command-line options for 'dev generate' sub-command. +type DevGenerateOptions struct{} - // Read the contents of the given file - content, err := os.ReadFile(fileName) - if err != nil { - return fmt.Errorf("unable to read the file %s: %w", fileName, err) - } +// NewDevGenerateCommand creates the `dev generate` sub-command. +func NewDevGenerateCommand() *cobra.Command { + o := &DevGenerateOptions{} + + cmd := &cobra.Command{ + Use: "generate NAME", + Aliases: []string{"g"}, + Args: cobra.ExactArgs(1), + Short: lang.CmdDevGenerateShort, + Example: lang.CmdDevGenerateExample, + RunE: o.Run, + } - gitServer := pkgConfig.InitOpts.GitServer - gitServer.Address = host + cmd.Flags().StringVar(&pkgConfig.GenerateOpts.URL, "url", "", "URL to the source git repository") + cmd.MarkFlagRequired("url") + cmd.Flags().StringVar(&pkgConfig.GenerateOpts.Version, "version", "", "The Version of the chart to use") + cmd.MarkFlagRequired("version") + cmd.Flags().StringVar(&pkgConfig.GenerateOpts.GitPath, "gitPath", "", "Relative path to the chart in the git repository") + cmd.Flags().StringVar(&pkgConfig.GenerateOpts.Output, "output-directory", "", "Output directory for the generated zarf.yaml") + cmd.MarkFlagRequired("output-directory") + cmd.Flags().StringVar(&pkgConfig.FindImagesOpts.KubeVersionOverride, "kube-version", "", lang.CmdDevFlagKubeVersion) - // Perform git url transformation via regex - text := string(content) + return cmd +} - // TODO(mkcp): Currently uses message for its log fn. Migrate to ctx and slog - processedText := transform.MutateGitURLsInText(message.Warnf, gitServer.Address, text, gitServer.PushUsername) +// Run performs the execution of 'dev generate' sub-command. +func (o *DevGenerateOptions) Run(cmd *cobra.Command, args []string) error { + pkgConfig.GenerateOpts.Name = args[0] - // Print the differences - // TODO(mkcp): Uses pterm to print text diffs. Decouple from pterm after we release logger. - dmp := diffmatchpatch.New() - diffs := dmp.DiffMain(text, processedText, true) - diffs = dmp.DiffCleanupSemantic(diffs) - pterm.Println(dmp.DiffPrettyText(diffs)) + pkgConfig.CreateOpts.BaseDir = "." + pkgConfig.FindImagesOpts.RepoHelmChartPath = pkgConfig.GenerateOpts.GitPath - // Ask the user before this destructive action - confirm := false - prompt := &survey.Confirm{ - Message: fmt.Sprintf(lang.CmdDevPatchGitOverwritePrompt, fileName), - } - if err := survey.AskOne(prompt, &confirm); err != nil { - return fmt.Errorf("confirm overwrite canceled: %w", err) - } + pkgClient, err := packager.New(&pkgConfig, packager.WithContext(cmd.Context())) + if err != nil { + return err + } + defer pkgClient.ClearTempPaths() - if confirm { - // Overwrite the file - err = os.WriteFile(fileName, []byte(processedText), helpers.ReadAllWriteUser) - if err != nil { - return fmt.Errorf("unable to write the changes back to the file: %w", err) - } - } - return nil - }, + err = pkgClient.Generate(cmd.Context()) + if err != nil { + return err + } + return nil } -var devSha256SumCmd = &cobra.Command{ - Use: "sha256sum { FILE | URL }", - Aliases: []string{"s"}, - Short: lang.CmdDevSha256sumShort, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) (err error) { - hashErr := errors.New("unable to compute the SHA256SUM hash") +// DevPatchGitOptions holds the command-line options for 'dev patch-git' sub-command. +type DevPatchGitOptions struct{} - fileName := args[0] +// NewDevPatchGitCommand creates the `dev patch-git` sub-command. +func NewDevPatchGitCommand() *cobra.Command { + o := &DevDeployOptions{} - var tmp string - var data io.ReadCloser + cmd := &cobra.Command{ + Use: "patch-git HOST FILE", + Aliases: []string{"p"}, + Short: lang.CmdDevPatchGitShort, + Args: cobra.ExactArgs(2), + RunE: o.Run, + } - if helpers.IsURL(fileName) { - message.Warn(lang.CmdDevSha256sumRemoteWarning) - logger.From(cmd.Context()).Warn("this is a remote source. If a published checksum is available you should use that rather than calculating it directly from the remote link") + // TODO(soltysh): get rid of pkgConfig global + cmd.Flags().StringVar(&pkgConfig.InitOpts.GitServer.PushUsername, "git-account", types.ZarfGitPushUser, lang.CmdDevFlagGitAccount) - fileBase, err := helpers.ExtractBasePathFromURL(fileName) - if err != nil { - return errors.Join(hashErr, err) - } + return cmd +} - if fileBase == "" { - fileBase = "sha-file" - } +// Run performs the execution of 'dev patch-git' sub-command. +func (o *DevPatchGitOptions) Run(_ *cobra.Command, args []string) error { + host, fileName := args[0], args[1] - tmp, err = utils.MakeTempDir(config.CommonOptions.TempDirectory) - if err != nil { - return errors.Join(hashErr, err) - } + // Read the contents of the given file + content, err := os.ReadFile(fileName) + if err != nil { + return fmt.Errorf("unable to read the file %s: %w", fileName, err) + } - downloadPath := filepath.Join(tmp, fileBase) - err = utils.DownloadToFile(cmd.Context(), fileName, downloadPath, "") - if err != nil { - return errors.Join(hashErr, err) - } + gitServer := pkgConfig.InitOpts.GitServer + gitServer.Address = host - fileName = downloadPath + // Perform git url transformation via regex + text := string(content) - defer func(path string) { - errRemove := os.RemoveAll(path) - err = errors.Join(err, errRemove) - }(tmp) + // TODO(mkcp): Currently uses message for its log fn. Migrate to ctx and slog + processedText := transform.MutateGitURLsInText(message.Warnf, gitServer.Address, text, gitServer.PushUsername) + + // Print the differences + // TODO(mkcp): Uses pterm to print text diffs. Decouple from pterm after we release logger. + dmp := diffmatchpatch.New() + diffs := dmp.DiffMain(text, processedText, true) + diffs = dmp.DiffCleanupSemantic(diffs) + pterm.Println(dmp.DiffPrettyText(diffs)) + + // Ask the user before this destructive action + confirm := false + prompt := &survey.Confirm{ + Message: fmt.Sprintf(lang.CmdDevPatchGitOverwritePrompt, fileName), + } + if err := survey.AskOne(prompt, &confirm); err != nil { + return fmt.Errorf("confirm overwrite canceled: %w", err) + } + + if confirm { + // Overwrite the file + err = os.WriteFile(fileName, []byte(processedText), helpers.ReadAllWriteUser) + if err != nil { + return fmt.Errorf("unable to write the changes back to the file: %w", err) } + } + return nil - if extractPath != "" { - if tmp == "" { - tmp, err = utils.MakeTempDir(config.CommonOptions.TempDirectory) - if err != nil { - return errors.Join(hashErr, err) - } - defer func(path string) { - errRemove := os.RemoveAll(path) - err = errors.Join(err, errRemove) - }(tmp) - } +} - extractedFile := filepath.Join(tmp, extractPath) +// DevSha256SumOptions holds the command-line options for 'dev sha256sum' sub-command. +type DevSha256SumOptions struct { + extractPath string +} - err = archiver.Extract(fileName, extractPath, tmp) - if err != nil { - return errors.Join(hashErr, err) - } +// NewDevSha256SumCommand creates the `dev sha256sum` sub-command. +func NewDevSha256SumCommand() *cobra.Command { + o := &DevSha256SumOptions{} - fileName = extractedFile - } + cmd := &cobra.Command{ + Use: "sha256sum { FILE | URL }", + Aliases: []string{"s"}, + Short: lang.CmdDevSha256sumShort, + Args: cobra.ExactArgs(1), + RunE: o.Run, + } + + cmd.Flags().StringVarP(&o.extractPath, "extract-path", "e", "", lang.CmdDevFlagExtractPath) + + return cmd +} - data, err = os.Open(fileName) +// Run performs the execution of 'dev sha256sum' sub-command. +func (o *DevSha256SumOptions) Run(cmd *cobra.Command, args []string) (err error) { + hashErr := errors.New("unable to compute the SHA256SUM hash") + + fileName := args[0] + + var tmp string + var data io.ReadCloser + + if helpers.IsURL(fileName) { + message.Warn(lang.CmdDevSha256sumRemoteWarning) + logger.From(cmd.Context()).Warn("this is a remote source. If a published checksum is available you should use that rather than calculating it directly from the remote link") + + fileBase, err := helpers.ExtractBasePathFromURL(fileName) if err != nil { return errors.Join(hashErr, err) } - defer func(data io.ReadCloser) { - errClose := data.Close() - err = errors.Join(err, errClose) - }(data) - hash, err := helpers.GetSHA256Hash(data) - if err != nil { - return errors.Join(hashErr, err) + if fileBase == "" { + fileBase = "sha-file" } - fmt.Println(hash) - return nil - }, -} -var devFindImagesCmd = &cobra.Command{ - Use: "find-images [ DIRECTORY ]", - Aliases: []string{"f"}, - Args: cobra.MaximumNArgs(1), - Short: lang.CmdDevFindImagesShort, - Long: lang.CmdDevFindImagesLong, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - pkgConfig.CreateOpts.BaseDir = setBaseDirectory(args) - - v := common.GetViper() - - pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap( - v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) - pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( - v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) - pkgClient, err := packager.New(&pkgConfig, packager.WithContext(cmd.Context())) + tmp, err = utils.MakeTempDir(config.CommonOptions.TempDirectory) if err != nil { - return err + return errors.Join(hashErr, err) } - defer pkgClient.ClearTempPaths() - - _, err = pkgClient.FindImages(ctx) - var lintErr *lint.LintError - if errors.As(err, &lintErr) { - common.PrintFindings(ctx, lintErr) - } + downloadPath := filepath.Join(tmp, fileBase) + err = utils.DownloadToFile(cmd.Context(), fileName, downloadPath, "") if err != nil { - return fmt.Errorf("unable to find images: %w", err) + return errors.Join(hashErr, err) } - return nil - }, -} -var devGenConfigFileCmd = &cobra.Command{ - Use: "generate-config [ FILENAME ]", - Aliases: []string{"gc"}, - Args: cobra.MaximumNArgs(1), - Short: lang.CmdDevGenerateConfigShort, - Long: lang.CmdDevGenerateConfigLong, - RunE: func(_ *cobra.Command, args []string) error { - // If a filename was provided, use that - fileName := "zarf-config.toml" - if len(args) > 0 { - fileName = args[0] - } + fileName = downloadPath - v := common.GetViper() - if err := v.SafeWriteConfigAs(fileName); err != nil { - return fmt.Errorf("unable to write the config file %s, make sure the file doesn't already exist: %w", fileName, err) - } - return nil - }, -} + defer func(path string) { + errRemove := os.RemoveAll(path) + err = errors.Join(err, errRemove) + }(tmp) + } -var devLintCmd = &cobra.Command{ - Use: "lint [ DIRECTORY ]", - Args: cobra.MaximumNArgs(1), - Aliases: []string{"l"}, - Short: lang.CmdDevLintShort, - Long: lang.CmdDevLintLong, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - config.CommonOptions.Confirm = true - pkgConfig.CreateOpts.BaseDir = setBaseDirectory(args) - v := common.GetViper() - pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap( - v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) - - err := lint.Validate(ctx, pkgConfig.CreateOpts.BaseDir, pkgConfig.CreateOpts.Flavor, pkgConfig.CreateOpts.SetVariables) - var lintErr *lint.LintError - if errors.As(err, &lintErr) { - common.PrintFindings(ctx, lintErr) - // Do not return an error if the findings are all warnings. - if lintErr.OnlyWarnings() { - return nil + if o.extractPath != "" { + if tmp == "" { + tmp, err = utils.MakeTempDir(config.CommonOptions.TempDirectory) + if err != nil { + return errors.Join(hashErr, err) } + defer func(path string) { + errRemove := os.RemoveAll(path) + err = errors.Join(err, errRemove) + }(tmp) } + + extractedFile := filepath.Join(tmp, o.extractPath) + + err = archiver.Extract(fileName, o.extractPath, tmp) if err != nil { - return err + return errors.Join(hashErr, err) } - return nil - }, -} -func init() { - v := common.GetViper() - rootCmd.AddCommand(devCmd) + fileName = extractedFile + } + + data, err = os.Open(fileName) + if err != nil { + return errors.Join(hashErr, err) + } + defer func(data io.ReadCloser) { + errClose := data.Close() + err = errors.Join(err, errClose) + }(data) + + hash, err := helpers.GetSHA256Hash(data) + if err != nil { + return errors.Join(hashErr, err) + } + fmt.Println(hash) + return nil +} - devCmd.AddCommand(devDeployCmd) - devCmd.AddCommand(devGenerateCmd) - devCmd.AddCommand(devTransformGitLinksCmd) - devCmd.AddCommand(devSha256SumCmd) - devCmd.AddCommand(devFindImagesCmd) - devCmd.AddCommand(devGenConfigFileCmd) - devCmd.AddCommand(devLintCmd) +// DevFindImagesOptions holds the command-line options for 'dev find-images' sub-command. +type DevFindImagesOptions struct{} - bindDevDeployFlags(v) - bindDevGenerateFlags(v) +// NewDevFindImagesCommand creates the `dev find-images` sub-command. +func NewDevFindImagesCommand(v *viper.Viper) *cobra.Command { + o := &DevFindImagesOptions{} - devSha256SumCmd.Flags().StringVarP(&extractPath, "extract-path", "e", "", lang.CmdDevFlagExtractPath) + cmd := &cobra.Command{ + Use: "find-images [ DIRECTORY ]", + Aliases: []string{"f"}, + Args: cobra.MaximumNArgs(1), + Short: lang.CmdDevFindImagesShort, + Long: lang.CmdDevFindImagesLong, + RunE: o.Run, + } - devFindImagesCmd.Flags().StringVarP(&pkgConfig.FindImagesOpts.RepoHelmChartPath, "repo-chart-path", "p", "", lang.CmdDevFlagRepoChartPath) + // TODO(soltysh): get rid of pkgConfig global + cmd.Flags().StringVarP(&pkgConfig.FindImagesOpts.RepoHelmChartPath, "repo-chart-path", "p", "", lang.CmdDevFlagRepoChartPath) // use the package create config for this and reset it here to avoid overwriting the config.CreateOptions.SetVariables - devFindImagesCmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdDevFlagSet) + cmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdDevFlagSet) - err := devFindImagesCmd.Flags().MarkDeprecated("set", "this field is replaced by create-set") + err := cmd.Flags().MarkDeprecated("set", "this field is replaced by create-set") if err != nil { logger.Default().Debug("unable to mark dev-find-images flag as set", "error", err) } - err = devFindImagesCmd.Flags().MarkHidden("set") + err = cmd.Flags().MarkHidden("set") if err != nil { logger.Default().Debug("unable to mark dev-find-images flag as hidden", "error", err) } - devFindImagesCmd.Flags().StringVarP(&pkgConfig.CreateOpts.Flavor, "flavor", "f", v.GetString(common.VPkgCreateFlavor), lang.CmdPackageCreateFlagFlavor) - devFindImagesCmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "create-set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdDevFlagSet) - devFindImagesCmd.Flags().StringToStringVar(&pkgConfig.PkgOpts.SetVariables, "deploy-set", v.GetStringMapString(common.VPkgDeploySet), lang.CmdPackageDeployFlagSet) + cmd.Flags().StringVarP(&pkgConfig.CreateOpts.Flavor, "flavor", "f", v.GetString(common.VPkgCreateFlavor), lang.CmdPackageCreateFlagFlavor) + cmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "create-set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdDevFlagSet) + cmd.Flags().StringToStringVar(&pkgConfig.PkgOpts.SetVariables, "deploy-set", v.GetStringMapString(common.VPkgDeploySet), lang.CmdPackageDeployFlagSet) // allow for the override of the default helm KubeVersion - devFindImagesCmd.Flags().StringVar(&pkgConfig.FindImagesOpts.KubeVersionOverride, "kube-version", "", lang.CmdDevFlagKubeVersion) + cmd.Flags().StringVar(&pkgConfig.FindImagesOpts.KubeVersionOverride, "kube-version", "", lang.CmdDevFlagKubeVersion) // check which manifests are using this particular image - devFindImagesCmd.Flags().StringVar(&pkgConfig.FindImagesOpts.Why, "why", "", lang.CmdDevFlagFindImagesWhy) + cmd.Flags().StringVar(&pkgConfig.FindImagesOpts.Why, "why", "", lang.CmdDevFlagFindImagesWhy) // skip searching cosign artifacts in find images - devFindImagesCmd.Flags().BoolVar(&pkgConfig.FindImagesOpts.SkipCosign, "skip-cosign", false, lang.CmdDevFlagFindImagesSkipCosign) + cmd.Flags().BoolVar(&pkgConfig.FindImagesOpts.SkipCosign, "skip-cosign", false, lang.CmdDevFlagFindImagesSkipCosign) - devFindImagesCmd.Flags().StringVar(&pkgConfig.FindImagesOpts.RegistryURL, "registry-url", defaultRegistry, lang.CmdDevFlagRegistry) + cmd.Flags().StringVar(&pkgConfig.FindImagesOpts.RegistryURL, "registry-url", defaultRegistry, lang.CmdDevFlagRegistry) - devLintCmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdPackageCreateFlagSet) - devLintCmd.Flags().StringVarP(&pkgConfig.CreateOpts.Flavor, "flavor", "f", v.GetString(common.VPkgCreateFlavor), lang.CmdPackageCreateFlagFlavor) - devTransformGitLinksCmd.Flags().StringVar(&pkgConfig.InitOpts.GitServer.PushUsername, "git-account", types.ZarfGitPushUser, lang.CmdDevFlagGitAccount) + return cmd } -func bindDevDeployFlags(v *viper.Viper) { - devDeployFlags := devDeployCmd.Flags() +// Run performs the execution of 'dev find-images' sub-command. +func (o *DevFindImagesOptions) Run(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + pkgConfig.CreateOpts.BaseDir = setBaseDirectory(args) - devDeployFlags.StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "create-set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdPackageCreateFlagSet) - devDeployFlags.StringToStringVar(&pkgConfig.CreateOpts.RegistryOverrides, "registry-override", v.GetStringMapString(common.VPkgCreateRegistryOverride), lang.CmdPackageCreateFlagRegistryOverride) - devDeployFlags.StringVarP(&pkgConfig.CreateOpts.Flavor, "flavor", "f", v.GetString(common.VPkgCreateFlavor), lang.CmdPackageCreateFlagFlavor) + v := common.GetViper() - devDeployFlags.StringVar(&pkgConfig.DeployOpts.RegistryURL, "registry-url", defaultRegistry, lang.CmdDevFlagRegistry) - err := devDeployFlags.MarkHidden("registry-url") + pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap( + v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) + pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( + v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) + pkgClient, err := packager.New(&pkgConfig, packager.WithContext(cmd.Context())) if err != nil { - logger.Default().Debug("unable to mark dev-deploy flag as hidden", "error", err) + return err } + defer pkgClient.ClearTempPaths() - devDeployFlags.StringToStringVar(&pkgConfig.PkgOpts.SetVariables, "deploy-set", v.GetStringMapString(common.VPkgDeploySet), lang.CmdPackageDeployFlagSet) + _, err = pkgClient.FindImages(ctx) - // Always require adopt-existing-resources flag (no viper) - devDeployFlags.BoolVar(&pkgConfig.DeployOpts.AdoptExistingResources, "adopt-existing-resources", false, lang.CmdPackageDeployFlagAdoptExistingResources) - devDeployFlags.DurationVar(&pkgConfig.DeployOpts.Timeout, "timeout", v.GetDuration(common.VPkgDeployTimeout), lang.CmdPackageDeployFlagTimeout) + var lintErr *lint.LintError + if errors.As(err, &lintErr) { + common.PrintFindings(ctx, lintErr) + } + if err != nil { + return fmt.Errorf("unable to find images: %w", err) + } + return nil +} + +// DevGenerateConfigOptions holds the command-line options for 'dev generate-config' sub-command. +type DevGenerateConfigOptions struct{} - devDeployFlags.IntVar(&pkgConfig.PkgOpts.Retries, "retries", v.GetInt(common.VPkgRetries), lang.CmdPackageFlagRetries) - devDeployFlags.StringVar(&pkgConfig.PkgOpts.OptionalComponents, "components", v.GetString(common.VPkgDeployComponents), lang.CmdPackageDeployFlagComponents) +// NewDevGenerateConfigCommand creates the `dev generate-config` sub-command. +func NewDevGenerateConfigCommand() *cobra.Command { + o := &DevGenerateConfigOptions{} - devDeployFlags.BoolVar(&pkgConfig.CreateOpts.NoYOLO, "no-yolo", v.GetBool(common.VDevDeployNoYolo), lang.CmdDevDeployFlagNoYolo) + cmd := &cobra.Command{ + Use: "generate-config [ FILENAME ]", + Aliases: []string{"gc"}, + Args: cobra.MaximumNArgs(1), + Short: lang.CmdDevGenerateConfigShort, + Long: lang.CmdDevGenerateConfigLong, + RunE: o.Run, + } + + return cmd } -func bindDevGenerateFlags(_ *viper.Viper) { - generateFlags := devGenerateCmd.Flags() +// Run performs the execution of 'dev generate-config' sub-command. +func (o *DevGenerateConfigOptions) Run(_ *cobra.Command, args []string) error { + // If a filename was provided, use that + fileName := "zarf-config.toml" + if len(args) > 0 { + fileName = args[0] + } + + v := common.GetViper() + if err := v.SafeWriteConfigAs(fileName); err != nil { + return fmt.Errorf("unable to write the config file %s, make sure the file doesn't already exist: %w", fileName, err) + } + return nil +} + +// DevLintOptions holds the command-line options for 'dev lint' sub-command. +type DevLintOptions struct{} + +// NewDevLintCommand creates the `dev lint` sub-command. +func NewDevLintCommand(v *viper.Viper) *cobra.Command { + o := &DevLintOptions{} - generateFlags.StringVar(&pkgConfig.GenerateOpts.URL, "url", "", "URL to the source git repository") - generateFlags.StringVar(&pkgConfig.GenerateOpts.Version, "version", "", "The Version of the chart to use") - generateFlags.StringVar(&pkgConfig.GenerateOpts.GitPath, "gitPath", "", "Relative path to the chart in the git repository") - generateFlags.StringVar(&pkgConfig.GenerateOpts.Output, "output-directory", "", "Output directory for the generated zarf.yaml") - generateFlags.StringVar(&pkgConfig.FindImagesOpts.KubeVersionOverride, "kube-version", "", lang.CmdDevFlagKubeVersion) + cmd := &cobra.Command{ + Use: "lint [ DIRECTORY ]", + Args: cobra.MaximumNArgs(1), + Aliases: []string{"l"}, + Short: lang.CmdDevLintShort, + Long: lang.CmdDevLintLong, + RunE: o.Run, + } + + cmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdPackageCreateFlagSet) + cmd.Flags().StringVarP(&pkgConfig.CreateOpts.Flavor, "flavor", "f", v.GetString(common.VPkgCreateFlavor), lang.CmdPackageCreateFlagFlavor) - devGenerateCmd.MarkFlagRequired("url") - devGenerateCmd.MarkFlagRequired("version") - devGenerateCmd.MarkFlagRequired("output-directory") + return cmd +} + +// Run performs the execution of 'dev lint' sub-command. +func (o *DevLintOptions) Run(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + config.CommonOptions.Confirm = true + pkgConfig.CreateOpts.BaseDir = setBaseDirectory(args) + v := common.GetViper() + pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap( + v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) + + err := lint.Validate(ctx, pkgConfig.CreateOpts.BaseDir, pkgConfig.CreateOpts.Flavor, pkgConfig.CreateOpts.SetVariables) + var lintErr *lint.LintError + if errors.As(err, &lintErr) { + common.PrintFindings(ctx, lintErr) + // Do not return an error if the findings are all warnings. + if lintErr.OnlyWarnings() { + return nil + } + } + if err != nil { + return err + } + return nil } diff --git a/src/cmd/initialize.go b/src/cmd/initialize.go index c946752d23..34e30d5e32 100644 --- a/src/cmd/initialize.go +++ b/src/cmd/initialize.go @@ -29,53 +29,112 @@ import ( "github.com/spf13/cobra" ) -// initCmd represents the init command. -var initCmd = &cobra.Command{ - Use: "init", - Aliases: []string{"i"}, - Short: lang.CmdInitShort, - Long: lang.CmdInitLong, - Example: lang.CmdInitExample, - RunE: func(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - if err := validateInitFlags(); err != nil { - return fmt.Errorf("invalid command flags were provided: %w", err) - } +// InitOptions holds the command-line options for 'init' sub-command. +type InitOptions struct{} + +// NewInitCommand creates the `init` sub-command. +func NewInitCommand() *cobra.Command { + o := InitOptions{} + + cmd := &cobra.Command{ + Use: "init", + Aliases: []string{"i"}, + Short: lang.CmdInitShort, + Long: lang.CmdInitLong, + Example: lang.CmdInitExample, + RunE: o.Run, + } - // Continue running package deploy for all components like any other package - initPackageName := sources.GetInitPackageName() - pkgConfig.PkgOpts.PackageSource = initPackageName + v := common.InitViper() - // Try to use an init-package in the executable directory if none exist in current working directory - var err error - if pkgConfig.PkgOpts.PackageSource, err = findInitPackage(cmd.Context(), initPackageName); err != nil { - return err - } + // Init package variable defaults that are non-zero values + // NOTE: these are not in common.setDefaults so that zarf tools update-creds does not erroneously update values back to the default + v.SetDefault(common.VInitGitPushUser, types.ZarfGitPushUser) + v.SetDefault(common.VInitRegistryPushUser, types.ZarfRegistryPushUser) - src, err := sources.New(ctx, &pkgConfig.PkgOpts) - if err != nil { - return err - } + // Init package set variable flags + cmd.Flags().StringToStringVar(&pkgConfig.PkgOpts.SetVariables, "set", v.GetStringMapString(common.VPkgDeploySet), lang.CmdInitFlagSet) - v := common.GetViper() - pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( - v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) + // Continue to require --confirm flag for init command to avoid accidental deployments + cmd.Flags().BoolVar(&config.CommonOptions.Confirm, "confirm", false, lang.CmdInitFlagConfirm) + cmd.Flags().StringVar(&pkgConfig.PkgOpts.OptionalComponents, "components", v.GetString(common.VInitComponents), lang.CmdInitFlagComponents) + cmd.Flags().StringVar(&pkgConfig.InitOpts.StorageClass, "storage-class", v.GetString(common.VInitStorageClass), lang.CmdInitFlagStorageClass) - pkgClient, err := packager.New(&pkgConfig, packager.WithSource(src), packager.WithContext(ctx)) - if err != nil { - return err - } - defer pkgClient.ClearTempPaths() + // Flags for using an external Git server + cmd.Flags().StringVar(&pkgConfig.InitOpts.GitServer.Address, "git-url", v.GetString(common.VInitGitURL), lang.CmdInitFlagGitURL) + cmd.Flags().StringVar(&pkgConfig.InitOpts.GitServer.PushUsername, "git-push-username", v.GetString(common.VInitGitPushUser), lang.CmdInitFlagGitPushUser) + cmd.Flags().StringVar(&pkgConfig.InitOpts.GitServer.PushPassword, "git-push-password", v.GetString(common.VInitGitPushPass), lang.CmdInitFlagGitPushPass) + cmd.Flags().StringVar(&pkgConfig.InitOpts.GitServer.PullUsername, "git-pull-username", v.GetString(common.VInitGitPullUser), lang.CmdInitFlagGitPullUser) + cmd.Flags().StringVar(&pkgConfig.InitOpts.GitServer.PullPassword, "git-pull-password", v.GetString(common.VInitGitPullPass), lang.CmdInitFlagGitPullPass) - err = pkgClient.Deploy(ctx) - if err != nil { - return err - } - // Since the new logger ignores pterm output the credential table is no longer printed on init. - // This note is the intended replacement, rather than printing creds by default. - logger.From(ctx).Info("init complete. To get credentials for Zarf deployed services run `zarf tools get-creds`") - return nil - }, + // Flags for using an external registry + cmd.Flags().StringVar(&pkgConfig.InitOpts.RegistryInfo.Address, "registry-url", v.GetString(common.VInitRegistryURL), lang.CmdInitFlagRegURL) + cmd.Flags().IntVar(&pkgConfig.InitOpts.RegistryInfo.NodePort, "nodeport", v.GetInt(common.VInitRegistryNodeport), lang.CmdInitFlagRegNodePort) + cmd.Flags().StringVar(&pkgConfig.InitOpts.RegistryInfo.PushUsername, "registry-push-username", v.GetString(common.VInitRegistryPushUser), lang.CmdInitFlagRegPushUser) + cmd.Flags().StringVar(&pkgConfig.InitOpts.RegistryInfo.PushPassword, "registry-push-password", v.GetString(common.VInitRegistryPushPass), lang.CmdInitFlagRegPushPass) + cmd.Flags().StringVar(&pkgConfig.InitOpts.RegistryInfo.PullUsername, "registry-pull-username", v.GetString(common.VInitRegistryPullUser), lang.CmdInitFlagRegPullUser) + cmd.Flags().StringVar(&pkgConfig.InitOpts.RegistryInfo.PullPassword, "registry-pull-password", v.GetString(common.VInitRegistryPullPass), lang.CmdInitFlagRegPullPass) + cmd.Flags().StringVar(&pkgConfig.InitOpts.RegistryInfo.Secret, "registry-secret", v.GetString(common.VInitRegistrySecret), lang.CmdInitFlagRegSecret) + + // Flags for using an external artifact server + cmd.Flags().StringVar(&pkgConfig.InitOpts.ArtifactServer.Address, "artifact-url", v.GetString(common.VInitArtifactURL), lang.CmdInitFlagArtifactURL) + cmd.Flags().StringVar(&pkgConfig.InitOpts.ArtifactServer.PushUsername, "artifact-push-username", v.GetString(common.VInitArtifactPushUser), lang.CmdInitFlagArtifactPushUser) + cmd.Flags().StringVar(&pkgConfig.InitOpts.ArtifactServer.PushToken, "artifact-push-token", v.GetString(common.VInitArtifactPushToken), lang.CmdInitFlagArtifactPushToken) + + // Flags that control how a deployment proceeds + // Always require adopt-existing-resources flag (no viper) + cmd.Flags().BoolVar(&pkgConfig.DeployOpts.AdoptExistingResources, "adopt-existing-resources", false, lang.CmdPackageDeployFlagAdoptExistingResources) + cmd.Flags().DurationVar(&pkgConfig.DeployOpts.Timeout, "timeout", v.GetDuration(common.VPkgDeployTimeout), lang.CmdPackageDeployFlagTimeout) + + cmd.Flags().IntVar(&pkgConfig.PkgOpts.Retries, "retries", v.GetInt(common.VPkgRetries), lang.CmdPackageFlagRetries) + cmd.Flags().StringVarP(&pkgConfig.PkgOpts.PublicKeyPath, "key", "k", v.GetString(common.VPkgPublicKey), lang.CmdPackageFlagFlagPublicKey) + cmd.Flags().BoolVar(&pkgConfig.PkgOpts.SkipSignatureValidation, "skip-signature-validation", false, lang.CmdPackageFlagSkipSignatureValidation) + + cmd.Flags().SortFlags = true + + return cmd +} + +// Run performs the execution of 'init' sub-command. +func (o *InitOptions) Run(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + if err := validateInitFlags(); err != nil { + return fmt.Errorf("invalid command flags were provided: %w", err) + } + + // Continue running package deploy for all components like any other package + initPackageName := sources.GetInitPackageName() + pkgConfig.PkgOpts.PackageSource = initPackageName + + // Try to use an init-package in the executable directory if none exist in current working directory + var err error + if pkgConfig.PkgOpts.PackageSource, err = findInitPackage(cmd.Context(), initPackageName); err != nil { + return err + } + + src, err := sources.New(ctx, &pkgConfig.PkgOpts) + if err != nil { + return err + } + + v := common.GetViper() + pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( + v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) + + pkgClient, err := packager.New(&pkgConfig, packager.WithSource(src), packager.WithContext(ctx)) + if err != nil { + return err + } + defer pkgClient.ClearTempPaths() + + err = pkgClient.Deploy(ctx) + if err != nil { + return err + } + // Since the new logger ignores pterm output the credential table is no longer printed on init. + // This note is the intended replacement, rather than printing creds by default. + logger.From(ctx).Info("init complete. To get credentials for Zarf deployed services run `zarf tools get-creds`") + return nil } func findInitPackage(ctx context.Context, initPackageName string) (string, error) { @@ -179,54 +238,3 @@ func validateInitFlags() error { } return nil } - -func init() { - v := common.InitViper() - - rootCmd.AddCommand(initCmd) - - // Init package variable defaults that are non-zero values - // NOTE: these are not in common.setDefaults so that zarf tools update-creds does not erroneously update values back to the default - v.SetDefault(common.VInitGitPushUser, types.ZarfGitPushUser) - v.SetDefault(common.VInitRegistryPushUser, types.ZarfRegistryPushUser) - - // Init package set variable flags - initCmd.Flags().StringToStringVar(&pkgConfig.PkgOpts.SetVariables, "set", v.GetStringMapString(common.VPkgDeploySet), lang.CmdInitFlagSet) - - // Continue to require --confirm flag for init command to avoid accidental deployments - initCmd.Flags().BoolVar(&config.CommonOptions.Confirm, "confirm", false, lang.CmdInitFlagConfirm) - initCmd.Flags().StringVar(&pkgConfig.PkgOpts.OptionalComponents, "components", v.GetString(common.VInitComponents), lang.CmdInitFlagComponents) - initCmd.Flags().StringVar(&pkgConfig.InitOpts.StorageClass, "storage-class", v.GetString(common.VInitStorageClass), lang.CmdInitFlagStorageClass) - - // Flags for using an external Git server - initCmd.Flags().StringVar(&pkgConfig.InitOpts.GitServer.Address, "git-url", v.GetString(common.VInitGitURL), lang.CmdInitFlagGitURL) - initCmd.Flags().StringVar(&pkgConfig.InitOpts.GitServer.PushUsername, "git-push-username", v.GetString(common.VInitGitPushUser), lang.CmdInitFlagGitPushUser) - initCmd.Flags().StringVar(&pkgConfig.InitOpts.GitServer.PushPassword, "git-push-password", v.GetString(common.VInitGitPushPass), lang.CmdInitFlagGitPushPass) - initCmd.Flags().StringVar(&pkgConfig.InitOpts.GitServer.PullUsername, "git-pull-username", v.GetString(common.VInitGitPullUser), lang.CmdInitFlagGitPullUser) - initCmd.Flags().StringVar(&pkgConfig.InitOpts.GitServer.PullPassword, "git-pull-password", v.GetString(common.VInitGitPullPass), lang.CmdInitFlagGitPullPass) - - // Flags for using an external registry - initCmd.Flags().StringVar(&pkgConfig.InitOpts.RegistryInfo.Address, "registry-url", v.GetString(common.VInitRegistryURL), lang.CmdInitFlagRegURL) - initCmd.Flags().IntVar(&pkgConfig.InitOpts.RegistryInfo.NodePort, "nodeport", v.GetInt(common.VInitRegistryNodeport), lang.CmdInitFlagRegNodePort) - initCmd.Flags().StringVar(&pkgConfig.InitOpts.RegistryInfo.PushUsername, "registry-push-username", v.GetString(common.VInitRegistryPushUser), lang.CmdInitFlagRegPushUser) - initCmd.Flags().StringVar(&pkgConfig.InitOpts.RegistryInfo.PushPassword, "registry-push-password", v.GetString(common.VInitRegistryPushPass), lang.CmdInitFlagRegPushPass) - initCmd.Flags().StringVar(&pkgConfig.InitOpts.RegistryInfo.PullUsername, "registry-pull-username", v.GetString(common.VInitRegistryPullUser), lang.CmdInitFlagRegPullUser) - initCmd.Flags().StringVar(&pkgConfig.InitOpts.RegistryInfo.PullPassword, "registry-pull-password", v.GetString(common.VInitRegistryPullPass), lang.CmdInitFlagRegPullPass) - initCmd.Flags().StringVar(&pkgConfig.InitOpts.RegistryInfo.Secret, "registry-secret", v.GetString(common.VInitRegistrySecret), lang.CmdInitFlagRegSecret) - - // Flags for using an external artifact server - initCmd.Flags().StringVar(&pkgConfig.InitOpts.ArtifactServer.Address, "artifact-url", v.GetString(common.VInitArtifactURL), lang.CmdInitFlagArtifactURL) - initCmd.Flags().StringVar(&pkgConfig.InitOpts.ArtifactServer.PushUsername, "artifact-push-username", v.GetString(common.VInitArtifactPushUser), lang.CmdInitFlagArtifactPushUser) - initCmd.Flags().StringVar(&pkgConfig.InitOpts.ArtifactServer.PushToken, "artifact-push-token", v.GetString(common.VInitArtifactPushToken), lang.CmdInitFlagArtifactPushToken) - - // Flags that control how a deployment proceeds - // Always require adopt-existing-resources flag (no viper) - initCmd.Flags().BoolVar(&pkgConfig.DeployOpts.AdoptExistingResources, "adopt-existing-resources", false, lang.CmdPackageDeployFlagAdoptExistingResources) - initCmd.Flags().DurationVar(&pkgConfig.DeployOpts.Timeout, "timeout", v.GetDuration(common.VPkgDeployTimeout), lang.CmdPackageDeployFlagTimeout) - - initCmd.Flags().IntVar(&pkgConfig.PkgOpts.Retries, "retries", v.GetInt(common.VPkgRetries), lang.CmdPackageFlagRetries) - initCmd.Flags().StringVarP(&pkgConfig.PkgOpts.PublicKeyPath, "key", "k", v.GetString(common.VPkgPublicKey), lang.CmdPackageFlagFlagPublicKey) - initCmd.Flags().BoolVar(&pkgConfig.PkgOpts.SkipSignatureValidation, "skip-signature-validation", false, lang.CmdPackageFlagSkipSignatureValidation) - - initCmd.Flags().SortFlags = true -} diff --git a/src/cmd/internal.go b/src/cmd/internal.go index fd5221325c..99fbfd9514 100644 --- a/src/cmd/internal.go +++ b/src/cmd/internal.go @@ -24,123 +24,177 @@ import ( "github.com/zarf-dev/zarf/src/pkg/message" ) -var ( - rollback bool -) +// NewInternalCommand creates the `internal` sub-command and its neste children. +func NewInternalCommand(rootCmd *cobra.Command) *cobra.Command { + cmd := &cobra.Command{ + Use: "internal", + Hidden: true, + Short: lang.CmdInternalShort, + } -var internalCmd = &cobra.Command{ - Use: "internal", - Hidden: true, - Short: lang.CmdInternalShort, + cmd.AddCommand(NewInternalAgentCommand()) + cmd.AddCommand(NewInternalHttpProxyCommand()) + cmd.AddCommand(NewInternalGenCliDocsCommand(rootCmd)) + cmd.AddCommand(NewInternalCreateReadOnlyGiteaUserCommand()) + cmd.AddCommand(NewInternalCreateArtifactRegistryTokenCommand()) + cmd.AddCommand(NewInternalUpdateGiteaPVCCommand()) + cmd.AddCommand(NewInternalIsValidHostnameCommand()) + cmd.AddCommand(NewInternalCrc32Command()) + + return cmd } -var agentCmd = &cobra.Command{ - Use: "agent", - Short: lang.CmdInternalAgentShort, - Long: lang.CmdInternalAgentLong, - RunE: func(cmd *cobra.Command, _ []string) error { - cluster, err := cluster.NewCluster() - if err != nil { - return err - } - return agent.StartWebhook(cmd.Context(), cluster) - }, +// InternalAgentOptions holds the command-line options for 'internal agent' sub-command. +type InternalAgentOptions struct{} + +// NewInternalAgentCommand creates the `internal agent` sub-command. +func NewInternalAgentCommand() *cobra.Command { + o := &InternalAgentOptions{} + + cmd := &cobra.Command{ + Use: "agent", + Short: lang.CmdInternalAgentShort, + Long: lang.CmdInternalAgentLong, + RunE: o.Run, + } + + return cmd } -var httpProxyCmd = &cobra.Command{ - Use: "http-proxy", - Short: lang.CmdInternalProxyShort, - Long: lang.CmdInternalProxyLong, - RunE: func(cmd *cobra.Command, _ []string) error { - cluster, err := cluster.NewCluster() - if err != nil { - return err - } - return agent.StartHTTPProxy(cmd.Context(), cluster) - }, +// Run performs the execution of 'internal agent' sub-command. +func (o *InternalAgentOptions) Run(cmd *cobra.Command, _ []string) error { + cluster, err := cluster.NewCluster() + if err != nil { + return err + } + return agent.StartWebhook(cmd.Context(), cluster) } -var genCLIDocs = &cobra.Command{ - Use: "gen-cli-docs", - Short: lang.CmdInternalGenerateCliDocsShort, - RunE: func(_ *cobra.Command, _ []string) error { - // Don't include the datestamp in the output - rootCmd.DisableAutoGenTag = true - - resetStringFlags := func(cmd *cobra.Command) { - cmd.Flags().VisitAll(func(flag *pflag.Flag) { - if flag.Value.Type() == "string" { - flag.DefValue = "" - } - }) - } +// InternalHttpProxyOptions holds the command-line options for 'internal http-proxy' sub-command. +type InternalHttpProxyOptions struct{} - for _, cmd := range rootCmd.Commands() { - if cmd.Use == "tools" { - for _, toolCmd := range cmd.Commands() { - // If the command is a vendored command, add a dummy flag to hide root flags from the docs - if common.CheckVendorOnlyFromPath(toolCmd) { - addHiddenDummyFlag(toolCmd, "log-level") - addHiddenDummyFlag(toolCmd, "log-format") - addHiddenDummyFlag(toolCmd, "architecture") - addHiddenDummyFlag(toolCmd, "no-log-file") - addHiddenDummyFlag(toolCmd, "no-progress") - addHiddenDummyFlag(toolCmd, "zarf-cache") - addHiddenDummyFlag(toolCmd, "tmpdir") - addHiddenDummyFlag(toolCmd, "insecure") - addHiddenDummyFlag(toolCmd, "no-color") - } +// NewInternalHttpProxyCommand creates the `internal http-proxy` sub-command. +func NewInternalHttpProxyCommand() *cobra.Command { + o := &InternalHttpProxyOptions{} + + cmd := &cobra.Command{ + Use: "http-proxy", + Short: lang.CmdInternalProxyShort, + Long: lang.CmdInternalProxyLong, + RunE: o.Run, + } + + return cmd +} + +func (o *InternalHttpProxyOptions) Run(cmd *cobra.Command, _ []string) error { + cluster, err := cluster.NewCluster() + if err != nil { + return err + } + return agent.StartHTTPProxy(cmd.Context(), cluster) +} + +// InternalGenCliDocsOptions holds the command-line options for 'internal gen-cli-docs' sub-command. +type InternalGenCliDocsOptions struct { + rootCmd *cobra.Command +} + +// NewInternalGenCliDocsCommand creates the `internal gen-cli-docs` sub-command. +func NewInternalGenCliDocsCommand(root *cobra.Command) *cobra.Command { + // TODO(soltysh): ideally this should be replace with cmd.Root() call from cobra + o := &InternalGenCliDocsOptions{ + rootCmd: root, + } + + cmd := &cobra.Command{ + Use: "gen-cli-docs", + Short: lang.CmdInternalGenerateCliDocsShort, + RunE: o.Run, + } + + return cmd +} + +// Run performs the execution of 'internal gen-cli-docs' sub-command. +func (o *InternalGenCliDocsOptions) Run(_ *cobra.Command, _ []string) error { + // Don't include the datestamp in the output + o.rootCmd.DisableAutoGenTag = true + + resetStringFlags := func(cmd *cobra.Command) { + cmd.Flags().VisitAll(func(flag *pflag.Flag) { + if flag.Value.Type() == "string" { + flag.DefValue = "" + } + }) + } - // Remove the default values from all of the helm commands during the CLI command doc generation - if toolCmd.Use == "helm" || toolCmd.Use == "sbom" { - toolCmd.PersistentFlags().VisitAll(func(flag *pflag.Flag) { - if flag.Value.Type() == "string" { - flag.DefValue = "" - } - }) - resetStringFlags(toolCmd) - for _, subCmd := range toolCmd.Commands() { - resetStringFlags(subCmd) - for _, helmSubCmd := range subCmd.Commands() { - resetStringFlags(helmSubCmd) - } + for _, cmd := range o.rootCmd.Commands() { + if cmd.Use == "tools" { + for _, toolCmd := range cmd.Commands() { + // If the command is a vendored command, add a dummy flag to hide root flags from the docs + if common.CheckVendorOnlyFromPath(toolCmd) { + addHiddenDummyFlag(toolCmd, "log-level") + addHiddenDummyFlag(toolCmd, "log-format") + addHiddenDummyFlag(toolCmd, "architecture") + addHiddenDummyFlag(toolCmd, "no-log-file") + addHiddenDummyFlag(toolCmd, "no-progress") + addHiddenDummyFlag(toolCmd, "zarf-cache") + addHiddenDummyFlag(toolCmd, "tmpdir") + addHiddenDummyFlag(toolCmd, "insecure") + addHiddenDummyFlag(toolCmd, "no-color") + } + + // Remove the default values from all of the helm commands during the CLI command doc generation + if toolCmd.Use == "helm" || toolCmd.Use == "sbom" { + toolCmd.PersistentFlags().VisitAll(func(flag *pflag.Flag) { + if flag.Value.Type() == "string" { + flag.DefValue = "" + } + }) + resetStringFlags(toolCmd) + for _, subCmd := range toolCmd.Commands() { + resetStringFlags(subCmd) + for _, helmSubCmd := range subCmd.Commands() { + resetStringFlags(helmSubCmd) } } + } - if toolCmd.Use == "monitor" { - resetStringFlags(toolCmd) - } + if toolCmd.Use == "monitor" { + resetStringFlags(toolCmd) + } - if toolCmd.Use == "yq" { - for _, subCmd := range toolCmd.Commands() { - if subCmd.Name() == "shell-completion" { - subCmd.Hidden = true - } + if toolCmd.Use == "yq" { + for _, subCmd := range toolCmd.Commands() { + if subCmd.Name() == "shell-completion" { + subCmd.Hidden = true } } } } } + } - if err := os.RemoveAll("./site/src/content/docs/commands"); err != nil { - return err - } - if err := os.Mkdir("./site/src/content/docs/commands", 0775); err != nil { - return err - } + if err := os.RemoveAll("./site/src/content/docs/commands"); err != nil { + return err + } + if err := os.Mkdir("./site/src/content/docs/commands", 0775); err != nil { + return err + } - var prependTitle = func(s string) string { - fmt.Println(s) + var prependTitle = func(s string) string { + fmt.Println(s) - name := filepath.Base(s) + name := filepath.Base(s) - // strip .md extension - name = name[:len(name)-3] + // strip .md extension + name = name[:len(name)-3] - // replace _ with space - title := strings.Replace(name, "_", " ", -1) + // replace _ with space + title := strings.Replace(name, "_", " ", -1) - return fmt.Sprintf(`--- + return fmt.Sprintf(`--- title: %s description: Zarf CLI command reference for %s. tableOfContents: false @@ -149,31 +203,115 @@ tableOfContents: false `, title, title) - } + } - var linkHandler = func(link string) string { - return "/commands/" + link[:len(link)-3] + "/" - } + var linkHandler = func(link string) string { + return "/commands/" + link[:len(link)-3] + "/" + } - return doc.GenMarkdownTreeCustom(rootCmd, "./site/src/content/docs/commands", prependTitle, linkHandler) - }, + return doc.GenMarkdownTreeCustom(o.rootCmd, "./site/src/content/docs/commands", prependTitle, linkHandler) } -var createReadOnlyGiteaUser = &cobra.Command{ - Use: "create-read-only-gitea-user", - Short: lang.CmdInternalCreateReadOnlyGiteaUserShort, - Long: lang.CmdInternalCreateReadOnlyGiteaUserLong, - RunE: func(cmd *cobra.Command, _ []string) error { - timeoutCtx, cancel := context.WithTimeout(cmd.Context(), cluster.DefaultTimeout) - defer cancel() - c, err := cluster.NewClusterWithWait(timeoutCtx) +func addHiddenDummyFlag(cmd *cobra.Command, flagDummy string) { + if cmd.PersistentFlags().Lookup(flagDummy) == nil { + var dummyStr string + cmd.PersistentFlags().StringVar(&dummyStr, flagDummy, "", "") + err := cmd.PersistentFlags().MarkHidden(flagDummy) if err != nil { - return err + logger.From(cmd.Context()).Debug("Unable to add hidden dummy flag", "error", err) } - state, err := c.LoadZarfState(cmd.Context()) + } +} + +// InternalCreateReadOnlyGiteaUserOptions holds the command-line options for 'internal create-read-only-gitea-user' sub-command. +type InternalCreateReadOnlyGiteaUserOptions struct{} + +// NewInternalCreateReadOnlyGiteaUserCommand creates the `internal create-read-oly-gitea-user` sub-command. +func NewInternalCreateReadOnlyGiteaUserCommand() *cobra.Command { + o := &InternalCreateReadOnlyGiteaUserOptions{} + + cmd := &cobra.Command{ + Use: "create-read-only-gitea-user", + Short: lang.CmdInternalCreateReadOnlyGiteaUserShort, + Long: lang.CmdInternalCreateReadOnlyGiteaUserLong, + RunE: o.Run, + } + + return cmd +} + +// Run performs the execution of 'internal create-read-only-gitea-user' sub-command. +func (o *InternalCreateReadOnlyGiteaUserOptions) Run(cmd *cobra.Command, _ []string) error { + timeoutCtx, cancel := context.WithTimeout(cmd.Context(), cluster.DefaultTimeout) + defer cancel() + c, err := cluster.NewClusterWithWait(timeoutCtx) + if err != nil { + return err + } + state, err := c.LoadZarfState(cmd.Context()) + if err != nil { + return err + } + tunnel, err := c.NewTunnel(cluster.ZarfNamespaceName, cluster.SvcResource, cluster.ZarfGitServerName, "", 0, cluster.ZarfGitServerPort) + if err != nil { + return err + } + _, err = tunnel.Connect(cmd.Context()) + if err != nil { + return err + } + defer tunnel.Close() + tunnelURL := tunnel.HTTPEndpoint() + giteaClient, err := gitea.NewClient(tunnelURL, state.GitServer.PushUsername, state.GitServer.PushPassword) + if err != nil { + return err + } + err = tunnel.Wrap(func() error { + err = giteaClient.CreateReadOnlyUser(cmd.Context(), state.GitServer.PullUsername, state.GitServer.PullPassword) if err != nil { return err } + return nil + }) + if err != nil { + return err + } + return nil +} + +// InternalCreateArtifactRegistryTokenOptions holds the command-line options for 'internal create-artifact-registry-token' sub-command. +type InternalCreateArtifactRegistryTokenOptions struct{} + +// NewInternalCreateArtifactRegistryTokenCommand creates the `internal create-artifact-registry-token` sub-command. +func NewInternalCreateArtifactRegistryTokenCommand() *cobra.Command { + o := &InternalCreateArtifactRegistryTokenOptions{} + + cmd := &cobra.Command{ + Use: "create-artifact-registry-token", + Short: lang.CmdInternalArtifactRegistryGiteaTokenShort, + Long: lang.CmdInternalArtifactRegistryGiteaTokenLong, + RunE: o.Run, + } + + return cmd +} + +// Run performs the execution of 'internal create-artifact-registry-token' sub-command. +func (o *InternalCreateArtifactRegistryTokenOptions) Run(cmd *cobra.Command, _ []string) error { + timeoutCtx, cancel := context.WithTimeout(cmd.Context(), cluster.DefaultTimeout) + defer cancel() + c, err := cluster.NewClusterWithWait(timeoutCtx) + if err != nil { + return err + } + ctx := cmd.Context() + state, err := c.LoadZarfState(ctx) + if err != nil { + return err + } + + // If we are setup to use an internal artifact server, create the artifact registry token + if state.ArtifactServer.IsInternal() { tunnel, err := c.NewTunnel(cluster.ZarfNamespaceName, cluster.SvcResource, cluster.ZarfGitServerName, "", 0, cluster.ZarfGitServerPort) if err != nil { return err @@ -189,140 +327,109 @@ var createReadOnlyGiteaUser = &cobra.Command{ return err } err = tunnel.Wrap(func() error { - err = giteaClient.CreateReadOnlyUser(cmd.Context(), state.GitServer.PullUsername, state.GitServer.PullPassword) + tokenSha1, err := giteaClient.CreatePackageRegistryToken(ctx) if err != nil { - return err + return fmt.Errorf("unable to create an artifact registry token for Gitea: %w", err) } + state.ArtifactServer.PushToken = tokenSha1 return nil }) if err != nil { return err } - return nil - }, -} - -var createPackageRegistryToken = &cobra.Command{ - Use: "create-artifact-registry-token", - Short: lang.CmdInternalArtifactRegistryGiteaTokenShort, - Long: lang.CmdInternalArtifactRegistryGiteaTokenLong, - RunE: func(cmd *cobra.Command, _ []string) error { - timeoutCtx, cancel := context.WithTimeout(cmd.Context(), cluster.DefaultTimeout) - defer cancel() - c, err := cluster.NewClusterWithWait(timeoutCtx) - if err != nil { - return err - } - ctx := cmd.Context() - state, err := c.LoadZarfState(ctx) - if err != nil { + if err := c.SaveZarfState(ctx, state); err != nil { return err } + } + return nil +} - // If we are setup to use an internal artifact server, create the artifact registry token - if state.ArtifactServer.IsInternal() { - tunnel, err := c.NewTunnel(cluster.ZarfNamespaceName, cluster.SvcResource, cluster.ZarfGitServerName, "", 0, cluster.ZarfGitServerPort) - if err != nil { - return err - } - _, err = tunnel.Connect(cmd.Context()) - if err != nil { - return err - } - defer tunnel.Close() - tunnelURL := tunnel.HTTPEndpoint() - giteaClient, err := gitea.NewClient(tunnelURL, state.GitServer.PushUsername, state.GitServer.PushPassword) - if err != nil { - return err - } - err = tunnel.Wrap(func() error { - tokenSha1, err := giteaClient.CreatePackageRegistryToken(ctx) - if err != nil { - return fmt.Errorf("unable to create an artifact registry token for Gitea: %w", err) - } - state.ArtifactServer.PushToken = tokenSha1 - return nil - }) - if err != nil { - return err - } - if err := c.SaveZarfState(ctx, state); err != nil { - return err - } - } - return nil - }, +// InternalUpdateGiteaPVCOptions holds the command-line options for 'internal update-gitea-pvc' sub-command. +type InternalUpdateGiteaPVCOptions struct { + rollback bool } -var updateGiteaPVC = &cobra.Command{ - Use: "update-gitea-pvc", - Short: lang.CmdInternalUpdateGiteaPVCShort, - Long: lang.CmdInternalUpdateGiteaPVCLong, - RunE: func(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - pvcName := os.Getenv("ZARF_VAR_GIT_SERVER_EXISTING_PVC") +// NewInternalUpdateGiteaPVCCommands creates the `internal update-gitea-pvc` sub-command. +func NewInternalUpdateGiteaPVCCommand() *cobra.Command { + o := &InternalUpdateGiteaPVCOptions{} - c, err := cluster.NewCluster() - if err != nil { - return err - } - // There is a possibility that the pvc does not yet exist and Gitea helm chart should create it - helmShouldCreate, err := c.UpdateGiteaPVC(ctx, pvcName, rollback) - if err != nil { - message.WarnErr(err, lang.CmdInternalUpdateGiteaPVCErr) - logger.From(ctx).Warn("Unable to update the existing Gitea persistent volume claim", "error", err.Error()) - } - fmt.Print(helmShouldCreate) - return nil - }, -} + cmd := &cobra.Command{ + Use: "update-gitea-pvc", + Short: lang.CmdInternalUpdateGiteaPVCShort, + Long: lang.CmdInternalUpdateGiteaPVCLong, + RunE: o.Run, + } -var isValidHostname = &cobra.Command{ - Use: "is-valid-hostname", - Short: lang.CmdInternalIsValidHostnameShort, - RunE: func(_ *cobra.Command, _ []string) error { - if valid := helpers.IsValidHostName(); !valid { - hostname, err := os.Hostname() - return fmt.Errorf("the hostname %s is not valid. Ensure the hostname meets RFC1123 requirements https://www.rfc-editor.org/rfc/rfc1123.html, error=%w", hostname, err) - } - return nil - }, + cmd.Flags().BoolVarP(&o.rollback, "rollback", "r", false, lang.CmdInternalFlagUpdateGiteaPVCRollback) + + return cmd } -var computeCrc32 = &cobra.Command{ - Use: "crc32 TEXT", - Aliases: []string{"c"}, - Short: lang.CmdInternalCrc32Short, - Args: cobra.ExactArgs(1), - Run: func(_ *cobra.Command, args []string) { - text := args[0] - hash := helpers.GetCRCHash(text) - fmt.Printf("%d\n", hash) - }, +// Run performs the execution of 'internal update-gitea-pvc' sub-command. +func (o *InternalUpdateGiteaPVCOptions) Run(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + pvcName := os.Getenv("ZARF_VAR_GIT_SERVER_EXISTING_PVC") + + c, err := cluster.NewCluster() + if err != nil { + return err + } + // There is a possibility that the pvc does not yet exist and Gitea helm chart should create it + helmShouldCreate, err := c.UpdateGiteaPVC(ctx, pvcName, o.rollback) + if err != nil { + message.WarnErr(err, lang.CmdInternalUpdateGiteaPVCErr) + logger.From(ctx).Warn("Unable to update the existing Gitea persistent volume claim", "error", err.Error()) + } + fmt.Print(helmShouldCreate) + return nil } -func init() { - rootCmd.AddCommand(internalCmd) +// InternalIsValidHostnameOptions holds the command-line options for 'internal is-valid-hostname' sub-command. +type InternalIsValidHostnameOptions struct{} - internalCmd.AddCommand(agentCmd) - internalCmd.AddCommand(httpProxyCmd) - internalCmd.AddCommand(genCLIDocs) - internalCmd.AddCommand(createReadOnlyGiteaUser) - internalCmd.AddCommand(createPackageRegistryToken) - internalCmd.AddCommand(updateGiteaPVC) - internalCmd.AddCommand(isValidHostname) - internalCmd.AddCommand(computeCrc32) +// NewInternalIsValidHostnameCommand creates the `internal is-valid-hostname` sub-command. +func NewInternalIsValidHostnameCommand() *cobra.Command { + o := &InternalIsValidHostnameOptions{} - updateGiteaPVC.Flags().BoolVarP(&rollback, "rollback", "r", false, lang.CmdInternalFlagUpdateGiteaPVCRollback) + cmd := &cobra.Command{ + Use: "is-valid-hostname", + Short: lang.CmdInternalIsValidHostnameShort, + RunE: o.Run, + } + + return cmd } -func addHiddenDummyFlag(cmd *cobra.Command, flagDummy string) { - if cmd.PersistentFlags().Lookup(flagDummy) == nil { - var dummyStr string - cmd.PersistentFlags().StringVar(&dummyStr, flagDummy, "", "") - err := cmd.PersistentFlags().MarkHidden(flagDummy) - if err != nil { - logger.From(cmd.Context()).Debug("Unable to add hidden dummy flag", "error", err) - } +// Run performs the execution of 'internal is-valid-hostname' sub-command. +func (o *InternalIsValidHostnameOptions) Run(_ *cobra.Command, _ []string) error { + if valid := helpers.IsValidHostName(); !valid { + hostname, err := os.Hostname() + return fmt.Errorf("the hostname %s is not valid. Ensure the hostname meets RFC1123 requirements https://www.rfc-editor.org/rfc/rfc1123.html, error=%w", hostname, err) } + return nil +} + +// InternalCrc32Options holds the command-line options for 'intenral crc32' sub-command. +type InternalCrc32Options struct{} + +// NewInternalCrc32Command creates the `internal crc32` sub-command. +func NewInternalCrc32Command() *cobra.Command { + o := &InternalCrc32Options{} + + cmd := &cobra.Command{ + Use: "crc32 TEXT", + Aliases: []string{"c"}, + Short: lang.CmdInternalCrc32Short, + Args: cobra.ExactArgs(1), + Run: o.Run, + } + + return cmd +} + +// Run performs the execution of 'internal crc32' sub-command. +func (o *InternalCrc32Options) Run(_ *cobra.Command, args []string) { + text := args[0] + hash := helpers.GetCRCHash(text) + fmt.Printf("%d\n", hash) } diff --git a/src/cmd/package.go b/src/cmd/package.go index 2fc06378ca..5fc4918d1f 100644 --- a/src/cmd/package.go +++ b/src/cmd/package.go @@ -35,364 +35,573 @@ import ( "github.com/zarf-dev/zarf/src/types" ) -var packageCmd = &cobra.Command{ - Use: "package", - Aliases: []string{"p"}, - Short: lang.CmdPackageShort, +func NewPackageCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "package", + Aliases: []string{"p"}, + Short: lang.CmdPackageShort, + } + + v := common.InitViper() + + cmd.Flags().IntVar(&config.CommonOptions.OCIConcurrency, "oci-concurrency", v.GetInt(common.VPkgOCIConcurrency), lang.CmdPackageFlagConcurrency) + cmd.Flags().StringVarP(&pkgConfig.PkgOpts.PublicKeyPath, "key", "k", v.GetString(common.VPkgPublicKey), lang.CmdPackageFlagFlagPublicKey) + + cmd.AddCommand(NewPackageCreateCommand(v)) + cmd.AddCommand(NewPackageDeployCommand(v)) + cmd.AddCommand(NewPackageMirrorResourcesCommand(v)) + cmd.AddCommand(NewPackageInspectCommand()) + cmd.AddCommand(NewPackageRemoveCommand(v)) + cmd.AddCommand(NewPackageListCommand()) + cmd.AddCommand(NewPackagePublishCommand(v)) + cmd.AddCommand(NewPackagePullCommand(v)) + + return cmd } -var packageCreateCmd = &cobra.Command{ - Use: "create [ DIRECTORY ]", - Aliases: []string{"c"}, - Args: cobra.MaximumNArgs(1), - Short: lang.CmdPackageCreateShort, - Long: lang.CmdPackageCreateLong, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - l := logger.From(ctx) - pkgConfig.CreateOpts.BaseDir = setBaseDirectory(args) - - var isCleanPathRegex = regexp.MustCompile(`^[a-zA-Z0-9\_\-\/\.\~\\:]+$`) - if !isCleanPathRegex.MatchString(config.CommonOptions.CachePath) { - // TODO(mkcp): Remove message on logger release - message.Warnf(lang.CmdPackageCreateCleanPathErr, config.ZarfDefaultCachePath) - l.Warn("invalid characters in Zarf cache path, using default", "cfg", config.ZarfDefaultCachePath, "default", config.ZarfDefaultCachePath) - config.CommonOptions.CachePath = config.ZarfDefaultCachePath - } +type PackageCreateOptions struct{} - v := common.GetViper() - pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap( - v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) +func NewPackageCreateCommand(v *viper.Viper) *cobra.Command { + o := &PackageCreateOptions{} - pkgClient, err := packager.New(&pkgConfig, - packager.WithContext(ctx), - ) - if err != nil { - return err - } - defer pkgClient.ClearTempPaths() + cmd := &cobra.Command{ + Use: "create [ DIRECTORY ]", + Aliases: []string{"c"}, + Args: cobra.MaximumNArgs(1), + Short: lang.CmdPackageCreateShort, + Long: lang.CmdPackageCreateLong, + RunE: o.Run, + } - err = pkgClient.Create(ctx) + // Always require confirm flag (no viper) + cmd.Flags().BoolVar(&config.CommonOptions.Confirm, "confirm", false, lang.CmdPackageCreateFlagConfirm) - // NOTE(mkcp): LintErrors are rendered with a table - var lintErr *lint.LintError - if errors.As(err, &lintErr) { - common.PrintFindings(ctx, lintErr) - } - if err != nil { - return fmt.Errorf("failed to create package: %w", err) - } - return nil - }, + outputDirectory := v.GetString("package.create.output_directory") + output := v.GetString(common.VPkgCreateOutput) + if outputDirectory != "" && output == "" { + v.Set(common.VPkgCreateOutput, outputDirectory) + } + cmd.Flags().StringVar(&pkgConfig.CreateOpts.Output, "output-directory", v.GetString("package.create.output_directory"), lang.CmdPackageCreateFlagOutput) + cmd.Flags().StringVarP(&pkgConfig.CreateOpts.Output, "output", "o", v.GetString(common.VPkgCreateOutput), lang.CmdPackageCreateFlagOutput) + + cmd.Flags().StringVar(&pkgConfig.CreateOpts.DifferentialPackagePath, "differential", v.GetString(common.VPkgCreateDifferential), lang.CmdPackageCreateFlagDifferential) + cmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdPackageCreateFlagSet) + cmd.Flags().BoolVarP(&pkgConfig.CreateOpts.ViewSBOM, "sbom", "s", v.GetBool(common.VPkgCreateSbom), lang.CmdPackageCreateFlagSbom) + cmd.Flags().StringVar(&pkgConfig.CreateOpts.SBOMOutputDir, "sbom-out", v.GetString(common.VPkgCreateSbomOutput), lang.CmdPackageCreateFlagSbomOut) + cmd.Flags().BoolVar(&pkgConfig.CreateOpts.SkipSBOM, "skip-sbom", v.GetBool(common.VPkgCreateSkipSbom), lang.CmdPackageCreateFlagSkipSbom) + cmd.Flags().IntVarP(&pkgConfig.CreateOpts.MaxPackageSizeMB, "max-package-size", "m", v.GetInt(common.VPkgCreateMaxPackageSize), lang.CmdPackageCreateFlagMaxPackageSize) + cmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.RegistryOverrides, "registry-override", v.GetStringMapString(common.VPkgCreateRegistryOverride), lang.CmdPackageCreateFlagRegistryOverride) + cmd.Flags().StringVarP(&pkgConfig.CreateOpts.Flavor, "flavor", "f", v.GetString(common.VPkgCreateFlavor), lang.CmdPackageCreateFlagFlavor) + + cmd.Flags().StringVar(&pkgConfig.CreateOpts.SigningKeyPath, "signing-key", v.GetString(common.VPkgCreateSigningKey), lang.CmdPackageCreateFlagSigningKey) + cmd.Flags().StringVar(&pkgConfig.CreateOpts.SigningKeyPassword, "signing-key-pass", v.GetString(common.VPkgCreateSigningKeyPassword), lang.CmdPackageCreateFlagSigningKeyPassword) + + cmd.Flags().StringVarP(&pkgConfig.CreateOpts.SigningKeyPath, "key", "k", v.GetString(common.VPkgCreateSigningKey), lang.CmdPackageCreateFlagDeprecatedKey) + cmd.Flags().StringVar(&pkgConfig.CreateOpts.SigningKeyPassword, "key-pass", v.GetString(common.VPkgCreateSigningKeyPassword), lang.CmdPackageCreateFlagDeprecatedKeyPassword) + + cmd.Flags().IntVar(&pkgConfig.PkgOpts.Retries, "retries", v.GetInt(common.VPkgRetries), lang.CmdPackageFlagRetries) + + errOD := cmd.Flags().MarkHidden("output-directory") + if errOD != nil { + logger.Default().Debug("unable to mark flag output-directory", "error", errOD) + } + errKey := cmd.Flags().MarkHidden("key") + if errKey != nil { + logger.Default().Debug("unable to mark flag key", "error", errKey) + } + errKP := cmd.Flags().MarkHidden("key-pass") + if errKP != nil { + logger.Default().Debug("unable to mark flag key-pass", "error", errKP) + } + + return cmd } -var packageDeployCmd = &cobra.Command{ - Use: "deploy [ PACKAGE_SOURCE ]", - Aliases: []string{"d"}, - Short: lang.CmdPackageDeployShort, - Long: lang.CmdPackageDeployLong, - Args: cobra.MaximumNArgs(1), - PreRun: func(_ *cobra.Command, _ []string) { - // If --insecure was provided, set --skip-signature-validation to match - if config.CommonOptions.Insecure { - pkgConfig.PkgOpts.SkipSignatureValidation = true - } - }, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - packageSource, err := choosePackage(ctx, args) - if err != nil { - return err - } - pkgConfig.PkgOpts.PackageSource = packageSource +func (o *PackageCreateOptions) Run(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + l := logger.From(ctx) + pkgConfig.CreateOpts.BaseDir = setBaseDirectory(args) + + var isCleanPathRegex = regexp.MustCompile(`^[a-zA-Z0-9\_\-\/\.\~\\:]+$`) + if !isCleanPathRegex.MatchString(config.CommonOptions.CachePath) { + // TODO(mkcp): Remove message on logger release + message.Warnf(lang.CmdPackageCreateCleanPathErr, config.ZarfDefaultCachePath) + l.Warn("invalid characters in Zarf cache path, using default", "cfg", config.ZarfDefaultCachePath, "default", config.ZarfDefaultCachePath) + config.CommonOptions.CachePath = config.ZarfDefaultCachePath + } + + v := common.GetViper() + pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap( + v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) + + pkgClient, err := packager.New(&pkgConfig, + packager.WithContext(ctx), + ) + if err != nil { + return err + } + defer pkgClient.ClearTempPaths() + + err = pkgClient.Create(ctx) + + // NOTE(mkcp): LintErrors are rendered with a table + var lintErr *lint.LintError + if errors.As(err, &lintErr) { + common.PrintFindings(ctx, lintErr) + } + if err != nil { + return fmt.Errorf("failed to create package: %w", err) + } + return nil +} + +type PackageDeployOptions struct{} + +func NewPackageDeployCommand(v *viper.Viper) *cobra.Command { + o := &PackageDeployOptions{} + + cmd := &cobra.Command{ + Use: "deploy [ PACKAGE_SOURCE ]", + Aliases: []string{"d"}, + Short: lang.CmdPackageDeployShort, + Long: lang.CmdPackageDeployLong, + Args: cobra.MaximumNArgs(1), + PreRun: o.PreRun, + RunE: o.Run, + } + + // Always require confirm flag (no viper) + cmd.Flags().BoolVar(&config.CommonOptions.Confirm, "confirm", false, lang.CmdPackageDeployFlagConfirm) + + // Always require adopt-existing-resources flag (no viper) + cmd.Flags().BoolVar(&pkgConfig.DeployOpts.AdoptExistingResources, "adopt-existing-resources", false, lang.CmdPackageDeployFlagAdoptExistingResources) + cmd.Flags().DurationVar(&pkgConfig.DeployOpts.Timeout, "timeout", v.GetDuration(common.VPkgDeployTimeout), lang.CmdPackageDeployFlagTimeout) + + cmd.Flags().IntVar(&pkgConfig.PkgOpts.Retries, "retries", v.GetInt(common.VPkgRetries), lang.CmdPackageFlagRetries) + cmd.Flags().StringToStringVar(&pkgConfig.PkgOpts.SetVariables, "set", v.GetStringMapString(common.VPkgDeploySet), lang.CmdPackageDeployFlagSet) + cmd.Flags().StringVar(&pkgConfig.PkgOpts.OptionalComponents, "components", v.GetString(common.VPkgDeployComponents), lang.CmdPackageDeployFlagComponents) + cmd.Flags().StringVar(&pkgConfig.PkgOpts.Shasum, "shasum", v.GetString(common.VPkgDeployShasum), lang.CmdPackageDeployFlagShasum) + cmd.Flags().StringVar(&pkgConfig.PkgOpts.SGetKeyPath, "sget", v.GetString(common.VPkgDeploySget), lang.CmdPackageDeployFlagSget) + cmd.Flags().BoolVar(&pkgConfig.PkgOpts.SkipSignatureValidation, "skip-signature-validation", false, lang.CmdPackageFlagSkipSignatureValidation) + + err := cmd.Flags().MarkHidden("sget") + if err != nil { + logger.Default().Debug("unable to mark flag sget", "error", err) + } + + return cmd +} + +func (o *PackageDeployOptions) PreRun(_ *cobra.Command, _ []string) { + // If --insecure was provided, set --skip-signature-validation to match + if config.CommonOptions.Insecure { + pkgConfig.PkgOpts.SkipSignatureValidation = true + } +} + +func (o *PackageDeployOptions) Run(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + packageSource, err := choosePackage(ctx, args) + if err != nil { + return err + } + pkgConfig.PkgOpts.PackageSource = packageSource + + v := common.GetViper() + pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( + v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) + + pkgClient, err := packager.New(&pkgConfig, packager.WithContext(cmd.Context())) + if err != nil { + return err + } + defer pkgClient.ClearTempPaths() + + if err := pkgClient.Deploy(ctx); err != nil { + return fmt.Errorf("failed to deploy package: %w", err) + } + return nil +} + +type PackageMirrorResourcesOptions struct{} + +func NewPackageMirrorResourcesCommand(v *viper.Viper) *cobra.Command { + o := &PackageMirrorResourcesOptions{} + + cmd := &cobra.Command{ + Use: "mirror-resources [ PACKAGE_SOURCE ]", + Aliases: []string{"mr"}, + Short: lang.CmdPackageMirrorShort, + Long: lang.CmdPackageMirrorLong, + Example: lang.CmdPackageMirrorExample, + Args: cobra.MaximumNArgs(1), + PreRun: o.PreRun, + RunE: o.Run, + } + + // Init package variable defaults that are non-zero values + // NOTE: these are not in common.setDefaults so that zarf tools update-creds does not erroneously update values back to the default + v.SetDefault(common.VInitGitPushUser, types.ZarfGitPushUser) + v.SetDefault(common.VInitRegistryPushUser, types.ZarfRegistryPushUser) + + // Always require confirm flag (no viper) + cmd.Flags().BoolVar(&config.CommonOptions.Confirm, "confirm", false, lang.CmdPackageDeployFlagConfirm) - v := common.GetViper() - pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( - v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) + cmd.Flags().StringVar(&pkgConfig.PkgOpts.Shasum, "shasum", "", lang.CmdPackagePullFlagShasum) + cmd.Flags().BoolVar(&pkgConfig.MirrorOpts.NoImgChecksum, "no-img-checksum", false, lang.CmdPackageMirrorFlagNoChecksum) + cmd.Flags().BoolVar(&pkgConfig.PkgOpts.SkipSignatureValidation, "skip-signature-validation", false, lang.CmdPackageFlagSkipSignatureValidation) + + cmd.Flags().IntVar(&pkgConfig.PkgOpts.Retries, "retries", v.GetInt(common.VPkgRetries), lang.CmdPackageFlagRetries) + cmd.Flags().StringVar(&pkgConfig.PkgOpts.OptionalComponents, "components", v.GetString(common.VPkgDeployComponents), lang.CmdPackageMirrorFlagComponents) + + // Flags for using an external Git server + cmd.Flags().StringVar(&pkgConfig.InitOpts.GitServer.Address, "git-url", v.GetString(common.VInitGitURL), lang.CmdInitFlagGitURL) + cmd.Flags().StringVar(&pkgConfig.InitOpts.GitServer.PushUsername, "git-push-username", v.GetString(common.VInitGitPushUser), lang.CmdInitFlagGitPushUser) + cmd.Flags().StringVar(&pkgConfig.InitOpts.GitServer.PushPassword, "git-push-password", v.GetString(common.VInitGitPushPass), lang.CmdInitFlagGitPushPass) + + // Flags for using an external registry + cmd.Flags().StringVar(&pkgConfig.InitOpts.RegistryInfo.Address, "registry-url", v.GetString(common.VInitRegistryURL), lang.CmdInitFlagRegURL) + cmd.Flags().StringVar(&pkgConfig.InitOpts.RegistryInfo.PushUsername, "registry-push-username", v.GetString(common.VInitRegistryPushUser), lang.CmdInitFlagRegPushUser) + cmd.Flags().StringVar(&pkgConfig.InitOpts.RegistryInfo.PushPassword, "registry-push-password", v.GetString(common.VInitRegistryPushPass), lang.CmdInitFlagRegPushPass) - pkgClient, err := packager.New(&pkgConfig, packager.WithContext(cmd.Context())) + return cmd +} + +func (o *PackageMirrorResourcesOptions) PreRun(_ *cobra.Command, _ []string) { + // If --insecure was provided, set --skip-signature-validation to match + if config.CommonOptions.Insecure { + pkgConfig.PkgOpts.SkipSignatureValidation = true + } +} + +func (o *PackageMirrorResourcesOptions) Run(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + var c *cluster.Cluster + if dns.IsServiceURL(pkgConfig.InitOpts.RegistryInfo.Address) || dns.IsServiceURL(pkgConfig.InitOpts.GitServer.Address) { + var err error + c, err = cluster.NewCluster() if err != nil { return err } - defer pkgClient.ClearTempPaths() + } + src, err := choosePackage(ctx, args) + if err != nil { + return err + } + filter := filters.Combine( + filters.ByLocalOS(runtime.GOOS), + filters.BySelectState(pkgConfig.PkgOpts.OptionalComponents), + ) + + loadOpt := packager2.LoadOptions{ + Source: src, + Shasum: pkgConfig.PkgOpts.Shasum, + PublicKeyPath: pkgConfig.PkgOpts.PublicKeyPath, + SkipSignatureValidation: pkgConfig.PkgOpts.SkipSignatureValidation, + Filter: filter, + } + pkgLayout, err := packager2.LoadPackage(cmd.Context(), loadOpt) + if err != nil { + return err + } + defer func() { + // Cleanup package files + err = errors.Join(err, pkgLayout.Cleanup()) + }() + + mirrorOpt := packager2.MirrorOptions{ + Cluster: c, + PkgLayout: pkgLayout, + Filter: filter, + RegistryInfo: pkgConfig.InitOpts.RegistryInfo, + GitInfo: pkgConfig.InitOpts.GitServer, + NoImageChecksum: pkgConfig.MirrorOpts.NoImgChecksum, + Retries: pkgConfig.PkgOpts.Retries, + } + err = packager2.Mirror(ctx, mirrorOpt) + if err != nil { + return err + } + return nil +} - if err := pkgClient.Deploy(ctx); err != nil { - return fmt.Errorf("failed to deploy package: %w", err) - } - return nil - }, +type PackageInspectOptions struct{} + +func NewPackageInspectCommand() *cobra.Command { + o := &PackageInspectOptions{} + cmd := &cobra.Command{ + Use: "inspect [ PACKAGE_SOURCE ]", + Aliases: []string{"i"}, + Short: lang.CmdPackageInspectShort, + Long: lang.CmdPackageInspectLong, + Args: cobra.MaximumNArgs(1), + PreRun: o.PreRun, + RunE: o.Run, + } + + cmd.Flags().BoolVarP(&pkgConfig.InspectOpts.ViewSBOM, "sbom", "s", false, lang.CmdPackageInspectFlagSbom) + cmd.Flags().StringVar(&pkgConfig.InspectOpts.SBOMOutputDir, "sbom-out", "", lang.CmdPackageInspectFlagSbomOut) + cmd.Flags().BoolVar(&pkgConfig.InspectOpts.ListImages, "list-images", false, lang.CmdPackageInspectFlagListImages) + cmd.Flags().BoolVar(&pkgConfig.PkgOpts.SkipSignatureValidation, "skip-signature-validation", false, lang.CmdPackageFlagSkipSignatureValidation) + + return cmd } -var packageMirrorCmd = &cobra.Command{ - Use: "mirror-resources [ PACKAGE_SOURCE ]", - Aliases: []string{"mr"}, - Short: lang.CmdPackageMirrorShort, - Long: lang.CmdPackageMirrorLong, - Example: lang.CmdPackageMirrorExample, - Args: cobra.MaximumNArgs(1), - PreRun: func(_ *cobra.Command, _ []string) { - // If --insecure was provided, set --skip-signature-validation to match - if config.CommonOptions.Insecure { - pkgConfig.PkgOpts.SkipSignatureValidation = true +func (o *PackageInspectOptions) PreRun(_ *cobra.Command, _ []string) { + // If --insecure was provided, set --skip-signature-validation to match + if config.CommonOptions.Insecure { + pkgConfig.PkgOpts.SkipSignatureValidation = true + } +} + +func (o *PackageInspectOptions) Run(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + // NOTE(mkcp): Gets user input with message + src, err := choosePackage(ctx, args) + if err != nil { + return err + } + + cluster, _ := cluster.NewCluster() //nolint:errcheck + inspectOpt := packager2.ZarfInspectOptions{ + Source: src, + SkipSignatureValidation: pkgConfig.PkgOpts.SkipSignatureValidation, + Cluster: cluster, + ListImages: pkgConfig.InspectOpts.ListImages, + ViewSBOM: pkgConfig.InspectOpts.ViewSBOM, + SBOMOutputDir: pkgConfig.InspectOpts.SBOMOutputDir, + PublicKeyPath: pkgConfig.PkgOpts.PublicKeyPath, + } + + if pkgConfig.InspectOpts.ListImages { + output, err := packager2.InspectList(ctx, inspectOpt) + if err != nil { + return fmt.Errorf("failed to inspect package: %w", err) } - }, - RunE: func(cmd *cobra.Command, args []string) (err error) { - ctx := cmd.Context() - var c *cluster.Cluster - if dns.IsServiceURL(pkgConfig.InitOpts.RegistryInfo.Address) || dns.IsServiceURL(pkgConfig.InitOpts.GitServer.Address) { - var err error - c, err = cluster.NewCluster() + for _, image := range output { + _, err := fmt.Fprintln(os.Stdout, "-", image) if err != nil { return err } } - src, err := choosePackage(ctx, args) - if err != nil { - return err - } - filter := filters.Combine( - filters.ByLocalOS(runtime.GOOS), - filters.BySelectState(pkgConfig.PkgOpts.OptionalComponents), - ) - - loadOpt := packager2.LoadOptions{ - Source: src, - Shasum: pkgConfig.PkgOpts.Shasum, - PublicKeyPath: pkgConfig.PkgOpts.PublicKeyPath, - SkipSignatureValidation: pkgConfig.PkgOpts.SkipSignatureValidation, - Filter: filter, - } - pkgLayout, err := packager2.LoadPackage(cmd.Context(), loadOpt) - if err != nil { - return err - } - defer func() { - // Cleanup package files - err = errors.Join(err, pkgLayout.Cleanup()) - }() - - mirrorOpt := packager2.MirrorOptions{ - Cluster: c, - PkgLayout: pkgLayout, - Filter: filter, - RegistryInfo: pkgConfig.InitOpts.RegistryInfo, - GitInfo: pkgConfig.InitOpts.GitServer, - NoImageChecksum: pkgConfig.MirrorOpts.NoImgChecksum, - Retries: pkgConfig.PkgOpts.Retries, - } - err = packager2.Mirror(ctx, mirrorOpt) - if err != nil { - return err - } - return nil - }, + } + + output, err := packager2.Inspect(ctx, inspectOpt) + if err != nil { + return fmt.Errorf("failed to inspect package: %w", err) + } + err = utils.ColorPrintYAML(output, nil, false) + if err != nil { + return err + } + return nil } -var packageInspectCmd = &cobra.Command{ - Use: "inspect [ PACKAGE_SOURCE ]", - Aliases: []string{"i"}, - Short: lang.CmdPackageInspectShort, - Long: lang.CmdPackageInspectLong, - Args: cobra.MaximumNArgs(1), - PreRun: func(_ *cobra.Command, _ []string) { - // If --insecure was provided, set --skip-signature-validation to match - if config.CommonOptions.Insecure { - pkgConfig.PkgOpts.SkipSignatureValidation = true - } - }, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - // NOTE(mkcp): Gets user input with message - src, err := choosePackage(ctx, args) - if err != nil { - return err - } +type PackageListOptions struct{} - cluster, _ := cluster.NewCluster() //nolint:errcheck - inspectOpt := packager2.ZarfInspectOptions{ - Source: src, - SkipSignatureValidation: pkgConfig.PkgOpts.SkipSignatureValidation, - Cluster: cluster, - ListImages: pkgConfig.InspectOpts.ListImages, - ViewSBOM: pkgConfig.InspectOpts.ViewSBOM, - SBOMOutputDir: pkgConfig.InspectOpts.SBOMOutputDir, - PublicKeyPath: pkgConfig.PkgOpts.PublicKeyPath, - } +func NewPackageListCommand() *cobra.Command { + o := &PackageListOptions{} - if pkgConfig.InspectOpts.ListImages { - output, err := packager2.InspectList(ctx, inspectOpt) - if err != nil { - return fmt.Errorf("failed to inspect package: %w", err) - } - for _, image := range output { - _, err := fmt.Fprintln(os.Stdout, "-", image) - if err != nil { - return err - } - } - } + cmd := &cobra.Command{ + Use: "list", + Aliases: []string{"l", "ls"}, + Short: lang.CmdPackageListShort, + RunE: o.Run, + } - output, err := packager2.Inspect(ctx, inspectOpt) - if err != nil { - return fmt.Errorf("failed to inspect package: %w", err) - } - err = utils.ColorPrintYAML(output, nil, false) - if err != nil { - return err - } - return nil - }, + return cmd } -var packageListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"l", "ls"}, - Short: lang.CmdPackageListShort, - RunE: func(cmd *cobra.Command, _ []string) error { - timeoutCtx, cancel := context.WithTimeout(cmd.Context(), cluster.DefaultTimeout) - defer cancel() - c, err := cluster.NewClusterWithWait(timeoutCtx) - if err != nil { - return err - } +func (o *PackageListOptions) Run(cmd *cobra.Command, _ []string) error { + timeoutCtx, cancel := context.WithTimeout(cmd.Context(), cluster.DefaultTimeout) + defer cancel() + c, err := cluster.NewClusterWithWait(timeoutCtx) + if err != nil { + return err + } + + ctx := cmd.Context() + deployedZarfPackages, err := c.GetDeployedZarfPackages(ctx) + if err != nil && len(deployedZarfPackages) == 0 { + return fmt.Errorf("unable to get the packages deployed to the cluster: %w", err) + } + + // Populate a matrix of all the deployed packages + packageData := [][]string{} - ctx := cmd.Context() - deployedZarfPackages, err := c.GetDeployedZarfPackages(ctx) - if err != nil && len(deployedZarfPackages) == 0 { - return fmt.Errorf("unable to get the packages deployed to the cluster: %w", err) + for _, pkg := range deployedZarfPackages { + var components []string + + for _, component := range pkg.DeployedComponents { + components = append(components, component.Name) } - // Populate a matrix of all the deployed packages - packageData := [][]string{} + packageData = append(packageData, []string{ + pkg.Name, pkg.Data.Metadata.Version, fmt.Sprintf("%v", components), + }) + } - for _, pkg := range deployedZarfPackages { - var components []string + header := []string{"Package", "Version", "Components"} + message.TableWithWriter(message.OutputWriter, header, packageData) - for _, component := range pkg.DeployedComponents { - components = append(components, component.Name) - } + // Print out any unmarshalling errors + if err != nil { + return fmt.Errorf("unable to read all of the packages deployed to the cluster: %w", err) + } + return nil +} - packageData = append(packageData, []string{ - pkg.Name, pkg.Data.Metadata.Version, fmt.Sprintf("%v", components), - }) - } +type PackageRemoveOptions struct{} - header := []string{"Package", "Version", "Components"} - message.TableWithWriter(message.OutputWriter, header, packageData) +func NewPackageRemoveCommand(v *viper.Viper) *cobra.Command { + o := &PackageRemoveOptions{} - // Print out any unmarshalling errors - if err != nil { - return fmt.Errorf("unable to read all of the packages deployed to the cluster: %w", err) - } - return nil - }, + cmd := &cobra.Command{ + Use: "remove { PACKAGE_SOURCE | PACKAGE_NAME } --confirm", + Aliases: []string{"u", "rm"}, + Args: cobra.MaximumNArgs(1), + Short: lang.CmdPackageRemoveShort, + PreRun: o.PreRun, + RunE: o.Run, + ValidArgsFunction: getPackageCompletionArgs, + } + + cmd.Flags().BoolVar(&config.CommonOptions.Confirm, "confirm", false, lang.CmdPackageRemoveFlagConfirm) + _ = cmd.MarkFlagRequired("confirm") + cmd.Flags().StringVar(&pkgConfig.PkgOpts.OptionalComponents, "components", v.GetString(common.VPkgDeployComponents), lang.CmdPackageRemoveFlagComponents) + cmd.Flags().BoolVar(&pkgConfig.PkgOpts.SkipSignatureValidation, "skip-signature-validation", false, lang.CmdPackageFlagSkipSignatureValidation) + + return cmd } -var packageRemoveCmd = &cobra.Command{ - Use: "remove { PACKAGE_SOURCE | PACKAGE_NAME } --confirm", - Aliases: []string{"u", "rm"}, - Args: cobra.MaximumNArgs(1), - Short: lang.CmdPackageRemoveShort, - PreRun: func(_ *cobra.Command, _ []string) { - // If --insecure was provided, set --skip-signature-validation to match - if config.CommonOptions.Insecure { - pkgConfig.PkgOpts.SkipSignatureValidation = true - } - }, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - packageSource, err := choosePackage(ctx, args) - if err != nil { - return err - } - filter := filters.Combine( - filters.ByLocalOS(runtime.GOOS), - filters.BySelectState(pkgConfig.PkgOpts.OptionalComponents), - ) - cluster, _ := cluster.NewCluster() //nolint:errcheck - removeOpt := packager2.RemoveOptions{ - Source: packageSource, - Cluster: cluster, - Filter: filter, - SkipSignatureValidation: pkgConfig.PkgOpts.SkipSignatureValidation, - PublicKeyPath: pkgConfig.PkgOpts.PublicKeyPath, - } - err = packager2.Remove(ctx, removeOpt) - if err != nil { - return err - } - return nil - }, - ValidArgsFunction: getPackageCompletionArgs, +func (o *PackageRemoveOptions) PreRun(_ *cobra.Command, _ []string) { + // If --insecure was provided, set --skip-signature-validation to match + if config.CommonOptions.Insecure { + pkgConfig.PkgOpts.SkipSignatureValidation = true + } } -var packagePublishCmd = &cobra.Command{ - Use: "publish { PACKAGE_SOURCE | SKELETON DIRECTORY } REPOSITORY", - Short: lang.CmdPackagePublishShort, - Example: lang.CmdPackagePublishExample, - Args: cobra.ExactArgs(2), - PreRun: func(_ *cobra.Command, _ []string) { - // If --insecure was provided, set --skip-signature-validation to match - if config.CommonOptions.Insecure { - pkgConfig.PkgOpts.SkipSignatureValidation = true - } - }, - RunE: func(cmd *cobra.Command, args []string) error { - pkgConfig.PkgOpts.PackageSource = args[0] +func (o *PackageRemoveOptions) Run(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + packageSource, err := choosePackage(ctx, args) + if err != nil { + return err + } + filter := filters.Combine( + filters.ByLocalOS(runtime.GOOS), + filters.BySelectState(pkgConfig.PkgOpts.OptionalComponents), + ) + cluster, _ := cluster.NewCluster() //nolint:errcheck + removeOpt := packager2.RemoveOptions{ + Source: packageSource, + Cluster: cluster, + Filter: filter, + SkipSignatureValidation: pkgConfig.PkgOpts.SkipSignatureValidation, + PublicKeyPath: pkgConfig.PkgOpts.PublicKeyPath, + } + err = packager2.Remove(ctx, removeOpt) + if err != nil { + return err + } + return nil +} - if !helpers.IsOCIURL(args[1]) { - return errors.New("Registry must be prefixed with 'oci://'") - } - parts := strings.Split(strings.TrimPrefix(args[1], helpers.OCIURLPrefix), "/") - ref := registry.Reference{ - Registry: parts[0], - Repository: strings.Join(parts[1:], "/"), - } - err := ref.ValidateRegistry() - if err != nil { - return err - } +type PackagePublishOptions struct{} - if helpers.IsDir(pkgConfig.PkgOpts.PackageSource) { - pkgConfig.CreateOpts.BaseDir = pkgConfig.PkgOpts.PackageSource - pkgConfig.CreateOpts.IsSkeleton = true - } +func NewPackagePublishCommand(v *viper.Viper) *cobra.Command { + o := &PackagePublishOptions{} - pkgConfig.PublishOpts.PackageDestination = ref.String() + cmd := &cobra.Command{ + Use: "publish { PACKAGE_SOURCE | SKELETON DIRECTORY } REPOSITORY", + Short: lang.CmdPackagePublishShort, + Example: lang.CmdPackagePublishExample, + Args: cobra.ExactArgs(2), + PreRun: o.PreRun, + RunE: o.Run, + } - pkgClient, err := packager.New(&pkgConfig, packager.WithContext(cmd.Context())) - if err != nil { - return err - } - defer pkgClient.ClearTempPaths() + cmd.Flags().StringVar(&pkgConfig.PublishOpts.SigningKeyPath, "signing-key", v.GetString(common.VPkgPublishSigningKey), lang.CmdPackagePublishFlagSigningKey) + cmd.Flags().StringVar(&pkgConfig.PublishOpts.SigningKeyPassword, "signing-key-pass", v.GetString(common.VPkgPublishSigningKeyPassword), lang.CmdPackagePublishFlagSigningKeyPassword) + cmd.Flags().BoolVar(&pkgConfig.PkgOpts.SkipSignatureValidation, "skip-signature-validation", false, lang.CmdPackageFlagSkipSignatureValidation) - if err := pkgClient.Publish(cmd.Context()); err != nil { - return fmt.Errorf("failed to publish package: %w", err) - } - return nil - }, + return cmd } -var packagePullCmd = &cobra.Command{ - Use: "pull PACKAGE_SOURCE", - Short: lang.CmdPackagePullShort, - Example: lang.CmdPackagePullExample, - Args: cobra.ExactArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - outputDir := pkgConfig.PullOpts.OutputDirectory - if outputDir == "" { - wd, err := os.Getwd() - if err != nil { - return err - } - outputDir = wd - } - err := packager2.Pull(cmd.Context(), args[0], outputDir, pkgConfig.PkgOpts.Shasum, filters.Empty()) +func (o *PackagePublishOptions) PreRun(_ *cobra.Command, _ []string) { + // If --insecure was provided, set --skip-signature-validation to match + if config.CommonOptions.Insecure { + pkgConfig.PkgOpts.SkipSignatureValidation = true + } +} + +func (o *PackagePublishOptions) Run(cmd *cobra.Command, args []string) error { + pkgConfig.PkgOpts.PackageSource = args[0] + + if !helpers.IsOCIURL(args[1]) { + return errors.New("Registry must be prefixed with 'oci://'") + } + parts := strings.Split(strings.TrimPrefix(args[1], helpers.OCIURLPrefix), "/") + ref := registry.Reference{ + Registry: parts[0], + Repository: strings.Join(parts[1:], "/"), + } + err := ref.ValidateRegistry() + if err != nil { + return err + } + + if helpers.IsDir(pkgConfig.PkgOpts.PackageSource) { + pkgConfig.CreateOpts.BaseDir = pkgConfig.PkgOpts.PackageSource + pkgConfig.CreateOpts.IsSkeleton = true + } + + pkgConfig.PublishOpts.PackageDestination = ref.String() + + pkgClient, err := packager.New(&pkgConfig, packager.WithContext(cmd.Context())) + if err != nil { + return err + } + defer pkgClient.ClearTempPaths() + + if err := pkgClient.Publish(cmd.Context()); err != nil { + return fmt.Errorf("failed to publish package: %w", err) + } + return nil +} + +type PackagePullOptions struct{} + +func NewPackagePullCommand(v *viper.Viper) *cobra.Command { + o := &PackagePullOptions{} + + cmd := &cobra.Command{ + Use: "pull PACKAGE_SOURCE", + Short: lang.CmdPackagePullShort, + Example: lang.CmdPackagePullExample, + Args: cobra.ExactArgs(1), + RunE: o.Run, + } + + cmd.Flags().StringVar(&pkgConfig.PkgOpts.Shasum, "shasum", "", lang.CmdPackagePullFlagShasum) + cmd.Flags().StringVarP(&pkgConfig.PullOpts.OutputDirectory, "output-directory", "o", v.GetString(common.VPkgPullOutputDir), lang.CmdPackagePullFlagOutputDirectory) + + return cmd +} + +func (o *PackagePullOptions) Run(cmd *cobra.Command, args []string) error { + outputDir := pkgConfig.PullOpts.OutputDirectory + if outputDir == "" { + wd, err := os.Getwd() if err != nil { return err } - return nil - }, + outputDir = wd + } + err := packager2.Pull(cmd.Context(), args[0], outputDir, pkgConfig.PkgOpts.Shasum, filters.Empty()) + if err != nil { + return err + } + return nil } func choosePackage(ctx context.Context, args []string) (string, error) { @@ -457,158 +666,3 @@ func getPackageCompletionArgs(cmd *cobra.Command, _ []string, _ string) ([]strin return pkgCandidates, cobra.ShellCompDirectiveDefault } - -func init() { - v := common.InitViper() - - rootCmd.AddCommand(packageCmd) - packageCmd.AddCommand(packageCreateCmd) - packageCmd.AddCommand(packageDeployCmd) - packageCmd.AddCommand(packageMirrorCmd) - packageCmd.AddCommand(packageInspectCmd) - packageCmd.AddCommand(packageRemoveCmd) - packageCmd.AddCommand(packageListCmd) - packageCmd.AddCommand(packagePublishCmd) - packageCmd.AddCommand(packagePullCmd) - - bindPackageFlags(v) - bindCreateFlags(v) - bindDeployFlags(v) - bindMirrorFlags(v) - bindInspectFlags(v) - bindRemoveFlags(v) - bindPublishFlags(v) - bindPullFlags(v) -} - -func bindPackageFlags(v *viper.Viper) { - packageFlags := packageCmd.PersistentFlags() - packageFlags.IntVar(&config.CommonOptions.OCIConcurrency, "oci-concurrency", v.GetInt(common.VPkgOCIConcurrency), lang.CmdPackageFlagConcurrency) - packageFlags.StringVarP(&pkgConfig.PkgOpts.PublicKeyPath, "key", "k", v.GetString(common.VPkgPublicKey), lang.CmdPackageFlagFlagPublicKey) -} - -func bindCreateFlags(v *viper.Viper) { - createFlags := packageCreateCmd.Flags() - - // Always require confirm flag (no viper) - createFlags.BoolVar(&config.CommonOptions.Confirm, "confirm", false, lang.CmdPackageCreateFlagConfirm) - - outputDirectory := v.GetString("package.create.output_directory") - output := v.GetString(common.VPkgCreateOutput) - if outputDirectory != "" && output == "" { - v.Set(common.VPkgCreateOutput, outputDirectory) - } - createFlags.StringVar(&pkgConfig.CreateOpts.Output, "output-directory", v.GetString("package.create.output_directory"), lang.CmdPackageCreateFlagOutput) - createFlags.StringVarP(&pkgConfig.CreateOpts.Output, "output", "o", v.GetString(common.VPkgCreateOutput), lang.CmdPackageCreateFlagOutput) - - createFlags.StringVar(&pkgConfig.CreateOpts.DifferentialPackagePath, "differential", v.GetString(common.VPkgCreateDifferential), lang.CmdPackageCreateFlagDifferential) - createFlags.StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdPackageCreateFlagSet) - createFlags.BoolVarP(&pkgConfig.CreateOpts.ViewSBOM, "sbom", "s", v.GetBool(common.VPkgCreateSbom), lang.CmdPackageCreateFlagSbom) - createFlags.StringVar(&pkgConfig.CreateOpts.SBOMOutputDir, "sbom-out", v.GetString(common.VPkgCreateSbomOutput), lang.CmdPackageCreateFlagSbomOut) - createFlags.BoolVar(&pkgConfig.CreateOpts.SkipSBOM, "skip-sbom", v.GetBool(common.VPkgCreateSkipSbom), lang.CmdPackageCreateFlagSkipSbom) - createFlags.IntVarP(&pkgConfig.CreateOpts.MaxPackageSizeMB, "max-package-size", "m", v.GetInt(common.VPkgCreateMaxPackageSize), lang.CmdPackageCreateFlagMaxPackageSize) - createFlags.StringToStringVar(&pkgConfig.CreateOpts.RegistryOverrides, "registry-override", v.GetStringMapString(common.VPkgCreateRegistryOverride), lang.CmdPackageCreateFlagRegistryOverride) - createFlags.StringVarP(&pkgConfig.CreateOpts.Flavor, "flavor", "f", v.GetString(common.VPkgCreateFlavor), lang.CmdPackageCreateFlagFlavor) - - createFlags.StringVar(&pkgConfig.CreateOpts.SigningKeyPath, "signing-key", v.GetString(common.VPkgCreateSigningKey), lang.CmdPackageCreateFlagSigningKey) - createFlags.StringVar(&pkgConfig.CreateOpts.SigningKeyPassword, "signing-key-pass", v.GetString(common.VPkgCreateSigningKeyPassword), lang.CmdPackageCreateFlagSigningKeyPassword) - - createFlags.StringVarP(&pkgConfig.CreateOpts.SigningKeyPath, "key", "k", v.GetString(common.VPkgCreateSigningKey), lang.CmdPackageCreateFlagDeprecatedKey) - createFlags.StringVar(&pkgConfig.CreateOpts.SigningKeyPassword, "key-pass", v.GetString(common.VPkgCreateSigningKeyPassword), lang.CmdPackageCreateFlagDeprecatedKeyPassword) - - createFlags.IntVar(&pkgConfig.PkgOpts.Retries, "retries", v.GetInt(common.VPkgRetries), lang.CmdPackageFlagRetries) - - errOD := createFlags.MarkHidden("output-directory") - if errOD != nil { - logger.Default().Debug("unable to mark flag output-directory", "error", errOD) - } - errKey := createFlags.MarkHidden("key") - if errKey != nil { - logger.Default().Debug("unable to mark flag key", "error", errKey) - } - errKP := createFlags.MarkHidden("key-pass") - if errKP != nil { - logger.Default().Debug("unable to mark flag key-pass", "error", errKP) - } -} - -func bindDeployFlags(v *viper.Viper) { - deployFlags := packageDeployCmd.Flags() - - // Always require confirm flag (no viper) - deployFlags.BoolVar(&config.CommonOptions.Confirm, "confirm", false, lang.CmdPackageDeployFlagConfirm) - - // Always require adopt-existing-resources flag (no viper) - deployFlags.BoolVar(&pkgConfig.DeployOpts.AdoptExistingResources, "adopt-existing-resources", false, lang.CmdPackageDeployFlagAdoptExistingResources) - deployFlags.DurationVar(&pkgConfig.DeployOpts.Timeout, "timeout", v.GetDuration(common.VPkgDeployTimeout), lang.CmdPackageDeployFlagTimeout) - - deployFlags.IntVar(&pkgConfig.PkgOpts.Retries, "retries", v.GetInt(common.VPkgRetries), lang.CmdPackageFlagRetries) - deployFlags.StringToStringVar(&pkgConfig.PkgOpts.SetVariables, "set", v.GetStringMapString(common.VPkgDeploySet), lang.CmdPackageDeployFlagSet) - deployFlags.StringVar(&pkgConfig.PkgOpts.OptionalComponents, "components", v.GetString(common.VPkgDeployComponents), lang.CmdPackageDeployFlagComponents) - deployFlags.StringVar(&pkgConfig.PkgOpts.Shasum, "shasum", v.GetString(common.VPkgDeployShasum), lang.CmdPackageDeployFlagShasum) - deployFlags.StringVar(&pkgConfig.PkgOpts.SGetKeyPath, "sget", v.GetString(common.VPkgDeploySget), lang.CmdPackageDeployFlagSget) - deployFlags.BoolVar(&pkgConfig.PkgOpts.SkipSignatureValidation, "skip-signature-validation", false, lang.CmdPackageFlagSkipSignatureValidation) - - err := deployFlags.MarkHidden("sget") - if err != nil { - logger.Default().Debug("unable to mark flag sget", "error", err) - } -} - -func bindMirrorFlags(v *viper.Viper) { - mirrorFlags := packageMirrorCmd.Flags() - - // Init package variable defaults that are non-zero values - // NOTE: these are not in common.setDefaults so that zarf tools update-creds does not erroneously update values back to the default - v.SetDefault(common.VInitGitPushUser, types.ZarfGitPushUser) - v.SetDefault(common.VInitRegistryPushUser, types.ZarfRegistryPushUser) - - // Always require confirm flag (no viper) - mirrorFlags.BoolVar(&config.CommonOptions.Confirm, "confirm", false, lang.CmdPackageDeployFlagConfirm) - - mirrorFlags.StringVar(&pkgConfig.PkgOpts.Shasum, "shasum", "", lang.CmdPackagePullFlagShasum) - mirrorFlags.BoolVar(&pkgConfig.MirrorOpts.NoImgChecksum, "no-img-checksum", false, lang.CmdPackageMirrorFlagNoChecksum) - mirrorFlags.BoolVar(&pkgConfig.PkgOpts.SkipSignatureValidation, "skip-signature-validation", false, lang.CmdPackageFlagSkipSignatureValidation) - - mirrorFlags.IntVar(&pkgConfig.PkgOpts.Retries, "retries", v.GetInt(common.VPkgRetries), lang.CmdPackageFlagRetries) - mirrorFlags.StringVar(&pkgConfig.PkgOpts.OptionalComponents, "components", v.GetString(common.VPkgDeployComponents), lang.CmdPackageMirrorFlagComponents) - - // Flags for using an external Git server - mirrorFlags.StringVar(&pkgConfig.InitOpts.GitServer.Address, "git-url", v.GetString(common.VInitGitURL), lang.CmdInitFlagGitURL) - mirrorFlags.StringVar(&pkgConfig.InitOpts.GitServer.PushUsername, "git-push-username", v.GetString(common.VInitGitPushUser), lang.CmdInitFlagGitPushUser) - mirrorFlags.StringVar(&pkgConfig.InitOpts.GitServer.PushPassword, "git-push-password", v.GetString(common.VInitGitPushPass), lang.CmdInitFlagGitPushPass) - - // Flags for using an external registry - mirrorFlags.StringVar(&pkgConfig.InitOpts.RegistryInfo.Address, "registry-url", v.GetString(common.VInitRegistryURL), lang.CmdInitFlagRegURL) - mirrorFlags.StringVar(&pkgConfig.InitOpts.RegistryInfo.PushUsername, "registry-push-username", v.GetString(common.VInitRegistryPushUser), lang.CmdInitFlagRegPushUser) - mirrorFlags.StringVar(&pkgConfig.InitOpts.RegistryInfo.PushPassword, "registry-push-password", v.GetString(common.VInitRegistryPushPass), lang.CmdInitFlagRegPushPass) -} - -func bindInspectFlags(_ *viper.Viper) { - inspectFlags := packageInspectCmd.Flags() - inspectFlags.BoolVarP(&pkgConfig.InspectOpts.ViewSBOM, "sbom", "s", false, lang.CmdPackageInspectFlagSbom) - inspectFlags.StringVar(&pkgConfig.InspectOpts.SBOMOutputDir, "sbom-out", "", lang.CmdPackageInspectFlagSbomOut) - inspectFlags.BoolVar(&pkgConfig.InspectOpts.ListImages, "list-images", false, lang.CmdPackageInspectFlagListImages) - inspectFlags.BoolVar(&pkgConfig.PkgOpts.SkipSignatureValidation, "skip-signature-validation", false, lang.CmdPackageFlagSkipSignatureValidation) -} - -func bindRemoveFlags(v *viper.Viper) { - removeFlags := packageRemoveCmd.Flags() - removeFlags.BoolVar(&config.CommonOptions.Confirm, "confirm", false, lang.CmdPackageRemoveFlagConfirm) - removeFlags.StringVar(&pkgConfig.PkgOpts.OptionalComponents, "components", v.GetString(common.VPkgDeployComponents), lang.CmdPackageRemoveFlagComponents) - removeFlags.BoolVar(&pkgConfig.PkgOpts.SkipSignatureValidation, "skip-signature-validation", false, lang.CmdPackageFlagSkipSignatureValidation) - _ = packageRemoveCmd.MarkFlagRequired("confirm") -} - -func bindPublishFlags(v *viper.Viper) { - publishFlags := packagePublishCmd.Flags() - publishFlags.StringVar(&pkgConfig.PublishOpts.SigningKeyPath, "signing-key", v.GetString(common.VPkgPublishSigningKey), lang.CmdPackagePublishFlagSigningKey) - publishFlags.StringVar(&pkgConfig.PublishOpts.SigningKeyPassword, "signing-key-pass", v.GetString(common.VPkgPublishSigningKeyPassword), lang.CmdPackagePublishFlagSigningKeyPassword) - publishFlags.BoolVar(&pkgConfig.PkgOpts.SkipSignatureValidation, "skip-signature-validation", false, lang.CmdPackageFlagSkipSignatureValidation) -} - -func bindPullFlags(v *viper.Viper) { - pullFlags := packagePullCmd.Flags() - pullFlags.StringVar(&pkgConfig.PkgOpts.Shasum, "shasum", "", lang.CmdPackagePullFlagShasum) - pullFlags.StringVarP(&pkgConfig.PullOpts.OutputDirectory, "output-directory", "o", v.GetString(common.VPkgPullOutputDir), lang.CmdPackagePullFlagOutputDirectory) -} diff --git a/src/cmd/root.go b/src/cmd/root.go index 3d045ab177..3ba69c02e9 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -44,17 +44,7 @@ var ( OutputWriter = os.Stdout ) -var rootCmd = &cobra.Command{ - Use: "zarf COMMAND", - Short: lang.RootCmdShort, - Long: lang.RootCmdLong, - Args: cobra.MaximumNArgs(1), - SilenceUsage: true, - // TODO(mkcp): Do we actually want to silence errors here? - SilenceErrors: true, - PersistentPreRunE: preRun, - Run: run, -} +var rootCmd = NewZarfCommand() func preRun(cmd *cobra.Command, _ []string) error { // If --insecure was provided, set --insecure-skip-tls-verify and --plain-http to match @@ -126,6 +116,31 @@ func run(cmd *cobra.Command, _ []string) { } } +// NewZarfCommand creates the `zarf` command and its nested children. +func NewZarfCommand() *cobra.Command { + rootCmd := &cobra.Command{ + Use: "zarf COMMAND", + Short: lang.RootCmdShort, + Long: lang.RootCmdLong, + Args: cobra.MaximumNArgs(1), + SilenceUsage: true, + // TODO(mkcp): Do we actually want to silence errors here? + SilenceErrors: true, + PersistentPreRunE: preRun, + Run: run, + } + + // TODO(soltysh): consider adding command groups + rootCmd.AddCommand(NewConnectCommand()) + rootCmd.AddCommand(NewDestroyCommand()) + rootCmd.AddCommand(NewDevCommand()) + rootCmd.AddCommand(NewInitCommand()) + rootCmd.AddCommand(NewInternalCommand(rootCmd)) + rootCmd.AddCommand(NewPackageCommand()) + + return rootCmd +} + // Execute is the entrypoint for the CLI. func Execute(ctx context.Context) { // Add `zarf say`