From 0f6b0efb013df662ad30578475d6142a4588a936 Mon Sep 17 00:00:00 2001 From: Simon Herrmann Date: Thu, 18 Dec 2025 15:37:32 +0100 Subject: [PATCH 1/8] feat: add basic smoketest functionality --- .mockery.yml | 4 + cli/cmd/root.go | 3 + cli/cmd/smoketest.go | 27 ++ cli/cmd/smoketest_codesphere.go | 322 ++++++++++++++++++++++ cli/cmd/smoketest_codesphere_test.go | 390 +++++++++++++++++++++++++++ docs/README.md | 1 + docs/oms-cli.md | 1 + docs/oms-cli_smoketest.md | 19 ++ docs/oms-cli_smoketest_codesphere.md | 52 ++++ go.mod | 1 + go.sum | 2 + internal/codesphere/codesphere.go | 114 ++++++++ internal/codesphere/mocks.go | 331 +++++++++++++++++++++++ 13 files changed, 1267 insertions(+) create mode 100644 cli/cmd/smoketest.go create mode 100644 cli/cmd/smoketest_codesphere.go create mode 100644 cli/cmd/smoketest_codesphere_test.go create mode 100644 docs/oms-cli_smoketest.md create mode 100644 docs/oms-cli_smoketest_codesphere.md create mode 100644 internal/codesphere/codesphere.go create mode 100644 internal/codesphere/mocks.go diff --git a/.mockery.yml b/.mockery.yml index fba11d22..77eb5b0b 100644 --- a/.mockery.yml +++ b/.mockery.yml @@ -42,3 +42,7 @@ packages: config: all: true interfaces: + github.com/codesphere-cloud/oms/pkg/codesphere: + config: + all: true + interfaces: diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 43d08c3a..10afd54f 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -72,6 +72,9 @@ func GetRootCmd() *cobra.Command { AddRegisterCmd(rootCmd, opts) AddRevokeCmd(rootCmd, opts) + // Smoke test commands + AddSmoketestCmd(rootCmd, opts) + return rootCmd } diff --git a/cli/cmd/smoketest.go b/cli/cmd/smoketest.go new file mode 100644 index 00000000..a19ba4aa --- /dev/null +++ b/cli/cmd/smoketest.go @@ -0,0 +1,27 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "github.com/codesphere-cloud/cs-go/pkg/io" + "github.com/spf13/cobra" +) + +// SmoketestCmd represents the smoketest command +type SmoketestCmd struct { + cmd *cobra.Command +} + +func AddSmoketestCmd(rootCmd *cobra.Command, opts *GlobalOptions) { + smoketest := SmoketestCmd{ + cmd: &cobra.Command{ + Use: "smoketest", + Short: "Run smoke tests for Codesphere components", + Long: io.Long(`Run automated smoke tests for Codesphere installations to verify functionality.`), + }, + } + rootCmd.AddCommand(smoketest.cmd) + + AddSmoketestCodesphereCmd(smoketest.cmd, opts) +} diff --git a/cli/cmd/smoketest_codesphere.go b/cli/cmd/smoketest_codesphere.go new file mode 100644 index 00000000..caedace7 --- /dev/null +++ b/cli/cmd/smoketest_codesphere.go @@ -0,0 +1,322 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "context" + "fmt" + "slices" + "strconv" + "strings" + "time" + + "github.com/codesphere-cloud/cs-go/pkg/io" + "github.com/codesphere-cloud/oms/internal/codesphere" + "github.com/codesphere-cloud/oms/internal/util" + "github.com/spf13/cobra" +) + +const ( + defaultTimeout = 10 * time.Minute + defaultProfile = "ci.yml" + smoketestEnvVarKey = "TEST_VAR" + smoketestEnvVarValue = "smoketest" + smoketestPipelineStage = "run" + + // Step names + stepCreateWorkspace = "createWorkspace" + stepSetEnvVar = "setEnvVar" + stepCreateFiles = "createFiles" + stepSyncLandscape = "syncLandscape" + stepStartPipeline = "startPipeline" + stepDeleteWorkspace = "deleteWorkspace" + + ciYmlContent = `schemaVersion: v0.2 +prepare: + steps: [] +test: + steps: [] +run: + service: + steps: + - name: Run php server + command: php -S 0.0.0.0:3000 index.html + plan: 20 + replicas: 1 + network: + ports: + - port: 3000 + isPublic: true + paths: + - port: 3000 + path: / + env: {} +` + + indexHtmlContent = ` + + + Smoketest + + +

Smoketest Successful

+ + +` + + // ANSI color codes + colorGreen = "\033[32m" + colorRed = "\033[31m" + colorReset = "\033[0m" +) + +type SmoketestCodesphereCmd struct { + cmd *cobra.Command + Opts *SmoketestCodesphereOpts + Client codesphere.Client +} + +type SmoketestCodesphereOpts struct { + *GlobalOptions + BaseURL string + Token string + TeamID string + PlanID string + Quiet bool + Timeout time.Duration + Profile string + Steps string +} + +func (c *SmoketestCodesphereCmd) RunE(_ *cobra.Command, args []string) error { + // Initialize client if not set (for testing) + if c.Client == nil { + client, err := codesphere.NewClient(c.Opts.BaseURL, c.Opts.Token) + if err != nil { + return fmt.Errorf("failed to create Codesphere client: %w", err) + } + c.Client = client + } + + return c.RunSmoketest() +} + +func AddSmoketestCodesphereCmd(parent *cobra.Command, opts *GlobalOptions) { + c := SmoketestCodesphereCmd{ + cmd: &cobra.Command{ + Use: "codesphere", + Short: "Run smoke tests for a Codesphere installation", + Long: io.Long(`Run automated smoke tests for a Codesphere installation by creating a workspace, + setting environment variables, executing commands, syncing landscape, and running a pipeline stage. + The workspace is automatically deleted after the test completes.`), + Example: formatExamplesWithBinary("smoketest codesphere", []io.Example{ + { + Cmd: "--baseurl https://codesphere.example.com/api --token YOUR_TOKEN --team-id TEAM_ID --plan-id PLAN_ID", + Desc: "Run smoke tests against a Codesphere installation", + }, + { + Cmd: "--baseurl https://codesphere.example.com/api --token YOUR_TOKEN --team-id TEAM_ID --plan-id PLAN_ID --quiet", + Desc: "Run smoke tests in quiet mode (no progress logging)", + }, + { + Cmd: "--baseurl https://codesphere.example.com/api --token YOUR_TOKEN --team-id TEAM_ID --plan-id PLAN_ID --timeout 15m", + Desc: "Run smoke tests with custom timeout", + }, + { + Cmd: "--baseurl https://codesphere.example.com/api --token YOUR_TOKEN --team-id TEAM_ID --plan-id PLAN_ID --steps createWorkspace,syncLandscape", + Desc: "Run only specific steps of the smoke test (workspace won't be deleted)", + }, + { + Cmd: "--baseurl https://codesphere.example.com/api --token YOUR_TOKEN --team-id TEAM_ID --plan-id PLAN_ID --steps createWorkspace,syncLandscape,deleteWorkspace", + Desc: "Run specific steps and delete the workspace afterwards", + }, + }, "oms-cli"), + }, + Opts: &SmoketestCodesphereOpts{GlobalOptions: opts}, + } + c.cmd.Flags().StringVar(&c.Opts.BaseURL, "baseurl", "", "Base URL of the Codesphere API") + c.cmd.Flags().StringVar(&c.Opts.Token, "token", "", "API token for authentication") + c.cmd.Flags().StringVar(&c.Opts.TeamID, "team-id", "", "Team ID for workspace creation") + c.cmd.Flags().StringVar(&c.Opts.PlanID, "plan-id", "", "Plan ID for workspace creation") + c.cmd.Flags().BoolVarP(&c.Opts.Quiet, "quiet", "q", false, "Suppress progress logging") + c.cmd.Flags().DurationVar(&c.Opts.Timeout, "timeout", defaultTimeout, "Timeout for the entire smoke test") + c.cmd.Flags().StringVar(&c.Opts.Profile, "profile", defaultProfile, "CI profile to use for landscape and pipeline") + c.cmd.Flags().StringVar(&c.Opts.Steps, "steps", "", "Comma-separated list of steps to run (createWorkspace,setEnvVar,createFiles,syncLandscape,startPipeline,deleteWorkspace). If empty, all steps including deleteWorkspace are run. If specified without deleteWorkspace, the workspace will be kept for manual inspection.") + + util.MarkFlagRequired(c.cmd, "baseurl") + util.MarkFlagRequired(c.cmd, "token") + util.MarkFlagRequired(c.cmd, "team-id") + util.MarkFlagRequired(c.cmd, "plan-id") + + c.cmd.RunE = c.RunE + + parent.AddCommand(c.cmd) +} + +func (c *SmoketestCodesphereCmd) RunSmoketest() (err error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Opts.Timeout) + defer cancel() + + teamID, parseErr := strconv.Atoi(c.Opts.TeamID) + if parseErr != nil { + return fmt.Errorf("invalid team-id: %w", parseErr) + } + planID, parseErr := strconv.Atoi(c.Opts.PlanID) + if parseErr != nil { + return fmt.Errorf("invalid plan-id: %w", parseErr) + } + steps := []string{stepCreateWorkspace, stepSetEnvVar, stepCreateFiles, stepSyncLandscape, stepStartPipeline, stepDeleteWorkspace} + if c.Opts.Steps != "" { + steps = strings.Split(c.Opts.Steps, ",") + for i := range steps { + steps[i] = strings.TrimSpace(steps[i]) + } + } + workspaceName := fmt.Sprintf("smoketest-%s", time.Now().Format("20060102-150405")) + + var workspaceID int + defer func() { + if err != nil { + c.logf("\n%sSmoketest failed: %s%s\n", colorRed, err.Error(), colorReset) + } + + if workspaceID != 0 && slices.Contains(steps, stepDeleteWorkspace) { + c.logStep(fmt.Sprintf("\nDeleting workspace %d", workspaceID)) + deleteErr := c.Client.DeleteWorkspace(context.Background(), workspaceID) + if deleteErr != nil { + c.logFailure() + if err == nil { + err = fmt.Errorf("failed to delete workspace: %w", deleteErr) + } + } + c.logSuccess() + } + + if err == nil { + c.logf("\n%sSmoketest completed successfully!%s\n", colorGreen, colorReset) + } + }() + + // Execute steps + for _, step := range steps { + switch step { + case stepCreateWorkspace: + if err = c.stepCreateWorkspace(ctx, teamID, planID, workspaceName, &workspaceID); err != nil { + return err + } + case stepSetEnvVar: + if err = c.stepSetEnvVar(ctx, workspaceID); err != nil { + return err + } + case stepCreateFiles: + if err = c.stepCreateFiles(ctx, workspaceID); err != nil { + return err + } + case stepSyncLandscape: + if err = c.stepSyncLandscape(ctx, workspaceID); err != nil { + return err + } + case stepStartPipeline: + if err = c.stepStartPipeline(ctx, workspaceID); err != nil { + return err + } + case stepDeleteWorkspace: + // Skip - handled in defer + continue + default: + return fmt.Errorf("unknown step: %s", step) + } + } + + return nil +} + +func (c *SmoketestCodesphereCmd) stepCreateWorkspace(ctx context.Context, teamID, planID int, workspaceName string, workspaceID *int) error { + c.logStep(fmt.Sprintf("Creating empty workspace '%s'", workspaceName)) + id, err := c.Client.CreateWorkspace(ctx, teamID, planID, workspaceName, nil) + if err != nil { + c.logFailure() + return fmt.Errorf("failed to create workspace: %w", err) + } + *workspaceID = id + c.logSuccess() + return nil +} + +func (c *SmoketestCodesphereCmd) stepSetEnvVar(ctx context.Context, workspaceID int) error { + c.logStep(fmt.Sprintf("Setting environment variable %s=%s", smoketestEnvVarKey, smoketestEnvVarValue)) + if err := c.Client.SetEnvVar(ctx, workspaceID, smoketestEnvVarKey, smoketestEnvVarValue); err != nil { + c.logFailure() + return fmt.Errorf("failed to set environment variable: %w", err) + } + c.logSuccess() + return nil +} + +func (c *SmoketestCodesphereCmd) stepCreateFiles(ctx context.Context, workspaceID int) error { + c.logStep("Creating ci.yml file") + ciYmlCmd := fmt.Sprintf(`echo '%s' > ci.yml`, ciYmlContent) + err := c.Client.ExecuteCommand(ctx, workspaceID, ciYmlCmd) + if err != nil { + c.logFailure() + return fmt.Errorf("failed to create ci.yml: %w", err) + } + c.logSuccess() + + c.logStep("Creating index.html file") + indexHtmlCmd := fmt.Sprintf(`echo '%s' > index.html`, indexHtmlContent) + err = c.Client.ExecuteCommand(ctx, workspaceID, indexHtmlCmd) + if err != nil { + c.logFailure() + return fmt.Errorf("failed to create index.html: %w", err) + } + c.logSuccess() + return nil +} + +func (c *SmoketestCodesphereCmd) stepSyncLandscape(ctx context.Context, workspaceID int) error { + c.logStep(fmt.Sprintf("Syncing landscape with profile '%s'", c.Opts.Profile)) + if err := c.Client.SyncLandscape(ctx, workspaceID, c.Opts.Profile); err != nil { + c.logFailure() + return fmt.Errorf("failed to sync landscape: %w", err) + } + c.logSuccess() + return nil +} + +func (c *SmoketestCodesphereCmd) stepStartPipeline(ctx context.Context, workspaceID int) error { + c.logStep(fmt.Sprintf("Starting '%s' pipeline stage", smoketestPipelineStage)) + if err := c.Client.StartPipeline(ctx, workspaceID, c.Opts.Profile, smoketestPipelineStage); err != nil { + c.logFailure() + return fmt.Errorf("failed to start pipeline: %w", err) + } + c.logSuccess() + return nil +} + +// Logging helpers + +func (c *SmoketestCodesphereCmd) logf(format string, args ...interface{}) { + if !c.Opts.Quiet { + fmt.Printf(format, args...) + } +} + +func (c *SmoketestCodesphereCmd) logStep(message string) { + if !c.Opts.Quiet { + fmt.Printf("%s...", message) + } +} + +func (c *SmoketestCodesphereCmd) logSuccess() { + if !c.Opts.Quiet { + fmt.Printf(" %ssucceeded%s\n", colorGreen, colorReset) + } +} + +func (c *SmoketestCodesphereCmd) logFailure() { + if !c.Opts.Quiet { + fmt.Printf(" %sfailed%s\n", colorRed, colorReset) + } +} diff --git a/cli/cmd/smoketest_codesphere_test.go b/cli/cmd/smoketest_codesphere_test.go new file mode 100644 index 00000000..c1d1822b --- /dev/null +++ b/cli/cmd/smoketest_codesphere_test.go @@ -0,0 +1,390 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package cmd_test + +import ( + "fmt" + "strings" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/spf13/cobra" + "github.com/stretchr/testify/mock" + + "github.com/codesphere-cloud/oms/cli/cmd" + "github.com/codesphere-cloud/oms/internal/codesphere" +) + +var _ = Describe("SmoketestCodesphereCmd", func() { + var ( + mockClient *codesphere.MockClient + c cmd.SmoketestCodesphereCmd + opts *cmd.SmoketestCodesphereOpts + ) + + BeforeEach(func() { + mockClient = codesphere.NewMockClient(GinkgoT()) + opts = &cmd.SmoketestCodesphereOpts{ + BaseURL: "https://test.codesphere.com/api", + Token: "test-token", + TeamID: "123", + PlanID: "456", + Quiet: true, // Suppress log output in tests + Timeout: 10 * time.Minute, + Profile: "ci.yml", + } + c = cmd.SmoketestCodesphereCmd{ + Opts: opts, + Client: mockClient, + } + }) + + AfterEach(func() { + mockClient.AssertExpectations(GinkgoT()) + }) + + Context("RunSmoketest", func() { + It("completes successfully with all steps", func() { + workspaceID := 789 + + // Expect all API calls in order + mockClient.EXPECT().CreateWorkspace( + mock.Anything, + 123, // teamID + 456, // planID + mock.AnythingOfType("string"), // workspace name is timestamped + (*string)(nil), // empty workspace + ).Return(workspaceID, nil).Once() + + mockClient.EXPECT().SetEnvVar( + mock.Anything, + workspaceID, + "TEST_VAR", + "smoketest", + ).Return(nil).Once() + + // Create ci.yml + mockClient.EXPECT().ExecuteCommand( + mock.Anything, + workspaceID, + mock.MatchedBy(func(cmd string) bool { + return strings.Contains(cmd, "> ci.yml") + }), + ).Return(nil).Once() + + // Create index.html + mockClient.EXPECT().ExecuteCommand( + mock.Anything, + workspaceID, + mock.MatchedBy(func(cmd string) bool { + return strings.Contains(cmd, "> index.html") + }), + ).Return(nil).Once() + + mockClient.EXPECT().SyncLandscape( + mock.Anything, + workspaceID, + "ci.yml", + ).Return(nil).Once() + + mockClient.EXPECT().StartPipeline( + mock.Anything, + workspaceID, + "ci.yml", + "run", + ).Return(nil).Once() + + mockClient.EXPECT().DeleteWorkspace( + mock.Anything, + workspaceID, + ).Return(nil).Once() + + err := c.RunSmoketest() + Expect(err).To(BeNil()) + }) + + It("deletes workspace even on CreateWorkspace failure", func() { + mockClient.EXPECT().CreateWorkspace( + mock.Anything, + 123, // teamID + 456, // planID + mock.AnythingOfType("string"), + (*string)(nil), // empty workspace + ).Return(0, fmt.Errorf("create failed")).Once() + + err := c.RunSmoketest() + Expect(err).To(MatchError(ContainSubstring("failed to create workspace"))) + }) + + It("deletes workspace on SetEnvVar failure", func() { + workspaceID := 789 + + mockClient.EXPECT().CreateWorkspace( + mock.Anything, + 123, // teamID + 456, // planID + mock.AnythingOfType("string"), + (*string)(nil), // empty workspace + ).Return(workspaceID, nil).Once() + + mockClient.EXPECT().SetEnvVar( + mock.Anything, + workspaceID, + "TEST_VAR", + "smoketest", + ).Return(fmt.Errorf("setenv failed")).Once() + + mockClient.EXPECT().DeleteWorkspace( + mock.Anything, + workspaceID, + ).Return(nil).Once() + + err := c.RunSmoketest() + Expect(err).To(MatchError(ContainSubstring("failed to set environment variable"))) + }) + + It("deletes workspace on ExecuteCommand failure", func() { + workspaceID := 789 + + mockClient.EXPECT().CreateWorkspace( + mock.Anything, + 123, // teamID + 456, // planID + mock.AnythingOfType("string"), + (*string)(nil), // empty workspace + ).Return(workspaceID, nil).Once() + + mockClient.EXPECT().SetEnvVar( + mock.Anything, + workspaceID, + "TEST_VAR", + "smoketest", + ).Return(nil).Once() + + // Create ci.yml fails + mockClient.EXPECT().ExecuteCommand( + mock.Anything, + workspaceID, + mock.MatchedBy(func(cmd string) bool { + return strings.Contains(cmd, "> ci.yml") + }), + ).Return(fmt.Errorf("exec failed")).Once() + + mockClient.EXPECT().DeleteWorkspace( + mock.Anything, + workspaceID, + ).Return(nil).Once() + + err := c.RunSmoketest() + Expect(err).To(MatchError(ContainSubstring("failed to create ci.yml"))) + }) + + It("deletes workspace on SyncLandscape failure", func() { + workspaceID := 789 + + mockClient.EXPECT().CreateWorkspace( + mock.Anything, + 123, // teamID + 456, // planID + mock.AnythingOfType("string"), + (*string)(nil), // empty workspace + ).Return(workspaceID, nil).Once() + + mockClient.EXPECT().SetEnvVar( + mock.Anything, + workspaceID, + "TEST_VAR", + "smoketest", + ).Return(nil).Once() + + // Create ci.yml + mockClient.EXPECT().ExecuteCommand( + mock.Anything, + workspaceID, + mock.MatchedBy(func(cmd string) bool { + return strings.Contains(cmd, "> ci.yml") + }), + ).Return(nil).Once() + + // Create index.html + mockClient.EXPECT().ExecuteCommand( + mock.Anything, + workspaceID, + mock.MatchedBy(func(cmd string) bool { + return strings.Contains(cmd, "> index.html") + }), + ).Return(nil).Once() + + mockClient.EXPECT().SyncLandscape( + mock.Anything, + workspaceID, + "ci.yml", + ).Return(fmt.Errorf("sync failed")).Once() + + mockClient.EXPECT().DeleteWorkspace( + mock.Anything, + workspaceID, + ).Return(nil).Once() + + err := c.RunSmoketest() + Expect(err).To(MatchError(ContainSubstring("failed to sync landscape"))) + }) + + It("deletes workspace on StartPipeline failure", func() { + workspaceID := 789 + + mockClient.EXPECT().CreateWorkspace( + mock.Anything, + 123, // teamID + 456, // planID + mock.AnythingOfType("string"), + (*string)(nil), // empty workspace + ).Return(workspaceID, nil).Once() + + mockClient.EXPECT().SetEnvVar( + mock.Anything, + workspaceID, + "TEST_VAR", + "smoketest", + ).Return(nil).Once() + + // Create ci.yml + mockClient.EXPECT().ExecuteCommand( + mock.Anything, + workspaceID, + mock.MatchedBy(func(cmd string) bool { + return strings.Contains(cmd, "> ci.yml") + }), + ).Return(nil).Once() + + // Create index.html + mockClient.EXPECT().ExecuteCommand( + mock.Anything, + workspaceID, + mock.MatchedBy(func(cmd string) bool { + return strings.Contains(cmd, "> index.html") + }), + ).Return(nil).Once() + + mockClient.EXPECT().SyncLandscape( + mock.Anything, + workspaceID, + "ci.yml", + ).Return(nil).Once() + + mockClient.EXPECT().StartPipeline( + mock.Anything, + workspaceID, + "ci.yml", + "run", + ).Return(fmt.Errorf("pipeline failed")).Once() + + mockClient.EXPECT().DeleteWorkspace( + mock.Anything, + workspaceID, + ).Return(nil).Once() + + err := c.RunSmoketest() + Expect(err).To(MatchError(ContainSubstring("failed to start pipeline"))) + }) + + It("returns cleanup error when DeleteWorkspace fails", func() { + workspaceID := 789 + + mockClient.EXPECT().CreateWorkspace( + mock.Anything, + 123, // teamID + 456, // planID + mock.AnythingOfType("string"), + (*string)(nil), // empty workspace + ).Return(workspaceID, nil).Once() + + mockClient.EXPECT().SetEnvVar( + mock.Anything, + workspaceID, + "TEST_VAR", + "smoketest", + ).Return(nil).Once() + + // Create ci.yml + mockClient.EXPECT().ExecuteCommand( + mock.Anything, + workspaceID, + mock.MatchedBy(func(cmd string) bool { + return strings.Contains(cmd, "> ci.yml") + }), + ).Return(nil).Once() + + // Create index.html + mockClient.EXPECT().ExecuteCommand( + mock.Anything, + workspaceID, + mock.MatchedBy(func(cmd string) bool { + return strings.Contains(cmd, "> index.html") + }), + ).Return(nil).Once() + + mockClient.EXPECT().SyncLandscape( + mock.Anything, + workspaceID, + "ci.yml", + ).Return(nil).Once() + + mockClient.EXPECT().StartPipeline( + mock.Anything, + workspaceID, + "ci.yml", + "run", + ).Return(nil).Once() + + mockClient.EXPECT().DeleteWorkspace( + mock.Anything, + workspaceID, + ).Return(fmt.Errorf("delete failed")).Once() + + err := c.RunSmoketest() + Expect(err).To(MatchError(ContainSubstring("failed to delete workspace"))) + }) + + It("runs only specified steps when steps flag is set", func() { + workspaceID := 789 + opts.Steps = "createWorkspace,setEnvVar" + + mockClient.EXPECT().CreateWorkspace( + mock.Anything, + 123, // teamID + 456, // planID + mock.AnythingOfType("string"), + (*string)(nil), + ).Return(workspaceID, nil).Once() + + mockClient.EXPECT().SetEnvVar( + mock.Anything, + workspaceID, + "TEST_VAR", + "smoketest", + ).Return(nil).Once() + + err := c.RunSmoketest() + Expect(err).To(BeNil()) + }) + }) +}) + +var _ = Describe("AddSmoketestCodesphereCmd", func() { + It("adds the smoketest codesphere command to the parent", func() { + parent := &cobra.Command{} + opts := &cmd.GlobalOptions{} + cmd.AddSmoketestCodesphereCmd(parent, opts) + found := false + for _, c := range parent.Commands() { + if c.Use == "codesphere" { + found = true + break + } + } + Expect(found).To(BeTrue()) + }) +}) diff --git a/docs/README.md b/docs/README.md index 64087115..f24192cf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -26,6 +26,7 @@ like downloading new versions. * [oms-cli list](oms-cli_list.md) - List resources available through OMS * [oms-cli register](oms-cli_register.md) - Register a new API key * [oms-cli revoke](oms-cli_revoke.md) - Revoke resources available through OMS +* [oms-cli smoketest](oms-cli_smoketest.md) - Run smoke tests for Codesphere components * [oms-cli update](oms-cli_update.md) - Update OMS related resources * [oms-cli version](oms-cli_version.md) - Print version diff --git a/docs/oms-cli.md b/docs/oms-cli.md index 64087115..f24192cf 100644 --- a/docs/oms-cli.md +++ b/docs/oms-cli.md @@ -26,6 +26,7 @@ like downloading new versions. * [oms-cli list](oms-cli_list.md) - List resources available through OMS * [oms-cli register](oms-cli_register.md) - Register a new API key * [oms-cli revoke](oms-cli_revoke.md) - Revoke resources available through OMS +* [oms-cli smoketest](oms-cli_smoketest.md) - Run smoke tests for Codesphere components * [oms-cli update](oms-cli_update.md) - Update OMS related resources * [oms-cli version](oms-cli_version.md) - Print version diff --git a/docs/oms-cli_smoketest.md b/docs/oms-cli_smoketest.md new file mode 100644 index 00000000..7b2c87d8 --- /dev/null +++ b/docs/oms-cli_smoketest.md @@ -0,0 +1,19 @@ +## oms-cli smoketest + +Run smoke tests for Codesphere components + +### Synopsis + +Run automated smoke tests for Codesphere installations to verify functionality. + +### Options + +``` + -h, --help help for smoketest +``` + +### SEE ALSO + +* [oms-cli](oms-cli.md) - Codesphere Operations Management System (OMS) +* [oms-cli smoketest codesphere](oms-cli_smoketest_codesphere.md) - Run smoke tests for a Codesphere installation + diff --git a/docs/oms-cli_smoketest_codesphere.md b/docs/oms-cli_smoketest_codesphere.md new file mode 100644 index 00000000..ac90f0fc --- /dev/null +++ b/docs/oms-cli_smoketest_codesphere.md @@ -0,0 +1,52 @@ +## oms-cli smoketest codesphere + +Run smoke tests for a Codesphere installation + +### Synopsis + +Run automated smoke tests for a Codesphere installation by creating a workspace, +setting environment variables, executing commands, syncing landscape, and running a pipeline stage. +The workspace is automatically deleted after the test completes. + +``` +oms-cli smoketest codesphere [flags] +``` + +### Examples + +``` +# Run smoke tests against a Codesphere installation +$ oms-cli smoketest codesphere --baseurl https://codesphere.example.com/api --token YOUR_TOKEN --team-id TEAM_ID --plan-id PLAN_ID + +# Run smoke tests in quiet mode (no progress logging) +$ oms-cli smoketest codesphere --baseurl https://codesphere.example.com/api --token YOUR_TOKEN --team-id TEAM_ID --plan-id PLAN_ID --quiet + +# Run smoke tests with custom timeout +$ oms-cli smoketest codesphere --baseurl https://codesphere.example.com/api --token YOUR_TOKEN --team-id TEAM_ID --plan-id PLAN_ID --timeout 15m + +# Run only specific steps of the smoke test (workspace won't be deleted) +$ oms-cli smoketest codesphere --baseurl https://codesphere.example.com/api --token YOUR_TOKEN --team-id TEAM_ID --plan-id PLAN_ID --steps createWorkspace,syncLandscape + +# Run specific steps and delete the workspace afterwards +$ oms-cli smoketest codesphere --baseurl https://codesphere.example.com/api --token YOUR_TOKEN --team-id TEAM_ID --plan-id PLAN_ID --steps createWorkspace,syncLandscape,deleteWorkspace + +``` + +### Options + +``` + --baseurl string Base URL of the Codesphere API + -h, --help help for codesphere + --plan-id string Plan ID for workspace creation + --profile string CI profile to use for landscape and pipeline (default "ci.yml") + -q, --quiet Suppress progress logging + --steps string Comma-separated list of steps to run (createWorkspace,setEnvVar,createFiles,syncLandscape,startPipeline,deleteWorkspace). If empty, all steps including deleteWorkspace are run. If specified without deleteWorkspace, the workspace will be kept for manual inspection. + --team-id string Team ID for workspace creation + --timeout duration Timeout for the entire smoke test (default 10m0s) + --token string API token for authentication +``` + +### SEE ALSO + +* [oms-cli smoketest](oms-cli_smoketest.md) - Run smoke tests for Codesphere components + diff --git a/go.mod b/go.mod index f1951b91..ef912ee7 100644 --- a/go.mod +++ b/go.mod @@ -490,6 +490,7 @@ require ( google.golang.org/grpc v1.75.0 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/mail.v2 v2.3.1 // indirect + gopkg.in/validator.v2 v2.0.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect honnef.co/go/tools v0.6.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect diff --git a/go.sum b/go.sum index 017a4cfa..65eac840 100644 --- a/go.sum +++ b/go.sum @@ -1566,6 +1566,8 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY= +gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/codesphere/codesphere.go b/internal/codesphere/codesphere.go new file mode 100644 index 00000000..f25462c8 --- /dev/null +++ b/internal/codesphere/codesphere.go @@ -0,0 +1,114 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + +package codesphere + +import ( + "context" + "fmt" + "net/url" + "time" + + "github.com/codesphere-cloud/cs-go/api" +) + +// Client interface abstracts Codesphere API operations for testing +type Client interface { + CreateWorkspace(ctx context.Context, teamID, planID int, name string, repoURL *string) (workspaceID int, err error) + SetEnvVar(ctx context.Context, workspaceID int, key, value string) error + ExecuteCommand(ctx context.Context, workspaceID int, command string) error + SyncLandscape(ctx context.Context, workspaceID int, profile string) error + StartPipeline(ctx context.Context, workspaceID int, profile, stage string) error + DeleteWorkspace(ctx context.Context, workspaceID int) error +} + +// APIClient wraps the cs-go API client +type APIClient struct { + client *api.Client +} + +// NewClient creates a new Codesphere API client +func NewClient(baseURL, token string) (Client, error) { + if baseURL == "" { + return nil, fmt.Errorf("baseURL is required") + } + if token == "" { + return nil, fmt.Errorf("token is required") + } + + parsedURL, err := url.Parse(baseURL) + if err != nil { + return nil, fmt.Errorf("invalid baseURL: %w", err) + } + + ctx := context.Background() + client := api.NewClient(ctx, api.Configuration{ + BaseUrl: parsedURL, + Token: token, + }) + + return &APIClient{client: client}, nil +} + +// CreateWorkspace creates a new workspace and waits for it to be running +func (c *APIClient) CreateWorkspace(ctx context.Context, teamID, planID int, name string, repoURL *string) (int, error) { + workspace, err := c.client.DeployWorkspace(api.DeployWorkspaceArgs{ + TeamId: teamID, + PlanId: planID, + Name: name, + GitUrl: repoURL, + Timeout: 10 * time.Minute, + EnvVars: map[string]string{}, // Empty map to avoid null + IsPrivateRepo: true, + }) + if err != nil { + return 0, fmt.Errorf("failed to create workspace: %w", err) + } + return workspace.Id, nil +} + +// SetEnvVar sets an environment variable in the workspace +func (c *APIClient) SetEnvVar(ctx context.Context, workspaceID int, key, value string) error { + envVars := map[string]string{key: value} + err := c.client.SetEnvVarOnWorkspace(workspaceID, envVars) + if err != nil { + return fmt.Errorf("failed to set environment variable: %w", err) + } + return nil +} + +// ExecuteCommand executes a command in the workspace +func (c *APIClient) ExecuteCommand(ctx context.Context, workspaceID int, command string) error { + _, _, err := c.client.ExecCommand(workspaceID, command, "", map[string]string{}) + if err != nil { + return fmt.Errorf("failed to execute command: %w", err) + } + return nil +} + +// SyncLandscape syncs the landscape/CI configuration +func (c *APIClient) SyncLandscape(ctx context.Context, workspaceID int, profile string) error { + err := c.client.DeployLandscape(workspaceID, profile) + if err != nil { + return fmt.Errorf("failed to sync landscape: %w", err) + } + return nil +} + +// StartPipeline starts a pipeline stage +func (c *APIClient) StartPipeline(ctx context.Context, workspaceID int, profile, stage string) error { + err := c.client.StartPipelineStage(workspaceID, profile, stage) + if err != nil { + return fmt.Errorf("failed to start pipeline: %w", err) + } + return nil +} + +// DeleteWorkspace deletes a workspace +func (c *APIClient) DeleteWorkspace(ctx context.Context, workspaceID int) error { + err := c.client.DeleteWorkspace(workspaceID) + if err != nil { + return fmt.Errorf("failed to delete workspace: %w", err) + } + return nil +} diff --git a/internal/codesphere/mocks.go b/internal/codesphere/mocks.go new file mode 100644 index 00000000..bb486ef5 --- /dev/null +++ b/internal/codesphere/mocks.go @@ -0,0 +1,331 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package codesphere + +import ( + "context" + mock "github.com/stretchr/testify/mock" +) + +// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockClient(t interface { + mock.TestingT + Cleanup(func()) +}) *MockClient { + mock := &MockClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// MockClient is an autogenerated mock type for the Client type +type MockClient struct { + mock.Mock +} + +type MockClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockClient) EXPECT() *MockClient_Expecter { + return &MockClient_Expecter{mock: &_m.Mock} +} + +// CreateWorkspace provides a mock function for the type MockClient +func (_mock *MockClient) CreateWorkspace(ctx context.Context, teamID int, planID int, name string, repoURL *string) (int, error) { + ret := _mock.Called(ctx, teamID, planID, name, repoURL) + + if len(ret) == 0 { + panic("no return value specified for CreateWorkspace") + } + + var r0 int + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, int, int, string, *string) (int, error)); ok { + return returnFunc(ctx, teamID, planID, name, repoURL) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, int, int, string, *string) int); ok { + r0 = returnFunc(ctx, teamID, planID, name, repoURL) + } else { + r0 = ret.Get(0).(int) + } + if returnFunc, ok := ret.Get(1).(func(context.Context, int, int, string, *string) error); ok { + r1 = returnFunc(ctx, teamID, planID, name, repoURL) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_CreateWorkspace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateWorkspace' +type MockClient_CreateWorkspace_Call struct { + *mock.Call +} + +// CreateWorkspace is a helper method to define mock.On call +// - ctx +// - teamID +// - planID +// - name +// - repoURL +func (_e *MockClient_Expecter) CreateWorkspace(ctx interface{}, teamID interface{}, planID interface{}, name interface{}, repoURL interface{}) *MockClient_CreateWorkspace_Call { + return &MockClient_CreateWorkspace_Call{Call: _e.mock.On("CreateWorkspace", ctx, teamID, planID, name, repoURL)} +} + +func (_c *MockClient_CreateWorkspace_Call) Run(run func(ctx context.Context, teamID int, planID int, name string, repoURL *string)) *MockClient_CreateWorkspace_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int), args[2].(int), args[3].(string), args[4].(*string)) + }) + return _c +} + +func (_c *MockClient_CreateWorkspace_Call) Return(workspaceID int, err error) *MockClient_CreateWorkspace_Call { + _c.Call.Return(workspaceID, err) + return _c +} + +func (_c *MockClient_CreateWorkspace_Call) RunAndReturn(run func(ctx context.Context, teamID int, planID int, name string, repoURL *string) (int, error)) *MockClient_CreateWorkspace_Call { + _c.Call.Return(run) + return _c +} + +// DeleteWorkspace provides a mock function for the type MockClient +func (_mock *MockClient) DeleteWorkspace(ctx context.Context, workspaceID int) error { + ret := _mock.Called(ctx, workspaceID) + + if len(ret) == 0 { + panic("no return value specified for DeleteWorkspace") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, int) error); ok { + r0 = returnFunc(ctx, workspaceID) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_DeleteWorkspace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteWorkspace' +type MockClient_DeleteWorkspace_Call struct { + *mock.Call +} + +// DeleteWorkspace is a helper method to define mock.On call +// - ctx +// - workspaceID +func (_e *MockClient_Expecter) DeleteWorkspace(ctx interface{}, workspaceID interface{}) *MockClient_DeleteWorkspace_Call { + return &MockClient_DeleteWorkspace_Call{Call: _e.mock.On("DeleteWorkspace", ctx, workspaceID)} +} + +func (_c *MockClient_DeleteWorkspace_Call) Run(run func(ctx context.Context, workspaceID int)) *MockClient_DeleteWorkspace_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int)) + }) + return _c +} + +func (_c *MockClient_DeleteWorkspace_Call) Return(err error) *MockClient_DeleteWorkspace_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_DeleteWorkspace_Call) RunAndReturn(run func(ctx context.Context, workspaceID int) error) *MockClient_DeleteWorkspace_Call { + _c.Call.Return(run) + return _c +} + +// ExecuteCommand provides a mock function for the type MockClient +func (_mock *MockClient) ExecuteCommand(ctx context.Context, workspaceID int, command string) error { + ret := _mock.Called(ctx, workspaceID, command) + + if len(ret) == 0 { + panic("no return value specified for ExecuteCommand") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, int, string) error); ok { + r0 = returnFunc(ctx, workspaceID, command) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_ExecuteCommand_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExecuteCommand' +type MockClient_ExecuteCommand_Call struct { + *mock.Call +} + +// ExecuteCommand is a helper method to define mock.On call +// - ctx +// - workspaceID +// - command +func (_e *MockClient_Expecter) ExecuteCommand(ctx interface{}, workspaceID interface{}, command interface{}) *MockClient_ExecuteCommand_Call { + return &MockClient_ExecuteCommand_Call{Call: _e.mock.On("ExecuteCommand", ctx, workspaceID, command)} +} + +func (_c *MockClient_ExecuteCommand_Call) Run(run func(ctx context.Context, workspaceID int, command string)) *MockClient_ExecuteCommand_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int), args[2].(string)) + }) + return _c +} + +func (_c *MockClient_ExecuteCommand_Call) Return(err error) *MockClient_ExecuteCommand_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_ExecuteCommand_Call) RunAndReturn(run func(ctx context.Context, workspaceID int, command string) error) *MockClient_ExecuteCommand_Call { + _c.Call.Return(run) + return _c +} + +// SetEnvVar provides a mock function for the type MockClient +func (_mock *MockClient) SetEnvVar(ctx context.Context, workspaceID int, key string, value string) error { + ret := _mock.Called(ctx, workspaceID, key, value) + + if len(ret) == 0 { + panic("no return value specified for SetEnvVar") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, int, string, string) error); ok { + r0 = returnFunc(ctx, workspaceID, key, value) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_SetEnvVar_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetEnvVar' +type MockClient_SetEnvVar_Call struct { + *mock.Call +} + +// SetEnvVar is a helper method to define mock.On call +// - ctx +// - workspaceID +// - key +// - value +func (_e *MockClient_Expecter) SetEnvVar(ctx interface{}, workspaceID interface{}, key interface{}, value interface{}) *MockClient_SetEnvVar_Call { + return &MockClient_SetEnvVar_Call{Call: _e.mock.On("SetEnvVar", ctx, workspaceID, key, value)} +} + +func (_c *MockClient_SetEnvVar_Call) Run(run func(ctx context.Context, workspaceID int, key string, value string)) *MockClient_SetEnvVar_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int), args[2].(string), args[3].(string)) + }) + return _c +} + +func (_c *MockClient_SetEnvVar_Call) Return(err error) *MockClient_SetEnvVar_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_SetEnvVar_Call) RunAndReturn(run func(ctx context.Context, workspaceID int, key string, value string) error) *MockClient_SetEnvVar_Call { + _c.Call.Return(run) + return _c +} + +// StartPipeline provides a mock function for the type MockClient +func (_mock *MockClient) StartPipeline(ctx context.Context, workspaceID int, profile string, stage string) error { + ret := _mock.Called(ctx, workspaceID, profile, stage) + + if len(ret) == 0 { + panic("no return value specified for StartPipeline") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, int, string, string) error); ok { + r0 = returnFunc(ctx, workspaceID, profile, stage) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_StartPipeline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StartPipeline' +type MockClient_StartPipeline_Call struct { + *mock.Call +} + +// StartPipeline is a helper method to define mock.On call +// - ctx +// - workspaceID +// - profile +// - stage +func (_e *MockClient_Expecter) StartPipeline(ctx interface{}, workspaceID interface{}, profile interface{}, stage interface{}) *MockClient_StartPipeline_Call { + return &MockClient_StartPipeline_Call{Call: _e.mock.On("StartPipeline", ctx, workspaceID, profile, stage)} +} + +func (_c *MockClient_StartPipeline_Call) Run(run func(ctx context.Context, workspaceID int, profile string, stage string)) *MockClient_StartPipeline_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int), args[2].(string), args[3].(string)) + }) + return _c +} + +func (_c *MockClient_StartPipeline_Call) Return(err error) *MockClient_StartPipeline_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_StartPipeline_Call) RunAndReturn(run func(ctx context.Context, workspaceID int, profile string, stage string) error) *MockClient_StartPipeline_Call { + _c.Call.Return(run) + return _c +} + +// SyncLandscape provides a mock function for the type MockClient +func (_mock *MockClient) SyncLandscape(ctx context.Context, workspaceID int, profile string) error { + ret := _mock.Called(ctx, workspaceID, profile) + + if len(ret) == 0 { + panic("no return value specified for SyncLandscape") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(context.Context, int, string) error); ok { + r0 = returnFunc(ctx, workspaceID, profile) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_SyncLandscape_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SyncLandscape' +type MockClient_SyncLandscape_Call struct { + *mock.Call +} + +// SyncLandscape is a helper method to define mock.On call +// - ctx +// - workspaceID +// - profile +func (_e *MockClient_Expecter) SyncLandscape(ctx interface{}, workspaceID interface{}, profile interface{}) *MockClient_SyncLandscape_Call { + return &MockClient_SyncLandscape_Call{Call: _e.mock.On("SyncLandscape", ctx, workspaceID, profile)} +} + +func (_c *MockClient_SyncLandscape_Call) Run(run func(ctx context.Context, workspaceID int, profile string)) *MockClient_SyncLandscape_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int), args[2].(string)) + }) + return _c +} + +func (_c *MockClient_SyncLandscape_Call) Return(err error) *MockClient_SyncLandscape_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_SyncLandscape_Call) RunAndReturn(run func(ctx context.Context, workspaceID int, profile string) error) *MockClient_SyncLandscape_Call { + _c.Call.Return(run) + return _c +} From f349e8826ceebda65ae81c94c2cb60f3c1c2d0a6 Mon Sep 17 00:00:00 2001 From: siherrmann <25087590+siherrmann@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:43:38 +0000 Subject: [PATCH 2/8] chore(docs): Auto-update docs and licenses Signed-off-by: siherrmann <25087590+siherrmann@users.noreply.github.com> --- NOTICE | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/NOTICE b/NOTICE index d7e513d3..3bef1f03 100644 --- a/NOTICE +++ b/NOTICE @@ -22,10 +22,10 @@ License: MIT License URL: https://github.com/clipperhouse/uax29/blob/v2.3.0/LICENSE ---------- -Module: github.com/codesphere-cloud/cs-go/pkg/io -Version: v0.14.1 +Module: github.com/codesphere-cloud/cs-go +Version: v0.15.0 License: Apache-2.0 -License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.14.1/LICENSE +License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.15.0/LICENSE ---------- Module: github.com/codesphere-cloud/oms/internal/tmpl @@ -77,9 +77,9 @@ License URL: https://github.com/inconshreveable/go-update/blob/8152e7eb6ccf/inte ---------- Module: github.com/jedib0t/go-pretty/v6 -Version: v6.7.5 +Version: v6.7.7 License: MIT -License URL: https://github.com/jedib0t/go-pretty/blob/v6.7.5/LICENSE +License URL: https://github.com/jedib0t/go-pretty/blob/v6.7.7/LICENSE ---------- Module: github.com/mattn/go-runewidth @@ -155,9 +155,9 @@ License URL: https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE ---------- Module: golang.org/x/crypto -Version: v0.45.0 +Version: v0.46.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/crypto/+/v0.45.0:LICENSE +License URL: https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE ---------- Module: golang.org/x/oauth2 @@ -167,9 +167,15 @@ License URL: https://cs.opensource.google/go/x/oauth2/+/v0.33.0:LICENSE ---------- Module: golang.org/x/text -Version: v0.31.0 +Version: v0.32.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/text/+/v0.31.0:LICENSE +License URL: https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE + +---------- +Module: gopkg.in/validator.v2 +Version: v2.0.1 +License: Apache-2.0 +License URL: https://github.com/go-validator/validator/blob/v2.0.1/LICENSE ---------- Module: gopkg.in/yaml.v3 From 055c657c2b4327ef375d2a0b0cc900513e2d19ac Mon Sep 17 00:00:00 2001 From: Simon Herrmann Date: Tue, 13 Jan 2026 16:26:35 +0100 Subject: [PATCH 3/8] update: seperate steps from command with new interface --- cli/cmd/smoketest_codesphere.go | 261 ++++++-------------------- cli/cmd/smoketest_codesphere_test.go | 12 +- internal/codesphere/teststeps/csgo.go | 162 ++++++++++++++++ internal/codesphere/teststeps/step.go | 61 ++++++ 4 files changed, 284 insertions(+), 212 deletions(-) create mode 100644 internal/codesphere/teststeps/csgo.go create mode 100644 internal/codesphere/teststeps/step.go diff --git a/cli/cmd/smoketest_codesphere.go b/cli/cmd/smoketest_codesphere.go index caedace7..ca69b7aa 100644 --- a/cli/cmd/smoketest_codesphere.go +++ b/cli/cmd/smoketest_codesphere.go @@ -6,103 +6,56 @@ package cmd import ( "context" "fmt" + "log" "slices" - "strconv" "strings" "time" "github.com/codesphere-cloud/cs-go/pkg/io" "github.com/codesphere-cloud/oms/internal/codesphere" + "github.com/codesphere-cloud/oms/internal/codesphere/teststeps" "github.com/codesphere-cloud/oms/internal/util" "github.com/spf13/cobra" ) const ( - defaultTimeout = 10 * time.Minute - defaultProfile = "ci.yml" - smoketestEnvVarKey = "TEST_VAR" - smoketestEnvVarValue = "smoketest" - smoketestPipelineStage = "run" - - // Step names - stepCreateWorkspace = "createWorkspace" - stepSetEnvVar = "setEnvVar" - stepCreateFiles = "createFiles" - stepSyncLandscape = "syncLandscape" - stepStartPipeline = "startPipeline" - stepDeleteWorkspace = "deleteWorkspace" - - ciYmlContent = `schemaVersion: v0.2 -prepare: - steps: [] -test: - steps: [] -run: - service: - steps: - - name: Run php server - command: php -S 0.0.0.0:3000 index.html - plan: 20 - replicas: 1 - network: - ports: - - port: 3000 - isPublic: true - paths: - - port: 3000 - path: / - env: {} -` - - indexHtmlContent = ` - - - Smoketest - - -

Smoketest Successful

- - -` - - // ANSI color codes - colorGreen = "\033[32m" - colorRed = "\033[31m" - colorReset = "\033[0m" + defaultTimeout = 10 * time.Minute + defaultProfile = "ci.yml" ) -type SmoketestCodesphereCmd struct { - cmd *cobra.Command - Opts *SmoketestCodesphereOpts - Client codesphere.Client +var availableSteps = []teststeps.SmokeTestStep{ + &teststeps.CreateWorkspaceStep{}, + &teststeps.SetEnvVarStep{}, + &teststeps.CreateFilesStep{}, + &teststeps.SyncLandscapeStep{}, + &teststeps.StartPipelineStep{}, + &teststeps.DeleteWorkspaceStep{}, } -type SmoketestCodesphereOpts struct { - *GlobalOptions - BaseURL string - Token string - TeamID string - PlanID string - Quiet bool - Timeout time.Duration - Profile string - Steps string +type SmoketestCodesphereCmd struct { + cmd *cobra.Command + Opts *teststeps.SmoketestCodesphereOpts } func (c *SmoketestCodesphereCmd) RunE(_ *cobra.Command, args []string) error { // Initialize client if not set (for testing) - if c.Client == nil { + if c.Opts.Client == nil { client, err := codesphere.NewClient(c.Opts.BaseURL, c.Opts.Token) if err != nil { return fmt.Errorf("failed to create Codesphere client: %w", err) } - c.Client = client + c.Opts.Client = client } return c.RunSmoketest() } func AddSmoketestCodesphereCmd(parent *cobra.Command, opts *GlobalOptions) { + var stepNames []string + for _, s := range availableSteps { + stepNames = append(stepNames, s.Name()) + } + c := SmoketestCodesphereCmd{ cmd: &cobra.Command{ Use: "codesphere", @@ -133,7 +86,7 @@ func AddSmoketestCodesphereCmd(parent *cobra.Command, opts *GlobalOptions) { }, }, "oms-cli"), }, - Opts: &SmoketestCodesphereOpts{GlobalOptions: opts}, + Opts: &teststeps.SmoketestCodesphereOpts{}, } c.cmd.Flags().StringVar(&c.Opts.BaseURL, "baseurl", "", "Base URL of the Codesphere API") c.cmd.Flags().StringVar(&c.Opts.Token, "token", "", "API token for authentication") @@ -142,7 +95,7 @@ func AddSmoketestCodesphereCmd(parent *cobra.Command, opts *GlobalOptions) { c.cmd.Flags().BoolVarP(&c.Opts.Quiet, "quiet", "q", false, "Suppress progress logging") c.cmd.Flags().DurationVar(&c.Opts.Timeout, "timeout", defaultTimeout, "Timeout for the entire smoke test") c.cmd.Flags().StringVar(&c.Opts.Profile, "profile", defaultProfile, "CI profile to use for landscape and pipeline") - c.cmd.Flags().StringVar(&c.Opts.Steps, "steps", "", "Comma-separated list of steps to run (createWorkspace,setEnvVar,createFiles,syncLandscape,startPipeline,deleteWorkspace). If empty, all steps including deleteWorkspace are run. If specified without deleteWorkspace, the workspace will be kept for manual inspection.") + c.cmd.Flags().StringSliceVar(&c.Opts.Steps, "steps", []string{}, fmt.Sprintf("Comma-separated list of steps to run (%s). If empty, all steps including deleteWorkspace are run. If specified without deleteWorkspace, the workspace will be kept for manual inspection.", strings.Join(stepNames, ","))) util.MarkFlagRequired(c.cmd, "baseurl") util.MarkFlagRequired(c.cmd, "token") @@ -158,165 +111,59 @@ func (c *SmoketestCodesphereCmd) RunSmoketest() (err error) { ctx, cancel := context.WithTimeout(context.Background(), c.Opts.Timeout) defer cancel() - teamID, parseErr := strconv.Atoi(c.Opts.TeamID) - if parseErr != nil { - return fmt.Errorf("invalid team-id: %w", parseErr) - } - planID, parseErr := strconv.Atoi(c.Opts.PlanID) - if parseErr != nil { - return fmt.Errorf("invalid plan-id: %w", parseErr) + availableStepsMap := make(map[string]teststeps.SmokeTestStep) + for _, s := range availableSteps { + availableStepsMap[s.Name()] = s } - steps := []string{stepCreateWorkspace, stepSetEnvVar, stepCreateFiles, stepSyncLandscape, stepStartPipeline, stepDeleteWorkspace} - if c.Opts.Steps != "" { - steps = strings.Split(c.Opts.Steps, ",") - for i := range steps { - steps[i] = strings.TrimSpace(steps[i]) - } + + stepsToRun := make([]teststeps.SmokeTestStep, len(availableSteps)) + copy(stepsToRun, availableSteps) + + if len(c.Opts.Steps) > 0 { + stepsToRun = slices.DeleteFunc(stepsToRun, func(s teststeps.SmokeTestStep) bool { + return !slices.Contains(c.Opts.Steps, s.Name()) + }) } - workspaceName := fmt.Sprintf("smoketest-%s", time.Now().Format("20060102-150405")) var workspaceID int + deleteStep := &teststeps.DeleteWorkspaceStep{} defer func() { if err != nil { - c.logf("\n%sSmoketest failed: %s%s\n", colorRed, err.Error(), colorReset) + log.Printf("Smoketest failed: %s", err.Error()) + } + + shouldDelete := false + for _, s := range stepsToRun { + if s.Name() == deleteStep.Name() { + shouldDelete = true + break + } } - if workspaceID != 0 && slices.Contains(steps, stepDeleteWorkspace) { - c.logStep(fmt.Sprintf("\nDeleting workspace %d", workspaceID)) - deleteErr := c.Client.DeleteWorkspace(context.Background(), workspaceID) + if workspaceID != 0 && shouldDelete { + deleteErr := deleteStep.Run(context.Background(), c.Opts, &workspaceID) if deleteErr != nil { - c.logFailure() if err == nil { - err = fmt.Errorf("failed to delete workspace: %w", deleteErr) + err = deleteErr } } - c.logSuccess() } if err == nil { - c.logf("\n%sSmoketest completed successfully!%s\n", colorGreen, colorReset) + log.Println("Smoketest completed successfully!") } }() // Execute steps - for _, step := range steps { - switch step { - case stepCreateWorkspace: - if err = c.stepCreateWorkspace(ctx, teamID, planID, workspaceName, &workspaceID); err != nil { - return err - } - case stepSetEnvVar: - if err = c.stepSetEnvVar(ctx, workspaceID); err != nil { - return err - } - case stepCreateFiles: - if err = c.stepCreateFiles(ctx, workspaceID); err != nil { - return err - } - case stepSyncLandscape: - if err = c.stepSyncLandscape(ctx, workspaceID); err != nil { - return err - } - case stepStartPipeline: - if err = c.stepStartPipeline(ctx, workspaceID); err != nil { - return err - } - case stepDeleteWorkspace: - // Skip - handled in defer + for _, step := range stepsToRun { + // Skip deleteWorkspace in the main loop as it's handled in defer + if step.Name() == deleteStep.Name() { continue - default: - return fmt.Errorf("unknown step: %s", step) + } + if err = step.Run(ctx, c.Opts, &workspaceID); err != nil { + return err } } return nil } - -func (c *SmoketestCodesphereCmd) stepCreateWorkspace(ctx context.Context, teamID, planID int, workspaceName string, workspaceID *int) error { - c.logStep(fmt.Sprintf("Creating empty workspace '%s'", workspaceName)) - id, err := c.Client.CreateWorkspace(ctx, teamID, planID, workspaceName, nil) - if err != nil { - c.logFailure() - return fmt.Errorf("failed to create workspace: %w", err) - } - *workspaceID = id - c.logSuccess() - return nil -} - -func (c *SmoketestCodesphereCmd) stepSetEnvVar(ctx context.Context, workspaceID int) error { - c.logStep(fmt.Sprintf("Setting environment variable %s=%s", smoketestEnvVarKey, smoketestEnvVarValue)) - if err := c.Client.SetEnvVar(ctx, workspaceID, smoketestEnvVarKey, smoketestEnvVarValue); err != nil { - c.logFailure() - return fmt.Errorf("failed to set environment variable: %w", err) - } - c.logSuccess() - return nil -} - -func (c *SmoketestCodesphereCmd) stepCreateFiles(ctx context.Context, workspaceID int) error { - c.logStep("Creating ci.yml file") - ciYmlCmd := fmt.Sprintf(`echo '%s' > ci.yml`, ciYmlContent) - err := c.Client.ExecuteCommand(ctx, workspaceID, ciYmlCmd) - if err != nil { - c.logFailure() - return fmt.Errorf("failed to create ci.yml: %w", err) - } - c.logSuccess() - - c.logStep("Creating index.html file") - indexHtmlCmd := fmt.Sprintf(`echo '%s' > index.html`, indexHtmlContent) - err = c.Client.ExecuteCommand(ctx, workspaceID, indexHtmlCmd) - if err != nil { - c.logFailure() - return fmt.Errorf("failed to create index.html: %w", err) - } - c.logSuccess() - return nil -} - -func (c *SmoketestCodesphereCmd) stepSyncLandscape(ctx context.Context, workspaceID int) error { - c.logStep(fmt.Sprintf("Syncing landscape with profile '%s'", c.Opts.Profile)) - if err := c.Client.SyncLandscape(ctx, workspaceID, c.Opts.Profile); err != nil { - c.logFailure() - return fmt.Errorf("failed to sync landscape: %w", err) - } - c.logSuccess() - return nil -} - -func (c *SmoketestCodesphereCmd) stepStartPipeline(ctx context.Context, workspaceID int) error { - c.logStep(fmt.Sprintf("Starting '%s' pipeline stage", smoketestPipelineStage)) - if err := c.Client.StartPipeline(ctx, workspaceID, c.Opts.Profile, smoketestPipelineStage); err != nil { - c.logFailure() - return fmt.Errorf("failed to start pipeline: %w", err) - } - c.logSuccess() - return nil -} - -// Logging helpers - -func (c *SmoketestCodesphereCmd) logf(format string, args ...interface{}) { - if !c.Opts.Quiet { - fmt.Printf(format, args...) - } -} - -func (c *SmoketestCodesphereCmd) logStep(message string) { - if !c.Opts.Quiet { - fmt.Printf("%s...", message) - } -} - -func (c *SmoketestCodesphereCmd) logSuccess() { - if !c.Opts.Quiet { - fmt.Printf(" %ssucceeded%s\n", colorGreen, colorReset) - } -} - -func (c *SmoketestCodesphereCmd) logFailure() { - if !c.Opts.Quiet { - fmt.Printf(" %sfailed%s\n", colorRed, colorReset) - } -} diff --git a/cli/cmd/smoketest_codesphere_test.go b/cli/cmd/smoketest_codesphere_test.go index c1d1822b..ef7fcc82 100644 --- a/cli/cmd/smoketest_codesphere_test.go +++ b/cli/cmd/smoketest_codesphere_test.go @@ -15,18 +15,19 @@ import ( "github.com/codesphere-cloud/oms/cli/cmd" "github.com/codesphere-cloud/oms/internal/codesphere" + "github.com/codesphere-cloud/oms/internal/codesphere/teststeps" ) var _ = Describe("SmoketestCodesphereCmd", func() { var ( mockClient *codesphere.MockClient c cmd.SmoketestCodesphereCmd - opts *cmd.SmoketestCodesphereOpts + opts *teststeps.SmoketestCodesphereOpts ) BeforeEach(func() { mockClient = codesphere.NewMockClient(GinkgoT()) - opts = &cmd.SmoketestCodesphereOpts{ + opts = &teststeps.SmoketestCodesphereOpts{ BaseURL: "https://test.codesphere.com/api", Token: "test-token", TeamID: "123", @@ -34,10 +35,11 @@ var _ = Describe("SmoketestCodesphereCmd", func() { Quiet: true, // Suppress log output in tests Timeout: 10 * time.Minute, Profile: "ci.yml", + Steps: []string{}, + Client: mockClient, } c = cmd.SmoketestCodesphereCmd{ - Opts: opts, - Client: mockClient, + Opts: opts, } }) @@ -350,7 +352,7 @@ var _ = Describe("SmoketestCodesphereCmd", func() { It("runs only specified steps when steps flag is set", func() { workspaceID := 789 - opts.Steps = "createWorkspace,setEnvVar" + opts.Steps = []string{"createWorkspace", "setEnvVar"} mockClient.EXPECT().CreateWorkspace( mock.Anything, diff --git a/internal/codesphere/teststeps/csgo.go b/internal/codesphere/teststeps/csgo.go new file mode 100644 index 00000000..f50ecc8f --- /dev/null +++ b/internal/codesphere/teststeps/csgo.go @@ -0,0 +1,162 @@ +package teststeps + +import ( + "context" + "fmt" + "strconv" + "time" +) + +const ( + smoketestEnvVarKey = "TEST_VAR" + smoketestEnvVarValue = "smoketest" + smoketestPipelineStage = "run" + + stepNameCreateWorkspace = "createWorkspace" + stepNameSetEnvVar = "setEnvVar" + stepNameCreateFiles = "createFiles" + stepNameSyncLandscape = "syncLandscape" + stepNameStartPipeline = "startPipeline" + stepNameDeleteWorkspace = "deleteWorkspace" + + ciYmlContent = `schemaVersion: v0.2 +prepare: + steps: [] +test: + steps: [] +run: + service: + steps: + - name: Run php server + command: php -S 0.0.0.0:3000 index.html + plan: 20 + replicas: 1 + network: + ports: + - port: 3000 + isPublic: true + paths: + - port: 3000 + path: / + env: {} +` + + indexHtmlContent = ` + + + Smoketest + + +

Smoketest Successful

+ + +` +) + +type CreateWorkspaceStep struct{} + +func (s *CreateWorkspaceStep) Name() string { return stepNameCreateWorkspace } + +func (s *CreateWorkspaceStep) Run(ctx context.Context, c *SmoketestCodesphereOpts, workspaceID *int) error { + teamID, parseErr := strconv.Atoi(c.TeamID) + if parseErr != nil { + return fmt.Errorf("invalid team-id: %w", parseErr) + } + planID, parseErr := strconv.Atoi(c.PlanID) + if parseErr != nil { + return fmt.Errorf("invalid plan-id: %w", parseErr) + } + workspaceName := fmt.Sprintf("smoketest-%s", time.Now().Format("20060102-150405")) + + c.logStep(fmt.Sprintf("Creating empty workspace '%s'", workspaceName)) + id, err := c.Client.CreateWorkspace(ctx, teamID, planID, workspaceName, nil) + if err != nil { + c.logFailure() + return fmt.Errorf("failed to create workspace: %w", err) + } + *workspaceID = id + c.logSuccess() + return nil +} + +type SetEnvVarStep struct{} + +func (s *SetEnvVarStep) Name() string { return stepNameSetEnvVar } + +func (s *SetEnvVarStep) Run(ctx context.Context, c *SmoketestCodesphereOpts, workspaceID *int) error { + c.logStep(fmt.Sprintf("Setting environment variable %s=%s", smoketestEnvVarKey, smoketestEnvVarValue)) + if err := c.Client.SetEnvVar(ctx, *workspaceID, smoketestEnvVarKey, smoketestEnvVarValue); err != nil { + c.logFailure() + return fmt.Errorf("failed to set environment variable: %w", err) + } + c.logSuccess() + return nil +} + +type CreateFilesStep struct{} + +func (s *CreateFilesStep) Name() string { return stepNameCreateFiles } + +func (s *CreateFilesStep) Run(ctx context.Context, c *SmoketestCodesphereOpts, workspaceID *int) error { + c.logStep("Creating ci.yml file") + ciYmlCmd := fmt.Sprintf(`echo '%s' > ci.yml`, ciYmlContent) + err := c.Client.ExecuteCommand(ctx, *workspaceID, ciYmlCmd) + if err != nil { + c.logFailure() + return fmt.Errorf("failed to create ci.yml: %w", err) + } + c.logSuccess() + + c.logStep("Creating index.html file") + indexHtmlCmd := fmt.Sprintf(`echo '%s' > index.html`, indexHtmlContent) + err = c.Client.ExecuteCommand(ctx, *workspaceID, indexHtmlCmd) + if err != nil { + c.logFailure() + return fmt.Errorf("failed to create index.html: %w", err) + } + c.logSuccess() + return nil +} + +type SyncLandscapeStep struct{} + +func (s *SyncLandscapeStep) Name() string { return stepNameSyncLandscape } + +func (s *SyncLandscapeStep) Run(ctx context.Context, c *SmoketestCodesphereOpts, workspaceID *int) error { + c.logStep(fmt.Sprintf("Syncing landscape with profile '%s'", c.Profile)) + if err := c.Client.SyncLandscape(ctx, *workspaceID, c.Profile); err != nil { + c.logFailure() + return fmt.Errorf("failed to sync landscape: %w", err) + } + c.logSuccess() + return nil +} + +type StartPipelineStep struct{} + +func (s *StartPipelineStep) Name() string { return stepNameStartPipeline } + +func (s *StartPipelineStep) Run(ctx context.Context, c *SmoketestCodesphereOpts, workspaceID *int) error { + c.logStep(fmt.Sprintf("Starting '%s' pipeline stage", smoketestPipelineStage)) + if err := c.Client.StartPipeline(ctx, *workspaceID, c.Profile, smoketestPipelineStage); err != nil { + c.logFailure() + return fmt.Errorf("failed to start pipeline: %w", err) + } + c.logSuccess() + return nil +} + +type DeleteWorkspaceStep struct{} + +func (s *DeleteWorkspaceStep) Name() string { return stepNameDeleteWorkspace } + +func (s *DeleteWorkspaceStep) Run(ctx context.Context, c *SmoketestCodesphereOpts, workspaceID *int) error { + c.logStep(fmt.Sprintf("\nDeleting workspace %d", *workspaceID)) + deleteErr := c.Client.DeleteWorkspace(ctx, *workspaceID) + if deleteErr != nil { + c.logFailure() + return fmt.Errorf("failed to delete workspace: %w", deleteErr) + } + c.logSuccess() + return nil +} diff --git a/internal/codesphere/teststeps/step.go b/internal/codesphere/teststeps/step.go new file mode 100644 index 00000000..6fad61fb --- /dev/null +++ b/internal/codesphere/teststeps/step.go @@ -0,0 +1,61 @@ +package teststeps + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/codesphere-cloud/oms/internal/codesphere" +) + +const ( + // ANSI color codes + colorGreen = "\033[32m" + colorRed = "\033[31m" + colorReset = "\033[0m" +) + +type SmokeTestStep interface { + Run(ctx context.Context, c *SmoketestCodesphereOpts, workspaceID *int) error + Name() string +} + +type SmoketestCodesphereOpts struct { + Client codesphere.Client + // Configuration options + BaseURL string + Token string + TeamID string + PlanID string + Quiet bool + Timeout time.Duration + Profile string + Steps []string +} + +// Logging helpers + +func (c *SmoketestCodesphereOpts) logf(format string, args ...interface{}) { + if !c.Quiet { + log.Printf(format, args...) + } +} + +func (c *SmoketestCodesphereOpts) logStep(message string) { + if !c.Quiet { + fmt.Printf("%s...", message) + } +} + +func (c *SmoketestCodesphereOpts) logSuccess() { + if !c.Quiet { + fmt.Printf(" %ssucceeded%s\n", colorGreen, colorReset) + } +} + +func (c *SmoketestCodesphereOpts) logFailure() { + if !c.Quiet { + fmt.Printf(" %sfailed%s\n", colorRed, colorReset) + } +} From 564542bb8bd9008919b7b1c05ba836db19e3132f Mon Sep 17 00:00:00 2001 From: siherrmann <25087590+siherrmann@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:30:32 +0000 Subject: [PATCH 4/8] chore(docs): Auto-update docs and licenses Signed-off-by: siherrmann <25087590+siherrmann@users.noreply.github.com> --- NOTICE | 6 ++++++ docs/oms-cli_smoketest_codesphere.md | 2 +- internal/codesphere/teststeps/csgo.go | 3 +++ internal/codesphere/teststeps/step.go | 3 +++ internal/tmpl/NOTICE | 2 +- 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/NOTICE b/NOTICE index 47e54689..0d2a452f 100644 --- a/NOTICE +++ b/NOTICE @@ -405,6 +405,12 @@ Version: v1.36.11 License: BSD-3-Clause License URL: https://github.com/protocolbuffers/protobuf-go/blob/v1.36.11/LICENSE +---------- +Module: gopkg.in/validator.v2 +Version: v2.0.1 +License: Apache-2.0 +License URL: https://github.com/go-validator/validator/blob/v2.0.1/LICENSE + ---------- Module: gopkg.in/yaml.v3 Version: v3.0.1 diff --git a/docs/oms-cli_smoketest_codesphere.md b/docs/oms-cli_smoketest_codesphere.md index ac90f0fc..50eb949b 100644 --- a/docs/oms-cli_smoketest_codesphere.md +++ b/docs/oms-cli_smoketest_codesphere.md @@ -40,7 +40,7 @@ $ oms-cli smoketest codesphere --baseurl https://codesphere.example.com/api --to --plan-id string Plan ID for workspace creation --profile string CI profile to use for landscape and pipeline (default "ci.yml") -q, --quiet Suppress progress logging - --steps string Comma-separated list of steps to run (createWorkspace,setEnvVar,createFiles,syncLandscape,startPipeline,deleteWorkspace). If empty, all steps including deleteWorkspace are run. If specified without deleteWorkspace, the workspace will be kept for manual inspection. + --steps strings Comma-separated list of steps to run (createWorkspace,setEnvVar,createFiles,syncLandscape,startPipeline,deleteWorkspace). If empty, all steps including deleteWorkspace are run. If specified without deleteWorkspace, the workspace will be kept for manual inspection. --team-id string Team ID for workspace creation --timeout duration Timeout for the entire smoke test (default 10m0s) --token string API token for authentication diff --git a/internal/codesphere/teststeps/csgo.go b/internal/codesphere/teststeps/csgo.go index f50ecc8f..da41de23 100644 --- a/internal/codesphere/teststeps/csgo.go +++ b/internal/codesphere/teststeps/csgo.go @@ -1,3 +1,6 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + package teststeps import ( diff --git a/internal/codesphere/teststeps/step.go b/internal/codesphere/teststeps/step.go index 6fad61fb..ef302dd1 100644 --- a/internal/codesphere/teststeps/step.go +++ b/internal/codesphere/teststeps/step.go @@ -1,3 +1,6 @@ +// Copyright (c) Codesphere Inc. +// SPDX-License-Identifier: Apache-2.0 + package teststeps import ( diff --git a/internal/tmpl/NOTICE b/internal/tmpl/NOTICE index 5ff6900d..47e54689 100644 --- a/internal/tmpl/NOTICE +++ b/internal/tmpl/NOTICE @@ -88,7 +88,7 @@ License: MIT License URL: https://github.com/clipperhouse/uax29/blob/v2.3.0/LICENSE ---------- -Module: github.com/codesphere-cloud/cs-go/pkg/io +Module: github.com/codesphere-cloud/cs-go Version: v0.15.0 License: Apache-2.0 License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.15.0/LICENSE From 723a07c4efcf1234eeb06d46a9f9a65193e1ad85 Mon Sep 17 00:00:00 2001 From: siherrmann <25087590+siherrmann@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:31:23 +0000 Subject: [PATCH 5/8] chore(docs): Auto-update docs and licenses Signed-off-by: siherrmann <25087590+siherrmann@users.noreply.github.com> --- internal/tmpl/NOTICE | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/tmpl/NOTICE b/internal/tmpl/NOTICE index 47e54689..0d2a452f 100644 --- a/internal/tmpl/NOTICE +++ b/internal/tmpl/NOTICE @@ -405,6 +405,12 @@ Version: v1.36.11 License: BSD-3-Clause License URL: https://github.com/protocolbuffers/protobuf-go/blob/v1.36.11/LICENSE +---------- +Module: gopkg.in/validator.v2 +Version: v2.0.1 +License: Apache-2.0 +License URL: https://github.com/go-validator/validator/blob/v2.0.1/LICENSE + ---------- Module: gopkg.in/yaml.v3 Version: v3.0.1 From dca295c89d733eb7cb3ec5015c6c482c28a59edc Mon Sep 17 00:00:00 2001 From: Simon Herrmann Date: Tue, 13 Jan 2026 16:33:51 +0100 Subject: [PATCH 6/8] fix: remove unused function --- internal/codesphere/teststeps/step.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/internal/codesphere/teststeps/step.go b/internal/codesphere/teststeps/step.go index 6fad61fb..1d287391 100644 --- a/internal/codesphere/teststeps/step.go +++ b/internal/codesphere/teststeps/step.go @@ -3,7 +3,6 @@ package teststeps import ( "context" "fmt" - "log" "time" "github.com/codesphere-cloud/oms/internal/codesphere" @@ -36,12 +35,6 @@ type SmoketestCodesphereOpts struct { // Logging helpers -func (c *SmoketestCodesphereOpts) logf(format string, args ...interface{}) { - if !c.Quiet { - log.Printf(format, args...) - } -} - func (c *SmoketestCodesphereOpts) logStep(message string) { if !c.Quiet { fmt.Printf("%s...", message) From 449692443408a12fa856d58481025f119fbabb0a Mon Sep 17 00:00:00 2001 From: siherrmann <25087590+siherrmann@users.noreply.github.com> Date: Wed, 14 Jan 2026 10:17:37 +0000 Subject: [PATCH 7/8] chore(docs): Auto-update docs and licenses Signed-off-by: siherrmann <25087590+siherrmann@users.noreply.github.com> --- NOTICE | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/NOTICE b/NOTICE index 0d2a452f..73f9dde7 100644 --- a/NOTICE +++ b/NOTICE @@ -317,9 +317,9 @@ License URL: https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE ---------- Module: golang.org/x/crypto -Version: v0.46.0 +Version: v0.47.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE +License URL: https://cs.opensource.google/go/x/crypto/+/v0.47.0:LICENSE ---------- Module: golang.org/x/net @@ -353,9 +353,9 @@ License URL: https://cs.opensource.google/go/x/term/+/v0.39.0:LICENSE ---------- Module: golang.org/x/text -Version: v0.32.0 +Version: v0.33.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE +License URL: https://cs.opensource.google/go/x/text/+/v0.33.0:LICENSE ---------- Module: golang.org/x/time/rate From 5d46ee201f470e6f1069a4de47123d39f1056457 Mon Sep 17 00:00:00 2001 From: siherrmann <25087590+siherrmann@users.noreply.github.com> Date: Wed, 14 Jan 2026 10:18:20 +0000 Subject: [PATCH 8/8] chore(docs): Auto-update docs and licenses Signed-off-by: siherrmann <25087590+siherrmann@users.noreply.github.com> --- internal/tmpl/NOTICE | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/tmpl/NOTICE b/internal/tmpl/NOTICE index 0d2a452f..73f9dde7 100644 --- a/internal/tmpl/NOTICE +++ b/internal/tmpl/NOTICE @@ -317,9 +317,9 @@ License URL: https://github.com/yaml/go-yaml/blob/v3.0.4/LICENSE ---------- Module: golang.org/x/crypto -Version: v0.46.0 +Version: v0.47.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/crypto/+/v0.46.0:LICENSE +License URL: https://cs.opensource.google/go/x/crypto/+/v0.47.0:LICENSE ---------- Module: golang.org/x/net @@ -353,9 +353,9 @@ License URL: https://cs.opensource.google/go/x/term/+/v0.39.0:LICENSE ---------- Module: golang.org/x/text -Version: v0.32.0 +Version: v0.33.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/text/+/v0.32.0:LICENSE +License URL: https://cs.opensource.google/go/x/text/+/v0.33.0:LICENSE ---------- Module: golang.org/x/time/rate