Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 71 additions & 12 deletions internal/command/mpg/v2/run_attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/superfly/flyctl/internal/appsecrets"
"github.com/superfly/flyctl/internal/flag"
"github.com/superfly/flyctl/internal/flapsutil"
"github.com/superfly/flyctl/internal/mpgutil"
"github.com/superfly/flyctl/internal/prompt"
mpgv2 "github.com/superfly/flyctl/internal/uiex/mpg/v2"
"github.com/superfly/flyctl/iostreams"
Expand Down Expand Up @@ -131,17 +132,12 @@ func RunAttach(ctx context.Context, clusterID string) error {
}
}

// Get cluster details and credentials. The public Machines API cluster show does
// not expose credentials, so we always use the legacy client for the connection
// URI and default DB name. This preserves the existing behavior.
clusterResp, err := legacyClient.GetClusterById(ctx, clusterID)
info, err := getClusterConnectionInfoPublicFirst(ctx, flapsClient, legacyClient, clusterID, username == "")
if err != nil {
return fmt.Errorf("failed retrieving cluster %s: %w", clusterID, err)
}

baseUri := clusterResp.Credentials.ConnectionUri
if baseUri == "" {
return fmt.Errorf("connection URI is empty; cannot attach without valid credentials")
if info.BaseURI == "" {
return fmt.Errorf("cluster is not ready; cannot attach without valid connection information")
}

var connectionUri string
Expand All @@ -155,14 +151,14 @@ func RunAttach(ctx context.Context, clusterID string) error {
user = creds.User
password = creds.Password
} else {
user = clusterResp.Credentials.User
password = clusterResp.Credentials.Password
user = info.DefaultUser
password = info.DefaultPassword
}

if db == "" {
db = clusterResp.Credentials.DBName
db = info.DefaultDBName
}
connectionUri, err = buildConnectionUri(baseUri, user, password, db)
connectionUri, err = buildConnectionUri(info.BaseURI, user, password, db)
if err != nil {
return fmt.Errorf("failed to build connection URI: %w", err)
}
Expand Down Expand Up @@ -331,3 +327,66 @@ func createAttachmentPublicFirst(ctx context.Context, flapsClient flapsutil.Flap

return nil
}

// clusterConnectionInfo holds the base URI and default credentials for attach.
type clusterConnectionInfo struct {
BaseURI string
DefaultUser string
DefaultPassword string
DefaultDBName string
}

// getClusterConnectionInfoPublicFirst falls back on cluster or credential 404s.
func getClusterConnectionInfoPublicFirst(ctx context.Context, flapsClient flapsutil.FlapsClient, legacyClient mpgv2.ClientV2, clusterID string, needDefaultCredentials bool) (clusterConnectionInfo, error) {
cluster, err := flapsClient.GetManagedPostgresCluster(ctx, clusterID)
if errors.Is(err, flaps.ErrFlapsNotFound) {
return getClusterConnectionInfoLegacy(ctx, legacyClient, clusterID)
}
if err != nil {
return clusterConnectionInfo{}, err
}

if cluster.Status == flaps.ManagedPostgresStatusFailed || cluster.Status == flaps.ManagedPostgresStatusError {
return clusterConnectionInfo{}, fmt.Errorf("cluster is in a failed state (status: %s); cannot attach", cluster.Status)
}

// Check before credentials so a credentials 404 cannot bypass the host check.
pooler := cluster.Endpoints.Primary.Pooler
if pooler.Host == "" || pooler.Port == 0 {
return clusterConnectionInfo{}, nil
}

info := clusterConnectionInfo{
BaseURI: fmt.Sprintf("postgresql://%s:%d/%s", pooler.Host, pooler.Port, mpgutil.DefaultDatabase),
DefaultDBName: mpgutil.DefaultDatabase,
}
if !needDefaultCredentials {
return info, nil
}

creds, err := flapsClient.GetManagedPostgresUserCredentials(ctx, clusterID, mpgutil.DefaultUsername)
if errors.Is(err, flaps.ErrFlapsNotFound) {
return getClusterConnectionInfoLegacy(ctx, legacyClient, clusterID)
}
if err != nil {
return clusterConnectionInfo{}, err
}
info.DefaultUser = creds.Username
info.DefaultPassword = creds.Password

return info, nil
}

func getClusterConnectionInfoLegacy(ctx context.Context, legacyClient mpgv2.ClientV2, clusterID string) (clusterConnectionInfo, error) {
clusterResp, err := legacyClient.GetClusterById(ctx, clusterID)
if err != nil {
return clusterConnectionInfo{}, err
}

return clusterConnectionInfo{
BaseURI: clusterResp.Credentials.ConnectionUri,
DefaultUser: clusterResp.Credentials.User,
DefaultPassword: clusterResp.Credentials.Password,
DefaultDBName: clusterResp.Credentials.DBName,
}, nil
}
Loading