Skip to content
40 changes: 26 additions & 14 deletions internal/utils/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,23 +167,35 @@ func ConnectByUrl(ctx context.Context, url string, options ...func(*pgx.ConnConf
cc.Fallbacks = fallbacks
})
conn, err := pgxv5.Connect(ctx, url, options...)
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
if strings.Contains(pgErr.Message, "connect: connection refused") {
CmdSuggestion = fmt.Sprintf("Make sure your local IP is allowed in Network Restrictions and Network Bans.\n%s/project/_/database/settings", CurrentProfile.DashboardURL)
} else if strings.Contains(pgErr.Message, "SSL connection is required") && viper.GetBool("DEBUG") {
CmdSuggestion = "SSL connection is not supported with --debug flag"
} else if strings.Contains(pgErr.Message, "SCRAM exchange: Wrong password") || strings.Contains(pgErr.Message, "failed SASL auth") {
// password authentication failed for user / invalid SCRAM server-final-message received from server
CmdSuggestion = "Try setting the SUPABASE_DB_PASSWORD environment variable"
} else if strings.Contains(pgErr.Message, "connect: no route to host") || strings.Contains(pgErr.Message, "Tenant or user not found") {
// Assumes IPv6 check has been performed before this
CmdSuggestion = "Make sure your project exists on profile: " + CurrentProfile.Name
}
}
SetConnectSuggestion(err)
return conn, err
}

const SuggestEnvVar = "Connect to your database by setting the env var correctly: SUPABASE_DB_PASSWORD"

// Sets CmdSuggestion to an actionable hint based on the given pg connection error.
func SetConnectSuggestion(err error) {
if err == nil {
return
}
msg := err.Error()
if strings.Contains(msg, "connect: connection refused") ||
strings.Contains(msg, "Address not in tenant allow_list") {
CmdSuggestion = fmt.Sprintf(
"Make sure your local IP is allowed in Network Restrictions and Network Bans.\n%s/project/_/database/settings",
CurrentProfile.DashboardURL,
)
} else if strings.Contains(msg, "SSL connection is required") && viper.GetBool("DEBUG") {
CmdSuggestion = "SSL connection is not supported with --debug flag"
} else if strings.Contains(msg, "SCRAM exchange: Wrong password") || strings.Contains(msg, "failed SASL auth") {
// password authentication failed for user / invalid SCRAM server-final-message received from server
CmdSuggestion = SuggestEnvVar
} else if strings.Contains(msg, "connect: no route to host") || strings.Contains(msg, "Tenant or user not found") {
// Assumes IPv6 check has been performed before this
CmdSuggestion = "Make sure your project exists on profile: " + CurrentProfile.Name
}
}

const (
SUPERUSER_ROLE = "supabase_admin"
CLI_LOGIN_PREFIX = "cli_login_"
Expand Down
77 changes: 77 additions & 0 deletions internal/utils/connect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,83 @@ func TestPoolerConfig(t *testing.T) {
})
}

func TestSetConnectSuggestion(t *testing.T) {
oldProfile := CurrentProfile
CurrentProfile = allProfiles[0]
defer t.Cleanup(func() { CurrentProfile = oldProfile })

cases := []struct {
name string
err error
suggestion string
debug bool
}{
{
name: "no-op on nil error",
err: nil,
suggestion: "",
},
{
name: "no-op on unrecognised error",
err: errors.New("some unknown error"),
suggestion: "",
},
{
name: "connection refused",
err: errors.New("connect: connection refused"),
suggestion: "Make sure your local IP is allowed in Network Restrictions and Network Bans",
},
{
name: "address not in allow list",
err: errors.New("server error (FATAL: Address not in tenant allow_list: {1,2,3} (SQLSTATE XX000))"),
suggestion: "Make sure your local IP is allowed in Network Restrictions and Network Bans",
},
{
name: "ssl required without debug flag",
err: errors.New("SSL connection is required"),
suggestion: "",
},
{
name: "ssl required with debug flag",
err: errors.New("SSL connection is required"),
debug: true,
suggestion: "SSL connection is not supported with --debug flag",
},
{
name: "wrong password via SCRAM",
err: errors.New("SCRAM exchange: Wrong password"),
suggestion: "Connect to your database by setting the env var correctly: SUPABASE_DB_PASSWORD",
},
{
name: "failed SASL auth",
err: errors.New("failed SASL auth"),
suggestion: "Connect to your database by setting the env var correctly: SUPABASE_DB_PASSWORD",
},
{
name: "no route to host",
err: errors.New("connect: no route to host"),
suggestion: "Make sure your project exists on profile: " + CurrentProfile.Name,
},
{
name: "tenant or user not found",
err: errors.New("Tenant or user not found"),
suggestion: "Make sure your project exists on profile: " + CurrentProfile.Name,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
CmdSuggestion = ""
viper.Set("DEBUG", tc.debug)
SetConnectSuggestion(tc.err)
if tc.suggestion == "" {
assert.Empty(t, CmdSuggestion)
} else {
assert.Contains(t, CmdSuggestion, tc.suggestion)
}
})
}
}

func TestPostgresURL(t *testing.T) {
url := ToPostgresURL(pgconn.Config{
Host: "2406:da18:4fd:9b0d:80ec:9812:3e65:450b",
Expand Down
9 changes: 5 additions & 4 deletions internal/utils/flags/db_url.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,6 @@ func RandomString(size int) (string, error) {
return string(data), nil
}

const suggestEnvVar = "Connect to your database by setting the env var: SUPABASE_DB_PASSWORD"

func NewDbConfigWithPassword(ctx context.Context, projectRef string) (pgconn.Config, error) {
config := pgconn.Config{
Host: utils.GetSupabaseDbHost(projectRef),
Expand All @@ -144,7 +142,10 @@ func NewDbConfigWithPassword(ctx context.Context, projectRef string) (pgconn.Con
fmt.Fprintln(logger, "Using database password from env var...")
poolerConfig.Password = config.Password
} else if err := initPoolerLogin(ctx, projectRef, poolerConfig); err != nil {
utils.CmdSuggestion = suggestEnvVar
utils.SetConnectSuggestion(err)
if utils.CmdSuggestion == "" {
utils.CmdSuggestion = utils.SuggestEnvVar
}
return *poolerConfig, err
}
return *poolerConfig, nil
Expand All @@ -157,7 +158,7 @@ func NewDbConfigWithPassword(ctx context.Context, projectRef string) (pgconn.Con
fmt.Fprintln(logger, "Using database password from env var...")
} else if err := initLoginRole(ctx, projectRef, &config); err != nil {
// Do not prompt because reading masked input is buggy on windows
utils.CmdSuggestion = suggestEnvVar
utils.CmdSuggestion = utils.SuggestEnvVar
return config, err
}
return config, nil
Expand Down
Loading