From cc232ed825d0a40806b97e17abd58efb1ef613ea Mon Sep 17 00:00:00 2001 From: Frank van Lankvelt Date: Mon, 31 Aug 2026 14:00:45 +0200 Subject: [PATCH 1/4] STAC-24987: release stackpacks 2 --- .../otelcomponentmapping_delete.go | 2 +- cmd/stackpack.go | 18 +--- cmd/stackpack/stackpack_package.go | 4 +- ...deploy_cmd.go => stackpack_test_deploy.go} | 80 +++++++++-------- ..._test.go => stackpack_test_deploy_test.go} | 2 +- cmd/stackpack/stackpack_validate.go | 14 ++- cmd/stackpack/stackpack_validate_test.go | 16 ++-- cmd/stackpack_test.go | 85 ++----------------- cmd/sts.go | 10 +-- cmd/sts_test.go | 4 +- 10 files changed, 78 insertions(+), 157 deletions(-) rename cmd/stackpack/{stackpack_test_deploy_cmd.go => stackpack_test_deploy.go} (87%) rename cmd/stackpack/{stackpack_test_deploy_cmd_test.go => stackpack_test_deploy_test.go} (99%) diff --git a/cmd/otelcomponentmapping/otelcomponentmapping_delete.go b/cmd/otelcomponentmapping/otelcomponentmapping_delete.go index 006b8805..45d988af 100644 --- a/cmd/otelcomponentmapping/otelcomponentmapping_delete.go +++ b/cmd/otelcomponentmapping/otelcomponentmapping_delete.go @@ -18,7 +18,7 @@ func OtelComponentMappingDeleteCommand(deps *di.Deps) *cobra.Command { cmd := &cobra.Command{ Use: "delete", Short: "Delete an OTel Component Mapping by identifier (URN)", - Long: "Delete an OTel Component Mapping by identifier (URN)", + Long: "Delete an OTel Component Mapping by identifier (URN).", Example: `# delete a component mapping by identifier sts otel-component-mapping delete --identifier urn:stackpack:stackpack-name:shared:otel-component-mapping:service`, RunE: deps.CmdRunEWithApi(RunDeleteComponentMappingCommand(args)), diff --git a/cmd/stackpack.go b/cmd/stackpack.go index a3db63cd..9201317f 100644 --- a/cmd/stackpack.go +++ b/cmd/stackpack.go @@ -1,17 +1,11 @@ package cmd import ( - "os" - "github.com/spf13/cobra" "github.com/stackvista/stackstate-cli/cmd/stackpack" "github.com/stackvista/stackstate-cli/internal/di" ) -const ( - experimentalStackpackEnvVar = "STS_EXPERIMENTAL_STACKPACKS" -) - func StackPackCommand(cli *di.Deps) *cobra.Command { cmd := &cobra.Command{ Use: "stackpack", @@ -31,14 +25,10 @@ func StackPackCommand(cli *di.Deps) *cobra.Command { cmd.AddCommand(stackpack.StackpackListVersionsCommand(cli)) cmd.AddCommand(stackpack.StackpackDeleteVersionCommand(cli)) cmd.AddCommand(stackpack.StackpackDeleteVersionsCommand(cli)) - - // The not-production-ready commands - if os.Getenv(experimentalStackpackEnvVar) != "" { - cmd.AddCommand(stackpack.StackpackScaffoldCommand(cli)) - cmd.AddCommand(stackpack.StackpackPackageCommand(cli)) - cmd.AddCommand(stackpack.StackpackTestDeployCommand(cli)) - cmd.AddCommand(stackpack.StackpackValidateCommand(cli)) - } + cmd.AddCommand(stackpack.StackpackScaffoldCommand(cli)) + cmd.AddCommand(stackpack.StackpackPackageCommand(cli)) + cmd.AddCommand(stackpack.StackpackTestDeployCommand(cli)) + cmd.AddCommand(stackpack.StackpackValidateCommand(cli)) return cmd } diff --git a/cmd/stackpack/stackpack_package.go b/cmd/stackpack/stackpack_package.go index c1861fd6..85d69ec7 100644 --- a/cmd/stackpack/stackpack_package.go +++ b/cmd/stackpack/stackpack_package.go @@ -109,8 +109,8 @@ sts stackpack package --force`, RunE: cli.CmdRunE(RunStackpackPackageCommand(args)), } - cmd.Flags().StringVarP(&args.StackpackDir, "stackpack-directory", "d", "", "Path to stackpack directory (defaults to current directory)") - cmd.Flags().StringVarP(&args.ArchiveFile, "archive-file", "f", "", "Path to the .sts file to create (defaults to -.sts in current directory)") + cmd.Flags().StringVarP(&args.StackpackDir, "directory", "d", "", "Path to stackpack directory (defaults to current directory)") + cmd.Flags().StringVarP(&args.ArchiveFile, "file", "f", "", "Path to the .sts file to create (defaults to -.sts in current directory)") cmd.Flags().BoolVar(&args.Force, "force", false, "Overwrite existing .sts file without prompting") return cmd diff --git a/cmd/stackpack/stackpack_test_deploy_cmd.go b/cmd/stackpack/stackpack_test_deploy.go similarity index 87% rename from cmd/stackpack/stackpack_test_deploy_cmd.go rename to cmd/stackpack/stackpack_test_deploy.go index 3f19243f..1cf48fce 100644 --- a/cmd/stackpack/stackpack_test_deploy_cmd.go +++ b/cmd/stackpack/stackpack_test_deploy.go @@ -59,7 +59,7 @@ sts stackpack test-deploy -d ./my-stackpack --yes`, RunE: cli.CmdRunEWithApi(RunStackpackTestDeployCommand(args)), } - cmd.Flags().StringVarP(&args.StackpackDir, "stackpack-directory", "d", "", "Path to stackpack directory (defaults to current directory)") + cmd.Flags().StringVarP(&args.StackpackDir, "directory", "d", "", "Path to stackpack directory (defaults to current directory)") cmd.Flags().StringToStringVarP(&args.Params, ParameterFlag, "p", args.Params, "List of parameters of the form \"key=value\"") cmd.Flags().BoolVarP(&args.Yes, "yes", "y", false, "Skip confirmation prompt before upload") @@ -76,10 +76,6 @@ func RunStackpackTestDeployCommand(args *TestDeployArgs) di.CmdWithApiFn { api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo, ) common.CLIError { - // Warn if JSON output is requested - not meaningful for test-deploy command - if cli.IsJson() { - cli.Printer.PrintLn("Warning: JSON output format is not meaningful for the test-deploy command, proceeding with text output") - } // Set default stackpack directory if args.StackpackDir == "" { @@ -98,12 +94,12 @@ func RunStackpackTestDeployCommand(args *TestDeployArgs) di.CmdWithApiFn { return common.NewRuntimeError(fmt.Errorf("failed to parse %s: %w", stackpackConfigFile, err)) } - cli.Printer.Success("Starting stackpack test-deploy sequence...") - cli.Printer.PrintLn(fmt.Sprintf(" Stackpack: %s (current version: %s)", originalInfo.Name, originalInfo.Version)) - cli.Printer.PrintLn("") + printSuccess(cli, "Starting stackpack test-deploy sequence...") + printMsg(cli, fmt.Sprintf(" Stackpack: %s (current version: %s)", originalInfo.Name, originalInfo.Version)) + printMsg(cli, "") // Step 1: Check installed version and determine base version for snapshot - cli.Printer.PrintLn("Step 1/5: Checking installed version...") + printMsg(cli, "Step 1/5: Checking installed version...") installedVersion, err := getInstalledStackpackVersion(cli, api, originalInfo.Name) if err != nil { return common.NewRuntimeError(fmt.Errorf("failed to check installed stackpack version: %w", err)) @@ -111,7 +107,7 @@ func RunStackpackTestDeployCommand(args *TestDeployArgs) di.CmdWithApiFn { baseVersionForSnapshot := originalInfo.Version if installedVersion != "" { - cli.Printer.PrintLn(fmt.Sprintf(" Found installed version: %s", installedVersion)) + printMsg(cli, fmt.Sprintf(" Found installed version: %s", installedVersion)) // Compare base versions (strip cli-test suffix from installed version for comparison) installedBaseVersion := installedVersion @@ -129,15 +125,15 @@ func RunStackpackTestDeployCommand(args *TestDeployArgs) di.CmdWithApiFn { baseComparison := compareVersions(installedBaseVersion, originalInfo.Version) if baseComparison > 0 || (baseComparison == 0 && strings.Contains(installedVersion, "-cli-test.")) { baseVersionForSnapshot = installedVersion - cli.Printer.PrintLn(fmt.Sprintf(" Using installed version as base: %s", baseVersionForSnapshot)) + printMsg(cli, fmt.Sprintf(" Using installed version as base: %s", baseVersionForSnapshot)) } else if baseComparison < 0 { - cli.Printer.PrintLn(fmt.Sprintf(" Using local version as base (higher than installed): %s", baseVersionForSnapshot)) + printMsg(cli, fmt.Sprintf(" Using local version as base (higher than installed): %s", baseVersionForSnapshot)) } } // Step 2: Create temporary directory and copy stackpack - cli.Printer.PrintLn("") - cli.Printer.PrintLn("Step 2/5: Creating temporary copy for testing...") + printMsg(cli, "") + printMsg(cli, "Step 2/5: Creating temporary copy for testing...") tempDir, err := os.MkdirTemp("", "stackpack-test-*") if err != nil { @@ -147,7 +143,7 @@ func RunStackpackTestDeployCommand(args *TestDeployArgs) di.CmdWithApiFn { // Ensure cleanup of temporary directory defer func() { if removeErr := os.RemoveAll(tempDir); removeErr != nil { - cli.Printer.PrintLn(fmt.Sprintf("Warning: Failed to cleanup temporary directory: %v", removeErr)) + printMsg(cli, fmt.Sprintf("Warning: Failed to cleanup temporary directory: %v", removeErr)) } }() @@ -155,22 +151,22 @@ func RunStackpackTestDeployCommand(args *TestDeployArgs) di.CmdWithApiFn { if err := copyDirectory(args.StackpackDir, tempStackpackDir); err != nil { return common.NewRuntimeError(fmt.Errorf("failed to copy stackpack to temporary directory: %w", err)) } - cli.Printer.Success("Temporary copy created") + printSuccess(cli, "Temporary copy created") // Step 3: Update version in temporary copy - cli.Printer.PrintLn("") - cli.Printer.PrintLn("Step 3/5: Bumping version for testing...") + printMsg(cli, "") + printMsg(cli, "Step 3/5: Bumping version for testing...") tempConfigPath := filepath.Join(tempStackpackDir, stackpackConfigFile) newVersion, err := bumpSnapshotVersionWithBase(tempConfigPath, baseVersionForSnapshot) if err != nil { return common.NewRuntimeError(fmt.Errorf("failed to bump version: %w", err)) } - cli.Printer.Success(fmt.Sprintf("Version bumped to: %s", newVersion)) + printSuccess(cli, fmt.Sprintf("Version bumped to: %s", newVersion)) // Step 4: Package stackpack from temporary directory - cli.Printer.PrintLn("") - cli.Printer.PrintLn("Step 4/5: Packaging stackpack...") + printMsg(cli, "") + printMsg(cli, "Step 4/5: Packaging stackpack...") packageArgs := &PackageArgs{ StackpackDir: tempStackpackDir, // Use temporary directory Force: true, // Always overwrite for testing @@ -183,19 +179,19 @@ func RunStackpackTestDeployCommand(args *TestDeployArgs) di.CmdWithApiFn { if err := runPackageStep(cli, packageArgs); err != nil { return err } - cli.Printer.Success("Stackpack packaged successfully") + printSuccess(cli, "Stackpack packaged successfully") // Step 5: Confirm upload (if needed) and execute upload/install workflow - if !args.Yes { - cli.Printer.PrintLn("") + if !args.Yes && !cli.IsJson() { + printMsg(cli, "") if !confirmUpload(cli, packageArgs.ArchiveFile) { return common.NewRuntimeError(fmt.Errorf("upload cancelled by user")) } } // Upload stackpack - cli.Printer.PrintLn("") - cli.Printer.PrintLn("Step 5/5: Uploading and installing/upgrading stackpack...") + printMsg(cli, "") + printMsg(cli, "Step 5/5: Uploading and installing/upgrading stackpack...") uploadArgs := &UploadArgs{ FilePath: packageArgs.ArchiveFile, } @@ -203,7 +199,7 @@ func RunStackpackTestDeployCommand(args *TestDeployArgs) di.CmdWithApiFn { if err := runUploadStep(cli, api, serverInfo, uploadArgs); err != nil { return err } - cli.Printer.Success("Stackpack uploaded successfully") + printSuccess(cli, "Stackpack uploaded successfully") // Install or upgrade stackpack based on installation status if installedVersion != "" { @@ -217,7 +213,7 @@ func RunStackpackTestDeployCommand(args *TestDeployArgs) di.CmdWithApiFn { if err := runUpgradeStep(cli, api, serverInfo, upgradeArgs); err != nil { return err } - cli.Printer.Success("Stackpack upgraded successfully") + printSuccess(cli, "Stackpack upgraded successfully") } else { installArgs := &InstallArgs{ Name: originalInfo.Name, @@ -230,15 +226,19 @@ func RunStackpackTestDeployCommand(args *TestDeployArgs) di.CmdWithApiFn { if err := runInstallStep(cli, api, serverInfo, installArgs); err != nil { return err } - cli.Printer.Success("Stackpack installed successfully") + printSuccess(cli, "Stackpack installed successfully") } - cli.Printer.PrintLn("") - cli.Printer.Success("🎉 Test-deploy sequence completed successfully!") + printMsg(cli, "") + printSuccess(cli, "🎉 Test-deploy sequence completed successfully!") // Clean up .sts file if err := os.Remove(packageArgs.ArchiveFile); err != nil { - cli.Printer.PrintLn(fmt.Sprintf("Note: Could not clean up .sts file %s: %v", packageArgs.ArchiveFile, err)) + printMsg(cli, fmt.Sprintf("Note: Could not clean up .sts file %s: %v", packageArgs.ArchiveFile, err)) + } else if cli.IsJson() { + cli.Printer.PrintJson(map[string]interface{}{ + "version": baseVersionForSnapshot, + }) } return nil @@ -315,8 +315,8 @@ func confirmUpload(cli *di.Deps, zipFile string) bool { serverURL = cli.CurrentContext.URL } - cli.Printer.PrintLn(fmt.Sprintf("⚠️ This will upload '%s' to SUSE Observability server:", filepath.Base(zipFile))) - cli.Printer.PrintLn(fmt.Sprintf(" Server: %s", serverURL)) + printMsg(cli, fmt.Sprintf("⚠️ This will upload '%s' to SUSE Observability server:", filepath.Base(zipFile))) + printMsg(cli, fmt.Sprintf(" Server: %s", serverURL)) fmt.Print(" Continue? (y/N): ") reader := bufio.NewReader(os.Stdin) @@ -489,3 +489,15 @@ func updateVersionInYaml(configPath, newVersion string) error { return nil } + +func printMsg(cli *di.Deps, msg string) { + if !cli.IsJson() { + cli.Printer.PrintLn(msg) + } +} + +func printSuccess(cli *di.Deps, msg string) { + if !cli.IsJson() { + cli.Printer.Success(msg) + } +} diff --git a/cmd/stackpack/stackpack_test_deploy_cmd_test.go b/cmd/stackpack/stackpack_test_deploy_test.go similarity index 99% rename from cmd/stackpack/stackpack_test_deploy_cmd_test.go rename to cmd/stackpack/stackpack_test_deploy_test.go index 16e42269..b832c7b5 100644 --- a/cmd/stackpack/stackpack_test_deploy_cmd_test.go +++ b/cmd/stackpack/stackpack_test_deploy_test.go @@ -33,7 +33,7 @@ func TestStackpackTestDeployCommand_FlagsAndStructure(t *testing.T) { // Test flags exist flags := cmd.Flags() - stackpackDirFlag := flags.Lookup("stackpack-directory") + stackpackDirFlag := flags.Lookup("directory") require.NotNil(t, stackpackDirFlag) assert.Equal(t, "d", stackpackDirFlag.Shorthand) diff --git a/cmd/stackpack/stackpack_validate.go b/cmd/stackpack/stackpack_validate.go index 7eef1b5c..5924c398 100644 --- a/cmd/stackpack/stackpack_validate.go +++ b/cmd/stackpack/stackpack_validate.go @@ -33,19 +33,17 @@ This command validates a stackpack by uploading it to the server. - If a directory is provided, it is automatically packaged into a .sts file before uploading - If a .sts file is provided, it is uploaded directly -Exactly one of --stackpack-directory or --stackpack-file must be specified. - -This command is experimental and requires STS_EXPERIMENTAL_STACKPACKS environment variable to be set.`, +Exactly one of --directory or --file must be specified.`, Example: `# Validate a stackpack directory (automatically packaged) -sts stackpack validate --stackpack-directory ./my-stackpack +sts stackpack validate --directory ./my-stackpack # Validate a pre-packaged .sts file -sts stackpack validate --stackpack-file ./my-stackpack.sts`, +sts stackpack validate --file ./my-stackpack.sts`, RunE: cli.CmdRunEWithApi(RunStackpackValidateCommand(args)), } - cmd.Flags().StringVarP(&args.StackpackDir, "stackpack-directory", "d", "", "Path to stackpack directory") - cmd.Flags().StringVarP(&args.StackpackFile, "stackpack-file", "f", "", "Path to .sts file") + cmd.Flags().StringVarP(&args.StackpackDir, "directory", "d", "", "Path to stackpack directory") + cmd.Flags().StringVarP(&args.StackpackFile, "file", "f", "", "Path to .sts file") return cmd } @@ -61,7 +59,7 @@ func RunStackpackValidateCommand(args *ValidateArgs) di.CmdWithApiFn { // Validate exactly one of directory or file is set if (args.StackpackDir == "" && args.StackpackFile == "") || (args.StackpackDir != "" && args.StackpackFile != "") { - return common.NewCLIArgParseError(fmt.Errorf("exactly one of --stackpack-directory or --stackpack-file must be specified")) + return common.NewCLIArgParseError(fmt.Errorf("exactly one of --directory or --file must be specified")) } // Prepare file to validate - if directory is provided, package it first diff --git a/cmd/stackpack/stackpack_validate_test.go b/cmd/stackpack/stackpack_validate_test.go index 0ff0a068..f65bdbd0 100644 --- a/cmd/stackpack/stackpack_validate_test.go +++ b/cmd/stackpack/stackpack_validate_test.go @@ -61,7 +61,7 @@ func TestValidate_WithDirectory_AutoPackages(t *testing.T) { require.NoError(t, os.MkdirAll(stackpackDir, 0755)) createTestStackpackDir(t, stackpackDir, "test-stackpack", "1.0.0") - _, err = di.ExecuteCommandWithContext(&cli.Deps, cmd, "--stackpack-directory", stackpackDir) + _, err = di.ExecuteCommandWithContext(&cli.Deps, cmd, "--directory", stackpackDir) require.NoError(t, err) // Verify success message @@ -81,7 +81,7 @@ func TestValidate_WithDirectory_InvalidStackpack(t *testing.T) { stackpackDir := filepath.Join(tempDir, "invalid-stackpack") require.NoError(t, os.MkdirAll(stackpackDir, 0755)) - _, err = di.ExecuteCommandWithContext(&cli.Deps, cmd, "--stackpack-directory", stackpackDir) + _, err = di.ExecuteCommandWithContext(&cli.Deps, cmd, "--directory", stackpackDir) require.Error(t, err) assert.Contains(t, err.Error(), "required stackpack item not found") } @@ -98,7 +98,7 @@ func TestValidate_WithDirectory_MissingStackpackYaml(t *testing.T) { require.NoError(t, os.MkdirAll(filepath.Join(stackpackDir, "resources"), 0755)) require.NoError(t, os.WriteFile(filepath.Join(stackpackDir, "README.md"), []byte("test"), 0644)) - _, err = di.ExecuteCommandWithContext(&cli.Deps, cmd, "--stackpack-directory", stackpackDir) + _, err = di.ExecuteCommandWithContext(&cli.Deps, cmd, "--directory", stackpackDir) require.Error(t, err) assert.Contains(t, err.Error(), "required stackpack item not found") } @@ -114,7 +114,7 @@ func TestValidate_WithPrePackagedFile(t *testing.T) { stackpackFile := filepath.Join(tempDir, "test.sts") require.NoError(t, os.WriteFile(stackpackFile, []byte("test stackpack content"), 0644)) - _, err = di.ExecuteCommandWithContext(&cli.Deps, cmd, "--stackpack-file", stackpackFile) + _, err = di.ExecuteCommandWithContext(&cli.Deps, cmd, "--file", stackpackFile) require.NoError(t, err) // Verify success message @@ -133,7 +133,7 @@ func TestValidate_JSONOutput(t *testing.T) { stackpackFile := filepath.Join(tempDir, "test.sts") require.NoError(t, os.WriteFile(stackpackFile, []byte("test content"), 0644)) - _, err = di.ExecuteCommandWithContext(&cli.Deps, cmd, "--stackpack-file", stackpackFile, "-o", "json") + _, err = di.ExecuteCommandWithContext(&cli.Deps, cmd, "--file", stackpackFile, "-o", "json") require.NoError(t, err) // Verify JSON was called @@ -174,7 +174,7 @@ func TestValidate_MutuallyExclusive(t *testing.T) { func TestValidate_NonexistentFile(t *testing.T) { cli, cmd := setupValidateCmd(t) - _, err := di.ExecuteCommandWithContext(&cli.Deps, cmd, "--stackpack-file", "/nonexistent/path/file.sts") + _, err := di.ExecuteCommandWithContext(&cli.Deps, cmd, "--file", "/nonexistent/path/file.sts") require.Error(t, err) assert.Contains(t, err.Error(), "failed to access stackpack file") } @@ -196,7 +196,7 @@ func TestValidate_WithDirectory_IncludingOptionalItems(t *testing.T) { require.NoError(t, os.MkdirAll(filepath.Join(stackpackDir, "includes"), 0755)) require.NoError(t, os.WriteFile(filepath.Join(stackpackDir, "includes", "include.txt"), []byte("include data"), 0644)) - _, err = di.ExecuteCommandWithContext(&cli.Deps, cmd, "--stackpack-directory", stackpackDir) + _, err = di.ExecuteCommandWithContext(&cli.Deps, cmd, "--directory", stackpackDir) require.NoError(t, err) // Verify success message @@ -208,7 +208,7 @@ func TestValidate_WithDirectory_IncludingOptionalItems(t *testing.T) { func TestValidate_NonexistentDirectory(t *testing.T) { cli, cmd := setupValidateCmd(t) - _, err := di.ExecuteCommandWithContext(&cli.Deps, cmd, "--stackpack-directory", "/nonexistent/stackpack/dir") + _, err := di.ExecuteCommandWithContext(&cli.Deps, cmd, "--directory", "/nonexistent/stackpack/dir") require.Error(t, err) assert.Contains(t, err.Error(), "required stackpack item not found") } diff --git a/cmd/stackpack_test.go b/cmd/stackpack_test.go index 3a3e7daa..cb54176f 100644 --- a/cmd/stackpack_test.go +++ b/cmd/stackpack_test.go @@ -1,91 +1,13 @@ package cmd import ( - "os" "testing" "github.com/stackvista/stackstate-cli/internal/di" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -func TestStackPackCommand_FeatureGating(t *testing.T) { - tests := []struct { - name string - envVarValue string - expectExperimentalCommands bool - description string - }{ - { - name: "experimental commands hidden by default", - envVarValue: "", - expectExperimentalCommands: false, - description: "When environment variable is not set, experimental commands should be hidden", - }, - { - name: "experimental commands visible when env var is set to 1", - envVarValue: "1", - expectExperimentalCommands: true, - description: "When environment variable is set to '1', experimental commands should be visible", - }, - { - name: "experimental commands visible when env var is set to any value", - envVarValue: "true", - expectExperimentalCommands: true, - description: "When environment variable is set to any non-empty value, experimental commands should be visible", - }, - { - name: "experimental commands visible when env var is set to enabled", - envVarValue: "enabled", - expectExperimentalCommands: true, - description: "When environment variable is set to 'enabled', experimental commands should be visible", - }, - } - - experimentalCommands := []string{"scaffold", "package", "test-deploy"} - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Store original environment value to restore later - originalValue := os.Getenv(experimentalStackpackEnvVar) - defer func() { - if originalValue == "" { - os.Unsetenv(experimentalStackpackEnvVar) - } else { - os.Setenv(experimentalStackpackEnvVar, originalValue) - } - }() - - // Set the environment variable for this test - if tt.envVarValue == "" { - os.Unsetenv(experimentalStackpackEnvVar) - } else { - err := os.Setenv(experimentalStackpackEnvVar, tt.envVarValue) - require.NoError(t, err) - } - - // Create the command - cli := di.NewMockDeps(t) - cmd := StackPackCommand(&cli.Deps) - - // Check each experimental command - for _, cmdName := range experimentalCommands { - foundCmd, _, err := cmd.Find([]string{cmdName}) - - if tt.expectExperimentalCommands { - assert.NoError(t, err, tt.description+" (command: %s)", cmdName) - assert.NotNil(t, foundCmd, tt.description+" (command: %s)", cmdName) - assert.Equal(t, cmdName, foundCmd.Use, tt.description+" (command: %s)", cmdName) - } else { - assert.Error(t, err, tt.description+" (command: %s)", cmdName) - assert.Contains(t, err.Error(), "unknown command", tt.description+" (command: %s)", cmdName) - } - } - }) - } -} - -func TestStackPackCommand_AlwaysPresentCommands(t *testing.T) { +func TestStackPackCommand_EnabledCommands(t *testing.T) { // Ensure that other commands are always present regardless of environment variable cli := di.NewMockDeps(t) cmd := StackPackCommand(&cli.Deps) @@ -100,10 +22,13 @@ func TestStackPackCommand_AlwaysPresentCommands(t *testing.T) { "upgrade", "confirm-manual-steps", "describe", + "scaffold", + "package", + "test-deploy", } for _, cmdName := range expectedCommands { - t.Run("command_"+cmdName+"_always_present", func(t *testing.T) { + t.Run("command_"+cmdName+"_enabled", func(t *testing.T) { foundCmd, _, err := cmd.Find([]string{cmdName}) assert.NoError(t, err, "Command %s should always be present", cmdName) assert.NotNil(t, foundCmd, "Command %s should always be present", cmdName) diff --git a/cmd/sts.go b/cmd/sts.go index e5788fdf..ab107f1c 100644 --- a/cmd/sts.go +++ b/cmd/sts.go @@ -1,8 +1,6 @@ package cmd import ( - "os" - "github.com/spf13/cobra" "github.com/stackvista/stackstate-cli/internal/di" ) @@ -34,12 +32,8 @@ func STSCommand(cli *di.Deps) *cobra.Command { cmd.AddCommand(UserSessionCommand(cli)) cmd.AddCommand(DashboardCommand(cli)) cmd.AddCommand(TopologyCommand(cli)) - - // Experimental commands for otel mapping - if os.Getenv("STS_EXPERIMENTAL_STACKPACKS") != "" { - cmd.AddCommand(OtelComponentMappingCommand(cli)) - cmd.AddCommand(OtelRelationMappingCommand(cli)) - } + cmd.AddCommand(OtelComponentMappingCommand(cli)) + cmd.AddCommand(OtelRelationMappingCommand(cli)) return cmd } diff --git a/cmd/sts_test.go b/cmd/sts_test.go index 699880cb..5ca7963f 100644 --- a/cmd/sts_test.go +++ b/cmd/sts_test.go @@ -39,6 +39,8 @@ func TestSTSCommandContainsExpectedSubcommands(t *testing.T) { "user-session", "dashboard", "topology", + "otel-component-mapping", + "otel-relation-mapping", } // Verify expected commands are present @@ -58,7 +60,7 @@ func TestSTSCommandStructure(t *testing.T) { cli := di.NewMockDeps(t) cmd := STSCommand(&cli.Deps) - assert.Len(t, cmd.Commands(), 17, "Expected 17 subcommands") + assert.Len(t, cmd.Commands(), 19, "Expected 19 subcommands") } func TestSTSCommandUsageTemplate(t *testing.T) { From 5ff87386d19d21869e9e715d79fcfb8c41cdeb10 Mon Sep 17 00:00:00 2001 From: Frank van Lankvelt Date: Mon, 31 Aug 2026 14:29:41 +0200 Subject: [PATCH 2/4] STAC-24987: mute commands when executed from other command --- cmd/stackpack/common.go | 52 ++++++++------- cmd/stackpack/stackpack_downgrade.go | 6 +- cmd/stackpack/stackpack_install.go | 90 ++++++++++++++------------ cmd/stackpack/stackpack_package.go | 32 ++++----- cmd/stackpack/stackpack_test_deploy.go | 11 ++-- cmd/stackpack/stackpack_upgrade.go | 8 +-- cmd/stackpack/stackpack_upload.go | 26 ++++---- 7 files changed, 118 insertions(+), 107 deletions(-) diff --git a/cmd/stackpack/common.go b/cmd/stackpack/common.go index c9901340..59704232 100644 --- a/cmd/stackpack/common.go +++ b/cmd/stackpack/common.go @@ -121,7 +121,7 @@ func (w *OperationWaiter) WaitForCompletion(options WaitOptions) error { // waitAndDisplayResult waits for a StackPack operation to complete, then displays the final status. // operationLabel is used in progress/success messages (e.g. "upgrade" or "downgrade"). -func waitAndDisplayResult(cli *di.Deps, api *stackstate_api.APIClient, stackPackName string, timeout time.Duration, operationLabel string) common.CLIError { +func waitAndDisplayResult(cli *di.Deps, api *stackstate_api.APIClient, stackPackName string, timeout time.Duration, operationLabel string, mute bool) common.CLIError { if !cli.IsJson() { cli.Printer.PrintLn("Waiting for " + operationLabel + " to complete...") } @@ -145,32 +145,34 @@ func waitAndDisplayResult(cli *di.Deps, api *stackstate_api.APIClient, stackPack return common.NewNotFoundError(err) } - if cli.IsJson() { - cli.Printer.PrintJson(map[string]interface{}{ - "stackpack": finalStackPack, - "status": "completed", - "current-version": finalStackPack.GetVersion(), - }) - } else { - cli.Printer.Success("StackPack " + operationLabel + " completed successfully") - - data := make([][]interface{}, 0) - for _, config := range finalStackPack.GetConfigurations() { - lastUpdateTime := time.UnixMilli(config.GetLastUpdateTimestamp()) - data = append(data, []interface{}{ - config.GetId(), - finalStackPack.GetName(), - config.GetStatus(), - config.GetStackPackVersion(), - lastUpdateTime, + if !mute { + if cli.IsJson() { + cli.Printer.PrintJson(map[string]interface{}{ + "stackpack": finalStackPack, + "status": "completed", + "current-version": finalStackPack.GetVersion(), }) - } + } else { + cli.Printer.Success("StackPack " + operationLabel + " completed successfully") + + data := make([][]interface{}, 0) + for _, config := range finalStackPack.GetConfigurations() { + lastUpdateTime := time.UnixMilli(config.GetLastUpdateTimestamp()) + data = append(data, []interface{}{ + config.GetId(), + finalStackPack.GetName(), + config.GetStatus(), + config.GetStackPackVersion(), + lastUpdateTime, + }) + } - cli.Printer.Table(printer.TableData{ - Header: []string{"id", "name", "status", "version", "last updated"}, - Data: data, - MissingTableDataMsg: printer.NotFoundMsg{Types: "configurations for " + stackPackName}, - }) + cli.Printer.Table(printer.TableData{ + Header: []string{"id", "name", "status", "version", "last updated"}, + Data: data, + MissingTableDataMsg: printer.NotFoundMsg{Types: "configurations for " + stackPackName}, + }) + } } return nil diff --git a/cmd/stackpack/stackpack_downgrade.go b/cmd/stackpack/stackpack_downgrade.go index 3fb2c885..3f63233c 100644 --- a/cmd/stackpack/stackpack_downgrade.go +++ b/cmd/stackpack/stackpack_downgrade.go @@ -30,7 +30,7 @@ sts stackpack downgrade --name kubernetes --stackpack-version 1.2.3 # downgrade and wait for completion sts stackpack downgrade --name kubernetes --stackpack-version 1.2.3 --wait`, - RunE: cli.CmdRunEWithApi(RunStackpackDowngradeCommand(args)), + RunE: cli.CmdRunEWithApi(RunStackpackDowngradeCommand(args, false)), } common.AddRequiredNameFlagVar(cmd, &args.TypeName, "Name of the StackPack") cmd.Flags().StringVar(&args.Version, StackpackVersionFlag, "", "Version to downgrade to") @@ -40,7 +40,7 @@ sts stackpack downgrade --name kubernetes --stackpack-version 1.2.3 --wait`, return cmd } -func RunStackpackDowngradeCommand(args *DowngradeArgs) di.CmdWithApiFn { +func RunStackpackDowngradeCommand(args *DowngradeArgs, mute bool) di.CmdWithApiFn { return func( cmd *cobra.Command, cli *di.Deps, @@ -55,7 +55,7 @@ func RunStackpackDowngradeCommand(args *DowngradeArgs) di.CmdWithApiFn { } if args.Wait { - if cliErr := waitAndDisplayResult(cli, api, args.TypeName, args.Timeout, "downgrade"); cliErr != nil { + if cliErr := waitAndDisplayResult(cli, api, args.TypeName, args.Timeout, "downgrade", mute); cliErr != nil { return cliErr } } else { diff --git a/cmd/stackpack/stackpack_install.go b/cmd/stackpack/stackpack_install.go index 69b4e4f8..0d8889dd 100644 --- a/cmd/stackpack/stackpack_install.go +++ b/cmd/stackpack/stackpack_install.go @@ -38,7 +38,7 @@ sts stackpack install --name kubernetes -p cluster_name=production --wait # install a specific (older) version, e.g. to downgrade sts stackpack install --name example --stackpack-version 1.2.3 -p "full_name=First Last"`, - RunE: cli.CmdRunEWithApi(RunStackpackInstallCommand(args)), + RunE: cli.CmdRunEWithApi(RunStackpackInstallCommand(args, false)), } common.AddRequiredNameFlagVar(cmd, &args.Name, "Name of the StackPack") pflags.EnumVar(cmd.Flags(), &args.UnlockedStrategy, @@ -55,7 +55,7 @@ sts stackpack install --name example --stackpack-version 1.2.3 -p "full_name=Fir return cmd } -func RunStackpackInstallCommand(args *InstallArgs) di.CmdWithApiFn { +func RunStackpackInstallCommand(args *InstallArgs, mute bool) di.CmdWithApiFn { return func( cmd *cobra.Command, cli *di.Deps, @@ -101,51 +101,55 @@ func RunStackpackInstallCommand(args *InstallArgs) di.CmdWithApiFn { } // Display final status - if cli.IsJson() { - cli.Printer.PrintJson(map[string]interface{}{ - "stackpack": finalStackPack, - "status": "completed", - }) - } else { - cli.Printer.Success("StackPack installation completed successfully") - - // Show configurations status - data := make([][]interface{}, 0) - for _, config := range finalStackPack.GetConfigurations() { - lastUpdateTime := time.UnixMilli(config.GetLastUpdateTimestamp()) - data = append(data, []interface{}{ - config.GetId(), - finalStackPack.GetName(), - config.GetStatus(), - config.GetStackPackVersion(), - lastUpdateTime, + if !mute { + if cli.IsJson() { + cli.Printer.PrintJson(map[string]interface{}{ + "stackpack": finalStackPack, + "status": "completed", }) + } else { + cli.Printer.Success("StackPack installation completed successfully") + + // Show configurations status + data := make([][]interface{}, 0) + for _, config := range finalStackPack.GetConfigurations() { + lastUpdateTime := time.UnixMilli(config.GetLastUpdateTimestamp()) + data = append(data, []interface{}{ + config.GetId(), + finalStackPack.GetName(), + config.GetStatus(), + config.GetStackPackVersion(), + lastUpdateTime, + }) + } + + cli.Printer.Table( + printer.TableData{ + Header: []string{"id", "name", "status", "version", "last updated"}, + Data: data, + MissingTableDataMsg: printer.NotFoundMsg{Types: "configurations for " + args.Name}, + }, + ) } - - cli.Printer.Table( - printer.TableData{ - Header: []string{"id", "name", "status", "version", "last updated"}, - Data: data, - MissingTableDataMsg: printer.NotFoundMsg{Types: "configurations for " + args.Name}, - }, - ) } } else { - if cli.IsJson() { - cli.Printer.PrintJson(map[string]interface{}{ - "instance": instance, - }) - } else { - lastUpdateTime := time.UnixMilli(instance.GetLastUpdateTimestamp()) - - cli.Printer.Success("StackPack instance installation triggered") - cli.Printer.Table( - printer.TableData{ - Header: []string{"id", "name", "status", "version", "last updated"}, - Data: [][]interface{}{{instance.Id, instance.Name, instance.Status, instance.StackPackVersion, lastUpdateTime}}, - MissingTableDataMsg: printer.NotFoundMsg{Types: "provision details of " + args.Name}, - }, - ) + if !mute { + if cli.IsJson() { + cli.Printer.PrintJson(map[string]interface{}{ + "instance": instance, + }) + } else { + lastUpdateTime := time.UnixMilli(instance.GetLastUpdateTimestamp()) + + cli.Printer.Success("StackPack instance installation triggered") + cli.Printer.Table( + printer.TableData{ + Header: []string{"id", "name", "status", "version", "last updated"}, + Data: [][]interface{}{{instance.Id, instance.Name, instance.Status, instance.StackPackVersion, lastUpdateTime}}, + MissingTableDataMsg: printer.NotFoundMsg{Types: "provision details of " + args.Name}, + }, + ) + } } } diff --git a/cmd/stackpack/stackpack_package.go b/cmd/stackpack/stackpack_package.go index 85d69ec7..4b0d54e9 100644 --- a/cmd/stackpack/stackpack_package.go +++ b/cmd/stackpack/stackpack_package.go @@ -106,7 +106,7 @@ sts stackpack package -f my-custom-archive.sts # Force overwrite existing .sts file sts stackpack package --force`, - RunE: cli.CmdRunE(RunStackpackPackageCommand(args)), + RunE: cli.CmdRunE(RunStackpackPackageCommand(args, false)), } cmd.Flags().StringVarP(&args.StackpackDir, "directory", "d", "", "Path to stackpack directory (defaults to current directory)") @@ -117,7 +117,7 @@ sts stackpack package --force`, } // RunStackpackPackageCommand executes the package command -func RunStackpackPackageCommand(args *PackageArgs) func(cli *di.Deps, cmd *cobra.Command) common.CLIError { +func RunStackpackPackageCommand(args *PackageArgs, mute bool) func(cli *di.Deps, cmd *cobra.Command) common.CLIError { return func(cli *di.Deps, cmd *cobra.Command) common.CLIError { // Set default stackpack directory if args.StackpackDir == "" { @@ -180,19 +180,21 @@ func RunStackpackPackageCommand(args *PackageArgs) func(cli *di.Deps, cmd *cobra return common.NewRuntimeError(fmt.Errorf("failed to create .sts file: %w", err)) } - if cli.IsJson() { - cli.Printer.PrintJson(map[string]interface{}{ - "success": true, - "stackpack_name": stackpackInfo.Name, - "stackpack_version": stackpackInfo.Version, - "zip_file": args.ArchiveFile, - "source_dir": args.StackpackDir, - }) - } else { - cli.Printer.Successf("Stackpack packaged successfully!") - cli.Printer.PrintLn("") - cli.Printer.PrintLn(fmt.Sprintf("Stackpack: %s (v%s)", stackpackInfo.Name, stackpackInfo.Version)) - cli.Printer.PrintLn(fmt.Sprintf(".sts file: %s", args.ArchiveFile)) + if !mute { + if cli.IsJson() { + cli.Printer.PrintJson(map[string]interface{}{ + "success": true, + "stackpack_name": stackpackInfo.Name, + "stackpack_version": stackpackInfo.Version, + "zip_file": args.ArchiveFile, + "source_dir": args.StackpackDir, + }) + } else { + cli.Printer.Successf("Stackpack packaged successfully!") + cli.Printer.PrintLn("") + cli.Printer.PrintLn(fmt.Sprintf("Stackpack: %s (v%s)", stackpackInfo.Name, stackpackInfo.Version)) + cli.Printer.PrintLn(fmt.Sprintf(".sts file: %s", args.ArchiveFile)) + } } return nil diff --git a/cmd/stackpack/stackpack_test_deploy.go b/cmd/stackpack/stackpack_test_deploy.go index 1cf48fce..60582e3f 100644 --- a/cmd/stackpack/stackpack_test_deploy.go +++ b/cmd/stackpack/stackpack_test_deploy.go @@ -237,7 +237,8 @@ func RunStackpackTestDeployCommand(args *TestDeployArgs) di.CmdWithApiFn { printMsg(cli, fmt.Sprintf("Note: Could not clean up .sts file %s: %v", packageArgs.ArchiveFile, err)) } else if cli.IsJson() { cli.Printer.PrintJson(map[string]interface{}{ - "version": baseVersionForSnapshot, + "stackpack": originalInfo.Name, + "version": newVersion, }) } @@ -333,7 +334,7 @@ func confirmUpload(cli *di.Deps, zipFile string) bool { func runPackageStep(cli *di.Deps, args *PackageArgs) common.CLIError { // Reuse the existing package command logic packageCmd := &cobra.Command{} - packageFn := RunStackpackPackageCommand(args) + packageFn := RunStackpackPackageCommand(args, true) return packageFn(cli, packageCmd) } @@ -341,7 +342,7 @@ func runPackageStep(cli *di.Deps, args *PackageArgs) common.CLIError { func runUploadStep(cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo, args *UploadArgs) common.CLIError { // Reuse the existing upload command logic uploadCmd := &cobra.Command{} - uploadFn := RunStackpackUploadCommand(args) + uploadFn := RunStackpackUploadCommand(args, true) return uploadFn(uploadCmd, cli, api, serverInfo) } @@ -349,7 +350,7 @@ func runUploadStep(cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stac func runInstallStep(cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo, args *InstallArgs) common.CLIError { // Reuse the existing install command logic installCmd := &cobra.Command{} - installFn := RunStackpackInstallCommand(args) + installFn := RunStackpackInstallCommand(args, true) return installFn(installCmd, cli, api, serverInfo) } @@ -357,7 +358,7 @@ func runInstallStep(cli *di.Deps, api *stackstate_api.APIClient, serverInfo *sta func runUpgradeStep(cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo, args *UpgradeArgs) common.CLIError { // Reuse the existing upgrade command logic upgradeCmd := &cobra.Command{} - upgradeFn := RunStackpackUpgradeCommand(args) + upgradeFn := RunStackpackUpgradeCommand(args, true) return upgradeFn(upgradeCmd, cli, api, serverInfo) } diff --git a/cmd/stackpack/stackpack_upgrade.go b/cmd/stackpack/stackpack_upgrade.go index c1d85da8..959a65ae 100644 --- a/cmd/stackpack/stackpack_upgrade.go +++ b/cmd/stackpack/stackpack_upgrade.go @@ -35,7 +35,7 @@ sts stackpack upgrade --name kubernetes # upgrade and wait for completion sts stackpack upgrade --name kubernetes --wait`, - RunE: cli.CmdRunEWithApi(RunStackpackUpgradeCommand(args)), + RunE: cli.CmdRunEWithApi(RunStackpackUpgradeCommand(args, false)), } common.AddRequiredNameFlagVar(cmd, &args.TypeName, "Name of the StackPack") pflags.EnumVar(cmd.Flags(), &args.UnlockedStrategy, @@ -49,7 +49,7 @@ sts stackpack upgrade --name kubernetes --wait`, cmd.Flags().DurationVar(&args.Timeout, "timeout", DefaultTimeout, "Timeout for waiting") return cmd } -func RunStackpackUpgradeCommand(args *UpgradeArgs) di.CmdWithApiFn { +func RunStackpackUpgradeCommand(args *UpgradeArgs, mute bool) di.CmdWithApiFn { return func( cmd *cobra.Command, cli *di.Deps, @@ -73,10 +73,10 @@ func RunStackpackUpgradeCommand(args *UpgradeArgs) di.CmdWithApiFn { } if args.Wait { - if cliErr := waitAndDisplayResult(cli, api, args.TypeName, args.Timeout, "upgrade"); cliErr != nil { + if cliErr := waitAndDisplayResult(cli, api, args.TypeName, args.Timeout, "upgrade", mute); cliErr != nil { return cliErr } - } else { + } else if !mute { if cli.IsJson() { cli.Printer.PrintJson(map[string]interface{}{ "success": true, diff --git a/cmd/stackpack/stackpack_upload.go b/cmd/stackpack/stackpack_upload.go index 0731642d..827cbc4d 100644 --- a/cmd/stackpack/stackpack_upload.go +++ b/cmd/stackpack/stackpack_upload.go @@ -23,14 +23,14 @@ func StackpackUploadCommand(cli *di.Deps) *cobra.Command { Long: "Upload a StackPack file (.sts) to SUSE Observability. After upload, the StackPack can be installed using 'sts stackpack install'.", Example: `# upload a StackPack sts stackpack upload --file my-stackpack.sts`, - RunE: cli.CmdRunEWithApi(RunStackpackUploadCommand(args)), + RunE: cli.CmdRunEWithApi(RunStackpackUploadCommand(args, false)), } common.AddRequiredFileFlagVar(cmd, &args.FilePath, "Stackpack file to upload (.sts file)") return cmd } -func RunStackpackUploadCommand(args *UploadArgs) di.CmdWithApiFn { +func RunStackpackUploadCommand(args *UploadArgs, mute bool) di.CmdWithApiFn { return func( cmd *cobra.Command, cli *di.Deps, @@ -48,16 +48,18 @@ func RunStackpackUploadCommand(args *UploadArgs) di.CmdWithApiFn { return common.NewResponseError(err, resp) } - if cli.IsJson() { - cli.Printer.PrintJson(map[string]interface{}{ - "uploaded-stackpack": stackpack, - }) - } else { - cli.Printer.Success(fmt.Sprintf("uploaded StackPack: %s", args.FilePath)) - cli.Printer.Table(printer.TableData{ - Header: []string{"name", "display name", "version"}, - Data: [][]interface{}{{stackpack.Name, stackpack.DisplayName, stackpack.Version}}, - }) + if !mute { + if cli.IsJson() { + cli.Printer.PrintJson(map[string]interface{}{ + "uploaded-stackpack": stackpack, + }) + } else { + cli.Printer.Success(fmt.Sprintf("uploaded StackPack: %s", args.FilePath)) + cli.Printer.Table(printer.TableData{ + Header: []string{"name", "display name", "version"}, + Data: [][]interface{}{{stackpack.Name, stackpack.DisplayName, stackpack.Version}}, + }) + } } return nil From 504d53027ca5e326dd6c4f3a4a2d264228e9d7a5 Mon Sep 17 00:00:00 2001 From: Frank van Lankvelt Date: Mon, 31 Aug 2026 15:55:02 +0200 Subject: [PATCH 3/4] STAC-24987: fix formatting --- cmd/stackpack/stackpack_install.go | 34 ++++++++++++-------------- cmd/stackpack/stackpack_test_deploy.go | 1 - cmd/stackpack_test.go | 6 ++--- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/cmd/stackpack/stackpack_install.go b/cmd/stackpack/stackpack_install.go index 0d8889dd..d8300367 100644 --- a/cmd/stackpack/stackpack_install.go +++ b/cmd/stackpack/stackpack_install.go @@ -132,24 +132,22 @@ func RunStackpackInstallCommand(args *InstallArgs, mute bool) di.CmdWithApiFn { ) } } - } else { - if !mute { - if cli.IsJson() { - cli.Printer.PrintJson(map[string]interface{}{ - "instance": instance, - }) - } else { - lastUpdateTime := time.UnixMilli(instance.GetLastUpdateTimestamp()) - - cli.Printer.Success("StackPack instance installation triggered") - cli.Printer.Table( - printer.TableData{ - Header: []string{"id", "name", "status", "version", "last updated"}, - Data: [][]interface{}{{instance.Id, instance.Name, instance.Status, instance.StackPackVersion, lastUpdateTime}}, - MissingTableDataMsg: printer.NotFoundMsg{Types: "provision details of " + args.Name}, - }, - ) - } + } else if !mute { + if cli.IsJson() { + cli.Printer.PrintJson(map[string]interface{}{ + "instance": instance, + }) + } else { + lastUpdateTime := time.UnixMilli(instance.GetLastUpdateTimestamp()) + + cli.Printer.Success("StackPack instance installation triggered") + cli.Printer.Table( + printer.TableData{ + Header: []string{"id", "name", "status", "version", "last updated"}, + Data: [][]interface{}{{instance.Id, instance.Name, instance.Status, instance.StackPackVersion, lastUpdateTime}}, + MissingTableDataMsg: printer.NotFoundMsg{Types: "provision details of " + args.Name}, + }, + ) } } diff --git a/cmd/stackpack/stackpack_test_deploy.go b/cmd/stackpack/stackpack_test_deploy.go index 60582e3f..8045340c 100644 --- a/cmd/stackpack/stackpack_test_deploy.go +++ b/cmd/stackpack/stackpack_test_deploy.go @@ -76,7 +76,6 @@ func RunStackpackTestDeployCommand(args *TestDeployArgs) di.CmdWithApiFn { api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo, ) common.CLIError { - // Set default stackpack directory if args.StackpackDir == "" { currentDir, err := os.Getwd() diff --git a/cmd/stackpack_test.go b/cmd/stackpack_test.go index cb54176f..c092872c 100644 --- a/cmd/stackpack_test.go +++ b/cmd/stackpack_test.go @@ -22,9 +22,9 @@ func TestStackPackCommand_EnabledCommands(t *testing.T) { "upgrade", "confirm-manual-steps", "describe", - "scaffold", - "package", - "test-deploy", + "scaffold", + "package", + "test-deploy", } for _, cmdName := range expectedCommands { From 57d38edaf2df0f77a3d35dc3af16ac4227bedb4c Mon Sep 17 00:00:00 2001 From: Frank van Lankvelt Date: Mon, 31 Aug 2026 16:33:05 +0200 Subject: [PATCH 4/4] STAC-24987: use DI to suppress output with mock printer --- cmd/stackpack/common.go | 52 +++++++++++----------- cmd/stackpack/stackpack_downgrade.go | 6 +-- cmd/stackpack/stackpack_install.go | 60 +++++++++++++------------- cmd/stackpack/stackpack_package.go | 32 +++++++------- cmd/stackpack/stackpack_test_deploy.go | 48 ++++++++++++++------- cmd/stackpack/stackpack_upgrade.go | 8 ++-- cmd/stackpack/stackpack_upload.go | 26 ++++++----- 7 files changed, 121 insertions(+), 111 deletions(-) diff --git a/cmd/stackpack/common.go b/cmd/stackpack/common.go index 59704232..c9901340 100644 --- a/cmd/stackpack/common.go +++ b/cmd/stackpack/common.go @@ -121,7 +121,7 @@ func (w *OperationWaiter) WaitForCompletion(options WaitOptions) error { // waitAndDisplayResult waits for a StackPack operation to complete, then displays the final status. // operationLabel is used in progress/success messages (e.g. "upgrade" or "downgrade"). -func waitAndDisplayResult(cli *di.Deps, api *stackstate_api.APIClient, stackPackName string, timeout time.Duration, operationLabel string, mute bool) common.CLIError { +func waitAndDisplayResult(cli *di.Deps, api *stackstate_api.APIClient, stackPackName string, timeout time.Duration, operationLabel string) common.CLIError { if !cli.IsJson() { cli.Printer.PrintLn("Waiting for " + operationLabel + " to complete...") } @@ -145,34 +145,32 @@ func waitAndDisplayResult(cli *di.Deps, api *stackstate_api.APIClient, stackPack return common.NewNotFoundError(err) } - if !mute { - if cli.IsJson() { - cli.Printer.PrintJson(map[string]interface{}{ - "stackpack": finalStackPack, - "status": "completed", - "current-version": finalStackPack.GetVersion(), - }) - } else { - cli.Printer.Success("StackPack " + operationLabel + " completed successfully") - - data := make([][]interface{}, 0) - for _, config := range finalStackPack.GetConfigurations() { - lastUpdateTime := time.UnixMilli(config.GetLastUpdateTimestamp()) - data = append(data, []interface{}{ - config.GetId(), - finalStackPack.GetName(), - config.GetStatus(), - config.GetStackPackVersion(), - lastUpdateTime, - }) - } - - cli.Printer.Table(printer.TableData{ - Header: []string{"id", "name", "status", "version", "last updated"}, - Data: data, - MissingTableDataMsg: printer.NotFoundMsg{Types: "configurations for " + stackPackName}, + if cli.IsJson() { + cli.Printer.PrintJson(map[string]interface{}{ + "stackpack": finalStackPack, + "status": "completed", + "current-version": finalStackPack.GetVersion(), + }) + } else { + cli.Printer.Success("StackPack " + operationLabel + " completed successfully") + + data := make([][]interface{}, 0) + for _, config := range finalStackPack.GetConfigurations() { + lastUpdateTime := time.UnixMilli(config.GetLastUpdateTimestamp()) + data = append(data, []interface{}{ + config.GetId(), + finalStackPack.GetName(), + config.GetStatus(), + config.GetStackPackVersion(), + lastUpdateTime, }) } + + cli.Printer.Table(printer.TableData{ + Header: []string{"id", "name", "status", "version", "last updated"}, + Data: data, + MissingTableDataMsg: printer.NotFoundMsg{Types: "configurations for " + stackPackName}, + }) } return nil diff --git a/cmd/stackpack/stackpack_downgrade.go b/cmd/stackpack/stackpack_downgrade.go index 3f63233c..3fb2c885 100644 --- a/cmd/stackpack/stackpack_downgrade.go +++ b/cmd/stackpack/stackpack_downgrade.go @@ -30,7 +30,7 @@ sts stackpack downgrade --name kubernetes --stackpack-version 1.2.3 # downgrade and wait for completion sts stackpack downgrade --name kubernetes --stackpack-version 1.2.3 --wait`, - RunE: cli.CmdRunEWithApi(RunStackpackDowngradeCommand(args, false)), + RunE: cli.CmdRunEWithApi(RunStackpackDowngradeCommand(args)), } common.AddRequiredNameFlagVar(cmd, &args.TypeName, "Name of the StackPack") cmd.Flags().StringVar(&args.Version, StackpackVersionFlag, "", "Version to downgrade to") @@ -40,7 +40,7 @@ sts stackpack downgrade --name kubernetes --stackpack-version 1.2.3 --wait`, return cmd } -func RunStackpackDowngradeCommand(args *DowngradeArgs, mute bool) di.CmdWithApiFn { +func RunStackpackDowngradeCommand(args *DowngradeArgs) di.CmdWithApiFn { return func( cmd *cobra.Command, cli *di.Deps, @@ -55,7 +55,7 @@ func RunStackpackDowngradeCommand(args *DowngradeArgs, mute bool) di.CmdWithApiF } if args.Wait { - if cliErr := waitAndDisplayResult(cli, api, args.TypeName, args.Timeout, "downgrade", mute); cliErr != nil { + if cliErr := waitAndDisplayResult(cli, api, args.TypeName, args.Timeout, "downgrade"); cliErr != nil { return cliErr } } else { diff --git a/cmd/stackpack/stackpack_install.go b/cmd/stackpack/stackpack_install.go index d8300367..69b4e4f8 100644 --- a/cmd/stackpack/stackpack_install.go +++ b/cmd/stackpack/stackpack_install.go @@ -38,7 +38,7 @@ sts stackpack install --name kubernetes -p cluster_name=production --wait # install a specific (older) version, e.g. to downgrade sts stackpack install --name example --stackpack-version 1.2.3 -p "full_name=First Last"`, - RunE: cli.CmdRunEWithApi(RunStackpackInstallCommand(args, false)), + RunE: cli.CmdRunEWithApi(RunStackpackInstallCommand(args)), } common.AddRequiredNameFlagVar(cmd, &args.Name, "Name of the StackPack") pflags.EnumVar(cmd.Flags(), &args.UnlockedStrategy, @@ -55,7 +55,7 @@ sts stackpack install --name example --stackpack-version 1.2.3 -p "full_name=Fir return cmd } -func RunStackpackInstallCommand(args *InstallArgs, mute bool) di.CmdWithApiFn { +func RunStackpackInstallCommand(args *InstallArgs) di.CmdWithApiFn { return func( cmd *cobra.Command, cli *di.Deps, @@ -101,38 +101,36 @@ func RunStackpackInstallCommand(args *InstallArgs, mute bool) di.CmdWithApiFn { } // Display final status - if !mute { - if cli.IsJson() { - cli.Printer.PrintJson(map[string]interface{}{ - "stackpack": finalStackPack, - "status": "completed", + if cli.IsJson() { + cli.Printer.PrintJson(map[string]interface{}{ + "stackpack": finalStackPack, + "status": "completed", + }) + } else { + cli.Printer.Success("StackPack installation completed successfully") + + // Show configurations status + data := make([][]interface{}, 0) + for _, config := range finalStackPack.GetConfigurations() { + lastUpdateTime := time.UnixMilli(config.GetLastUpdateTimestamp()) + data = append(data, []interface{}{ + config.GetId(), + finalStackPack.GetName(), + config.GetStatus(), + config.GetStackPackVersion(), + lastUpdateTime, }) - } else { - cli.Printer.Success("StackPack installation completed successfully") - - // Show configurations status - data := make([][]interface{}, 0) - for _, config := range finalStackPack.GetConfigurations() { - lastUpdateTime := time.UnixMilli(config.GetLastUpdateTimestamp()) - data = append(data, []interface{}{ - config.GetId(), - finalStackPack.GetName(), - config.GetStatus(), - config.GetStackPackVersion(), - lastUpdateTime, - }) - } - - cli.Printer.Table( - printer.TableData{ - Header: []string{"id", "name", "status", "version", "last updated"}, - Data: data, - MissingTableDataMsg: printer.NotFoundMsg{Types: "configurations for " + args.Name}, - }, - ) } + + cli.Printer.Table( + printer.TableData{ + Header: []string{"id", "name", "status", "version", "last updated"}, + Data: data, + MissingTableDataMsg: printer.NotFoundMsg{Types: "configurations for " + args.Name}, + }, + ) } - } else if !mute { + } else { if cli.IsJson() { cli.Printer.PrintJson(map[string]interface{}{ "instance": instance, diff --git a/cmd/stackpack/stackpack_package.go b/cmd/stackpack/stackpack_package.go index 4b0d54e9..85d69ec7 100644 --- a/cmd/stackpack/stackpack_package.go +++ b/cmd/stackpack/stackpack_package.go @@ -106,7 +106,7 @@ sts stackpack package -f my-custom-archive.sts # Force overwrite existing .sts file sts stackpack package --force`, - RunE: cli.CmdRunE(RunStackpackPackageCommand(args, false)), + RunE: cli.CmdRunE(RunStackpackPackageCommand(args)), } cmd.Flags().StringVarP(&args.StackpackDir, "directory", "d", "", "Path to stackpack directory (defaults to current directory)") @@ -117,7 +117,7 @@ sts stackpack package --force`, } // RunStackpackPackageCommand executes the package command -func RunStackpackPackageCommand(args *PackageArgs, mute bool) func(cli *di.Deps, cmd *cobra.Command) common.CLIError { +func RunStackpackPackageCommand(args *PackageArgs) func(cli *di.Deps, cmd *cobra.Command) common.CLIError { return func(cli *di.Deps, cmd *cobra.Command) common.CLIError { // Set default stackpack directory if args.StackpackDir == "" { @@ -180,21 +180,19 @@ func RunStackpackPackageCommand(args *PackageArgs, mute bool) func(cli *di.Deps, return common.NewRuntimeError(fmt.Errorf("failed to create .sts file: %w", err)) } - if !mute { - if cli.IsJson() { - cli.Printer.PrintJson(map[string]interface{}{ - "success": true, - "stackpack_name": stackpackInfo.Name, - "stackpack_version": stackpackInfo.Version, - "zip_file": args.ArchiveFile, - "source_dir": args.StackpackDir, - }) - } else { - cli.Printer.Successf("Stackpack packaged successfully!") - cli.Printer.PrintLn("") - cli.Printer.PrintLn(fmt.Sprintf("Stackpack: %s (v%s)", stackpackInfo.Name, stackpackInfo.Version)) - cli.Printer.PrintLn(fmt.Sprintf(".sts file: %s", args.ArchiveFile)) - } + if cli.IsJson() { + cli.Printer.PrintJson(map[string]interface{}{ + "success": true, + "stackpack_name": stackpackInfo.Name, + "stackpack_version": stackpackInfo.Version, + "zip_file": args.ArchiveFile, + "source_dir": args.StackpackDir, + }) + } else { + cli.Printer.Successf("Stackpack packaged successfully!") + cli.Printer.PrintLn("") + cli.Printer.PrintLn(fmt.Sprintf("Stackpack: %s (v%s)", stackpackInfo.Name, stackpackInfo.Version)) + cli.Printer.PrintLn(fmt.Sprintf(".sts file: %s", args.ArchiveFile)) } return nil diff --git a/cmd/stackpack/stackpack_test_deploy.go b/cmd/stackpack/stackpack_test_deploy.go index 8045340c..dd9ba478 100644 --- a/cmd/stackpack/stackpack_test_deploy.go +++ b/cmd/stackpack/stackpack_test_deploy.go @@ -15,6 +15,7 @@ import ( "github.com/stackvista/stackstate-cli/generated/stackstate_api" "github.com/stackvista/stackstate-cli/internal/common" "github.com/stackvista/stackstate-cli/internal/di" + "github.com/stackvista/stackstate-cli/internal/printer" ) const ( @@ -332,33 +333,50 @@ func confirmUpload(cli *di.Deps, zipFile string) bool { // runPackageStep executes the package command logic func runPackageStep(cli *di.Deps, args *PackageArgs) common.CLIError { // Reuse the existing package command logic - packageCmd := &cobra.Command{} - packageFn := RunStackpackPackageCommand(args, true) - return packageFn(cli, packageCmd) + return runCmd(cli, func(ctx *di.Deps) common.CLIError { + packageCmd := &cobra.Command{} + packageFn := RunStackpackPackageCommand(args) + return packageFn(ctx, packageCmd) + }) } // runUploadStep executes the upload command logic func runUploadStep(cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo, args *UploadArgs) common.CLIError { - // Reuse the existing upload command logic - uploadCmd := &cobra.Command{} - uploadFn := RunStackpackUploadCommand(args, true) - return uploadFn(uploadCmd, cli, api, serverInfo) + return runCmd(cli, func(ctx *di.Deps) common.CLIError { + // Reuse the existing upload command logic + uploadCmd := &cobra.Command{} + uploadFn := RunStackpackUploadCommand(args) + return uploadFn(uploadCmd, ctx, api, serverInfo) + }) } // runInstallStep executes the install command logic func runInstallStep(cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo, args *InstallArgs) common.CLIError { - // Reuse the existing install command logic - installCmd := &cobra.Command{} - installFn := RunStackpackInstallCommand(args, true) - return installFn(installCmd, cli, api, serverInfo) + return runCmd(cli, func(ctx *di.Deps) common.CLIError { + // Reuse the existing install command logic + installCmd := &cobra.Command{} + installFn := RunStackpackInstallCommand(args) + return installFn(installCmd, ctx, api, serverInfo) + }) } // runUpgradeStep executes the upgrade command logic func runUpgradeStep(cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo, args *UpgradeArgs) common.CLIError { - // Reuse the existing upgrade command logic - upgradeCmd := &cobra.Command{} - upgradeFn := RunStackpackUpgradeCommand(args, true) - return upgradeFn(upgradeCmd, cli, api, serverInfo) + return runCmd(cli, func(ctx *di.Deps) common.CLIError { + // Reuse the existing upgrade command logic + upgradeCmd := &cobra.Command{} + upgradeFn := RunStackpackUpgradeCommand(args) + return upgradeFn(upgradeCmd, ctx, api, serverInfo) + }) +} + +func runCmd(cli *di.Deps, f func(ctx *di.Deps) common.CLIError) common.CLIError { + oldPr := cli.Printer + pr := printer.NewMockPrinter(nil) + cli.Printer = &pr + err := f(cli) + cli.Printer = oldPr + return err } // getInstalledStackpackVersion checks if a stackpack is installed and returns its version diff --git a/cmd/stackpack/stackpack_upgrade.go b/cmd/stackpack/stackpack_upgrade.go index 959a65ae..c1d85da8 100644 --- a/cmd/stackpack/stackpack_upgrade.go +++ b/cmd/stackpack/stackpack_upgrade.go @@ -35,7 +35,7 @@ sts stackpack upgrade --name kubernetes # upgrade and wait for completion sts stackpack upgrade --name kubernetes --wait`, - RunE: cli.CmdRunEWithApi(RunStackpackUpgradeCommand(args, false)), + RunE: cli.CmdRunEWithApi(RunStackpackUpgradeCommand(args)), } common.AddRequiredNameFlagVar(cmd, &args.TypeName, "Name of the StackPack") pflags.EnumVar(cmd.Flags(), &args.UnlockedStrategy, @@ -49,7 +49,7 @@ sts stackpack upgrade --name kubernetes --wait`, cmd.Flags().DurationVar(&args.Timeout, "timeout", DefaultTimeout, "Timeout for waiting") return cmd } -func RunStackpackUpgradeCommand(args *UpgradeArgs, mute bool) di.CmdWithApiFn { +func RunStackpackUpgradeCommand(args *UpgradeArgs) di.CmdWithApiFn { return func( cmd *cobra.Command, cli *di.Deps, @@ -73,10 +73,10 @@ func RunStackpackUpgradeCommand(args *UpgradeArgs, mute bool) di.CmdWithApiFn { } if args.Wait { - if cliErr := waitAndDisplayResult(cli, api, args.TypeName, args.Timeout, "upgrade", mute); cliErr != nil { + if cliErr := waitAndDisplayResult(cli, api, args.TypeName, args.Timeout, "upgrade"); cliErr != nil { return cliErr } - } else if !mute { + } else { if cli.IsJson() { cli.Printer.PrintJson(map[string]interface{}{ "success": true, diff --git a/cmd/stackpack/stackpack_upload.go b/cmd/stackpack/stackpack_upload.go index 827cbc4d..0731642d 100644 --- a/cmd/stackpack/stackpack_upload.go +++ b/cmd/stackpack/stackpack_upload.go @@ -23,14 +23,14 @@ func StackpackUploadCommand(cli *di.Deps) *cobra.Command { Long: "Upload a StackPack file (.sts) to SUSE Observability. After upload, the StackPack can be installed using 'sts stackpack install'.", Example: `# upload a StackPack sts stackpack upload --file my-stackpack.sts`, - RunE: cli.CmdRunEWithApi(RunStackpackUploadCommand(args, false)), + RunE: cli.CmdRunEWithApi(RunStackpackUploadCommand(args)), } common.AddRequiredFileFlagVar(cmd, &args.FilePath, "Stackpack file to upload (.sts file)") return cmd } -func RunStackpackUploadCommand(args *UploadArgs, mute bool) di.CmdWithApiFn { +func RunStackpackUploadCommand(args *UploadArgs) di.CmdWithApiFn { return func( cmd *cobra.Command, cli *di.Deps, @@ -48,18 +48,16 @@ func RunStackpackUploadCommand(args *UploadArgs, mute bool) di.CmdWithApiFn { return common.NewResponseError(err, resp) } - if !mute { - if cli.IsJson() { - cli.Printer.PrintJson(map[string]interface{}{ - "uploaded-stackpack": stackpack, - }) - } else { - cli.Printer.Success(fmt.Sprintf("uploaded StackPack: %s", args.FilePath)) - cli.Printer.Table(printer.TableData{ - Header: []string{"name", "display name", "version"}, - Data: [][]interface{}{{stackpack.Name, stackpack.DisplayName, stackpack.Version}}, - }) - } + if cli.IsJson() { + cli.Printer.PrintJson(map[string]interface{}{ + "uploaded-stackpack": stackpack, + }) + } else { + cli.Printer.Success(fmt.Sprintf("uploaded StackPack: %s", args.FilePath)) + cli.Printer.Table(printer.TableData{ + Header: []string{"name", "display name", "version"}, + Data: [][]interface{}{{stackpack.Name, stackpack.DisplayName, stackpack.Version}}, + }) } return nil