Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .mockery.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@ packages:
config:
all: true
interfaces:
github.com/codesphere-cloud/oms/pkg/codesphere:
config:
all: true
interfaces:
8 changes: 7 additions & 1 deletion NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ License: MIT
License URL: https://github.com/clipperhouse/uax29/blob/v2.4.0/LICENSE

----------
Module: github.com/codesphere-cloud/cs-go/pkg/io
Module: github.com/codesphere-cloud/cs-go
Version: v0.16.4
License: Apache-2.0
License URL: https://github.com/codesphere-cloud/cs-go/blob/v0.16.4/LICENSE
Expand Down Expand Up @@ -441,6 +441,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
Expand Down
3 changes: 3 additions & 0 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ func GetRootCmd() *cobra.Command {
AddRegisterCmd(rootCmd, opts)
AddRevokeCmd(rootCmd, opts)

// Smoke test commands
AddSmoketestCmd(rootCmd, opts)

return rootCmd
}

Expand Down
27 changes: 27 additions & 0 deletions cli/cmd/smoketest.go
Original file line number Diff line number Diff line change
@@ -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)
}
169 changes: 169 additions & 0 deletions cli/cmd/smoketest_codesphere.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright (c) Codesphere Inc.
// SPDX-License-Identifier: Apache-2.0

package cmd

import (
"context"
"fmt"
"log"
"slices"
"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"
)

var availableSteps = []teststeps.SmokeTestStep{
&teststeps.CreateWorkspaceStep{},
&teststeps.SetEnvVarStep{},
&teststeps.CreateFilesStep{},
&teststeps.SyncLandscapeStep{},
&teststeps.StartPipelineStep{},
&teststeps.DeleteWorkspaceStep{},
}

type SmoketestCodesphereCmd struct {
cmd *cobra.Command
// TODO (Simon)for now I kept the opts in the teststeps package,
// but if we add more tests we should move unified opts here and probably use seperate
// structs for the different test types (base smoke test, ui test etc.)
Opts *teststeps.SmoketestCodesphereOpts
}

func (c *SmoketestCodesphereCmd) RunE(_ *cobra.Command, args []string) error {
client, err := codesphere.NewClient(c.Opts.BaseURL, c.Opts.Token)
if err != nil {
return fmt.Errorf("failed to create Codesphere client: %w", err)
}
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",
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: &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")
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().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")
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()

availableStepsMap := make(map[string]teststeps.SmokeTestStep)
for _, s := range availableSteps {
availableStepsMap[s.Name()] = s
}

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())
})
}

var workspaceID int
deleteStep := &teststeps.DeleteWorkspaceStep{}
defer func() {
if err != nil {
log.Printf("Smoketest failed: %s", err.Error())
}

shouldDelete := false
for _, s := range stepsToRun {
if s.Name() == deleteStep.Name() {
shouldDelete = true
break
}
}

if workspaceID != 0 && shouldDelete {
deleteErr := deleteStep.Run(context.Background(), c.Opts, &workspaceID)
if deleteErr != nil {
if err == nil {
err = deleteErr
}
}
}

if err == nil {
log.Println("Smoketest completed successfully!")
}
}()

// Execute steps
for _, step := range stepsToRun {
// Skip deleteWorkspace in the main loop as it's handled in defer
if step.Name() == deleteStep.Name() {
continue
}
if err = step.Run(ctx, c.Opts, &workspaceID); err != nil {
return err
}
}

return nil
}
Loading