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
3 changes: 0 additions & 3 deletions src/cmd/cli/command/cd.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ func cdCommand(cmd *cobra.Command, args []string, command client.CdCommand, fabr

session, err := newCommandSessionWithOpts(cmd, commandSessionOpts{
CheckAccountInfo: true,
RequireStack: false, // for `cd` it's OK to proceed without a stack
})
if err != nil {
return err
Expand Down Expand Up @@ -118,7 +117,6 @@ var cdTearDownCmd = &cobra.Command{

session, err := newCommandSessionWithOpts(cmd, commandSessionOpts{
CheckAccountInfo: true,
RequireStack: false, // for `cd` it's OK to proceed without a stack
})
if err != nil {
return err
Expand All @@ -139,7 +137,6 @@ var cdListCmd = &cobra.Command{

session, err := newCommandSessionWithOpts(cmd, commandSessionOpts{
CheckAccountInfo: true,
RequireStack: false, // for `cd` it's OK to proceed without a stack
})
if err != nil {
return err
Expand Down
4 changes: 1 addition & 3 deletions src/cmd/cli/command/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ func makeComposeUpCmd() *cobra.Command {

options := newSessionLoaderOptionsForCommand(cmd)
options.AllowStackCreation = true
options.RequireStack = true
sm, err := newStackManagerForLoader(ctx, configureLoader(cmd))
if err != nil {
return err
Expand Down Expand Up @@ -546,10 +545,9 @@ func makeComposeConfigCmd() *cobra.Command {

session, err := newCommandSessionWithOpts(cmd, commandSessionOpts{
CheckAccountInfo: false,
RequireStack: false, // for `compose config` it's OK to proceed without a stack
})
if err != nil {
return fmt.Errorf("loading session: %w", err)
return err
}

_, err = session.Provider.AccountInfo(ctx)
Expand Down
16 changes: 14 additions & 2 deletions src/cmd/cli/command/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/AlecAivazis/survey/v2"
"github.com/DefangLabs/defang/src/pkg/cli"
"github.com/DefangLabs/defang/src/pkg/cli/client"
"github.com/DefangLabs/defang/src/pkg/session"
"github.com/DefangLabs/defang/src/pkg/term"
"github.com/bufbuild/connect-go"
"github.com/joho/godotenv"
Expand All @@ -30,6 +31,7 @@ var configSetCmd = &cobra.Command{
Aliases: []string{"set", "add", "put"},
Short: "Adds or updates a sensitive config value",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
fromEnv, _ := cmd.Flags().GetBool("env")
random, _ := cmd.Flags().GetBool("random")
envFile, _ := cmd.Flags().GetString("env-file")
Expand Down Expand Up @@ -62,11 +64,21 @@ var configSetCmd = &cobra.Command{
return errors.New("too many arguments; provide a single CONFIG or use --env, --random, or --env-file")
}

// Make sure we have a project to set config for before asking for a value
session, err := newCommandSession(cmd)
options := newSessionLoaderOptionsForCommand(cmd)
options.GetStackOpts.AllowStackCreation = true
sm, err := newStackManagerForLoader(ctx, configureLoader(cmd))
if err != nil {
return err
}
sessionLoader := session.NewSessionLoader(global.Client, sm, options)
session, err := sessionLoader.LoadSession(ctx)
if err != nil {
return err
}
_, err = session.Provider.AccountInfo(ctx)
if err != nil {
return fmt.Errorf("failed to get account info from provider %q: %w", session.Stack.Provider, err)
}

projectName, err := client.LoadProjectNameWithFallback(cmd.Context(), session.Loader, session.Provider)
if err != nil {
Expand Down
8 changes: 1 addition & 7 deletions src/cmd/cli/command/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,20 @@ import (

type commandSessionOpts struct {
CheckAccountInfo bool
RequireStack bool
}

func newCommandSession(cmd *cobra.Command) (*session.Session, error) {
return newCommandSessionWithOpts(cmd, commandSessionOpts{
CheckAccountInfo: true,
RequireStack: true,
})
}

func newCommandSessionWithOpts(cmd *cobra.Command, opts commandSessionOpts) (*session.Session, error) {
ctx := cmd.Context()

options := newSessionLoaderOptionsForCommand(cmd)
options.RequireStack = opts.RequireStack
sm, err := newStackManagerForLoader(ctx, configureLoader(cmd))
if err != nil {
if opts.RequireStack {
return nil, err
}
term.Debugf("Could not create stack manager: %v", err)
}
sessionLoader := session.NewSessionLoader(global.Client, sm, options)
Expand Down Expand Up @@ -80,10 +74,10 @@ func newSessionLoaderOptionsForCommand(cmd *cobra.Command) session.SessionLoader
}
}
return session.SessionLoaderOptions{
ProviderID: *provider,
ComposeFilePaths: configPaths,
ProjectName: projectName,
GetStackOpts: stacks.GetStackOpts{
ProviderID: *provider,
Interactive: !global.NonInteractive,
Stack: stack,
},
Expand Down
15 changes: 9 additions & 6 deletions src/cmd/cli/command/whoami.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package command

import (
"encoding/json"
"fmt"

"github.com/DefangLabs/defang/src/pkg/auth"
"github.com/DefangLabs/defang/src/pkg/cli"
Expand All @@ -22,25 +21,29 @@ var whoamiCmd = &cobra.Command{

global.NonInteractive = true // don't show provider prompt

var provider client.Provider
session, err := newCommandSessionWithOpts(cmd, commandSessionOpts{
CheckAccountInfo: false,
RequireStack: false, // for WhoAmI it's OK to proceed without a stack
CheckAccountInfo: false, // because we do it inside cli.Whoami
})
if err != nil {
return fmt.Errorf("loading session: %w", err)
if !jsonMode {
term.Warnf("Provider account information not available: %v", err)
}
} else {
provider = session.Provider
}

token := client.GetExistingToken(global.Cluster)

userInfo, err := auth.FetchUserInfo(ctx, token)
if err != nil {
// Either the auth service is down, or we're using a Fabric JWT: skip workspace information
if !jsonMode {
if !jsonMode && global.HasTty {
term.Warn("Workspace information unavailable:", err)
}
}

data, err := cli.Whoami(ctx, global.Client, session.Provider, userInfo, global.Tenant)
data, err := cli.Whoami(ctx, global.Client, provider, userInfo, global.Tenant)
if err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions src/pkg/cli/client/projectName.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/DefangLabs/defang/src/pkg/term"
)

// Deprecated: should use stacks instead of ProjectName fallback.
func LoadProjectNameWithFallback(ctx context.Context, loader Loader, provider Provider) (string, error) {
var loadErr error
if loader != nil {
Expand Down
1 change: 1 addition & 0 deletions src/pkg/cli/client/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ type Provider interface {
Preview(context.Context, *DeployRequest) (*defangv1.DeployResponse, error)
PutConfig(context.Context, *defangv1.PutConfigRequest) error
QueryLogs(context.Context, *defangv1.TailRequest) (ServerStream[defangv1.TailResponse], error)
// Deprecated: should use stacks instead of ProjectName fallback.
RemoteProjectName(context.Context) (string, error)
SetCanIUseConfig(*defangv1.CanIUseResponse)
SetUpCD(context.Context) error
Expand Down
31 changes: 3 additions & 28 deletions src/pkg/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package session

import (
"context"
"errors"
"fmt"
"os"

"github.com/DefangLabs/defang/src/pkg"
"github.com/DefangLabs/defang/src/pkg/cli"
Expand All @@ -27,7 +25,6 @@ type Session struct {
}

type SessionLoaderOptions struct {
ProviderID client.ProviderID
ProjectName string
ComposeFilePaths []string
stacks.GetStackOpts
Expand Down Expand Up @@ -77,34 +74,12 @@ func (sl *SessionLoader) loadStack(ctx context.Context) (*stacks.Parameters, str
Provider: sl.opts.ProviderID,
}, "no stack manager available", nil
}

stack, whence, err := sl.sm.GetStack(ctx, sl.opts.GetStackOpts)
if err != nil {
if !errors.Is(err, stacks.ErrDefaultStackNotSet) {
return nil, "", err
}
if sl.opts.ProviderID != "" {
whence = "--provider flag"
}
_, envSet := os.LookupEnv("DEFANG_PROVIDER")
if envSet {
whence = "DEFANG_PROVIDER"
}
if whence == "" {
whence = "fallback stack"
}
if sl.opts.ProviderID == client.ProviderAuto {
sl.opts.ProviderID = client.ProviderDefang
}
return &stacks.Parameters{
Name: stacks.DefaultBeta,
Provider: sl.opts.ProviderID,
}, whence, nil
}
envProvider := os.Getenv("DEFANG_PROVIDER")
if envProvider != "" && client.ProviderID(envProvider) != stack.Provider {
os.Unsetenv("DEFANG_PROVIDER")
term.Warnf("The variable DEFANG_PROVIDER is set to %q in the environment, but the selected stack %q uses provider %q. So the environment variable will be ignored.", envProvider, stack.Name, stack.Provider)
return nil, whence, err
}

if err := stacks.LoadStackEnv(*stack, true); err != nil {
return nil, whence, fmt.Errorf("failed to load stack env: %w", err)
}
Expand Down
52 changes: 7 additions & 45 deletions src/pkg/session/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,30 +82,13 @@ func TestLoadSession(t *testing.T) {
options SessionLoaderOptions
existingStack *stacks.Parameters
stacksList []stacks.ListItem
getStackError error
expectedError string
expectedStack *stacks.Parameters
expectedEnv map[string]string
}{
{
name: "empty options - fallback stack",
options: SessionLoaderOptions{},
expectedStack: &stacks.Parameters{
Name: "beta",
},
},
{
name: "specified non-existing stack",
options: SessionLoaderOptions{
GetStackOpts: stacks.GetStackOpts{
Stack: "missingstack",
},
},

expectedError: "stack \"missingstack\" does not exist",
expectedEnv: map[string]string{},
},
{
name: "specified existing stack",
name: "specified stack",
options: SessionLoaderOptions{
GetStackOpts: stacks.GetStackOpts{
Stack: "existingstack",
Expand All @@ -123,28 +106,6 @@ func TestLoadSession(t *testing.T) {
},
},
},
{
name: "only project name specified",
options: SessionLoaderOptions{
ProjectName: "foo",
},
expectedStack: &stacks.Parameters{
Name: "beta",
},
},
{
name: "provider specified without stack assumes beta stack",
options: SessionLoaderOptions{
ProjectName: "foo",
ProviderID: client.ProviderAWS,
},
expectedStack: &stacks.Parameters{
Name: "beta",
Provider: client.ProviderAWS,
Variables: map[string]string{},
},
expectedError: "",
},
}

for _, tt := range tests {
Expand All @@ -164,9 +125,8 @@ func TestLoadSession(t *testing.T) {
if tt.options.GetStackOpts.Stack != "" {
// For specified non-existing stack, return ErrNotExist
sm.On("GetStack", ctx, mock.Anything).Maybe().Return(nil, "", &stacks.ErrNotExist{StackName: tt.options.GetStackOpts.Stack})
} else {
// For empty stack (should fall back to beta), return ErrDefaultStackNotSet
sm.On("GetStack", ctx, mock.Anything).Maybe().Return(nil, "", stacks.ErrDefaultStackNotSet)
} else if tt.getStackError != nil {
sm.On("GetStack", ctx, mock.Anything).Maybe().Return(nil, "", tt.getStackError)
}
} else {
sm.On("GetStack", ctx, mock.Anything).Maybe().Return(tt.existingStack, "local", nil)
Expand Down Expand Up @@ -220,7 +180,9 @@ func TestLoadSession_NoStackManager(t *testing.T) {
ctx := t.Context()

options := SessionLoaderOptions{
ProviderID: client.ProviderDefang,
GetStackOpts: stacks.GetStackOpts{
ProviderID: client.ProviderDefang,
},
}

loader := NewSessionLoader(client.MockFabricClient{}, nil, options)
Expand Down
38 changes: 34 additions & 4 deletions src/pkg/stacks/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"
"time"

"github.com/DefangLabs/defang/src/pkg/cli/client"
"github.com/DefangLabs/defang/src/pkg/elicitations"
"github.com/DefangLabs/defang/src/pkg/modes"
"github.com/DefangLabs/defang/src/pkg/term"
Expand Down Expand Up @@ -200,20 +201,50 @@ func (sm *manager) Create(params Parameters) (string, error) {
}

type GetStackOpts struct {
ProviderID client.ProviderID
Stack string
Interactive bool
RequireStack bool
AllowStackCreation bool
}

func (sl *manager) GetStack(ctx context.Context, opts GetStackOpts) (*Parameters, string, error) {
// use --stack if available
if opts.Stack != "" {
return sl.getSpecifiedStack(ctx, opts.Stack)
}
if opts.Interactive && opts.RequireStack {
// use --provider if available
if opts.ProviderID != client.ProviderAuto && opts.ProviderID != "" {
whence := "DEFANG_PROVIDER"
envProvider := os.Getenv("DEFANG_PROVIDER")
if envProvider != opts.ProviderID.String() {
whence = "--provider flag"
}
return &Parameters{
Name: DefaultBeta,
Provider: opts.ProviderID,
}, whence, nil
}
// fallback to interactive
if opts.Interactive {
return sl.getStackInteractively(ctx, opts)
}
return sl.getDefaultStack(ctx)
// fallback to default stack
stack, whence, err := sl.getDefaultStack(ctx)
if err != nil {
if !errors.Is(err, ErrDefaultStackNotSet) {
return nil, "", err
}
whence := "fallback stack"

// fallback to fallback
stack = &Parameters{
Name: DefaultBeta,
Provider: client.ProviderDefang,
}
return stack, whence, nil
}

return stack, whence, nil
}

type ErrNotExist struct {
Expand Down Expand Up @@ -280,7 +311,6 @@ func (sm *manager) getDefaultStack(ctx context.Context) (*Parameters, string, er
if connect.CodeOf(err) != connect.CodeNotFound {
return nil, "", err
}
term.Debugf("No default stack set for project %q; using fallback", sm.projectName)
return nil, "", ErrDefaultStackNotSet
}

Expand Down
Loading
Loading