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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,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 @@ -142,13 +143,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_TENANT` - The name of the tenant to use.
- `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 pkgs/defang/cli.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ buildGoModule {
pname = "defang-cli";
version = "git";
src = ../../src;
vendorHash = "sha256-8caEfevVUKXsYA5YyVDuPZTqMnYXIZnC4kTTGfPmuqA="; # TODO: use fetchFromGitHub
vendorHash = "sha256-E+80Fv4XGAzq2PqvObqIWlATGsDMfx2we62fA7kLHSo="; # TODO: use fetchFromGitHub

subPackages = [ "cmd/cli" ];

Expand Down
5 changes: 2 additions & 3 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 All @@ -43,7 +44,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_TENANT` - The name of the tenant to use.
- `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 All @@ -52,4 +52,3 @@ The Defang CLI recognizes the following environment variables:

Environment variables will be loaded from a `.defangrc` file in the current directory, if it exists. This file follows
the same format as a `.env` file: `KEY=VALUE` pairs on each line, lines starting with `#` are treated as comments and ignored.

3 changes: 1 addition & 2 deletions 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 All @@ -43,7 +44,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_TENANT` - The name of the tenant to use.
- `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 All @@ -52,4 +52,3 @@ The Defang CLI recognizes the following environment variables:

Environment variables will be loaded from a `.defangrc` file in the current directory, if it exists. This file follows
the same format as a `.env` file: `KEY=VALUE` pairs on each line, lines starting with `#` are treated as comments and ignored.

99 changes: 38 additions & 61 deletions src/cmd/cli/command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"os/exec"
"path/filepath"
"regexp"
"sort"
"slices"
"strings"
"time"

Expand Down Expand Up @@ -52,19 +52,18 @@ var (
cluster string
colorMode = ColorAuto
sourcePlatform = migrate.SourcePlatformUnspecified // default to auto-detecting the source platform
doDebug = false
doDebug = pkg.GetenvBool("DEFANG_DEBUG")
hasTty = term.IsTerminal() && !pkg.GetenvBool("CI")
hideUpdate = pkg.GetenvBool("DEFANG_HIDE_UPDATE")
mode = Mode(defangv1.DeploymentMode_MODE_UNSPECIFIED)
modelId = os.Getenv("DEFANG_MODEL_ID") // for Pro users only
nonInteractive = !hasTty
org string
tenantFlag string
org = os.Getenv("DEFANG_ORG")
providerID = cliClient.ProviderID(pkg.Getenv("DEFANG_PROVIDER", "auto"))
verbose = false
verbose = pkg.GetenvBool("DEFANG_VERBOSE")
)

func getCluster() string {
func getOrgCluster() string {
if org == "" {
return cluster
}
Expand Down Expand Up @@ -165,12 +164,11 @@ func SetupCommands(ctx context.Context, version string) {
RootCmd.PersistentFlags().Var(&colorMode, "color", fmt.Sprintf(`colorize output; one of %v`, allColorModes))
RootCmd.PersistentFlags().StringVarP(&cluster, "cluster", "s", pcluster.DefangFabric, "Defang cluster to connect to")
RootCmd.PersistentFlags().MarkHidden("cluster")
RootCmd.PersistentFlags().StringVar(&org, "org", os.Getenv("DEFANG_ORG"), "override GitHub organization name (tenant)")
RootCmd.PersistentFlags().StringVar(&tenantFlag, "tenant", "", "select tenant by name")
RootCmd.PersistentFlags().StringVar(&org, "org", "", "override organization name (tenant)")
RootCmd.PersistentFlags().VarP(&providerID, "provider", "P", fmt.Sprintf(`bring-your-own-cloud provider; one of %v`, cliClient.AllProviders()))
// RootCmd.Flag("provider").NoOptDefVal = "auto" NO this will break the "--provider aws"
RootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose logging") // backwards compat: only used by tail
RootCmd.PersistentFlags().BoolVar(&doDebug, "debug", pkg.GetenvBool("DEFANG_DEBUG"), "debug logging for troubleshooting the CLI")
RootCmd.PersistentFlags().BoolVar(&doDebug, "debug", false, "debug logging for troubleshooting the CLI")
RootCmd.PersistentFlags().BoolVar(&dryrun.DoDryRun, "dry-run", false, "dry run (don't actually change anything)")
RootCmd.PersistentFlags().BoolVarP(&nonInteractive, "non-interactive", "T", !hasTty, "disable interactive prompts / no TTY")
RootCmd.PersistentFlags().StringP("project-name", "p", "", "project name")
Expand Down Expand Up @@ -372,19 +370,10 @@ var RootCmd = &cobra.Command{
}
}

// Configure tenant selection based on --tenant flag
if f := cmd.Root().Flag("tenant"); f != nil && f.Changed {
// Highest precedence: explicit --tenant flag
auth.SetSelectedTenantName(tenantFlag)
} else if envTenant := os.Getenv("DEFANG_TENANT"); strings.TrimSpace(envTenant) != "" {
// Next precedence: DEFANG_TENANT environment variable
auth.SetSelectedTenantName(envTenant)
} else {
// Default behavior: auto-select tenant by JWT subject if no explicit name is provided
auth.SetAutoSelectBySub(true)
}
// Configure tenant selection based on --org flag
auth.SetSelectedTenantName(org)

client, err = cli.Connect(ctx, getCluster())
client, err = cli.Connect(ctx, getOrgCluster())

if v, err := client.GetVersions(ctx); err == nil {
version := cmd.Root().Version // HACK to avoid circular dependency with RootCmd
Expand All @@ -405,20 +394,20 @@ var RootCmd = &cobra.Command{
if nonInteractive {
err = client.CheckLoginAndToS(ctx)
} else {
err = login.InteractiveRequireLoginAndToS(ctx, client, getCluster())
err = login.InteractiveRequireLoginAndToS(ctx, client, getOrgCluster())
}

if err != nil {
return err
}

// Ensure tenant is resolved post-login as we now have a token
if tok := pcluster.GetExistingToken(getCluster()); tok != "" {
if tok := pcluster.GetExistingToken(getOrgCluster()); tok != "" {
if err2 := auth.ResolveAndSetTenantFromToken(ctx, tok); err2 != nil {
return err2
}
// log the tenant name and id
term.Debug("Selected tenant:", auth.GetSelectedTenantName(), "(", auth.GetSelectedTenantID(), ")")
term.Debugf("Selected tenant: %q (%s)", auth.GetSelectedTenantName(), auth.GetSelectedTenantID())
}

return nil
Expand All @@ -427,61 +416,49 @@ var RootCmd = &cobra.Command{

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())
if strings.TrimSpace(tok) == "" {
return errors.New("not logged in; run 'defang login'")
}

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

// Sort by name for stable output
sort.Slice(tenants, func(i, j int) bool { return strings.ToLower(tenants[i].Name) < strings.ToLower(tenants[j].Name) })

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

currentID := auth.GetSelectedTenantID()
currentName := auth.GetSelectedTenantName()
// Sort by name for stable output
slices.SortStableFunc(tenants, func(a, b auth.Tenant) int {
return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name))
})

// Compute longest name for aligned output
maxNameLen := 0
for _, t := range tenants {
if l := len(t.Name); l > maxNameLen {
maxNameLen = l
}
}
printTenants := make([]struct {
Active string
auth.Tenant
}, len(tenants))

for _, t := range 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) != "")
marker := "-"
if selected {
marker = "*" // highlight selected
}

var line string
if verbose {
line = fmt.Sprintf("%s %-*s (%s)\n", marker, maxNameLen, t.Name, t.ID)
} else {
line = fmt.Sprintf("%s %s\n", marker, t.Name)
printTenants[i].Active = "*" // highlight selected
}
}

if selected {
term.Printc(term.BrightCyan, line)
} else {
term.Printc(term.InfoColor, line)
}
attrs := []string{"Active", "Name"}
if verbose {
attrs = append(attrs, "ID")
}
return nil
return term.Table(printTenants, attrs)
},
}

Expand All @@ -493,11 +470,11 @@ var loginCmd = &cobra.Command{
trainingOptOut, _ := cmd.Flags().GetBool("training-opt-out")

if nonInteractive {
if err := login.NonInteractiveGitHubLogin(cmd.Context(), client, getCluster()); err != nil {
if err := login.NonInteractiveGitHubLogin(cmd.Context(), client, getOrgCluster()); err != nil {
return err
}
} else {
err := login.InteractiveLogin(cmd.Context(), client, getCluster())
err := login.InteractiveLogin(cmd.Context(), client, getOrgCluster())
if err != nil {
return err
}
Expand Down Expand Up @@ -614,7 +591,7 @@ var generateCmd = &cobra.Command{
Heroku: migrate.NewHerokuClient(),
ModelID: modelId,
Fabric: client,
Cluster: getCluster(),
Cluster: getOrgCluster(),
}

sample := ""
Expand Down Expand Up @@ -642,7 +619,7 @@ var initCmd = &cobra.Command{
Heroku: migrate.NewHerokuClient(),
ModelID: modelId,
Fabric: client,
Cluster: getCluster(),
Cluster: getOrgCluster(),
}

if len(args) > 0 {
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/cli/command/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ set_config - This tool sets or updates configuration variables for a deployed ap
`),
)

cluster := getCluster()
cluster := getOrgCluster()

// Setup resources
term.Debug("Setting up resources")
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 @@ -50,8 +50,8 @@ require (
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
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.18.0
Expand Down
8 changes: 4 additions & 4 deletions src/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,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 @@ -301,8 +301,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