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
2 changes: 1 addition & 1 deletion internal/command/launch/describe_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func describeRedisPlan(ctx context.Context, p plan.RedisPlan) (string, error) {
}

func describeUpstashRedisPlan(ctx context.Context, p *plan.UpstashRedisPlan) (string, error) {
plan, err := redis.DeterminePlan(ctx)
plan, err := redis.DeterminePlan(ctx, "")
if err != nil {
return "<plan not found, this is an error>", fmt.Errorf("redis plan not found: %w", err)
}
Expand Down
9 changes: 8 additions & 1 deletion internal/command/launch/launch_databases.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,14 @@ func (state *launchState) createUpstashRedis(ctx context.Context) error {
}
}

db, err := redis.Create(ctx, org, dbName, &region, len(readReplicaRegions) == 0, redisPlan.Eviction, &readReplicaRegions)
// Get the default plan (pay-as-you-go)
plan, err := redis.DeterminePlan(ctx, "")
if err != nil {
return err
}

// Launch uses defaults: no auto-upgrade (not available for pay-as-you-go anyway), no prodpack
db, err := redis.Create(ctx, org, dbName, &region, plan, len(readReplicaRegions) == 0, redisPlan.Eviction, false, false, &readReplicaRegions)
if err != nil {
return err
}
Expand Down
128 changes: 112 additions & 16 deletions internal/command/redis/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"slices"
"strings"

"github.com/spf13/cobra"

Expand All @@ -24,6 +25,28 @@ const (
redisPlanPayAsYouGo = "ekQ85Yjkw155ohQ5ALYq0M"
)

// Legacy plans that are no longer available for new databases
// but existing databases can remain on them
// These match the normalized display names (lowercase, spaces replaced with underscores)
var legacyPlans = []string{
"pro_2k", // "Pro 2k"
"pro_10k", // "Pro 10k"
"starter", // "Starter"
"standard", // "Standard"
}

func isLegacyPlan(planName string) bool {
normalized := strings.ToLower(strings.ReplaceAll(planName, " ", "_"))
return slices.Contains(legacyPlans, normalized)
}

// isFixedPlan checks if a plan is one of the new fixed plans (not pay-as-you-go)
// Auto-upgrade is only available for fixed plans
func isFixedPlan(planName string) bool {
return strings.HasPrefix(strings.ToLower(planName), "flyio_fixed_") ||
strings.HasPrefix(strings.ToLower(planName), "fixed ")
}

func newCreate() (cmd *cobra.Command) {
const (
long = `Create an Upstash Redis database`
Expand All @@ -43,6 +66,10 @@ func newCreate() (cmd *cobra.Command) {
Shorthand: "n",
Description: "The name of your Redis database",
},
flag.String{
Name: "plan",
Description: "The plan for your Redis database (default: pay-as-you-go)",
},
flag.Bool{
Name: "no-replicas",
Description: "Don't prompt for selecting replica regions",
Expand All @@ -55,6 +82,14 @@ func newCreate() (cmd *cobra.Command) {
Name: "disable-eviction",
Description: "Disallow writes when the max data size limit has been reached",
},
flag.Bool{
Name: "enable-auto-upgrade",
Description: "Automatically upgrade to a higher plan when hitting resource limits",
},
flag.Bool{
Name: "enable-prodpack",
Description: "Enable ProdPack add-on for additional features ($200/mo)",
},
)

return cmd
Expand All @@ -63,6 +98,12 @@ func newCreate() (cmd *cobra.Command) {
func runCreate(ctx context.Context) (err error) {
io := iostreams.FromContext(ctx)

// Validate --plan flag early before prompting for other options
planName := flag.GetString(ctx, "plan")
if planName != "" && isLegacyPlan(planName) {
return fmt.Errorf("plan %q is no longer available for new databases. Please choose a current plan", planName)
}

// pre-fetch platform regions for later use
prompt.PlatformRegions(ctx)

Expand Down Expand Up @@ -106,11 +147,49 @@ func runCreate(ctx context.Context) (err error) {
return
}
}
_, err = Create(ctx, org, name, primaryRegion, flag.GetBool(ctx, "no-replicas"), enableEviction, nil)

// Determine plan (already validated above if --plan was specified)
plan, err := DeterminePlan(ctx, planName)
if err != nil {
return err
}

// Check if the selected plan is a fixed plan
planIsFixed := isFixedPlan(plan.DisplayName)

// Prompt for auto-upgrade option (fixed plans only)
var enableAutoUpgrade bool
if planIsFixed {
if flag.IsSpecified(ctx, "enable-auto-upgrade") {
enableAutoUpgrade = flag.GetBool(ctx, "enable-auto-upgrade")
} else {
fmt.Fprintf(io.Out, "\nAuto-upgrade automatically switches to a higher plan when you hit resource limits.\nThis setting can be changed later.\n\n")
enableAutoUpgrade, err = prompt.Confirm(ctx, "Would you like to enable auto-upgrade?")
if err != nil {
return
}
}
} else if flag.IsSpecified(ctx, "enable-auto-upgrade") && flag.GetBool(ctx, "enable-auto-upgrade") {
fmt.Fprintf(io.Out, "\nNote: Auto-upgrade is only available for fixed plans, not pay-as-you-go.\n")
}

// prompt for prodpack option (pay-as-you-go and fixed plans)
var enableProdpack bool
if flag.IsSpecified(ctx, "enable-prodpack") {
enableProdpack = flag.GetBool(ctx, "enable-prodpack")
} else {
fmt.Fprintf(io.Out, "\nProdPack adds enhanced features for production workloads at $200/mo.\nThis setting can be changed later.\n\n")
enableProdpack, err = prompt.Confirm(ctx, "Would you like to enable ProdPack?")
if err != nil {
return
}
}

_, err = Create(ctx, org, name, primaryRegion, plan, flag.GetBool(ctx, "no-replicas"), enableEviction, enableAutoUpgrade, enableProdpack, nil)
return err
}

func Create(ctx context.Context, org *fly.Organization, name string, region *fly.Region, disallowReplicas bool, enableEviction bool, readRegions *[]fly.Region) (addOn *gql.AddOn, err error) {
func Create(ctx context.Context, org *fly.Organization, name string, region *fly.Region, plan *gql.ListAddOnPlansAddOnPlansAddOnPlanConnectionNodesAddOnPlan, disallowReplicas bool, enableEviction bool, enableAutoUpgrade bool, enableProdpack bool, readRegions *[]fly.Region) (addOn *gql.AddOn, err error) {
var (
io = iostreams.FromContext(ctx)
colorize = io.ColorScheme()
Expand Down Expand Up @@ -146,11 +225,6 @@ func Create(ctx context.Context, org *fly.Organization, name string, region *fly
}
}

plan, err := DeterminePlan(ctx)
if err != nil {
return nil, err
}

s := spinner.Run(io, "Launching...")

params := RedisConfiguration{
Expand All @@ -159,6 +233,8 @@ func Create(ctx context.Context, org *fly.Organization, name string, region *fly
PrimaryRegion: region,
ReadRegions: *readRegions,
Eviction: enableEviction,
AutoUpgrade: enableAutoUpgrade,
ProdPack: enableProdpack,
}

addOn, err = ProvisionDatabase(ctx, org, params)
Expand All @@ -181,6 +257,8 @@ type RedisConfiguration struct {
PrimaryRegion *fly.Region
ReadRegions []fly.Region
Eviction bool
AutoUpgrade bool
ProdPack bool
}

func ProvisionDatabase(ctx context.Context, org *fly.Organization, config RedisConfiguration) (addOn *gql.AddOn, err error) {
Expand All @@ -197,6 +275,12 @@ func ProvisionDatabase(ctx context.Context, org *fly.Organization, config RedisC
if config.Eviction {
options["eviction"] = true
}
if config.AutoUpgrade {
options["auto_upgrade"] = true
}
if config.ProdPack {
options["prod_pack"] = true
}

input := gql.CreateAddOnInput{
OrganizationId: org.ID,
Expand All @@ -216,21 +300,33 @@ func ProvisionDatabase(ctx context.Context, org *fly.Organization, config RedisC
return &response.CreateAddOn.AddOn, nil
}

func DeterminePlan(ctx context.Context) (*gql.ListAddOnPlansAddOnPlansAddOnPlanConnectionNodesAddOnPlan, error) {
func DeterminePlan(ctx context.Context, planName string) (*gql.ListAddOnPlansAddOnPlansAddOnPlanConnectionNodesAddOnPlan, error) {
client := flyutil.ClientFromContext(ctx)

planId := redisPlanPayAsYouGo

// Now that we have the Plan ID, look up the actual plan
allAddons, err := gql.ListAddOnPlans(ctx, client.GenqClient(), gql.AddOnTypeUpstashRedis)
// Fetch all available plans
allPlans, err := gql.ListAddOnPlans(ctx, client.GenqClient(), gql.AddOnTypeUpstashRedis)
if err != nil {
return nil, err
}

for _, addon := range allAddons.AddOnPlans.Nodes {
if addon.Id == planId {
return &addon, nil
// If a specific plan is requested, use it if it's not a legacy plan
if planName != "" {
if isLegacyPlan(planName) {
return nil, fmt.Errorf("plan %q is no longer available for new databases. Please choose a current plan", planName)
}
for _, plan := range allPlans.AddOnPlans.Nodes {
if plan.DisplayName == planName || plan.Id == planName {
return &plan, nil
}
}
return nil, fmt.Errorf("plan %q not found", planName)
}

// Default to pay-as-you-go plan
for _, plan := range allPlans.AddOnPlans.Nodes {
if plan.Id == redisPlanPayAsYouGo {
return &plan, nil
}
}
return nil, errors.New("plan not found")
return nil, errors.New("default plan not found")
}
24 changes: 19 additions & 5 deletions internal/command/redis/plans.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,39 @@ func runPlans(ctx context.Context) (err error) {
)

result, err := gql.ListAddOnPlans(ctx, client, gql.AddOnTypeUpstashRedis)
if err != nil {
return err
}

var rows [][]string

fmt.Fprintf(out, "\nRedis databases run on Fly.io, fully managed by Upstash.com. \nOther limits, besides memory, apply to most plans. Learn more at https://fly.io/docs/reference/redis\n\n")
fmt.Fprintf(out, "\nRedis databases run on Fly.io, fully managed by Upstash.com.\nOther limits, besides memory, apply to most plans. Learn more at https://fly.io/docs/reference/redis\n\n")

for _, plan := range result.AddOnPlans.Nodes {
// Filter out legacy plans - only show plans available for new databases
if isLegacyPlan(plan.DisplayName) {
continue
}

// Format price
var price string
if plan.PricePerMonth == 0 {
price = "Free"
} else {
price = fmt.Sprintf("$%d/mo", plan.PricePerMonth/100)
}

row := []string{
plan.DisplayName,
plan.MaxDataSize,
price,
plan.Description,
}

var price string

row = append(row, price)
rows = append(rows, row)
}

_ = render.Table(out, "", rows, "Name", "Description")
_ = render.Table(out, "", rows, "Name", "Max Data Size", "Price", "Description")

return
}
16 changes: 15 additions & 1 deletion internal/command/redis/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ func runStatus(ctx context.Context) (err error) {
evictionStatus = "Enabled"
}

autoUpgradeStatus := "Disabled"

if options["auto_upgrade"] != nil && options["auto_upgrade"].(bool) {
autoUpgradeStatus = "Enabled"
}

prodPackStatus := "Disabled"

if options["prod_pack"] != nil && options["prod_pack"].(bool) {
prodPackStatus = "Enabled"
}

obj := [][]string{
{
addOn.Id,
Expand All @@ -72,11 +84,13 @@ func runStatus(ctx context.Context) (err error) {
addOn.PrimaryRegion,
readRegions,
evictionStatus,
autoUpgradeStatus,
prodPackStatus,
addOn.PublicUrl,
},
}

var cols = []string{"ID", "Name", "Plan", "Primary Region", "Read Regions", "Eviction", "Private URL"}
var cols = []string{"ID", "Name", "Plan", "Primary Region", "Read Regions", "Eviction", "Auto-Upgrade", "ProdPack", "Private URL"}

if err = render.VerticalTable(io.Out, "Redis", obj, cols...); err != nil {
return
Expand Down
Loading
Loading