chore: add cli command to fetch group sync settings as json (#14694)

* chore: add cli command to fetch group sync settings as json
This commit is contained in:
Steven Masley
2024-09-17 14:08:33 -05:00
committed by GitHub
parent 45160c7679
commit d96adad56f
20 changed files with 626 additions and 13 deletions

View File

@ -26,6 +26,7 @@ func (r *RootCmd) organizations() *serpent.Command {
r.createOrganization(), r.createOrganization(),
r.organizationMembers(orgContext), r.organizationMembers(orgContext),
r.organizationRoles(orgContext), r.organizationRoles(orgContext),
r.organizationSettings(orgContext),
}, },
} }

209
cli/organizationsettings.go Normal file
View File

@ -0,0 +1,209 @@
package cli
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"github.com/google/uuid"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)
func (r *RootCmd) organizationSettings(orgContext *OrganizationContext) *serpent.Command {
settings := []organizationSetting{
{
Name: "group-sync",
Aliases: []string{"groupsync"},
Short: "Group sync settings to sync groups from an IdP.",
Patch: func(ctx context.Context, cli *codersdk.Client, org uuid.UUID, input json.RawMessage) (any, error) {
var req codersdk.GroupSyncSettings
err := json.Unmarshal(input, &req)
if err != nil {
return nil, xerrors.Errorf("unmarshalling group sync settings: %w", err)
}
return cli.PatchGroupIDPSyncSettings(ctx, org.String(), req)
},
Fetch: func(ctx context.Context, cli *codersdk.Client, org uuid.UUID) (any, error) {
return cli.GroupIDPSyncSettings(ctx, org.String())
},
},
{
Name: "role-sync",
Aliases: []string{"rolesync"},
Short: "Role sync settings to sync organization roles from an IdP.",
Patch: func(ctx context.Context, cli *codersdk.Client, org uuid.UUID, input json.RawMessage) (any, error) {
var req codersdk.RoleSyncSettings
err := json.Unmarshal(input, &req)
if err != nil {
return nil, xerrors.Errorf("unmarshalling role sync settings: %w", err)
}
return cli.PatchRoleIDPSyncSettings(ctx, org.String(), req)
},
Fetch: func(ctx context.Context, cli *codersdk.Client, org uuid.UUID) (any, error) {
return cli.RoleIDPSyncSettings(ctx, org.String())
},
},
}
cmd := &serpent.Command{
Use: "settings",
Short: "Manage organization settings.",
Aliases: []string{"setting"},
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
Children: []*serpent.Command{
r.printOrganizationSetting(orgContext, settings),
r.setOrganizationSettings(orgContext, settings),
},
}
return cmd
}
type organizationSetting struct {
Name string
Aliases []string
Short string
Patch func(ctx context.Context, cli *codersdk.Client, org uuid.UUID, input json.RawMessage) (any, error)
Fetch func(ctx context.Context, cli *codersdk.Client, org uuid.UUID) (any, error)
}
func (r *RootCmd) setOrganizationSettings(orgContext *OrganizationContext, settings []organizationSetting) *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "set",
Short: "Update specified organization setting.",
Long: FormatExamples(
Example{
Description: "Update group sync settings.",
Command: "coder organization settings set groupsync < input.json",
},
),
Options: []serpent.Option{},
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
}
for _, set := range settings {
set := set
patch := set.Patch
cmd.Children = append(cmd.Children, &serpent.Command{
Use: set.Name,
Aliases: set.Aliases,
Short: set.Short,
Options: []serpent.Option{},
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
ctx := inv.Context()
org, err := orgContext.Selected(inv, client)
if err != nil {
return err
}
// Read in the json
inputData, err := io.ReadAll(inv.Stdin)
if err != nil {
return xerrors.Errorf("reading stdin: %w", err)
}
output, err := patch(ctx, client, org.ID, inputData)
if err != nil {
return xerrors.Errorf("patching %q: %w", set.Name, err)
}
settingJSON, err := json.Marshal(output)
if err != nil {
return fmt.Errorf("failed to marshal organization setting %s: %w", inv.Args[0], err)
}
var dst bytes.Buffer
err = json.Indent(&dst, settingJSON, "", "\t")
if err != nil {
return fmt.Errorf("failed to indent organization setting as json %s: %w", inv.Args[0], err)
}
_, err = fmt.Fprintln(inv.Stdout, dst.String())
return err
},
})
}
return cmd
}
func (r *RootCmd) printOrganizationSetting(orgContext *OrganizationContext, settings []organizationSetting) *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "show",
Short: "Outputs specified organization setting.",
Long: FormatExamples(
Example{
Description: "Output group sync settings.",
Command: "coder organization settings show groupsync",
},
),
Options: []serpent.Option{},
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
}
for _, set := range settings {
set := set
fetch := set.Fetch
cmd.Children = append(cmd.Children, &serpent.Command{
Use: set.Name,
Aliases: set.Aliases,
Short: set.Short,
Options: []serpent.Option{},
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
ctx := inv.Context()
org, err := orgContext.Selected(inv, client)
if err != nil {
return err
}
output, err := fetch(ctx, client, org.ID)
if err != nil {
return xerrors.Errorf("patching %q: %w", set.Name, err)
}
settingJSON, err := json.Marshal(output)
if err != nil {
return fmt.Errorf("failed to marshal organization setting %s: %w", inv.Args[0], err)
}
var dst bytes.Buffer
err = json.Indent(&dst, settingJSON, "", "\t")
if err != nil {
return fmt.Errorf("failed to indent organization setting as json %s: %w", inv.Args[0], err)
}
_, err = fmt.Fprintln(inv.Stdout, dst.String())
return err
},
})
}
return cmd
}

View File

@ -29,6 +29,7 @@ import (
"golang.org/x/mod/semver" "golang.org/x/mod/semver"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/database/db2sdk"
"github.com/coder/pretty" "github.com/coder/pretty"
"github.com/coder/coder/v2/buildinfo" "github.com/coder/coder/v2/buildinfo"
@ -657,7 +658,10 @@ func (o *OrganizationContext) Selected(inv *serpent.Invocation, client *codersdk
} }
// No org selected, and we are more than 1? Return an error. // No org selected, and we are more than 1? Return an error.
return codersdk.Organization{}, xerrors.Errorf("Must select an organization with --org=<org_name>.") validOrgs := db2sdk.List(orgs, func(org codersdk.Organization) string {
return fmt.Sprintf("%q", org.Name)
})
return codersdk.Organization{}, xerrors.Errorf("Must select an organization with --org=<org_name>. Choose from: %s", strings.Join(validOrgs, ", "))
} }
func splitNamedWorkspace(identifier string) (owner string, workspaceName string, err error) { func splitNamedWorkspace(identifier string) (owner string, workspaceName string, err error) {

View File

@ -8,12 +8,13 @@ USAGE:
Aliases: organization, org, orgs Aliases: organization, org, orgs
SUBCOMMANDS: SUBCOMMANDS:
create Create a new organization. create Create a new organization.
members Manage organization members members Manage organization members
roles Manage organization roles. roles Manage organization roles.
show Show the organization. Using "selected" will show the selected settings Manage organization settings.
organization from the "--org" flag. Using "me" will show all show Show the organization. Using "selected" will show the selected
organizations you are a member of. organization from the "--org" flag. Using "me" will show all
organizations you are a member of.
OPTIONS: OPTIONS:
-O, --org string, $CODER_ORGANIZATION -O, --org string, $CODER_ORGANIZATION

View File

@ -0,0 +1,15 @@
coder v0.0.0-devel
USAGE:
coder organizations settings
Manage organization settings.
Aliases: setting
SUBCOMMANDS:
set Update specified organization setting.
show Outputs specified organization setting.
———
Run `coder --help` for a list of global options.

View File

@ -0,0 +1,17 @@
coder v0.0.0-devel
USAGE:
coder organizations settings set
Update specified organization setting.
- Update group sync settings.:
$ coder organization settings set groupsync < input.json
SUBCOMMANDS:
group-sync Group sync settings to sync groups from an IdP.
role-sync Role sync settings to sync organization roles from an IdP.
———
Run `coder --help` for a list of global options.

View File

@ -0,0 +1,17 @@
coder v0.0.0-devel
USAGE:
coder organizations settings set
Update specified organization setting.
- Update group sync settings.:
$ coder organization settings set groupsync < input.json
SUBCOMMANDS:
group-sync Group sync settings to sync groups from an IdP.
role-sync Role sync settings to sync organization roles from an IdP.
———
Run `coder --help` for a list of global options.

View File

@ -0,0 +1,17 @@
coder v0.0.0-devel
USAGE:
coder organizations settings show
Outputs specified organization setting.
- Output group sync settings.:
$ coder organization settings show groupsync
SUBCOMMANDS:
group-sync Group sync settings to sync groups from an IdP.
role-sync Role sync settings to sync organization roles from an IdP.
———
Run `coder --help` for a list of global options.

View File

@ -0,0 +1,17 @@
coder v0.0.0-devel
USAGE:
coder organizations settings show
Outputs specified organization setting.
- Output group sync settings.:
$ coder organization settings show groupsync
SUBCOMMANDS:
group-sync Group sync settings to sync groups from an IdP.
role-sync Role sync settings to sync organization roles from an IdP.
———
Run `coder --help` for a list of global options.

View File

@ -857,6 +857,41 @@
"description": "Show role(s)", "description": "Show role(s)",
"path": "reference/cli/organizations_roles_show.md" "path": "reference/cli/organizations_roles_show.md"
}, },
{
"title": "organizations settings",
"description": "Manage organization settings.",
"path": "reference/cli/organizations_settings.md"
},
{
"title": "organizations settings set",
"description": "Update specified organization setting.",
"path": "reference/cli/organizations_settings_set.md"
},
{
"title": "organizations settings set group-sync",
"description": "Group sync settings to sync groups from an IdP.",
"path": "reference/cli/organizations_settings_set_group-sync.md"
},
{
"title": "organizations settings set role-sync",
"description": "Role sync settings to sync organization roles from an IdP.",
"path": "reference/cli/organizations_settings_set_role-sync.md"
},
{
"title": "organizations settings show",
"description": "Outputs specified organization setting.",
"path": "reference/cli/organizations_settings_show.md"
},
{
"title": "organizations settings show group-sync",
"description": "Group sync settings to sync groups from an IdP.",
"path": "reference/cli/organizations_settings_show_group-sync.md"
},
{
"title": "organizations settings show role-sync",
"description": "Role sync settings to sync organization roles from an IdP.",
"path": "reference/cli/organizations_settings_show_role-sync.md"
},
{ {
"title": "organizations show", "title": "organizations show",
"description": "Show the organization. Using \"selected\" will show the selected organization from the \"--org\" flag. Using \"me\" will show all organizations you are a member of.", "description": "Show the organization. Using \"selected\" will show the selected organization from the \"--org\" flag. Using \"me\" will show all organizations you are a member of.",

View File

@ -18,12 +18,13 @@ coder organizations [flags] [subcommand]
## Subcommands ## Subcommands
| Name | Purpose | | Name | Purpose |
| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [<code>show</code>](./organizations_show.md) | Show the organization. Using "selected" will show the selected organization from the "--org" flag. Using "me" will show all organizations you are a member of. | | [<code>show</code>](./organizations_show.md) | Show the organization. Using "selected" will show the selected organization from the "--org" flag. Using "me" will show all organizations you are a member of. |
| [<code>create</code>](./organizations_create.md) | Create a new organization. | | [<code>create</code>](./organizations_create.md) | Create a new organization. |
| [<code>members</code>](./organizations_members.md) | Manage organization members | | [<code>members</code>](./organizations_members.md) | Manage organization members |
| [<code>roles</code>](./organizations_roles.md) | Manage organization roles. | | [<code>roles</code>](./organizations_roles.md) | Manage organization roles. |
| [<code>settings</code>](./organizations_settings.md) | Manage organization settings. |
## Options ## Options

View File

@ -0,0 +1,22 @@
<!-- DO NOT EDIT | GENERATED CONTENT -->
# organizations settings
Manage organization settings.
Aliases:
- setting
## Usage
```console
coder organizations settings
```
## Subcommands
| Name | Purpose |
| ----------------------------------------------------- | --------------------------------------- |
| [<code>show</code>](./organizations_settings_show.md) | Outputs specified organization setting. |
| [<code>set</code>](./organizations_settings_set.md) | Update specified organization setting. |

View File

@ -0,0 +1,26 @@
<!-- DO NOT EDIT | GENERATED CONTENT -->
# organizations settings set
Update specified organization setting.
## Usage
```console
coder organizations settings set
```
## Description
```console
- Update group sync settings.:
$ coder organization settings set groupsync < input.json
```
## Subcommands
| Name | Purpose |
| --------------------------------------------------------------------- | ---------------------------------------------------------- |
| [<code>group-sync</code>](./organizations_settings_set_group-sync.md) | Group sync settings to sync groups from an IdP. |
| [<code>role-sync</code>](./organizations_settings_set_role-sync.md) | Role sync settings to sync organization roles from an IdP. |

View File

@ -0,0 +1,15 @@
<!-- DO NOT EDIT | GENERATED CONTENT -->
# organizations settings set group-sync
Group sync settings to sync groups from an IdP.
Aliases:
- groupsync
## Usage
```console
coder organizations settings set group-sync
```

View File

@ -0,0 +1,15 @@
<!-- DO NOT EDIT | GENERATED CONTENT -->
# organizations settings set role-sync
Role sync settings to sync organization roles from an IdP.
Aliases:
- rolesync
## Usage
```console
coder organizations settings set role-sync
```

View File

@ -0,0 +1,26 @@
<!-- DO NOT EDIT | GENERATED CONTENT -->
# organizations settings show
Outputs specified organization setting.
## Usage
```console
coder organizations settings show
```
## Description
```console
- Output group sync settings.:
$ coder organization settings show groupsync
```
## Subcommands
| Name | Purpose |
| ---------------------------------------------------------------------- | ---------------------------------------------------------- |
| [<code>group-sync</code>](./organizations_settings_show_group-sync.md) | Group sync settings to sync groups from an IdP. |
| [<code>role-sync</code>](./organizations_settings_show_role-sync.md) | Role sync settings to sync organization roles from an IdP. |

View File

@ -0,0 +1,15 @@
<!-- DO NOT EDIT | GENERATED CONTENT -->
# organizations settings show group-sync
Group sync settings to sync groups from an IdP.
Aliases:
- groupsync
## Usage
```console
coder organizations settings show group-sync
```

View File

@ -0,0 +1,15 @@
<!-- DO NOT EDIT | GENERATED CONTENT -->
# organizations settings show role-sync
Role sync settings to sync organization roles from an IdP.
Aliases:
- rolesync
## Usage
```console
coder organizations settings show role-sync
```

View File

@ -0,0 +1,130 @@
package cli_test
import (
"bytes"
"encoding/json"
"regexp"
"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/rbac"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
"github.com/coder/coder/v2/enterprise/coderd/license"
"github.com/coder/coder/v2/testutil"
)
func TestUpdateGroupSync(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentMultiOrganization)}
owner, first := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: dv,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureMultipleOrganizations: 1,
},
},
})
ctx := testutil.Context(t, testutil.WaitLong)
inv, root := clitest.New(t, "organization", "settings", "set", "groupsync")
//nolint:gocritic // Using the owner, testing the cli not perms
clitest.SetupConfig(t, owner, root)
expectedSettings := codersdk.GroupSyncSettings{
Field: "groups",
Mapping: map[string][]uuid.UUID{
"test": {first.OrganizationID},
},
RegexFilter: regexp.MustCompile("^foo"),
AutoCreateMissing: true,
LegacyNameMapping: nil,
}
expectedData, err := json.Marshal(expectedSettings)
require.NoError(t, err)
buf := new(bytes.Buffer)
inv.Stdout = buf
inv.Stdin = bytes.NewBuffer(expectedData)
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
require.JSONEq(t, string(expectedData), buf.String())
// Now read it back
inv, root = clitest.New(t, "organization", "settings", "show", "groupsync")
//nolint:gocritic // Using the owner, testing the cli not perms
clitest.SetupConfig(t, owner, root)
buf = new(bytes.Buffer)
inv.Stdout = buf
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
require.JSONEq(t, string(expectedData), buf.String())
})
}
func TestUpdateRoleSync(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentMultiOrganization)}
owner, _ := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: dv,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureMultipleOrganizations: 1,
},
},
})
ctx := testutil.Context(t, testutil.WaitLong)
inv, root := clitest.New(t, "organization", "settings", "set", "rolesync")
//nolint:gocritic // Using the owner, testing the cli not perms
clitest.SetupConfig(t, owner, root)
expectedSettings := codersdk.RoleSyncSettings{
Field: "roles",
Mapping: map[string][]string{
"test": {rbac.RoleOrgAdmin()},
},
}
expectedData, err := json.Marshal(expectedSettings)
require.NoError(t, err)
buf := new(bytes.Buffer)
inv.Stdout = buf
inv.Stdin = bytes.NewBuffer(expectedData)
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
require.JSONEq(t, string(expectedData), buf.String())
// Now read it back
inv, root = clitest.New(t, "organization", "settings", "show", "rolesync")
//nolint:gocritic // Using the owner, testing the cli not perms
clitest.SetupConfig(t, owner, root)
buf = new(bytes.Buffer)
inv.Stdout = buf
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
require.JSONEq(t, string(expectedData), buf.String())
})
}

View File

@ -9,6 +9,7 @@ import (
"github.com/coder/coder/v2/coderd/idpsync" "github.com/coder/coder/v2/coderd/idpsync"
"github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/rbac/policy" "github.com/coder/coder/v2/coderd/rbac/policy"
"github.com/coder/coder/v2/codersdk"
) )
// @Summary Get group IdP Sync settings by organization // @Summary Get group IdP Sync settings by organization
@ -61,6 +62,20 @@ func (api *API) patchGroupIDPSyncSettings(rw http.ResponseWriter, r *http.Reques
return return
} }
if len(req.LegacyNameMapping) > 0 {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Unexpected field 'legacy_group_name_mapping'. Field not allowed, set to null or remove it.",
Detail: "legacy_group_name_mapping is deprecated, use mapping instead",
Validations: []codersdk.ValidationError{
{
Field: "legacy_group_name_mapping",
Detail: "field is not allowed",
},
},
})
return
}
//nolint:gocritic // Requires system context to update runtime config //nolint:gocritic // Requires system context to update runtime config
sysCtx := dbauthz.AsSystemRestricted(ctx) sysCtx := dbauthz.AsSystemRestricted(ctx)
err := api.IDPSync.UpdateGroupSettings(sysCtx, org.ID, api.Database, req) err := api.IDPSync.UpdateGroupSettings(sysCtx, org.ID, api.Database, req)