From 6a59ee11636821a83e3b143d24cd5e5ce4ea5be6 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 16 Sep 2024 14:18:55 -0500 Subject: [PATCH 1/9] chore: add cli command to fetch group sync settings as json --- cli/organization.go | 1 + cli/organizationsettings.go | 84 +++++++++++++++++++++++++++++++++++++ cli/root.go | 6 ++- 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 cli/organizationsettings.go diff --git a/cli/organization.go b/cli/organization.go index 0f83d9b1173c3..941219a0a6739 100644 --- a/cli/organization.go +++ b/cli/organization.go @@ -26,6 +26,7 @@ func (r *RootCmd) organizations() *serpent.Command { r.createOrganization(), r.organizationMembers(orgContext), r.organizationRoles(orgContext), + r.organizationSettings(orgContext), }, } diff --git a/cli/organizationsettings.go b/cli/organizationsettings.go new file mode 100644 index 0000000000000..afa772249c2c8 --- /dev/null +++ b/cli/organizationsettings.go @@ -0,0 +1,84 @@ +package cli + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" + + "github.com/coder/coder/v2/codersdk" + "github.com/coder/serpent" +) + +func (r *RootCmd) organizationSettings(orgContext *OrganizationContext) *serpent.Command { + cmd := &serpent.Command{ + Use: "settings", + Short: "Manage organization settings.", + Aliases: []string{"setting"}, + Handler: func(inv *serpent.Invocation) error { + return inv.Command.HelpHandler(inv) + }, + Hidden: true, + Children: []*serpent.Command{ + r.printOrganizationSetting(orgContext), + }, + } + return cmd +} + +func (r *RootCmd) printOrganizationSetting(orgContext *OrganizationContext) *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(1), + r.InitClient(client), + ), + Handler: func(inv *serpent.Invocation) error { + ctx := inv.Context() + org, err := orgContext.Selected(inv, client) + if err != nil { + return err + } + + var setting any + switch strings.ToLower(inv.Args[0]) { + case "groupsync", "group-sync": + setting, err = client.GroupIDPSyncSettings(ctx, org.ID.String()) + case "rolesync", "role-sync": + // TODO: Implement role sync settings. + return fmt.Errorf("role sync settings are not implemented") + default: + _, _ = fmt.Fprintln(inv.Stderr, "Valid organization settings are: 'groupsync', 'rolesync'") + return fmt.Errorf("unknown organization setting %s", inv.Args[0]) + } + + if err != nil { + return fmt.Errorf("failed to get organization setting %s: %w", inv.Args[0], err) + } + + settingJson, err := json.Marshal(setting) + 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 +} diff --git a/cli/root.go b/cli/root.go index 4945ecdd1656c..87a4f4b422b56 100644 --- a/cli/root.go +++ b/cli/root.go @@ -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=.") + 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=. Choose from: %s", strings.Join(validOrgs, ", ")) } func splitNamedWorkspace(identifier string) (owner string, workspaceName string, err error) { From 4e0146f217fd6254bcdf4252dc5fd2c22ee3f454 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 16 Sep 2024 15:56:34 -0500 Subject: [PATCH 2/9] linting --- cli/organizationsettings.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/organizationsettings.go b/cli/organizationsettings.go index afa772249c2c8..cd46a3f987c5b 100644 --- a/cli/organizationsettings.go +++ b/cli/organizationsettings.go @@ -65,13 +65,13 @@ func (r *RootCmd) printOrganizationSetting(orgContext *OrganizationContext) *ser return fmt.Errorf("failed to get organization setting %s: %w", inv.Args[0], err) } - settingJson, err := json.Marshal(setting) + settingJSON, err := json.Marshal(setting) 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") + 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) } From 87b9c454e1e366e65b6ea5cf04d667db272981e8 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 16 Sep 2024 16:34:13 -0500 Subject: [PATCH 3/9] work on cli test --- cli/organizationsettings.go | 72 +++++++++++++++++++++ enterprise/cli/organizationsettings_test.go | 71 ++++++++++++++++++++ enterprise/coderd/idpsync.go | 15 +++++ 3 files changed, 158 insertions(+) create mode 100644 enterprise/cli/organizationsettings_test.go diff --git a/cli/organizationsettings.go b/cli/organizationsettings.go index cd46a3f987c5b..e9323d9394cb7 100644 --- a/cli/organizationsettings.go +++ b/cli/organizationsettings.go @@ -4,8 +4,11 @@ import ( "bytes" "encoding/json" "fmt" + "io" "strings" + "golang.org/x/xerrors" + "github.com/coder/coder/v2/codersdk" "github.com/coder/serpent" ) @@ -21,6 +24,75 @@ func (r *RootCmd) organizationSettings(orgContext *OrganizationContext) *serpent Hidden: true, Children: []*serpent.Command{ r.printOrganizationSetting(orgContext), + r.updateOrganizationSetting(orgContext), + }, + } + return cmd +} + +func (r *RootCmd) updateOrganizationSetting(orgContext *OrganizationContext) *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(1), + 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) + } + + var setting any + switch strings.ToLower(inv.Args[0]) { + case "groupsync", "group-sync": + var req codersdk.GroupSyncSettings + err = json.Unmarshal(inputData, &req) + if err != nil { + return xerrors.Errorf("unmarshalling group sync settings: %w", err) + } + setting, err = client.PatchGroupIDPSyncSettings(ctx, org.ID.String(), req) + case "rolesync", "role-sync": + // TODO: Implement role sync settings. + return fmt.Errorf("role sync settings are not implemented") + default: + _, _ = fmt.Fprintln(inv.Stderr, "Valid organization settings are: 'groupsync', 'rolesync'") + return fmt.Errorf("unknown organization setting %s", inv.Args[0]) + } + + if err != nil { + return fmt.Errorf("failed to get organization setting %s: %w", inv.Args[0], err) + } + + settingJSON, err := json.Marshal(setting) + 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 diff --git a/enterprise/cli/organizationsettings_test.go b/enterprise/cli/organizationsettings_test.go new file mode 100644 index 0000000000000..4285bb0f3bcfc --- /dev/null +++ b/enterprise/cli/organizationsettings_test.go @@ -0,0 +1,71 @@ +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/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) { + 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") + 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") + 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()) + }) +} diff --git a/enterprise/coderd/idpsync.go b/enterprise/coderd/idpsync.go index f5b3b6f0130d4..096209ffe8292 100644 --- a/enterprise/coderd/idpsync.go +++ b/enterprise/coderd/idpsync.go @@ -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) From 6225ddf92beca8d2795ca28fce1c7a98580cc512 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 17 Sep 2024 11:29:06 -0500 Subject: [PATCH 4/9] chore add unit test for role sync --- cli/organizationsettings.go | 11 +++-- enterprise/cli/organizationsettings_test.go | 51 +++++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/cli/organizationsettings.go b/cli/organizationsettings.go index e9323d9394cb7..2a92c2254f188 100644 --- a/cli/organizationsettings.go +++ b/cli/organizationsettings.go @@ -69,8 +69,12 @@ func (r *RootCmd) updateOrganizationSetting(orgContext *OrganizationContext) *se } setting, err = client.PatchGroupIDPSyncSettings(ctx, org.ID.String(), req) case "rolesync", "role-sync": - // TODO: Implement role sync settings. - return fmt.Errorf("role sync settings are not implemented") + var req codersdk.RoleSyncSettings + err = json.Unmarshal(inputData, &req) + if err != nil { + return xerrors.Errorf("unmarshalling role sync settings: %w", err) + } + setting, err = client.PatchRoleIDPSyncSettings(ctx, org.ID.String(), req) default: _, _ = fmt.Fprintln(inv.Stderr, "Valid organization settings are: 'groupsync', 'rolesync'") return fmt.Errorf("unknown organization setting %s", inv.Args[0]) @@ -126,8 +130,7 @@ func (r *RootCmd) printOrganizationSetting(orgContext *OrganizationContext) *ser case "groupsync", "group-sync": setting, err = client.GroupIDPSyncSettings(ctx, org.ID.String()) case "rolesync", "role-sync": - // TODO: Implement role sync settings. - return fmt.Errorf("role sync settings are not implemented") + setting, err = client.RoleIDPSyncSettings(ctx, org.ID.String()) default: _, _ = fmt.Fprintln(inv.Stderr, "Valid organization settings are: 'groupsync', 'rolesync'") return fmt.Errorf("unknown organization setting %s", inv.Args[0]) diff --git a/enterprise/cli/organizationsettings_test.go b/enterprise/cli/organizationsettings_test.go index 4285bb0f3bcfc..de04a2a4b2c80 100644 --- a/enterprise/cli/organizationsettings_test.go +++ b/enterprise/cli/organizationsettings_test.go @@ -11,6 +11,7 @@ import ( "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" @@ -69,3 +70,53 @@ func TestUpdateGroupSync(t *testing.T) { require.JSONEq(t, string(expectedData), buf.String()) }) } + +func TestUpdateRoleSync(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + 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") + 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") + 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()) + }) +} From c04801986dcab2638ca1d267b7218427f71944c3 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 17 Sep 2024 12:08:17 -0500 Subject: [PATCH 5/9] linting --- enterprise/cli/organizationsettings_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/enterprise/cli/organizationsettings_test.go b/enterprise/cli/organizationsettings_test.go index de04a2a4b2c80..a7796216d19c8 100644 --- a/enterprise/cli/organizationsettings_test.go +++ b/enterprise/cli/organizationsettings_test.go @@ -22,6 +22,8 @@ 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)} @@ -38,6 +40,7 @@ func TestUpdateGroupSync(t *testing.T) { 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{ @@ -75,6 +78,8 @@ 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)} @@ -111,6 +116,7 @@ func TestUpdateRoleSync(t *testing.T) { // 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) From 5bdbd393d3d63df56a848ed6bba40ddf77e4c635 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 17 Sep 2024 12:32:48 -0500 Subject: [PATCH 6/9] linting --- enterprise/cli/organizationsettings_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/enterprise/cli/organizationsettings_test.go b/enterprise/cli/organizationsettings_test.go index a7796216d19c8..44438a6fb088f 100644 --- a/enterprise/cli/organizationsettings_test.go +++ b/enterprise/cli/organizationsettings_test.go @@ -64,6 +64,7 @@ func TestUpdateGroupSync(t *testing.T) { // 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) @@ -96,6 +97,7 @@ func TestUpdateRoleSync(t *testing.T) { 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{ From c4482e68484fbcc51d9a41f94f6f1d35b920f498 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 17 Sep 2024 12:34:23 -0500 Subject: [PATCH 7/9] update golden files --- cli/testdata/coder_organizations_--help.golden | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/testdata/coder_organizations_--help.golden b/cli/testdata/coder_organizations_--help.golden index e246b9f856195..9a78ff9c66dab 100644 --- a/cli/testdata/coder_organizations_--help.golden +++ b/cli/testdata/coder_organizations_--help.golden @@ -8,12 +8,12 @@ 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. + 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 From ec78d0091f98c4c01a14cb1d4c9abf695c4b671f Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 17 Sep 2024 13:10:56 -0500 Subject: [PATCH 8/9] make cobra sub commands --- cli/organizationsettings.go | 228 +++++++++++------- .../coder_organizations_--help.golden | 1 + ...coder_organizations_settings_--help.golden | 15 ++ ...r_organizations_settings_set_--help.golden | 17 ++ ...izations_settings_set_--help_--help.golden | 17 ++ ..._organizations_settings_show_--help.golden | 17 ++ ...zations_settings_show_--help_--help.golden | 17 ++ 7 files changed, 223 insertions(+), 89 deletions(-) create mode 100644 cli/testdata/coder_organizations_settings_--help.golden create mode 100644 cli/testdata/coder_organizations_settings_set_--help.golden create mode 100644 cli/testdata/coder_organizations_settings_set_--help_--help.golden create mode 100644 cli/testdata/coder_organizations_settings_show_--help.golden create mode 100644 cli/testdata/coder_organizations_settings_show_--help_--help.golden diff --git a/cli/organizationsettings.go b/cli/organizationsettings.go index 2a92c2254f188..c4422c8d372c6 100644 --- a/cli/organizationsettings.go +++ b/cli/organizationsettings.go @@ -2,11 +2,12 @@ package cli import ( "bytes" + "context" "encoding/json" "fmt" "io" - "strings" + "github.com/google/uuid" "golang.org/x/xerrors" "github.com/coder/coder/v2/codersdk" @@ -14,6 +15,40 @@ import ( ) 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.", @@ -21,19 +56,26 @@ func (r *RootCmd) organizationSettings(orgContext *OrganizationContext) *serpent Handler: func(inv *serpent.Invocation) error { return inv.Command.HelpHandler(inv) }, - Hidden: true, Children: []*serpent.Command{ - r.printOrganizationSetting(orgContext), - r.updateOrganizationSetting(orgContext), + r.printOrganizationSetting(orgContext, settings), + r.setOrganizationSettings(orgContext, settings), }, } return cmd } -func (r *RootCmd) updateOrganizationSetting(orgContext *OrganizationContext) *serpent.Command { +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 ", + Use: "set", Short: "Update specified organization setting.", Long: FormatExamples( Example{ @@ -43,69 +85,68 @@ func (r *RootCmd) updateOrganizationSetting(orgContext *OrganizationContext) *se ), Options: []serpent.Option{}, Middleware: serpent.Chain( - serpent.RequireNArgs(1), + 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 - } + return inv.Command.HelpHandler(inv) + }, + } - // Read in the json - inputData, err := io.ReadAll(inv.Stdin) - if err != nil { - return xerrors.Errorf("reading stdin: %w", err) - } + 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 + } - var setting any - switch strings.ToLower(inv.Args[0]) { - case "groupsync", "group-sync": - var req codersdk.GroupSyncSettings - err = json.Unmarshal(inputData, &req) + // Read in the json + inputData, err := io.ReadAll(inv.Stdin) if err != nil { - return xerrors.Errorf("unmarshalling group sync settings: %w", err) + return xerrors.Errorf("reading stdin: %w", err) } - setting, err = client.PatchGroupIDPSyncSettings(ctx, org.ID.String(), req) - case "rolesync", "role-sync": - var req codersdk.RoleSyncSettings - err = json.Unmarshal(inputData, &req) + + output, err := patch(ctx, client, org.ID, inputData) if err != nil { - return xerrors.Errorf("unmarshalling role sync settings: %w", err) + return xerrors.Errorf("patching %q: %w", set.Name, err) } - setting, err = client.PatchRoleIDPSyncSettings(ctx, org.ID.String(), req) - default: - _, _ = fmt.Fprintln(inv.Stderr, "Valid organization settings are: 'groupsync', 'rolesync'") - return fmt.Errorf("unknown organization setting %s", inv.Args[0]) - } - - if err != nil { - return fmt.Errorf("failed to get organization setting %s: %w", inv.Args[0], err) - } - - settingJSON, err := json.Marshal(setting) - 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 - }, + + 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) *serpent.Command { +func (r *RootCmd) printOrganizationSetting(orgContext *OrganizationContext, settings []organizationSetting) *serpent.Command { client := new(codersdk.Client) cmd := &serpent.Command{ - Use: "show ", + Use: "show", Short: "Outputs specified organization setting.", Long: FormatExamples( Example{ @@ -115,45 +156,54 @@ func (r *RootCmd) printOrganizationSetting(orgContext *OrganizationContext) *ser ), Options: []serpent.Option{}, Middleware: serpent.Chain( - serpent.RequireNArgs(1), + 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 - } - - var setting any - switch strings.ToLower(inv.Args[0]) { - case "groupsync", "group-sync": - setting, err = client.GroupIDPSyncSettings(ctx, org.ID.String()) - case "rolesync", "role-sync": - setting, err = client.RoleIDPSyncSettings(ctx, org.ID.String()) - default: - _, _ = fmt.Fprintln(inv.Stderr, "Valid organization settings are: 'groupsync', 'rolesync'") - return fmt.Errorf("unknown organization setting %s", inv.Args[0]) - } - - if err != nil { - return fmt.Errorf("failed to get organization setting %s: %w", inv.Args[0], err) - } - - settingJSON, err := json.Marshal(setting) - 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 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 } diff --git a/cli/testdata/coder_organizations_--help.golden b/cli/testdata/coder_organizations_--help.golden index 9a78ff9c66dab..5b06825e39c27 100644 --- a/cli/testdata/coder_organizations_--help.golden +++ b/cli/testdata/coder_organizations_--help.golden @@ -11,6 +11,7 @@ SUBCOMMANDS: 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. diff --git a/cli/testdata/coder_organizations_settings_--help.golden b/cli/testdata/coder_organizations_settings_--help.golden new file mode 100644 index 0000000000000..39597c1f2f510 --- /dev/null +++ b/cli/testdata/coder_organizations_settings_--help.golden @@ -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. diff --git a/cli/testdata/coder_organizations_settings_set_--help.golden b/cli/testdata/coder_organizations_settings_set_--help.golden new file mode 100644 index 0000000000000..e86ceddf73865 --- /dev/null +++ b/cli/testdata/coder_organizations_settings_set_--help.golden @@ -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. diff --git a/cli/testdata/coder_organizations_settings_set_--help_--help.golden b/cli/testdata/coder_organizations_settings_set_--help_--help.golden new file mode 100644 index 0000000000000..e86ceddf73865 --- /dev/null +++ b/cli/testdata/coder_organizations_settings_set_--help_--help.golden @@ -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. diff --git a/cli/testdata/coder_organizations_settings_show_--help.golden b/cli/testdata/coder_organizations_settings_show_--help.golden new file mode 100644 index 0000000000000..ee575a0fd067b --- /dev/null +++ b/cli/testdata/coder_organizations_settings_show_--help.golden @@ -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. diff --git a/cli/testdata/coder_organizations_settings_show_--help_--help.golden b/cli/testdata/coder_organizations_settings_show_--help_--help.golden new file mode 100644 index 0000000000000..ee575a0fd067b --- /dev/null +++ b/cli/testdata/coder_organizations_settings_show_--help_--help.golden @@ -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. From 3dc174ff02bbb9c6395d1c09ae4495bc9d3c2e85 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Tue, 17 Sep 2024 13:55:00 -0500 Subject: [PATCH 9/9] make gen --- docs/manifest.json | 35 +++++++++++++++++++ docs/reference/cli/organizations.md | 13 +++---- docs/reference/cli/organizations_settings.md | 22 ++++++++++++ .../cli/organizations_settings_set.md | 26 ++++++++++++++ .../organizations_settings_set_group-sync.md | 15 ++++++++ .../organizations_settings_set_role-sync.md | 15 ++++++++ .../cli/organizations_settings_show.md | 26 ++++++++++++++ .../organizations_settings_show_group-sync.md | 15 ++++++++ .../organizations_settings_show_role-sync.md | 15 ++++++++ 9 files changed, 176 insertions(+), 6 deletions(-) create mode 100644 docs/reference/cli/organizations_settings.md create mode 100644 docs/reference/cli/organizations_settings_set.md create mode 100644 docs/reference/cli/organizations_settings_set_group-sync.md create mode 100644 docs/reference/cli/organizations_settings_set_role-sync.md create mode 100644 docs/reference/cli/organizations_settings_show.md create mode 100644 docs/reference/cli/organizations_settings_show_group-sync.md create mode 100644 docs/reference/cli/organizations_settings_show_role-sync.md diff --git a/docs/manifest.json b/docs/manifest.json index 75ee5e6e81b90..7d9c422b284a9 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -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.", diff --git a/docs/reference/cli/organizations.md b/docs/reference/cli/organizations.md index 82570489603be..1fbd076425ace 100644 --- a/docs/reference/cli/organizations.md +++ b/docs/reference/cli/organizations.md @@ -18,12 +18,13 @@ coder organizations [flags] [subcommand] ## Subcommands -| Name | Purpose | -| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [show](./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. | -| [create](./organizations_create.md) | Create a new organization. | -| [members](./organizations_members.md) | Manage organization members | -| [roles](./organizations_roles.md) | Manage organization roles. | +| Name | Purpose | +| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [show](./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. | +| [create](./organizations_create.md) | Create a new organization. | +| [members](./organizations_members.md) | Manage organization members | +| [roles](./organizations_roles.md) | Manage organization roles. | +| [settings](./organizations_settings.md) | Manage organization settings. | ## Options diff --git a/docs/reference/cli/organizations_settings.md b/docs/reference/cli/organizations_settings.md new file mode 100644 index 0000000000000..15093c984fedc --- /dev/null +++ b/docs/reference/cli/organizations_settings.md @@ -0,0 +1,22 @@ + + +# organizations settings + +Manage organization settings. + +Aliases: + +- setting + +## Usage + +```console +coder organizations settings +``` + +## Subcommands + +| Name | Purpose | +| ----------------------------------------------------- | --------------------------------------- | +| [show](./organizations_settings_show.md) | Outputs specified organization setting. | +| [set](./organizations_settings_set.md) | Update specified organization setting. | diff --git a/docs/reference/cli/organizations_settings_set.md b/docs/reference/cli/organizations_settings_set.md new file mode 100644 index 0000000000000..b4fd819184030 --- /dev/null +++ b/docs/reference/cli/organizations_settings_set.md @@ -0,0 +1,26 @@ + + +# 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 | +| --------------------------------------------------------------------- | ---------------------------------------------------------- | +| [group-sync](./organizations_settings_set_group-sync.md) | Group sync settings to sync groups from an IdP. | +| [role-sync](./organizations_settings_set_role-sync.md) | Role sync settings to sync organization roles from an IdP. | diff --git a/docs/reference/cli/organizations_settings_set_group-sync.md b/docs/reference/cli/organizations_settings_set_group-sync.md new file mode 100644 index 0000000000000..f60a456771763 --- /dev/null +++ b/docs/reference/cli/organizations_settings_set_group-sync.md @@ -0,0 +1,15 @@ + + +# organizations settings set group-sync + +Group sync settings to sync groups from an IdP. + +Aliases: + +- groupsync + +## Usage + +```console +coder organizations settings set group-sync +``` diff --git a/docs/reference/cli/organizations_settings_set_role-sync.md b/docs/reference/cli/organizations_settings_set_role-sync.md new file mode 100644 index 0000000000000..40203b21f752e --- /dev/null +++ b/docs/reference/cli/organizations_settings_set_role-sync.md @@ -0,0 +1,15 @@ + + +# 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 +``` diff --git a/docs/reference/cli/organizations_settings_show.md b/docs/reference/cli/organizations_settings_show.md new file mode 100644 index 0000000000000..651f0a6f199de --- /dev/null +++ b/docs/reference/cli/organizations_settings_show.md @@ -0,0 +1,26 @@ + + +# 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 | +| ---------------------------------------------------------------------- | ---------------------------------------------------------- | +| [group-sync](./organizations_settings_show_group-sync.md) | Group sync settings to sync groups from an IdP. | +| [role-sync](./organizations_settings_show_role-sync.md) | Role sync settings to sync organization roles from an IdP. | diff --git a/docs/reference/cli/organizations_settings_show_group-sync.md b/docs/reference/cli/organizations_settings_show_group-sync.md new file mode 100644 index 0000000000000..6ae796d117e61 --- /dev/null +++ b/docs/reference/cli/organizations_settings_show_group-sync.md @@ -0,0 +1,15 @@ + + +# organizations settings show group-sync + +Group sync settings to sync groups from an IdP. + +Aliases: + +- groupsync + +## Usage + +```console +coder organizations settings show group-sync +``` diff --git a/docs/reference/cli/organizations_settings_show_role-sync.md b/docs/reference/cli/organizations_settings_show_role-sync.md new file mode 100644 index 0000000000000..8a32c138517d1 --- /dev/null +++ b/docs/reference/cli/organizations_settings_show_role-sync.md @@ -0,0 +1,15 @@ + + +# 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 +```