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 internal/command/launch/describe_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

"github.com/samber/lo"
"github.com/superfly/flyctl/internal/command/launch/plan"
"github.com/superfly/flyctl/internal/command/mpg"
mpgplans "github.com/superfly/flyctl/internal/command/mpg/plans"
"github.com/superfly/flyctl/internal/command/redis"
)

Expand Down Expand Up @@ -86,7 +86,7 @@ func describeObjectStoragePlan(p plan.ObjectStoragePlan) (string, error) {
func describeManagedPostgresPlan(p *plan.ManagedPostgresPlan, launchPlan *plan.LaunchPlan) (string, error) {
info := []string{}

planDetails, ok := mpg.MPGPlans[p.Plan]
planDetails, ok := mpgplans.MPGPlans[p.Plan]

if p.DbName != "" {
info = append(info, fmt.Sprintf("\"%s\"", p.GetDbName(launchPlan)))
Expand Down
35 changes: 17 additions & 18 deletions internal/command/launch/launch_databases.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ import (
"github.com/superfly/flyctl/internal/appsecrets"
extensions_core "github.com/superfly/flyctl/internal/command/extensions/core"
"github.com/superfly/flyctl/internal/command/launch/plan"
"github.com/superfly/flyctl/internal/command/mpg"
mpgv1cmd "github.com/superfly/flyctl/internal/command/mpg/v1"
"github.com/superfly/flyctl/internal/command/postgres"
"github.com/superfly/flyctl/internal/command/redis"
"github.com/superfly/flyctl/internal/flapsutil"
"github.com/superfly/flyctl/internal/flyutil"
"github.com/superfly/flyctl/internal/spinner"
"github.com/superfly/flyctl/internal/uiex"
"github.com/superfly/flyctl/internal/uiexutil"
mpgv1 "github.com/superfly/flyctl/internal/uiex/mpg/v1"
"github.com/superfly/flyctl/iostreams"
)

Expand Down Expand Up @@ -171,9 +170,9 @@ func (state *launchState) createFlyPostgres(ctx context.Context) error {

func (state *launchState) createManagedPostgres(ctx context.Context) error {
var (
io = iostreams.FromContext(ctx)
pgPlan = state.Plan.Postgres.ManagedPostgres
uiexClient = uiexutil.ClientFromContext(ctx)
io = iostreams.FromContext(ctx)
pgPlan = state.Plan.Postgres.ManagedPostgres
mpgClient = mpgv1.ClientFromContext(ctx)
)

// Check if we should attach to an existing cluster instead of creating a new one
Expand Down Expand Up @@ -203,7 +202,7 @@ func (state *launchState) createManagedPostgres(ctx context.Context) error {
}

// Create cluster using the same parameters as mpg create
params := &mpg.CreateClusterParams{
params := &mpgv1cmd.CreateClusterParams{
Name: pgPlan.DbName,
OrgSlug: slug,
Region: pgPlan.Region,
Expand All @@ -212,7 +211,7 @@ func (state *launchState) createManagedPostgres(ctx context.Context) error {
}

// Create cluster using the UI-EX client with retry logic for network errors
input := uiex.CreateClusterInput{
input := mpgv1.CreateClusterInput{
Name: params.Name,
Region: params.Region,
Plan: params.Plan,
Expand All @@ -222,11 +221,11 @@ func (state *launchState) createManagedPostgres(ctx context.Context) error {

fmt.Fprintf(io.Out, "Provisioning Managed Postgres cluster...\n")

var response uiex.CreateClusterResponse
var response mpgv1.CreateClusterResponse
err = retry.Do(
func() error {
var retryErr error
response, retryErr = uiexClient.CreateCluster(ctx, input)
response, retryErr = mpgClient.CreateCluster(ctx, input)

return retryErr
},
Expand Down Expand Up @@ -262,7 +261,7 @@ func (state *launchState) createManagedPostgres(ctx context.Context) error {
// Use retry.Do with a 15-minute timeout and exponential backoff
err = retry.Do(
func() error {
cluster, err := uiexClient.GetManagedClusterById(ctx, response.Data.Id)
cluster, err := mpgClient.GetManagedClusterById(ctx, response.Data.Id)
if err != nil {
// For network errors, return the error to trigger retry
if containsNetworkError(err.Error()) {
Expand Down Expand Up @@ -325,11 +324,11 @@ func (state *launchState) createManagedPostgres(ctx context.Context) error {
}

// Get the cluster credentials with retry logic
var cluster uiex.GetManagedClusterResponse
var cluster mpgv1.GetManagedClusterResponse
err = retry.Do(
func() error {
var retryErr error
cluster, retryErr = uiexClient.GetManagedClusterById(ctx, response.Data.Id)
cluster, retryErr = mpgClient.GetManagedClusterById(ctx, response.Data.Id)

return retryErr
},
Expand Down Expand Up @@ -364,19 +363,19 @@ func (state *launchState) createManagedPostgres(ctx context.Context) error {
// attachToManagedPostgres attaches an existing Managed Postgres cluster to the app
func (state *launchState) attachToManagedPostgres(ctx context.Context, clusterID string) error {
var (
io = iostreams.FromContext(ctx)
uiexClient = uiexutil.ClientFromContext(ctx)
client = flyutil.ClientFromContext(ctx)
io = iostreams.FromContext(ctx)
mpgClient = mpgv1.ClientFromContext(ctx)
client = flyutil.ClientFromContext(ctx)
)

// Get cluster details to verify it exists and get credentials
fmt.Fprintf(io.Out, "Attaching to existing Managed Postgres cluster %s...\n", clusterID)

var cluster uiex.GetManagedClusterResponse
var cluster mpgv1.GetManagedClusterResponse
err := retry.Do(
func() error {
var retryErr error
cluster, retryErr = uiexClient.GetManagedClusterById(ctx, clusterID)
cluster, retryErr = mpgClient.GetManagedClusterById(ctx, clusterID)

return retryErr
},
Expand Down
18 changes: 10 additions & 8 deletions internal/command/launch/plan/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"fmt"

fly "github.com/superfly/fly-go"
"github.com/superfly/flyctl/internal/command/mpg"
"github.com/superfly/flyctl/internal/command/mpg/plans"
mpgutils "github.com/superfly/flyctl/internal/command/mpg/utils"
mpgv1 "github.com/superfly/flyctl/internal/command/mpg/v1"
"github.com/superfly/flyctl/internal/flag"
"github.com/superfly/flyctl/internal/prompt"
"github.com/superfly/flyctl/iostreams"
Expand Down Expand Up @@ -66,10 +68,10 @@ func DefaultPostgres(ctx context.Context, plan *LaunchPlan, mpgEnabled bool) (Po
}

// Normal flow: prefer managed if enabled and available
orgSlug, err := mpg.ResolveOrganizationSlug(ctx, plan.OrgSlug)
orgSlug, err := mpgutils.ResolveOrganizationSlug(ctx, plan.OrgSlug)
if err == nil && mpgEnabled {
// 2025-08-06: only default to MPG in interactive for now, we should update this down the road
validRegion, err := mpg.IsValidMPGRegion(ctx, orgSlug, plan.RegionCode)
validRegion, err := mpgv1.IsValidMPGRegion(ctx, orgSlug, plan.RegionCode)
if isInteractive {
if err == nil && validRegion {
// Managed postgres is available in this region, use it
Expand Down Expand Up @@ -120,7 +122,7 @@ func createManagedPostgresPlan(ctx context.Context, plan *LaunchPlan, planType s

// Display plan details if we have an IO context
if io != nil && planType != "" {
if planDetails, exists := mpg.MPGPlans[planType]; exists {
if planDetails, exists := plans.MPGPlans[planType]; exists {
colorize := io.ColorScheme()
fmt.Fprintf(io.Out, "\nSelected Managed Postgres Plan: %s\n", colorize.Purple(planDetails.Name))
fmt.Fprintf(io.Out, " CPU: %s\n", planDetails.CPU)
Expand All @@ -144,12 +146,12 @@ func createManagedPostgresPlan(ctx context.Context, plan *LaunchPlan, planType s
func handleForcedManagedPostgres(ctx context.Context, plan *LaunchPlan) (PostgresPlan, error) {
io := iostreams.FromContext(ctx)

orgSlug, err := mpg.ResolveOrganizationSlug(ctx, plan.OrgSlug)
orgSlug, err := mpgutils.ResolveOrganizationSlug(ctx, plan.OrgSlug)
if err != nil {
return createFlyPostgresPlan(plan), nil
}

validRegion, err := mpg.IsValidMPGRegion(ctx, orgSlug, plan.RegionCode)
validRegion, err := mpgv1.IsValidMPGRegion(ctx, orgSlug, plan.RegionCode)

if err == nil && validRegion {
// Region supports managed postgres
Expand All @@ -163,7 +165,7 @@ func handleForcedManagedPostgres(ctx context.Context, plan *LaunchPlan) (Postgre
return handleInteractiveRegionSwitch(ctx, plan, orgSlug)
} else {
// Non-interactive: fail with error
availableCodes, _ := mpg.GetAvailableMPGRegionCodes(ctx, orgSlug)
availableCodes, _ := mpgv1.GetAvailableMPGRegionCodes(ctx, orgSlug)

return PostgresPlan{}, fmt.Errorf("managed postgres is not available in region %s. Available regions: %v", plan.RegionCode, availableCodes)
}
Expand All @@ -174,7 +176,7 @@ func handleInteractiveRegionSwitch(ctx context.Context, plan *LaunchPlan, orgSlu
io := iostreams.FromContext(ctx)

// Get available MPG regions
availableRegions, err := mpg.GetAvailableMPGRegions(ctx, orgSlug)
availableRegions, err := mpgv1.GetAvailableMPGRegions(ctx, orgSlug)
if err != nil || len(availableRegions) == 0 {
if io != nil {
colorize := io.ColorScheme()
Expand Down
79 changes: 40 additions & 39 deletions internal/command/launch/plan/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import (
"github.com/superfly/flyctl/internal/flyutil"
"github.com/superfly/flyctl/internal/mock"
"github.com/superfly/flyctl/internal/uiex"
mpgv1 "github.com/superfly/flyctl/internal/uiex/mpg/v1"
"github.com/superfly/flyctl/internal/uiexutil"
"github.com/superfly/flyctl/iostreams"
)

// mockUIEXClient implements uiexutil.Client for testing
type mockUIEXClient struct {
mpgRegions []uiex.MPGRegion
mpgRegions []mpgv1.MPGRegion
}

func (m *mockUIEXClient) ListOrganizations(ctx context.Context, admin bool) ([]uiex.Organization, error) {
Expand All @@ -33,8 +34,8 @@ func (m *mockUIEXClient) PromoteMachineEgressIP(ctx context.Context, appName str
return nil
}

func (m *mockUIEXClient) ListMPGRegions(ctx context.Context, orgSlug string) (uiex.ListMPGRegionsResponse, error) {
return uiex.ListMPGRegionsResponse{Data: m.mpgRegions}, nil
func (m *mockUIEXClient) ListMPGRegions(ctx context.Context, orgSlug string) (mpgv1.ListMPGRegionsResponse, error) {
return mpgv1.ListMPGRegionsResponse{Data: m.mpgRegions}, nil
}

// mockGenqClient implements the genq.Client interface for testing
Expand All @@ -52,76 +53,76 @@ func (m *mockGenqClient) MakeRequest(ctx context.Context, req *genq.Request, res
return nil
}

func (m *mockUIEXClient) ListManagedClusters(ctx context.Context, orgSlug string, deleted bool) (uiex.ListManagedClustersResponse, error) {
return uiex.ListManagedClustersResponse{}, nil
func (m *mockUIEXClient) ListManagedClusters(ctx context.Context, orgSlug string, deleted bool) (mpgv1.ListManagedClustersResponse, error) {
return mpgv1.ListManagedClustersResponse{}, nil
}

func (m *mockUIEXClient) GetManagedCluster(ctx context.Context, orgSlug string, id string) (uiex.GetManagedClusterResponse, error) {
return uiex.GetManagedClusterResponse{}, nil
func (m *mockUIEXClient) GetManagedCluster(ctx context.Context, orgSlug string, id string) (mpgv1.GetManagedClusterResponse, error) {
return mpgv1.GetManagedClusterResponse{}, nil
}

func (m *mockUIEXClient) GetManagedClusterById(ctx context.Context, id string) (uiex.GetManagedClusterResponse, error) {
return uiex.GetManagedClusterResponse{}, nil
func (m *mockUIEXClient) GetManagedClusterById(ctx context.Context, id string) (mpgv1.GetManagedClusterResponse, error) {
return mpgv1.GetManagedClusterResponse{}, nil
}

func (m *mockUIEXClient) CreateUser(ctx context.Context, id string, input uiex.CreateUserInput) (uiex.CreateUserResponse, error) {
return uiex.CreateUserResponse{}, nil
func (m *mockUIEXClient) CreateUser(ctx context.Context, id string, input mpgv1.CreateUserInput) (mpgv1.CreateUserResponse, error) {
return mpgv1.CreateUserResponse{}, nil
}

func (m *mockUIEXClient) CreateUserWithRole(ctx context.Context, id string, input uiex.CreateUserWithRoleInput) (uiex.CreateUserWithRoleResponse, error) {
return uiex.CreateUserWithRoleResponse{}, nil
func (m *mockUIEXClient) CreateUserWithRole(ctx context.Context, id string, input mpgv1.CreateUserWithRoleInput) (mpgv1.CreateUserWithRoleResponse, error) {
return mpgv1.CreateUserWithRoleResponse{}, nil
}

func (m *mockUIEXClient) UpdateUserRole(ctx context.Context, id string, username string, input uiex.UpdateUserRoleInput) (uiex.UpdateUserRoleResponse, error) {
return uiex.UpdateUserRoleResponse{}, nil
func (m *mockUIEXClient) UpdateUserRole(ctx context.Context, id string, username string, input mpgv1.UpdateUserRoleInput) (mpgv1.UpdateUserRoleResponse, error) {
return mpgv1.UpdateUserRoleResponse{}, nil
}

func (m *mockUIEXClient) DeleteUser(ctx context.Context, id string, username string) error {
return nil
}

func (m *mockUIEXClient) GetUserCredentials(ctx context.Context, id string, username string) (uiex.GetUserCredentialsResponse, error) {
return uiex.GetUserCredentialsResponse{}, nil
func (m *mockUIEXClient) GetUserCredentials(ctx context.Context, id string, username string) (mpgv1.GetUserCredentialsResponse, error) {
return mpgv1.GetUserCredentialsResponse{}, nil
}

func (m *mockUIEXClient) ListUsers(ctx context.Context, id string) (uiex.ListUsersResponse, error) {
return uiex.ListUsersResponse{}, nil
func (m *mockUIEXClient) ListUsers(ctx context.Context, id string) (mpgv1.ListUsersResponse, error) {
return mpgv1.ListUsersResponse{}, nil
}

func (m *mockUIEXClient) ListDatabases(ctx context.Context, id string) (uiex.ListDatabasesResponse, error) {
return uiex.ListDatabasesResponse{}, nil
func (m *mockUIEXClient) ListDatabases(ctx context.Context, id string) (mpgv1.ListDatabasesResponse, error) {
return mpgv1.ListDatabasesResponse{}, nil
}

func (m *mockUIEXClient) CreateDatabase(ctx context.Context, id string, input uiex.CreateDatabaseInput) (uiex.CreateDatabaseResponse, error) {
return uiex.CreateDatabaseResponse{}, nil
func (m *mockUIEXClient) CreateDatabase(ctx context.Context, id string, input mpgv1.CreateDatabaseInput) (mpgv1.CreateDatabaseResponse, error) {
return mpgv1.CreateDatabaseResponse{}, nil
}

func (m *mockUIEXClient) CreateCluster(ctx context.Context, input uiex.CreateClusterInput) (uiex.CreateClusterResponse, error) {
return uiex.CreateClusterResponse{}, nil
func (m *mockUIEXClient) CreateCluster(ctx context.Context, input mpgv1.CreateClusterInput) (mpgv1.CreateClusterResponse, error) {
return mpgv1.CreateClusterResponse{}, nil
}

func (m *mockUIEXClient) DestroyCluster(ctx context.Context, orgSlug string, id string) error {
return nil
}

func (m *mockUIEXClient) ListManagedClusterBackups(ctx context.Context, clusterID string) (uiex.ListManagedClusterBackupsResponse, error) {
return uiex.ListManagedClusterBackupsResponse{}, nil
func (m *mockUIEXClient) ListManagedClusterBackups(ctx context.Context, clusterID string) (mpgv1.ListManagedClusterBackupsResponse, error) {
return mpgv1.ListManagedClusterBackupsResponse{}, nil
}

func (m *mockUIEXClient) CreateManagedClusterBackup(ctx context.Context, clusterID string, input uiex.CreateManagedClusterBackupInput) (uiex.CreateManagedClusterBackupResponse, error) {
return uiex.CreateManagedClusterBackupResponse{}, nil
func (m *mockUIEXClient) CreateManagedClusterBackup(ctx context.Context, clusterID string, input mpgv1.CreateManagedClusterBackupInput) (mpgv1.CreateManagedClusterBackupResponse, error) {
return mpgv1.CreateManagedClusterBackupResponse{}, nil
}

func (m *mockUIEXClient) RestoreManagedClusterBackup(ctx context.Context, clusterID string, input uiex.RestoreManagedClusterBackupInput) (uiex.RestoreManagedClusterBackupResponse, error) {
return uiex.RestoreManagedClusterBackupResponse{}, nil
func (m *mockUIEXClient) RestoreManagedClusterBackup(ctx context.Context, clusterID string, input mpgv1.RestoreManagedClusterBackupInput) (mpgv1.RestoreManagedClusterBackupResponse, error) {
return mpgv1.RestoreManagedClusterBackupResponse{}, nil
}

func (m *mockUIEXClient) CreateAttachment(ctx context.Context, clusterId string, input uiex.CreateAttachmentInput) (uiex.CreateAttachmentResponse, error) {
return uiex.CreateAttachmentResponse{}, nil
func (m *mockUIEXClient) CreateAttachment(ctx context.Context, clusterId string, input mpgv1.CreateAttachmentInput) (mpgv1.CreateAttachmentResponse, error) {
return mpgv1.CreateAttachmentResponse{}, nil
}

func (m *mockUIEXClient) DeleteAttachment(ctx context.Context, clusterId string, appName string) (uiex.DeleteAttachmentResponse, error) {
return uiex.DeleteAttachmentResponse{}, nil
func (m *mockUIEXClient) DeleteAttachment(ctx context.Context, clusterId string, appName string) (mpgv1.DeleteAttachmentResponse, error) {
return mpgv1.DeleteAttachmentResponse{}, nil
}

func (m *mockUIEXClient) CreateBuild(ctx context.Context, in uiex.CreateBuildRequest) (*uiex.BuildResponse, error) {
Expand Down Expand Up @@ -239,14 +240,14 @@ func TestDefaultPostgres_ForceTypes(t *testing.T) {
ctx = flagctx.NewContext(ctx, flagSet)

// Set up mock UIEX client for MPG regions
var mpgRegions []uiex.MPGRegion
var mpgRegions []mpgv1.MPGRegion
if tt.mpgRegionsWithIAD {
mpgRegions = []uiex.MPGRegion{
mpgRegions = []mpgv1.MPGRegion{
{Code: "iad", Available: true},
{Code: "lax", Available: true},
}
} else {
mpgRegions = []uiex.MPGRegion{
mpgRegions = []mpgv1.MPGRegion{
{Code: "lax", Available: true},
{Code: "fra", Available: true},
// iad is not in the list, so it's not available
Expand Down Expand Up @@ -329,7 +330,7 @@ func TestDefaultPostgres_RegionSwitching(t *testing.T) {
ctx = flagctx.NewContext(ctx, flagSet)

// Set up mock UIEX client where iad doesn't support MPG but lax does
mpgRegions := []uiex.MPGRegion{
mpgRegions := []mpgv1.MPGRegion{
{Code: "lax", Available: true},
{Code: "fra", Available: true},
// iad is not in the list, so it's not available
Expand Down
6 changes: 3 additions & 3 deletions internal/command/launch/webui.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
fly "github.com/superfly/fly-go"
"github.com/superfly/flyctl/helpers"
"github.com/superfly/flyctl/internal/command/launch/plan"
"github.com/superfly/flyctl/internal/command/mpg"
mpgv1 "github.com/superfly/flyctl/internal/command/mpg/v1"
"github.com/superfly/flyctl/internal/logger"
state2 "github.com/superfly/flyctl/internal/state"
"github.com/superfly/flyctl/internal/tracing"
Expand Down Expand Up @@ -103,13 +103,13 @@ func (state *launchState) EditInWebUi(ctx context.Context) error {
}

// Check if region is supported for managed Postgres
validRegion, err := mpg.IsValidMPGRegion(ctx, org.RawSlug, region)
validRegion, err := mpgv1.IsValidMPGRegion(ctx, org.RawSlug, region)
if err != nil {
return fmt.Errorf("failed to validate MPG region: %w", err)
}

if !validRegion {
availableCodes, _ := mpg.GetAvailableMPGRegionCodes(ctx, org.Slug)
availableCodes, _ := mpgv1.GetAvailableMPGRegionCodes(ctx, org.Slug)

return fmt.Errorf("region %s is not available for Managed Postgres. Available regions: %v", region, availableCodes)
}
Expand Down
Loading
Loading