Skip to content

Commit

Permalink
Add custom access level to access context manager (#3653) (#6611)
Browse files Browse the repository at this point in the history
* custom access level added to the resource

* example CEL expression doc referred

Signed-off-by: Modular Magician <[email protected]>
  • Loading branch information
modular-magician authored Jun 16, 2020
1 parent 8dc798e commit 4b93678
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .changelog/3653.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
```release-note:enhancement
access_context_manager: custom access level added to `google_access_context_manager_access_level`

```
187 changes: 187 additions & 0 deletions google/resource_access_context_manager_access_level.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,51 @@ for the AccessLevel to be applied. Default value: "AND" Possible values: ["AND",
},
},
},
ConflictsWith: []string{"custom"},
},
"custom": {
Type: schema.TypeList,
Optional: true,
Description: `Custom access level conditions are set using the Cloud Common Expression Language to represent the necessary conditions for the level to apply to a request.
See CEL spec at: https://github.com/google/cel-spec.`,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"expr": {
Type: schema.TypeList,
Required: true,
Description: `Represents a textual expression in the Common Expression Language (CEL) syntax. CEL is a C-like expression language.
This page details the objects and attributes that are used to the build the CEL expressions for
custom access levels - https://cloud.google.com/access-context-manager/docs/custom-access-level-spec.`,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"expression": {
Type: schema.TypeString,
Required: true,
Description: `Textual representation of an expression in Common Expression Language syntax.`,
},
"description": {
Type: schema.TypeString,
Optional: true,
Description: `Description of the expression`,
},
"location": {
Type: schema.TypeString,
Optional: true,
Description: `String indicating the location of the expression for error reporting, e.g. a file name and a position in the file`,
},
"title": {
Type: schema.TypeString,
Optional: true,
Description: `Title for the expression, i.e. a short string describing its purpose.`,
},
},
},
},
},
},
ConflictsWith: []string{"basic"},
},
"description": {
Type: schema.TypeString,
Expand Down Expand Up @@ -255,6 +300,12 @@ func resourceAccessContextManagerAccessLevelCreate(d *schema.ResourceData, meta
} else if v, ok := d.GetOkExists("basic"); !isEmptyValue(reflect.ValueOf(basicProp)) && (ok || !reflect.DeepEqual(v, basicProp)) {
obj["basic"] = basicProp
}
customProp, err := expandAccessContextManagerAccessLevelCustom(d.Get("custom"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("custom"); !isEmptyValue(reflect.ValueOf(customProp)) && (ok || !reflect.DeepEqual(v, customProp)) {
obj["custom"] = customProp
}
parentProp, err := expandAccessContextManagerAccessLevelParent(d.Get("parent"), d, config)
if err != nil {
return err
Expand Down Expand Up @@ -341,6 +392,9 @@ func resourceAccessContextManagerAccessLevelRead(d *schema.ResourceData, meta in
if err := d.Set("basic", flattenAccessContextManagerAccessLevelBasic(res["basic"], d, config)); err != nil {
return fmt.Errorf("Error reading AccessLevel: %s", err)
}
if err := d.Set("custom", flattenAccessContextManagerAccessLevelCustom(res["custom"], d, config)); err != nil {
return fmt.Errorf("Error reading AccessLevel: %s", err)
}
if err := d.Set("name", flattenAccessContextManagerAccessLevelName(res["name"], d, config)); err != nil {
return fmt.Errorf("Error reading AccessLevel: %s", err)
}
Expand Down Expand Up @@ -370,6 +424,12 @@ func resourceAccessContextManagerAccessLevelUpdate(d *schema.ResourceData, meta
} else if v, ok := d.GetOkExists("basic"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, basicProp)) {
obj["basic"] = basicProp
}
customProp, err := expandAccessContextManagerAccessLevelCustom(d.Get("custom"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("custom"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, customProp)) {
obj["custom"] = customProp
}

obj, err = resourceAccessContextManagerAccessLevelEncoder(d, meta, obj)
if err != nil {
Expand All @@ -395,6 +455,10 @@ func resourceAccessContextManagerAccessLevelUpdate(d *schema.ResourceData, meta
if d.HasChange("basic") {
updateMask = append(updateMask, "basic")
}

if d.HasChange("custom") {
updateMask = append(updateMask, "custom")
}
// updateMask is a URL parameter but not present in the schema, so replaceVars
// won't set it
url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")})
Expand Down Expand Up @@ -605,6 +669,54 @@ func flattenAccessContextManagerAccessLevelBasicConditionsRegions(v interface{},
return v
}

func flattenAccessContextManagerAccessLevelCustom(v interface{}, d *schema.ResourceData, config *Config) interface{} {
if v == nil {
return nil
}
original := v.(map[string]interface{})
if len(original) == 0 {
return nil
}
transformed := make(map[string]interface{})
transformed["expr"] =
flattenAccessContextManagerAccessLevelCustomExpr(original["expr"], d, config)
return []interface{}{transformed}
}
func flattenAccessContextManagerAccessLevelCustomExpr(v interface{}, d *schema.ResourceData, config *Config) interface{} {
if v == nil {
return nil
}
original := v.(map[string]interface{})
if len(original) == 0 {
return nil
}
transformed := make(map[string]interface{})
transformed["expression"] =
flattenAccessContextManagerAccessLevelCustomExprExpression(original["expression"], d, config)
transformed["title"] =
flattenAccessContextManagerAccessLevelCustomExprTitle(original["title"], d, config)
transformed["description"] =
flattenAccessContextManagerAccessLevelCustomExprDescription(original["description"], d, config)
transformed["location"] =
flattenAccessContextManagerAccessLevelCustomExprLocation(original["location"], d, config)
return []interface{}{transformed}
}
func flattenAccessContextManagerAccessLevelCustomExprExpression(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func flattenAccessContextManagerAccessLevelCustomExprTitle(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func flattenAccessContextManagerAccessLevelCustomExprDescription(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func flattenAccessContextManagerAccessLevelCustomExprLocation(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func flattenAccessContextManagerAccessLevelName(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}
Expand Down Expand Up @@ -835,6 +947,81 @@ func expandAccessContextManagerAccessLevelBasicConditionsRegions(v interface{},
return v, nil
}

func expandAccessContextManagerAccessLevelCustom(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
l := v.([]interface{})
if len(l) == 0 || l[0] == nil {
return nil, nil
}
raw := l[0]
original := raw.(map[string]interface{})
transformed := make(map[string]interface{})

transformedExpr, err := expandAccessContextManagerAccessLevelCustomExpr(original["expr"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedExpr); val.IsValid() && !isEmptyValue(val) {
transformed["expr"] = transformedExpr
}

return transformed, nil
}

func expandAccessContextManagerAccessLevelCustomExpr(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
l := v.([]interface{})
if len(l) == 0 || l[0] == nil {
return nil, nil
}
raw := l[0]
original := raw.(map[string]interface{})
transformed := make(map[string]interface{})

transformedExpression, err := expandAccessContextManagerAccessLevelCustomExprExpression(original["expression"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedExpression); val.IsValid() && !isEmptyValue(val) {
transformed["expression"] = transformedExpression
}

transformedTitle, err := expandAccessContextManagerAccessLevelCustomExprTitle(original["title"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedTitle); val.IsValid() && !isEmptyValue(val) {
transformed["title"] = transformedTitle
}

transformedDescription, err := expandAccessContextManagerAccessLevelCustomExprDescription(original["description"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedDescription); val.IsValid() && !isEmptyValue(val) {
transformed["description"] = transformedDescription
}

transformedLocation, err := expandAccessContextManagerAccessLevelCustomExprLocation(original["location"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedLocation); val.IsValid() && !isEmptyValue(val) {
transformed["location"] = transformedLocation
}

return transformed, nil
}

func expandAccessContextManagerAccessLevelCustomExprExpression(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandAccessContextManagerAccessLevelCustomExprTitle(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandAccessContextManagerAccessLevelCustomExprDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandAccessContextManagerAccessLevelCustomExprLocation(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandAccessContextManagerAccessLevelParent(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}
Expand Down
41 changes: 41 additions & 0 deletions google/resource_access_context_manager_access_level_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,26 @@ func testAccCheckAccessContextManagerAccessLevelDestroyProducer(t *testing.T) fu
}
}

func testAccAccessContextManagerAccessLevel_customTest(t *testing.T) {
org := getTestOrgFromEnv(t)

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAccessContextManagerAccessLevelDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccAccessContextManagerAccessLevel_custom(org, "my policy", "level"),
},
{
ResourceName: "google_access_context_manager_access_level.test-access",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccAccessContextManagerAccessLevel_basic(org, policyTitle, levelTitleName string) string {
return fmt.Sprintf(`
resource "google_access_context_manager_access_policy" "test-access" {
Expand All @@ -105,6 +125,27 @@ resource "google_access_context_manager_access_level" "test-access" {
`, org, policyTitle, levelTitleName, levelTitleName)
}

func testAccAccessContextManagerAccessLevel_custom(org, policyTitle, levelTitleName string) string {
return fmt.Sprintf(`
resource "google_access_context_manager_access_policy" "test-access" {
parent = "organizations/%s"
title = "%s"
}
resource "google_access_context_manager_access_level" "test-access" {
parent = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}"
name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/accessLevels/%s"
title = "%s"
description = "hello"
custom {
expr {
expression = "device.os_type == OsType.DESKTOP_MAC"
}
}
}
`, org, policyTitle, levelTitleName, levelTitleName)
}

func testAccAccessContextManagerAccessLevel_basicUpdated(org, policyTitle, levelTitleName string) string {
return fmt.Sprintf(`
resource "google_access_context_manager_access_policy" "test-access" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func TestAccAccessContextManager(t *testing.T) {
"service_perimeter_resource": testAccAccessContextManagerServicePerimeterResource_basicTest,
"access_level": testAccAccessContextManagerAccessLevel_basicTest,
"access_level_full": testAccAccessContextManagerAccessLevel_fullTest,
"access_level_custom": testAccAccessContextManagerAccessLevel_customTest,
}

for name, tc := range testCases {
Expand Down
32 changes: 32 additions & 0 deletions website/docs/r/access_context_manager_access_level.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ The following arguments are supported:
(Optional)
A set of predefined conditions for the access level and a combining function. Structure is documented below.

* `custom` -
(Optional)
Custom access level conditions are set using the Cloud Common Expression Language to represent the necessary conditions for the level to apply to a request.
See CEL spec at: https://github.com/google/cel-spec. Structure is documented below.


The `basic` block supports:

Expand Down Expand Up @@ -219,6 +224,33 @@ The `os_constraints` block supports:
* `DESKTOP_LINUX`
* `DESKTOP_CHROME_OS`

The `custom` block supports:

* `expr` -
(Required)
Represents a textual expression in the Common Expression Language (CEL) syntax. CEL is a C-like expression language.
This page details the objects and attributes that are used to the build the CEL expressions for
custom access levels - https://cloud.google.com/access-context-manager/docs/custom-access-level-spec. Structure is documented below.


The `expr` block supports:

* `expression` -
(Required)
Textual representation of an expression in Common Expression Language syntax.

* `title` -
(Optional)
Title for the expression, i.e. a short string describing its purpose.

* `description` -
(Optional)
Description of the expression

* `location` -
(Optional)
String indicating the location of the expression for error reporting, e.g. a file name and a position in the file

## Attributes Reference

In addition to the arguments listed above, the following computed attributes are exported:
Expand Down

0 comments on commit 4b93678

Please sign in to comment.