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.organizationMembers(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/xerrors"
"github.com/coder/coder/v2/coderd/database/db2sdk"
"github.com/coder/pretty"
"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.
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) {

View File

@ -8,12 +8,13 @@ USAGE:
Aliases: organization, org, orgs
SUBCOMMANDS:
create Create a new organization.
members Manage organization members
roles Manage organization roles.
show 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.
create Create a new organization.
members Manage organization members
roles Manage organization roles.
settings Manage organization settings.
show 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.
OPTIONS:
-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)",
"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",
"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
| 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>create</code>](./organizations_create.md) | Create a new organization. |
| [<code>members</code>](./organizations_members.md) | Manage organization members |
| [<code>roles</code>](./organizations_roles.md) | Manage organization roles. |
| 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>create</code>](./organizations_create.md) | Create a new organization. |
| [<code>members</code>](./organizations_members.md) | Manage organization members |
| [<code>roles</code>](./organizations_roles.md) | Manage organization roles. |
| [<code>settings</code>](./organizations_settings.md) | Manage organization settings. |
## 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/rbac"
"github.com/coder/coder/v2/coderd/rbac/policy"
"github.com/coder/coder/v2/codersdk"
)
// @Summary Get group IdP Sync settings by organization
@ -61,6 +62,20 @@ func (api *API) patchGroupIDPSyncSettings(rw http.ResponseWriter, r *http.Reques
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
sysCtx := dbauthz.AsSystemRestricted(ctx)
err := api.IDPSync.UpdateGroupSettings(sysCtx, org.ID, api.Database, req)