diff --git a/VERSION b/VERSION index ece61c6..f9cbc01 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.6 \ No newline at end of file +1.0.7 \ No newline at end of file diff --git a/api/setting.go b/api/setting.go index 09b82cb..fab775f 100644 --- a/api/setting.go +++ b/api/setting.go @@ -6,6 +6,8 @@ type SettingName string const ( // SettingWorkspaceApproval is the setting name for workspace approval config. SettingWorkspaceApproval SettingName = "bb.workspace.approval" + // SettingWorkspaceProfile is the setting name for workspace profile settings. + SettingWorkspaceProfile SettingName = "bb.workspace.profile" // SettingWorkspaceExternalApproval is the setting name for workspace external approval config. SettingWorkspaceExternalApproval SettingName = "bb.workspace.approval.external" ) diff --git a/docs/data-sources/setting.md b/docs/data-sources/setting.md index 1ce3d3d..bcf5780 100644 --- a/docs/data-sources/setting.md +++ b/docs/data-sources/setting.md @@ -19,12 +19,28 @@ The setting data source. - `name` (String) +### Optional + +- `workspace_profile` (Block List, Max: 1) (see [below for nested schema](#nestedblock--workspace_profile)) + ### Read-Only - `approval_flow` (Block List) Configure risk level and approval flow for different tasks. Require ENTERPRISE subscription. (see [below for nested schema](#nestedblock--approval_flow)) - `external_approval_nodes` (Block List) Configure external nodes in the approval flow. Require ENTERPRISE subscription. (see [below for nested schema](#nestedblock--external_approval_nodes)) - `id` (String) The ID of this resource. + +### Nested Schema for `workspace_profile` + +Optional: + +- `disallow_password_signin` (Boolean) Whether to disallow password signin. (Except workspace admins). Require ENTERPRISE subscription +- `disallow_signup` (Boolean) Disallow self-service signup, users can only be invited by the owner. Require PRO subscription. +- `domains` (List of String) The workspace domain, e.g. bytebase.com. Required for the group +- `enforce_identity_domain` (Boolean) Only user and group from the domains can be created and login. +- `external_url` (String) The URL user visits Bytebase. The external URL is used for: 1. Constructing the correct callback URL when configuring the VCS provider. The callback URL points to the frontend; 2. Creating the correct webhook endpoint when configuring the project GitOps workflow. The webhook endpoint points to the backend. + + ### Nested Schema for `approval_flow` diff --git a/docs/resources/setting.md b/docs/resources/setting.md index 4912e6f..2e1b80e 100644 --- a/docs/resources/setting.md +++ b/docs/resources/setting.md @@ -23,6 +23,7 @@ The setting resource. - `approval_flow` (Block List) Configure risk level and approval flow for different tasks. Require ENTERPRISE subscription. (see [below for nested schema](#nestedblock--approval_flow)) - `external_approval_nodes` (Block List) Configure external nodes in the approval flow. Require ENTERPRISE subscription. (see [below for nested schema](#nestedblock--external_approval_nodes)) +- `workspace_profile` (Block List, Max: 1) (see [below for nested schema](#nestedblock--workspace_profile)) ### Read-Only @@ -100,3 +101,16 @@ Required: - `title` (String) The external node title. + + +### Nested Schema for `workspace_profile` + +Optional: + +- `disallow_password_signin` (Boolean) Whether to disallow password signin. (Except workspace admins). Require ENTERPRISE subscription +- `disallow_signup` (Boolean) Disallow self-service signup, users can only be invited by the owner. Require PRO subscription. +- `domains` (List of String) The workspace domain, e.g. bytebase.com. Required for the group +- `enforce_identity_domain` (Boolean) Only user and group from the domains can be created and login. +- `external_url` (String) The URL user visits Bytebase. The external URL is used for: 1. Constructing the correct callback URL when configuring the VCS provider. The callback URL points to the frontend; 2. Creating the correct webhook endpoint when configuring the project GitOps workflow. The webhook endpoint points to the backend. + + diff --git a/examples/settings/main.tf b/examples/settings/main.tf index 8dc2b66..4c9244f 100644 --- a/examples/settings/main.tf +++ b/examples/settings/main.tf @@ -25,6 +25,10 @@ data "bytebase_setting" "external_approval" { name = "bb.workspace.approval.external" } +data "bytebase_setting" "workspace_profile" { + name = "bb.workspace.profile" +} + output "approval_flow" { value = data.bytebase_setting.approval_flow } @@ -32,3 +36,7 @@ output "approval_flow" { output "external_approval" { value = data.bytebase_setting.external_approval } + +output "workspace_profile" { + value = data.bytebase_setting.workspace_profile +} diff --git a/examples/setup/gitops.tf b/examples/setup/gitops.tf index a30fa6a..1b91dc1 100644 --- a/examples/setup/gitops.tf +++ b/examples/setup/gitops.tf @@ -10,7 +10,9 @@ resource "bytebase_vcs_provider" "github" { resource "bytebase_vcs_connector" "github" { depends_on = [ bytebase_project.sample_project, - bytebase_vcs_provider.github + bytebase_vcs_provider.github, + # vcs connector requires the external_url. + bytebase_setting.workspace_profile ] resource_id = "connector-github" diff --git a/examples/setup/main.tf b/examples/setup/main.tf index 580fef4..d6c3f96 100644 --- a/examples/setup/main.tf +++ b/examples/setup/main.tf @@ -25,3 +25,12 @@ locals { instance_id_prod = "prod-sample-instance" project_id = "project-sample" } + +resource "bytebase_setting" "workspace_profile" { + name = "bb.workspace.profile" + + workspace_profile { + external_url = "https://bytebase.example.com" + domains = ["bytebase.com"] + } +} diff --git a/examples/setup/users.tf b/examples/setup/users.tf index 99c6136..5a6c3ef 100644 --- a/examples/setup/users.tf +++ b/examples/setup/users.tf @@ -20,7 +20,9 @@ resource "bytebase_user" "project_developer" { resource "bytebase_group" "developers" { depends_on = [ bytebase_user.workspace_dba, - bytebase_user.project_developer + bytebase_user.project_developer, + # group requires the domain. + bytebase_setting.workspace_profile ] email = "developers@bytebase.com" diff --git a/provider/data_source_setting.go b/provider/data_source_setting.go index 5b5c6f7..239a169 100644 --- a/provider/data_source_setting.go +++ b/provider/data_source_setting.go @@ -28,10 +28,60 @@ func dataSourceSetting() *schema.Resource { ValidateFunc: validation.StringInSlice([]string{ string(api.SettingWorkspaceApproval), string(api.SettingWorkspaceExternalApproval), + string(api.SettingWorkspaceProfile), }, false), }, "approval_flow": getWorkspaceApprovalSetting(true), "external_approval_nodes": getExternalApprovalSetting(true), + "workspace_profile": getWorkspaceProfileSetting(true), + }, + } +} + +func getWorkspaceProfileSetting(computed bool) *schema.Schema { + return &schema.Schema{ + Computed: computed, + Optional: true, + Default: nil, + Type: schema.TypeList, + MaxItems: 1, + MinItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "external_url": { + Type: schema.TypeString, + Computed: computed, + Optional: true, + Description: "The URL user visits Bytebase. The external URL is used for: 1. Constructing the correct callback URL when configuring the VCS provider. The callback URL points to the frontend; 2. Creating the correct webhook endpoint when configuring the project GitOps workflow. The webhook endpoint points to the backend.", + }, + "disallow_signup": { + Type: schema.TypeBool, + Computed: computed, + Optional: true, + Description: "Disallow self-service signup, users can only be invited by the owner. Require PRO subscription.", + }, + "disallow_password_signin": { + Type: schema.TypeBool, + Computed: computed, + Optional: true, + Description: "Whether to disallow password signin. (Except workspace admins). Require ENTERPRISE subscription", + }, + "domains": { + Type: schema.TypeList, + Computed: computed, + Optional: true, + Description: "The workspace domain, e.g. bytebase.com. Required for the group", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "enforce_identity_domain": { + Type: schema.TypeBool, + Computed: computed, + Optional: true, + Description: "Only user and group from the domains can be created and login.", + }, + }, }, } } @@ -215,6 +265,12 @@ func setSettingMessage(ctx context.Context, d *schema.ResourceData, client api.C return diag.Errorf("cannot set external_approval_nodes: %s", err.Error()) } } + if value := setting.Value.GetWorkspaceProfileSettingValue(); value != nil { + settingVal := flattenWorkspaceProfileSetting(value) + if err := d.Set("workspace_profile", settingVal); err != nil { + return diag.Errorf("cannot set workspace_profile: %s", err.Error()) + } + } return nil } @@ -354,3 +410,15 @@ func flattenExternalApprovalSetting(setting *v1pb.ExternalApprovalSetting) []int } return []interface{}{approvalSetting} } + +func flattenWorkspaceProfileSetting(setting *v1pb.WorkspaceProfileSetting) []interface{} { + raw := map[string]interface{}{} + + raw["external_url"] = setting.ExternalUrl + raw["disallow_signup"] = setting.DisallowSignup + raw["disallow_password_signin"] = setting.DisallowPasswordSignin + raw["enforce_identity_domain"] = setting.EnforceIdentityDomain + raw["domains"] = setting.Domains + + return []interface{}{raw} +} diff --git a/provider/resource_setting.go b/provider/resource_setting.go index 7fd5ffa..1dfc4d5 100644 --- a/provider/resource_setting.go +++ b/provider/resource_setting.go @@ -34,10 +34,12 @@ func resourceSetting() *schema.Resource { ValidateFunc: validation.StringInSlice([]string{ string(api.SettingWorkspaceApproval), string(api.SettingWorkspaceExternalApproval), + string(api.SettingWorkspaceProfile), }, false), }, "approval_flow": getWorkspaceApprovalSetting(false), "external_approval_nodes": getExternalApprovalSetting(false), + "workspace_profile": getWorkspaceProfileSetting(false), }, } } @@ -52,6 +54,7 @@ func resourceSettingUpsert(ctx context.Context, d *schema.ResourceData, m interf setting := &v1pb.Setting{ Name: settingName, } + updateMasks := []string{} switch name { case api.SettingWorkspaceApproval: @@ -74,11 +77,22 @@ func resourceSettingUpsert(ctx context.Context, d *schema.ResourceData, m interf ExternalApprovalSettingValue: externalApproval, }, } + case api.SettingWorkspaceProfile: + workspaceProfile, updatePathes, err := convertToV1WorkspaceProfileSetting(d) + if err != nil { + return diag.FromErr(err) + } + setting.Value = &v1pb.Value{ + Value: &v1pb.Value_WorkspaceProfileSettingValue{ + WorkspaceProfileSettingValue: workspaceProfile, + }, + } + updateMasks = updatePathes default: return diag.FromErr(errors.Errorf("Unsupport setting: %v", name)) } - updatedSetting, err := c.UpsertSetting(ctx, setting, []string{}) + updatedSetting, err := c.UpsertSetting(ctx, setting, updateMasks) if err != nil { return diag.FromErr(err) } @@ -93,6 +107,45 @@ func resourceSettingUpsert(ctx context.Context, d *schema.ResourceData, m interf return diags } +func convertToV1WorkspaceProfileSetting(d *schema.ResourceData) (*v1pb.WorkspaceProfileSetting, []string, error) { + rawList, ok := d.Get("workspace_profile").([]interface{}) + if !ok || len(rawList) != 1 { + return nil, nil, errors.Errorf("invalid workspace_profile") + } + + updateMasks := []string{} + raw := rawList[0].(map[string]interface{}) + + workspacePrfile := &v1pb.WorkspaceProfileSetting{} + + if externalURL, ok := raw["external_url"]; ok { + workspacePrfile.ExternalUrl = externalURL.(string) + updateMasks = append(updateMasks, "value.workspace_profile_setting_value.external_url") + } + if disallowSignup, ok := raw["disallow_signup"]; ok { + workspacePrfile.DisallowSignup = disallowSignup.(bool) + updateMasks = append(updateMasks, "value.workspace_profile_setting_value.disallow_signup") + } + if disallowPasswordSignin, ok := raw["disallow_password_signin"]; ok { + workspacePrfile.DisallowPasswordSignin = disallowPasswordSignin.(bool) + updateMasks = append(updateMasks, "value.workspace_profile_setting_value.disallow_password_signin") + } + if domains, ok := raw["domains"]; ok { + if enforceIdentityDomain, ok := raw["enforce_identity_domain"]; ok { + workspacePrfile.EnforceIdentityDomain = enforceIdentityDomain.(bool) + updateMasks = append(updateMasks, "value.workspace_profile_setting_value.enforce_identity_domain") + } + for _, domain := range domains.([]interface{}) { + workspacePrfile.Domains = append(workspacePrfile.Domains, domain.(string)) + } + updateMasks = append(updateMasks, "value.workspace_profile_setting_value.domains") + } else if _, ok := raw["enforce_identity_domain"]; ok { + return nil, nil, errors.Errorf("enforce_identity_domain must works with domains") + } + + return workspacePrfile, updateMasks, nil +} + func convertToV1ExternalNodesSetting(d *schema.ResourceData) (*v1pb.ExternalApprovalSetting, error) { rawList, ok := d.Get("external_approval_nodes").([]interface{}) if !ok || len(rawList) != 1 {