From bf61ca14c00d245c04fa90bb0c147bbe2c99e0c2 Mon Sep 17 00:00:00 2001 From: Akshit Garg Date: Thu, 15 Oct 2026 13:06:47 +0530 Subject: [PATCH 1/2] feat(mpg): add `fly mpg fork` - Fork a v2 Managed Postgres cluster into a new cluster through the Machines API (POST /v1/postgres/{id}/fork) - --name defaults to -fork; --plan is optional and, when left out, the fork keeps the source cluster's plan - Add ForkManagedPostgresCluster and ListManagedPostgresPlans to the FlapsClient interface and its mocks Needs a fly-go release with superfly/fly-go#322. Co-Authored-By: Claude Opus 5.5 --- internal/build/imgsrc/flaps_mock_test.go | 30 ++++ internal/command/deploy/mock_client_test.go | 8 ++ internal/command/mpg/fork.go | 66 +++++++++ internal/command/mpg/fork_test.go | 143 ++++++++++++++++++++ internal/command/mpg/mpg.go | 1 + internal/command/mpg/v2/run_fork.go | 38 ++++++ internal/command/mpg/v2/run_fork_test.go | 97 +++++++++++++ internal/flapsutil/flaps_client.go | 2 + internal/inmem/flaps_client.go | 8 ++ internal/mock/flaps_client.go | 10 ++ 10 files changed, 403 insertions(+) create mode 100644 internal/command/mpg/fork.go create mode 100644 internal/command/mpg/fork_test.go create mode 100644 internal/command/mpg/v2/run_fork.go create mode 100644 internal/command/mpg/v2/run_fork_test.go diff --git a/internal/build/imgsrc/flaps_mock_test.go b/internal/build/imgsrc/flaps_mock_test.go index b2a204d9a1..e04f4bcaa5 100644 --- a/internal/build/imgsrc/flaps_mock_test.go +++ b/internal/build/imgsrc/flaps_mock_test.go @@ -523,6 +523,21 @@ func (mr *MockFlapsClientMockRecorder) FindLease(ctx, appName, machineID any) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindLease", reflect.TypeOf((*MockFlapsClient)(nil).FindLease), ctx, appName, machineID) } +// ForkManagedPostgresCluster mocks base method. +func (m *MockFlapsClient) ForkManagedPostgresCluster(ctx context.Context, id string, req flaps.ForkManagedPostgresClusterRequest) (flaps.ManagedPostgresCluster, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ForkManagedPostgresCluster", ctx, id, req) + ret0, _ := ret[0].(flaps.ManagedPostgresCluster) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ForkManagedPostgresCluster indicates an expected call of ForkManagedPostgresCluster. +func (mr *MockFlapsClientMockRecorder) ForkManagedPostgresCluster(ctx, id, req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ForkManagedPostgresCluster", reflect.TypeOf((*MockFlapsClient)(nil).ForkManagedPostgresCluster), ctx, id, req) +} + // GenerateSecretKey mocks base method. func (m *MockFlapsClient) GenerateSecretKey(ctx context.Context, appName, name, typ string) (*fly.SetSecretKeyResp, error) { m.ctrl.T.Helper() @@ -943,6 +958,21 @@ func (mr *MockFlapsClientMockRecorder) ListManagedPostgresExtensions(ctx, id, da return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListManagedPostgresExtensions", reflect.TypeOf((*MockFlapsClient)(nil).ListManagedPostgresExtensions), ctx, id, database) } +// ListManagedPostgresPlans mocks base method. +func (m *MockFlapsClient) ListManagedPostgresPlans(ctx context.Context, orgSlug string) ([]flaps.ManagedPostgresPlan, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListManagedPostgresPlans", ctx, orgSlug) + ret0, _ := ret[0].([]flaps.ManagedPostgresPlan) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListManagedPostgresPlans indicates an expected call of ListManagedPostgresPlans. +func (mr *MockFlapsClientMockRecorder) ListManagedPostgresPlans(ctx, orgSlug any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListManagedPostgresPlans", reflect.TypeOf((*MockFlapsClient)(nil).ListManagedPostgresPlans), ctx, orgSlug) +} + // ListManagedPostgresUsers mocks base method. func (m *MockFlapsClient) ListManagedPostgresUsers(ctx context.Context, id string) ([]flaps.ManagedPostgresUser, error) { m.ctrl.T.Helper() diff --git a/internal/command/deploy/mock_client_test.go b/internal/command/deploy/mock_client_test.go index ecda5ab1a4..f576f94c4c 100644 --- a/internal/command/deploy/mock_client_test.go +++ b/internal/command/deploy/mock_client_test.go @@ -261,6 +261,10 @@ func (m *mockFlapsClient) FindLease(ctx context.Context, appName, machineID stri return nil, fmt.Errorf("failed to find lease for %s", machineID) } +func (m *mockFlapsClient) ForkManagedPostgresCluster(ctx context.Context, id string, req flaps.ForkManagedPostgresClusterRequest) (flaps.ManagedPostgresCluster, error) { + panic("not implemented") +} + func (m *mockFlapsClient) GenerateSecretKey(ctx context.Context, appName, name, typ string) (*fly.SetSecretKeyResp, error) { return nil, fmt.Errorf("failed to generate app secret %s", name) @@ -492,6 +496,10 @@ func (m *mockFlapsClient) ListManagedPostgresExtensions(ctx context.Context, id, return nil, fmt.Errorf("not implemented") } +func (m *mockFlapsClient) ListManagedPostgresPlans(ctx context.Context, orgSlug string) ([]flaps.ManagedPostgresPlan, error) { + return nil, fmt.Errorf("not implemented") +} + func (m *mockFlapsClient) ListAppSecrets(ctx context.Context, appName string, version *uint64, showSecrets bool) ([]fly.AppSecret, error) { return nil, fmt.Errorf("failed to list app secrets") } diff --git a/internal/command/mpg/fork.go b/internal/command/mpg/fork.go new file mode 100644 index 0000000000..1001056ff5 --- /dev/null +++ b/internal/command/mpg/fork.go @@ -0,0 +1,66 @@ +package mpg + +import ( + "context" + "fmt" + "strings" + + "github.com/spf13/cobra" + "github.com/superfly/flyctl/internal/command" + cmdv2 "github.com/superfly/flyctl/internal/command/mpg/v2" + "github.com/superfly/flyctl/internal/flag" + "github.com/superfly/flyctl/internal/uiex/mpg" +) + +func newFork() *cobra.Command { + const ( + long = `Fork a Managed Postgres cluster into a new cluster from the source's current +data, leaving the source cluster unchanged. The fork is provisioned +asynchronously in the same organization and billed separately. + +The fork uses the source cluster's plan unless --plan picks a different one, +including a smaller one.` + short = "Fork MPG cluster into a new cluster from its current data." + usage = "fork [CLUSTER_ID]" + ) + + cmd := command.New(usage, short, long, runFork, + command.RequireSession, + requireMacaroonToken, + ) + + cmd.Args = cobra.MaximumNArgs(1) + + flag.Add(cmd, + flag.String{ + Name: "name", + Shorthand: "n", + Description: "The name of the forked cluster (defaults to -fork)", + }, + flag.String{ + Name: "plan", + Description: "The plan for the forked cluster: " + planNames() + " (defaults to the source cluster's plan)", + }, + ) + + return cmd +} + +func runFork(ctx context.Context) error { + clusterID := flag.FirstArg(ctx) + name := flag.GetString(ctx, "name") + // The API is the source of truth for plans and matches them + // case-insensitively, so --plan is passed through rather than checked + // against MPGPlans. Leaving it empty keeps the source cluster's plan. + plan := strings.TrimSpace(flag.GetString(ctx, "plan")) + + cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") + if err != nil { + return err + } + if cluster.Version == mpg.VersionV1 { + return fmt.Errorf("forking is not supported for this cluster") + } + + return cmdv2.RunFork(ctx, cluster.Id, name, plan) +} diff --git a/internal/command/mpg/fork_test.go b/internal/command/mpg/fork_test.go new file mode 100644 index 0000000000..715f723c7b --- /dev/null +++ b/internal/command/mpg/fork_test.go @@ -0,0 +1,143 @@ +package mpg + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/superfly/fly-go/flaps" + "github.com/superfly/flyctl/internal/flag/flagctx" + "github.com/superfly/flyctl/internal/flapsutil" + "github.com/superfly/flyctl/internal/mock" + mpgv1 "github.com/superfly/flyctl/internal/uiex/mpg/v1" +) + +func TestRunFork(t *testing.T) { + const clusterID = "cluster-123" + + tests := []struct { + name string + clusterVersion int + args []string + wantErr string + wantFork bool + wantName string + wantPlan string + }{ + { + name: "rejects v1 cluster before request", + clusterVersion: 1, + args: []string{clusterID, "--plan", "basic"}, + wantErr: "forking is not supported for this cluster", + }, + { + name: "sends no plan when --plan is omitted", + clusterVersion: 2, + args: []string{clusterID}, + wantFork: true, + }, + { + name: "passes name and plan through", + clusterVersion: 2, + args: []string{clusterID, "--name", "forked-db", "--plan", "starter"}, + wantFork: true, + wantName: "forked-db", + wantPlan: "starter", + }, + { + name: "accepts -n shorthand for name", + clusterVersion: 2, + args: []string{clusterID, "-n", "forked-db"}, + wantFork: true, + wantName: "forked-db", + }, + { + name: "trims plan whitespace", + clusterVersion: 2, + args: []string{clusterID, "--plan", " launch "}, + wantFork: true, + wantPlan: "launch", + }, + { + name: "blank plan keeps the source plan", + clusterVersion: 2, + args: []string{clusterID, "--plan", " "}, + wantFork: true, + }, + { + name: "does not validate plan against the local plan list", + clusterVersion: 2, + args: []string{clusterID, "--plan", "PERFORMANCE"}, + wantFork: true, + wantPlan: "PERFORMANCE", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := setupTestContext() + cmd := newFork() + require.NoError(t, cmd.Flags().Parse(tt.args)) + ctx = flagctx.NewContext(ctx, cmd.Flags()) + + forkCalled := false + v1Client := &mock.MpgV1Client{ + GetManagedClusterByIdFunc: func(context.Context, string) (mpgv1.GetManagedClusterResponse, error) { + if tt.clusterVersion != 1 { + return mpgv1.GetManagedClusterResponse{}, errors.New("not found") + } + + return mpgv1.GetManagedClusterResponse{Data: mpgv1.ManagedCluster{Id: clusterID}}, nil + }, + } + publicClient := &mock.FlapsClient{ + GetManagedPostgresClusterFunc: func(context.Context, string) (flaps.ManagedPostgresCluster, error) { + if tt.clusterVersion != 2 { + return flaps.ManagedPostgresCluster{}, flaps.ErrFlapsNotFound + } + + return flaps.ManagedPostgresCluster{ID: clusterID, Name: "source-db"}, nil + }, + ForkManagedPostgresClusterFunc: func(_ context.Context, gotClusterID string, req flaps.ForkManagedPostgresClusterRequest) (flaps.ManagedPostgresCluster, error) { + forkCalled = true + assert.Equal(t, clusterID, gotClusterID) + assert.Equal(t, tt.wantName, req.Name) + assert.Equal(t, tt.wantPlan, req.Plan) + + return flaps.ManagedPostgresCluster{ID: "cluster-fork", Name: "source-db-fork", Plan: "basic"}, nil + }, + } + ctx = mpgv1.NewContextWithClient(ctx, v1Client) + ctx = flapsutil.NewContextWithClient(ctx, publicClient) + + err := runFork(ctx) + if tt.wantErr != "" { + require.EqualError(t, err, tt.wantErr) + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.wantFork, forkCalled) + }) + } +} + +func TestNewForkFlags(t *testing.T) { + cmd := newFork() + + require.Error(t, cmd.Args(cmd, []string{"a", "b"})) + require.NoError(t, cmd.Args(cmd, []string{})) + require.NoError(t, cmd.Args(cmd, []string{"a"})) + + name := cmd.Flags().Lookup("name") + require.NotNil(t, name) + assert.Equal(t, "n", name.Shorthand) + assert.Empty(t, name.DefValue) + + plan := cmd.Flags().Lookup("plan") + require.NotNil(t, plan) + assert.Empty(t, plan.DefValue) + assert.Contains(t, plan.Usage, planNames()) + assert.Contains(t, plan.Usage, "defaults to the source cluster's plan") +} diff --git a/internal/command/mpg/mpg.go b/internal/command/mpg/mpg.go index eef4d79b30..5c5882cfc7 100644 --- a/internal/command/mpg/mpg.go +++ b/internal/command/mpg/mpg.go @@ -45,6 +45,7 @@ func New() *cobra.Command { newDestroy(), newBackup(), newRestore(), + newFork(), newDatabases(), newUsers(), ) diff --git a/internal/command/mpg/v2/run_fork.go b/internal/command/mpg/v2/run_fork.go new file mode 100644 index 0000000000..2f4f921de3 --- /dev/null +++ b/internal/command/mpg/v2/run_fork.go @@ -0,0 +1,38 @@ +package cmdv2 + +import ( + "context" + "fmt" + + "github.com/superfly/fly-go/flaps" + "github.com/superfly/flyctl/internal/flapsutil" + "github.com/superfly/flyctl/iostreams" +) + +// RunFork forks clusterID into a new cluster. An empty name or plan is left +// out of the request so the API applies its defaults: the source's name with +// a -fork suffix, and the source cluster's plan. +func RunFork(ctx context.Context, clusterID, name, plan string) error { + out := iostreams.FromContext(ctx).Out + + fmt.Fprintf(out, "Forking cluster %s...\n", clusterID) + + request := flaps.ForkManagedPostgresClusterRequest{ + Name: name, + Plan: plan, + } + + response, err := flapsutil.ClientFromContext(ctx).ForkManagedPostgresCluster(ctx, clusterID, request) + if err != nil { + return fmt.Errorf("failed to fork cluster: %w", err) + } + + fmt.Fprintf(out, "Fork initiated successfully!\n") + fmt.Fprintf(out, " Cluster ID: %s\n", response.ID) + fmt.Fprintf(out, " Cluster Name: %s\n", response.Name) + if response.Plan != "" { + fmt.Fprintf(out, " Plan: %s\n", response.Plan) + } + + return nil +} diff --git a/internal/command/mpg/v2/run_fork_test.go b/internal/command/mpg/v2/run_fork_test.go new file mode 100644 index 0000000000..050cc1320d --- /dev/null +++ b/internal/command/mpg/v2/run_fork_test.go @@ -0,0 +1,97 @@ +package cmdv2 + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "testing" + + "github.com/stretchr/testify/require" + "github.com/superfly/fly-go/flaps" + "github.com/superfly/flyctl/internal/flapsutil" + "github.com/superfly/flyctl/internal/mock" + "github.com/superfly/flyctl/iostreams" +) + +func forkTestContext() (context.Context, *bytes.Buffer) { + io, _, stdout, _ := iostreams.Test() + + return iostreams.NewContext(context.Background(), io), stdout +} + +func TestRunForkPassesNameAndPlan(t *testing.T) { + ctx, stdout := forkTestContext() + called := false + ctx = flapsutil.NewContextWithClient(ctx, &mock.FlapsClient{ + ForkManagedPostgresClusterFunc: func(_ context.Context, id string, req flaps.ForkManagedPostgresClusterRequest) (flaps.ManagedPostgresCluster, error) { + called = true + require.Equal(t, "mpg-source", id) + require.Equal(t, "forked-db", req.Name) + require.Equal(t, "starter", req.Plan) + + return flaps.ManagedPostgresCluster{ID: "mpg-fork", Name: "forked-db", Plan: "starter"}, nil + }, + }) + + require.NoError(t, RunFork(ctx, "mpg-source", "forked-db", "starter")) + require.True(t, called) + require.Contains(t, stdout.String(), "Forking cluster mpg-source...") + require.Contains(t, stdout.String(), "Fork initiated successfully!") + require.Contains(t, stdout.String(), "Cluster ID: mpg-fork") + require.Contains(t, stdout.String(), "Cluster Name: forked-db") + require.Contains(t, stdout.String(), "Plan: starter") +} + +func TestRunForkWithoutPlanOmitsPlan(t *testing.T) { + ctx, stdout := forkTestContext() + var captured flaps.ForkManagedPostgresClusterRequest + ctx = flapsutil.NewContextWithClient(ctx, &mock.FlapsClient{ + ForkManagedPostgresClusterFunc: func(_ context.Context, _ string, req flaps.ForkManagedPostgresClusterRequest) (flaps.ManagedPostgresCluster, error) { + captured = req + + // The API copies the source cluster's plan when none is given. + return flaps.ManagedPostgresCluster{ID: "mpg-fork", Name: "source-db-fork", Plan: "launch"}, nil + }, + }) + + require.NoError(t, RunFork(ctx, "mpg-source", "", "")) + require.Empty(t, captured.Plan) + require.Empty(t, captured.Name) + + body, err := json.Marshal(captured) + require.NoError(t, err) + require.JSONEq(t, `{}`, string(body)) + + require.Contains(t, stdout.String(), "Cluster ID: mpg-fork") + require.Contains(t, stdout.String(), "Cluster Name: source-db-fork") + require.Contains(t, stdout.String(), "Plan: launch") +} + +func TestRunForkWrapsError(t *testing.T) { + ctx, stdout := forkTestContext() + publicErr := &flaps.FlapsError{ResponseStatusCode: 422, OriginalError: errors.New("plan is invalid")} + ctx = flapsutil.NewContextWithClient(ctx, &mock.FlapsClient{ + ForkManagedPostgresClusterFunc: func(context.Context, string, flaps.ForkManagedPostgresClusterRequest) (flaps.ManagedPostgresCluster, error) { + return flaps.ManagedPostgresCluster{}, publicErr + }, + }) + + err := RunFork(ctx, "mpg-source", "", "nope") + require.ErrorIs(t, err, publicErr) + require.EqualError(t, err, "failed to fork cluster: plan is invalid") + require.NotContains(t, stdout.String(), "Fork initiated successfully!") +} + +func TestRunForkDoesNotFallBackOnPublic404(t *testing.T) { + ctx, _ := forkTestContext() + ctx = flapsutil.NewContextWithClient(ctx, &mock.FlapsClient{ + ForkManagedPostgresClusterFunc: func(context.Context, string, flaps.ForkManagedPostgresClusterRequest) (flaps.ManagedPostgresCluster, error) { + return flaps.ManagedPostgresCluster{}, flaps.ErrFlapsNotFound + }, + }) + + err := RunFork(ctx, "mpg-missing", "", "") + require.ErrorIs(t, err, flaps.ErrFlapsNotFound) + require.ErrorContains(t, err, "failed to fork cluster:") +} diff --git a/internal/flapsutil/flaps_client.go b/internal/flapsutil/flaps_client.go index e67572454b..d72f184bd1 100644 --- a/internal/flapsutil/flaps_client.go +++ b/internal/flapsutil/flaps_client.go @@ -43,6 +43,7 @@ type FlapsClient interface { Exec(ctx context.Context, appName, machineID string, in *fly.MachineExecRequest) (*fly.MachineExecResponse, error) ExtendVolume(ctx context.Context, appName, volumeId string, size_gb int) (*fly.Volume, bool, error) FindLease(ctx context.Context, appName, machineID string) (*fly.MachineLease, error) + ForkManagedPostgresCluster(ctx context.Context, id string, req flaps.ForkManagedPostgresClusterRequest) (flaps.ManagedPostgresCluster, error) GenerateSecretKey(ctx context.Context, appName, name string, typ string) (*fly.SetSecretKeyResp, error) Get(ctx context.Context, appName, machineID string) (*fly.Machine, error) GetApp(ctx context.Context, name string) (app *flaps.App, err error) @@ -73,6 +74,7 @@ type FlapsClient interface { ListManagedPostgresUsers(ctx context.Context, id string) ([]flaps.ManagedPostgresUser, error) ListManagedPostgresBackups(ctx context.Context, id string) ([]flaps.ManagedPostgresBackup, error) ListManagedPostgresExtensions(ctx context.Context, id, database string) ([]flaps.ManagedPostgresExtension, error) + ListManagedPostgresPlans(ctx context.Context, orgSlug string) ([]flaps.ManagedPostgresPlan, error) ListSecretKeys(ctx context.Context, appName string, version *uint64) ([]fly.SecretKey, error) NewRequest(ctx context.Context, method, path string, in any, headers map[string][]string) (*http.Request, error) RefreshLease(ctx context.Context, appName, machineID string, ttl *int, nonce string) (*fly.MachineLease, error) diff --git a/internal/inmem/flaps_client.go b/internal/inmem/flaps_client.go index 3102c2a834..d44c284448 100644 --- a/internal/inmem/flaps_client.go +++ b/internal/inmem/flaps_client.go @@ -128,6 +128,10 @@ func (m *FlapsClient) FindLease(ctx context.Context, appName, machineID string) panic("TODO") } +func (m *FlapsClient) ForkManagedPostgresCluster(ctx context.Context, id string, req flaps.ForkManagedPostgresClusterRequest) (flaps.ManagedPostgresCluster, error) { + panic("TODO") +} + func (m *FlapsClient) GenerateSecretKey(ctx context.Context, appName, name string, typ string) (*fly.SetSecretKeyResp, error) { panic("TODO") } @@ -307,6 +311,10 @@ func (m *FlapsClient) ListManagedPostgresExtensions(ctx context.Context, id, dat panic("TODO") } +func (m *FlapsClient) ListManagedPostgresPlans(ctx context.Context, orgSlug string) ([]flaps.ManagedPostgresPlan, error) { + panic("TODO") +} + func (m *FlapsClient) ListSecretKeys(ctx context.Context, appName string, version *uint64) ([]fly.SecretKey, error) { panic("TODO") } diff --git a/internal/mock/flaps_client.go b/internal/mock/flaps_client.go index f14f575d98..1d4402d1b7 100644 --- a/internal/mock/flaps_client.go +++ b/internal/mock/flaps_client.go @@ -44,6 +44,7 @@ type FlapsClient struct { ExecFunc func(ctx context.Context, appName, machineID string, in *fly.MachineExecRequest) (*fly.MachineExecResponse, error) ExtendVolumeFunc func(ctx context.Context, appName, volumeId string, size_gb int) (*fly.Volume, bool, error) FindLeaseFunc func(ctx context.Context, appName, machineID string) (*fly.MachineLease, error) + ForkManagedPostgresClusterFunc func(ctx context.Context, id string, req flaps.ForkManagedPostgresClusterRequest) (flaps.ManagedPostgresCluster, error) GenerateSecretKeyFunc func(ctx context.Context, appName, name string, typ string) (*fly.SetSecretKeyResp, error) GetFunc func(ctx context.Context, appName, machineID string) (*fly.Machine, error) GetAppFunc func(ctx context.Context, name string) (app *flaps.App, err error) @@ -74,6 +75,7 @@ type FlapsClient struct { ListManagedPostgresUsersFunc func(ctx context.Context, id string) ([]flaps.ManagedPostgresUser, error) ListManagedPostgresBackupsFunc func(ctx context.Context, id string) ([]flaps.ManagedPostgresBackup, error) ListManagedPostgresExtensionsFunc func(ctx context.Context, id, database string) ([]flaps.ManagedPostgresExtension, error) + ListManagedPostgresPlansFunc func(ctx context.Context, orgSlug string) ([]flaps.ManagedPostgresPlan, error) ListSecretKeysFunc func(ctx context.Context, appName string, version *uint64) ([]fly.SecretKey, error) NewRequestFunc func(ctx context.Context, method, path string, in any, headers map[string][]string) (*http.Request, error) RefreshLeaseFunc func(ctx context.Context, appName, machineID string, ttl *int, nonce string) (*fly.MachineLease, error) @@ -228,6 +230,10 @@ func (m *FlapsClient) FindLease(ctx context.Context, appName, machineID string) return m.FindLeaseFunc(ctx, appName, machineID) } +func (m *FlapsClient) ForkManagedPostgresCluster(ctx context.Context, id string, req flaps.ForkManagedPostgresClusterRequest) (flaps.ManagedPostgresCluster, error) { + return m.ForkManagedPostgresClusterFunc(ctx, id, req) +} + func (m *FlapsClient) GenerateSecretKey(ctx context.Context, appName, name string, typ string) (*fly.SetSecretKeyResp, error) { return m.GenerateSecretKeyFunc(ctx, appName, name, typ) } @@ -348,6 +354,10 @@ func (m *FlapsClient) ListManagedPostgresExtensions(ctx context.Context, id, dat return m.ListManagedPostgresExtensionsFunc(ctx, id, database) } +func (m *FlapsClient) ListManagedPostgresPlans(ctx context.Context, orgSlug string) ([]flaps.ManagedPostgresPlan, error) { + return m.ListManagedPostgresPlansFunc(ctx, orgSlug) +} + func (m *FlapsClient) ListSecretKeys(ctx context.Context, appName string, version *uint64) ([]fly.SecretKey, error) { return m.ListSecretKeysFunc(ctx, appName, version) } From da4305bae4d70e59b77eb1391d9cf70aaee12ab9 Mon Sep 17 00:00:00 2001 From: Akshit Garg Date: Thu, 15 Oct 2026 19:11:37 +0530 Subject: [PATCH 2/2] chore(deps): bump fly-go to v0.11.5 Co-Authored-By: Claude Opus 5.5 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 29a4b5f472..11ab4d272b 100644 --- a/go.mod +++ b/go.mod @@ -76,7 +76,7 @@ require ( github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.12.1 github.com/superfly/client-signals/go v0.4.4 - github.com/superfly/fly-go v0.11.3 + github.com/superfly/fly-go v0.11.5 github.com/superfly/graphql v0.2.6 github.com/superfly/macaroon v0.3.2 github.com/superfly/tokenizer v0.0.3-0.20240826174224-a17a2e0a9dc0 diff --git a/go.sum b/go.sum index 2be7620a7b..6695c5a9e4 100644 --- a/go.sum +++ b/go.sum @@ -662,8 +662,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/superfly/client-signals/go v0.4.4 h1:btAouksYdwOkVCIqI/JI09bt17A6iIVuwVJGWndfmc8= github.com/superfly/client-signals/go v0.4.4/go.mod h1:FTpkC1/boj2Lez8w98k9Zs9ihtvz8a1VeGXDtaq7asY= -github.com/superfly/fly-go v0.11.3 h1:EFSOoyVAjO6bP2GVXssItA0voBcvjf47/jOUW6+SCfo= -github.com/superfly/fly-go v0.11.3/go.mod h1:88plLE+2q2fBSanY75Hxy6uoUomXzA5Z1RY398okErQ= +github.com/superfly/fly-go v0.11.5 h1:wn1vP8Zz3J9x1OEJG3IxIw213eq+4tBuR4tngj7atVo= +github.com/superfly/fly-go v0.11.5/go.mod h1:88plLE+2q2fBSanY75Hxy6uoUomXzA5Z1RY398okErQ= github.com/superfly/graphql v0.2.6 h1:zppbodNerWecoXEdjkhrqaNaSjGqobhXNlViHFuZzb4= github.com/superfly/graphql v0.2.6/go.mod h1:CVfDl31srm8HnJ9udwLu6hFNUW/P6GUM2dKcG1YQ8jc= github.com/superfly/macaroon v0.3.2 h1:vrPnUZUsd0DkSO6ievTESgGE1B3LJE3trXnDYZU1KTc=