From e8fbbca5680ad55a7b2ffd0e0f337004d8864327 Mon Sep 17 00:00:00 2001 From: eileencodes Date: Fri, 16 Oct 2026 16:04:23 -0400 Subject: [PATCH] Fix confusing missing role error When a database doesn't have a role created yet (ie creating a postgres database from the CLI) calling `pscale role default ` will give an error that either the db or branch doesn't exist, which is confusing. Instead point the user at creating a role if the branch does actually exist. --- internal/cmd/role/default.go | 34 +++++++++ internal/cmd/role/default_test.go | 114 ++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) diff --git a/internal/cmd/role/default.go b/internal/cmd/role/default.go index 597817b5..e6a69943 100644 --- a/internal/cmd/role/default.go +++ b/internal/cmd/role/default.go @@ -41,6 +41,17 @@ new password for the default role.`, if err != nil { switch cmdutil.ErrCode(err) { case ps.ErrNotFound: + // The default role endpoint also 404s when the branch exists + // but its default credentials have never been created. + if _, branchErr := client.PostgresBranches.Get(ctx, &ps.GetPostgresBranchRequest{ + Organization: ch.Config.Organization, + Database: database, + Branch: branch, + }); branchErr == nil { + end() + return defaultRoleNotCreatedError(ch, database, branch) + } + return cmdutil.HandleNotFoundWithServiceTokenCheck( ctx, cmd, ch.Config, ch.Client, err, "read_branch", @@ -58,3 +69,26 @@ new password for the default role.`, return cmd } + +func defaultRoleNotCreatedError(ch *cmdutil.Helper, database, branch string) error { + org := ch.Config.Organization + if ch.Printer.Format() != printer.JSON { + return fmt.Errorf("the default role for %s/%s has not been created yet. Run 'pscale role reset-default %s %s' to create it", + printer.BoldBlue(database), printer.BoldBlue(branch), database, branch) + } + + msg := fmt.Sprintf("the default role for %s/%s has not been created yet", database, branch) + resp := cmdutil.JSONErrorResponse{ + Status: "action_required", + Error: msg, + Issues: []cmdutil.JSONErrorIssue{{Code: "NOT_FOUND", Message: msg}}, + NextSteps: []string{ + "Ask the user to approve creating credentials for the default role", + fmt.Sprintf("pscale role reset-default %s %s --org %s --format json --force", database, branch, org), + }, + } + if err := ch.Printer.PrintJSON(resp); err != nil { + return err + } + return cmdutil.JSONReportedError(cmdutil.ActionRequestedExitCode) +} diff --git a/internal/cmd/role/default_test.go b/internal/cmd/role/default_test.go index 557d626f..05145d68 100644 --- a/internal/cmd/role/default_test.go +++ b/internal/cmd/role/default_test.go @@ -3,6 +3,8 @@ package role import ( "bytes" "context" + "encoding/json" + "errors" "testing" "github.com/planetscale/cli/internal/cmdutil" @@ -49,3 +51,115 @@ func TestRole_DefaultCmd(t *testing.T) { c.Assert(svc.GetDefaultRoleFnInvoked, qt.IsTrue) c.Assert(buf.String(), qt.Contains, `"username": "postgres"`) } + +func TestRole_DefaultCmd_NotCreated(t *testing.T) { + c := qt.New(t) + + format := printer.Human + p := printer.NewPrinter(&format) + + roles := &mock.PostgresRolesService{ + GetDefaultRoleFn: func(ctx context.Context, req *ps.GetDefaultPostgresRoleRequest) (*ps.PostgresRole, error) { + return nil, &ps.Error{Code: ps.ErrNotFound} + }, + } + branches := &mock.PostgresBranchesService{ + GetFn: func(ctx context.Context, req *ps.GetPostgresBranchRequest) (*ps.PostgresBranch, error) { + c.Assert(req.Organization, qt.Equals, "planetscale") + c.Assert(req.Database, qt.Equals, "mydb") + c.Assert(req.Branch, qt.Equals, "main") + return &ps.PostgresBranch{Name: "main"}, nil + }, + } + + ch := &cmdutil.Helper{ + Printer: p, + Config: &config.Config{Organization: "planetscale"}, + Client: func() (*ps.Client, error) { + return &ps.Client{PostgresRoles: roles, PostgresBranches: branches}, nil + }, + } + + cmd := DefaultCmd(ch) + cmd.SetArgs([]string{"mydb", "main"}) + err := cmd.Execute() + c.Assert(err, qt.IsNotNil) + c.Assert(branches.GetFnInvoked, qt.IsTrue) + c.Assert(err.Error(), qt.Contains, "has not been created yet") + c.Assert(err.Error(), qt.Contains, "pscale role reset-default mydb main") + c.Assert(err.Error(), qt.Not(qt.Contains), "does not exist") +} + +func TestRole_DefaultCmd_NotCreatedJSON(t *testing.T) { + c := qt.New(t) + + var buf bytes.Buffer + format := printer.JSON + p := printer.NewPrinter(&format) + p.SetResourceOutput(&buf) + + roles := &mock.PostgresRolesService{ + GetDefaultRoleFn: func(ctx context.Context, req *ps.GetDefaultPostgresRoleRequest) (*ps.PostgresRole, error) { + return nil, &ps.Error{Code: ps.ErrNotFound} + }, + } + branches := &mock.PostgresBranchesService{ + GetFn: func(ctx context.Context, req *ps.GetPostgresBranchRequest) (*ps.PostgresBranch, error) { + return &ps.PostgresBranch{Name: "main"}, nil + }, + } + + ch := &cmdutil.Helper{ + Printer: p, + Config: &config.Config{Organization: "planetscale"}, + Client: func() (*ps.Client, error) { + return &ps.Client{PostgresRoles: roles, PostgresBranches: branches}, nil + }, + } + + cmd := DefaultCmd(ch) + cmd.SetArgs([]string{"mydb", "main"}) + err := cmd.Execute() + + var cmdErr *cmdutil.Error + c.Assert(errors.As(err, &cmdErr), qt.IsTrue) + c.Assert(cmdErr.ExitCode, qt.Equals, cmdutil.ActionRequestedExitCode) + + var resp cmdutil.JSONErrorResponse + c.Assert(json.Unmarshal(buf.Bytes(), &resp), qt.IsNil) + c.Assert(resp.Status, qt.Equals, "action_required") + c.Assert(resp.Code(), qt.Equals, "NOT_FOUND") + c.Assert(resp.NextSteps, qt.Contains, "pscale role reset-default mydb main --org planetscale --format json --force") +} + +func TestRole_DefaultCmd_BranchNotFound(t *testing.T) { + c := qt.New(t) + + format := printer.Human + p := printer.NewPrinter(&format) + + roles := &mock.PostgresRolesService{ + GetDefaultRoleFn: func(ctx context.Context, req *ps.GetDefaultPostgresRoleRequest) (*ps.PostgresRole, error) { + return nil, &ps.Error{Code: ps.ErrNotFound} + }, + } + branches := &mock.PostgresBranchesService{ + GetFn: func(ctx context.Context, req *ps.GetPostgresBranchRequest) (*ps.PostgresBranch, error) { + return nil, &ps.Error{Code: ps.ErrNotFound} + }, + } + + ch := &cmdutil.Helper{ + Printer: p, + Config: &config.Config{Organization: "planetscale"}, + Client: func() (*ps.Client, error) { + return &ps.Client{PostgresRoles: roles, PostgresBranches: branches}, nil + }, + } + + cmd := DefaultCmd(ch) + cmd.SetArgs([]string{"mydb", "main"}) + err := cmd.Execute() + c.Assert(err, qt.IsNotNil) + c.Assert(err.Error(), qt.Contains, "does not exist") +}