diff --git a/pkg/cmd/gpucreate/gpucreate.go b/pkg/cmd/gpucreate/gpucreate.go index 9950fe86..6034414f 100644 --- a/pkg/cmd/gpucreate/gpucreate.go +++ b/pkg/cmd/gpucreate/gpucreate.go @@ -105,6 +105,7 @@ type GPUCreateStore interface { DeleteWorkspace(workspaceID string) (*entity.Workspace, error) GetAllInstanceTypesWithWorkspaceGroups(orgID string) (*gpusearch.AllInstanceTypesResponse, error) GetLaunchable(launchableID string) (*store.LaunchableResponse, error) + GetLaunchableLifeCycleScript(launchableID, scriptID string) (*store.LifeCycleScriptResponse, error) RedeemCouponCode(organizationID string, code string) (*store.RedeemCouponCodeResponse, error) } @@ -394,6 +395,11 @@ func fetchAndDisplayLaunchable(gpuCreateStore GPUCreateStore, t *terminal.Termin return nil, fmt.Errorf("failed to fetch launchable %q: %w", launchableID, err) } + // Inline the script body; the launchable GET only returns its id. + if err := inlineLaunchableLifeCycleScript(gpuCreateStore, launchableID, info); err != nil { + return nil, err + } + t.Vprintf("Deploying launchable: %q\n", info.Name) if info.Description != "" { t.Vprintf("Description: %s\n", info.Description) @@ -410,6 +416,24 @@ func fetchAndDisplayLaunchable(gpuCreateStore GPUCreateStore, t *terminal.Termin return info, nil } +func inlineLaunchableLifeCycleScript(gpuCreateStore GPUCreateStore, launchableID string, info *store.LaunchableResponse) error { + if info == nil || info.BuildRequest.VMBuild == nil { + return nil + } + attr := info.BuildRequest.VMBuild.LifeCycleScriptAttr + if attr == nil || attr.ID == "" || attr.Script != "" { + return nil + } + resp, err := gpuCreateStore.GetLaunchableLifeCycleScript(launchableID, attr.ID) + if err != nil { + return fmt.Errorf("failed to fetch lifecycle script %q for launchable %q: %w", attr.ID, launchableID, err) + } + if resp != nil && resp.Attrs != nil { + attr.Script = resp.Attrs.Script + } + return nil +} + func launchableBuildModeName(info *store.LaunchableResponse) string { switch { case info.BuildRequest.CustomContainer != nil: diff --git a/pkg/cmd/gpucreate/gpucreate_test.go b/pkg/cmd/gpucreate/gpucreate_test.go index be791982..d315a928 100644 --- a/pkg/cmd/gpucreate/gpucreate_test.go +++ b/pkg/cmd/gpucreate/gpucreate_test.go @@ -110,6 +110,12 @@ func (m *MockGPUCreateStore) GetLaunchable(launchableID string) (*store.Launchab }, nil } +func (m *MockGPUCreateStore) GetLaunchableLifeCycleScript(launchableID, scriptID string) (*store.LifeCycleScriptResponse, error) { + return &store.LifeCycleScriptResponse{ + Attrs: &store.LifeCycleScriptAttr{ID: scriptID, Script: "echo mock-script"}, + }, nil +} + func (m *MockGPUCreateStore) RedeemCouponCode(organizationID string, code string) (*store.RedeemCouponCodeResponse, error) { return &store.RedeemCouponCodeResponse{}, nil } diff --git a/pkg/store/workspace.go b/pkg/store/workspace.go index 35f5d4f1..030eef07 100644 --- a/pkg/store/workspace.go +++ b/pkg/store/workspace.go @@ -283,6 +283,29 @@ func (s AuthHTTPStore) GetLaunchable(launchableID string) (*LaunchableResponse, return &result, nil } +// LifeCycleScriptResponse holds a lifecycle script with the script body populated. +type LifeCycleScriptResponse struct { + Attrs *LifeCycleScriptAttr `json:"attrs"` +} + +// GetLaunchableLifeCycleScript fetches the full lifecycle script for a launchable. +func (s AuthHTTPStore) GetLaunchableLifeCycleScript(launchableID, scriptID string) (*LifeCycleScriptResponse, error) { + var result LifeCycleScriptResponse + res, err := s.authHTTPClient.restyClient.R(). + SetHeader("Content-Type", "application/json"). + SetQueryParam("envId", launchableID). + SetQueryParam("scriptId", scriptID). + SetResult(&result). + Get("api/launchable/lifecycle-script") + if err != nil { + return nil, breverrors.WrapAndTrace(err) + } + if res.IsError() { + return nil, NewHTTPResponseError(res) + } + return &result, nil +} + type GetWorkspacesOptions struct { UserID string Name string