diff --git a/internal/command/mpg/attach.go b/internal/command/mpg/attach.go index 399b0ee670..8a3a9ab5b1 100644 --- a/internal/command/mpg/attach.go +++ b/internal/command/mpg/attach.go @@ -8,8 +8,10 @@ import ( "github.com/superfly/flyctl/internal/appconfig" "github.com/superfly/flyctl/internal/command" cmdv1 "github.com/superfly/flyctl/internal/command/mpg/v1" + cmdv2 "github.com/superfly/flyctl/internal/command/mpg/v2" "github.com/superfly/flyctl/internal/flag" "github.com/superfly/flyctl/internal/flyutil" + "github.com/superfly/flyctl/internal/uiex/mpg" "github.com/superfly/flyctl/iostreams" ) @@ -85,5 +87,9 @@ func runAttach(ctx context.Context) error { appName, appOrgSlug, cluster.Id, clusterOrgSlug) } - return cmdv1.RunAttach(ctx, cluster.Id) + if cluster.Version == mpg.VersionV1 { + return cmdv1.RunAttach(ctx, cluster.Id) + } + + return cmdv2.RunAttach(ctx, cluster.Id) } diff --git a/internal/command/mpg/backup.go b/internal/command/mpg/backup.go index 952bc8d808..dc5181c2db 100644 --- a/internal/command/mpg/backup.go +++ b/internal/command/mpg/backup.go @@ -6,7 +6,9 @@ import ( "github.com/spf13/cobra" "github.com/superfly/flyctl/internal/command" cmdv1 "github.com/superfly/flyctl/internal/command/mpg/v1" + cmdv2 "github.com/superfly/flyctl/internal/command/mpg/v2" "github.com/superfly/flyctl/internal/flag" + "github.com/superfly/flyctl/internal/uiex/mpg" ) func newBackup() *cobra.Command { @@ -54,16 +56,16 @@ func newBackupList() *cobra.Command { func runBackupList(ctx context.Context) error { clusterID := flag.FirstArg(ctx) - if clusterID == "" { - cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") - if err != nil { - return err - } + cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") + if err != nil { + return err + } - clusterID = cluster.Id + if cluster.Version == mpg.VersionV1 { + return cmdv1.RunBackupList(ctx, cluster.Id) } - return cmdv1.RunBackupList(ctx, clusterID) + return cmdv2.RunBackupList(ctx, cluster.Id) } func newBackupCreate() *cobra.Command { @@ -92,14 +94,15 @@ func newBackupCreate() *cobra.Command { func runBackupCreate(ctx context.Context) error { clusterID := flag.FirstArg(ctx) - if clusterID == "" { - cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") - if err != nil { - return err - } - clusterID = cluster.Id + cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") + if err != nil { + return err + } + + if cluster.Version == mpg.VersionV1 { + return cmdv1.RunBackupCreate(ctx, cluster.Id) } - return cmdv1.RunBackupCreate(ctx, clusterID) + return cmdv2.RunBackupCreate(ctx, clusterID) } diff --git a/internal/command/mpg/connect.go b/internal/command/mpg/connect.go index 2e24dc8f06..b3a5a9c6a7 100644 --- a/internal/command/mpg/connect.go +++ b/internal/command/mpg/connect.go @@ -7,8 +7,13 @@ import ( "github.com/spf13/cobra" "github.com/superfly/flyctl/internal/command" cmdv1 "github.com/superfly/flyctl/internal/command/mpg/v1" + cmdv2 "github.com/superfly/flyctl/internal/command/mpg/v2" "github.com/superfly/flyctl/internal/flag" - mpgv1 "github.com/superfly/flyctl/internal/uiex/mpg/v1" + "github.com/superfly/flyctl/internal/uiex/mpg" +) + +const ( + localProxyPort = "16380" ) func newConnect() (cmd *cobra.Command) { @@ -40,24 +45,12 @@ func newConnect() (cmd *cobra.Command) { func runConnect(ctx context.Context) (err error) { clusterID := flag.FirstArg(ctx) + var orgSlug string - if clusterID != "" { - // If cluster ID is provided, fetch directly without prompting for org - mpgClient := mpgv1.ClientFromContext(ctx) - response, err := mpgClient.GetManagedClusterById(ctx, clusterID) - if err != nil { - return fmt.Errorf("failed retrieving cluster %s: %w", clusterID, err) - } - orgSlug = response.Data.Organization.Slug - } else { - // Otherwise, prompt for org/cluster selection - cluster, resolvedOrgSlug, err := ClusterFromArgOrSelect(ctx, clusterID, "") - if err != nil { - return err - } - clusterID = cluster.Id - orgSlug = resolvedOrgSlug + cluster, orgSlug, err := ClusterFromArgOrSelect(ctx, clusterID, "") + if err != nil { + return err } // Resolve org slug alias for wireguard tunnel @@ -66,5 +59,9 @@ func runConnect(ctx context.Context) (err error) { return fmt.Errorf("failed to resolve organization slug: %w", err) } - return cmdv1.RunConnect(ctx, clusterID, resolvedOrgSlug) + if cluster.Version == mpg.VersionV1 { + return cmdv1.RunConnect(ctx, cluster.Id, resolvedOrgSlug) + } + + return cmdv2.RunConnect(ctx, cluster.Id, resolvedOrgSlug, localProxyPort) } diff --git a/internal/command/mpg/databases.go b/internal/command/mpg/databases.go index a6f0a8ab69..5888fdf5f8 100644 --- a/internal/command/mpg/databases.go +++ b/internal/command/mpg/databases.go @@ -6,7 +6,9 @@ import ( "github.com/spf13/cobra" "github.com/superfly/flyctl/internal/command" cmdv1 "github.com/superfly/flyctl/internal/command/mpg/v1" + cmdv2 "github.com/superfly/flyctl/internal/command/mpg/v2" "github.com/superfly/flyctl/internal/flag" + "github.com/superfly/flyctl/internal/uiex/mpg" ) func newDatabases() *cobra.Command { @@ -47,16 +49,16 @@ func newDatabasesList() *cobra.Command { func runDatabasesList(ctx context.Context) error { clusterID := flag.FirstArg(ctx) - if clusterID == "" { - cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") - if err != nil { - return err - } + cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") + if err != nil { + return err + } - clusterID = cluster.Id + if cluster.Version == mpg.VersionV1 { + return cmdv1.RunDatabasesList(ctx, clusterID) } - return cmdv1.RunDatabasesList(ctx, clusterID) + return cmdv2.RunDatabasesList(ctx, clusterID) } func newDatabasesCreate() *cobra.Command { @@ -85,14 +87,14 @@ func newDatabasesCreate() *cobra.Command { func runDatabasesCreate(ctx context.Context) error { clusterID := flag.FirstArg(ctx) - if clusterID == "" { - cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") - if err != nil { - return err - } + cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") + if err != nil { + return err + } - clusterID = cluster.Id + if cluster.Version == mpg.VersionV1 { + return cmdv1.RunDatabasesCreate(ctx, cluster.Id) } - return cmdv1.RunDatabasesCreate(ctx, clusterID) + return cmdv2.RunDatabasesCreate(ctx, cluster.Id) } diff --git a/internal/command/mpg/destroy.go b/internal/command/mpg/destroy.go index 6df3a6b88c..63ab3892d0 100644 --- a/internal/command/mpg/destroy.go +++ b/internal/command/mpg/destroy.go @@ -6,7 +6,9 @@ import ( "github.com/spf13/cobra" "github.com/superfly/flyctl/internal/command" cmdv1 "github.com/superfly/flyctl/internal/command/mpg/v1" + cmdv2 "github.com/superfly/flyctl/internal/command/mpg/v2" "github.com/superfly/flyctl/internal/flag" + "github.com/superfly/flyctl/internal/uiex/mpg" ) func newDestroy() *cobra.Command { @@ -32,7 +34,15 @@ This action is not reversible.` } func runDestroy(ctx context.Context) error { - clusterId := flag.FirstArg(ctx) + clusterID := flag.FirstArg(ctx) + cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") + if err != nil { + return err + } - return cmdv1.RunDestroy(ctx, clusterId) + if cluster.Version == mpg.VersionV1 { + return cmdv1.RunDestroy(ctx, cluster.Id) + } + + return cmdv2.RunDestroy(ctx, cluster.Id) } diff --git a/internal/command/mpg/detach.go b/internal/command/mpg/detach.go index 8ad9c0e12c..ebfadb9292 100644 --- a/internal/command/mpg/detach.go +++ b/internal/command/mpg/detach.go @@ -8,8 +8,10 @@ import ( "github.com/superfly/flyctl/internal/appconfig" "github.com/superfly/flyctl/internal/command" cmdv1 "github.com/superfly/flyctl/internal/command/mpg/v1" + cmdv2 "github.com/superfly/flyctl/internal/command/mpg/v2" "github.com/superfly/flyctl/internal/flag" "github.com/superfly/flyctl/internal/flyutil" + "github.com/superfly/flyctl/internal/uiex/mpg" "github.com/superfly/flyctl/iostreams" ) @@ -69,5 +71,9 @@ func runDetach(ctx context.Context) error { appName, appOrgSlug, cluster.Id, clusterOrgSlug) } - return cmdv1.RunDetach(ctx, cluster.Id, appName) + if cluster.Version == mpg.VersionV1 { + return cmdv1.RunDetach(ctx, cluster.Id, appName) + } + + return cmdv2.RunDetach(ctx, cluster.Id, appName) } diff --git a/internal/command/mpg/list.go b/internal/command/mpg/list.go index 4928424c17..0781ee8805 100644 --- a/internal/command/mpg/list.go +++ b/internal/command/mpg/list.go @@ -8,7 +8,7 @@ import ( cmdv1 "github.com/superfly/flyctl/internal/command/mpg/v1" "github.com/superfly/flyctl/internal/command/orgs" "github.com/superfly/flyctl/internal/flag" - mpgv1 "github.com/superfly/flyctl/internal/uiex/mpg/v1" + mpg "github.com/superfly/flyctl/internal/uiex/mpg" ) func newList() *cobra.Command { @@ -42,11 +42,12 @@ func runList(ctx context.Context) error { return err } + // TODO: Move out of cmdv1 and list all across v1 and v2 return cmdv1.RunList(ctx, org.Slug) } // formatAttachedApps formats the list of attached apps for display. // Delegates to the v1 implementation. -func formatAttachedApps(apps []mpgv1.AttachedApp) string { +func formatAttachedApps(apps []mpg.AttachedApp) string { return cmdv1.FormatAttachedApps(apps) } diff --git a/internal/command/mpg/mpg.go b/internal/command/mpg/mpg.go index 6e701ee22e..6d01219ace 100644 --- a/internal/command/mpg/mpg.go +++ b/internal/command/mpg/mpg.go @@ -12,6 +12,7 @@ import ( "github.com/superfly/flyctl/internal/flag" "github.com/superfly/flyctl/internal/flyutil" "github.com/superfly/flyctl/internal/prompt" + "github.com/superfly/flyctl/internal/uiex/mpg" mpgv1 "github.com/superfly/flyctl/internal/uiex/mpg/v1" ) @@ -104,9 +105,10 @@ func New() *cobra.Command { // otherwise it prompts the user to select a cluster from the available ones for // the given organization. // It prompts for the org if the org slug is not provided. -func ClusterFromArgOrSelect(ctx context.Context, clusterID, orgSlug string) (*mpgv1.ManagedCluster, string, error) { - mpgClient := mpgv1.ClientFromContext(ctx) - +func ClusterFromArgOrSelect(ctx context.Context, clusterID, orgSlug string) (*mpg.Cluster, string, error) { + // If a cluster ID is provided, fetch the cluster directly + // TODO + if orgSlug == "" { org, err := prompt.Org(ctx) if err != nil { @@ -116,44 +118,71 @@ func ClusterFromArgOrSelect(ctx context.Context, clusterID, orgSlug string) (*mp orgSlug = org.RawSlug } - clustersResponse, err := mpgClient.ListManagedClusters(ctx, orgSlug, false) + // Fetch V1 clusters + mpgv1Client := mpgv1.ClientFromContext(ctx) + clustersResponse, err := mpgv1Client.ListManagedClusters(ctx, orgSlug, false) if err != nil { return nil, orgSlug, fmt.Errorf("failed retrieving postgres clusters: %w", err) } - if len(clustersResponse.Data) == 0 { - return nil, orgSlug, fmt.Errorf("no managed postgres clusters found in organization %s", orgSlug) + // // Fetch V2 clusters + // v2Client := mpgv2.Client{ + // Client: uiexClient, + // } + // clustersV2, err := v2Client.ListManagedClusters(ctx, orgSlug, false) + // if err != nil { + // return nil, orgSlug, fmt.Errorf("failed retrieving postgres clusters: %w", err) + // } + + // if len(clustersV1.Data) == 0 && len(clustersV2.Data) == 0 { + // return nil, orgSlug, fmt.Errorf("no managed postgres clusters found in organization %s", orgSlug) + // } + + // clusters := slices.Concat(clustersV1.Data, clustersV2.Data) + clusters := make([]*mpg.Cluster, 0, len(clustersResponse.Data)) + for _, cluster := range clustersResponse.Data { + clusters = append(clusters, &mpg.Cluster{ + Id: cluster.Id, + Name: cluster.Name, + Region: cluster.Region, + Status: cluster.Status, + Plan: cluster.Plan, + Disk: cluster.Disk, + Replicas: cluster.Replicas, + Organization: cluster.Organization, + Version: mpg.VersionV1, + }) } + // If a cluster ID is provided via flag, find it if clusterID != "" { - // If a cluster ID is provided via flag, find it - for i := range clustersResponse.Data { - if clustersResponse.Data[i].Id == clusterID { - return &clustersResponse.Data[i], orgSlug, nil + for _, cluster := range clusters { + if cluster.Id == clusterID { + return cluster, orgSlug, nil } } return nil, orgSlug, fmt.Errorf("managed postgres cluster %q not found in organization %s", clusterID, orgSlug) - } else { - // Otherwise, prompt the user to select a cluster - var options []string - for _, cluster := range clustersResponse.Data { - options = append(options, fmt.Sprintf("%s [%s] (%s)", cluster.Name, cluster.Id, cluster.Region)) - } + } - var index int - selectErr := prompt.Select(ctx, &index, "Select a Postgres cluster", "", options...) - if selectErr != nil { - return nil, orgSlug, selectErr - } + // Otherwise, prompt the user to select a cluster + var options []string + for _, cluster := range clusters { + options = append(options, fmt.Sprintf("%s [%s] (%s)", cluster.Name, cluster.Id, cluster.Region)) + } - return &clustersResponse.Data[index], orgSlug, nil + var index int + selectErr := prompt.Select(ctx, &index, "Select a Postgres cluster", "", options...) + if selectErr != nil { + return nil, orgSlug, selectErr } + + return clusters[index], orgSlug, nil } // ClusterFromFlagOrSelect retrieves the cluster ID from the --cluster flag. // If the flag is not set, it prompts the user to select a cluster from the available ones for the given organization. -func ClusterFromFlagOrSelect(ctx context.Context, orgSlug string) (*mpgv1.ManagedCluster, error) { +func ClusterFromFlagOrSelect(ctx context.Context, orgSlug string) (*mpg.Cluster, error) { clusterID := flag.GetMPGClusterID(ctx) cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, orgSlug) diff --git a/internal/command/mpg/mpg_test.go b/internal/command/mpg/mpg_test.go index 4a5a76ee29..47ee2f6029 100644 --- a/internal/command/mpg/mpg_test.go +++ b/internal/command/mpg/mpg_test.go @@ -17,6 +17,7 @@ import ( "github.com/superfly/flyctl/internal/config" "github.com/superfly/flyctl/internal/flag/flagctx" "github.com/superfly/flyctl/internal/mock" + "github.com/superfly/flyctl/internal/uiex/mpg" mpgv1 "github.com/superfly/flyctl/internal/uiex/mpg/v1" "github.com/superfly/flyctl/iostreams" ) @@ -364,7 +365,7 @@ func TestStatusCommand_Logic(t *testing.T) { Organization: fly.Organization{ Slug: "test-org", }, - IpAssignments: mpgv1.ManagedClusterIpAssignments{ + IpAssignments: mpg.ManagedClusterIpAssignments{ Direct: "10.0.0.1", }, } @@ -534,17 +535,17 @@ func TestCreateCommand_Logic(t *testing.T) { return mpgv1.CreateClusterResponse{ Data: struct { - Id string `json:"id"` - Name string `json:"name"` - Status *string `json:"status"` - Plan string `json:"plan"` - Environment *string `json:"environment"` - Region string `json:"region"` - Organization fly.Organization `json:"organization"` - Replicas int `json:"replicas"` - Disk int `json:"disk"` - IpAssignments mpgv1.ManagedClusterIpAssignments `json:"ip_assignments"` - PostGISEnabled bool `json:"postgis_enabled"` + Id string `json:"id"` + Name string `json:"name"` + Status *string `json:"status"` + Plan string `json:"plan"` + Environment *string `json:"environment"` + Region string `json:"region"` + Organization fly.Organization `json:"organization"` + Replicas int `json:"replicas"` + Disk int `json:"disk"` + IpAssignments mpg.ManagedClusterIpAssignments `json:"ip_assignments"` + PostGISEnabled bool `json:"postgis_enabled"` }{ Id: expectedCluster.Id, Name: expectedCluster.Name, @@ -1163,17 +1164,17 @@ func TestCreateCommand_WithPGMajorVersion(t *testing.T) { return mpgv1.CreateClusterResponse{ Data: struct { - Id string `json:"id"` - Name string `json:"name"` - Status *string `json:"status"` - Plan string `json:"plan"` - Environment *string `json:"environment"` - Region string `json:"region"` - Organization fly.Organization `json:"organization"` - Replicas int `json:"replicas"` - Disk int `json:"disk"` - IpAssignments mpgv1.ManagedClusterIpAssignments `json:"ip_assignments"` - PostGISEnabled bool `json:"postgis_enabled"` + Id string `json:"id"` + Name string `json:"name"` + Status *string `json:"status"` + Plan string `json:"plan"` + Environment *string `json:"environment"` + Region string `json:"region"` + Organization fly.Organization `json:"organization"` + Replicas int `json:"replicas"` + Disk int `json:"disk"` + IpAssignments mpg.ManagedClusterIpAssignments `json:"ip_assignments"` + PostGISEnabled bool `json:"postgis_enabled"` }{ Id: "test-cluster-123", Name: "test-db", @@ -1511,12 +1512,12 @@ func TestInvalidPGMajorVersion_Error(t *testing.T) { func TestFormatAttachedApps(t *testing.T) { tests := []struct { name string - apps []mpgv1.AttachedApp + apps []mpg.AttachedApp expected string }{ { name: "no attached apps", - apps: []mpgv1.AttachedApp{}, + apps: []mpg.AttachedApp{}, expected: "", }, { @@ -1526,14 +1527,14 @@ func TestFormatAttachedApps(t *testing.T) { }, { name: "single app", - apps: []mpgv1.AttachedApp{ + apps: []mpg.AttachedApp{ {Name: "my-web-app", Id: 1}, }, expected: "my-web-app", }, { name: "two apps", - apps: []mpgv1.AttachedApp{ + apps: []mpg.AttachedApp{ {Name: "my-web-app", Id: 1}, {Name: "my-api", Id: 2}, }, @@ -1541,7 +1542,7 @@ func TestFormatAttachedApps(t *testing.T) { }, { name: "three apps", - apps: []mpgv1.AttachedApp{ + apps: []mpg.AttachedApp{ {Name: "app-one", Id: 1}, {Name: "app-two", Id: 2}, {Name: "app-three", Id: 3}, @@ -1648,7 +1649,7 @@ func TestListCommand_WithAttachedApps(t *testing.T) { Organization: fly.Organization{ Slug: "test-org", }, - AttachedApps: []mpgv1.AttachedApp{ + AttachedApps: []mpg.AttachedApp{ {Name: "web-app", Id: 100}, {Name: "api-app", Id: 101}, }, @@ -1662,7 +1663,7 @@ func TestListCommand_WithAttachedApps(t *testing.T) { Organization: fly.Organization{ Slug: "test-org", }, - AttachedApps: []mpgv1.AttachedApp{}, // No attached apps + AttachedApps: []mpg.AttachedApp{}, // No attached apps }, } diff --git a/internal/command/mpg/proxy.go b/internal/command/mpg/proxy.go index 47277f80a1..52025dea3d 100644 --- a/internal/command/mpg/proxy.go +++ b/internal/command/mpg/proxy.go @@ -7,9 +7,10 @@ import ( "github.com/spf13/cobra" "github.com/superfly/flyctl/internal/command" cmdv1 "github.com/superfly/flyctl/internal/command/mpg/v1" + cmdv2 "github.com/superfly/flyctl/internal/command/mpg/v2" "github.com/superfly/flyctl/internal/flag" "github.com/superfly/flyctl/internal/flag/flagnames" - mpgv1 "github.com/superfly/flyctl/internal/uiex/mpg/v1" + "github.com/superfly/flyctl/internal/uiex/mpg" ) func newProxy() (cmd *cobra.Command) { @@ -44,31 +45,22 @@ func newProxy() (cmd *cobra.Command) { func runProxy(ctx context.Context) (err error) { clusterID := flag.FirstArg(ctx) - var orgSlug string - - if clusterID != "" { - // If cluster ID is provided, fetch directly without prompting for org - mpgClient := mpgv1.ClientFromContext(ctx) - response, err := mpgClient.GetManagedClusterById(ctx, clusterID) - if err != nil { - return fmt.Errorf("failed retrieving cluster %s: %w", clusterID, err) - } - orgSlug = response.Data.Organization.Slug - } else { - // Otherwise, prompt for org/cluster selection - cluster, resolvedOrgSlug, err := ClusterFromArgOrSelect(ctx, clusterID, "") - if err != nil { - return err - } - clusterID = cluster.Id - orgSlug = resolvedOrgSlug + cluster, orgSlug, err := ClusterFromArgOrSelect(ctx, clusterID, "") + if err != nil { + return err } + localProxyPort := flag.GetString(ctx, flagnames.LocalPort) + // Resolve org slug alias for wireguard tunnel resolvedOrgSlug, err := AliasedOrganizationSlug(ctx, orgSlug) if err != nil { return fmt.Errorf("failed to resolve organization slug: %w", err) } - return cmdv1.RunProxy(ctx, clusterID, resolvedOrgSlug) + if cluster.Version == mpg.VersionV1 { + return cmdv1.RunProxy(ctx, cluster.Id, resolvedOrgSlug, localProxyPort) + } + + return cmdv2.RunProxy(ctx, cluster.Id, resolvedOrgSlug, localProxyPort) } diff --git a/internal/command/mpg/restore.go b/internal/command/mpg/restore.go index 605c12f2fa..a05b2735f0 100644 --- a/internal/command/mpg/restore.go +++ b/internal/command/mpg/restore.go @@ -2,11 +2,14 @@ package mpg import ( "context" + "fmt" "github.com/spf13/cobra" "github.com/superfly/flyctl/internal/command" cmdv1 "github.com/superfly/flyctl/internal/command/mpg/v1" + cmdv2 "github.com/superfly/flyctl/internal/command/mpg/v2" "github.com/superfly/flyctl/internal/flag" + "github.com/superfly/flyctl/internal/uiex/mpg" ) func newRestore() *cobra.Command { @@ -34,14 +37,20 @@ func newRestore() *cobra.Command { func runRestore(ctx context.Context) error { clusterID := flag.FirstArg(ctx) - if clusterID == "" { - cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") - if err != nil { - return err - } + cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") + if err != nil { + return err + } + + backupID := flag.GetString(ctx, "backup-id") + if backupID == "" { + return fmt.Errorf("--backup-id flag is required") + } + + if cluster.Version == mpg.VersionV1 { + return cmdv1.RunRestore(ctx, cluster.Id, backupID) - clusterID = cluster.Id } - return cmdv1.RunRestore(ctx, clusterID) + return cmdv2.RunRestore(ctx, cluster.Id, backupID) } diff --git a/internal/command/mpg/status.go b/internal/command/mpg/status.go index bd000e034a..73e28e8720 100644 --- a/internal/command/mpg/status.go +++ b/internal/command/mpg/status.go @@ -6,7 +6,9 @@ import ( "github.com/spf13/cobra" "github.com/superfly/flyctl/internal/command" cmdv1 "github.com/superfly/flyctl/internal/command/mpg/v1" + cmdv2 "github.com/superfly/flyctl/internal/command/mpg/v2" "github.com/superfly/flyctl/internal/flag" + "github.com/superfly/flyctl/internal/uiex/mpg" ) func newStatus() *cobra.Command { @@ -29,14 +31,15 @@ func newStatus() *cobra.Command { func runStatus(ctx context.Context) error { clusterID := flag.FirstArg(ctx) - if clusterID == "" { - cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") - if err != nil { - return err - } + cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") + if err != nil { + return err + } + + if cluster.Version == mpg.VersionV1 { + return cmdv1.RunStatus(ctx, cluster.Id) - clusterID = cluster.Id } - return cmdv1.RunStatus(ctx, clusterID) + return cmdv2.RunStatus(ctx, cluster.Id) } diff --git a/internal/command/mpg/users.go b/internal/command/mpg/users.go index e5140199c3..18e8cfff4e 100644 --- a/internal/command/mpg/users.go +++ b/internal/command/mpg/users.go @@ -6,7 +6,9 @@ import ( "github.com/spf13/cobra" "github.com/superfly/flyctl/internal/command" cmdv1 "github.com/superfly/flyctl/internal/command/mpg/v1" + cmdv2 "github.com/superfly/flyctl/internal/command/mpg/v2" "github.com/superfly/flyctl/internal/flag" + "github.com/superfly/flyctl/internal/uiex/mpg" ) func newUsers() *cobra.Command { @@ -49,16 +51,16 @@ func newUsersList() *cobra.Command { func runUsersList(ctx context.Context) error { clusterID := flag.FirstArg(ctx) - if clusterID == "" { - cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") - if err != nil { - return err - } + cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") + if err != nil { + return err + } - clusterID = cluster.Id + if cluster.Version == mpg.VersionV1 { + return cmdv1.RunUsersList(ctx, cluster.Id) } - return cmdv1.RunUsersList(ctx, clusterID) + return cmdv2.RunUsersList(ctx, cluster.Id) } func newUsersCreate() *cobra.Command { @@ -92,16 +94,17 @@ func newUsersCreate() *cobra.Command { func runUsersCreate(ctx context.Context) error { clusterID := flag.FirstArg(ctx) - if clusterID == "" { - cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") - if err != nil { - return err - } + cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") + if err != nil { + return err + } + + if cluster.Version == mpg.VersionV1 { + return cmdv1.RunUsersCreate(ctx, cluster.Id) - clusterID = cluster.Id } - return cmdv1.RunUsersCreate(ctx, clusterID) + return cmdv2.RunUsersCreate(ctx, cluster.Id) } func newUsersSetRole() *cobra.Command { @@ -136,16 +139,16 @@ func newUsersSetRole() *cobra.Command { func runUsersSetRole(ctx context.Context) error { clusterID := flag.FirstArg(ctx) - if clusterID == "" { - cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") - if err != nil { - return err - } + cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") + if err != nil { + return err + } - clusterID = cluster.Id + if cluster.Version == mpg.VersionV1 { + return cmdv1.RunUsersSetRole(ctx, cluster.Id) } - return cmdv1.RunUsersSetRole(ctx, clusterID) + return cmdv2.RunUsersSetRole(ctx, cluster.Id) } func newUsersDelete() *cobra.Command { @@ -176,14 +179,14 @@ func newUsersDelete() *cobra.Command { func runUsersDelete(ctx context.Context) error { clusterID := flag.FirstArg(ctx) - if clusterID == "" { - cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") - if err != nil { - return err - } + cluster, _, err := ClusterFromArgOrSelect(ctx, clusterID, "") + if err != nil { + return err + } - clusterID = cluster.Id + if cluster.Version == mpg.VersionV1 { + return cmdv1.RunUsersDelete(ctx, cluster.Id) } - return cmdv1.RunUsersDelete(ctx, clusterID) + return cmdv2.RunUsersDelete(ctx, cluster.Id) } diff --git a/internal/command/mpg/v1/run_list.go b/internal/command/mpg/v1/run_list.go index 57a3fec595..1f21de5c13 100644 --- a/internal/command/mpg/v1/run_list.go +++ b/internal/command/mpg/v1/run_list.go @@ -12,6 +12,7 @@ import ( "github.com/superfly/flyctl/internal/flag" "github.com/superfly/flyctl/internal/flyutil" "github.com/superfly/flyctl/internal/render" + "github.com/superfly/flyctl/internal/uiex/mpg" mpgv1 "github.com/superfly/flyctl/internal/uiex/mpg/v1" ) @@ -66,7 +67,7 @@ func RunList(ctx context.Context, orgSlug string) error { } // FormatAttachedApps formats the list of attached apps for display -func FormatAttachedApps(apps []mpgv1.AttachedApp) string { +func FormatAttachedApps(apps []mpg.AttachedApp) string { if len(apps) == 0 { return "" } diff --git a/internal/command/mpg/v1/run_proxy.go b/internal/command/mpg/v1/run_proxy.go index e92fbb629a..da7cf3e9c7 100644 --- a/internal/command/mpg/v1/run_proxy.go +++ b/internal/command/mpg/v1/run_proxy.go @@ -6,15 +6,13 @@ import ( "github.com/superfly/flyctl/agent" "github.com/superfly/flyctl/internal/flag" - "github.com/superfly/flyctl/internal/flag/flagnames" "github.com/superfly/flyctl/internal/flyutil" mpgv1 "github.com/superfly/flyctl/internal/uiex/mpg/v1" "github.com/superfly/flyctl/proxy" ) -func RunProxy(ctx context.Context, clusterID string, resolvedOrgSlug string) error { - localProxyPort := flag.GetString(ctx, flagnames.LocalPort) - _, params, _, err := GetMpgProxyParams(ctx, localProxyPort, "", clusterID, resolvedOrgSlug) +func RunProxy(ctx context.Context, clusterID string, resolvedOrgSlug string, proxyPort string) error { + _, params, _, err := GetMpgProxyParams(ctx, proxyPort, "", clusterID, resolvedOrgSlug) if err != nil { return err } diff --git a/internal/command/mpg/v1/run_restore.go b/internal/command/mpg/v1/run_restore.go index f96cc7374f..d9a40789fb 100644 --- a/internal/command/mpg/v1/run_restore.go +++ b/internal/command/mpg/v1/run_restore.go @@ -4,20 +4,14 @@ import ( "context" "fmt" - "github.com/superfly/flyctl/internal/flag" mpgv1 "github.com/superfly/flyctl/internal/uiex/mpg/v1" "github.com/superfly/flyctl/iostreams" ) -func RunRestore(ctx context.Context, clusterID string) error { +func RunRestore(ctx context.Context, clusterID string, backupID string) error { out := iostreams.FromContext(ctx).Out mpgClient := mpgv1.ClientFromContext(ctx) - backupID := flag.GetString(ctx, "backup-id") - if backupID == "" { - return fmt.Errorf("--backup-id flag is required") - } - fmt.Fprintf(out, "Restoring cluster %s from backup %s...\n", clusterID, backupID) input := mpgv1.RestoreManagedClusterBackupInput{ diff --git a/internal/command/mpg/v2/run_attach.go b/internal/command/mpg/v2/run_attach.go new file mode 100644 index 0000000000..b4e604a5bc --- /dev/null +++ b/internal/command/mpg/v2/run_attach.go @@ -0,0 +1,9 @@ +package cmdv2 + +import ( + "context" +) + +func RunAttach(ctx context.Context, clusterID string) error { + return nil +} diff --git a/internal/command/mpg/v2/run_backup.go b/internal/command/mpg/v2/run_backup.go new file mode 100644 index 0000000000..10054d79fe --- /dev/null +++ b/internal/command/mpg/v2/run_backup.go @@ -0,0 +1,13 @@ +package cmdv2 + +import ( + "context" +) + +func RunBackupList(ctx context.Context, clusterID string) error { + return nil +} + +func RunBackupCreate(ctx context.Context, clusterID string) error { + return nil +} diff --git a/internal/command/mpg/v2/run_connect.go b/internal/command/mpg/v2/run_connect.go new file mode 100644 index 0000000000..3ecf3e222e --- /dev/null +++ b/internal/command/mpg/v2/run_connect.go @@ -0,0 +1,9 @@ +package cmdv2 + +import ( + "context" +) + +func RunConnect(ctx context.Context, clusterID, orgSlug string, proxyPort string) (err error) { + return nil +} diff --git a/internal/command/mpg/v2/run_create.go b/internal/command/mpg/v2/run_create.go new file mode 100644 index 0000000000..663f787b3d --- /dev/null +++ b/internal/command/mpg/v2/run_create.go @@ -0,0 +1,21 @@ +package cmdv2 + +import ( + "context" + + "github.com/superfly/fly-go" +) + +type CreateClusterParams struct { + Name string + OrgSlug string + Region string + Plan string + VolumeSizeGB int + PostGISEnabled bool + PGMajorVersion int +} + +func RunCreate(ctx context.Context, org *fly.Organization, appName string) error { + return nil +} diff --git a/internal/command/mpg/v2/run_databases.go b/internal/command/mpg/v2/run_databases.go new file mode 100644 index 0000000000..b22f92f5f4 --- /dev/null +++ b/internal/command/mpg/v2/run_databases.go @@ -0,0 +1,13 @@ +package cmdv2 + +import ( + "context" +) + +func RunDatabasesList(ctx context.Context, clusterID string) error { + return nil +} + +func RunDatabasesCreate(ctx context.Context, clusterID string) error { + return nil +} diff --git a/internal/command/mpg/v2/run_destroy.go b/internal/command/mpg/v2/run_destroy.go new file mode 100644 index 0000000000..8e851bece6 --- /dev/null +++ b/internal/command/mpg/v2/run_destroy.go @@ -0,0 +1,9 @@ +package cmdv2 + +import ( + "context" +) + +func RunDestroy(ctx context.Context, clusterID string) error { + return nil +} diff --git a/internal/command/mpg/v2/run_detach.go b/internal/command/mpg/v2/run_detach.go new file mode 100644 index 0000000000..cc22ab5ec8 --- /dev/null +++ b/internal/command/mpg/v2/run_detach.go @@ -0,0 +1,9 @@ +package cmdv2 + +import ( + "context" +) + +func RunDetach(ctx context.Context, clusterID string, appName string) error { + return nil +} diff --git a/internal/command/mpg/v2/run_proxy.go b/internal/command/mpg/v2/run_proxy.go new file mode 100644 index 0000000000..928dd56113 --- /dev/null +++ b/internal/command/mpg/v2/run_proxy.go @@ -0,0 +1,9 @@ +package cmdv2 + +import ( + "context" +) + +func RunProxy(ctx context.Context, clusterID string, orgSlug string, proxyPort string) error { + return nil +} diff --git a/internal/command/mpg/v2/run_restore.go b/internal/command/mpg/v2/run_restore.go new file mode 100644 index 0000000000..e526cf650e --- /dev/null +++ b/internal/command/mpg/v2/run_restore.go @@ -0,0 +1,9 @@ +package cmdv2 + +import ( + "context" +) + +func RunRestore(ctx context.Context, clusterID string, backupID string) error { + return nil +} diff --git a/internal/command/mpg/v2/run_status.go b/internal/command/mpg/v2/run_status.go new file mode 100644 index 0000000000..f46fb111de --- /dev/null +++ b/internal/command/mpg/v2/run_status.go @@ -0,0 +1,9 @@ +package cmdv2 + +import ( + "context" +) + +func RunStatus(ctx context.Context, clusterID string) error { + return nil +} diff --git a/internal/command/mpg/v2/run_users.go b/internal/command/mpg/v2/run_users.go new file mode 100644 index 0000000000..47db8ac1f2 --- /dev/null +++ b/internal/command/mpg/v2/run_users.go @@ -0,0 +1,21 @@ +package cmdv2 + +import ( + "context" +) + +func RunUsersList(ctx context.Context, clusterID string) error { + return nil +} + +func RunUsersCreate(ctx context.Context, clusterID string) error { + return nil +} + +func RunUsersSetRole(ctx context.Context, clusterID string) error { + return nil +} + +func RunUsersDelete(ctx context.Context, clusterID string) error { + return nil +} diff --git a/internal/uiex/mpg/types.go b/internal/uiex/mpg/types.go new file mode 100644 index 0000000000..9c9d24fc84 --- /dev/null +++ b/internal/uiex/mpg/types.go @@ -0,0 +1,35 @@ +package mpg + +import "github.com/superfly/fly-go" + +type Version int + +const ( + VersionV1 Version = iota + VersionV2 +) + +// Unified cluster type that holds fields that are common +// across V1 and V2 +type Cluster struct { + Id string `json:"id"` + Name string `json:"name"` + Region string `json:"region"` + Status string `json:"status"` + Plan string `json:"plan"` + Disk int `json:"disk"` + Replicas int `json:"replicas"` + Organization fly.Organization `json:"organization"` + IpAssignments ManagedClusterIpAssignments `json:"ip_assignments"` + AttachedApps []AttachedApp `json:"attached_apps"` + Version Version `json:"-"` +} + +type ManagedClusterIpAssignments struct { + Direct string `json:"direct"` +} + +type AttachedApp struct { + Name string `json:"name"` + Id int64 `json:"id"` +} diff --git a/internal/uiex/mpg/v1/client.go b/internal/uiex/mpg/v1/client.go index ab29ca714f..6a80dbf82d 100644 --- a/internal/uiex/mpg/v1/client.go +++ b/internal/uiex/mpg/v1/client.go @@ -7,6 +7,7 @@ import ( "github.com/superfly/fly-go" "github.com/superfly/flyctl/internal/uiex" + "github.com/superfly/flyctl/internal/uiex/mpg" ) type contextKey struct{} @@ -70,10 +71,6 @@ func ClientFromContext(ctx context.Context) ClientV1 { return c } -type ManagedClusterIpAssignments struct { - Direct string `json:"direct"` -} - type MPGRegion struct { Code string `json:"code"` // e.g., "fra" Available bool `json:"available"` // Whether this region supports MPG @@ -111,22 +108,17 @@ type RestoreManagedClusterBackupResponse struct { Data ManagedCluster `json:"data"` } -type AttachedApp struct { - Name string `json:"name"` - Id int64 `json:"id"` -} - type ManagedCluster struct { - Id string `json:"id"` - Name string `json:"name"` - Region string `json:"region"` - Status string `json:"status"` - Plan string `json:"plan"` - Disk int `json:"disk"` - Replicas int `json:"replicas"` - Organization fly.Organization `json:"organization"` - IpAssignments ManagedClusterIpAssignments `json:"ip_assignments"` - AttachedApps []AttachedApp `json:"attached_apps"` + Id string `json:"id"` + Name string `json:"name"` + Region string `json:"region"` + Status string `json:"status"` + Plan string `json:"plan"` + Disk int `json:"disk"` + Replicas int `json:"replicas"` + Organization fly.Organization `json:"organization"` + IpAssignments mpg.ManagedClusterIpAssignments `json:"ip_assignments"` + AttachedApps []mpg.AttachedApp `json:"attached_apps"` } type ListManagedClustersResponse struct { @@ -220,17 +212,17 @@ type CreateClusterResponse struct { Ok bool `json:"ok"` Errors uiex.DetailedErrors `json:"errors"` Data struct { - Id string `json:"id"` - Name string `json:"name"` - Status *string `json:"status"` - Plan string `json:"plan"` - Environment *string `json:"environment"` - Region string `json:"region"` - Organization fly.Organization `json:"organization"` - Replicas int `json:"replicas"` - Disk int `json:"disk"` - IpAssignments ManagedClusterIpAssignments `json:"ip_assignments"` - PostGISEnabled bool `json:"postgis_enabled"` + Id string `json:"id"` + Name string `json:"name"` + Status *string `json:"status"` + Plan string `json:"plan"` + Environment *string `json:"environment"` + Region string `json:"region"` + Organization fly.Organization `json:"organization"` + Replicas int `json:"replicas"` + Disk int `json:"disk"` + IpAssignments mpg.ManagedClusterIpAssignments `json:"ip_assignments"` + PostGISEnabled bool `json:"postgis_enabled"` } `json:"data"` }