|
| 1 | +package mpg |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "fmt" |
| 6 | + |
| 7 | + "github.com/spf13/cobra" |
| 8 | + "github.com/superfly/flyctl/internal/appconfig" |
| 9 | + "github.com/superfly/flyctl/internal/command" |
| 10 | + "github.com/superfly/flyctl/internal/flag" |
| 11 | + "github.com/superfly/flyctl/internal/flyutil" |
| 12 | + "github.com/superfly/flyctl/internal/uiexutil" |
| 13 | + "github.com/superfly/flyctl/iostreams" |
| 14 | +) |
| 15 | + |
| 16 | +func newDetach() *cobra.Command { |
| 17 | + const ( |
| 18 | + short = "Detach a managed Postgres cluster from an app" |
| 19 | + long = short + ". " + |
| 20 | + `This command will remove the attachment record linking the app to the cluster. |
| 21 | +Note: This does NOT remove any secrets from the app. Use 'fly secrets unset' to remove secrets.` |
| 22 | + usage = "detach <CLUSTER ID>" |
| 23 | + ) |
| 24 | + |
| 25 | + cmd := command.New(usage, short, long, runDetach, |
| 26 | + command.RequireSession, |
| 27 | + command.RequireAppName, |
| 28 | + ) |
| 29 | + cmd.Args = cobra.MaximumNArgs(1) |
| 30 | + |
| 31 | + flag.Add(cmd, |
| 32 | + flag.App(), |
| 33 | + flag.AppConfig(), |
| 34 | + ) |
| 35 | + |
| 36 | + return cmd |
| 37 | +} |
| 38 | + |
| 39 | +func runDetach(ctx context.Context) error { |
| 40 | + // Check token compatibility early |
| 41 | + if err := validateMPGTokenCompatibility(ctx); err != nil { |
| 42 | + return err |
| 43 | + } |
| 44 | + |
| 45 | + var ( |
| 46 | + clusterId = flag.FirstArg(ctx) |
| 47 | + appName = appconfig.NameFromContext(ctx) |
| 48 | + client = flyutil.ClientFromContext(ctx) |
| 49 | + io = iostreams.FromContext(ctx) |
| 50 | + ) |
| 51 | + |
| 52 | + // Get app details to determine which org it belongs to |
| 53 | + app, err := client.GetAppBasic(ctx, appName) |
| 54 | + if err != nil { |
| 55 | + return fmt.Errorf("failed retrieving app %s: %w", appName, err) |
| 56 | + } |
| 57 | + |
| 58 | + appOrgSlug := app.Organization.RawSlug |
| 59 | + if appOrgSlug != "" && clusterId == "" { |
| 60 | + fmt.Fprintf(io.Out, "Listing clusters in organization %s\n", appOrgSlug) |
| 61 | + } |
| 62 | + |
| 63 | + // Get cluster details |
| 64 | + cluster, _, err := ClusterFromArgOrSelect(ctx, clusterId, appOrgSlug) |
| 65 | + if err != nil { |
| 66 | + return fmt.Errorf("failed retrieving cluster %s: %w", clusterId, err) |
| 67 | + } |
| 68 | + |
| 69 | + clusterOrgSlug := cluster.Organization.Slug |
| 70 | + |
| 71 | + // Verify that the app and cluster are in the same organization |
| 72 | + if appOrgSlug != clusterOrgSlug { |
| 73 | + return fmt.Errorf("app %s is in organization %s, but cluster %s is in organization %s. They must be in the same organization", |
| 74 | + appName, appOrgSlug, cluster.Id, clusterOrgSlug) |
| 75 | + } |
| 76 | + |
| 77 | + uiexClient := uiexutil.ClientFromContext(ctx) |
| 78 | + |
| 79 | + // Delete the attachment record |
| 80 | + _, err = uiexClient.DeleteAttachment(ctx, cluster.Id, appName) |
| 81 | + if err != nil { |
| 82 | + return fmt.Errorf("failed to detach: %w", err) |
| 83 | + } |
| 84 | + |
| 85 | + fmt.Fprintf(io.Out, "\nPostgres cluster %s has been detached from %s\n", cluster.Id, appName) |
| 86 | + fmt.Fprintf(io.Out, "Note: This only removes the attachment record. Any secrets (like DATABASE_URL) are still set on the app.\n") |
| 87 | + fmt.Fprintf(io.Out, "Use 'fly secrets unset DATABASE_URL -a %s' to remove the connection string.\n", appName) |
| 88 | + |
| 89 | + return nil |
| 90 | +} |
0 commit comments