Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/cmd/cli/command/globals.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func NewGlobalConfig() *GlobalConfig {

hastty := term.IsTerminal() && !pkg.GetenvBool("CI")

tenant := types.TenantNameOrID("")
tenant := client.GetCurrentTenant()
if fromEnv, ok := os.LookupEnv("DEFANG_WORKSPACE"); ok {
tenant = types.TenantNameOrID(fromEnv)
} else if fromEnv, ok := os.LookupEnv("DEFANG_ORG"); ok {
Expand Down
24 changes: 21 additions & 3 deletions src/cmd/cli/command/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import (
"github.com/DefangLabs/defang/src/pkg/cli"
"github.com/DefangLabs/defang/src/pkg/cli/client"
"github.com/DefangLabs/defang/src/pkg/term"
"github.com/DefangLabs/defang/src/pkg/types"
"github.com/spf13/cobra"
)

func ListWorkspaces(cmd *cobra.Command, args []string) error {
func listWorkspaces(cmd *cobra.Command, args []string) error {
jsonMode, _ := cmd.Flags().GetBool("json")
verbose := global.Verbose

Expand Down Expand Up @@ -64,7 +65,7 @@ var workspaceCmd = &cobra.Command{
Args: cobra.NoArgs,
Annotations: authNeededAnnotation,
Short: "Manage workspaces",
RunE: ListWorkspaces,
RunE: listWorkspaces,
}

var workspaceListCmd = &cobra.Command{
Expand All @@ -73,11 +74,28 @@ var workspaceListCmd = &cobra.Command{
Args: cobra.NoArgs,
Annotations: authNeededAnnotation,
Short: "List available workspaces",
RunE: ListWorkspaces,
RunE: listWorkspaces,
}

var workspaceSelectCmd = &cobra.Command{
Use: "select WORKSPACE",
Aliases: []string{"use", "switch"},
Args: cobra.ExactArgs(1),
// Annotations: authNeededAnnotation,
Short: "Select a workspace to use",
RunE: func(cmd *cobra.Command, args []string) error {
global.Tenant = types.TenantNameOrID(args[0])
if _, err := cli.ConnectWithTenant(cmd.Context(), global.Cluster, global.Tenant); err != nil {
return err
}
term.Infof("Switched to workspace %q\n", global.Tenant)
return client.SetCurrentTenant(global.Tenant)
},
}

func init() {
workspaceCmd.Flags().Bool("json", pkg.GetenvBool("DEFANG_JSON"), "print output in JSON format")
workspaceListCmd.Flags().Bool("json", pkg.GetenvBool("DEFANG_JSON"), "print output in JSON format")
workspaceCmd.AddCommand(workspaceListCmd)
workspaceCmd.AddCommand(workspaceSelectCmd)
}
12 changes: 12 additions & 0 deletions src/pkg/cli/client/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"path/filepath"
"time"

"github.com/DefangLabs/defang/src/pkg/types"
"github.com/google/uuid"
)

Expand All @@ -20,6 +21,7 @@ var (
type State struct {
AnonID string
TermsAcceptedAt time.Time
Tenant types.TenantNameOrID
}

func initState(path string) State {
Expand Down Expand Up @@ -63,3 +65,13 @@ func AcceptTerms() error {
func TermsAccepted() bool {
return state.termsAccepted()
}

func SetCurrentTenant(tenant types.TenantNameOrID) error {
state.Tenant = tenant
return state.write(statePath)
}
Comment on lines +69 to +72
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential data loss: SetCurrentTenant may overwrite uninitialized state.

If SetCurrentTenant is called before any getter (like GetAnonID or GetCurrentTenant), the package-level state variable will still be zero-valued. Writing it will overwrite any existing AnonID and TermsAcceptedAt in the state file.

Consider initializing the state before modifying it:

Proposed fix
 func SetCurrentTenant(tenant types.TenantNameOrID) error {
+	state = initState(statePath)
 	state.Tenant = tenant
 	return state.write(statePath)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func SetCurrentTenant(tenant types.TenantNameOrID) error {
state.Tenant = tenant
return state.write(statePath)
}
func SetCurrentTenant(tenant types.TenantNameOrID) error {
state = initState(statePath)
state.Tenant = tenant
return state.write(statePath)
}
🤖 Prompt for AI Agents
In `@src/pkg/cli/client/state.go` around lines 69 - 72, SetCurrentTenant currently
assigns to the zero-valued package-level state and writes it, which can erase
existing fields; before mutating, read/initialize the existing state from disk
(e.g., call the state reader like state.read(statePath) or use the package
getter that ensures state is loaded), handle any read error, then set
state.Tenant and call state.write(statePath) so existing AnonID and
TermsAcceptedAt are preserved; update SetCurrentTenant to perform this
load-before-write flow and propagate errors.


func GetCurrentTenant() types.TenantNameOrID {
state = initState(statePath)
return state.Tenant
}
Loading