diff --git a/src/cmd/cli/command/estimate_test.go b/src/cmd/cli/command/estimate_test.go index a0161bb33..5f71a4120 100644 --- a/src/cmd/cli/command/estimate_test.go +++ b/src/cmd/cli/command/estimate_test.go @@ -129,6 +129,7 @@ func TestPrintEstimate(t *testing.T) { expectedOutput := ` Estimate for Deployment Mode: AFFORDABLE +Available on all tiers. This mode is optimized for low cost and rapid iteration. Your application will be deployed with spot instances. Databases will be provisioned using resources optimized for burstable memory. Deployments are replaced entirely on diff --git a/src/cmd/cli/command/whoami.go b/src/cmd/cli/command/whoami.go index 96353fb31..1e21e5bff 100644 --- a/src/cmd/cli/command/whoami.go +++ b/src/cmd/cli/command/whoami.go @@ -5,7 +5,6 @@ import ( "github.com/DefangLabs/defang/src/pkg/cli" "github.com/DefangLabs/defang/src/pkg/cli/client" "github.com/DefangLabs/defang/src/pkg/term" - defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" "github.com/spf13/cobra" ) @@ -49,9 +48,6 @@ var whoamiCmd = &cobra.Command{ if !global.Verbose { data.Tenant = "" data.TenantID = "" - if data.SubscriberTier == defangv1.SubscriptionTier_SUBSCRIPTION_TIER_UNSPECIFIED { - data.SubscriberTier = defangv1.SubscriptionTier_HOBBY // don't show "SUBSCRIPTION_TIER_UNSPECIFIED" - } } cols := []string{ diff --git a/src/pkg/cli/client/byoc/baseclient.go b/src/pkg/cli/client/byoc/baseclient.go index ca816b12d..7973f0735 100644 --- a/src/pkg/cli/client/byoc/baseclient.go +++ b/src/pkg/cli/client/byoc/baseclient.go @@ -122,7 +122,7 @@ func (e ErrNoPermission) Error() string { func (b *ByocBaseClient) GetServiceInfos(ctx context.Context, projectName, delegateDomain, etag string, services map[string]composeTypes.ServiceConfig) ([]*defangv1.ServiceInfo, error) { numGPUS := compose.GetNumOfGPUs(services) if numGPUS > 0 && !b.AllowGPU { - return nil, ErrNoPermission("usage of GPUs. Please upgrade on https://s.defang.io/subscription") + return nil, ErrNoPermission("GPU access requires a Pro subscription. Upgrade at https://s.defang.io/subscription") } serviceInfoMap := make(map[string]*Node) diff --git a/src/pkg/cli/estimate.go b/src/pkg/cli/estimate.go index 4ea2ffd8f..705fb1e28 100644 --- a/src/pkg/cli/estimate.go +++ b/src/pkg/cli/estimate.go @@ -95,6 +95,7 @@ func GeneratePreview(ctx context.Context, project *compose.Project, client clien } var affordableModeEstimateSummary = ` +Available on all tiers. This mode is optimized for low cost and rapid iteration. Your application will be deployed with spot instances. Databases will be provisioned using resources optimized for burstable memory. Deployments are replaced entirely on @@ -103,12 +104,14 @@ Services will be exposed directly to the public internet for easy debugging. This mode emphasizes affordability over availability.` var balancedModeEstimateSummary = ` +Requires Pro tier. This mode strikes a balance between cost and availability. Your application will be deployed with spot instances. Databases will be provisioned using resources optimized for production. Services in the "internal" network will be deployed to a private subnet with a NAT gateway for outbound internet access.` var highAvailabilityModeEstimateSummary = ` +Requires Enterprise tier. This mode prioritizes availability. Your application will be deployed with on-demand instances in multiple availability zones. Databases will be provisioned using resources optimized for production. diff --git a/src/pkg/cli/whoami.go b/src/pkg/cli/whoami.go index 6e17e0b1c..77b224ee1 100644 --- a/src/pkg/cli/whoami.go +++ b/src/pkg/cli/whoami.go @@ -3,23 +3,22 @@ package cli import ( "context" + "github.com/DefangLabs/defang/src/pkg" "github.com/DefangLabs/defang/src/pkg/auth" "github.com/DefangLabs/defang/src/pkg/cli/client" "github.com/DefangLabs/defang/src/pkg/term" "github.com/DefangLabs/defang/src/pkg/types" - - defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" ) type ShowAccountData struct { - Provider client.ProviderID `json:"provider"` - SubscriberTier defangv1.SubscriptionTier `json:"subscriberTier"` - Region string `json:"region"` - Workspace string `json:"workspace"` - Tenant string `json:"tenant,omitempty"` // this is the subdomain - TenantID string `json:"tenantId,omitempty"` - Email string `json:"email"` - Name string `json:"name"` + Provider client.ProviderID `json:"provider"` + SubscriberTier string `json:"subscriberTier"` + Region string `json:"region"` + Workspace string `json:"workspace"` + Tenant string `json:"tenant,omitempty"` // this is the subdomain + TenantID string `json:"tenantId,omitempty"` + Email string `json:"email"` + Name string `json:"name"` } func Whoami(ctx context.Context, fabric client.FabricClient, maybeProvider client.Provider, userInfo *auth.UserInfo, tenantSelection types.TenantNameOrID) (ShowAccountData, error) { @@ -36,7 +35,7 @@ func Whoami(ctx context.Context, fabric client.FabricClient, maybeProvider clien term.Debug("User ID: " + resp.UserId) showData := ShowAccountData{ Region: resp.Region, - SubscriberTier: resp.Tier, + SubscriberTier: pkg.SubscriptionTierToString(resp.Tier), Tenant: resp.Tenant, TenantID: resp.TenantId, Workspace: ResolveWorkspaceName(userInfo, tenantSelection), diff --git a/src/pkg/cli/whoami_test.go b/src/pkg/cli/whoami_test.go index 4d95c2eb7..28880ebc1 100644 --- a/src/pkg/cli/whoami_test.go +++ b/src/pkg/cli/whoami_test.go @@ -58,7 +58,7 @@ func TestWhoami(t *testing.T) { want := ShowAccountData{ Provider: client.ProviderDefang, - SubscriberTier: defangv1.SubscriptionTier_PRO, + SubscriberTier: "Pro", Region: "us-west-2", Workspace: "Tenant One", Tenant: "tenant-1", diff --git a/src/pkg/utils.go b/src/pkg/utils.go index 8c8926f75..29af7b4fb 100644 --- a/src/pkg/utils.go +++ b/src/pkg/utils.go @@ -134,13 +134,15 @@ func SubscriptionTierToString(tier defangv1.SubscriptionTier) string { case defangv1.SubscriptionTier_SUBSCRIPTION_TIER_UNSPECIFIED: fallthrough // free tier case defangv1.SubscriptionTier_HOBBY: - return "Hobby" + return "Starter" case defangv1.SubscriptionTier_PERSONAL: - return "Personal" + return "Personal (Legacy)" case defangv1.SubscriptionTier_PRO: return "Pro" case defangv1.SubscriptionTier_TEAM: - return "Team" + return "Enterprise" + case defangv1.SubscriptionTier_EXPIRED: + return "Expired" default: return "Unknown" } diff --git a/src/pkg/utils_test.go b/src/pkg/utils_test.go index 494d8bd7b..19ea970b5 100644 --- a/src/pkg/utils_test.go +++ b/src/pkg/utils_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -23,6 +24,30 @@ func TestGetenvBool(t *testing.T) { } } +func TestSubscriptionTierToString(t *testing.T) { + tests := []struct { + name string + tier defangv1.SubscriptionTier + want string + }{ + {"unspecified", defangv1.SubscriptionTier_SUBSCRIPTION_TIER_UNSPECIFIED, "Starter"}, + {"hobby", defangv1.SubscriptionTier_HOBBY, "Starter"}, + {"personal", defangv1.SubscriptionTier_PERSONAL, "Personal (Legacy)"}, + {"pro", defangv1.SubscriptionTier_PRO, "Pro"}, + {"team", defangv1.SubscriptionTier_TEAM, "Enterprise"}, + {"expired", defangv1.SubscriptionTier_EXPIRED, "Expired"}, + {"unknown", defangv1.SubscriptionTier(999), "Unknown"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := SubscriptionTierToString(tt.tier); got != tt.want { + t.Errorf("SubscriptionTierToString(%v) = %q, want %q", tt.tier, got, tt.want) + } + }) + } +} + func TestIsValidServiceName(t *testing.T) { tests := []struct { name string