Skip to content
Closed
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6b972cf
update faq link
jordanstephens Dec 10, 2025
0239c2f
formatting
jordanstephens Dec 10, 2025
db33f9c
use LoadProjectName w/o fallback
jordanstephens Dec 9, 2025
dd30736
present remote stacks during selection
jordanstephens Dec 9, 2025
9000227
support remote stacks
jordanstephens Dec 10, 2025
ce47b56
avoid storing provider preference in fabric
jordanstephens Dec 10, 2025
150da27
pass projectName to ProviderPreparer
jordanstephens Dec 10, 2025
df0b73c
always use newProviderChecked
jordanstephens Dec 10, 2025
ba980b0
remove support for --provider=auto
jordanstephens Dec 10, 2025
b6472cf
newProviderChecked now considers existing deployments
jordanstephens Dec 10, 2025
e49f41e
use newProvider when non-interactive
jordanstephens Dec 10, 2025
96ae9d6
only use local stack files when project is loaded from working directory
jordanstephens Dec 10, 2025
7ed79e1
automatically determine if we should ask for profile or access key
jordanstephens Dec 10, 2025
772cd5b
explain stacks during selection
jordanstephens Dec 10, 2025
cd2a514
support loading remote stack from flag
jordanstephens Dec 10, 2025
ffd4261
move debug line after error handling
jordanstephens Dec 11, 2025
81a92ea
avoid double-wrapping error
jordanstephens Dec 11, 2025
73ea43a
lowercase error msg
jordanstephens Dec 11, 2025
64334ac
remove misleading comment
jordanstephens Dec 11, 2025
ec2af38
refactor stack list collection
jordanstephens Dec 11, 2025
b8bf6bd
only stack file at startup if --project-name is not set
jordanstephens Dec 11, 2025
763c6ec
factor out a StacksManager
jordanstephens Dec 11, 2025
948dc28
avoid writing stacks when useWkDir false
jordanstephens Dec 11, 2025
6a15a1b
provider tests
jordanstephens Dec 11, 2025
0638a39
remove redundant check
jordanstephens Dec 11, 2025
6b6aaac
improve error handling when reading aws profiles
jordanstephens Dec 11, 2025
9071d6a
avoid nil ptr access when fetching deployment timestamp
jordanstephens Dec 11, 2025
ff6d7e6
just print "defang up" for hint
jordanstephens Dec 11, 2025
1f28f84
define stacks.LoadParameters
jordanstephens Dec 11, 2025
4834d72
avoid reading/writing files when outside project wkdir
jordanstephens Dec 11, 2025
d3c091f
explore storing mcp client capabilities
jordanstephens Dec 11, 2025
c3b7ea6
Revert "explore storing mcp client capabilities"
jordanstephens Dec 11, 2025
a616520
mock ListDeployments to include test-stack
jordanstephens Dec 11, 2025
850f0de
remove redundant call to sm.Create
jordanstephens Dec 11, 2025
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ The Defang CLI recognizes the following environment variables:
- `DEFANG_NO_CACHE` - If set to `true`, disables pull-through caching of container images; defaults to `false`
- `DEFANG_ORG` - The name of the organization to use; defaults to the user's GitHub name
- `DEFANG_PREFIX` - The prefix to use for all BYOC resources; defaults to `Defang`
- `DEFANG_PROVIDER` - The name of the cloud provider to use, `auto` (default), `aws`, `digitalocean`, `gcp`, or `defang`
- `DEFANG_PROVIDER` - The name of the cloud provider to use, `aws`, `digitalocean`, `gcp`, or `defang`
- `DEFANG_PULUMI_BACKEND` - The Pulumi backend URL or `"pulumi-cloud"`; defaults to a self-hosted backend
- `DEFANG_PULUMI_DEBUG` - If set to `true`, enables debug logging for Pulumi operations; defaults to `false`
- `DEFANG_PULUMI_DIFF` - If set to `true`, shows the Pulumi diff during deployments; defaults to `false`
Expand Down
2 changes: 1 addition & 1 deletion pkgs/npm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The Defang CLI recognizes the following environment variables:
- `DEFANG_NO_CACHE` - If set to `true`, disables pull-through caching of container images; defaults to `false`
- `DEFANG_ORG` - The name of the organization to use; defaults to the user's GitHub name
- `DEFANG_PREFIX` - The prefix to use for all BYOC resources; defaults to `Defang`
- `DEFANG_PROVIDER` - The name of the cloud provider to use, `auto` (default), `aws`, `digitalocean`, `gcp`, or `defang`
- `DEFANG_PROVIDER` - The name of the cloud provider to use, `aws`, `digitalocean`, `gcp`, or `defang`
- `DEFANG_PULUMI_BACKEND` - The Pulumi backend URL or `"pulumi-cloud"`; defaults to a self-hosted backend
- `DEFANG_PULUMI_DEBUG` - If set to `true`, enables debug logging for Pulumi operations; defaults to `false`
- `DEFANG_PULUMI_DIFF` - If set to `true`, shows the Pulumi diff during deployments; defaults to `false`
Expand Down
2 changes: 1 addition & 1 deletion src/.goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ release:
2. Extract the archive. This should reveal the binary file for Defang.
3. Manually place the binary file in a directory that's included in your system's `PATH` environment variable.
### Additional Step for MacOS Users
If you're having trouble running the binary on MacOS, please check our [FAQs](https://docs.defang.io/docs/faq#im-having-trouble-running-the-binary-on-my-mac-what-should-i-do).
If you're having trouble running the binary on MacOS, please check our [FAQs](https://docs.defang.io/docs/intro/faq/questions#im-having-trouble-running-the-binary-on-my-mac-what-should-i-do).

Please remember this software is in beta, so please report any issues or feedback through our GitHub page. Your help in improving Defang is greatly appreciated!
# mode: keep-existing
Expand Down
2 changes: 1 addition & 1 deletion src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The Defang CLI recognizes the following environment variables:
- `DEFANG_NO_CACHE` - If set to `true`, disables pull-through caching of container images; defaults to `false`
- `DEFANG_ORG` - The name of the organization to use; defaults to the user's GitHub name
- `DEFANG_PREFIX` - The prefix to use for all BYOC resources; defaults to `Defang`
- `DEFANG_PROVIDER` - The name of the cloud provider to use, `auto` (default), `aws`, `digitalocean`, `gcp`, or `defang`
- `DEFANG_PROVIDER` - The name of the cloud provider to use, `aws`, `digitalocean`, `gcp`, or `defang`
- `DEFANG_PULUMI_BACKEND` - The Pulumi backend URL or `"pulumi-cloud"`; defaults to a self-hosted backend
- `DEFANG_PULUMI_DEBUG` - If set to `true`, enables debug logging for Pulumi operations; defaults to `false`
- `DEFANG_PULUMI_DIFF` - If set to `true`, shows the Pulumi diff during deployments; defaults to `false`
Expand Down
23 changes: 11 additions & 12 deletions src/cmd/cli/command/cd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"os"

"github.com/DefangLabs/defang/src/pkg/cli"
cliClient "github.com/DefangLabs/defang/src/pkg/cli/client"
"github.com/DefangLabs/defang/src/pkg/cli/client/byoc/aws"
"github.com/DefangLabs/defang/src/pkg/cli/compose"
"github.com/DefangLabs/defang/src/pkg/term"
Expand Down Expand Up @@ -36,19 +35,19 @@ var cdCmd = &cobra.Command{
func bootstrapCommand(cmd *cobra.Command, args []string, command string) error {
ctx := cmd.Context()
loader := configureLoader(cmd)
provider, err := newProviderChecked(ctx, loader)
if err != nil {
return err
}

if len(args) == 0 {
projectName, err := cliClient.LoadProjectNameWithFallback(ctx, loader, provider)
projectName, err := loader.LoadProjectName(ctx)
if err != nil {
return err
}
args = []string{projectName}
}

provider, err := newProviderChecked(ctx, args[0], false)
if err != nil {
return err
}
var errs []error
for _, projectName := range args {
err := canIUseProvider(ctx, provider, projectName, 0)
Expand Down Expand Up @@ -103,8 +102,7 @@ var cdTearDownCmd = &cobra.Command{
RunE: func(cmd *cobra.Command, args []string) error {
force, _ := cmd.Flags().GetBool("force")

loader := configureLoader(cmd)
provider, err := newProviderChecked(cmd.Context(), loader)
provider, err := newProviderChecked(cmd.Context(), "", false)
if err != nil {
return err
}
Expand All @@ -122,7 +120,7 @@ var cdListCmd = &cobra.Command{
remote, _ := cmd.Flags().GetBool("remote")
all, _ := cmd.Flags().GetBool("all")

provider, err := newProviderChecked(cmd.Context(), nil)
provider, err := newProviderChecked(cmd.Context(), "", false)
if err != nil {
return err
}
Expand Down Expand Up @@ -156,7 +154,9 @@ var cdPreviewCmd = &cobra.Command{
return err
}

provider, err := newProviderChecked(cmd.Context(), loader)
projectNameFlag, _ := cmd.Flags().GetString("project-name")
saveStacksToWkDir := projectNameFlag == ""
provider, err := newProviderChecked(cmd.Context(), project.Name, saveStacksToWkDir)
if err != nil {
return err
}
Expand All @@ -183,8 +183,7 @@ var cdInstallCmd = &cobra.Command{
Short: "Install the CD resources into the cluster",
Hidden: true, // users shouldn't have to run this manually, because it's done on deploy
RunE: func(cmd *cobra.Command, args []string) error {
loader := configureLoader(cmd)
provider, err := newProviderChecked(cmd.Context(), loader)
provider, err := newProviderChecked(cmd.Context(), "", false)
if err != nil {
return err
}
Expand Down
147 changes: 67 additions & 80 deletions src/cmd/cli/command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/AlecAivazis/survey/v2"
"github.com/DefangLabs/defang/src/pkg"
"github.com/DefangLabs/defang/src/pkg/agent"
agentTools "github.com/DefangLabs/defang/src/pkg/agent/tools"
"github.com/DefangLabs/defang/src/pkg/cli"
cliClient "github.com/DefangLabs/defang/src/pkg/cli/client"
"github.com/DefangLabs/defang/src/pkg/cli/client/byoc"
Expand All @@ -24,6 +25,7 @@ import (
"github.com/DefangLabs/defang/src/pkg/clouds/aws"
"github.com/DefangLabs/defang/src/pkg/debug"
"github.com/DefangLabs/defang/src/pkg/dryrun"
"github.com/DefangLabs/defang/src/pkg/elicitations"
"github.com/DefangLabs/defang/src/pkg/login"
"github.com/DefangLabs/defang/src/pkg/logs"
"github.com/DefangLabs/defang/src/pkg/mcp"
Expand Down Expand Up @@ -86,7 +88,7 @@ func Execute(ctx context.Context) error {

if strings.Contains(err.Error(), "maximum number of projects") {
projectName := "<name>"
provider, err := newProviderChecked(ctx, nil)
provider, err := newProviderChecked(ctx, projectName, false)
if err != nil {
return err
}
Expand Down Expand Up @@ -433,10 +435,17 @@ var RootCmd = &cobra.Command{
}
}

// Read the global flags again from any .defang files in the cwd
err = global.loadDotDefang(global.getStackName(cmd.Flags()))
if err != nil {
return err
if !cmd.Flags().Changed("project-name") {
// TODO: consider connecting to fabric before loading the stack
// so we can support loading stacks from remote
// Read the global flags from the selected stack file
err = global.loadStackFile(global.getStackName(cmd.Flags()))
if err != nil {
// if the stack file does not exist, continue without error, we will load it from the remote later.
if !errors.Is(err, os.ErrNotExist) {
return err
}
}
}

err = global.syncFlagsWithEnv(cmd.Flags())
Expand Down Expand Up @@ -524,9 +533,8 @@ var whoamiCmd = &cobra.Command{
Args: cobra.NoArgs,
Short: "Show the current user",
RunE: func(cmd *cobra.Command, args []string) error {
loader := configureLoader(cmd)
global.NonInteractive = true // don't show provider prompt
provider, err := newProvider(cmd.Context(), loader)
provider, err := newProviderChecked(cmd.Context(), "", false)
if err != nil {
term.Debug("unable to get provider:", err)
}
Expand Down Expand Up @@ -576,7 +584,9 @@ var certGenerateCmd = &cobra.Command{
return err
}

provider, err := newProviderChecked(cmd.Context(), loader)
projectNameFlag, _ := cmd.Flags().GetString("project-name")
saveStacksToWkDir := projectNameFlag == ""
provider, err := newProviderChecked(cmd.Context(), project.Name, saveStacksToWkDir)
if err != nil {
return err
}
Expand Down Expand Up @@ -748,12 +758,13 @@ var configSetCmd = &cobra.Command{

// Make sure we have a project to set config for before asking for a value
loader := configureLoader(cmd)
provider, err := newProviderChecked(cmd.Context(), loader)
projectName, err := loader.LoadProjectName(cmd.Context())
if err != nil {
return err
}

projectName, err := cliClient.LoadProjectNameWithFallback(cmd.Context(), loader, provider)
projectNameFlag, _ := cmd.Flags().GetString("project-name")
saveStacksToWkDir := projectNameFlag == ""
provider, err := newProviderChecked(cmd.Context(), projectName, saveStacksToWkDir)
if err != nil {
return err
}
Expand Down Expand Up @@ -879,12 +890,13 @@ var configDeleteCmd = &cobra.Command{
Short: "Removes one or more config values",
RunE: func(cmd *cobra.Command, names []string) error {
loader := configureLoader(cmd)
provider, err := newProviderChecked(cmd.Context(), loader)
projectName, err := loader.LoadProjectName(cmd.Context())
if err != nil {
return err
}

projectName, err := cliClient.LoadProjectNameWithFallback(cmd.Context(), loader, provider)
projectNameFlag, _ := cmd.Flags().GetString("project-name")
saveStacksToWkDir := projectNameFlag == ""
provider, err := newProviderChecked(cmd.Context(), projectName, saveStacksToWkDir)
if err != nil {
return err
}
Expand Down Expand Up @@ -912,12 +924,14 @@ var configListCmd = &cobra.Command{
Short: "List configs",
RunE: func(cmd *cobra.Command, args []string) error {
loader := configureLoader(cmd)
provider, err := newProviderChecked(cmd.Context(), loader)

projectName, err := loader.LoadProjectName(cmd.Context())
if err != nil {
return err
}

projectName, err := cliClient.LoadProjectNameWithFallback(cmd.Context(), loader, provider)
projectNameFlag, _ := cmd.Flags().GetString("project-name")
saveStacksToWkDir := projectNameFlag == ""
provider, err := newProviderChecked(cmd.Context(), projectName, saveStacksToWkDir)
if err != nil {
return err
}
Expand All @@ -943,12 +957,13 @@ var debugCmd = &cobra.Command{
}

loader := configureLoader(cmd)
_, err := newProviderChecked(ctx, loader)
project, err := loader.LoadProject(ctx)
if err != nil {
return err
}

project, err := loader.LoadProject(ctx)
projectNameFlag, _ := cmd.Flags().GetString("project-name")
saveStacksToWkDir := projectNameFlag == ""
_, err = newProviderChecked(ctx, project.Name, saveStacksToWkDir)
if err != nil {
return err
}
Expand Down Expand Up @@ -992,12 +1007,13 @@ var deleteCmd = &cobra.Command{
var tail, _ = cmd.Flags().GetBool("tail")

loader := configureLoader(cmd)
provider, err := newProviderChecked(cmd.Context(), loader)
projectName, err := loader.LoadProjectName(cmd.Context())
if err != nil {
return err
}

projectName, err := cliClient.LoadProjectNameWithFallback(cmd.Context(), loader, provider)
projectNameFlag, _ := cmd.Flags().GetString("project-name")
saveStacksToWkDir := projectNameFlag == ""
provider, err := newProviderChecked(cmd.Context(), projectName, saveStacksToWkDir)
if err != nil {
return err
}
Expand Down Expand Up @@ -1238,7 +1254,7 @@ var providerDescription = map[cliClient.ProviderID]string{
cliClient.ProviderGCP: "Deploy to Google Cloud Platform using gcloud Application Default Credentials.",
}

func updateProviderID(ctx context.Context, loader cliClient.Loader) error {
func updateProviderID(ctx context.Context) error {
extraMsg := ""
whence := "default project"

Expand All @@ -1255,24 +1271,16 @@ func updateProviderID(ctx context.Context, loader cliClient.Loader) error {

switch global.ProviderID {
case cliClient.ProviderAuto:
if global.NonInteractive {
// Defaults to defang provider in non-interactive mode
if awsInEnv() {
term.Warn("Using Defang playground, but AWS environment variables were detected; did you forget --provider=aws or DEFANG_PROVIDER=aws?")
}
if doInEnv() {
term.Warn("Using Defang playground, but DIGITALOCEAN_TOKEN environment variable was detected; did you forget --provider=digitalocean or DEFANG_PROVIDER=digitalocean?")
}
if gcpInEnv() {
term.Warn("Using Defang playground, but GCP_PROJECT_ID/CLOUDSDK_CORE_PROJECT environment variable was detected; did you forget --provider=gcp or DEFANG_PROVIDER=gcp?")
}
global.ProviderID = cliClient.ProviderDefang
} else {
var err error
if whence, err = determineProviderID(ctx, loader); err != nil {
return err
}
if awsInEnv() {
term.Warn("Using Defang playground, but AWS environment variables were detected; did you forget --provider=aws or DEFANG_PROVIDER=aws?")
}
if doInEnv() {
term.Warn("Using Defang playground, but DIGITALOCEAN_TOKEN environment variable was detected; did you forget --provider=digitalocean or DEFANG_PROVIDER=digitalocean?")
}
if gcpInEnv() {
term.Warn("Using Defang playground, but GCP_PROJECT_ID/CLOUDSDK_CORE_PROJECT environment variable was detected; did you forget --provider=gcp or DEFANG_PROVIDER=gcp?")
}
global.ProviderID = cliClient.ProviderDefang
case cliClient.ProviderAWS:
if !awsInConfig(ctx) {
term.Warn("AWS provider was selected, but AWS environment is not set")
Expand All @@ -1294,19 +1302,32 @@ func updateProviderID(ctx context.Context, loader cliClient.Loader) error {
return nil
}

func newProvider(ctx context.Context, loader cliClient.Loader) (cliClient.Provider, error) {
if err := updateProviderID(ctx, loader); err != nil {
func newProvider(ctx context.Context) (cliClient.Provider, error) {
if err := updateProviderID(ctx); err != nil {
return nil, err
}

provider := cli.NewProvider(ctx, global.ProviderID, global.Client, global.Stack)
return provider, nil
}

func newProviderChecked(ctx context.Context, loader cliClient.Loader) (cliClient.Provider, error) {
provider, err := newProvider(ctx, loader)
type providerCreator struct{}

func (pc *providerCreator) NewProvider(ctx context.Context, providerId cliClient.ProviderID, client cliClient.FabricClient, stack string) cliClient.Provider {
return cli.NewProvider(ctx, providerId, client, stack)
}

func newProviderChecked(ctx context.Context, projectName string, useWkDir bool) (cliClient.Provider, error) {
if global.NonInteractive {
return newProvider(ctx)
}
pc := &providerCreator{}
elicitationsClient := elicitations.NewSurveyClient(os.Stdin, os.Stdout, os.Stderr)
ec := elicitations.NewController(elicitationsClient)
pp := agentTools.NewProviderPreparer(pc, ec, global.Client)
_, provider, err := pp.SetupProvider(ctx, projectName, &global.Stack, useWkDir)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to setup provider: %w", err)
}
_, err = provider.AccountInfo(ctx)
return provider, err
Expand All @@ -1316,40 +1337,6 @@ func canIUseProvider(ctx context.Context, provider cliClient.Provider, projectNa
return cliClient.CanIUseProvider(ctx, global.Client, provider, projectName, global.Stack, serviceCount)
}

func determineProviderID(ctx context.Context, loader cliClient.Loader) (string, error) {
var projectName string
if loader != nil {
var err error
projectName, err = loader.LoadProjectName(ctx)
if err != nil {
term.Warnf("Unable to load project: %v", err)
}

if projectName != "" && !RootCmd.PersistentFlags().Changed("provider") { // If user manually selected auto provider, do not load from remote
resp, err := global.Client.GetSelectedProvider(ctx, &defangv1.GetSelectedProviderRequest{Project: projectName})
if err != nil {
term.Debugf("Unable to get selected provider: %v", err)
} else if resp.Provider != defangv1.Provider_PROVIDER_UNSPECIFIED {
global.ProviderID.SetValue(resp.Provider)
return "stored preference", nil
}
}
}

whence, err := interactiveSelectProvider(cliClient.AllProviders())

// Save the selected provider to the fabric
if projectName != "" {
if err := global.Client.SetSelectedProvider(ctx, &defangv1.SetSelectedProviderRequest{Project: projectName, Provider: global.ProviderID.Value()}); err != nil {
term.Debugf("Unable to save selected provider to defang server: %v", err)
} else {
term.Printf("%v is now the default provider for project %v and will auto-select next time if no other provider is specified. Use --provider=auto to reselect.", global.ProviderID, projectName)
}
}

return whence, err
}

func interactiveSelectProvider(providers []cliClient.ProviderID) (string, error) {
if len(providers) < 2 {
panic("interactiveSelectProvider called with less than 2 providers")
Expand Down
Loading
Loading