diff --git a/.github/infra/gcp.tf b/.github/infra/gcp.tf
index e72263f4..ebfdfb98 100644
--- a/.github/infra/gcp.tf
+++ b/.github/infra/gcp.tf
@@ -57,3 +57,9 @@ resource "google_project_service" "group-settings" {
project = data.google_project.project.project_id
service = "groupssettings.googleapis.com"
}
+
+ // Enable the chrome policy api service
+ resource "google_project_service" "chrome-policy" {
+ project = data.google_project.project.project_id
+ service = "chromepolicy.googleapis.com"
+ }
diff --git a/docs/data-sources/chrome_policy_schema.md b/docs/data-sources/chrome_policy_schema.md
new file mode 100644
index 00000000..68eb260c
--- /dev/null
+++ b/docs/data-sources/chrome_policy_schema.md
@@ -0,0 +1,95 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "googleworkspace_chrome_policy_schema Data Source - terraform-provider-googleworkspace"
+subcategory: ""
+description: |-
+ Chrome Policy Schema data source in the Terraform Googleworkspace provider.
+---
+
+# googleworkspace_chrome_policy_schema (Data Source)
+
+Chrome Policy Schema data source in the Terraform Googleworkspace provider.
+
+## Example Usage
+
+```terraform
+data "googleworkspace_chrome_policy_schema" "example" {
+ schema_name = "chrome.printers.AllowForUsers"
+}
+
+output "field_descriptions" {
+ value = data.googleworkspace_chrome_policy_schema.example.field_descriptions
+}
+```
+
+
+## Schema
+
+### Required
+
+- **schema_name** (String) The full qualified name of the policy schema
+
+### Optional
+
+- **id** (String) The ID of this resource.
+
+### Read-Only
+
+- **access_restrictions** (List of String) Specific access restrictions related to this policy.
+- **additional_target_key_names** (List of Object) Additional key names that will be used to identify the target of the policy value. When specifying a policyTargetKey, each of the additional keys specified here will have to be included in the additionalTargetKeys map. (see [below for nested schema](#nestedatt--additional_target_key_names))
+- **definition** (List of Object) Schema definition using proto descriptor. (see [below for nested schema](#nestedatt--definition))
+- **field_descriptions** (String) Detailed description of each field that is part of the schema, represented as a JSON string.
+- **notices** (List of Object) Special notice messages related to setting certain values in certain fields in the schema. (see [below for nested schema](#nestedatt--notices))
+- **policy_description** (String) Description about the policy schema for user consumption.
+- **support_uri** (String) URI to related support article for this schema.
+
+
+### Nested Schema for `additional_target_key_names`
+
+Read-Only:
+
+- **key** (String)
+- **key_description** (String)
+
+
+
+### Nested Schema for `definition`
+
+Read-Only:
+
+- **enum_type** (List of Object) (see [below for nested schema](#nestedobjatt--definition--enum_type))
+- **message_type** (String)
+- **name** (String)
+- **package** (String)
+- **syntax** (String)
+
+
+### Nested Schema for `definition.enum_type`
+
+Read-Only:
+
+- **name** (String)
+- **value** (List of Object) (see [below for nested schema](#nestedobjatt--definition--enum_type--value))
+
+
+### Nested Schema for `definition.enum_type.value`
+
+Read-Only:
+
+- **name** (String)
+- **number** (Number)
+
+
+
+
+
+### Nested Schema for `notices`
+
+Read-Only:
+
+- **acknowledgement_required** (Boolean)
+- **field** (String)
+- **notice_message** (String)
+- **notice_value** (String)
+
+
diff --git a/docs/resources/chrome_policy.md b/docs/resources/chrome_policy.md
new file mode 100644
index 00000000..223f49c6
--- /dev/null
+++ b/docs/resources/chrome_policy.md
@@ -0,0 +1,52 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "googleworkspace_chrome_policy Resource - terraform-provider-googleworkspace"
+subcategory: ""
+description: |-
+ Chrome Policy resource in the Terraform Googleworkspace provider. Currently only supports policies not requiring additionalTargetKeys.
+---
+
+# googleworkspace_chrome_policy (Resource)
+
+Chrome Policy resource in the Terraform Googleworkspace provider. Currently only supports policies not requiring additionalTargetKeys.
+
+## Example Usage
+
+```terraform
+resource "googleworkspace_org_unit" "example" {
+ name = "example"
+ parent_org_unit_path = "/"
+}
+
+resource "googleworkspace_chrome_policy" "example" {
+ org_unit_id = googleworkspace_org_unit.test.id
+ policies {
+ schema_name = "chrome.users.MaxConnectionsPerProxy"
+ schema_values = {
+ maxConnectionsPerProxy = jsonencode(34)
+ }
+ }
+}
+```
+
+
+## Schema
+
+### Required
+
+- **org_unit_id** (String) The target org unit on which this policy is applied.
+- **policies** (Block List, Min: 1) Policies to set for the org unit (see [below for nested schema](#nestedblock--policies))
+
+### Optional
+
+- **id** (String) The ID of this resource.
+
+
+### Nested Schema for `policies`
+
+Required:
+
+- **schema_name** (String) The full qualified name of the policy schema.
+- **schema_values** (Map of String) JSON encoded map that represents key/value pairs that correspond to the given schema.
+
+
diff --git a/examples/data-sources/googleworkspace_chrome_policy_schema/data-source.tf b/examples/data-sources/googleworkspace_chrome_policy_schema/data-source.tf
new file mode 100644
index 00000000..e4d78235
--- /dev/null
+++ b/examples/data-sources/googleworkspace_chrome_policy_schema/data-source.tf
@@ -0,0 +1,7 @@
+data "googleworkspace_chrome_policy_schema" "example" {
+ schema_name = "chrome.printers.AllowForUsers"
+}
+
+output "field_descriptions" {
+ value = data.googleworkspace_chrome_policy_schema.example.field_descriptions
+}
\ No newline at end of file
diff --git a/examples/resources/googleworkspace_chrome_policy/resource.tf b/examples/resources/googleworkspace_chrome_policy/resource.tf
new file mode 100644
index 00000000..25663982
--- /dev/null
+++ b/examples/resources/googleworkspace_chrome_policy/resource.tf
@@ -0,0 +1,14 @@
+resource "googleworkspace_org_unit" "example" {
+ name = "example"
+ parent_org_unit_path = "/"
+}
+
+resource "googleworkspace_chrome_policy" "example" {
+ org_unit_id = googleworkspace_org_unit.test.id
+ policies {
+ schema_name = "chrome.users.MaxConnectionsPerProxy"
+ schema_values = {
+ maxConnectionsPerProxy = jsonencode(34)
+ }
+ }
+}
\ No newline at end of file
diff --git a/internal/provider/data_source_chrome_policy_schema.go b/internal/provider/data_source_chrome_policy_schema.go
new file mode 100644
index 00000000..0e26e1f9
--- /dev/null
+++ b/internal/provider/data_source_chrome_policy_schema.go
@@ -0,0 +1,266 @@
+package googleworkspace
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "google.golang.org/api/chromepolicy/v1"
+)
+
+func dataSourceChromePolicySchema() *schema.Resource {
+ return &schema.Resource{
+ Description: "Chrome Policy Schema data source in the Terraform Googleworkspace provider.",
+
+ ReadContext: dataSourceChromePolicySchemaRead,
+
+ Schema: map[string]*schema.Schema{
+ // Intentionally ignoring field 'name' https://developers.google.com/chrome/policy/reference/rest/v1/customers.policySchemas#PolicySchema
+ // it is a confusing field, that includes url segments the practitioner won't find useful.
+ // Format: name=customers/{customer}/policySchemas/{schema_namespace}
+ // Using the output field 'schema_name' instead as the field the practitioner specifies
+ "schema_name": {
+ Description: "The full qualified name of the policy schema",
+ Type: schema.TypeString,
+ Required: true,
+ },
+ "policy_description": {
+ Description: "Description about the policy schema for user consumption.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "additional_target_key_names": {
+ Description: "Additional key names that will be used to identify the target of the policy value. When specifying a policyTargetKey, each of the additional keys specified here will have to be included in the additionalTargetKeys map.",
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "key": {
+ Description: "Key name.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "key_description": {
+ Description: "Key description.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ },
+ },
+ "definition": {
+ Description: "Schema definition using proto descriptor.",
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "name": {
+ Description: "file name, relative to root of source tree",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "package": {
+ Description: "e.g. 'foo', 'foo.bar', etc.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "message_type": {
+ Description: "All top-level definitions in this file, represented as a JSON string",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "enum_type": {
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "name": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "value": {
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "name": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "number": {
+ Type: schema.TypeInt,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "syntax": {
+ Description: "The syntax of the proto file. The supported values are 'proto' and 'proto3'.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ },
+ },
+ "field_descriptions": {
+ Description: "Detailed description of each field that is part of the schema, represented as a JSON string.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "access_restrictions": {
+ Description: "Specific access restrictions related to this policy.",
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ "notices": {
+ Description: "Special notice messages related to setting certain values in certain fields in the schema.",
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "field": {
+ Description: "The field name associated with the notice.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "notice_value": {
+ Description: "The value of the field that has a notice. When setting the field to this value, the user may be required to acknowledge the notice message in order for the value to be set.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "notice_message": {
+ Description: "The notice message associate with the value of the field.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "acknowledgement_required": {
+ Description: "Whether the user needs to acknowledge the notice message before the value can be set.",
+ Type: schema.TypeBool,
+ Computed: true,
+ },
+ },
+ },
+ },
+ "support_uri": {
+ Description: "URI to related support article for this schema.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ }
+}
+
+func dataSourceChromePolicySchemaRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ client := meta.(*apiClient)
+
+ chromePolicyService, diags := client.NewChromePolicyService()
+ if diags.HasError() {
+ return diags
+ }
+
+ chromePolicySchemasService, diags := GetChromePolicySchemasService(chromePolicyService)
+ if diags.HasError() {
+ return diags
+ }
+
+ policySchema, err := chromePolicySchemasService.Get(fmt.Sprintf("customers/%s/policySchemas/%s", client.Customer, d.Get("schema_name").(string))).Do()
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ d.SetId(policySchema.SchemaName)
+ d.Set("schema_name", policySchema.SchemaName)
+ d.Set("policy_description", policySchema.PolicyDescription)
+ d.Set("support_uri", policySchema.SupportUri)
+ if err := d.Set("additional_target_key_names", flattenAdditionalTargetKeyNames(policySchema.AdditionalTargetKeyNames)); err != nil {
+ return diag.FromErr(err)
+ }
+ if err := d.Set("definition", flattenDefinition(policySchema.Definition)); err != nil {
+ return diag.FromErr(err)
+ }
+
+ // this attribute contains recursive types, so we store it as json
+ fieldDescriptions, _ := json.MarshalIndent(policySchema.FieldDescriptions, "", " ")
+ d.Set("field_descriptions", string(fieldDescriptions))
+
+ if err := d.Set("access_restrictions", policySchema.AccessRestrictions); err != nil {
+ return diag.FromErr(err)
+ }
+ if err := d.Set("notices", flattenNotices(policySchema.Notices)); err != nil {
+ return diag.FromErr(err)
+ }
+
+ return nil
+}
+
+func flattenNotices(ns []*chromepolicy.GoogleChromePolicyV1PolicySchemaNoticeDescription) []interface{} {
+ result := make([]interface{}, len(ns))
+
+ for i, n := range ns {
+ obj := make(map[string]interface{})
+ obj["field"] = n.Field
+ obj["notice_value"] = n.NoticeValue
+ obj["notice_message"] = n.NoticeMessage
+ obj["acknowledgement_required"] = n.AcknowledgementRequired
+ result[i] = obj
+ }
+
+ return result
+}
+
+func flattenEnumType(es []*chromepolicy.Proto2EnumDescriptorProto) []interface{} {
+ result := make([]interface{}, len(es))
+
+ for i, e := range es {
+ obj := make(map[string]interface{})
+
+ obj["name"] = e.Name
+ values := make([]interface{}, len(e.Value))
+ for j, v := range e.Value {
+ values[j] = map[string]interface{}{
+ "name": v.Name,
+ "number": int(v.Number),
+ }
+ }
+ obj["value"] = values
+
+ result[i] = obj
+ }
+
+ return result
+}
+
+func flattenDefinition(d *chromepolicy.Proto2FileDescriptorProto) []interface{} {
+ result := make([]interface{}, 1)
+ obj := make(map[string]interface{})
+
+ obj["name"] = d.Name
+ obj["package"] = d.Package
+ obj["syntax"] = d.Syntax
+ obj["enum_type"] = flattenEnumType(d.EnumType)
+ // this attribute contains recursive types, so we store it as json
+ msgType, _ := json.MarshalIndent(d.MessageType, "", " ")
+ obj["message_type"] = string(msgType)
+
+ result[0] = obj
+ return result
+}
+
+func flattenAdditionalTargetKeyNames(as []*chromepolicy.GoogleChromePolicyV1AdditionalTargetKeyName) []interface{} {
+ result := make([]interface{}, len(as))
+ for i, a := range as {
+ obj := make(map[string]interface{})
+ obj["key"] = a.Key
+ obj["key_description"] = a.KeyDescription
+ result[i] = obj
+ }
+ return result
+}
diff --git a/internal/provider/data_source_chrome_policy_schema_test.go b/internal/provider/data_source_chrome_policy_schema_test.go
new file mode 100644
index 00000000..f4e10996
--- /dev/null
+++ b/internal/provider/data_source_chrome_policy_schema_test.go
@@ -0,0 +1,38 @@
+package googleworkspace
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+)
+
+func TestAccDataSourceChromePolicySchema(t *testing.T) {
+ t.Parallel()
+
+ schemaName := "chrome.printers.AllowForUsers"
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccDataSourceChromePolicySchema(schemaName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.googleworkspace_chrome_policy_schema.test", "schema_name", schemaName),
+ resource.TestCheckResourceAttr("data.googleworkspace_chrome_policy_schema.test", "policy_description", "Allows a printer for users in a given organization."),
+ resource.TestCheckResourceAttr("data.googleworkspace_chrome_policy_schema.test", "additional_target_key_names.#", "1"),
+ resource.TestCheckResourceAttr("data.googleworkspace_chrome_policy_schema.test", "additional_target_key_names.0.key", "printer_id"),
+ ),
+ },
+ },
+ })
+}
+
+func testAccDataSourceChromePolicySchema(schemaName string) string {
+ return fmt.Sprintf(`
+data "googleworkspace_chrome_policy_schema" "test" {
+ schema_name = "%s"
+}
+`, schemaName)
+}
diff --git a/internal/provider/data_source_privileges.go b/internal/provider/data_source_privileges.go
index 34aa0971..f9e04679 100644
--- a/internal/provider/data_source_privileges.go
+++ b/internal/provider/data_source_privileges.go
@@ -13,7 +13,7 @@ func dataSourcePrivileges() *schema.Resource {
return &schema.Resource{
Description: "Privileges data source in the Terraform Googleworkspace provider.",
- ReadContext: dataSourcePrivilegeRead,
+ ReadContext: dataSourcePrivilegesRead,
Schema: map[string]*schema.Schema{
"etag": {
@@ -59,7 +59,7 @@ func dataSourcePrivileges() *schema.Resource {
}
}
-func dataSourcePrivilegeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+func dataSourcePrivilegesRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*apiClient)
directoryService, diags := client.NewDirectoryService()
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index 7c4945c2..cecec6f1 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -14,6 +14,7 @@ import (
)
var DefaultClientScopes = []string{
+ "https://www.googleapis.com/auth/chrome.management.policy",
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/admin.directory.customer",
"https://www.googleapis.com/auth/admin.directory.domain",
@@ -87,18 +88,20 @@ func New(version string) func() *schema.Provider {
},
},
DataSourcesMap: map[string]*schema.Resource{
- "googleworkspace_domain": dataSourceDomain(),
- "googleworkspace_domain_alias": dataSourceDomainAlias(),
- "googleworkspace_group": dataSourceGroup(),
- "googleworkspace_group_member": dataSourceGroupMember(),
- "googleworkspace_group_settings": dataSourceGroupSettings(),
- "googleworkspace_org_unit": dataSourceOrgUnit(),
- "googleworkspace_privileges": dataSourcePrivileges(),
- "googleworkspace_role": dataSourceRole(),
- "googleworkspace_schema": dataSourceSchema(),
- "googleworkspace_user": dataSourceUser(),
+ "googleworkspace_chrome_policy_schema": dataSourceChromePolicySchema(),
+ "googleworkspace_domain": dataSourceDomain(),
+ "googleworkspace_domain_alias": dataSourceDomainAlias(),
+ "googleworkspace_group": dataSourceGroup(),
+ "googleworkspace_group_member": dataSourceGroupMember(),
+ "googleworkspace_group_settings": dataSourceGroupSettings(),
+ "googleworkspace_org_unit": dataSourceOrgUnit(),
+ "googleworkspace_privileges": dataSourcePrivileges(),
+ "googleworkspace_role": dataSourceRole(),
+ "googleworkspace_schema": dataSourceSchema(),
+ "googleworkspace_user": dataSourceUser(),
},
ResourcesMap: map[string]*schema.Resource{
+ "googleworkspace_chrome_policy": resourceChromePolicy(),
"googleworkspace_domain": resourceDomain(),
"googleworkspace_domain_alias": resourceDomainAlias(),
"googleworkspace_group": resourceGroup(),
diff --git a/internal/provider/provider_config.go b/internal/provider/provider_config.go
index 92ae0bf5..67498e7a 100644
--- a/internal/provider/provider_config.go
+++ b/internal/provider/provider_config.go
@@ -11,6 +11,7 @@ import (
"golang.org/x/oauth2"
googleoauth "golang.org/x/oauth2/google"
+ "google.golang.org/api/chromepolicy/v1"
"google.golang.org/api/option"
directory "google.golang.org/api/admin/directory/v1"
@@ -73,6 +74,28 @@ func (c *apiClient) loadAndValidate(ctx context.Context) diag.Diagnostics {
return diags
}
+func (c *apiClient) NewChromePolicyService() (*chromepolicy.Service, diag.Diagnostics) {
+ var diags diag.Diagnostics
+
+ log.Printf("[INFO] Instantiating Google Admin Chrome Policy service")
+
+ chromePolicyService, err := chromepolicy.NewService(context.Background(), option.WithHTTPClient(c.client))
+ if err != nil {
+ return nil, diag.FromErr(err)
+ }
+
+ if chromePolicyService == nil {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Directory Service could not be created.",
+ })
+
+ return nil, diags
+ }
+
+ return chromePolicyService, diags
+}
+
func (c *apiClient) NewDirectoryService() (*directory.Service, diag.Diagnostics) {
var diags diag.Diagnostics
diff --git a/internal/provider/resource_chrome_policy.go b/internal/provider/resource_chrome_policy.go
new file mode 100644
index 00000000..2fdd0765
--- /dev/null
+++ b/internal/provider/resource_chrome_policy.go
@@ -0,0 +1,574 @@
+package googleworkspace
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+ "reflect"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+ "google.golang.org/api/chromepolicy/v1"
+)
+
+func resourceChromePolicy() *schema.Resource {
+ return &schema.Resource{
+ Description: "Chrome Policy resource in the Terraform Googleworkspace provider. Currently only supports policies not requiring additionalTargetKeys.",
+
+ CreateContext: resourceChromePolicyCreate,
+ UpdateContext: resourceChromePolicyUpdate,
+ ReadContext: resourceChromePolicyRead,
+ DeleteContext: resourceChromePolicyDelete,
+
+ Schema: map[string]*schema.Schema{
+ "org_unit_id": {
+ Description: "The target org unit on which this policy is applied.",
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ DiffSuppressFunc: diffSuppressOrgUnitId,
+ },
+ "policies": {
+ Description: "Policies to set for the org unit",
+ Type: schema.TypeList,
+ Required: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "schema_name": {
+ Description: "The full qualified name of the policy schema.",
+ Type: schema.TypeString,
+ Required: true,
+ },
+ "schema_values": {
+ Description: "JSON encoded map that represents key/value pairs that " +
+ "correspond to the given schema. ",
+ Type: schema.TypeMap,
+ Required: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ ValidateDiagFunc: validation.ToDiagFunc(
+ validation.StringIsJSON,
+ ),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func resourceChromePolicyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ client := meta.(*apiClient)
+
+ chromePolicyService, diags := client.NewChromePolicyService()
+ if diags.HasError() {
+ return diags
+ }
+
+ chromePoliciesService, diags := GetChromePoliciesService(chromePolicyService)
+ if diags.HasError() {
+ return diags
+ }
+
+ orgUnitId := strings.TrimPrefix(d.Get("org_unit_id").(string), "id:")
+
+ log.Printf("[DEBUG] Creating Chrome Policy for org:%s", orgUnitId)
+
+ policyTargetKey := &chromepolicy.GoogleChromePolicyV1PolicyTargetKey{
+ TargetResource: "orgunits/" + orgUnitId,
+ }
+
+ diags = validateChromePolicies(d, client)
+ if diags.HasError() {
+ return diags
+ }
+
+ policies, diags := expandChromePoliciesValues(d.Get("policies").([]interface{}))
+ if diags.HasError() {
+ return diags
+ }
+
+ var modifyRequests []*chromepolicy.GoogleChromePolicyV1ModifyOrgUnitPolicyRequest
+ for _, p := range policies {
+ var keys []string
+ var schemaValues map[string]interface{}
+ if err := json.Unmarshal(p.Value, &schemaValues); err != nil {
+ return diag.FromErr(err)
+ }
+ for key := range schemaValues {
+ keys = append(keys, key)
+ }
+ modifyRequests = append(modifyRequests, &chromepolicy.GoogleChromePolicyV1ModifyOrgUnitPolicyRequest{
+ PolicyTargetKey: policyTargetKey,
+ PolicyValue: p,
+ UpdateMask: strings.Join(keys, ","),
+ })
+ }
+
+ _, err := chromePoliciesService.Orgunits.BatchModify(fmt.Sprintf("customers/%s", client.Customer), &chromepolicy.GoogleChromePolicyV1BatchModifyOrgUnitPoliciesRequest{Requests: modifyRequests}).Do()
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ log.Printf("[DEBUG] Finished creating Chrome Policy for org:%s", orgUnitId)
+ d.SetId(orgUnitId)
+
+ return resourceChromePolicyRead(ctx, d, meta)
+}
+
+func resourceChromePolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ client := meta.(*apiClient)
+
+ chromePolicyService, diags := client.NewChromePolicyService()
+ if diags.HasError() {
+ return diags
+ }
+
+ chromePoliciesService, diags := GetChromePoliciesService(chromePolicyService)
+ if diags.HasError() {
+ return diags
+ }
+
+ log.Printf("[DEBUG] Updating Chrome Policy for org:%s", d.Id())
+
+ policyTargetKey := &chromepolicy.GoogleChromePolicyV1PolicyTargetKey{
+ TargetResource: "orgunits/" + d.Id(),
+ }
+
+ // Update is achieved by inheriting defaults for the previous policySchemas, and then applying the new set
+ old, _ := d.GetChange("policies")
+
+ var requests []*chromepolicy.GoogleChromePolicyV1InheritOrgUnitPolicyRequest
+ for _, p := range old.([]interface{}) {
+ policy := p.(map[string]interface{})
+ schemaName := policy["schema_name"].(string)
+
+ requests = append(requests, &chromepolicy.GoogleChromePolicyV1InheritOrgUnitPolicyRequest{
+ PolicyTargetKey: policyTargetKey,
+ PolicySchema: schemaName,
+ })
+ }
+
+ _, err := chromePoliciesService.Orgunits.BatchInherit(fmt.Sprintf("customers/%s", client.Customer), &chromepolicy.GoogleChromePolicyV1BatchInheritOrgUnitPoliciesRequest{Requests: requests}).Do()
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ // run create
+ diags = resourceChromePolicyCreate(ctx, d, meta)
+ if diags.HasError() {
+ return diags
+ }
+
+ log.Printf("[DEBUG] Finished Updating Chrome Policy for org:%s", d.Id())
+
+ return diags
+}
+
+func resourceChromePolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ client := meta.(*apiClient)
+
+ chromePolicyService, diags := client.NewChromePolicyService()
+ if diags.HasError() {
+ return diags
+ }
+
+ chromePoliciesService, diags := GetChromePoliciesService(chromePolicyService)
+ if diags.HasError() {
+ return diags
+ }
+
+ log.Printf("[DEBUG] Getting Chrome Policy for org:%s", d.Id())
+
+ policyTargetKey := &chromepolicy.GoogleChromePolicyV1PolicyTargetKey{
+ TargetResource: "orgunits/" + d.Id(),
+ }
+
+ policiesObj := []*chromepolicy.GoogleChromePolicyV1PolicyValue{}
+ for _, p := range d.Get("policies").([]interface{}) {
+ policy := p.(map[string]interface{})
+ schemaName := policy["schema_name"].(string)
+
+ var resp *chromepolicy.GoogleChromePolicyV1ResolveResponse
+ // the resolve endpoint does not like being called in quick succession
+ err := retryTimeDuration(ctx, time.Minute, func() error {
+ var retryErr error
+
+ // we will resolve each individual policySchema by fully qualified name, so the responses should be a single result
+ resp, retryErr = chromePoliciesService.Resolve(fmt.Sprintf("customers/%s", client.Customer), &chromepolicy.GoogleChromePolicyV1ResolveRequest{
+ PolicySchemaFilter: schemaName,
+ PolicyTargetKey: policyTargetKey,
+ }).Do()
+
+ return retryErr
+ })
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ if len(resp.ResolvedPolicies) != 1 {
+ return diag.Errorf("unexpected number of resolved policies for schema: %s", schemaName)
+ }
+
+ value := resp.ResolvedPolicies[0].Value
+
+ policiesObj = append(policiesObj, value)
+ }
+
+ policies, diags := flattenChromePolicies(policiesObj, client)
+ if diags.HasError() {
+ return diags
+ }
+
+ if err := d.Set("policies", policies); err != nil {
+ return diag.FromErr(err)
+ }
+
+ log.Printf("[DEBUG] Finished getting Chrome Policy for org:%s", d.Id())
+ return nil
+}
+
+func resourceChromePolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+ client := meta.(*apiClient)
+
+ chromePolicyService, diags := client.NewChromePolicyService()
+ if diags.HasError() {
+ return diags
+ }
+
+ chromePoliciesService, diags := GetChromePoliciesService(chromePolicyService)
+ if diags.HasError() {
+ return diags
+ }
+
+ log.Printf("[DEBUG] Deleting Chrome Policy for org:%s", d.Id())
+
+ policyTargetKey := &chromepolicy.GoogleChromePolicyV1PolicyTargetKey{
+ TargetResource: "orgunits/" + d.Id(),
+ }
+
+ var requests []*chromepolicy.GoogleChromePolicyV1InheritOrgUnitPolicyRequest
+ for _, p := range d.Get("policies").([]interface{}) {
+ policy := p.(map[string]interface{})
+ schemaName := policy["schema_name"].(string)
+
+ requests = append(requests, &chromepolicy.GoogleChromePolicyV1InheritOrgUnitPolicyRequest{
+ PolicyTargetKey: policyTargetKey,
+ PolicySchema: schemaName,
+ })
+ }
+
+ _, err := chromePoliciesService.Orgunits.BatchInherit(fmt.Sprintf("customers/%s", client.Customer), &chromepolicy.GoogleChromePolicyV1BatchInheritOrgUnitPoliciesRequest{Requests: requests}).Do()
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ log.Printf("[DEBUG] Finished deleting Chrome Policy for org:%s", d.Id())
+ return nil
+}
+
+// Chrome Policies
+
+func validateChromePolicies(d *schema.ResourceData, client *apiClient) diag.Diagnostics {
+ var diags diag.Diagnostics
+
+ new := d.Get("policies")
+
+ chromePolicyService, diags := client.NewChromePolicyService()
+ if diags.HasError() {
+ return diags
+ }
+
+ chromePolicySchemasService, diags := GetChromePolicySchemasService(chromePolicyService)
+ if diags.HasError() {
+ return diags
+ }
+
+ // Validate config against schemas
+ for _, policy := range new.([]interface{}) {
+ schemaName := policy.(map[string]interface{})["schema_name"].(string)
+
+ schemaDef, err := chromePolicySchemasService.Get(fmt.Sprintf("customers/%s/policySchemas/%s", client.Customer, schemaName)).Do()
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ if schemaDef == nil || schemaDef.Definition == nil || schemaDef.Definition.MessageType == nil {
+ return append(diags, diag.Diagnostic{
+ Summary: fmt.Sprintf("schema definition (%s) is empty", schemaName),
+ Severity: diag.Error,
+ })
+ }
+
+ schemaFieldMap := map[string][]*chromepolicy.Proto2FieldDescriptorProto{}
+ for _, schemaField := range schemaDef.Definition.MessageType {
+ for _, schemaNestedField := range schemaField.Field {
+ schemaFieldMap[schemaNestedField.Name] = schemaField.Field
+ }
+ }
+
+ policyDef := policy.(map[string]interface{})["schema_values"].(map[string]interface{})
+
+ for polKey, polJsonVal := range policyDef {
+ if _, ok := schemaFieldMap[polKey]; !ok {
+ return append(diags, diag.Diagnostic{
+ Summary: fmt.Sprintf("field name (%s) is not found in this schema definition (%s)", polKey, schemaName),
+ Severity: diag.Error,
+ })
+ }
+
+ var polVal interface{}
+ err := json.Unmarshal([]byte(polJsonVal.(string)), &polVal)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ for _, schemaField := range schemaFieldMap[polKey] {
+
+ if schemaField == nil {
+ return append(diags, diag.Diagnostic{
+ Summary: fmt.Sprintf("field type is not defined for field name (%s)", polKey),
+ Severity: diag.Warning,
+ })
+ }
+
+ validType := validatePolicyFieldValueType(schemaField.Type, polVal)
+ if !validType {
+ return append(diags, diag.Diagnostic{
+ Summary: fmt.Sprintf("value provided for %s is of incorrect type (expected type: %s)", schemaField.Name, schemaField.Type),
+ Severity: diag.Error,
+ })
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+// This will take a value and validate whether the type is correct
+func validatePolicyFieldValueType(fieldType string, fieldValue interface{}) bool {
+ valid := false
+
+ switch fieldType {
+ case "TYPE_BOOL":
+ valid = reflect.ValueOf(fieldValue).Kind() == reflect.Bool
+ case "TYPE_FLOAT":
+ fallthrough
+ case "TYPE_DOUBLE":
+ valid = reflect.ValueOf(fieldValue).Kind() == reflect.Float64
+ case "TYPE_INT64":
+ fallthrough
+ case "TYPE_FIXED64":
+ fallthrough
+ case "TYPE_SFIXED64":
+ fallthrough
+ case "TYPE_SINT64":
+ fallthrough
+ case "TYPE_UINT64":
+ // this is unmarshalled as a float, check that it's an int
+ if reflect.ValueOf(fieldValue).Kind() == reflect.Float64 &&
+ fieldValue == float64(int(fieldValue.(float64))) {
+ valid = true
+ }
+ case "TYPE_INT32":
+ fallthrough
+ case "TYPE_FIXED32":
+ fallthrough
+ case "TYPE_SFIXED32":
+ fallthrough
+ case "TYPE_SINT32":
+ fallthrough
+ case "TYPE_UINT32":
+ // this is unmarshalled as a float, check that it's an int
+ if reflect.ValueOf(fieldValue).Kind() == reflect.Float32 &&
+ fieldValue == float32(int(fieldValue.(float32))) {
+ valid = true
+ }
+ case "TYPE_ENUM":
+ fallthrough
+ case "TYPE_MESSAGE":
+ fallthrough
+ case "TYPE_STRING":
+ fallthrough
+ default:
+ valid = reflect.ValueOf(fieldValue).Kind() == reflect.String
+ }
+
+ return valid
+}
+
+// The API returns numeric values as strings. This will convert it to the appropriate type
+func convertPolicyFieldValueType(fieldType string, fieldValue interface{}) (interface{}, error) {
+ // If it's not of type string, then we'll assume it's the right type
+ if reflect.ValueOf(fieldValue).Kind() != reflect.String {
+ return fieldValue, nil
+ }
+
+ var err error
+ var value interface{}
+
+ switch fieldType {
+ case "TYPE_BOOL":
+ value, err = strconv.ParseBool(fieldValue.(string))
+ case "TYPE_FLOAT":
+ fallthrough
+ case "TYPE_DOUBLE":
+ value, err = strconv.ParseFloat(fieldValue.(string), 64)
+ case "TYPE_INT64":
+ fallthrough
+ case "TYPE_FIXED64":
+ fallthrough
+ case "TYPE_SFIXED64":
+ fallthrough
+ case "TYPE_SINT64":
+ fallthrough
+ case "TYPE_UINT64":
+ value, err = strconv.ParseInt(fieldValue.(string), 10, 64)
+ case "TYPE_INT32":
+ fallthrough
+ case "TYPE_FIXED32":
+ fallthrough
+ case "TYPE_SFIXED32":
+ fallthrough
+ case "TYPE_SINT32":
+ fallthrough
+ case "TYPE_UINT32":
+ value, err = strconv.ParseInt(fieldValue.(string), 10, 32)
+ case "TYPE_ENUM":
+ fallthrough
+ case "TYPE_MESSAGE":
+ fallthrough
+ case "TYPE_STRING":
+ fallthrough
+ default:
+ value = fieldValue
+ }
+
+ return value, err
+}
+
+func expandChromePoliciesValues(policies []interface{}) ([]*chromepolicy.GoogleChromePolicyV1PolicyValue, diag.Diagnostics) {
+ var diags diag.Diagnostics
+ result := []*chromepolicy.GoogleChromePolicyV1PolicyValue{}
+
+ for _, p := range policies {
+ policy := p.(map[string]interface{})
+
+ schemaName := policy["schema_name"].(string)
+ schemaValues := policy["schema_values"].(map[string]interface{})
+
+ policyValuesObj := map[string]interface{}{}
+
+ for k, v := range schemaValues {
+ var polVal interface{}
+ err := json.Unmarshal([]byte(v.(string)), &polVal)
+ if err != nil {
+ return nil, diag.FromErr(err)
+ }
+
+ policyValuesObj[k] = polVal
+ }
+
+ // create the json object and assign to the schema
+ schemaValuesJson, err := json.Marshal(policyValuesObj)
+ if err != nil {
+ return nil, diag.FromErr(err)
+ }
+
+ policyObj := chromepolicy.GoogleChromePolicyV1PolicyValue{
+ PolicySchema: schemaName,
+ Value: schemaValuesJson,
+ }
+
+ result = append(result, &policyObj)
+ }
+
+ return result, diags
+}
+
+func flattenChromePolicies(policiesObj []*chromepolicy.GoogleChromePolicyV1PolicyValue, client *apiClient) ([]map[string]interface{}, diag.Diagnostics) {
+ var policies []map[string]interface{}
+
+ chromePolicyService, diags := client.NewChromePolicyService()
+ if diags.HasError() {
+ return nil, diags
+ }
+
+ schemaService, diags := GetChromePolicySchemasService(chromePolicyService)
+ if diags.HasError() {
+ return nil, diags
+ }
+
+ for _, polObj := range policiesObj {
+ schemaDef, err := schemaService.Get(fmt.Sprintf("customers/%s/policySchemas/%s", client.Customer, polObj.PolicySchema)).Do()
+ if err != nil {
+ return nil, diag.FromErr(err)
+ }
+
+ if schemaDef == nil || schemaDef.Definition == nil || schemaDef.Definition.MessageType == nil {
+ return nil, append(diags, diag.Diagnostic{
+ Summary: fmt.Sprintf("schema definition (%s) is not defined", polObj.PolicySchema),
+ Severity: diag.Warning,
+ })
+ }
+
+ schemaFieldMap := map[string][]*chromepolicy.Proto2FieldDescriptorProto{}
+ for _, schemaField := range schemaDef.Definition.MessageType {
+ for _, schemaNestedField := range schemaField.Field {
+ schemaFieldMap[schemaNestedField.Name] = schemaField.Field
+ }
+ }
+
+ var schemaValuesObj map[string]interface{}
+
+ err = json.Unmarshal(polObj.Value, &schemaValuesObj)
+ if err != nil {
+ return nil, diag.FromErr(err)
+ }
+
+ schemaValues := map[string]interface{}{}
+ for k, v := range schemaValuesObj {
+ if _, ok := schemaFieldMap[k]; !ok {
+ return nil, append(diags, diag.Diagnostic{
+ Summary: fmt.Sprintf("field name (%s) is not found in this schema definition (%s)", k, polObj.PolicySchema),
+ Severity: diag.Warning,
+ })
+ }
+
+ for _, schemaField := range schemaFieldMap[k] {
+
+ if schemaField == nil {
+ return nil, append(diags, diag.Diagnostic{
+ Summary: fmt.Sprintf("field type is not defined for field name (%s)", k),
+ Severity: diag.Warning,
+ })
+ }
+
+ val, err := convertPolicyFieldValueType(schemaField.Type, v)
+ if err != nil {
+ return nil, diag.FromErr(err)
+ }
+
+ jsonVal, err := json.Marshal(val)
+ if err != nil {
+ return nil, diag.FromErr(err)
+ }
+ schemaValues[k] = string(jsonVal)
+ }
+ }
+
+ policies = append(policies, map[string]interface{}{
+ "schema_name": polObj.PolicySchema,
+ "schema_values": schemaValues,
+ })
+ }
+
+ return policies, nil
+}
diff --git a/internal/provider/resource_chrome_policy_test.go b/internal/provider/resource_chrome_policy_test.go
new file mode 100644
index 00000000..3b4cd60a
--- /dev/null
+++ b/internal/provider/resource_chrome_policy_test.go
@@ -0,0 +1,252 @@
+package googleworkspace
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+ "google.golang.org/api/chromepolicy/v1"
+)
+
+func TestAccResourceChromePolicy_basic(t *testing.T) {
+ t.Parallel()
+
+ ouName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccResourceChromePolicy_basic(ouName, 33),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.#", "1"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.0.schema_name", "chrome.users.MaxConnectionsPerProxy"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.0.schema_values.maxConnectionsPerProxy", "33"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccResourceChromePolicy_update(t *testing.T) {
+ t.Parallel()
+
+ ouName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccResourceChromePolicy_basic(ouName, 33),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.#", "1"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.0.schema_name", "chrome.users.MaxConnectionsPerProxy"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.0.schema_values.maxConnectionsPerProxy", "33"),
+ ),
+ },
+ {
+ Config: testAccResourceChromePolicy_basic(ouName, 34),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.#", "1"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.0.schema_name", "chrome.users.MaxConnectionsPerProxy"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.0.schema_values.maxConnectionsPerProxy", "34"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccResourceChromePolicy_multiple(t *testing.T) {
+ t.Parallel()
+
+ ouName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
+
+ // ensures previously set field was reset/removed
+ // this passing also implies Delete works correctly
+ // based on the implementation
+ testCheck := func(s *terraform.State) error {
+ client, err := googleworkspaceTestClient()
+ if err != nil {
+ return err
+ }
+
+ rs, ok := s.RootModule().Resources["googleworkspace_org_unit.test"]
+ if !ok {
+ return fmt.Errorf("Can't find org unit resource: googleworkspace_org_unit.test")
+ }
+
+ if rs.Primary.ID == "" {
+ return fmt.Errorf("org unit ID not set")
+ }
+
+ chromePolicyService, diags := client.NewChromePolicyService()
+ if diags.HasError() {
+ return errors.New(diags[0].Summary)
+ }
+
+ chromePoliciesService, diags := GetChromePoliciesService(chromePolicyService)
+ if diags.HasError() {
+ return errors.New(diags[0].Summary)
+ }
+
+ policyTargetKey := &chromepolicy.GoogleChromePolicyV1PolicyTargetKey{
+ TargetResource: "orgunits/" + strings.TrimPrefix(rs.Primary.ID, "id:"),
+ }
+
+ resp, err := chromePoliciesService.Resolve(fmt.Sprintf("customers/%s", client.Customer), &chromepolicy.GoogleChromePolicyV1ResolveRequest{
+ PolicySchemaFilter: "chrome.users.MaxConnectionsPerProxy",
+ PolicyTargetKey: policyTargetKey,
+ }).Do()
+ if err != nil {
+ return err
+ }
+ if len(resp.ResolvedPolicies) > 0 {
+ return fmt.Errorf("Expected policy to be reset")
+ }
+ return nil
+ }
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccResourceChromePolicy_multiple(ouName, 33, ".*@example"),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.#", "2"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.0.schema_name", "chrome.users.RestrictSigninToPattern"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.0.schema_values.restrictSigninToPattern", encode(".*@example")),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.1.schema_name", "chrome.users.MaxConnectionsPerProxy"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.1.schema_values.maxConnectionsPerProxy", "33"),
+ ),
+ },
+ {
+ Config: testAccResourceChromePolicy_multipleRearranged(ouName, 34, ".*@example.com"),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.#", "2"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.0.schema_name", "chrome.users.MaxConnectionsPerProxy"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.0.schema_values.maxConnectionsPerProxy", "34"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.1.schema_name", "chrome.users.RestrictSigninToPattern"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.1.schema_values.restrictSigninToPattern", encode(".*@example.com")),
+ ),
+ },
+ {
+ Config: testAccResourceChromePolicy_multipleDifferent(ouName, true, ".*@example.com"),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.#", "2"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.0.schema_name", "chrome.users.OnlineRevocationChecks"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.0.schema_values.enableOnlineRevocationChecks", "true"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.1.schema_name", "chrome.users.RestrictSigninToPattern"),
+ resource.TestCheckResourceAttr("googleworkspace_chrome_policy.test", "policies.1.schema_values.restrictSigninToPattern", encode(".*@example.com")),
+ testCheck,
+ ),
+ },
+ },
+ })
+}
+
+func encode(content string) string {
+ res, _ := json.Marshal(content)
+ return string(res)
+}
+
+func testAccResourceChromePolicy_multiple(ouName string, conns int, pattern string) string {
+ return fmt.Sprintf(`
+resource "googleworkspace_org_unit" "test" {
+ name = "%s"
+ parent_org_unit_path = "/"
+}
+
+resource "googleworkspace_chrome_policy" "test" {
+ org_unit_id = googleworkspace_org_unit.test.id
+ policies {
+ schema_name = "chrome.users.RestrictSigninToPattern"
+ schema_values = {
+ restrictSigninToPattern = jsonencode("%s")
+ }
+ }
+ policies {
+ schema_name = "chrome.users.MaxConnectionsPerProxy"
+ schema_values = {
+ maxConnectionsPerProxy = jsonencode(%d)
+ }
+ }
+}
+`, ouName, pattern, conns)
+}
+
+func testAccResourceChromePolicy_multipleRearranged(ouName string, conns int, pattern string) string {
+ return fmt.Sprintf(`
+resource "googleworkspace_org_unit" "test" {
+ name = "%s"
+ parent_org_unit_path = "/"
+}
+
+resource "googleworkspace_chrome_policy" "test" {
+ org_unit_id = googleworkspace_org_unit.test.id
+ policies {
+ schema_name = "chrome.users.MaxConnectionsPerProxy"
+ schema_values = {
+ maxConnectionsPerProxy = jsonencode(%d)
+ }
+ }
+ policies {
+ schema_name = "chrome.users.RestrictSigninToPattern"
+ schema_values = {
+ restrictSigninToPattern = jsonencode("%s")
+ }
+ }
+}
+`, ouName, conns, pattern)
+}
+
+func testAccResourceChromePolicy_multipleDifferent(ouName string, enabled bool, pattern string) string {
+ return fmt.Sprintf(`
+resource "googleworkspace_org_unit" "test" {
+ name = "%s"
+ parent_org_unit_path = "/"
+}
+
+resource "googleworkspace_chrome_policy" "test" {
+ org_unit_id = googleworkspace_org_unit.test.id
+ policies {
+ schema_name = "chrome.users.OnlineRevocationChecks"
+ schema_values = {
+ enableOnlineRevocationChecks = jsonencode(%t)
+ }
+ }
+ policies {
+ schema_name = "chrome.users.RestrictSigninToPattern"
+ schema_values = {
+ restrictSigninToPattern = jsonencode("%s")
+ }
+ }
+}
+`, ouName, enabled, pattern)
+}
+
+func testAccResourceChromePolicy_basic(ouName string, conns int) string {
+ return fmt.Sprintf(`
+resource "googleworkspace_org_unit" "test" {
+ name = "%s"
+ parent_org_unit_path = "/"
+}
+
+resource "googleworkspace_chrome_policy" "test" {
+ org_unit_id = googleworkspace_org_unit.test.id
+ policies {
+ schema_name = "chrome.users.MaxConnectionsPerProxy"
+ schema_values = {
+ maxConnectionsPerProxy = jsonencode(%d)
+ }
+ }
+}
+`, ouName, conns)
+}
diff --git a/internal/provider/resource_role_assignment.go b/internal/provider/resource_role_assignment.go
index 52f600c7..1adf729a 100644
--- a/internal/provider/resource_role_assignment.go
+++ b/internal/provider/resource_role_assignment.go
@@ -56,19 +56,22 @@ func resourceRoleAssignment() *schema.Resource {
ForceNew: true,
},
"org_unit_id": {
- Description: "If the role is restricted to an organization unit, this contains the ID for the organization unit the exercise of this role is restricted to.",
- Type: schema.TypeString,
- Optional: true,
- ForceNew: true,
- DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
- // for some reason the Google API returns org unit ids with a "id:" prefix
- return strings.TrimPrefix(old, "id:") == strings.TrimPrefix(new, "id:")
- },
+ Description: "If the role is restricted to an organization unit, this contains the ID for the organization unit the exercise of this role is restricted to.",
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ DiffSuppressFunc: diffSuppressOrgUnitId,
},
},
}
}
+// for some reason the Google API returns org unit ids with a "id:" prefix
+// some resources won't accept this prefix when specifying an org unit id
+func diffSuppressOrgUnitId(k, old, new string, d *schema.ResourceData) bool {
+ return strings.TrimPrefix(old, "id:") == strings.TrimPrefix(new, "id:")
+}
+
func resourceRolesAssignmentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var diags diag.Diagnostics
@@ -94,9 +97,7 @@ func resourceRolesAssignmentCreate(ctx context.Context, d *schema.ResourceData,
}
scopeType := strings.ToUpper(d.Get("scope_type").(string))
- orgUnitId := d.Get("org_unit_id").(string)
- // for some reason the Google API returns org unit ids with a "id:" prefix
- orgUnitId = strings.TrimPrefix(orgUnitId, "id:")
+ orgUnitId := strings.TrimPrefix(d.Get("org_unit_id").(string), "id:")
if scopeType == "ORG_UNIT" && orgUnitId == "" {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
@@ -156,8 +157,7 @@ func resourceRoleAssignmentRead(ctx context.Context, d *schema.ResourceData, met
d.Set("etag", ra.Etag)
d.Set("assigned_to", ra.AssignedTo)
d.Set("scope_type", ra.ScopeType)
- // for some reason the Google API returns org unit ids with a "id:" prefix
- d.Set("org_unit_id", strings.TrimPrefix(ra.OrgUnitId, "id:"))
+ d.Set("org_unit_id", ra.OrgUnitId)
log.Printf("[DEBUG] Finished getting RoleAssignment %q", d.Id())
diff --git a/internal/provider/retry_utils.go b/internal/provider/retry_utils.go
index 5d59a93e..aeb0cc42 100644
--- a/internal/provider/retry_utils.go
+++ b/internal/provider/retry_utils.go
@@ -26,6 +26,10 @@ func retryTimeDuration(ctx context.Context, duration time.Duration, retryFunc fu
return resource.RetryableError(err)
}
+ if IsRateLimitExceeded(err) {
+ return resource.RetryableError(err)
+ }
+
return resource.NonRetryableError(err)
})
}
@@ -53,3 +57,17 @@ func IsTemporarilyUnavailable(err error) bool {
return false
}
+
+func IsRateLimitExceeded(err error) bool {
+ gerr, ok := err.(*googleapi.Error)
+ if !ok {
+ return false
+ }
+
+ if gerr.Code == 429 {
+ log.Printf("[DEBUG] Dismissed an error as retryable based on error code: %s", err)
+ return true
+ }
+ return false
+
+}
diff --git a/internal/provider/services.go b/internal/provider/services.go
index 3215d529..37fa95e0 100644
--- a/internal/provider/services.go
+++ b/internal/provider/services.go
@@ -6,9 +6,44 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
directory "google.golang.org/api/admin/directory/v1"
+ "google.golang.org/api/chromepolicy/v1"
"google.golang.org/api/groupssettings/v1"
)
+func GetChromePoliciesService(chromePolicyService *chromepolicy.Service) (*chromepolicy.CustomersPoliciesService, diag.Diagnostics) {
+ var diags diag.Diagnostics
+
+ log.Printf("[INFO] Instantiating Google Admin Chrome Policies service")
+ customersService := chromePolicyService.Customers
+ if customersService == nil || customersService.Policies == nil {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Chrome Policies Service could not be created.",
+ })
+
+ return nil, diags
+ }
+
+ return customersService.Policies, diags
+}
+
+func GetChromePolicySchemasService(chromePolicyService *chromepolicy.Service) (*chromepolicy.CustomersPolicySchemasService, diag.Diagnostics) {
+ var diags diag.Diagnostics
+
+ log.Printf("[INFO] Instantiating Google Admin Chrome Policy Schemas service")
+ customersService := chromePolicyService.Customers
+ if customersService == nil || customersService.PolicySchemas == nil {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Chrome Policy Schemas Service could not be created.",
+ })
+
+ return nil, diags
+ }
+
+ return customersService.PolicySchemas, diags
+}
+
func GetDomainAliasesService(directoryService *directory.Service) (*directory.DomainAliasesService, diag.Diagnostics) {
var diags diag.Diagnostics