|
| 1 | +package postgres |
| 2 | + |
| 3 | +import ( |
| 4 | + "errors" |
| 5 | + "fmt" |
| 6 | + |
| 7 | + "github.com/databricks/cli/libs/flags" |
| 8 | + "github.com/databricks/databricks-sdk-go/service/postgres" |
| 9 | + "github.com/spf13/cobra" |
| 10 | +) |
| 11 | + |
| 12 | +// createRoleOverride appends an example body to the auto-generated help and |
| 13 | +// rejects wrapped {"role": ...} bodies with a clear client-side error. |
| 14 | +// The --json flag binds to the inner Role object (CreateRoleRequest.Role, |
| 15 | +// JSON-tagged "role"), so users supply spec/name/etc. directly. Without an |
| 16 | +// example, the auto-generated `// TODO: complex arg: spec` flags leave no |
| 17 | +// hint about the body shape and the API's "Field 'role' is required" error |
| 18 | +// is unhelpful when the request body is wrong. |
| 19 | +func createRoleOverride(createRoleCmd *cobra.Command, _ *postgres.CreateRoleRequest) { |
| 20 | + prevPreRunE := createRoleCmd.PreRunE |
| 21 | + createRoleCmd.PreRunE = func(cmd *cobra.Command, args []string) error { |
| 22 | + if err := rejectWrappedRoleJSON(cmd); err != nil { |
| 23 | + return err |
| 24 | + } |
| 25 | + if prevPreRunE != nil { |
| 26 | + return prevPreRunE(cmd, args) |
| 27 | + } |
| 28 | + return nil |
| 29 | + } |
| 30 | + |
| 31 | + createRoleCmd.Long += ` |
| 32 | +
|
| 33 | + Body shape (passed via --json): fields go directly on the Role object. |
| 34 | + Do not wrap them in '{"role": ...}' — the CLI rejects wrapped bodies |
| 35 | + client-side with a hint pointing to the right shape. |
| 36 | +
|
| 37 | + Example — create a service-principal-backed role: |
| 38 | +
|
| 39 | + databricks postgres create-role projects/<PROJECT_ID>/branches/<BRANCH_ID> \ |
| 40 | + --role-id <SP_CLIENT_ID> \ |
| 41 | + --json '{"spec": {"identity_type": "SERVICE_PRINCIPAL", "postgres_role": "<SP_CLIENT_ID>", "auth_method": "LAKEBASE_OAUTH_V1"}}' |
| 42 | +
|
| 43 | + The example omits 'membership_roles' so the role starts with default |
| 44 | + privileges only — grant database/schema/table access separately via |
| 45 | + SQL, following least privilege. Set 'membership_roles' (e.g. |
| 46 | + ["DATABRICKS_SUPERUSER"]) only when broad administrative access is |
| 47 | + intentional. |
| 48 | +
|
| 49 | + See databricks-sdk-go/service/postgres.RoleRoleSpec for the full set of |
| 50 | + spec fields.` |
| 51 | +} |
| 52 | + |
| 53 | +// rejectWrappedRoleJSON returns a clear error when --json is a top-level |
| 54 | +// object containing a "role" key. Without this guard the generated unmarshal |
| 55 | +// strips the unknown outer "role" field with a warning and ships an empty |
| 56 | +// body, and the server rejects with a confusing "Field 'role' is required" |
| 57 | +// message. |
| 58 | +func rejectWrappedRoleJSON(cmd *cobra.Command) error { |
| 59 | + // These checks are internal invariants — postgres create-role is a |
| 60 | + // generated command and always has a *flags.JsonFlag for --json. A |
| 61 | + // future codegen/refactor change could break that, and we want loud |
| 62 | + // breakage rather than a silently-disabled guard. |
| 63 | + flag := cmd.Flags().Lookup("json") |
| 64 | + if flag == nil { |
| 65 | + return errors.New("internal: postgres create-role expected a --json flag; this override is wired to the wrong command") |
| 66 | + } |
| 67 | + jf, ok := flag.Value.(*flags.JsonFlag) |
| 68 | + if !ok { |
| 69 | + return fmt.Errorf("internal: postgres create-role --json flag has unexpected type %T; expected *flags.JsonFlag", flag.Value) |
| 70 | + } |
| 71 | + return jf.RejectWrappedJSON("role", `databricks postgres create-role projects/<PROJECT_ID>/branches/<BRANCH_ID> \ |
| 72 | + --role-id <SP_CLIENT_ID> \ |
| 73 | + --json '{"spec": {"identity_type": "SERVICE_PRINCIPAL", "postgres_role": "<SP_CLIENT_ID>", "auth_method": "LAKEBASE_OAUTH_V1"}}'`) |
| 74 | +} |
| 75 | + |
| 76 | +func init() { |
| 77 | + createRoleOverrides = append(createRoleOverrides, createRoleOverride) |
| 78 | +} |
0 commit comments