From d76f52c881fa170b9c244f48f5eba4124b11491b Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:13:06 +0100 Subject: [PATCH 1/8] feat: update wake up command to wait for workspace to be running --- api/mocks.go | 10 +- cli/cmd/mocks.go | 272 ++++++++---------------------------------- cli/cmd/wakeup.go | 73 +++++++++--- pkg/exporter/mocks.go | 79 ++---------- pkg/git/mocks.go | 31 +---- pkg/io/mocks.go | 99 +++------------ 6 files changed, 140 insertions(+), 424 deletions(-) diff --git a/api/mocks.go b/api/mocks.go index 05a287a..5e2084e 100644 --- a/api/mocks.go +++ b/api/mocks.go @@ -92,20 +92,14 @@ type MockTime_Sleep_Call struct { } // Sleep is a helper method to define mock.On call -// - duration time.Duration +// - duration func (_e *MockTime_Expecter) Sleep(duration interface{}) *MockTime_Sleep_Call { return &MockTime_Sleep_Call{Call: _e.mock.On("Sleep", duration)} } func (_c *MockTime_Sleep_Call) Run(run func(duration time.Duration)) *MockTime_Sleep_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 time.Duration - if args[0] != nil { - arg0 = args[0].(time.Duration) - } - run( - arg0, - ) + run(args[0].(time.Duration)) }) return _c } diff --git a/cli/cmd/mocks.go b/cli/cmd/mocks.go index 5a6f9cf..83945b2 100644 --- a/cli/cmd/mocks.go +++ b/cli/cmd/mocks.go @@ -62,20 +62,14 @@ type MockClient_DeleteWorkspace_Call struct { } // DeleteWorkspace is a helper method to define mock.On call -// - wsId int +// - wsId func (_e *MockClient_Expecter) DeleteWorkspace(wsId interface{}) *MockClient_DeleteWorkspace_Call { return &MockClient_DeleteWorkspace_Call{Call: _e.mock.On("DeleteWorkspace", wsId)} } func (_c *MockClient_DeleteWorkspace_Call) Run(run func(wsId int)) *MockClient_DeleteWorkspace_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 int - if args[0] != nil { - arg0 = args[0].(int) - } - run( - arg0, - ) + run(args[0].(int)) }) return _c } @@ -113,26 +107,15 @@ type MockClient_DeployLandscape_Call struct { } // DeployLandscape is a helper method to define mock.On call -// - wsId int -// - profile string +// - wsId +// - profile func (_e *MockClient_Expecter) DeployLandscape(wsId interface{}, profile interface{}) *MockClient_DeployLandscape_Call { return &MockClient_DeployLandscape_Call{Call: _e.mock.On("DeployLandscape", wsId, profile)} } func (_c *MockClient_DeployLandscape_Call) Run(run func(wsId int, profile string)) *MockClient_DeployLandscape_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 int - if args[0] != nil { - arg0 = args[0].(int) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - run( - arg0, - arg1, - ) + run(args[0].(int), args[1].(string)) }) return _c } @@ -181,20 +164,14 @@ type MockClient_DeployWorkspace_Call struct { } // DeployWorkspace is a helper method to define mock.On call -// - args api.DeployWorkspaceArgs +// - args func (_e *MockClient_Expecter) DeployWorkspace(args interface{}) *MockClient_DeployWorkspace_Call { return &MockClient_DeployWorkspace_Call{Call: _e.mock.On("DeployWorkspace", args)} } func (_c *MockClient_DeployWorkspace_Call) Run(run func(args api.DeployWorkspaceArgs)) *MockClient_DeployWorkspace_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 api.DeployWorkspaceArgs - if args[0] != nil { - arg0 = args[0].(api.DeployWorkspaceArgs) - } - run( - arg0, - ) + run(args[0].(api.DeployWorkspaceArgs)) }) return _c } @@ -247,38 +224,17 @@ type MockClient_ExecCommand_Call struct { } // ExecCommand is a helper method to define mock.On call -// - workspaceId int -// - command string -// - workdir string -// - env map[string]string +// - workspaceId +// - command +// - workdir +// - env func (_e *MockClient_Expecter) ExecCommand(workspaceId interface{}, command interface{}, workdir interface{}, env interface{}) *MockClient_ExecCommand_Call { return &MockClient_ExecCommand_Call{Call: _e.mock.On("ExecCommand", workspaceId, command, workdir, env)} } func (_c *MockClient_ExecCommand_Call) Run(run func(workspaceId int, command string, workdir string, env map[string]string)) *MockClient_ExecCommand_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 int - if args[0] != nil { - arg0 = args[0].(int) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - var arg3 map[string]string - if args[3] != nil { - arg3 = args[3].(map[string]string) - } - run( - arg0, - arg1, - arg2, - arg3, - ) + run(args[0].(int), args[1].(string), args[2].(string), args[3].(map[string]string)) }) return _c } @@ -327,26 +283,15 @@ type MockClient_GetPipelineState_Call struct { } // GetPipelineState is a helper method to define mock.On call -// - wsId int -// - stage string +// - wsId +// - stage func (_e *MockClient_Expecter) GetPipelineState(wsId interface{}, stage interface{}) *MockClient_GetPipelineState_Call { return &MockClient_GetPipelineState_Call{Call: _e.mock.On("GetPipelineState", wsId, stage)} } func (_c *MockClient_GetPipelineState_Call) Run(run func(wsId int, stage string)) *MockClient_GetPipelineState_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 int - if args[0] != nil { - arg0 = args[0].(int) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - run( - arg0, - arg1, - ) + run(args[0].(int), args[1].(string)) }) return _c } @@ -393,20 +338,14 @@ type MockClient_GetWorkspace_Call struct { } // GetWorkspace is a helper method to define mock.On call -// - workspaceId int +// - workspaceId func (_e *MockClient_Expecter) GetWorkspace(workspaceId interface{}) *MockClient_GetWorkspace_Call { return &MockClient_GetWorkspace_Call{Call: _e.mock.On("GetWorkspace", workspaceId)} } func (_c *MockClient_GetWorkspace_Call) Run(run func(workspaceId int)) *MockClient_GetWorkspace_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 int - if args[0] != nil { - arg0 = args[0].(int) - } - run( - arg0, - ) + run(args[0].(int)) }) return _c } @@ -444,32 +383,16 @@ type MockClient_GitPull_Call struct { } // GitPull is a helper method to define mock.On call -// - wsId int -// - remote string -// - branch string +// - wsId +// - remote +// - branch func (_e *MockClient_Expecter) GitPull(wsId interface{}, remote interface{}, branch interface{}) *MockClient_GitPull_Call { return &MockClient_GitPull_Call{Call: _e.mock.On("GitPull", wsId, remote, branch)} } func (_c *MockClient_GitPull_Call) Run(run func(wsId int, remote string, branch string)) *MockClient_GitPull_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 int - if args[0] != nil { - arg0 = args[0].(int) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(int), args[1].(string), args[2].(string)) }) return _c } @@ -683,20 +606,14 @@ type MockClient_ListWorkspaces_Call struct { } // ListWorkspaces is a helper method to define mock.On call -// - teamId int +// - teamId func (_e *MockClient_Expecter) ListWorkspaces(teamId interface{}) *MockClient_ListWorkspaces_Call { return &MockClient_ListWorkspaces_Call{Call: _e.mock.On("ListWorkspaces", teamId)} } func (_c *MockClient_ListWorkspaces_Call) Run(run func(teamId int)) *MockClient_ListWorkspaces_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 int - if args[0] != nil { - arg0 = args[0].(int) - } - run( - arg0, - ) + run(args[0].(int)) }) return _c } @@ -734,26 +651,15 @@ type MockClient_ScaleWorkspace_Call struct { } // ScaleWorkspace is a helper method to define mock.On call -// - wsId int -// - replicas int +// - wsId +// - replicas func (_e *MockClient_Expecter) ScaleWorkspace(wsId interface{}, replicas interface{}) *MockClient_ScaleWorkspace_Call { return &MockClient_ScaleWorkspace_Call{Call: _e.mock.On("ScaleWorkspace", wsId, replicas)} } func (_c *MockClient_ScaleWorkspace_Call) Run(run func(wsId int, replicas int)) *MockClient_ScaleWorkspace_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 int - if args[0] != nil { - arg0 = args[0].(int) - } - var arg1 int - if args[1] != nil { - arg1 = args[1].(int) - } - run( - arg0, - arg1, - ) + run(args[0].(int), args[1].(int)) }) return _c } @@ -791,26 +697,15 @@ type MockClient_SetEnvVarOnWorkspace_Call struct { } // SetEnvVarOnWorkspace is a helper method to define mock.On call -// - workspaceId int -// - vars map[string]string +// - workspaceId +// - vars func (_e *MockClient_Expecter) SetEnvVarOnWorkspace(workspaceId interface{}, vars interface{}) *MockClient_SetEnvVarOnWorkspace_Call { return &MockClient_SetEnvVarOnWorkspace_Call{Call: _e.mock.On("SetEnvVarOnWorkspace", workspaceId, vars)} } func (_c *MockClient_SetEnvVarOnWorkspace_Call) Run(run func(workspaceId int, vars map[string]string)) *MockClient_SetEnvVarOnWorkspace_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 int - if args[0] != nil { - arg0 = args[0].(int) - } - var arg1 map[string]string - if args[1] != nil { - arg1 = args[1].(map[string]string) - } - run( - arg0, - arg1, - ) + run(args[0].(int), args[1].(map[string]string)) }) return _c } @@ -848,32 +743,16 @@ type MockClient_StartPipelineStage_Call struct { } // StartPipelineStage is a helper method to define mock.On call -// - wsId int -// - profile string -// - stage string +// - wsId +// - profile +// - stage func (_e *MockClient_Expecter) StartPipelineStage(wsId interface{}, profile interface{}, stage interface{}) *MockClient_StartPipelineStage_Call { return &MockClient_StartPipelineStage_Call{Call: _e.mock.On("StartPipelineStage", wsId, profile, stage)} } func (_c *MockClient_StartPipelineStage_Call) Run(run func(wsId int, profile string, stage string)) *MockClient_StartPipelineStage_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 int - if args[0] != nil { - arg0 = args[0].(int) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(int), args[1].(string), args[2].(string)) }) return _c } @@ -911,26 +790,15 @@ type MockClient_WaitForWorkspaceRunning_Call struct { } // WaitForWorkspaceRunning is a helper method to define mock.On call -// - workspace *api.Workspace -// - timeout time.Duration +// - workspace +// - timeout func (_e *MockClient_Expecter) WaitForWorkspaceRunning(workspace interface{}, timeout interface{}) *MockClient_WaitForWorkspaceRunning_Call { return &MockClient_WaitForWorkspaceRunning_Call{Call: _e.mock.On("WaitForWorkspaceRunning", workspace, timeout)} } func (_c *MockClient_WaitForWorkspaceRunning_Call) Run(run func(workspace *api.Workspace, timeout time.Duration)) *MockClient_WaitForWorkspaceRunning_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 *api.Workspace - if args[0] != nil { - arg0 = args[0].(*api.Workspace) - } - var arg1 time.Duration - if args[1] != nil { - arg1 = args[1].(time.Duration) - } - run( - arg0, - arg1, - ) + run(args[0].(*api.Workspace), args[1].(time.Duration)) }) return _c } @@ -979,20 +847,14 @@ type MockClient_WorkspaceStatus_Call struct { } // WorkspaceStatus is a helper method to define mock.On call -// - workspaceId int +// - workspaceId func (_e *MockClient_Expecter) WorkspaceStatus(workspaceId interface{}) *MockClient_WorkspaceStatus_Call { return &MockClient_WorkspaceStatus_Call{Call: _e.mock.On("WorkspaceStatus", workspaceId)} } func (_c *MockClient_WorkspaceStatus_Call) Run(run func(workspaceId int)) *MockClient_WorkspaceStatus_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 int - if args[0] != nil { - arg0 = args[0].(int) - } - run( - arg0, - ) + run(args[0].(int)) }) return _c } @@ -1057,44 +919,18 @@ type MockCommandExecutor_Execute_Call struct { } // Execute is a helper method to define mock.On call -// - ctx context.Context -// - name string -// - args []string -// - stdout io.Writer -// - stderr io.Writer +// - ctx +// - name +// - args +// - stdout +// - stderr func (_e *MockCommandExecutor_Expecter) Execute(ctx interface{}, name interface{}, args interface{}, stdout interface{}, stderr interface{}) *MockCommandExecutor_Execute_Call { return &MockCommandExecutor_Execute_Call{Call: _e.mock.On("Execute", ctx, name, args, stdout, stderr)} } func (_c *MockCommandExecutor_Execute_Call) Run(run func(ctx context.Context, name string, args []string, stdout io.Writer, stderr io.Writer)) *MockCommandExecutor_Execute_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 []string - if args[2] != nil { - arg2 = args[2].([]string) - } - var arg3 io.Writer - if args[3] != nil { - arg3 = args[3].(io.Writer) - } - var arg4 io.Writer - if args[4] != nil { - arg4 = args[4].(io.Writer) - } - run( - arg0, - arg1, - arg2, - arg3, - arg4, - ) + run(args[0].(context.Context), args[1].(string), args[2].([]string), args[3].(io.Writer), args[4].(io.Writer)) }) return _c } @@ -1159,20 +995,14 @@ type MockPrompt_InputPrompt_Call struct { } // InputPrompt is a helper method to define mock.On call -// - prompt string +// - prompt func (_e *MockPrompt_Expecter) InputPrompt(prompt interface{}) *MockPrompt_InputPrompt_Call { return &MockPrompt_InputPrompt_Call{Call: _e.mock.On("InputPrompt", prompt)} } func (_c *MockPrompt_InputPrompt_Call) Run(run func(prompt string)) *MockPrompt_InputPrompt_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } @@ -1237,20 +1067,14 @@ type MockBrowser_OpenIde_Call struct { } // OpenIde is a helper method to define mock.On call -// - path string +// - path func (_e *MockBrowser_Expecter) OpenIde(path interface{}) *MockBrowser_OpenIde_Call { return &MockBrowser_OpenIde_Call{Call: _e.mock.On("OpenIde", path)} } func (_c *MockBrowser_OpenIde_Call) Run(run func(path string)) *MockBrowser_OpenIde_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } diff --git a/cli/cmd/wakeup.go b/cli/cmd/wakeup.go index 6bbffde..cd0eea7 100644 --- a/cli/cmd/wakeup.go +++ b/cli/cmd/wakeup.go @@ -5,6 +5,8 @@ package cmd import ( "fmt" + "log" + "net/http" "time" "github.com/codesphere-cloud/cs-go/pkg/io" @@ -68,31 +70,66 @@ func (c *WakeUpCmd) WakeUpWorkspace(client Client, wsId int) error { return fmt.Errorf("failed to get workspace status: %w", err) } - if status.IsRunning { - fmt.Printf("Workspace %d (%s) is already running\n", wsId, workspace.Name) - return nil + if !status.IsRunning { + log.Printf("Waking up workspace %d (%s)...\n", wsId, workspace.Name) + + // Scale workspace to at least 1 replica to wake it up + // If workspace already has replicas configured (but not running), preserve that count + targetReplicas := 1 + if workspace.Replicas > 1 { + targetReplicas = workspace.Replicas + } + + err = client.ScaleWorkspace(wsId, targetReplicas) + if err != nil { + return fmt.Errorf("failed to scale workspace: %w", err) + } + + log.Printf("Waiting for workspace %d to be running...\n", wsId) + err = client.WaitForWorkspaceRunning(&workspace, c.Opts.Timeout) + if err != nil { + return fmt.Errorf("workspace did not become running: %w", err) + } } - fmt.Printf("Waking up workspace %d (%s)...\n", wsId, workspace.Name) - - // Scale workspace to at least 1 replica to wake it up - // If workspace already has replicas configured (but not running), preserve that count - targetReplicas := 1 - if workspace.Replicas > 1 { - targetReplicas = workspace.Replicas + if workspace.DevDomain == nil || *workspace.DevDomain == "" { + log.Printf("Workspace %d does not have a dev domain, skipping health check\n", wsId) + return nil } - err = client.ScaleWorkspace(wsId, targetReplicas) + log.Printf("Checking health of workspace %d (%s)...\n", wsId, workspace.Name) + err = c.waitForWorkspaceHealthy(*workspace.DevDomain, c.Opts.Timeout) if err != nil { - return fmt.Errorf("failed to scale workspace: %w", err) + return fmt.Errorf("workspace did not become healthy: %w", err) } - fmt.Printf("Waiting for workspace %d to be running...\n", wsId) - err = client.WaitForWorkspaceRunning(&workspace, c.Opts.Timeout) - if err != nil { - return fmt.Errorf("workspace did not become running: %w", err) - } + log.Printf("Workspace %d is healthy and ready\n", wsId) - fmt.Printf("Workspace %d is now running\n", wsId) return nil } + +func (c *WakeUpCmd) waitForWorkspaceHealthy(devDomain string, timeout time.Duration) error { + url := fmt.Sprintf("https://%s", devDomain) + delay := 5 * time.Second + maxWaitTime := time.Now().Add(timeout) + + httpClient := &http.Client{ + Timeout: 10 * time.Second, + } + + for { + resp, err := httpClient.Get(url) + if err == nil { + resp.Body.Close() + if resp.StatusCode == http.StatusOK { + return nil + } + } + + if time.Now().After(maxWaitTime) { + return fmt.Errorf("timeout waiting for workspace to be healthy at %s", url) + } + + time.Sleep(delay) + } +} diff --git a/pkg/exporter/mocks.go b/pkg/exporter/mocks.go index a2febe4..67c0262 100644 --- a/pkg/exporter/mocks.go +++ b/pkg/exporter/mocks.go @@ -104,32 +104,16 @@ type MockExporter_ExportImages_Call struct { } // ExportImages is a helper method to define mock.On call -// - ctx context.Context -// - registry string -// - imagePrefix string +// - ctx +// - registry +// - imagePrefix func (_e *MockExporter_Expecter) ExportImages(ctx interface{}, registry interface{}, imagePrefix interface{}) *MockExporter_ExportImages_Call { return &MockExporter_ExportImages_Call{Call: _e.mock.On("ExportImages", ctx, registry, imagePrefix)} } func (_c *MockExporter_ExportImages_Call) Run(run func(ctx context.Context, registry string, imagePrefix string)) *MockExporter_ExportImages_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - run( - arg0, - arg1, - arg2, - ) + run(args[0].(context.Context), args[1].(string), args[2].(string)) }) return _c } @@ -167,50 +151,19 @@ type MockExporter_ExportKubernetesArtifacts_Call struct { } // ExportKubernetesArtifacts is a helper method to define mock.On call -// - registry string -// - image string -// - namespace string -// - pullSecret string -// - hostname string -// - ingressClass string +// - registry +// - image +// - namespace +// - pullSecret +// - hostname +// - ingressClass func (_e *MockExporter_Expecter) ExportKubernetesArtifacts(registry interface{}, image interface{}, namespace interface{}, pullSecret interface{}, hostname interface{}, ingressClass interface{}) *MockExporter_ExportKubernetesArtifacts_Call { return &MockExporter_ExportKubernetesArtifacts_Call{Call: _e.mock.On("ExportKubernetesArtifacts", registry, image, namespace, pullSecret, hostname, ingressClass)} } func (_c *MockExporter_ExportKubernetesArtifacts_Call) Run(run func(registry string, image string, namespace string, pullSecret string, hostname string, ingressClass string)) *MockExporter_ExportKubernetesArtifacts_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - var arg3 string - if args[3] != nil { - arg3 = args[3].(string) - } - var arg4 string - if args[4] != nil { - arg4 = args[4].(string) - } - var arg5 string - if args[5] != nil { - arg5 = args[5].(string) - } - run( - arg0, - arg1, - arg2, - arg3, - arg4, - arg5, - ) + run(args[0].(string), args[1].(string), args[2].(string), args[3].(string), args[4].(string), args[5].(string)) }) return _c } @@ -259,20 +212,14 @@ type MockExporter_ReadYmlFile_Call struct { } // ReadYmlFile is a helper method to define mock.On call -// - path string +// - path func (_e *MockExporter_Expecter) ReadYmlFile(path interface{}) *MockExporter_ReadYmlFile_Call { return &MockExporter_ReadYmlFile_Call{Call: _e.mock.On("ReadYmlFile", path)} } func (_c *MockExporter_ReadYmlFile_Call) Run(run func(path string)) *MockExporter_ReadYmlFile_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - run( - arg0, - ) + run(args[0].(string)) }) return _c } diff --git a/pkg/git/mocks.go b/pkg/git/mocks.go index fa2afdb..a3f3c0f 100644 --- a/pkg/git/mocks.go +++ b/pkg/git/mocks.go @@ -71,38 +71,17 @@ type MockGit_CloneRepository_Call struct { } // CloneRepository is a helper method to define mock.On call -// - fs *cs.FileSystem -// - url string -// - branch string -// - path string +// - fs +// - url +// - branch +// - path func (_e *MockGit_Expecter) CloneRepository(fs interface{}, url interface{}, branch interface{}, path interface{}) *MockGit_CloneRepository_Call { return &MockGit_CloneRepository_Call{Call: _e.mock.On("CloneRepository", fs, url, branch, path)} } func (_c *MockGit_CloneRepository_Call) Run(run func(fs *cs.FileSystem, url string, branch string, path string)) *MockGit_CloneRepository_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 *cs.FileSystem - if args[0] != nil { - arg0 = args[0].(*cs.FileSystem) - } - var arg1 string - if args[1] != nil { - arg1 = args[1].(string) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - var arg3 string - if args[3] != nil { - arg3 = args[3].(string) - } - run( - arg0, - arg1, - arg2, - arg3, - ) + run(args[0].(*cs.FileSystem), args[1].(string), args[2].(string), args[3].(string)) }) return _c } diff --git a/pkg/io/mocks.go b/pkg/io/mocks.go index 92c93f6..8e26b84 100644 --- a/pkg/io/mocks.go +++ b/pkg/io/mocks.go @@ -69,26 +69,15 @@ type MockExec_ExecuteCommand_Call struct { } // ExecuteCommand is a helper method to define mock.On call -// - ctx context.Context -// - cmdArgs []string +// - ctx +// - cmdArgs func (_e *MockExec_Expecter) ExecuteCommand(ctx interface{}, cmdArgs interface{}) *MockExec_ExecuteCommand_Call { return &MockExec_ExecuteCommand_Call{Call: _e.mock.On("ExecuteCommand", ctx, cmdArgs)} } func (_c *MockExec_ExecuteCommand_Call) Run(run func(ctx context.Context, cmdArgs []string)) *MockExec_ExecuteCommand_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 context.Context - if args[0] != nil { - arg0 = args[0].(context.Context) - } - var arg1 []string - if args[1] != nil { - arg1 = args[1].([]string) - } - run( - arg0, - arg1, - ) + run(args[0].(context.Context), args[1].([]string)) }) return _c } @@ -142,26 +131,15 @@ type MockHttpServer_Handle_Call struct { } // Handle is a helper method to define mock.On call -// - pattern string -// - handler http.Handler +// - pattern +// - handler func (_e *MockHttpServer_Expecter) Handle(pattern interface{}, handler interface{}) *MockHttpServer_Handle_Call { return &MockHttpServer_Handle_Call{Call: _e.mock.On("Handle", pattern, handler)} } func (_c *MockHttpServer_Handle_Call) Run(run func(pattern string, handler http.Handler)) *MockHttpServer_Handle_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 http.Handler - if args[1] != nil { - arg1 = args[1].(http.Handler) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(http.Handler)) }) return _c } @@ -188,26 +166,15 @@ type MockHttpServer_HandleFunc_Call struct { } // HandleFunc is a helper method to define mock.On call -// - pattern string -// - handler func(http.ResponseWriter, *http.Request) +// - pattern +// - handler func (_e *MockHttpServer_Expecter) HandleFunc(pattern interface{}, handler interface{}) *MockHttpServer_HandleFunc_Call { return &MockHttpServer_HandleFunc_Call{Call: _e.mock.On("HandleFunc", pattern, handler)} } func (_c *MockHttpServer_HandleFunc_Call) Run(run func(pattern string, handler func(http.ResponseWriter, *http.Request))) *MockHttpServer_HandleFunc_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 func(http.ResponseWriter, *http.Request) - if args[1] != nil { - arg1 = args[1].(func(http.ResponseWriter, *http.Request)) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(func(http.ResponseWriter, *http.Request))) }) return _c } @@ -245,26 +212,15 @@ type MockHttpServer_ListenAndServe_Call struct { } // ListenAndServe is a helper method to define mock.On call -// - addr string -// - handler http.Handler +// - addr +// - handler func (_e *MockHttpServer_Expecter) ListenAndServe(addr interface{}, handler interface{}) *MockHttpServer_ListenAndServe_Call { return &MockHttpServer_ListenAndServe_Call{Call: _e.mock.On("ListenAndServe", addr, handler)} } func (_c *MockHttpServer_ListenAndServe_Call) Run(run func(addr string, handler http.Handler)) *MockHttpServer_ListenAndServe_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 string - if args[0] != nil { - arg0 = args[0].(string) - } - var arg1 http.Handler - if args[1] != nil { - arg1 = args[1].(http.Handler) - } - run( - arg0, - arg1, - ) + run(args[0].(string), args[1].(http.Handler)) }) return _c } @@ -291,38 +247,17 @@ type MockHttpServer_Redirect_Call struct { } // Redirect is a helper method to define mock.On call -// - w http.ResponseWriter -// - r *http.Request -// - url string -// - code int +// - w +// - r +// - url +// - code func (_e *MockHttpServer_Expecter) Redirect(w interface{}, r interface{}, url interface{}, code interface{}) *MockHttpServer_Redirect_Call { return &MockHttpServer_Redirect_Call{Call: _e.mock.On("Redirect", w, r, url, code)} } func (_c *MockHttpServer_Redirect_Call) Run(run func(w http.ResponseWriter, r *http.Request, url string, code int)) *MockHttpServer_Redirect_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 http.ResponseWriter - if args[0] != nil { - arg0 = args[0].(http.ResponseWriter) - } - var arg1 *http.Request - if args[1] != nil { - arg1 = args[1].(*http.Request) - } - var arg2 string - if args[2] != nil { - arg2 = args[2].(string) - } - var arg3 int - if args[3] != nil { - arg3 = args[3].(int) - } - run( - arg0, - arg1, - arg2, - arg3, - ) + run(args[0].(http.ResponseWriter), args[1].(*http.Request), args[2].(string), args[3].(int)) }) return _c } From d6bef282b009533f063378f1f7a2e7e342ee476e Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Fri, 20 Feb 2026 09:01:50 +0100 Subject: [PATCH 2/8] feat: add ScaleLandscapeServices method and update wake-up command to support scaling specific services --- api/workspace.go | 9 +++ cli/cmd/client.go | 1 + cli/cmd/mocks.go | 46 ++++++++++++ cli/cmd/wakeup.go | 66 +++++++++++++++++- cli/cmd/wakeup_test.go | 155 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 275 insertions(+), 2 deletions(-) diff --git a/api/workspace.go b/api/workspace.go index 2ec8ef1..0191cb0 100644 --- a/api/workspace.go +++ b/api/workspace.go @@ -119,6 +119,15 @@ func (c *Client) ScaleWorkspace(wsId int, replicas int) error { return errors.FormatAPIError(err) } +// ScaleLandscapeServices scales landscape services by name. +// The services map contains service name -> replica count. +func (c *Client) ScaleLandscapeServices(wsId int, services map[string]int) error { + req := c.api.WorkspacesAPI.WorkspacesScaleLandscapeServices(c.ctx, float32(wsId)). + RequestBody(services) + _, err := req.Execute() + return errors.FormatAPIError(err) +} + // Waits for a given workspace to be running. // // Returns [TimedOut] error if the workspace does not become running in time. diff --git a/cli/cmd/client.go b/cli/cmd/client.go index 4c33402..cc68016 100644 --- a/cli/cmd/client.go +++ b/cli/cmd/client.go @@ -23,6 +23,7 @@ type Client interface { WorkspaceStatus(workspaceId int) (*api.WorkspaceStatus, error) WaitForWorkspaceRunning(workspace *api.Workspace, timeout time.Duration) error ScaleWorkspace(wsId int, replicas int) error + ScaleLandscapeServices(wsId int, services map[string]int) error SetEnvVarOnWorkspace(workspaceId int, vars map[string]string) error ExecCommand(workspaceId int, command string, workdir string, env map[string]string) (string, string, error) ListWorkspacePlans() ([]api.WorkspacePlan, error) diff --git a/cli/cmd/mocks.go b/cli/cmd/mocks.go index 83945b2..810b78e 100644 --- a/cli/cmd/mocks.go +++ b/cli/cmd/mocks.go @@ -628,6 +628,52 @@ func (_c *MockClient_ListWorkspaces_Call) RunAndReturn(run func(teamId int) ([]a return _c } +// ScaleLandscapeServices provides a mock function for the type MockClient +func (_mock *MockClient) ScaleLandscapeServices(wsId int, services map[string]int) error { + ret := _mock.Called(wsId, services) + + if len(ret) == 0 { + panic("no return value specified for ScaleLandscapeServices") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(int, map[string]int) error); ok { + r0 = returnFunc(wsId, services) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// MockClient_ScaleLandscapeServices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ScaleLandscapeServices' +type MockClient_ScaleLandscapeServices_Call struct { + *mock.Call +} + +// ScaleLandscapeServices is a helper method to define mock.On call +// - wsId +// - services +func (_e *MockClient_Expecter) ScaleLandscapeServices(wsId interface{}, services interface{}) *MockClient_ScaleLandscapeServices_Call { + return &MockClient_ScaleLandscapeServices_Call{Call: _e.mock.On("ScaleLandscapeServices", wsId, services)} +} + +func (_c *MockClient_ScaleLandscapeServices_Call) Run(run func(wsId int, services map[string]int)) *MockClient_ScaleLandscapeServices_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(int), args[1].(map[string]int)) + }) + return _c +} + +func (_c *MockClient_ScaleLandscapeServices_Call) Return(err error) *MockClient_ScaleLandscapeServices_Call { + _c.Call.Return(err) + return _c +} + +func (_c *MockClient_ScaleLandscapeServices_Call) RunAndReturn(run func(wsId int, services map[string]int) error) *MockClient_ScaleLandscapeServices_Call { + _c.Call.Return(run) + return _c +} + // ScaleWorkspace provides a mock function for the type MockClient func (_mock *MockClient) ScaleWorkspace(wsId int, replicas int) error { ret := _mock.Called(wsId, replicas) diff --git a/cli/cmd/wakeup.go b/cli/cmd/wakeup.go index cd0eea7..48478a6 100644 --- a/cli/cmd/wakeup.go +++ b/cli/cmd/wakeup.go @@ -7,6 +7,8 @@ import ( "fmt" "log" "net/http" + "strconv" + "strings" "time" "github.com/codesphere-cloud/cs-go/pkg/io" @@ -15,7 +17,10 @@ import ( type WakeUpOptions struct { GlobalOptions - Timeout time.Duration + Timeout time.Duration + SyncLandscape bool + Profile string + ScaleServices string // format: "service=replicas,service2=replicas" } type WakeUpCmd struct { @@ -42,11 +47,14 @@ func AddWakeUpCmd(rootCmd *cobra.Command, opts GlobalOptions) { cmd: &cobra.Command{ Use: "wake-up", Short: "Wake up an on-demand workspace", - Long: `Wake up an on-demand workspace by scaling it to 1 replica via the API.`, + Long: `Wake up an on-demand workspace by scaling it to 1 replica via the API. Optionally syncs the landscape to start services.`, Example: io.FormatExampleCommands("wake-up", []io.Example{ {Cmd: "-w 1234", Desc: "wake up workspace 1234"}, {Cmd: "", Desc: "wake up workspace set by environment variable CS_WORKSPACE_ID"}, {Cmd: "-w 1234 --timeout 60s", Desc: "wake up workspace with 60 second timeout"}, + {Cmd: "-w 1234 --sync-landscape", Desc: "wake up workspace and deploy landscape from CI profile"}, + {Cmd: "-w 1234 --sync-landscape --profile prod", Desc: "wake up workspace and deploy landscape with prod profile"}, + {Cmd: "-w 1234 --scale-services web=1,api=2", Desc: "wake up workspace and scale specific services"}, }), }, Opts: WakeUpOptions{ @@ -54,6 +62,9 @@ func AddWakeUpCmd(rootCmd *cobra.Command, opts GlobalOptions) { }, } wakeup.cmd.Flags().DurationVar(&wakeup.Opts.Timeout, "timeout", 120*time.Second, "Timeout for waking up the workspace") + wakeup.cmd.Flags().BoolVar(&wakeup.Opts.SyncLandscape, "sync-landscape", false, "Deploy landscape from CI profile after waking up") + wakeup.cmd.Flags().StringVarP(&wakeup.Opts.Profile, "profile", "p", "", "CI profile to use for landscape deploy (e.g. 'prod' for ci.prod.yml)") + wakeup.cmd.Flags().StringVar(&wakeup.Opts.ScaleServices, "scale-services", "", "Scale specific landscape services (format: 'service=replicas,service2=replicas')") rootCmd.AddCommand(wakeup.cmd) wakeup.cmd.RunE = wakeup.RunE } @@ -92,6 +103,28 @@ func (c *WakeUpCmd) WakeUpWorkspace(client Client, wsId int) error { } } + if c.Opts.SyncLandscape { + log.Printf("Deploying landscape for workspace %d...\n", wsId) + err = client.DeployLandscape(wsId, c.Opts.Profile) + if err != nil { + return fmt.Errorf("failed to deploy landscape: %w", err) + } + log.Printf("Landscape deployment initiated for workspace %d\n", wsId) + } + + if c.Opts.ScaleServices != "" { + services, err := parseScaleServices(c.Opts.ScaleServices) + if err != nil { + return fmt.Errorf("failed to parse scale-services: %w", err) + } + log.Printf("Scaling landscape services for workspace %d: %v\n", wsId, services) + err = client.ScaleLandscapeServices(wsId, services) + if err != nil { + return fmt.Errorf("failed to scale landscape services: %w", err) + } + log.Printf("Landscape services scaled for workspace %d\n", wsId) + } + if workspace.DevDomain == nil || *workspace.DevDomain == "" { log.Printf("Workspace %d does not have a dev domain, skipping health check\n", wsId) return nil @@ -133,3 +166,32 @@ func (c *WakeUpCmd) waitForWorkspaceHealthy(devDomain string, timeout time.Durat time.Sleep(delay) } } + +// parseScaleServices parses a string like "web=1,api=2" into a map[string]int +func parseScaleServices(s string) (map[string]int, error) { + result := make(map[string]int) + if s == "" { + return result, nil + } + + pairs := strings.Split(s, ",") + for _, pair := range pairs { + parts := strings.SplitN(strings.TrimSpace(pair), "=", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid format '%s', expected 'service=replicas'", pair) + } + service := strings.TrimSpace(parts[0]) + if service == "" { + return nil, fmt.Errorf("empty service name in '%s'", pair) + } + replicas, err := strconv.Atoi(strings.TrimSpace(parts[1])) + if err != nil { + return nil, fmt.Errorf("invalid replica count '%s' for service '%s': %w", parts[1], service, err) + } + if replicas < 0 { + return nil, fmt.Errorf("replica count must be non-negative for service '%s'", service) + } + result[service] = replicas + } + return result, nil +} diff --git a/cli/cmd/wakeup_test.go b/cli/cmd/wakeup_test.go index 4b09ef7..865e8c4 100644 --- a/cli/cmd/wakeup_test.go +++ b/cli/cmd/wakeup_test.go @@ -98,5 +98,160 @@ var _ = Describe("WakeUp", func() { Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to scale workspace")) }) + + It("should sync landscape when SyncLandscape flag is set", func() { + workspace := api.Workspace{ + Id: wsId, + TeamId: teamId, + Name: "test-workspace", + } + c.Opts.SyncLandscape = true + + mockClient.EXPECT().GetWorkspace(wsId).Return(workspace, nil) + mockClient.EXPECT().WorkspaceStatus(wsId).Return(&api.WorkspaceStatus{IsRunning: false}, nil) + mockClient.EXPECT().ScaleWorkspace(wsId, 1).Return(nil) + mockClient.EXPECT().WaitForWorkspaceRunning(mock.Anything, mock.Anything).Return(nil) + mockClient.EXPECT().DeployLandscape(wsId, "").Return(nil) + + err := c.WakeUpWorkspace(mockClient, wsId) + + Expect(err).ToNot(HaveOccurred()) + }) + + It("should sync landscape with custom profile", func() { + workspace := api.Workspace{ + Id: wsId, + TeamId: teamId, + Name: "test-workspace", + } + c.Opts.SyncLandscape = true + c.Opts.Profile = "prod" + + mockClient.EXPECT().GetWorkspace(wsId).Return(workspace, nil) + mockClient.EXPECT().WorkspaceStatus(wsId).Return(&api.WorkspaceStatus{IsRunning: false}, nil) + mockClient.EXPECT().ScaleWorkspace(wsId, 1).Return(nil) + mockClient.EXPECT().WaitForWorkspaceRunning(mock.Anything, mock.Anything).Return(nil) + mockClient.EXPECT().DeployLandscape(wsId, "prod").Return(nil) + + err := c.WakeUpWorkspace(mockClient, wsId) + + Expect(err).ToNot(HaveOccurred()) + }) + + It("should return error if DeployLandscape fails", func() { + workspace := api.Workspace{ + Id: wsId, + TeamId: teamId, + Name: "test-workspace", + } + c.Opts.SyncLandscape = true + + mockClient.EXPECT().GetWorkspace(wsId).Return(workspace, nil) + mockClient.EXPECT().WorkspaceStatus(wsId).Return(&api.WorkspaceStatus{IsRunning: false}, nil) + mockClient.EXPECT().ScaleWorkspace(wsId, 1).Return(nil) + mockClient.EXPECT().WaitForWorkspaceRunning(mock.Anything, mock.Anything).Return(nil) + mockClient.EXPECT().DeployLandscape(wsId, "").Return(fmt.Errorf("deploy error")) + + err := c.WakeUpWorkspace(mockClient, wsId) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to deploy landscape")) + }) + + It("should sync landscape even when workspace is already running", func() { + workspace := api.Workspace{ + Id: wsId, + TeamId: teamId, + Name: "test-workspace", + } + c.Opts.SyncLandscape = true + + mockClient.EXPECT().GetWorkspace(wsId).Return(workspace, nil) + mockClient.EXPECT().WorkspaceStatus(wsId).Return(&api.WorkspaceStatus{IsRunning: true}, nil) + mockClient.EXPECT().DeployLandscape(wsId, "").Return(nil) + + err := c.WakeUpWorkspace(mockClient, wsId) + + Expect(err).ToNot(HaveOccurred()) + }) + + It("should scale landscape services when ScaleServices flag is set", func() { + workspace := api.Workspace{ + Id: wsId, + TeamId: teamId, + Name: "test-workspace", + } + c.Opts.ScaleServices = "web=1,api=2" + + mockClient.EXPECT().GetWorkspace(wsId).Return(workspace, nil) + mockClient.EXPECT().WorkspaceStatus(wsId).Return(&api.WorkspaceStatus{IsRunning: false}, nil) + mockClient.EXPECT().ScaleWorkspace(wsId, 1).Return(nil) + mockClient.EXPECT().WaitForWorkspaceRunning(mock.Anything, mock.Anything).Return(nil) + mockClient.EXPECT().ScaleLandscapeServices(wsId, map[string]int{"web": 1, "api": 2}).Return(nil) + + err := c.WakeUpWorkspace(mockClient, wsId) + + Expect(err).ToNot(HaveOccurred()) + }) + + It("should return error if ScaleLandscapeServices fails", func() { + workspace := api.Workspace{ + Id: wsId, + TeamId: teamId, + Name: "test-workspace", + } + c.Opts.ScaleServices = "web=1" + + mockClient.EXPECT().GetWorkspace(wsId).Return(workspace, nil) + mockClient.EXPECT().WorkspaceStatus(wsId).Return(&api.WorkspaceStatus{IsRunning: false}, nil) + mockClient.EXPECT().ScaleWorkspace(wsId, 1).Return(nil) + mockClient.EXPECT().WaitForWorkspaceRunning(mock.Anything, mock.Anything).Return(nil) + mockClient.EXPECT().ScaleLandscapeServices(wsId, map[string]int{"web": 1}).Return(fmt.Errorf("scale services error")) + + err := c.WakeUpWorkspace(mockClient, wsId) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to scale landscape services")) + }) + + It("should return error for invalid scale-services format", func() { + workspace := api.Workspace{ + Id: wsId, + TeamId: teamId, + Name: "test-workspace", + } + c.Opts.ScaleServices = "invalid" + + mockClient.EXPECT().GetWorkspace(wsId).Return(workspace, nil) + mockClient.EXPECT().WorkspaceStatus(wsId).Return(&api.WorkspaceStatus{IsRunning: false}, nil) + mockClient.EXPECT().ScaleWorkspace(wsId, 1).Return(nil) + mockClient.EXPECT().WaitForWorkspaceRunning(mock.Anything, mock.Anything).Return(nil) + + err := c.WakeUpWorkspace(mockClient, wsId) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failed to parse scale-services")) + }) + + It("should use both sync-landscape and scale-services together", func() { + workspace := api.Workspace{ + Id: wsId, + TeamId: teamId, + Name: "test-workspace", + } + c.Opts.SyncLandscape = true + c.Opts.ScaleServices = "web=1" + + mockClient.EXPECT().GetWorkspace(wsId).Return(workspace, nil) + mockClient.EXPECT().WorkspaceStatus(wsId).Return(&api.WorkspaceStatus{IsRunning: false}, nil) + mockClient.EXPECT().ScaleWorkspace(wsId, 1).Return(nil) + mockClient.EXPECT().WaitForWorkspaceRunning(mock.Anything, mock.Anything).Return(nil) + mockClient.EXPECT().DeployLandscape(wsId, "").Return(nil) + mockClient.EXPECT().ScaleLandscapeServices(wsId, map[string]int{"web": 1}).Return(nil) + + err := c.WakeUpWorkspace(mockClient, wsId) + + Expect(err).ToNot(HaveOccurred()) + }) }) }) From 6f5e9c80bf8794faee6dc92f1780aa0c8cc39a4d Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:45:34 +0100 Subject: [PATCH 3/8] fix: enhance curl command to support path handling and follow redirects --- cli/cmd/curl.go | 21 ++++++++++++++++----- cli/cmd/curl_test.go | 16 ++++++++++++---- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/cli/cmd/curl.go b/cli/cmd/curl.go index c365fbd..384264d 100644 --- a/cli/cmd/curl.go +++ b/cli/cmd/curl.go @@ -10,6 +10,7 @@ import ( "log" "os" "os/exec" + "strings" "time" io_pkg "github.com/codesphere-cloud/cs-go/pkg/io" @@ -53,8 +54,17 @@ func (c *CurlCmd) RunE(_ *cobra.Command, args []string) error { return fmt.Errorf("failed to get API token: %w", err) } - path := args[0] - curlArgs := args[1:] + path := "/" + var curlArgs []string + + if len(args) > 0 { + if strings.HasPrefix(args[0], "/") { + path = args[0] + curlArgs = args[1:] + } else { + curlArgs = args + } + } return c.CurlWorkspace(client, wsId, token, path, curlArgs) } @@ -70,9 +80,10 @@ func AddCurlCmd(rootCmd *cobra.Command, opts GlobalOptions) { {Cmd: "/api/health -w 1234", Desc: "GET request to health endpoint"}, {Cmd: "/api/data -w 1234 -- -XPOST -d '{\"key\":\"value\"}'", Desc: "POST request with data"}, {Cmd: "/api/endpoint -w 1234 -- -v", Desc: "verbose output"}, + {Cmd: "-w 1234 -- -v", Desc: "verbose request to workspace root"}, {Cmd: "/ -- -k", Desc: "skip TLS verification"}, {Cmd: "/ -- -I", Desc: "HEAD request using workspace from env var"}, }), - Args: cobra.MinimumNArgs(1), + Args: cobra.ArbitraryArgs, }, Opts: CurlOptions{ GlobalOptions: opts, @@ -104,8 +115,8 @@ func (c *CurlCmd) CurlWorkspace(client Client, wsId int, token string, path stri ctx, cancel := context.WithTimeout(context.Background(), c.Opts.Timeout) defer cancel() - // Build curl command with authentication header - cmdArgs := []string{"curl", "-H", fmt.Sprintf("x-forward-security: %s", token)} + // Build curl command with authentication header and -L to follow redirects + cmdArgs := []string{"curl", "-L", "-H", fmt.Sprintf("x-forward-security: %s", token)} cmdArgs = append(cmdArgs, curlArgs...) cmdArgs = append(cmdArgs, url) diff --git a/cli/cmd/curl_test.go b/cli/cmd/curl_test.go index eb376d6..32febd4 100644 --- a/cli/cmd/curl_test.go +++ b/cli/cmd/curl_test.go @@ -61,11 +61,15 @@ var _ = Describe("Curl", func() { mock.Anything, "curl", mock.MatchedBy(func(args []string) bool { - // Verify the args contain the expected header, flag, and URL + // Verify the args contain the expected header, flag, -L and URL + hasFollowRedirects := false hasHeader := false hasFlag := false hasURL := false for i, arg := range args { + if arg == "-L" { + hasFollowRedirects = true + } if arg == "-H" && i+1 < len(args) && args[i+1] == fmt.Sprintf("x-forward-security: %s", token) { hasHeader = true } @@ -76,7 +80,7 @@ var _ = Describe("Curl", func() { hasURL = true } } - return hasHeader && hasFlag && hasURL + return hasFollowRedirects && hasHeader && hasFlag && hasURL }), mock.Anything, mock.Anything, @@ -93,10 +97,14 @@ var _ = Describe("Curl", func() { mock.Anything, "curl", mock.MatchedBy(func(args []string) bool { - // Verify the URL contains the custom path + // Verify the URL contains the custom path and -L flag + hasFollowRedirects := false hasHeader := false hasURL := false for i, arg := range args { + if arg == "-L" { + hasFollowRedirects = true + } if arg == "-H" && i+1 < len(args) && args[i+1] == fmt.Sprintf("x-forward-security: %s", token) { hasHeader = true } @@ -104,7 +112,7 @@ var _ = Describe("Curl", func() { hasURL = true } } - return hasHeader && hasURL + return hasFollowRedirects && hasHeader && hasURL }), mock.Anything, mock.Anything, From bcc4bdd5b6a91dd7791479067f021063bcc41927 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 25 Feb 2026 10:48:23 +0100 Subject: [PATCH 4/8] fix: log workspace response status in waitForWorkspaceHealthy function --- cli/cmd/wakeup.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/cmd/wakeup.go b/cli/cmd/wakeup.go index 48478a6..4b335f6 100644 --- a/cli/cmd/wakeup.go +++ b/cli/cmd/wakeup.go @@ -157,6 +157,9 @@ func (c *WakeUpCmd) waitForWorkspaceHealthy(devDomain string, timeout time.Durat if resp.StatusCode == http.StatusOK { return nil } + + log.Printf("Workspace %s responded with status code %d\n", devDomain, resp.StatusCode) + return nil } if time.Now().After(maxWaitTime) { From c7d6ed0e605c0738c1258013a258c814dfd9d0e3 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:52:30 +0100 Subject: [PATCH 5/8] fix: update authorization header to use X-CS-Authorization and modify waitForWorkspaceHealthy to accept token --- cli/cmd/curl.go | 2 +- cli/cmd/curl_test.go | 4 ++-- cli/cmd/wakeup.go | 23 +++++++++++++++++------ 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/cli/cmd/curl.go b/cli/cmd/curl.go index 384264d..df1fd2b 100644 --- a/cli/cmd/curl.go +++ b/cli/cmd/curl.go @@ -116,7 +116,7 @@ func (c *CurlCmd) CurlWorkspace(client Client, wsId int, token string, path stri defer cancel() // Build curl command with authentication header and -L to follow redirects - cmdArgs := []string{"curl", "-L", "-H", fmt.Sprintf("x-forward-security: %s", token)} + cmdArgs := []string{"curl", "-L", "-H", fmt.Sprintf("X-CS-Authorization: Bearer %s", token)} cmdArgs = append(cmdArgs, curlArgs...) cmdArgs = append(cmdArgs, url) diff --git a/cli/cmd/curl_test.go b/cli/cmd/curl_test.go index 32febd4..ef0bc5d 100644 --- a/cli/cmd/curl_test.go +++ b/cli/cmd/curl_test.go @@ -70,7 +70,7 @@ var _ = Describe("Curl", func() { if arg == "-L" { hasFollowRedirects = true } - if arg == "-H" && i+1 < len(args) && args[i+1] == fmt.Sprintf("x-forward-security: %s", token) { + if arg == "-H" && i+1 < len(args) && args[i+1] == fmt.Sprintf("X-CS-Authorization: Bearer %s", token) { hasHeader = true } if arg == "-I" { @@ -105,7 +105,7 @@ var _ = Describe("Curl", func() { if arg == "-L" { hasFollowRedirects = true } - if arg == "-H" && i+1 < len(args) && args[i+1] == fmt.Sprintf("x-forward-security: %s", token) { + if arg == "-H" && i+1 < len(args) && args[i+1] == fmt.Sprintf("X-CS-Authorization: Bearer %s", token) { hasHeader = true } if arg == "https://42-3000.dev.5.codesphere.com/custom/path" { diff --git a/cli/cmd/wakeup.go b/cli/cmd/wakeup.go index 4b335f6..4561769 100644 --- a/cli/cmd/wakeup.go +++ b/cli/cmd/wakeup.go @@ -131,7 +131,13 @@ func (c *WakeUpCmd) WakeUpWorkspace(client Client, wsId int) error { } log.Printf("Checking health of workspace %d (%s)...\n", wsId, workspace.Name) - err = c.waitForWorkspaceHealthy(*workspace.DevDomain, c.Opts.Timeout) + + token, err := c.Opts.Env.GetApiToken() + if err != nil { + return fmt.Errorf("failed to get API token: %w", err) + } + + err = c.waitForWorkspaceHealthy(*workspace.DevDomain, token, c.Opts.Timeout) if err != nil { return fmt.Errorf("workspace did not become healthy: %w", err) } @@ -141,7 +147,7 @@ func (c *WakeUpCmd) WakeUpWorkspace(client Client, wsId int) error { return nil } -func (c *WakeUpCmd) waitForWorkspaceHealthy(devDomain string, timeout time.Duration) error { +func (c *WakeUpCmd) waitForWorkspaceHealthy(devDomain string, token string, timeout time.Duration) error { url := fmt.Sprintf("https://%s", devDomain) delay := 5 * time.Second maxWaitTime := time.Now().Add(timeout) @@ -151,15 +157,20 @@ func (c *WakeUpCmd) waitForWorkspaceHealthy(devDomain string, timeout time.Durat } for { - resp, err := httpClient.Get(url) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("X-CS-Authorization", fmt.Sprintf("Bearer %s", token)) + + resp, err := httpClient.Do(req) if err == nil { resp.Body.Close() if resp.StatusCode == http.StatusOK { return nil } - - log.Printf("Workspace %s responded with status code %d\n", devDomain, resp.StatusCode) - return nil + log.Printf("Workspace %s responded with status code %d, retrying...\n", devDomain, resp.StatusCode) } if time.Now().After(maxWaitTime) { From d5db257d6a4ed7e3fb88e5573cf9a84c2ec4f493 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Fri, 27 Feb 2026 16:11:13 +0100 Subject: [PATCH 6/8] fix: update error message for service replica count validation --- api/workspace.go | 4 ++-- cli/cmd/wakeup.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/workspace.go b/api/workspace.go index 2009857..56977d6 100644 --- a/api/workspace.go +++ b/api/workspace.go @@ -124,8 +124,8 @@ func (c *Client) ScaleWorkspace(wsId int, replicas int) error { func (c *Client) ScaleLandscapeServices(wsId int, services map[string]int) error { req := c.api.WorkspacesAPI.WorkspacesScaleLandscapeServices(c.ctx, float32(wsId)). RequestBody(services) - _, err := req.Execute() - return errors.FormatAPIError(err) + resp, err := req.Execute() + return errors.FormatAPIError(resp, err) } // Waits for a given workspace to be running. diff --git a/cli/cmd/wakeup.go b/cli/cmd/wakeup.go index 054d3e7..d5391e2 100644 --- a/cli/cmd/wakeup.go +++ b/cli/cmd/wakeup.go @@ -202,8 +202,8 @@ func parseScaleServices(s string) (map[string]int, error) { if err != nil { return nil, fmt.Errorf("invalid replica count '%s' for service '%s': %w", parts[1], service, err) } - if replicas < 0 { - return nil, fmt.Errorf("replica count must be non-negative for service '%s'", service) + if replicas < 1 { + return nil, fmt.Errorf("replica count must be at least 1 for service '%s'", service) } result[service] = replicas } From 41eec555f70b0d8f28a8ff24d6cb81a4320a6847 Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:13:04 +0000 Subject: [PATCH 7/8] chore(docs): Auto-update docs and licenses Signed-off-by: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> --- NOTICE | 12 +- api/mocks.go | 10 +- cli/cmd/mocks.go | 289 ++++++++++++++++++++++++++++++++++-------- docs/cs_curl.md | 3 + docs/cs_wake-up.md | 18 ++- pkg/exporter/mocks.go | 79 ++++++++++-- pkg/git/mocks.go | 31 ++++- pkg/io/mocks.go | 99 ++++++++++++--- pkg/tmpl/NOTICE | 12 +- 9 files changed, 450 insertions(+), 103 deletions(-) diff --git a/NOTICE b/NOTICE index c861e91..16e5f1e 100644 --- a/NOTICE +++ b/NOTICE @@ -5,9 +5,9 @@ This project includes code licensed under the following terms: ---------- Module: code.gitea.io/sdk/gitea -Version: v0.22.1 +Version: v0.23.2 License: MIT -License URL: https://gitea.com/gitea/go-sdk/src/tag/gitea/v0.22.1/gitea/LICENSE +License URL: https://gitea.com/gitea/go-sdk/src/tag/gitea/v0.23.2/gitea/LICENSE ---------- Module: dario.cat/mergo @@ -365,9 +365,9 @@ License URL: https://github.com/xanzy/ssh-agent/blob/v0.3.3/LICENSE ---------- Module: gitlab.com/gitlab-org/api/client-go -Version: v1.11.0 +Version: v1.39.0 License: Apache-2.0 -License URL: https://gitlab.com/gitlab-org/api/blob/client-go/v1.11.0/client-go/LICENSE +License URL: https://gitlab.com/gitlab-org/api/blob/client-go/v1.39.0/client-go/LICENSE ---------- Module: go.yaml.in/yaml/v2 @@ -401,9 +401,9 @@ License URL: https://cs.opensource.google/go/x/net/+/v0.50.0:LICENSE ---------- Module: golang.org/x/oauth2 -Version: v0.34.0 +Version: v0.35.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/oauth2/+/v0.34.0:LICENSE +License URL: https://cs.opensource.google/go/x/oauth2/+/v0.35.0:LICENSE ---------- Module: golang.org/x/sync/errgroup diff --git a/api/mocks.go b/api/mocks.go index 5e2084e..05a287a 100644 --- a/api/mocks.go +++ b/api/mocks.go @@ -92,14 +92,20 @@ type MockTime_Sleep_Call struct { } // Sleep is a helper method to define mock.On call -// - duration +// - duration time.Duration func (_e *MockTime_Expecter) Sleep(duration interface{}) *MockTime_Sleep_Call { return &MockTime_Sleep_Call{Call: _e.mock.On("Sleep", duration)} } func (_c *MockTime_Sleep_Call) Run(run func(duration time.Duration)) *MockTime_Sleep_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(time.Duration)) + var arg0 time.Duration + if args[0] != nil { + arg0 = args[0].(time.Duration) + } + run( + arg0, + ) }) return _c } diff --git a/cli/cmd/mocks.go b/cli/cmd/mocks.go index 810b78e..42246f9 100644 --- a/cli/cmd/mocks.go +++ b/cli/cmd/mocks.go @@ -62,14 +62,20 @@ type MockClient_DeleteWorkspace_Call struct { } // DeleteWorkspace is a helper method to define mock.On call -// - wsId +// - wsId int func (_e *MockClient_Expecter) DeleteWorkspace(wsId interface{}) *MockClient_DeleteWorkspace_Call { return &MockClient_DeleteWorkspace_Call{Call: _e.mock.On("DeleteWorkspace", wsId)} } func (_c *MockClient_DeleteWorkspace_Call) Run(run func(wsId int)) *MockClient_DeleteWorkspace_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(int)) + var arg0 int + if args[0] != nil { + arg0 = args[0].(int) + } + run( + arg0, + ) }) return _c } @@ -107,15 +113,26 @@ type MockClient_DeployLandscape_Call struct { } // DeployLandscape is a helper method to define mock.On call -// - wsId -// - profile +// - wsId int +// - profile string func (_e *MockClient_Expecter) DeployLandscape(wsId interface{}, profile interface{}) *MockClient_DeployLandscape_Call { return &MockClient_DeployLandscape_Call{Call: _e.mock.On("DeployLandscape", wsId, profile)} } func (_c *MockClient_DeployLandscape_Call) Run(run func(wsId int, profile string)) *MockClient_DeployLandscape_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(int), args[1].(string)) + var arg0 int + if args[0] != nil { + arg0 = args[0].(int) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } @@ -164,14 +181,20 @@ type MockClient_DeployWorkspace_Call struct { } // DeployWorkspace is a helper method to define mock.On call -// - args +// - args api.DeployWorkspaceArgs func (_e *MockClient_Expecter) DeployWorkspace(args interface{}) *MockClient_DeployWorkspace_Call { return &MockClient_DeployWorkspace_Call{Call: _e.mock.On("DeployWorkspace", args)} } func (_c *MockClient_DeployWorkspace_Call) Run(run func(args api.DeployWorkspaceArgs)) *MockClient_DeployWorkspace_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(api.DeployWorkspaceArgs)) + var arg0 api.DeployWorkspaceArgs + if args[0] != nil { + arg0 = args[0].(api.DeployWorkspaceArgs) + } + run( + arg0, + ) }) return _c } @@ -224,17 +247,38 @@ type MockClient_ExecCommand_Call struct { } // ExecCommand is a helper method to define mock.On call -// - workspaceId -// - command -// - workdir -// - env +// - workspaceId int +// - command string +// - workdir string +// - env map[string]string func (_e *MockClient_Expecter) ExecCommand(workspaceId interface{}, command interface{}, workdir interface{}, env interface{}) *MockClient_ExecCommand_Call { return &MockClient_ExecCommand_Call{Call: _e.mock.On("ExecCommand", workspaceId, command, workdir, env)} } func (_c *MockClient_ExecCommand_Call) Run(run func(workspaceId int, command string, workdir string, env map[string]string)) *MockClient_ExecCommand_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(int), args[1].(string), args[2].(string), args[3].(map[string]string)) + var arg0 int + if args[0] != nil { + arg0 = args[0].(int) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 map[string]string + if args[3] != nil { + arg3 = args[3].(map[string]string) + } + run( + arg0, + arg1, + arg2, + arg3, + ) }) return _c } @@ -283,15 +327,26 @@ type MockClient_GetPipelineState_Call struct { } // GetPipelineState is a helper method to define mock.On call -// - wsId -// - stage +// - wsId int +// - stage string func (_e *MockClient_Expecter) GetPipelineState(wsId interface{}, stage interface{}) *MockClient_GetPipelineState_Call { return &MockClient_GetPipelineState_Call{Call: _e.mock.On("GetPipelineState", wsId, stage)} } func (_c *MockClient_GetPipelineState_Call) Run(run func(wsId int, stage string)) *MockClient_GetPipelineState_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(int), args[1].(string)) + var arg0 int + if args[0] != nil { + arg0 = args[0].(int) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + run( + arg0, + arg1, + ) }) return _c } @@ -338,14 +393,20 @@ type MockClient_GetWorkspace_Call struct { } // GetWorkspace is a helper method to define mock.On call -// - workspaceId +// - workspaceId int func (_e *MockClient_Expecter) GetWorkspace(workspaceId interface{}) *MockClient_GetWorkspace_Call { return &MockClient_GetWorkspace_Call{Call: _e.mock.On("GetWorkspace", workspaceId)} } func (_c *MockClient_GetWorkspace_Call) Run(run func(workspaceId int)) *MockClient_GetWorkspace_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(int)) + var arg0 int + if args[0] != nil { + arg0 = args[0].(int) + } + run( + arg0, + ) }) return _c } @@ -383,16 +444,32 @@ type MockClient_GitPull_Call struct { } // GitPull is a helper method to define mock.On call -// - wsId -// - remote -// - branch +// - wsId int +// - remote string +// - branch string func (_e *MockClient_Expecter) GitPull(wsId interface{}, remote interface{}, branch interface{}) *MockClient_GitPull_Call { return &MockClient_GitPull_Call{Call: _e.mock.On("GitPull", wsId, remote, branch)} } func (_c *MockClient_GitPull_Call) Run(run func(wsId int, remote string, branch string)) *MockClient_GitPull_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(int), args[1].(string), args[2].(string)) + var arg0 int + if args[0] != nil { + arg0 = args[0].(int) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -606,14 +683,20 @@ type MockClient_ListWorkspaces_Call struct { } // ListWorkspaces is a helper method to define mock.On call -// - teamId +// - teamId int func (_e *MockClient_Expecter) ListWorkspaces(teamId interface{}) *MockClient_ListWorkspaces_Call { return &MockClient_ListWorkspaces_Call{Call: _e.mock.On("ListWorkspaces", teamId)} } func (_c *MockClient_ListWorkspaces_Call) Run(run func(teamId int)) *MockClient_ListWorkspaces_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(int)) + var arg0 int + if args[0] != nil { + arg0 = args[0].(int) + } + run( + arg0, + ) }) return _c } @@ -651,15 +734,26 @@ type MockClient_ScaleLandscapeServices_Call struct { } // ScaleLandscapeServices is a helper method to define mock.On call -// - wsId -// - services +// - wsId int +// - services map[string]int func (_e *MockClient_Expecter) ScaleLandscapeServices(wsId interface{}, services interface{}) *MockClient_ScaleLandscapeServices_Call { return &MockClient_ScaleLandscapeServices_Call{Call: _e.mock.On("ScaleLandscapeServices", wsId, services)} } func (_c *MockClient_ScaleLandscapeServices_Call) Run(run func(wsId int, services map[string]int)) *MockClient_ScaleLandscapeServices_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(int), args[1].(map[string]int)) + var arg0 int + if args[0] != nil { + arg0 = args[0].(int) + } + var arg1 map[string]int + if args[1] != nil { + arg1 = args[1].(map[string]int) + } + run( + arg0, + arg1, + ) }) return _c } @@ -697,15 +791,26 @@ type MockClient_ScaleWorkspace_Call struct { } // ScaleWorkspace is a helper method to define mock.On call -// - wsId -// - replicas +// - wsId int +// - replicas int func (_e *MockClient_Expecter) ScaleWorkspace(wsId interface{}, replicas interface{}) *MockClient_ScaleWorkspace_Call { return &MockClient_ScaleWorkspace_Call{Call: _e.mock.On("ScaleWorkspace", wsId, replicas)} } func (_c *MockClient_ScaleWorkspace_Call) Run(run func(wsId int, replicas int)) *MockClient_ScaleWorkspace_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(int), args[1].(int)) + var arg0 int + if args[0] != nil { + arg0 = args[0].(int) + } + var arg1 int + if args[1] != nil { + arg1 = args[1].(int) + } + run( + arg0, + arg1, + ) }) return _c } @@ -743,15 +848,26 @@ type MockClient_SetEnvVarOnWorkspace_Call struct { } // SetEnvVarOnWorkspace is a helper method to define mock.On call -// - workspaceId -// - vars +// - workspaceId int +// - vars map[string]string func (_e *MockClient_Expecter) SetEnvVarOnWorkspace(workspaceId interface{}, vars interface{}) *MockClient_SetEnvVarOnWorkspace_Call { return &MockClient_SetEnvVarOnWorkspace_Call{Call: _e.mock.On("SetEnvVarOnWorkspace", workspaceId, vars)} } func (_c *MockClient_SetEnvVarOnWorkspace_Call) Run(run func(workspaceId int, vars map[string]string)) *MockClient_SetEnvVarOnWorkspace_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(int), args[1].(map[string]string)) + var arg0 int + if args[0] != nil { + arg0 = args[0].(int) + } + var arg1 map[string]string + if args[1] != nil { + arg1 = args[1].(map[string]string) + } + run( + arg0, + arg1, + ) }) return _c } @@ -789,16 +905,32 @@ type MockClient_StartPipelineStage_Call struct { } // StartPipelineStage is a helper method to define mock.On call -// - wsId -// - profile -// - stage +// - wsId int +// - profile string +// - stage string func (_e *MockClient_Expecter) StartPipelineStage(wsId interface{}, profile interface{}, stage interface{}) *MockClient_StartPipelineStage_Call { return &MockClient_StartPipelineStage_Call{Call: _e.mock.On("StartPipelineStage", wsId, profile, stage)} } func (_c *MockClient_StartPipelineStage_Call) Run(run func(wsId int, profile string, stage string)) *MockClient_StartPipelineStage_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(int), args[1].(string), args[2].(string)) + var arg0 int + if args[0] != nil { + arg0 = args[0].(int) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -836,15 +968,26 @@ type MockClient_WaitForWorkspaceRunning_Call struct { } // WaitForWorkspaceRunning is a helper method to define mock.On call -// - workspace -// - timeout +// - workspace *api.Workspace +// - timeout time.Duration func (_e *MockClient_Expecter) WaitForWorkspaceRunning(workspace interface{}, timeout interface{}) *MockClient_WaitForWorkspaceRunning_Call { return &MockClient_WaitForWorkspaceRunning_Call{Call: _e.mock.On("WaitForWorkspaceRunning", workspace, timeout)} } func (_c *MockClient_WaitForWorkspaceRunning_Call) Run(run func(workspace *api.Workspace, timeout time.Duration)) *MockClient_WaitForWorkspaceRunning_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*api.Workspace), args[1].(time.Duration)) + var arg0 *api.Workspace + if args[0] != nil { + arg0 = args[0].(*api.Workspace) + } + var arg1 time.Duration + if args[1] != nil { + arg1 = args[1].(time.Duration) + } + run( + arg0, + arg1, + ) }) return _c } @@ -893,14 +1036,20 @@ type MockClient_WorkspaceStatus_Call struct { } // WorkspaceStatus is a helper method to define mock.On call -// - workspaceId +// - workspaceId int func (_e *MockClient_Expecter) WorkspaceStatus(workspaceId interface{}) *MockClient_WorkspaceStatus_Call { return &MockClient_WorkspaceStatus_Call{Call: _e.mock.On("WorkspaceStatus", workspaceId)} } func (_c *MockClient_WorkspaceStatus_Call) Run(run func(workspaceId int)) *MockClient_WorkspaceStatus_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(int)) + var arg0 int + if args[0] != nil { + arg0 = args[0].(int) + } + run( + arg0, + ) }) return _c } @@ -965,18 +1114,44 @@ type MockCommandExecutor_Execute_Call struct { } // Execute is a helper method to define mock.On call -// - ctx -// - name -// - args -// - stdout -// - stderr +// - ctx context.Context +// - name string +// - args []string +// - stdout io.Writer +// - stderr io.Writer func (_e *MockCommandExecutor_Expecter) Execute(ctx interface{}, name interface{}, args interface{}, stdout interface{}, stderr interface{}) *MockCommandExecutor_Execute_Call { return &MockCommandExecutor_Execute_Call{Call: _e.mock.On("Execute", ctx, name, args, stdout, stderr)} } func (_c *MockCommandExecutor_Execute_Call) Run(run func(ctx context.Context, name string, args []string, stdout io.Writer, stderr io.Writer)) *MockCommandExecutor_Execute_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].([]string), args[3].(io.Writer), args[4].(io.Writer)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 []string + if args[2] != nil { + arg2 = args[2].([]string) + } + var arg3 io.Writer + if args[3] != nil { + arg3 = args[3].(io.Writer) + } + var arg4 io.Writer + if args[4] != nil { + arg4 = args[4].(io.Writer) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + ) }) return _c } @@ -1041,14 +1216,20 @@ type MockPrompt_InputPrompt_Call struct { } // InputPrompt is a helper method to define mock.On call -// - prompt +// - prompt string func (_e *MockPrompt_Expecter) InputPrompt(prompt interface{}) *MockPrompt_InputPrompt_Call { return &MockPrompt_InputPrompt_Call{Call: _e.mock.On("InputPrompt", prompt)} } func (_c *MockPrompt_InputPrompt_Call) Run(run func(prompt string)) *MockPrompt_InputPrompt_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } @@ -1113,14 +1294,20 @@ type MockBrowser_OpenIde_Call struct { } // OpenIde is a helper method to define mock.On call -// - path +// - path string func (_e *MockBrowser_Expecter) OpenIde(path interface{}) *MockBrowser_OpenIde_Call { return &MockBrowser_OpenIde_Call{Call: _e.mock.On("OpenIde", path)} } func (_c *MockBrowser_OpenIde_Call) Run(run func(path string)) *MockBrowser_OpenIde_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } diff --git a/docs/cs_curl.md b/docs/cs_curl.md index 5bf8d07..73bd022 100644 --- a/docs/cs_curl.md +++ b/docs/cs_curl.md @@ -25,6 +25,9 @@ $ cs curl /api/data -w 1234 -- -XPOST -d '{"key":"value"}' # verbose output $ cs curl /api/endpoint -w 1234 -- -v +# verbose request to workspace root +$ cs curl -w 1234 -- -v + # skip TLS verification $ cs curl / -- -k diff --git a/docs/cs_wake-up.md b/docs/cs_wake-up.md index fa746d5..478f373 100644 --- a/docs/cs_wake-up.md +++ b/docs/cs_wake-up.md @@ -4,7 +4,7 @@ Wake up an on-demand workspace ### Synopsis -Wake up an on-demand workspace by scaling it to 1 replica via the API. +Wake up an on-demand workspace by scaling it to 1 replica via the API. Optionally syncs the landscape to start services. ``` cs wake-up [flags] @@ -21,13 +21,25 @@ $ cs wake-up # wake up workspace with 60 second timeout $ cs wake-up -w 1234 --timeout 60s + +# wake up workspace and deploy landscape from CI profile +$ cs wake-up -w 1234 --sync-landscape + +# wake up workspace and deploy landscape with prod profile +$ cs wake-up -w 1234 --sync-landscape --profile prod + +# wake up workspace and scale specific services +$ cs wake-up -w 1234 --scale-services web=1,api=2 ``` ### Options ``` - -h, --help help for wake-up - --timeout duration Timeout for waking up the workspace (default 2m0s) + -h, --help help for wake-up + -p, --profile string CI profile to use for landscape deploy (e.g. 'prod' for ci.prod.yml) + --scale-services string Scale specific landscape services (format: 'service=replicas,service2=replicas') + --sync-landscape Deploy landscape from CI profile after waking up + --timeout duration Timeout for waking up the workspace (default 2m0s) ``` ### Options inherited from parent commands diff --git a/pkg/exporter/mocks.go b/pkg/exporter/mocks.go index 67c0262..a2febe4 100644 --- a/pkg/exporter/mocks.go +++ b/pkg/exporter/mocks.go @@ -104,16 +104,32 @@ type MockExporter_ExportImages_Call struct { } // ExportImages is a helper method to define mock.On call -// - ctx -// - registry -// - imagePrefix +// - ctx context.Context +// - registry string +// - imagePrefix string func (_e *MockExporter_Expecter) ExportImages(ctx interface{}, registry interface{}, imagePrefix interface{}) *MockExporter_ExportImages_Call { return &MockExporter_ExportImages_Call{Call: _e.mock.On("ExportImages", ctx, registry, imagePrefix)} } func (_c *MockExporter_ExportImages_Call) Run(run func(ctx context.Context, registry string, imagePrefix string)) *MockExporter_ExportImages_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + run( + arg0, + arg1, + arg2, + ) }) return _c } @@ -151,19 +167,50 @@ type MockExporter_ExportKubernetesArtifacts_Call struct { } // ExportKubernetesArtifacts is a helper method to define mock.On call -// - registry -// - image -// - namespace -// - pullSecret -// - hostname -// - ingressClass +// - registry string +// - image string +// - namespace string +// - pullSecret string +// - hostname string +// - ingressClass string func (_e *MockExporter_Expecter) ExportKubernetesArtifacts(registry interface{}, image interface{}, namespace interface{}, pullSecret interface{}, hostname interface{}, ingressClass interface{}) *MockExporter_ExportKubernetesArtifacts_Call { return &MockExporter_ExportKubernetesArtifacts_Call{Call: _e.mock.On("ExportKubernetesArtifacts", registry, image, namespace, pullSecret, hostname, ingressClass)} } func (_c *MockExporter_ExportKubernetesArtifacts_Call) Run(run func(registry string, image string, namespace string, pullSecret string, hostname string, ingressClass string)) *MockExporter_ExportKubernetesArtifacts_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(string), args[2].(string), args[3].(string), args[4].(string), args[5].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + var arg4 string + if args[4] != nil { + arg4 = args[4].(string) + } + var arg5 string + if args[5] != nil { + arg5 = args[5].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + ) }) return _c } @@ -212,14 +259,20 @@ type MockExporter_ReadYmlFile_Call struct { } // ReadYmlFile is a helper method to define mock.On call -// - path +// - path string func (_e *MockExporter_Expecter) ReadYmlFile(path interface{}) *MockExporter_ReadYmlFile_Call { return &MockExporter_ReadYmlFile_Call{Call: _e.mock.On("ReadYmlFile", path)} } func (_c *MockExporter_ReadYmlFile_Call) Run(run func(path string)) *MockExporter_ReadYmlFile_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) }) return _c } diff --git a/pkg/git/mocks.go b/pkg/git/mocks.go index a3f3c0f..fa2afdb 100644 --- a/pkg/git/mocks.go +++ b/pkg/git/mocks.go @@ -71,17 +71,38 @@ type MockGit_CloneRepository_Call struct { } // CloneRepository is a helper method to define mock.On call -// - fs -// - url -// - branch -// - path +// - fs *cs.FileSystem +// - url string +// - branch string +// - path string func (_e *MockGit_Expecter) CloneRepository(fs interface{}, url interface{}, branch interface{}, path interface{}) *MockGit_CloneRepository_Call { return &MockGit_CloneRepository_Call{Call: _e.mock.On("CloneRepository", fs, url, branch, path)} } func (_c *MockGit_CloneRepository_Call) Run(run func(fs *cs.FileSystem, url string, branch string, path string)) *MockGit_CloneRepository_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*cs.FileSystem), args[1].(string), args[2].(string), args[3].(string)) + var arg0 *cs.FileSystem + if args[0] != nil { + arg0 = args[0].(*cs.FileSystem) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + ) }) return _c } diff --git a/pkg/io/mocks.go b/pkg/io/mocks.go index 8e26b84..92c93f6 100644 --- a/pkg/io/mocks.go +++ b/pkg/io/mocks.go @@ -69,15 +69,26 @@ type MockExec_ExecuteCommand_Call struct { } // ExecuteCommand is a helper method to define mock.On call -// - ctx -// - cmdArgs +// - ctx context.Context +// - cmdArgs []string func (_e *MockExec_Expecter) ExecuteCommand(ctx interface{}, cmdArgs interface{}) *MockExec_ExecuteCommand_Call { return &MockExec_ExecuteCommand_Call{Call: _e.mock.On("ExecuteCommand", ctx, cmdArgs)} } func (_c *MockExec_ExecuteCommand_Call) Run(run func(ctx context.Context, cmdArgs []string)) *MockExec_ExecuteCommand_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]string)) + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 []string + if args[1] != nil { + arg1 = args[1].([]string) + } + run( + arg0, + arg1, + ) }) return _c } @@ -131,15 +142,26 @@ type MockHttpServer_Handle_Call struct { } // Handle is a helper method to define mock.On call -// - pattern -// - handler +// - pattern string +// - handler http.Handler func (_e *MockHttpServer_Expecter) Handle(pattern interface{}, handler interface{}) *MockHttpServer_Handle_Call { return &MockHttpServer_Handle_Call{Call: _e.mock.On("Handle", pattern, handler)} } func (_c *MockHttpServer_Handle_Call) Run(run func(pattern string, handler http.Handler)) *MockHttpServer_Handle_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(http.Handler)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 http.Handler + if args[1] != nil { + arg1 = args[1].(http.Handler) + } + run( + arg0, + arg1, + ) }) return _c } @@ -166,15 +188,26 @@ type MockHttpServer_HandleFunc_Call struct { } // HandleFunc is a helper method to define mock.On call -// - pattern -// - handler +// - pattern string +// - handler func(http.ResponseWriter, *http.Request) func (_e *MockHttpServer_Expecter) HandleFunc(pattern interface{}, handler interface{}) *MockHttpServer_HandleFunc_Call { return &MockHttpServer_HandleFunc_Call{Call: _e.mock.On("HandleFunc", pattern, handler)} } func (_c *MockHttpServer_HandleFunc_Call) Run(run func(pattern string, handler func(http.ResponseWriter, *http.Request))) *MockHttpServer_HandleFunc_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(func(http.ResponseWriter, *http.Request))) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 func(http.ResponseWriter, *http.Request) + if args[1] != nil { + arg1 = args[1].(func(http.ResponseWriter, *http.Request)) + } + run( + arg0, + arg1, + ) }) return _c } @@ -212,15 +245,26 @@ type MockHttpServer_ListenAndServe_Call struct { } // ListenAndServe is a helper method to define mock.On call -// - addr -// - handler +// - addr string +// - handler http.Handler func (_e *MockHttpServer_Expecter) ListenAndServe(addr interface{}, handler interface{}) *MockHttpServer_ListenAndServe_Call { return &MockHttpServer_ListenAndServe_Call{Call: _e.mock.On("ListenAndServe", addr, handler)} } func (_c *MockHttpServer_ListenAndServe_Call) Run(run func(addr string, handler http.Handler)) *MockHttpServer_ListenAndServe_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string), args[1].(http.Handler)) + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + var arg1 http.Handler + if args[1] != nil { + arg1 = args[1].(http.Handler) + } + run( + arg0, + arg1, + ) }) return _c } @@ -247,17 +291,38 @@ type MockHttpServer_Redirect_Call struct { } // Redirect is a helper method to define mock.On call -// - w -// - r -// - url -// - code +// - w http.ResponseWriter +// - r *http.Request +// - url string +// - code int func (_e *MockHttpServer_Expecter) Redirect(w interface{}, r interface{}, url interface{}, code interface{}) *MockHttpServer_Redirect_Call { return &MockHttpServer_Redirect_Call{Call: _e.mock.On("Redirect", w, r, url, code)} } func (_c *MockHttpServer_Redirect_Call) Run(run func(w http.ResponseWriter, r *http.Request, url string, code int)) *MockHttpServer_Redirect_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(http.ResponseWriter), args[1].(*http.Request), args[2].(string), args[3].(int)) + var arg0 http.ResponseWriter + if args[0] != nil { + arg0 = args[0].(http.ResponseWriter) + } + var arg1 *http.Request + if args[1] != nil { + arg1 = args[1].(*http.Request) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 int + if args[3] != nil { + arg3 = args[3].(int) + } + run( + arg0, + arg1, + arg2, + arg3, + ) }) return _c } diff --git a/pkg/tmpl/NOTICE b/pkg/tmpl/NOTICE index c861e91..16e5f1e 100644 --- a/pkg/tmpl/NOTICE +++ b/pkg/tmpl/NOTICE @@ -5,9 +5,9 @@ This project includes code licensed under the following terms: ---------- Module: code.gitea.io/sdk/gitea -Version: v0.22.1 +Version: v0.23.2 License: MIT -License URL: https://gitea.com/gitea/go-sdk/src/tag/gitea/v0.22.1/gitea/LICENSE +License URL: https://gitea.com/gitea/go-sdk/src/tag/gitea/v0.23.2/gitea/LICENSE ---------- Module: dario.cat/mergo @@ -365,9 +365,9 @@ License URL: https://github.com/xanzy/ssh-agent/blob/v0.3.3/LICENSE ---------- Module: gitlab.com/gitlab-org/api/client-go -Version: v1.11.0 +Version: v1.39.0 License: Apache-2.0 -License URL: https://gitlab.com/gitlab-org/api/blob/client-go/v1.11.0/client-go/LICENSE +License URL: https://gitlab.com/gitlab-org/api/blob/client-go/v1.39.0/client-go/LICENSE ---------- Module: go.yaml.in/yaml/v2 @@ -401,9 +401,9 @@ License URL: https://cs.opensource.google/go/x/net/+/v0.50.0:LICENSE ---------- Module: golang.org/x/oauth2 -Version: v0.34.0 +Version: v0.35.0 License: BSD-3-Clause -License URL: https://cs.opensource.google/go/x/oauth2/+/v0.34.0:LICENSE +License URL: https://cs.opensource.google/go/x/oauth2/+/v0.35.0:LICENSE ---------- Module: golang.org/x/sync/errgroup From 724db194588c5563aaa2c1276146515f81d0a0de Mon Sep 17 00:00:00 2001 From: OliverTrautvetter <66372584+OliverTrautvetter@users.noreply.github.com> Date: Fri, 27 Feb 2026 16:15:28 +0100 Subject: [PATCH 8/8] fix: lint error --- cli/cmd/wakeup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/cmd/wakeup.go b/cli/cmd/wakeup.go index d5391e2..eae64d3 100644 --- a/cli/cmd/wakeup.go +++ b/cli/cmd/wakeup.go @@ -166,7 +166,7 @@ func (c *WakeUpCmd) waitForWorkspaceHealthy(devDomain string, token string, time resp, err := httpClient.Do(req) if err == nil { - resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode == http.StatusOK { return nil }