Skip to content
Draft
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Install the Defang CLI from one of the following sources:
## Support

- File any issues [here](https://github.com/DefangLabs/defang/issues)
- Join our [Discord community](https://s.defang.io/discord) for real-time help and discussions

## Command completion

Expand Down Expand Up @@ -155,13 +156,12 @@ The Defang CLI recognizes the following environment variables:
- `DEFANG_ISSUER` - The OAuth2 issuer to use for authentication; defaults to `https://auth.defang.io`
- `DEFANG_MODEL_ID` - The model ID of the LLM to use for the generate/debug AI integration (Pro users only)
- `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_ORG` - The name of the organization to use; defaults to the user's personal org
Copy link
Contributor

Choose a reason for hiding this comment

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

the "Github name" -> "personal org" change doesn't seem to be propagated to the other READMEs

- `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_PULUMI_BACKEND` - The Pulumi backend URL or `"pulumi-cloud"`; defaults to a self-hosted backend
- `DEFANG_PULUMI_DIR` - Run Pulumi from this folder, instead of spawning a cloud task; requires `--debug` (BYOC only)
- `DEFANG_PULUMI_VERSION` - Override the version of the Pulumi image to use (`aws` provider only)
- `DEFANG_SUFFIX` - The suffix to use for all BYOC resources; defaults to the stack name, or `beta` if unset.
- `NO_COLOR` - If set to any value, disables color output; by default, color output is enabled depending on the terminal
- `PULUMI_ACCESS_TOKEN` - The Pulumi access token to use for authentication to Pulumi Cloud; see `DEFANG_PULUMI_BACKEND`
- `PULUMI_CONFIG_PASSPHRASE` - Passphrase used to generate a unique key for your stack, and configuration and encrypted state values
Expand Down
4 changes: 2 additions & 2 deletions pkgs/npm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ The Defang Command-Line Interface [(CLI)](https://docs.defang.io/docs/getting-st

## Support

- File any issues [here](https://github.com/DefangLabs/defang/issues)
- File any issues [right here on GitHub](https://github.com/DefangLabs/defang/issues)
- Join our [Discord community](https://s.defang.io/discord) for real-time help and discussions

## Environment Variables

Expand Down Expand Up @@ -46,7 +47,6 @@ The Defang CLI recognizes the following environment variables:
- `DEFANG_PULUMI_BACKEND` - The Pulumi backend URL or `"pulumi-cloud"`; defaults to a self-hosted backend
- `DEFANG_PULUMI_DIR` - Run Pulumi from this folder, instead of spawning a cloud task; requires `--debug` (BYOC only)
- `DEFANG_PULUMI_VERSION` - Override the version of the Pulumi image to use (`aws` provider only)
- `DEFANG_SUFFIX` - The suffix to use for all BYOC resources; defaults to the stack name, or `beta` if unset.
- `NO_COLOR` - If set to any value, disables color output; by default, color output is enabled depending on the terminal
- `PULUMI_ACCESS_TOKEN` - The Pulumi access token to use for authentication to Pulumi Cloud; see `DEFANG_PULUMI_BACKEND`
- `PULUMI_CONFIG_PASSPHRASE` - Passphrase used to generate a unique key for your stack, and configuration and encrypted state values
Expand Down
2 changes: 1 addition & 1 deletion src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The Defang Command-Line Interface [(CLI)](https://docs.defang.io/docs/getting-st
## Support

- File any issues [here](https://github.com/DefangLabs/defang/issues)
- Join our [Discord community](https://s.defang.io/discord) for real-time help and discussions

## Environment Variables

Expand Down Expand Up @@ -46,7 +47,6 @@ The Defang CLI recognizes the following environment variables:
- `DEFANG_PULUMI_BACKEND` - The Pulumi backend URL or `"pulumi-cloud"`; defaults to a self-hosted backend
- `DEFANG_PULUMI_DIR` - Run Pulumi from this folder, instead of spawning a cloud task; requires `--debug` (BYOC only)
- `DEFANG_PULUMI_VERSION` - Override the version of the Pulumi image to use (`aws` provider only)
- `DEFANG_SUFFIX` - The suffix to use for all BYOC resources; defaults to the stack name, or `beta` if unset.
- `NO_COLOR` - If set to any value, disables color output; by default, color output is enabled depending on the terminal
- `PULUMI_ACCESS_TOKEN` - The Pulumi access token to use for authentication to Pulumi Cloud; see `DEFANG_PULUMI_BACKEND`
- `PULUMI_CONFIG_PASSPHRASE` - Passphrase used to generate a unique key for your stack, and configuration and encrypted state values
Expand Down
81 changes: 72 additions & 9 deletions src/cmd/cli/command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@ import (
"os/exec"
"path/filepath"
"regexp"
"slices"
"strings"
"time"

"github.com/AlecAivazis/survey/v2"
"github.com/DefangLabs/defang/src/pkg"
"github.com/DefangLabs/defang/src/pkg/auth"
"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"
"github.com/DefangLabs/defang/src/pkg/cli/client/byoc/gcp"
"github.com/DefangLabs/defang/src/pkg/cli/compose"
"github.com/DefangLabs/defang/src/pkg/clouds/aws"
pcluster "github.com/DefangLabs/defang/src/pkg/cluster"
"github.com/DefangLabs/defang/src/pkg/dryrun"
"github.com/DefangLabs/defang/src/pkg/login"
"github.com/DefangLabs/defang/src/pkg/logs"
Expand Down Expand Up @@ -206,6 +209,9 @@ func SetupCommands(ctx context.Context, version string) {
// loginCmd.Flags().Bool("skip-prompt", false, "skip the login prompt if already logged in"); TODO: Implement this
RootCmd.AddCommand(loginCmd)

// Tenants Command
RootCmd.AddCommand(tenantsCmd)

// Whoami Command
whoamiCmd.PersistentFlags().Bool("json", pkg.GetenvBool("DEFANG_JSON"), "print output in JSON format")
RootCmd.AddCommand(whoamiCmd)
Expand Down Expand Up @@ -341,12 +347,10 @@ var RootCmd = &cobra.Command{
ctx := cmd.Context()
term.SetDebug(global.Debug)

// Don't track/connect the completion commands
if IsCompletionCommand(cmd) {
return nil
}

// Use "defer" to track any errors that occur during the command
defer func() {
var errString = ""
if err != nil {
Expand All @@ -356,7 +360,6 @@ var RootCmd = &cobra.Command{
track.Cmd(cmd, "Invoked", P("args", args), P("err", errString), P("non-interactive", global.NonInteractive), P("provider", global.ProviderID))
}()

// Do this first, since any errors will be printed to the console
switch global.ColorMode {
case ColorNever:
term.ForceColor(false)
Expand All @@ -365,13 +368,11 @@ var RootCmd = &cobra.Command{
}

if cwd, _ := cmd.Flags().GetString("cwd"); cwd != "" {
// Change directory before running the command
if err = os.Chdir(cwd); err != nil {
return err
}
}

// Read the global flags again from any .defang files in the cwd
err = global.loadDotDefang(global.getStackName(cmd.Flags()))
if err != nil {
return err
Expand All @@ -382,18 +383,22 @@ var RootCmd = &cobra.Command{
return err
}

auth.SetSelectedTenantName(global.Org)

global.Client, err = cli.Connect(ctx, getCluster())
if err != nil {
return err
}

if v, err := global.Client.GetVersions(ctx); err == nil {
version := cmd.Root().Version // HACK to avoid circular dependency with RootCmd
version := cmd.Root().Version
term.Debug("Fabric:", v.Fabric, "CLI:", version, "CLI-Min:", v.CliMin)
if global.HasTty && isNewer(version, v.CliMin) && !isUpgradeCommand(cmd) {
term.Warn("Your CLI version is outdated. Please upgrade to the latest version by running:\n\n defang upgrade\n")
global.HideUpdate = true // hide the upgrade hint at the end
global.HideUpdate = true
}
}

// Check if we are correctly logged in, but only if the command needs authorization
if _, ok := cmd.Annotations[authNeeded]; !ok {
return nil
}
Expand All @@ -404,7 +409,65 @@ var RootCmd = &cobra.Command{
err = login.InteractiveRequireLoginAndToS(ctx, global.Client, getCluster())
}

return err
if err != nil {
return err
}

if tok := pcluster.GetExistingToken(getCluster()); tok != "" {
if err2 := auth.ResolveAndSetTenantFromToken(ctx, tok); err2 != nil {
return err2
}
term.Debugf("Selected tenant: %q (%s)", auth.GetSelectedTenantName(), auth.GetSelectedTenantID())
}

return nil
},
}

var tenantsCmd = &cobra.Command{
Use: "tenants",
Aliases: []string{"tenant", "orgs", "org"},
Args: cobra.NoArgs,
Annotations: authNeededAnnotation,
Short: "List tenants available to the logged-in user",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()

tok := pcluster.GetExistingToken(getCluster())
tenants, err := auth.ListTenantsFromToken(ctx, tok)
if err != nil {
return err
}

if len(tenants) == 0 {
term.Warn("No tenants found")
return nil
}

slices.SortStableFunc(tenants, func(a, b auth.Tenant) int {
return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name))
})

printTenants := make([]struct {
Active string
auth.Tenant
}, len(tenants))

currentID := auth.GetSelectedTenantID()
currentName := auth.GetSelectedTenantName()
for i, t := range tenants {
printTenants[i].Tenant = t
selected := t.ID == currentID || (currentID == "" && t.Name == currentName && strings.TrimSpace(currentName) != "")
if selected {
printTenants[i].Active = "*" // highlight selected
}
}

attrs := []string{"Active", "Name"}
if global.Verbose {
attrs = append(attrs, "ID")
}
return term.Table(printTenants, attrs)
},
}

Expand Down
6 changes: 3 additions & 3 deletions src/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.24

toolchain go1.24.5

replace github.com/spf13/cobra v1.8.0 => github.com/DefangLabs/cobra v1.8.0-defang
replace github.com/spf13/cobra v1.10.1 => github.com/DefangLabs/cobra v1.10.1-defang

require (
cloud.google.com/go/artifactregistry v1.16.1
Expand Down Expand Up @@ -52,8 +52,8 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
github.com/ross96D/cancelreader v0.2.6
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.6
github.com/spf13/cobra v1.10.1
github.com/spf13/pflag v1.0.9
github.com/stretchr/testify v1.10.0
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/mod v0.21.0
Expand Down
8 changes: 4 additions & 4 deletions src/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkk
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/DefangLabs/cobra v1.8.0-defang h1:rTzAg1XbEk3yXUmQPumcwkLgi8iNCby5CjyG3sCwzKk=
github.com/DefangLabs/cobra v1.8.0-defang/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/DefangLabs/cobra v1.10.1-defang h1:Jsj/7J/hcEVnOnRB/qyNQgZY8pjAONfhHntw3w+UwQA=
github.com/DefangLabs/cobra v1.10.1-defang/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/DefangLabs/secret-detector v0.0.0-20250811234530-d4b4214cd679 h1:qNT7R4qrN+5u5ajSbqSW1opHP4LA8lzA+ASyw5MQZjs=
github.com/DefangLabs/secret-detector v0.0.0-20250811234530-d4b4214cd679/go.mod h1:blbwPQh4DTlCZEfk1BLU4oMIhLda2U+A840Uag9DsZw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 h1:f2Qw/Ehhimh5uO1fayV0QIW7DShEQqhtUfhYc+cBPlw=
Expand Down Expand Up @@ -304,8 +304,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
Loading
Loading