Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 2 additions & 3 deletions assets/test_workspace.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{
"workspaceId": "test_workspace_id",
"workspaceGroupId": "test_workspace_group_id"
}
"workspaceId": "test_workspace_id"
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ module github.com/brevdev/brev-cli
go 1.25.0

require (
buf.build/gen/go/brevdev/devplane/connectrpc/go v1.20.0-20260626205643-49b0d20e08f1.1
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260626205643-49b0d20e08f1.1
buf.build/gen/go/brevdev/devplane/connectrpc/go v1.20.0-20260708012811-ecba52f49600.1
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260708012811-ecba52f49600.1
connectrpc.com/connect v1.20.0
github.com/NVIDIA/go-nvml v0.13.0-1
github.com/alessio/shellescape v1.4.1
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
buf.build/gen/go/brevdev/devplane/connectrpc/go v1.20.0-20260626205643-49b0d20e08f1.1 h1:Qj4BTbhIF0KE5YHiJJ+SN2goGYF8dJC1l8cv69YU/Ms=
buf.build/gen/go/brevdev/devplane/connectrpc/go v1.20.0-20260626205643-49b0d20e08f1.1/go.mod h1:KW+lsYUmrF994Z/zj/wibrS7zhitXrYLicqR5BbVSp0=
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260626205643-49b0d20e08f1.1 h1:+GNKe6qV3aRH+N/FBlH6NfqyKOxMecAtbHndj3NPZc4=
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260626205643-49b0d20e08f1.1/go.mod h1:V/y7Wxg0QvU4XPVwqErF5NHLobUT1QEyfgrGuQIxdPo=
buf.build/gen/go/brevdev/devplane/connectrpc/go v1.20.0-20260708012811-ecba52f49600.1 h1:xanul5g4JQ0OPAQ3tjN8bTznw+aA6B/oq3pzOy8kC8Q=
buf.build/gen/go/brevdev/devplane/connectrpc/go v1.20.0-20260708012811-ecba52f49600.1/go.mod h1:ZxWENaPM6882Wtl2z6rZYVpXoagSyF6DiY/6m4BjGMU=
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260708012811-ecba52f49600.1 h1:KMs3AGf1zys1H8TnjBCorCd12zzWoUQae956KgsNfRM=
buf.build/gen/go/brevdev/devplane/protocolbuffers/go v1.36.11-20260708012811-ecba52f49600.1/go.mod h1:V/y7Wxg0QvU4XPVwqErF5NHLobUT1QEyfgrGuQIxdPo=
buf.build/gen/go/brevdev/protoc-gen-gotag/protocolbuffers/go v1.36.11-20220906235457-8b4922735da5.1 h1:6amhprQmCKJ4wgJ6ngkh32d9V+dQcOLUZ/SfHdOnYgo=
buf.build/gen/go/brevdev/protoc-gen-gotag/protocolbuffers/go v1.36.11-20220906235457-8b4922735da5.1/go.mod h1:O+pnSHMru/naTMrm4tmpBoH3wz6PHa+R75HR7Mv8X2g=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
Expand Down
9 changes: 0 additions & 9 deletions pkg/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,6 @@ func NewBrevCommand() *cobra.Command { //nolint:funlen,gocognit,gocyclo // defin

analytics.SetUserStore(noLoginCmdStore)

workspaceGroupID, err := fsStore.GetCurrentWorkspaceGroupID()
if err != nil {
fmt.Printf("%v\n", err)
}
if workspaceGroupID != "" {
loginCmdStore.WithStaticHeader("X-Workspace-Group-ID", workspaceGroupID)
noLoginCmdStore.WithStaticHeader("X-Workspace-Group-ID", workspaceGroupID)
}

cmds := &cobra.Command{
SilenceErrors: true,
SilenceUsage: true,
Expand Down
1 change: 0 additions & 1 deletion pkg/cmd/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ func createEmptyWorkspace(user *entity.User, t *terminal.Terminal, options Creat
} else {
t.Vprintf("\tCPU instance %s\n", t.Green(cwOptions.WorkspaceClassID))
}
t.Vprintf("\tCloud %s\n\n", t.Green(cwOptions.WorkspaceGroupID))

s := t.NewSpinner()
s.Suffix = " Creating your instance. Hang tight 🤙"
Expand Down
63 changes: 40 additions & 23 deletions pkg/cmd/gpucreate/gpucreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ type GPUCreateStore interface {
GetWorkspace(workspaceID string) (*entity.Workspace, error)
CreateWorkspace(organizationID string, options *store.CreateWorkspacesOptions) (*entity.Workspace, error)
DeleteWorkspace(workspaceID string) (*entity.Workspace, error)
GetAllInstanceTypesWithWorkspaceGroups(orgID string) (*gpusearch.AllInstanceTypesResponse, error)
GetAllInstanceTypesWithCloudCreds(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)
Expand Down Expand Up @@ -768,11 +768,11 @@ func newCreateContext(t *terminal.Terminal, store GPUCreateStore, opts GPUCreate
}
ctx.org = org

// Fetch instance types with workspace groups
allInstanceTypes, err := store.GetAllInstanceTypesWithWorkspaceGroups(org.ID)
// Fetch instance types with cloud credentials.
allInstanceTypes, err := store.GetAllInstanceTypesWithCloudCreds(org.ID)
if err != nil {
ctx.logf("Warning: could not fetch instance types with workspace groups: %s\n", err.Error())
ctx.logf("Falling back to default workspace group\n")
ctx.logf("Warning: could not fetch instance types with cloud credentials: %s\n", err.Error())
ctx.logf("Falling back to default cloud credential\n")
}
ctx.allInstanceTypes = allInstanceTypes

Expand All @@ -791,7 +791,7 @@ func (c *createContext) validateInstanceTypeAvailability(instanceType string) er
if c.allInstanceTypes == nil {
return nil
}
if c.allInstanceTypes.GetWorkspaceGroupID(instanceType) != "" {
if c.allInstanceTypes.GetCloudCredID(instanceType) != "" {
return nil
}
if !c.allInstanceTypes.HasInstanceType(instanceType) {
Expand Down Expand Up @@ -1042,8 +1042,8 @@ func (c *createContext) createWorkspace(name string, spec InstanceSpec) (*entity
}

if c.allInstanceTypes != nil {
if wgID := c.allInstanceTypes.GetWorkspaceGroupID(spec.Type); wgID != "" {
cwOptions.WorkspaceGroupID = wgID
if cloudCredID := c.allInstanceTypes.GetCloudCredID(spec.Type); cloudCredID != "" {
cwOptions.WithCloudCredID(cloudCredID)
}
}

Expand All @@ -1057,10 +1057,10 @@ func (c *createContext) createWorkspace(name string, spec InstanceSpec) (*entity
}
}

if cwOptions.WorkspaceGroupID == "" {
if cwOptions.CloudCredID == "" {
if c.allInstanceTypes == nil {
return nil, breverrors.NewValidationError(fmt.Sprintf(
"could not resolve workspace group for %q (instance-type listing was unavailable); please retry",
"could not resolve cloud credential for %q (instance-type listing was unavailable); please retry",
spec.Type,
))
}
Expand Down Expand Up @@ -1176,13 +1176,17 @@ func applyLaunchableConfig(cwOptions *store.CreateWorkspacesOptions, launchableI
return
}

wsReq := info.CreateWorkspaceRequest
applyLaunchableWorkspaceRequest(cwOptions, info.CreateWorkspaceRequest)
applyLaunchableBuildRequest(cwOptions, info.BuildRequest)
applyLaunchableFile(cwOptions, info.File)
applyLaunchableLabels(cwOptions, launchableID, info)
}

// Use launchable's workspace group if not already resolved from instance types
if cwOptions.WorkspaceGroupID == "" && wsReq.WorkspaceGroupID != "" {
cwOptions.WorkspaceGroupID = wsReq.WorkspaceGroupID
func applyLaunchableWorkspaceRequest(cwOptions *store.CreateWorkspacesOptions, wsReq store.LaunchableWorkspaceRequest) {
// Use launchable's cloud credential if not already resolved from instance types.
if cwOptions.CloudCredID == "" && wsReq.CloudCredID != "" {
cwOptions.WithCloudCredID(wsReq.CloudCredID)
}

// Location / sub-location
if wsReq.Location != "" {
cwOptions.Location = wsReq.Location
Expand All @@ -1198,8 +1202,13 @@ func applyLaunchableConfig(cwOptions *store.CreateWorkspacesOptions, launchableI
cwOptions.DiskStorage = normalizeDiskStorage(wsReq.Storage)
}

if len(wsReq.FirewallRules) > 0 {
cwOptions.FirewallRules = resolveFirewallRulesClientIP(wsReq.FirewallRules, publicIPLookup)
}
}

func applyLaunchableBuildRequest(cwOptions *store.CreateWorkspacesOptions, build store.LaunchableBuildRequest) {
// Build configuration from launchable
build := info.BuildRequest
switch {
case build.VMBuild != nil:
cwOptions.VMBuild = build.VMBuild
Expand All @@ -1208,7 +1217,7 @@ func applyLaunchableConfig(cwOptions *store.CreateWorkspacesOptions, launchableI
cwOptions.CustomContainer = build.CustomContainer
case build.DockerCompose != nil:
cwOptions.VMBuild = nil
cwOptions.DockerCompose = build.DockerCompose
cwOptions.DockerCompose = normalizeLaunchableDockerCompose(build.DockerCompose)
}

// Port mappings from build request ports
Expand All @@ -1219,18 +1228,26 @@ func applyLaunchableConfig(cwOptions *store.CreateWorkspacesOptions, launchableI
}
cwOptions.PortMappings = portMappings
}
}

if len(wsReq.FirewallRules) > 0 {
cwOptions.FirewallRules = resolveFirewallRulesClientIP(wsReq.FirewallRules, publicIPLookup)
func normalizeLaunchableDockerCompose(dockerCompose *store.DockerCompose) *store.DockerCompose {
normalized := *dockerCompose
if normalized.FileURL != "" {
normalized.YamlString = ""
}
return &normalized
}
Comment on lines +1233 to +1239

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

@patelspratik patelspratik Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was a bug I found at the same time while I was trying to test. Turns out the create command with a launchable was wrong and threw a hard error CLI side. You can't supply both of those fields.


func applyLaunchableFile(cwOptions *store.CreateWorkspacesOptions, file *store.LaunchableFile) {
// Files from launchable
if info.File != nil {
if file != nil {
cwOptions.Files = []map[string]string{
{"url": info.File.URL, "path": info.File.Path},
{"url": file.URL, "path": file.Path},
}
}
}

func applyLaunchableLabels(cwOptions *store.CreateWorkspacesOptions, launchableID string, info *store.LaunchableResponse) {
// Labels for tracking and UI rendering — merge with any existing labels
var labels map[string]string
if existing, ok := cwOptions.Labels.(map[string]string); ok && existing != nil {
Expand All @@ -1239,8 +1256,8 @@ func applyLaunchableConfig(cwOptions *store.CreateWorkspacesOptions, launchableI
labels = make(map[string]string)
}
labels["launchableId"] = launchableID
labels["launchableInstanceType"] = wsReq.InstanceType
labels["workspaceGroupId"] = cwOptions.WorkspaceGroupID
labels["launchableInstanceType"] = info.CreateWorkspaceRequest.InstanceType
labels["cloudCredId"] = cwOptions.CloudCredID
labels["launchableCreatedByUserId"] = info.CreatedByUserID
labels["launchableCreatedByOrgId"] = info.CreatedByOrgID
labels["launchableRawURL"] = "/launchable/deploy/now?launchableID=" + launchableID
Expand Down
Loading
Loading