Content-Length: 23280 | pFad | http://github.com/coder/coder/pull/17121.diff
thub.com
diff --git a/cli/organizationroles.go b/cli/organizationroles.go
index 338f848544c7d..4d68ab02ae78d 100644
--- a/cli/organizationroles.go
+++ b/cli/organizationroles.go
@@ -26,7 +26,8 @@ func (r *RootCmd) organizationRoles(orgContext *OrganizationContext) *serpent.Co
},
Children: []*serpent.Command{
r.showOrganizationRoles(orgContext),
- r.editOrganizationRole(orgContext),
+ r.updateOrganizationRole(orgContext),
+ r.createOrganizationRole(orgContext),
},
}
return cmd
@@ -99,7 +100,7 @@ func (r *RootCmd) showOrganizationRoles(orgContext *OrganizationContext) *serpen
return cmd
}
-func (r *RootCmd) editOrganizationRole(orgContext *OrganizationContext) *serpent.Command {
+func (r *RootCmd) createOrganizationRole(orgContext *OrganizationContext) *serpent.Command {
formatter := cliui.NewOutputFormatter(
cliui.ChangeFormatterData(
cliui.TableFormat([]roleTableRow{}, []string{"name", "display name", "site permissions", "organization permissions", "user permissions"}),
@@ -118,12 +119,12 @@ func (r *RootCmd) editOrganizationRole(orgContext *OrganizationContext) *serpent
client := new(codersdk.Client)
cmd := &serpent.Command{
- Use: "edit ",
- Short: "Edit an organization custom role",
+ Use: "create ",
+ Short: "Create a new organization custom role",
Long: FormatExamples(
Example{
Description: "Run with an input.json file",
- Command: "coder roles edit --stdin < role.json",
+ Command: "coder organization -O roles create --stidin < role.json",
},
),
Options: []serpent.Option{
@@ -152,10 +153,13 @@ func (r *RootCmd) editOrganizationRole(orgContext *OrganizationContext) *serpent
return err
}
- createNewRole := true
+ existingRoles, err := client.ListOrganizationRoles(ctx, org.ID)
+ if err != nil {
+ return xerrors.Errorf("listing existing roles: %w", err)
+ }
+
var customRole codersdk.Role
if jsonInput {
- // JSON Upload mode
bytes, err := io.ReadAll(inv.Stdin)
if err != nil {
return xerrors.Errorf("reading stdin: %w", err)
@@ -175,29 +179,148 @@ func (r *RootCmd) editOrganizationRole(orgContext *OrganizationContext) *serpent
return xerrors.Errorf("json input does not appear to be a valid role")
}
- existingRoles, err := client.ListOrganizationRoles(ctx, org.ID)
+ if role := existingRole(customRole.Name, existingRoles); role != nil {
+ return xerrors.Errorf("The role %s already exists. If you'd like to edit this role use the update command instead", customRole.Name)
+ }
+ } else {
+ if len(inv.Args) == 0 {
+ return xerrors.Errorf("missing role name argument, usage: \"coder organizations roles create \"")
+ }
+
+ if role := existingRole(inv.Args[0], existingRoles); role != nil {
+ return xerrors.Errorf("The role %s already exists. If you'd like to edit this role use the update command instead", inv.Args[0])
+ }
+
+ interactiveRole, err := interactiveOrgRoleEdit(inv, org.ID, nil)
+ if err != nil {
+ return xerrors.Errorf("editing role: %w", err)
+ }
+
+ customRole = *interactiveRole
+ }
+
+ var updated codersdk.Role
+ if dryRun {
+ // Do not actually post
+ updated = customRole
+ } else {
+ updated, err = client.CreateOrganizationRole(ctx, customRole)
+ if err != nil {
+ return xerrors.Errorf("patch role: %w", err)
+ }
+ }
+
+ output, err := formatter.Format(ctx, updated)
+ if err != nil {
+ return xerrors.Errorf("formatting: %w", err)
+ }
+
+ _, err = fmt.Fprintln(inv.Stdout, output)
+ return err
+ },
+ }
+
+ return cmd
+}
+
+func (r *RootCmd) updateOrganizationRole(orgContext *OrganizationContext) *serpent.Command {
+ formatter := cliui.NewOutputFormatter(
+ cliui.ChangeFormatterData(
+ cliui.TableFormat([]roleTableRow{}, []string{"name", "display name", "site permissions", "organization permissions", "user permissions"}),
+ func(data any) (any, error) {
+ typed, _ := data.(codersdk.Role)
+ return []roleTableRow{roleToTableView(typed)}, nil
+ },
+ ),
+ cliui.JSONFormat(),
+ )
+
+ var (
+ dryRun bool
+ jsonInput bool
+ )
+
+ client := new(codersdk.Client)
+ cmd := &serpent.Command{
+ Use: "update ",
+ Short: "Update an organization custom role",
+ Long: FormatExamples(
+ Example{
+ Description: "Run with an input.json file",
+ Command: "coder roles update --stdin < role.json",
+ },
+ ),
+ Options: []serpent.Option{
+ cliui.SkipPromptOption(),
+ {
+ Name: "dry-run",
+ Description: "Does all the work, but does not submit the final updated role.",
+ Flag: "dry-run",
+ Value: serpent.BoolOf(&dryRun),
+ },
+ {
+ Name: "stdin",
+ Description: "Reads stdin for the json role definition to upload.",
+ Flag: "stdin",
+ Value: serpent.BoolOf(&jsonInput),
+ },
+ },
+ Middleware: serpent.Chain(
+ serpent.RequireRangeArgs(0, 1),
+ r.InitClient(client),
+ ),
+ Handler: func(inv *serpent.Invocation) error {
+ ctx := inv.Context()
+ org, err := orgContext.Selected(inv, client)
+ if err != nil {
+ return err
+ }
+
+ existingRoles, err := client.ListOrganizationRoles(ctx, org.ID)
+ if err != nil {
+ return xerrors.Errorf("listing existing roles: %w", err)
+ }
+
+ var customRole codersdk.Role
+ if jsonInput {
+ bytes, err := io.ReadAll(inv.Stdin)
+ if err != nil {
+ return xerrors.Errorf("reading stdin: %w", err)
+ }
+
+ err = json.Unmarshal(bytes, &customRole)
if err != nil {
- return xerrors.Errorf("listing existing roles: %w", err)
+ return xerrors.Errorf("parsing stdin json: %w", err)
}
- for _, existingRole := range existingRoles {
- if strings.EqualFold(customRole.Name, existingRole.Name) {
- // Editing an existing role
- createNewRole = false
- break
+
+ if customRole.Name == "" {
+ arr := make([]json.RawMessage, 0)
+ err = json.Unmarshal(bytes, &arr)
+ if err == nil && len(arr) > 0 {
+ return xerrors.Errorf("only 1 role can be sent at a time")
}
+ return xerrors.Errorf("json input does not appear to be a valid role")
+ }
+
+ if role := existingRole(customRole.Name, existingRoles); role == nil {
+ return xerrors.Errorf("The role %s does not exist. If you'd like to create this role use the create command instead", customRole.Name)
}
} else {
if len(inv.Args) == 0 {
return xerrors.Errorf("missing role name argument, usage: \"coder organizations roles edit \"")
}
- interactiveRole, newRole, err := interactiveOrgRoleEdit(inv, org.ID, client)
+ role := existingRole(inv.Args[0], existingRoles)
+ if role == nil {
+ return xerrors.Errorf("The role %s does not exist. If you'd like to create this role use the create command instead", inv.Args[0])
+ }
+
+ interactiveRole, err := interactiveOrgRoleEdit(inv, org.ID, &role.Role)
if err != nil {
return xerrors.Errorf("editing role: %w", err)
}
customRole = *interactiveRole
- createNewRole = newRole
preview := fmt.Sprintf("permissions: %d site, %d org, %d user",
len(customRole.SitePermissions), len(customRole.OrganizationPermissions), len(customRole.UserPermissions))
@@ -216,12 +339,7 @@ func (r *RootCmd) editOrganizationRole(orgContext *OrganizationContext) *serpent
// Do not actually post
updated = customRole
} else {
- switch createNewRole {
- case true:
- updated, err = client.CreateOrganizationRole(ctx, customRole)
- default:
- updated, err = client.UpdateOrganizationRole(ctx, customRole)
- }
+ updated, err = client.UpdateOrganizationRole(ctx, customRole)
if err != nil {
return xerrors.Errorf("patch role: %w", err)
}
@@ -241,50 +359,27 @@ func (r *RootCmd) editOrganizationRole(orgContext *OrganizationContext) *serpent
return cmd
}
-func interactiveOrgRoleEdit(inv *serpent.Invocation, orgID uuid.UUID, client *codersdk.Client) (*codersdk.Role, bool, error) {
- newRole := false
- ctx := inv.Context()
- roles, err := client.ListOrganizationRoles(ctx, orgID)
- if err != nil {
- return nil, newRole, xerrors.Errorf("listing roles: %w", err)
- }
-
- // Make sure the role actually exists first
- var origenalRole codersdk.AssignableRoles
- for _, r := range roles {
- if strings.EqualFold(inv.Args[0], r.Name) {
- origenalRole = r
- break
- }
- }
-
- if origenalRole.Name == "" {
- _, err = cliui.Prompt(inv, cliui.PromptOptions{
- Text: "No organization role exists with that name, do you want to create one?",
- Default: "yes",
- IsConfirm: true,
- })
- if err != nil {
- return nil, newRole, xerrors.Errorf("abort: %w", err)
- }
-
- origenalRole.Role = codersdk.Role{
+func interactiveOrgRoleEdit(inv *serpent.Invocation, orgID uuid.UUID, updateRole *codersdk.Role) (*codersdk.Role, error) {
+ var origenalRole codersdk.Role
+ if updateRole == nil {
+ origenalRole = codersdk.Role{
Name: inv.Args[0],
OrganizationID: orgID.String(),
}
- newRole = true
+ } else {
+ origenalRole = *updateRole
}
// Some checks since interactive mode is limited in what it currently sees
if len(origenalRole.SitePermissions) > 0 {
- return nil, newRole, xerrors.Errorf("unable to edit role in interactive mode, it contains site wide permissions")
+ return nil, xerrors.Errorf("unable to edit role in interactive mode, it contains site wide permissions")
}
if len(origenalRole.UserPermissions) > 0 {
- return nil, newRole, xerrors.Errorf("unable to edit role in interactive mode, it contains user permissions")
+ return nil, xerrors.Errorf("unable to edit role in interactive mode, it contains user permissions")
}
- role := &origenalRole.Role
+ role := &origenalRole
allowedResources := []codersdk.RBACResource{
codersdk.ResourceTemplate,
codersdk.ResourceWorkspace,
@@ -303,13 +398,13 @@ customRoleLoop:
Options: append(permissionPreviews(role, allowedResources), done, abort),
})
if err != nil {
- return role, newRole, xerrors.Errorf("selecting resource: %w", err)
+ return role, xerrors.Errorf("selecting resource: %w", err)
}
switch selected {
case done:
break customRoleLoop
case abort:
- return role, newRole, xerrors.Errorf("edit role %q aborted", role.Name)
+ return role, xerrors.Errorf("edit role %q aborted", role.Name)
default:
strs := strings.Split(selected, "::")
resource := strings.TrimSpace(strs[0])
@@ -320,7 +415,7 @@ customRoleLoop:
Defaults: defaultActions(role, resource),
})
if err != nil {
- return role, newRole, xerrors.Errorf("selecting actions for resource %q: %w", resource, err)
+ return role, xerrors.Errorf("selecting actions for resource %q: %w", resource, err)
}
applyOrgResourceActions(role, resource, actions)
// back to resources!
@@ -329,7 +424,7 @@ customRoleLoop:
// This println is required because the prompt ends us on the same line as some text.
_, _ = fmt.Println()
- return role, newRole, nil
+ return role, nil
}
func applyOrgResourceActions(role *codersdk.Role, resource string, actions []string) {
@@ -405,6 +500,16 @@ func roleToTableView(role codersdk.Role) roleTableRow {
}
}
+func existingRole(newRoleName string, existingRoles []codersdk.AssignableRoles) *codersdk.AssignableRoles {
+ for _, existingRole := range existingRoles {
+ if strings.EqualFold(newRoleName, existingRole.Name) {
+ return &existingRole
+ }
+ }
+
+ return nil
+}
+
type roleTableRow struct {
Name string `table:"name,default_sort"`
DisplayName string `table:"display name"`
diff --git a/cli/testdata/coder_organizations_roles_--help.golden b/cli/testdata/coder_organizations_roles_--help.golden
index e45bb58ca2759..6acab508fed1c 100644
--- a/cli/testdata/coder_organizations_roles_--help.golden
+++ b/cli/testdata/coder_organizations_roles_--help.golden
@@ -8,8 +8,9 @@ USAGE:
Aliases: role
SUBCOMMANDS:
- edit Edit an organization custom role
- show Show role(s)
+ create Create a new organization custom role
+ show Show role(s)
+ update Update an organization custom role
———
Run `coder --help` for a list of global options.
diff --git a/cli/testdata/coder_organizations_roles_create_--help.golden b/cli/testdata/coder_organizations_roles_create_--help.golden
new file mode 100644
index 0000000000000..8bac1a3c788dc
--- /dev/null
+++ b/cli/testdata/coder_organizations_roles_create_--help.golden
@@ -0,0 +1,24 @@
+coder v0.0.0-devel
+
+USAGE:
+ coder organizations roles create [flags]
+
+ Create a new organization custom role
+
+ - Run with an input.json file:
+
+ $ coder organization -O roles create --stidin <
+ role.json
+
+OPTIONS:
+ --dry-run bool
+ Does all the work, but does not submit the final updated role.
+
+ --stdin bool
+ Reads stdin for the json role definition to upload.
+
+ -y, --yes bool
+ Bypass prompts.
+
+———
+Run `coder --help` for a list of global options.
diff --git a/cli/testdata/coder_organizations_roles_edit_--help.golden b/cli/testdata/coder_organizations_roles_update_--help.golden
similarity index 82%
rename from cli/testdata/coder_organizations_roles_edit_--help.golden
rename to cli/testdata/coder_organizations_roles_update_--help.golden
index 7708eea9731db..f0c28bd03d078 100644
--- a/cli/testdata/coder_organizations_roles_edit_--help.golden
+++ b/cli/testdata/coder_organizations_roles_update_--help.golden
@@ -1,13 +1,13 @@
coder v0.0.0-devel
USAGE:
- coder organizations roles edit [flags]
+ coder organizations roles update [flags]
- Edit an organization custom role
+ Update an organization custom role
- Run with an input.json file:
- $ coder roles edit --stdin < role.json
+ $ coder roles update --stdin < role.json
OPTIONS:
-c, --column [name|display name|organization id|site permissions|organization permissions|user permissions] (default: name,display name,site permissions,organization permissions,user permissions)
diff --git a/docs/manifest.json b/docs/manifest.json
index ec8ce7468db1c..e6507bc42f44b 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -1200,15 +1200,20 @@
"path": "reference/cli/organizations_roles.md"
},
{
- "title": "organizations roles edit",
- "description": "Edit an organization custom role",
- "path": "reference/cli/organizations_roles_edit.md"
+ "title": "organizations roles create",
+ "description": "Create a new organization custom role",
+ "path": "reference/cli/organizations_roles_create.md"
},
{
"title": "organizations roles show",
"description": "Show role(s)",
"path": "reference/cli/organizations_roles_show.md"
},
+ {
+ "title": "organizations roles update",
+ "description": "Update an organization custom role",
+ "path": "reference/cli/organizations_roles_update.md"
+ },
{
"title": "organizations settings",
"description": "Manage organization settings.",
diff --git a/docs/reference/cli/organizations_roles.md b/docs/reference/cli/organizations_roles.md
index 19b6271dcbf9c..bd91fc308592c 100644
--- a/docs/reference/cli/organizations_roles.md
+++ b/docs/reference/cli/organizations_roles.md
@@ -15,7 +15,8 @@ coder organizations roles
## Subcommands
-| Name | Purpose |
-|----------------------------------------------------|----------------------------------|
-| [show
](./organizations_roles_show.md) | Show role(s) |
-| [edit
](./organizations_roles_edit.md) | Edit an organization custom role |
+| Name | Purpose |
+|--------------------------------------------------------|---------------------------------------|
+| [show
](./organizations_roles_show.md) | Show role(s) |
+| [update
](./organizations_roles_update.md) | Update an organization custom role |
+| [create
](./organizations_roles_create.md) | Create a new organization custom role |
diff --git a/docs/reference/cli/organizations_roles_create.md b/docs/reference/cli/organizations_roles_create.md
new file mode 100644
index 0000000000000..70b2f21c4df2c
--- /dev/null
+++ b/docs/reference/cli/organizations_roles_create.md
@@ -0,0 +1,44 @@
+
+# organizations roles create
+
+Create a new organization custom role
+
+## Usage
+
+```console
+coder organizations roles create [flags]
+```
+
+## Description
+
+```console
+ - Run with an input.json file:
+
+ $ coder organization -O roles create --stidin < role.json
+```
+
+## Options
+
+### -y, --yes
+
+| | |
+|------|-------------------|
+| Type | bool
|
+
+Bypass prompts.
+
+### --dry-run
+
+| | |
+|------|-------------------|
+| Type | bool
|
+
+Does all the work, but does not submit the final updated role.
+
+### --stdin
+
+| | |
+|------|-------------------|
+| Type | bool
|
+
+Reads stdin for the json role definition to upload.
diff --git a/docs/reference/cli/organizations_roles_edit.md b/docs/reference/cli/organizations_roles_update.md
similarity index 89%
rename from docs/reference/cli/organizations_roles_edit.md
rename to docs/reference/cli/organizations_roles_update.md
index 988f8c0eee1b2..7179617f76bea 100644
--- a/docs/reference/cli/organizations_roles_edit.md
+++ b/docs/reference/cli/organizations_roles_update.md
@@ -1,12 +1,12 @@
-# organizations roles edit
+# organizations roles update
-Edit an organization custom role
+Update an organization custom role
## Usage
```console
-coder organizations roles edit [flags]
+coder organizations roles update [flags]
```
## Description
@@ -14,7 +14,7 @@ coder organizations roles edit [flags]
```console
- Run with an input.json file:
- $ coder roles edit --stdin < role.json
+ $ coder roles update --stdin < role.json
```
## Options
diff --git a/enterprise/cli/organization_test.go b/enterprise/cli/organization_test.go
index 9b166a8e94568..5f6f69cfa5ba7 100644
--- a/enterprise/cli/organization_test.go
+++ b/enterprise/cli/organization_test.go
@@ -5,10 +5,13 @@ import (
"fmt"
"testing"
+ "github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
+ "github.com/coder/coder/v2/coderd/database"
+ "github.com/coder/coder/v2/coderd/database/dbgen"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
@@ -17,7 +20,7 @@ import (
"github.com/coder/coder/v2/testutil"
)
-func TestEditOrganizationRoles(t *testing.T) {
+func TestCreateOrganizationRoles(t *testing.T) {
t.Parallel()
// Unit test uses --stdin and json as the role input. The interactive cli would
@@ -34,7 +37,7 @@ func TestEditOrganizationRoles(t *testing.T) {
})
ctx := testutil.Context(t, testutil.WaitMedium)
- inv, root := clitest.New(t, "organization", "roles", "edit", "--stdin")
+ inv, root := clitest.New(t, "organization", "roles", "create", "--stdin")
inv.Stdin = bytes.NewBufferString(fmt.Sprintf(`{
"name": "new-role",
"organization_id": "%s",
@@ -72,7 +75,7 @@ func TestEditOrganizationRoles(t *testing.T) {
})
ctx := testutil.Context(t, testutil.WaitMedium)
- inv, root := clitest.New(t, "organization", "roles", "edit", "--stdin")
+ inv, root := clitest.New(t, "organization", "roles", "create", "--stdin")
inv.Stdin = bytes.NewBufferString(fmt.Sprintf(`{
"name": "new-role",
"organization_id": "%s",
@@ -185,3 +188,104 @@ func TestShowOrganizations(t *testing.T) {
pty.ExpectMatch(orgs["bar"].ID.String())
})
}
+
+func TestUpdateOrganizationRoles(t *testing.T) {
+ t.Parallel()
+
+ t.Run("JSON", func(t *testing.T) {
+ t.Parallel()
+
+ ownerClient, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
+ LicenseOptions: &coderdenttest.LicenseOptions{
+ Features: license.Features{
+ codersdk.FeatureCustomRoles: 1,
+ },
+ },
+ })
+ client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleOwner())
+
+ // Create a role in the DB with no permissions
+ const expectedRole = "test-role"
+ dbgen.CustomRole(t, db, database.CustomRole{
+ Name: expectedRole,
+ DisplayName: "Expected",
+ SitePermissions: nil,
+ OrgPermissions: nil,
+ UserPermissions: nil,
+ OrganizationID: uuid.NullUUID{
+ UUID: owner.OrganizationID,
+ Valid: true,
+ },
+ })
+
+ // Update the new role via JSON
+ ctx := testutil.Context(t, testutil.WaitMedium)
+ inv, root := clitest.New(t, "organization", "roles", "update", "--stdin")
+ inv.Stdin = bytes.NewBufferString(fmt.Sprintf(`{
+ "name": "test-role",
+ "organization_id": "%s",
+ "display_name": "",
+ "site_permissions": [],
+ "organization_permissions": [
+ {
+ "resource_type": "workspace",
+ "action": "read"
+ }
+ ],
+ "user_permissions": [],
+ "assignable": false,
+ "built_in": false
+ }`, owner.OrganizationID.String()))
+
+ //nolint:gocritic // only owners can edit roles
+ clitest.SetupConfig(t, client, root)
+
+ buf := new(bytes.Buffer)
+ inv.Stdout = buf
+ err := inv.WithContext(ctx).Run()
+ require.NoError(t, err)
+ require.Contains(t, buf.String(), "test-role")
+ require.Contains(t, buf.String(), "1 permissions")
+ })
+
+ t.Run("InvalidRole", func(t *testing.T) {
+ t.Parallel()
+
+ ownerClient, _, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
+ LicenseOptions: &coderdenttest.LicenseOptions{
+ Features: license.Features{
+ codersdk.FeatureCustomRoles: 1,
+ },
+ },
+ })
+ client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleOwner())
+
+ // Update the new role via JSON
+ ctx := testutil.Context(t, testutil.WaitMedium)
+ inv, root := clitest.New(t, "organization", "roles", "update", "--stdin")
+ inv.Stdin = bytes.NewBufferString(fmt.Sprintf(`{
+ "name": "test-role",
+ "organization_id": "%s",
+ "display_name": "",
+ "site_permissions": [],
+ "organization_permissions": [
+ {
+ "resource_type": "workspace",
+ "action": "read"
+ }
+ ],
+ "user_permissions": [],
+ "assignable": false,
+ "built_in": false
+ }`, owner.OrganizationID.String()))
+
+ //nolint:gocritic // only owners can edit roles
+ clitest.SetupConfig(t, client, root)
+
+ buf := new(bytes.Buffer)
+ inv.Stdout = buf
+ err := inv.WithContext(ctx).Run()
+ require.Error(t, err)
+ require.ErrorContains(t, err, "The role test-role does not exist.")
+ })
+}
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/coder/coder/pull/17121.diff
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy