From ab48e5fe0ef9e00657df8890933a7463f4d2b3bb Mon Sep 17 00:00:00 2001 From: lillian Date: Mon, 7 Sep 2026 13:40:27 +0000 Subject: [PATCH 1/2] Use Flaps for app and region lookups that only read Flaps-available fields Many commands fetched an app through the GraphQL GetAppCompact or GetAppBasic queries but only read the name, organization slug, network, or Postgres role, all of which the Flaps GET /apps/{name} endpoint returns. Switch those callers to the Flaps client, and add a small flapsutil.IsPostgresApp helper for the app_role check. Regions move to the Flaps /platform/regions endpoint everywhere, including the cached list in the prompt package. It returns the same fly.Region type plus the nearest region code, so the nearest-region lookup for unattached volumes uses it too. Org-scoped app completion lists apps through Flaps by org slug, and the launch Postgres planner checks for an existing database app with one Flaps lookup instead of listing every visible app. Callers that feed the app or org GraphQL ID into a mutation, such as SSH certificate issuance through flypg, stay on GraphQL. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01DMttEkUAVhL7YZFRFjhHwB --- internal/command/apps/restart.go | 14 ++--- internal/command/command_run.go | 5 +- internal/command/console/console.go | 5 +- internal/command/curl/curl.go | 19 ++++--- internal/command/dashboard/root.go | 7 ++- internal/command/dig/dig.go | 3 +- internal/command/image/show.go | 8 +-- internal/command/image/update.go | 9 ++-- internal/command/image/update_machines.go | 5 +- internal/command/launch/launch_databases.go | 18 +++---- internal/command/launch/plan/postgres_test.go | 45 ++++++++-------- internal/command/launch/state.go | 6 ++- internal/command/machine/destroy.go | 4 +- internal/command/machine/run.go | 5 +- internal/command/ping/ping.go | 3 +- internal/command/postgres/add_flycast.go | 9 ++-- internal/command/postgres/attach.go | 24 ++++----- internal/command/postgres/backup.go | 8 +-- internal/command/postgres/config_show.go | 17 +++---- internal/command/postgres/create.go | 14 ++--- internal/command/postgres/db.go | 13 +++-- internal/command/postgres/restart.go | 9 ++-- internal/command/postgres/users.go | 13 +++-- internal/command/proxy/proxy.go | 17 +++---- internal/command/ssh/console.go | 4 +- internal/command/ssh/sftp.go | 5 +- internal/command/tokens/list.go | 3 +- internal/command/volumes/destroy.go | 5 +- internal/command/volumes/extend.go | 7 +-- internal/command/volumes/fork.go | 8 +-- internal/command/volumes/list.go | 10 ++-- internal/command/volumes/show.go | 6 +-- internal/command/volumes/volumes.go | 10 ++-- internal/flag/completion/completions.go | 51 ++++++++++++------- internal/flapsutil/app.go | 8 +++ internal/prompt/prompt.go | 13 ++++- 36 files changed, 207 insertions(+), 203 deletions(-) diff --git a/internal/command/apps/restart.go b/internal/command/apps/restart.go index 631f9b50e2..75527bddd4 100644 --- a/internal/command/apps/restart.go +++ b/internal/command/apps/restart.go @@ -7,12 +7,12 @@ import ( "github.com/spf13/cobra" fly "github.com/superfly/fly-go" + "github.com/superfly/fly-go/flaps" "github.com/superfly/flyctl/internal/appconfig" "github.com/superfly/flyctl/internal/command" "github.com/superfly/flyctl/internal/flag" "github.com/superfly/flyctl/internal/flag/completion" "github.com/superfly/flyctl/internal/flapsutil" - "github.com/superfly/flyctl/internal/flyutil" "github.com/superfly/flyctl/internal/machine" ) @@ -50,8 +50,8 @@ func newRestart() *cobra.Command { func runRestart(ctx context.Context) error { var ( - appName = flag.FirstArg(ctx) - client = flyutil.ClientFromContext(ctx) + appName = flag.FirstArg(ctx) + flapsClient = flapsutil.ClientFromContext(ctx) ) if appName == "" { @@ -61,16 +61,16 @@ func runRestart(ctx context.Context) error { } } - app, err := client.GetAppCompact(ctx, appName) + app, err := flapsClient.GetApp(ctx, appName) if err != nil { return err } - if app.IsPostgresApp() { + if flapsutil.IsPostgresApp(app) { return fmt.Errorf("postgres apps should use `fly pg restart` instead") } - ctx, err = BuildContext(ctx, app) + ctx, err = BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) if err != nil { return err } @@ -78,7 +78,7 @@ func runRestart(ctx context.Context) error { return runMachinesRestart(ctx, app) } -func runMachinesRestart(ctx context.Context, app *fly.AppCompact) error { +func runMachinesRestart(ctx context.Context, app *flaps.App) error { input := &fly.RestartMachineInput{ ForceStop: flag.GetBool(ctx, "force-stop"), SkipHealthChecks: flag.GetBool(ctx, "skip-health-checks"), diff --git a/internal/command/command_run.go b/internal/command/command_run.go index 8ea63f9159..4f09d259e6 100644 --- a/internal/command/command_run.go +++ b/internal/command/command_run.go @@ -416,15 +416,14 @@ func DetermineMounts(ctx context.Context, appName string, mounts []fly.MachineMo } func getUnattachedVolumes(ctx context.Context, appName, regionCode string) (map[string][]fly.Volume, error) { - apiclient := flyutil.ClientFromContext(ctx) flapsClient := flapsutil.ClientFromContext(ctx) if regionCode == "" { - region, err := apiclient.GetNearestRegion(ctx) + regionData, err := flapsClient.GetRegions(ctx) if err != nil { return nil, err } - regionCode = region.Code + regionCode = regionData.Nearest } volumes, err := flapsClient.GetVolumes(ctx, appName) diff --git a/internal/command/console/console.go b/internal/command/console/console.go index f951ef780d..b2d7cba4ed 100644 --- a/internal/command/console/console.go +++ b/internal/command/console/console.go @@ -180,10 +180,11 @@ func runConsole(ctx context.Context) error { return fmt.Errorf("failed to get app: %w", err) } - network, err := apiClient.GetAppNetwork(ctx, app.Name) + flapsApp, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, app.Name) if err != nil { return fmt.Errorf("failed to get app network: %w", err) } + network := flapsApp.Network appConfig := appconfig.ConfigFromContext(ctx) if appConfig == nil { @@ -208,7 +209,7 @@ func runConsole(ctx context.Context) error { defer cleanup() } - _, dialer, err := agent.BringUpAgent(ctx, apiClient, app, *network, false) + _, dialer, err := agent.BringUpAgent(ctx, apiClient, app, network, false) if err != nil { return err } diff --git a/internal/command/curl/curl.go b/internal/command/curl/curl.go index 622ae9e2d3..659c71cf92 100644 --- a/internal/command/curl/curl.go +++ b/internal/command/curl/curl.go @@ -23,7 +23,7 @@ import ( "github.com/superfly/flyctl/internal/command" "github.com/superfly/flyctl/internal/config" "github.com/superfly/flyctl/internal/flag" - "github.com/superfly/flyctl/internal/flyutil" + "github.com/superfly/flyctl/internal/flapsutil" "github.com/superfly/flyctl/internal/render" "github.com/superfly/flyctl/iostreams" ) @@ -79,17 +79,16 @@ func run(ctx context.Context) error { } func fetchRegionCodes(ctx context.Context) (codes []string, err error) { - client := flyutil.ClientFromContext(ctx) + flapsClient := flapsutil.ClientFromContext(ctx) - var regions []fly.Region - if regions, _, err = client.PlatformRegions(ctx); err != nil { - err = fmt.Errorf("failed retrieving regions: %w", err) - - return - } else if len(regions) == 0 { - err = errors.New("no regions could be retrieved") + regionData, err := flapsClient.GetRegions(ctx) + if err != nil { + return nil, fmt.Errorf("failed retrieving regions: %w", err) + } - return + regions := regionData.Regions + if len(regions) == 0 { + return nil, errors.New("no regions could be retrieved") } // Filter out deprecated regions diff --git a/internal/command/dashboard/root.go b/internal/command/dashboard/root.go index 1a71131c31..1f6815d7fb 100644 --- a/internal/command/dashboard/root.go +++ b/internal/command/dashboard/root.go @@ -9,7 +9,7 @@ import ( "github.com/superfly/flyctl/internal/appconfig" "github.com/superfly/flyctl/internal/command" "github.com/superfly/flyctl/internal/flag" - "github.com/superfly/flyctl/internal/flyutil" + "github.com/superfly/flyctl/internal/flapsutil" "github.com/superfly/flyctl/iostreams" ) @@ -67,13 +67,12 @@ func runDashboardMetrics(ctx context.Context) error { appName := appconfig.NameFromContext(ctx) if flag.GetBool(ctx, "grafana") { - client := flyutil.ClientFromContext(ctx) - app, err := client.GetAppBasic(ctx, appName) + app, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, appName) if err != nil { return fmt.Errorf("failed to get app info: %w", err) } - url := fmt.Sprintf("https://fly-metrics.net/d/fly-app/fly-app?orgId=%s&var-app=%s", app.Organization.InternalNumericID, appName) + url := fmt.Sprintf("https://fly-metrics.net/d/fly-app/fly-app?orgId=%d&var-app=%s", app.Organization.InternalNumericID, appName) return runDashboardOpen(ctx, url) } diff --git a/internal/command/dig/dig.go b/internal/command/dig/dig.go index 8244792831..3ca5aa323b 100644 --- a/internal/command/dig/dig.go +++ b/internal/command/dig/dig.go @@ -15,6 +15,7 @@ import ( "github.com/spf13/cobra" "github.com/superfly/flyctl/agent" + "github.com/superfly/flyctl/internal/flapsutil" "github.com/superfly/flyctl/iostreams" "github.com/superfly/flyctl/internal/appconfig" @@ -74,7 +75,7 @@ func run(ctx context.Context) error { if orgSlug == "" { appName := appconfig.NameFromContext(ctx) - app, err := client.GetAppBasic(ctx, appName) + app, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, appName) if err != nil { return fmt.Errorf("get app: %w", err) } diff --git a/internal/command/image/show.go b/internal/command/image/show.go index 1d9c1b27f0..7fce70b8e1 100644 --- a/internal/command/image/show.go +++ b/internal/command/image/show.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/cobra" fly "github.com/superfly/fly-go" + "github.com/superfly/fly-go/flaps" "github.com/superfly/flyctl/internal/appconfig" "github.com/superfly/flyctl/internal/command" "github.com/superfly/flyctl/internal/config" @@ -44,11 +45,10 @@ func newShow() *cobra.Command { func runShow(ctx context.Context) error { var ( - client = flyutil.ClientFromContext(ctx) appName = appconfig.NameFromContext(ctx) ) - app, err := client.GetAppCompact(ctx, appName) + app, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, appName) if err != nil { return fmt.Errorf("get app: %w", err) } @@ -56,7 +56,7 @@ func runShow(ctx context.Context) error { return showMachineImage(ctx, app) } -func showMachineImage(ctx context.Context, app *fly.AppCompact) error { +func showMachineImage(ctx context.Context, app *flaps.App) error { var ( io = iostreams.FromContext(ctx) colorize = io.ColorScheme() @@ -158,7 +158,7 @@ func showMachineImage(ctx context.Context, app *fly.AppCompact) error { latest = latestImage } - if app.IsPostgresApp() { + if flapsutil.IsPostgresApp(app) { // Abort if we detect a postgres machine running a different major version. if latest.Tag != latestImage.Tag { return fmt.Errorf("major version mismatch detected") diff --git a/internal/command/image/update.go b/internal/command/image/update.go index f64e7aaa9b..77a7d2e1b0 100644 --- a/internal/command/image/update.go +++ b/internal/command/image/update.go @@ -10,7 +10,7 @@ import ( "github.com/superfly/flyctl/internal/command" "github.com/superfly/flyctl/internal/command/apps" "github.com/superfly/flyctl/internal/flag" - "github.com/superfly/flyctl/internal/flyutil" + "github.com/superfly/flyctl/internal/flapsutil" ) func newUpdate() *cobra.Command { @@ -49,20 +49,19 @@ The update will perform a rolling restart against each Machine, which may result func runUpdate(ctx context.Context) error { var ( appName = appconfig.NameFromContext(ctx) - client = flyutil.ClientFromContext(ctx) ) - app, err := client.GetAppCompact(ctx, appName) + app, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, appName) if err != nil { return fmt.Errorf("get app: %w", err) } - ctx, err = apps.BuildContext(ctx, app) + ctx, err = apps.BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) if err != nil { return err } - if app.IsPostgresApp() { + if flapsutil.IsPostgresApp(app) { return updatePostgresOnMachines(ctx, app) } diff --git a/internal/command/image/update_machines.go b/internal/command/image/update_machines.go index 97f905db44..4a91c02350 100644 --- a/internal/command/image/update_machines.go +++ b/internal/command/image/update_machines.go @@ -6,6 +6,7 @@ import ( "strings" fly "github.com/superfly/fly-go" + "github.com/superfly/fly-go/flaps" "github.com/superfly/flyctl/agent" "github.com/superfly/flyctl/flypg" "github.com/superfly/flyctl/internal/appsecrets" @@ -15,7 +16,7 @@ import ( "github.com/superfly/flyctl/iostreams" ) -func updateImageForMachines(ctx context.Context, app *fly.AppCompact) error { +func updateImageForMachines(ctx context.Context, app *flaps.App) error { var ( io = iostreams.FromContext(ctx) @@ -83,7 +84,7 @@ type member struct { TargetConfig fly.MachineConfig } -func updatePostgresOnMachines(ctx context.Context, app *fly.AppCompact) (err error) { +func updatePostgresOnMachines(ctx context.Context, app *flaps.App) (err error) { var ( io = iostreams.FromContext(ctx) colorize = io.ColorScheme() diff --git a/internal/command/launch/launch_databases.go b/internal/command/launch/launch_databases.go index df0606c347..9dfba0129c 100644 --- a/internal/command/launch/launch_databases.go +++ b/internal/command/launch/launch_databases.go @@ -77,9 +77,8 @@ func (state *launchState) createDatabases(ctx context.Context) error { func (state *launchState) createFlyPostgres(ctx context.Context) error { var ( - pgPlan = state.Plan.Postgres.FlyPostgres - apiClient = flyutil.ClientFromContext(ctx) - io = iostreams.FromContext(ctx) + pgPlan = state.Plan.Postgres.FlyPostgres + io = iostreams.FromContext(ctx) ) attachToExisting := false @@ -88,12 +87,8 @@ func (state *launchState) createFlyPostgres(ctx context.Context) error { pgPlan.AppName = fmt.Sprintf("%s-db", state.appConfig.AppName) } - if apps, err := apiClient.GetApps(ctx, nil); err == nil { - for _, app := range apps { - if app.Name == pgPlan.AppName { - attachToExisting = true - } - } + if _, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, pgPlan.AppName); err == nil { + attachToExisting = true } if attachToExisting { @@ -486,11 +481,12 @@ func (state *launchState) createUpstashRedis(ctx context.Context) error { var readReplicaRegions []fly.Region { - client := flyutil.ClientFromContext(ctx) - regions, _, err := client.PlatformRegions(ctx) + flapsClient := flapsutil.ClientFromContext(ctx) + regionData, err := flapsClient.GetRegions(ctx) if err != nil { return err } + regions := regionData.Regions // Filter out deprecated regions regions = lo.Filter(regions, func(r fly.Region, _ int) bool { return !r.Deprecated diff --git a/internal/command/launch/plan/postgres_test.go b/internal/command/launch/plan/postgres_test.go index 2dcd76baf9..34d5a6fd93 100644 --- a/internal/command/launch/plan/postgres_test.go +++ b/internal/command/launch/plan/postgres_test.go @@ -173,36 +173,29 @@ func TestDefaultPostgres_ForceTypes(t *testing.T) { ctx = flagctx.NewContext(ctx, flagSet) // Set up mock flaps client for MPG region availability - var mpgRegions []fly.Region + // Platform regions come from Flaps; MPGAvailable marks MPG region support. + var regions []fly.Region if tt.mpgRegionsWithIAD { - mpgRegions = []fly.Region{ - {Code: "iad", MPGAvailable: true}, - {Code: "lax", MPGAvailable: true}, + regions = []fly.Region{ + {Code: "iad", Name: "Ashburn, Virginia (US)", MPGAvailable: true}, + {Code: "lax", Name: "Los Angeles, California (US)", MPGAvailable: true}, + {Code: "fra", Name: "Frankfurt, Germany"}, } } else { - mpgRegions = []fly.Region{ - {Code: "lax", MPGAvailable: true}, - {Code: "fra", MPGAvailable: true}, - // iad is not in the list, so it's not available + regions = []fly.Region{ + {Code: "iad", Name: "Ashburn, Virginia (US)"}, // iad is not MPG-available + {Code: "lax", Name: "Los Angeles, California (US)", MPGAvailable: true}, + {Code: "fra", Name: "Frankfurt, Germany", MPGAvailable: true}, } } ctx = flapsutil.NewContextWithClient(ctx, &mock.FlapsClient{ GetRegionsFunc: func(ctx context.Context) (*flaps.RegionData, error) { - return &flaps.RegionData{Regions: mpgRegions}, nil + return &flaps.RegionData{Regions: regions, Nearest: "iad"}, nil }, }) - // Set up mock API client for platform regions mockClient := &mock.Client{ - PlatformRegionsFunc: func(ctx context.Context) ([]fly.Region, *fly.Region, error) { - // Return some mock regions for testing - return []fly.Region{ - {Code: "iad", Name: "Ashburn, Virginia (US)"}, - {Code: "lax", Name: "Los Angeles, California (US)"}, - {Code: "fra", Name: "Frankfurt, Germany"}, - }, &fly.Region{Code: "iad", Name: "Ashburn, Virginia (US)"}, nil - }, GenqClientFunc: func() genq.Client { return &mockGenqClient{} }, @@ -269,15 +262,17 @@ func TestDefaultPostgres_RegionSwitching(t *testing.T) { mockUIEX := &mockUIEXClient{} ctx = uiexutil.NewContextWithClient(ctx, mockUIEX) - // Set up mock API client for platform regions - mockClient := &mock.Client{ - PlatformRegionsFunc: func(ctx context.Context) ([]fly.Region, *fly.Region, error) { - return []fly.Region{ + ctx = flapsutil.NewContextWithClient(ctx, &mock.FlapsClient{ + GetRegionsFunc: func(ctx context.Context) (*flaps.RegionData, error) { + return &flaps.RegionData{Regions: []fly.Region{ {Code: "iad", Name: "Ashburn, Virginia (US)"}, - {Code: "lax", Name: "Los Angeles, California (US)"}, - {Code: "fra", Name: "Frankfurt, Germany"}, - }, &fly.Region{Code: "iad", Name: "Ashburn, Virginia (US)"}, nil + {Code: "lax", Name: "Los Angeles, California (US)", MPGAvailable: true}, + {Code: "fra", Name: "Frankfurt, Germany", MPGAvailable: true}, + }, Nearest: "iad"}, nil }, + }) + + mockClient := &mock.Client{ GenqClientFunc: func() genq.Client { return &mockGenqClient{} }, diff --git a/internal/command/launch/state.go b/internal/command/launch/state.go index 0757919a13..f6bbf00187 100644 --- a/internal/command/launch/state.go +++ b/internal/command/launch/state.go @@ -11,6 +11,7 @@ import ( "github.com/superfly/flyctl/gql" "github.com/superfly/flyctl/internal/appconfig" "github.com/superfly/flyctl/internal/command/launch/plan" + "github.com/superfly/flyctl/internal/flapsutil" "github.com/superfly/flyctl/internal/flyutil" "github.com/superfly/flyctl/iostreams" ) @@ -93,12 +94,13 @@ func (state *launchState) Org(ctx context.Context) (*fly.Organization, error) { } func (state *launchState) Region(ctx context.Context) (fly.Region, error) { - apiClient := flyutil.ClientFromContext(ctx) + flapsClient := flapsutil.ClientFromContext(ctx) regions, err := cacheGrab(state.cache, "regions", func() ([]fly.Region, error) { - regions, _, err := apiClient.PlatformRegions(ctx) + regionData, err := flapsClient.GetRegions(ctx) if err != nil { return nil, err } + regions := regionData.Regions // Filter out deprecated regions regions = lo.Filter(regions, func(r fly.Region, _ int) bool { return !r.Deprecated diff --git a/internal/command/machine/destroy.go b/internal/command/machine/destroy.go index 5a33cc08af..548840be52 100644 --- a/internal/command/machine/destroy.go +++ b/internal/command/machine/destroy.go @@ -11,7 +11,6 @@ import ( "github.com/superfly/flyctl/internal/command" "github.com/superfly/flyctl/internal/flag" "github.com/superfly/flyctl/internal/flapsutil" - "github.com/superfly/flyctl/internal/flyutil" mach "github.com/superfly/flyctl/internal/machine" "github.com/superfly/flyctl/internal/prompt" "github.com/superfly/flyctl/iostreams" @@ -141,8 +140,7 @@ func singleDestroyRun(ctx context.Context, machine *fly.Machine) error { appName := appconfig.NameFromContext(ctx) // This is used for the deletion hook below. - client := flyutil.ClientFromContext(ctx) - app, err := client.GetAppCompact(ctx, appName) + app, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, appName) if err != nil { return fmt.Errorf("could not get app '%s': %w", appName, err) } diff --git a/internal/command/machine/run.go b/internal/command/machine/run.go index 9cf4581176..dbe5114b88 100644 --- a/internal/command/machine/run.go +++ b/internal/command/machine/run.go @@ -381,10 +381,11 @@ func runMachineRun(ctx context.Context) error { } } - network, err := client.GetAppNetwork(ctx, app.Name) + flapsApp, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, app.Name) if err != nil { return err } + network := flapsApp.Network machineConf := &fly.MachineConfig{ AutoDestroy: destroy, @@ -478,7 +479,7 @@ func runMachineRun(ctx context.Context) error { } if interact { - _, dialer, err := agent.BringUpAgent(ctx, client, app, *network, false) + _, dialer, err := agent.BringUpAgent(ctx, client, app, network, false) if err != nil { return err } diff --git a/internal/command/ping/ping.go b/internal/command/ping/ping.go index db95ffe040..e25d456077 100644 --- a/internal/command/ping/ping.go +++ b/internal/command/ping/ping.go @@ -19,6 +19,7 @@ import ( "github.com/superfly/flyctl/internal/command" "github.com/superfly/flyctl/internal/command/dig" "github.com/superfly/flyctl/internal/flag" + "github.com/superfly/flyctl/internal/flapsutil" "github.com/superfly/flyctl/internal/flyutil" "golang.org/x/net/icmp" "golang.org/x/net/ipv6" @@ -101,7 +102,7 @@ func run(ctx context.Context) error { if orgSlug == "" { appName := appconfig.NameFromContext(ctx) - app, err := client.GetAppBasic(ctx, appName) + app, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, appName) if err != nil { return fmt.Errorf("get app: %w", err) } diff --git a/internal/command/postgres/add_flycast.go b/internal/command/postgres/add_flycast.go index d96b73683f..d0dbb0b05e 100644 --- a/internal/command/postgres/add_flycast.go +++ b/internal/command/postgres/add_flycast.go @@ -11,7 +11,7 @@ import ( "github.com/superfly/flyctl/internal/command" "github.com/superfly/flyctl/internal/command/apps" "github.com/superfly/flyctl/internal/flag" - "github.com/superfly/flyctl/internal/flyutil" + "github.com/superfly/flyctl/internal/flapsutil" mach "github.com/superfly/flyctl/internal/machine" "github.com/superfly/flyctl/internal/prompt" ) @@ -42,20 +42,19 @@ func newAddFlycast() *cobra.Command { func runAddFlycast(ctx context.Context) error { var ( - client = flyutil.ClientFromContext(ctx) appName = appconfig.NameFromContext(ctx) ) - app, err := client.GetAppCompact(ctx, appName) + app, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, appName) if err != nil { return fmt.Errorf("failed retrieving app %s: %w", appName, err) } - if !app.IsPostgresApp() { + if !flapsutil.IsPostgresApp(app) { return fmt.Errorf("app %s is not a postgres app", appName) } - ctx, err = apps.BuildContext(ctx, app) + ctx, err = apps.BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) if err != nil { return err } diff --git a/internal/command/postgres/attach.go b/internal/command/postgres/attach.go index 7471aa7800..9a2dda4d76 100644 --- a/internal/command/postgres/attach.go +++ b/internal/command/postgres/attach.go @@ -74,27 +74,27 @@ func newAttach() *cobra.Command { func runAttach(ctx context.Context) error { var ( - pgAppName = flag.FirstArg(ctx) - appName = appconfig.NameFromContext(ctx) - client = flyutil.ClientFromContext(ctx) + pgAppName = flag.FirstArg(ctx) + appName = appconfig.NameFromContext(ctx) + flapsClient = flapsutil.ClientFromContext(ctx) ) - pgApp, err := client.GetAppCompact(ctx, pgAppName) + pgApp, err := flapsClient.GetApp(ctx, pgAppName) if err != nil { return fmt.Errorf("failed retrieving postgres app %s: %w", pgAppName, err) } - if !pgApp.IsPostgresApp() { + if !flapsutil.IsPostgresApp(pgApp) { return fmt.Errorf("app %s is not a postgres app", pgAppName) } - app, err := client.GetAppCompact(ctx, appName) + app, err := flapsClient.GetApp(ctx, appName) if err != nil { return fmt.Errorf("failed retrieving app %s: %w", appName, err) } // Build context around the postgres app - ctx, err = apps.BuildContext(ctx, pgApp) + ctx, err = apps.BuildContextForNetwork(ctx, pgApp.Organization.Slug, pgApp.Network) if err != nil { return err } @@ -120,28 +120,28 @@ func runAttach(ctx context.Context) error { // AttachCluster is mean't to be called from an external package. func AttachCluster(ctx context.Context, params AttachParams) error { var ( - client = flyutil.ClientFromContext(ctx) + flapsClient = flapsutil.ClientFromContext(ctx) pgAppName = params.PgAppName appName = params.AppName ) - pgApp, err := client.GetAppCompact(ctx, pgAppName) + pgApp, err := flapsClient.GetApp(ctx, pgAppName) if err != nil { return fmt.Errorf("failed retrieving postgres app %s: %w", pgAppName, err) } - if !pgApp.IsPostgresApp() { + if !flapsutil.IsPostgresApp(pgApp) { return fmt.Errorf("app %s is not a postgres app", pgAppName) } - ctx, err = apps.BuildContext(ctx, pgApp) + ctx, err = apps.BuildContextForNetwork(ctx, pgApp.Organization.Slug, pgApp.Network) if err != nil { return err } // Verify that the target app exists. - _, err = client.GetAppBasic(ctx, appName) + _, err = flapsClient.GetApp(ctx, appName) if err != nil { return fmt.Errorf("failed retrieving app %s: %w", appName, err) } diff --git a/internal/command/postgres/backup.go b/internal/command/postgres/backup.go index 7e264473d1..ceb849c1ee 100644 --- a/internal/command/postgres/backup.go +++ b/internal/command/postgres/backup.go @@ -292,14 +292,14 @@ func runBackupEnable(ctx context.Context) error { client = flyutil.ClientFromContext(ctx) ) - app, err := client.GetAppCompact(ctx, appName) + flapsClient := flapsutil.ClientFromContext(ctx) + + app, err := flapsClient.GetApp(ctx, appName) if err != nil { return err } - flapsClient := flapsutil.ClientFromContext(ctx) - - if !app.IsPostgresApp() { + if !flapsutil.IsPostgresApp(app) { return fmt.Errorf("app %s is not a postgres app", appName) } diff --git a/internal/command/postgres/config_show.go b/internal/command/postgres/config_show.go index 449b704c71..dcad4d2177 100644 --- a/internal/command/postgres/config_show.go +++ b/internal/command/postgres/config_show.go @@ -7,14 +7,14 @@ import ( "strings" "github.com/spf13/cobra" - fly "github.com/superfly/fly-go" + "github.com/superfly/fly-go/flaps" "github.com/superfly/flyctl/agent" "github.com/superfly/flyctl/flypg" "github.com/superfly/flyctl/internal/appconfig" "github.com/superfly/flyctl/internal/command" "github.com/superfly/flyctl/internal/command/apps" "github.com/superfly/flyctl/internal/flag" - "github.com/superfly/flyctl/internal/flyutil" + "github.com/superfly/flyctl/internal/flapsutil" mach "github.com/superfly/flyctl/internal/machine" "github.com/superfly/flyctl/internal/render" "github.com/superfly/flyctl/iostreams" @@ -44,20 +44,19 @@ func newConfigShow() (cmd *cobra.Command) { func runConfigShow(ctx context.Context) error { var ( - client = flyutil.ClientFromContext(ctx) appName = appconfig.NameFromContext(ctx) ) - app, err := client.GetAppCompact(ctx, appName) + app, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, appName) if err != nil { return fmt.Errorf("failed retrieving app %s: %w", appName, err) } - if !app.IsPostgresApp() { + if !flapsutil.IsPostgresApp(app) { return fmt.Errorf("app %s is not a postgres app", appName) } - ctx, err = apps.BuildContext(ctx, app) + ctx, err = apps.BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) if err != nil { return err } @@ -65,14 +64,14 @@ func runConfigShow(ctx context.Context) error { return runMachineConfigShow(ctx, app) } -func runMachineConfigShow(ctx context.Context, app *fly.AppCompact) (err error) { +func runMachineConfigShow(ctx context.Context, app *flaps.App) (err error) { var ( MinPostgresHaVersion = "0.0.19" MinPostgresStandaloneVersion = "0.0.7" MinPostgresFlexVersion = "0.0.3" ) - ctx, err = apps.BuildContext(ctx, app) + ctx, err = apps.BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) if err != nil { return err } @@ -99,7 +98,7 @@ func runMachineConfigShow(ctx context.Context, app *fly.AppCompact) (err error) return showSettings(ctx, app, manager, leader.PrivateIP) } -func showSettings(ctx context.Context, app *fly.AppCompact, manager string, leaderIP string) error { +func showSettings(ctx context.Context, app *flaps.App, manager string, leaderIP string) error { var ( io = iostreams.FromContext(ctx) colorize = io.ColorScheme() diff --git a/internal/command/postgres/create.go b/internal/command/postgres/create.go index 1d9c041aee..25daa56550 100644 --- a/internal/command/postgres/create.go +++ b/internal/command/postgres/create.go @@ -104,10 +104,10 @@ func newCreate() *cobra.Command { // be safely passed through from other commands. func run(ctx context.Context) (err error) { var ( - appName = flag.GetString(ctx, "name") - client = flyutil.ClientFromContext(ctx) - io = iostreams.FromContext(ctx) - colorize = io.ColorScheme() + appName = flag.GetString(ctx, "name") + flapsClient = flapsutil.ClientFromContext(ctx) + io = iostreams.FromContext(ctx) + colorize = io.ColorScheme() ) // pre-fetch platform regions for later use @@ -170,17 +170,17 @@ func run(ctx context.Context) (err error) { } // Resolve specified fork-from app - forkApp, err := client.GetAppCompact(ctx, forkSlice[0]) + forkApp, err := flapsClient.GetApp(ctx, forkSlice[0]) if err != nil { return fmt.Errorf("Failed to resolve the specified fork-from app %s: %w", forkSlice[0], err) } // Confirm fork-app is a postgres app - if !forkApp.IsPostgresApp() { + if !flapsutil.IsPostgresApp(forkApp) { return fmt.Errorf("The fork-from app %q must be a postgres app", forkApp.Name) } - ctx, err := apps.BuildContext(ctx, forkApp) + ctx, err := apps.BuildContextForNetwork(ctx, forkApp.Organization.Slug, forkApp.Network) if err != nil { return err } diff --git a/internal/command/postgres/db.go b/internal/command/postgres/db.go index 39ac6a7998..ec472fd19d 100644 --- a/internal/command/postgres/db.go +++ b/internal/command/postgres/db.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/spf13/cobra" - fly "github.com/superfly/fly-go" + "github.com/superfly/fly-go/flaps" "github.com/superfly/flyctl/agent" "github.com/superfly/flyctl/flypg" "github.com/superfly/flyctl/internal/appconfig" @@ -14,7 +14,7 @@ import ( "github.com/superfly/flyctl/internal/command/apps" "github.com/superfly/flyctl/internal/config" "github.com/superfly/flyctl/internal/flag" - "github.com/superfly/flyctl/internal/flyutil" + "github.com/superfly/flyctl/internal/flapsutil" mach "github.com/superfly/flyctl/internal/machine" "github.com/superfly/flyctl/internal/render" "github.com/superfly/flyctl/iostreams" @@ -63,20 +63,19 @@ func newListDbs() *cobra.Command { func runListDbs(ctx context.Context) error { var ( - client = flyutil.ClientFromContext(ctx) appName = appconfig.NameFromContext(ctx) ) - app, err := client.GetAppCompact(ctx, appName) + app, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, appName) if err != nil { return fmt.Errorf("failed retrieving app %s: %w", appName, err) } - if !app.IsPostgresApp() { + if !flapsutil.IsPostgresApp(app) { return fmt.Errorf("app %s is not a postgres app", appName) } - ctx, err = apps.BuildContext(ctx, app) + ctx, err = apps.BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) if err != nil { return err } @@ -84,7 +83,7 @@ func runListDbs(ctx context.Context) error { return runMachineListDbs(ctx, app) } -func runMachineListDbs(ctx context.Context, app *fly.AppCompact) error { +func runMachineListDbs(ctx context.Context, app *flaps.App) error { var ( MinPostgresHaVersion = "0.0.19" MinPostgresFlexVersion = "0.0.3" diff --git a/internal/command/postgres/restart.go b/internal/command/postgres/restart.go index dbc0e489e0..6820d37383 100644 --- a/internal/command/postgres/restart.go +++ b/internal/command/postgres/restart.go @@ -12,7 +12,7 @@ import ( "github.com/superfly/flyctl/internal/command" "github.com/superfly/flyctl/internal/command/apps" "github.com/superfly/flyctl/internal/flag" - "github.com/superfly/flyctl/internal/flyutil" + "github.com/superfly/flyctl/internal/flapsutil" mach "github.com/superfly/flyctl/internal/machine" "github.com/superfly/flyctl/iostreams" ) @@ -50,19 +50,18 @@ func newRestart() *cobra.Command { func runRestart(ctx context.Context) error { var ( appName = appconfig.NameFromContext(ctx) - client = flyutil.ClientFromContext(ctx) ) - app, err := client.GetAppCompact(ctx, appName) + app, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, appName) if err != nil { return err } - if !app.IsPostgresApp() { + if !flapsutil.IsPostgresApp(app) { return fmt.Errorf("app %s is not a postgres app", appName) } - ctx, err = apps.BuildContext(ctx, app) + ctx, err = apps.BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) if err != nil { return err } diff --git a/internal/command/postgres/users.go b/internal/command/postgres/users.go index 8329c59b52..c7b3e93a89 100644 --- a/internal/command/postgres/users.go +++ b/internal/command/postgres/users.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/spf13/cobra" - fly "github.com/superfly/fly-go" + "github.com/superfly/fly-go/flaps" "github.com/superfly/flyctl/agent" "github.com/superfly/flyctl/flypg" "github.com/superfly/flyctl/internal/appconfig" @@ -14,7 +14,7 @@ import ( "github.com/superfly/flyctl/internal/command/apps" "github.com/superfly/flyctl/internal/config" "github.com/superfly/flyctl/internal/flag" - "github.com/superfly/flyctl/internal/flyutil" + "github.com/superfly/flyctl/internal/flapsutil" mach "github.com/superfly/flyctl/internal/machine" "github.com/superfly/flyctl/internal/render" "github.com/superfly/flyctl/iostreams" @@ -64,20 +64,19 @@ func newListUsers() *cobra.Command { func runListUsers(ctx context.Context) error { var ( - client = flyutil.ClientFromContext(ctx) appName = appconfig.NameFromContext(ctx) ) - app, err := client.GetAppCompact(ctx, appName) + app, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, appName) if err != nil { return fmt.Errorf("failed retrieving app %s: %w", appName, err) } - if !app.IsPostgresApp() { + if !flapsutil.IsPostgresApp(app) { return fmt.Errorf("app %s is not a postgres app", appName) } - ctx, err = apps.BuildContext(ctx, app) + ctx, err = apps.BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) if err != nil { return err } @@ -85,7 +84,7 @@ func runListUsers(ctx context.Context) error { return runMachineListUsers(ctx, app) } -func runMachineListUsers(ctx context.Context, app *fly.AppCompact) (err error) { +func runMachineListUsers(ctx context.Context, app *flaps.App) (err error) { // Minimum image version requirements var ( MinPostgresHaVersion = "0.0.19" diff --git a/internal/command/proxy/proxy.go b/internal/command/proxy/proxy.go index 82ba34c8ac..889604a083 100644 --- a/internal/command/proxy/proxy.go +++ b/internal/command/proxy/proxy.go @@ -13,6 +13,7 @@ import ( "github.com/superfly/flyctl/internal/command" "github.com/superfly/flyctl/internal/flag" "github.com/superfly/flyctl/internal/flag/flagnames" + "github.com/superfly/flyctl/internal/flapsutil" "github.com/superfly/flyctl/internal/flyutil" "github.com/superfly/flyctl/internal/prompt" "github.com/superfly/flyctl/iostreams" @@ -90,18 +91,14 @@ func run(ctx context.Context) (err error) { orgSlug = org.Slug } - network, err := client.GetAppNetwork(ctx, appName) - if err != nil { - return err - } - - // var app *fly.App + var network string if appName != "" { - app, err := client.GetAppBasic(ctx, appName) + app, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, appName) if err != nil { return err } orgSlug = app.Organization.Slug + network = app.Network } agentclient, err := agent.Establish(ctx, client) @@ -110,12 +107,12 @@ func run(ctx context.Context) (err error) { } // do this explicitly so we can get the DNS server address - _, err = agentclient.Establish(ctx, orgSlug, *network) + _, err = agentclient.Establish(ctx, orgSlug, network) if err != nil { return err } - dialer, err := agentclient.ConnectToTunnel(ctx, orgSlug, *network, flag.GetBool(ctx, "quiet")) + dialer, err := agentclient.ConnectToTunnel(ctx, orgSlug, network, flag.GetBool(ctx, "quiet")) if err != nil { return err } @@ -129,7 +126,7 @@ func run(ctx context.Context) (err error) { OrganizationSlug: orgSlug, Dialer: dialer, PromptInstance: promptInstance, - Network: *network, + Network: network, } if len(args) > 1 { diff --git a/internal/command/ssh/console.go b/internal/command/ssh/console.go index ff9a8b9fb9..ccb3c4a05d 100644 --- a/internal/command/ssh/console.go +++ b/internal/command/ssh/console.go @@ -175,12 +175,12 @@ func runConsole(ctx context.Context) error { return fmt.Errorf("get app: %w", err) } - network, err := client.GetAppNetwork(ctx, app.Name) + flapsApp, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, app.Name) if err != nil { return fmt.Errorf("get app network: %w", err) } - agentclient, dialer, err := agent.BringUpAgent(ctx, client, app, *network, quiet(ctx)) + agentclient, dialer, err := agent.BringUpAgent(ctx, client, app, flapsApp.Network, quiet(ctx)) if err != nil { return err } diff --git a/internal/command/ssh/sftp.go b/internal/command/ssh/sftp.go index 461641bf33..0349d5f910 100644 --- a/internal/command/ssh/sftp.go +++ b/internal/command/ssh/sftp.go @@ -19,6 +19,7 @@ import ( "github.com/superfly/flyctl/internal/appconfig" "github.com/superfly/flyctl/internal/command" "github.com/superfly/flyctl/internal/flag" + "github.com/superfly/flyctl/internal/flapsutil" "github.com/superfly/flyctl/internal/flyutil" "github.com/chzyer/readline" @@ -137,12 +138,12 @@ func newSFTPConnection(ctx context.Context) (*sftp.Client, error) { return nil, fmt.Errorf("get app: %w", err) } - network, err := client.GetAppNetwork(ctx, appName) + flapsApp, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, appName) if err != nil { return nil, fmt.Errorf("get app network: %w", err) } - agentclient, dialer, err := agent.BringUpAgent(ctx, client, app, *network, quiet(ctx)) + agentclient, dialer, err := agent.BringUpAgent(ctx, client, app, flapsApp.Network, quiet(ctx)) if err != nil { return nil, err } diff --git a/internal/command/tokens/list.go b/internal/command/tokens/list.go index f4e484df8f..20251bd609 100644 --- a/internal/command/tokens/list.go +++ b/internal/command/tokens/list.go @@ -10,6 +10,7 @@ import ( "github.com/superfly/flyctl/internal/command" "github.com/superfly/flyctl/internal/command/orgs" "github.com/superfly/flyctl/internal/flag" + "github.com/superfly/flyctl/internal/flapsutil" "github.com/superfly/flyctl/internal/flyutil" "github.com/superfly/flyctl/internal/render" "github.com/superfly/flyctl/iostreams" @@ -69,7 +70,7 @@ func runList(ctx context.Context) (err error) { // --org passed must match the selected app's org if orgFlag != "" { // Get app details, so we can identify its organization slug - app, err := apiClient.GetAppCompact(ctx, appName) + app, err := flapsutil.ClientFromContext(ctx).GetApp(ctx, appName) if err != nil { return fmt.Errorf("failed retrieving app %s: %w", appName, err) } diff --git a/internal/command/volumes/destroy.go b/internal/command/volumes/destroy.go index 58d6d3da9c..3b92db4ddd 100644 --- a/internal/command/volumes/destroy.go +++ b/internal/command/volumes/destroy.go @@ -61,11 +61,10 @@ func runDestroy(ctx context.Context) error { flapsClient := flapsutil.ClientFromContext(ctx) if len(volIDs) == 0 { - app, err := client.GetAppBasic(ctx, appName) - if err != nil { + if _, err := flapsClient.GetApp(ctx, appName); err != nil { return err } - volume, err := selectVolume(ctx, flapsClient, app) + volume, err := selectVolume(ctx, flapsClient, appName) if err != nil { return err } diff --git a/internal/command/volumes/extend.go b/internal/command/volumes/extend.go index ec3ca6101c..00de7e01c5 100644 --- a/internal/command/volumes/extend.go +++ b/internal/command/volumes/extend.go @@ -12,7 +12,6 @@ import ( "github.com/superfly/flyctl/internal/config" "github.com/superfly/flyctl/internal/flag" "github.com/superfly/flyctl/internal/flapsutil" - "github.com/superfly/flyctl/internal/flyutil" "github.com/superfly/flyctl/internal/render" "github.com/superfly/flyctl/iostreams" ) @@ -55,14 +54,12 @@ func runExtend(ctx context.Context) error { io = iostreams.FromContext(ctx) colorize = io.ColorScheme() appName = appconfig.NameFromContext(ctx) - client = flyutil.ClientFromContext(ctx) volID = flag.FirstArg(ctx) ) flapsClient := flapsutil.ClientFromContext(ctx) - app, err := client.GetAppBasic(ctx, appName) - if err != nil { + if _, err := flapsClient.GetApp(ctx, appName); err != nil { return err } @@ -85,7 +82,7 @@ func runExtend(ctx context.Context) error { } if volID == "" { - volume, err := selectVolume(ctx, flapsClient, app) + volume, err := selectVolume(ctx, flapsClient, appName) if err != nil { return err } diff --git a/internal/command/volumes/fork.go b/internal/command/volumes/fork.go index 970ace75d7..9ffb00a4c2 100644 --- a/internal/command/volumes/fork.go +++ b/internal/command/volumes/fork.go @@ -11,7 +11,6 @@ import ( "github.com/superfly/flyctl/internal/config" "github.com/superfly/flyctl/internal/flag" "github.com/superfly/flyctl/internal/flapsutil" - "github.com/superfly/flyctl/internal/flyutil" "github.com/superfly/flyctl/internal/render" "github.com/superfly/flyctl/iostreams" ) @@ -68,7 +67,6 @@ func runFork(ctx context.Context) error { cfg = config.FromContext(ctx) appName = appconfig.NameFromContext(ctx) volID = flag.FirstArg(ctx) - client = flyutil.ClientFromContext(ctx) ) flapsClient := flapsutil.ClientFromContext(ctx) @@ -78,12 +76,10 @@ func runFork(ctx context.Context) error { err error ) if volID == "" { - var app *fly.AppBasic - app, err = client.GetAppBasic(ctx, appName) - if err != nil { + if _, err = flapsClient.GetApp(ctx, appName); err != nil { return err } - vol, err = selectVolume(ctx, flapsClient, app) + vol, err = selectVolume(ctx, flapsClient, appName) if err != nil { return err } diff --git a/internal/command/volumes/list.go b/internal/command/volumes/list.go index 04d6e24332..ef1613035a 100644 --- a/internal/command/volumes/list.go +++ b/internal/command/volumes/list.go @@ -11,7 +11,6 @@ import ( "github.com/superfly/flyctl/internal/config" "github.com/superfly/flyctl/internal/flag" "github.com/superfly/flyctl/internal/flapsutil" - "github.com/superfly/flyctl/internal/flyutil" "github.com/superfly/flyctl/internal/render" "github.com/superfly/flyctl/iostreams" ) @@ -46,17 +45,16 @@ func newList() *cobra.Command { func runList(ctx context.Context) error { cfg := config.FromContext(ctx) - apiClient := flyutil.ClientFromContext(ctx) appName := appconfig.NameFromContext(ctx) - app, err := apiClient.GetAppBasic(ctx, appName) + flapsClient := flapsutil.ClientFromContext(ctx) + + _, err := flapsClient.GetApp(ctx, appName) if err != nil { return err } - flapsClient := flapsutil.ClientFromContext(ctx) - var volumes []fly.Volume if flag.GetBool(ctx, "all") { volumes, err = flapsClient.GetAllVolumes(ctx, appName) @@ -73,5 +71,5 @@ func runList(ctx context.Context) error { return render.JSON(out, volumes) } - return renderTable(ctx, volumes, app, out, true) + return renderTable(ctx, volumes, out, true) } diff --git a/internal/command/volumes/show.go b/internal/command/volumes/show.go index 0d1f8f9560..8cdb179639 100644 --- a/internal/command/volumes/show.go +++ b/internal/command/volumes/show.go @@ -64,12 +64,10 @@ func runShow(ctx context.Context) error { err error ) if volumeID == "" { - var app *fly.AppBasic - app, err = client.GetAppBasic(ctx, appName) - if err != nil { + if _, err = flapsClient.GetApp(ctx, appName); err != nil { return err } - volume, err = selectVolume(ctx, flapsClient, app) + volume, err = selectVolume(ctx, flapsClient, appName) if err != nil { return err } diff --git a/internal/command/volumes/volumes.go b/internal/command/volumes/volumes.go index ff453b0ced..ee2c6c6c0c 100644 --- a/internal/command/volumes/volumes.go +++ b/internal/command/volumes/volumes.go @@ -90,7 +90,7 @@ func countVolumesMatchingName(ctx context.Context, appName string, volumeName st return matches, nil } -func renderTable(ctx context.Context, volumes []fly.Volume, app *fly.AppBasic, out io.Writer, showHostStatus bool) error { +func renderTable(ctx context.Context, volumes []fly.Volume, out io.Writer, showHostStatus bool) error { rows := make([][]string, 0, len(volumes)) unreachableVolumes := false for _, volume := range volumes { @@ -129,19 +129,19 @@ func renderTable(ctx context.Context, volumes []fly.Volume, app *fly.AppBasic, o return nil } -func selectVolume(ctx context.Context, flapsClient flapsutil.FlapsClient, app *fly.AppBasic) (*fly.Volume, error) { +func selectVolume(ctx context.Context, flapsClient flapsutil.FlapsClient, appName string) (*fly.Volume, error) { if !iostreams.FromContext(ctx).IsInteractive() { return nil, fmt.Errorf("volume ID must be specified when not running interactively") } - volumes, err := flapsClient.GetVolumes(ctx, app.Name) + volumes, err := flapsClient.GetVolumes(ctx, appName) if err != nil { return nil, err } if len(volumes) == 0 { - return nil, fmt.Errorf("no volumes found in app '%s'", app.Name) + return nil, fmt.Errorf("no volumes found in app '%s'", appName) } out := new(bytes.Buffer) - err = renderTable(ctx, volumes, app, out, false) + err = renderTable(ctx, volumes, out, false) if err != nil { return nil, err } diff --git a/internal/flag/completion/completions.go b/internal/flag/completion/completions.go index bfc6829601..7822214eac 100644 --- a/internal/flag/completion/completions.go +++ b/internal/flag/completion/completions.go @@ -9,7 +9,9 @@ import ( "github.com/samber/lo" "github.com/spf13/cobra" fly "github.com/superfly/fly-go" + "github.com/superfly/fly-go/flaps" "github.com/superfly/flyctl/internal/flag/flagnames" + "github.com/superfly/flyctl/internal/flapsutil" "github.com/superfly/flyctl/internal/flyutil" ) @@ -19,41 +21,46 @@ func CompleteApps( args []string, partial string, ) ([]string, error) { - var ( - client = flyutil.ClientFromContext(ctx) + type appInfo struct { + name, orgName, status string + } - apps []fly.App - err error - ) + var apps []appInfo orgFiltered := false // We can't use `flag.*` here because of import cycles. *sigh* orgFlag := cmd.Flag(flagnames.Org) if orgFlag != nil && orgFlag.Changed { - var org *fly.Organization - org, err = client.GetOrganizationBySlug(ctx, orgFlag.Value.String()) + flapsClient := flapsutil.ClientFromContext(ctx) + flapsApps, err := flapsClient.ListApps(ctx, flaps.ListAppsRequest{OrgSlug: orgFlag.Value.String()}) if err != nil { return nil, err } - apps, err = client.GetAppsForOrganization(ctx, org.ID) + for _, app := range flapsApps { + apps = append(apps, appInfo{name: app.Name, orgName: app.Organization.Name, status: app.Status}) + } orgFiltered = true } else { - apps, err = client.GetApps(ctx, nil) - } - if err != nil { - return nil, err + client := flyutil.ClientFromContext(ctx) + gqlApps, err := client.GetApps(ctx, nil) + if err != nil { + return nil, err + } + for _, app := range gqlApps { + apps = append(apps, appInfo{name: app.Name, orgName: app.Organization.Name, status: app.Status}) + } } - ret := lo.FilterMap(apps, func(app fly.App, _ int) (string, bool) { - if strings.HasPrefix(app.Name, partial) { + ret := lo.FilterMap(apps, func(app appInfo, _ int) (string, bool) { + if strings.HasPrefix(app.name, partial) { var info []string if !orgFiltered { - info = append(info, app.Organization.Name) + info = append(info, app.orgName) } - info = append(info, app.Status) + info = append(info, app.status) - return fmt.Sprintf("%s\t%s", app.Name, strings.Join(info, ", ")), true + return fmt.Sprintf("%s\t%s", app.name, strings.Join(info, ", ")), true } return "", false @@ -97,17 +104,23 @@ func CompleteRegions( args []string, partial string, ) ([]string, error) { - client := flyutil.ClientFromContext(ctx) + flapsClient := flapsutil.ClientFromContext(ctx) format := func(org fly.Region) string { return fmt.Sprintf("%s\t%s", org.Code, org.Name) } // TODO(ali): Do we need to worry about which ones are marked as "gateway"? - regions, reqRegion, err := client.PlatformRegions(ctx) + regionData, err := flapsClient.GetRegions(ctx) if err != nil { return nil, err } + regions := regionData.Regions + + var reqRegion *fly.Region + if nearest, ok := lo.Find(regions, func(r fly.Region) bool { return r.Code == regionData.Nearest }); ok { + reqRegion = &nearest + } // Filter out deprecated regions regions = lo.Filter(regions, func(r fly.Region, _ int) bool { diff --git a/internal/flapsutil/app.go b/internal/flapsutil/app.go index afcd56d84c..1c24d161a0 100644 --- a/internal/flapsutil/app.go +++ b/internal/flapsutil/app.go @@ -2,6 +2,14 @@ package flapsutil import "github.com/superfly/fly-go/flaps" +// PostgresAppRole is the app role Flaps reports for Fly Postgres clusters. +const PostgresAppRole = "postgres_cluster" + +// IsPostgresApp reports whether app is a Fly Postgres cluster. +func IsPostgresApp(app *flaps.App) bool { + return app != nil && app.AppRole == PostgresAppRole +} + // DefaultNetwork is the name Flaps reports for an organization's default // network. The web API and WireGuard tunnels identify that network by an // empty name instead. diff --git a/internal/prompt/prompt.go b/internal/prompt/prompt.go index f6a3980bd0..acf44839ab 100644 --- a/internal/prompt/prompt.go +++ b/internal/prompt/prompt.go @@ -18,6 +18,7 @@ import ( fly "github.com/superfly/fly-go" "github.com/superfly/flyctl/internal/config" "github.com/superfly/flyctl/internal/flag" + "github.com/superfly/flyctl/internal/flapsutil" "github.com/superfly/flyctl/internal/flyutil" "github.com/superfly/flyctl/internal/future" "github.com/superfly/flyctl/internal/sort" @@ -324,11 +325,19 @@ var ( func PlatformRegions(ctx context.Context) *future.Future[RegionInfo] { regionsOnce.Do(func() { regionsFuture = future.Spawn(func() (RegionInfo, error) { - client := flyutil.ClientFromContext(ctx) - regions, defaultRegion, err := client.PlatformRegions(ctx) + flapsClient := flapsutil.ClientFromContext(ctx) + regionData, err := flapsClient.GetRegions(ctx) if err != nil { return RegionInfo{}, err } + regions := regionData.Regions + + var defaultRegion *fly.Region + if regionData.Nearest != "" { + if nearest, ok := lo.Find(regions, func(r fly.Region) bool { return r.Code == regionData.Nearest }); ok { + defaultRegion = &nearest + } + } // Filter out deprecated regions regions = lo.Filter(regions, func(r fly.Region, _ int) bool { From e17a50696efc7b3c37bb41c21e125aee9b42c878 Mon Sep 17 00:00:00 2001 From: lillian Date: Mon, 7 Sep 2026 18:49:14 +0000 Subject: [PATCH 2/2] Route Flaps app tunnels through the default-network mapping Use apps.BuildContextForApp and flapsutil.NetworkName at every site that opens an agent tunnel from a Flaps app, so apps on the org's default network get the empty network name the WireGuard API expects. tokens list compares the Flaps org slug against both the raw slug and the "personal" alias of the selected org. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01DMttEkUAVhL7YZFRFjhHwB --- internal/command/apps/restart.go | 2 +- internal/command/console/console.go | 2 +- internal/command/image/update.go | 2 +- internal/command/machine/run.go | 2 +- internal/command/postgres/add_flycast.go | 2 +- internal/command/postgres/attach.go | 4 ++-- internal/command/postgres/config_show.go | 4 ++-- internal/command/postgres/create.go | 2 +- internal/command/postgres/db.go | 2 +- internal/command/postgres/restart.go | 2 +- internal/command/postgres/users.go | 2 +- internal/command/proxy/proxy.go | 2 +- internal/command/ssh/console.go | 2 +- internal/command/ssh/sftp.go | 2 +- internal/command/tokens/list.go | 6 ++++-- 15 files changed, 20 insertions(+), 18 deletions(-) diff --git a/internal/command/apps/restart.go b/internal/command/apps/restart.go index 75527bddd4..f814b29f67 100644 --- a/internal/command/apps/restart.go +++ b/internal/command/apps/restart.go @@ -70,7 +70,7 @@ func runRestart(ctx context.Context) error { return fmt.Errorf("postgres apps should use `fly pg restart` instead") } - ctx, err = BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) + ctx, err = BuildContextForApp(ctx, app) if err != nil { return err } diff --git a/internal/command/console/console.go b/internal/command/console/console.go index b2d7cba4ed..bb4807421d 100644 --- a/internal/command/console/console.go +++ b/internal/command/console/console.go @@ -184,7 +184,7 @@ func runConsole(ctx context.Context) error { if err != nil { return fmt.Errorf("failed to get app network: %w", err) } - network := flapsApp.Network + network := flapsutil.NetworkName(flapsApp) appConfig := appconfig.ConfigFromContext(ctx) if appConfig == nil { diff --git a/internal/command/image/update.go b/internal/command/image/update.go index 77a7d2e1b0..5ffcf7a88a 100644 --- a/internal/command/image/update.go +++ b/internal/command/image/update.go @@ -56,7 +56,7 @@ func runUpdate(ctx context.Context) error { return fmt.Errorf("get app: %w", err) } - ctx, err = apps.BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) + ctx, err = apps.BuildContextForApp(ctx, app) if err != nil { return err } diff --git a/internal/command/machine/run.go b/internal/command/machine/run.go index dbe5114b88..b53546c13f 100644 --- a/internal/command/machine/run.go +++ b/internal/command/machine/run.go @@ -385,7 +385,7 @@ func runMachineRun(ctx context.Context) error { if err != nil { return err } - network := flapsApp.Network + network := flapsutil.NetworkName(flapsApp) machineConf := &fly.MachineConfig{ AutoDestroy: destroy, diff --git a/internal/command/postgres/add_flycast.go b/internal/command/postgres/add_flycast.go index d0dbb0b05e..de119e354e 100644 --- a/internal/command/postgres/add_flycast.go +++ b/internal/command/postgres/add_flycast.go @@ -54,7 +54,7 @@ func runAddFlycast(ctx context.Context) error { return fmt.Errorf("app %s is not a postgres app", appName) } - ctx, err = apps.BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) + ctx, err = apps.BuildContextForApp(ctx, app) if err != nil { return err } diff --git a/internal/command/postgres/attach.go b/internal/command/postgres/attach.go index 9a2dda4d76..dbf5761c16 100644 --- a/internal/command/postgres/attach.go +++ b/internal/command/postgres/attach.go @@ -94,7 +94,7 @@ func runAttach(ctx context.Context) error { } // Build context around the postgres app - ctx, err = apps.BuildContextForNetwork(ctx, pgApp.Organization.Slug, pgApp.Network) + ctx, err = apps.BuildContextForApp(ctx, pgApp) if err != nil { return err } @@ -135,7 +135,7 @@ func AttachCluster(ctx context.Context, params AttachParams) error { return fmt.Errorf("app %s is not a postgres app", pgAppName) } - ctx, err = apps.BuildContextForNetwork(ctx, pgApp.Organization.Slug, pgApp.Network) + ctx, err = apps.BuildContextForApp(ctx, pgApp) if err != nil { return err } diff --git a/internal/command/postgres/config_show.go b/internal/command/postgres/config_show.go index dcad4d2177..ecb2cfc026 100644 --- a/internal/command/postgres/config_show.go +++ b/internal/command/postgres/config_show.go @@ -56,7 +56,7 @@ func runConfigShow(ctx context.Context) error { return fmt.Errorf("app %s is not a postgres app", appName) } - ctx, err = apps.BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) + ctx, err = apps.BuildContextForApp(ctx, app) if err != nil { return err } @@ -71,7 +71,7 @@ func runMachineConfigShow(ctx context.Context, app *flaps.App) (err error) { MinPostgresFlexVersion = "0.0.3" ) - ctx, err = apps.BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) + ctx, err = apps.BuildContextForApp(ctx, app) if err != nil { return err } diff --git a/internal/command/postgres/create.go b/internal/command/postgres/create.go index 25daa56550..d973745612 100644 --- a/internal/command/postgres/create.go +++ b/internal/command/postgres/create.go @@ -180,7 +180,7 @@ func run(ctx context.Context) (err error) { return fmt.Errorf("The fork-from app %q must be a postgres app", forkApp.Name) } - ctx, err := apps.BuildContextForNetwork(ctx, forkApp.Organization.Slug, forkApp.Network) + ctx, err := apps.BuildContextForApp(ctx, forkApp) if err != nil { return err } diff --git a/internal/command/postgres/db.go b/internal/command/postgres/db.go index ec472fd19d..3c49a44335 100644 --- a/internal/command/postgres/db.go +++ b/internal/command/postgres/db.go @@ -75,7 +75,7 @@ func runListDbs(ctx context.Context) error { return fmt.Errorf("app %s is not a postgres app", appName) } - ctx, err = apps.BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) + ctx, err = apps.BuildContextForApp(ctx, app) if err != nil { return err } diff --git a/internal/command/postgres/restart.go b/internal/command/postgres/restart.go index 6820d37383..edd8c8c2e2 100644 --- a/internal/command/postgres/restart.go +++ b/internal/command/postgres/restart.go @@ -61,7 +61,7 @@ func runRestart(ctx context.Context) error { return fmt.Errorf("app %s is not a postgres app", appName) } - ctx, err = apps.BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) + ctx, err = apps.BuildContextForApp(ctx, app) if err != nil { return err } diff --git a/internal/command/postgres/users.go b/internal/command/postgres/users.go index c7b3e93a89..0e25244ca1 100644 --- a/internal/command/postgres/users.go +++ b/internal/command/postgres/users.go @@ -76,7 +76,7 @@ func runListUsers(ctx context.Context) error { return fmt.Errorf("app %s is not a postgres app", appName) } - ctx, err = apps.BuildContextForNetwork(ctx, app.Organization.Slug, app.Network) + ctx, err = apps.BuildContextForApp(ctx, app) if err != nil { return err } diff --git a/internal/command/proxy/proxy.go b/internal/command/proxy/proxy.go index 889604a083..0c9657eaf6 100644 --- a/internal/command/proxy/proxy.go +++ b/internal/command/proxy/proxy.go @@ -98,7 +98,7 @@ func run(ctx context.Context) (err error) { return err } orgSlug = app.Organization.Slug - network = app.Network + network = flapsutil.NetworkName(app) } agentclient, err := agent.Establish(ctx, client) diff --git a/internal/command/ssh/console.go b/internal/command/ssh/console.go index ccb3c4a05d..7ea95ef4d8 100644 --- a/internal/command/ssh/console.go +++ b/internal/command/ssh/console.go @@ -180,7 +180,7 @@ func runConsole(ctx context.Context) error { return fmt.Errorf("get app network: %w", err) } - agentclient, dialer, err := agent.BringUpAgent(ctx, client, app, flapsApp.Network, quiet(ctx)) + agentclient, dialer, err := agent.BringUpAgent(ctx, client, app, flapsutil.NetworkName(flapsApp), quiet(ctx)) if err != nil { return err } diff --git a/internal/command/ssh/sftp.go b/internal/command/ssh/sftp.go index 0349d5f910..b8b12f10e8 100644 --- a/internal/command/ssh/sftp.go +++ b/internal/command/ssh/sftp.go @@ -143,7 +143,7 @@ func newSFTPConnection(ctx context.Context) (*sftp.Client, error) { return nil, fmt.Errorf("get app network: %w", err) } - agentclient, dialer, err := agent.BringUpAgent(ctx, client, app, flapsApp.Network, quiet(ctx)) + agentclient, dialer, err := agent.BringUpAgent(ctx, client, app, flapsutil.NetworkName(flapsApp), quiet(ctx)) if err != nil { return nil, err } diff --git a/internal/command/tokens/list.go b/internal/command/tokens/list.go index 20251bd609..d9b5842a45 100644 --- a/internal/command/tokens/list.go +++ b/internal/command/tokens/list.go @@ -81,8 +81,10 @@ func runList(ctx context.Context) (err error) { return fmt.Errorf("failed retrieving org %w", err) } - // Throw an error if app's org slug does not match --org slug - if app.Organization.Slug != org.Slug { + // Throw an error if app's org slug does not match --org slug. + // Flaps reports the raw org slug, while the selected org may carry + // the "personal" alias. + if app.Organization.Slug != org.RawSlug && app.Organization.Slug != org.Slug { return fmt.Errorf("failed to retrieve tokens, selected application \"%s\" does not belong to selected organization \"%s\"", appName, org.Slug) } }