Skip to content

Commit

Permalink
Add region_from_zone provider function (#10073)
Browse files Browse the repository at this point in the history
* add regionFromZone function logic

* add region_from_zone_test.go

* add region_from_zone_internal_test.go

* add region_from_zone.html.markdown

* get region_from_zone_test.go passing

* fix region_from_zone_internal_test.go tests

* shorten test config setup time

* have more explicit error for invalid input

* Update mmv1/third_party/terraform/functions/region_from_zone.go

Co-authored-by: Sarah French <[email protected]>

* use compute_disk for better test time

* Update mmv1/third_party/terraform/website/docs/functions/region_from_zone.html.markdown

Co-authored-by: Sarah French <[email protected]>

* Update mmv1/third_party/terraform/functions/region_from_zone_test.go

* Add `location_from_id` provider-defined function (#10061)

* Add `location_from_id` function, tests, docs

* Fix whitespace in acc test HCL config

* fix runtime error and update internal test output

* Update mmv1/third_party/terraform/functions/region_from_zone_test.go

Co-authored-by: Sarah French <[email protected]>

* update to use projectRegionRegex

* Update mmv1/third_party/terraform/functions/region_from_zone.go

---------

Co-authored-by: Sarah French <[email protected]>
  • Loading branch information
BBBmau and SarahFrench authored Mar 11, 2024
1 parent f830c0a commit 5e798ac
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 0 deletions.
64 changes: 64 additions & 0 deletions mmv1/third_party/terraform/functions/region_from_zone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package functions

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/function"
)

var _ function.Function = RegionFromZoneFunction{}

func NewRegionFromZoneFunction() function.Function {
return &RegionFromZoneFunction{}
}

type RegionFromZoneFunction struct{}

func (f RegionFromZoneFunction) Metadata(ctx context.Context, req function.MetadataRequest, resp *function.MetadataResponse) {
resp.Name = "region_from_zone"
}

func (f RegionFromZoneFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) {
resp.Definition = function.Definition{
Summary: "Returns the region within a provided resource's zone",
Description: "Takes a single string argument, which should be a resource's zone.",
Parameters: []function.Parameter{
function.StringParameter{
Name: "zone",
Description: "A string of a resource's zone.",
},
},
Return: function.StringReturn{},
}
}

func (f RegionFromZoneFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) {
// Load arguments from function call
var arg0 string
resp.Diagnostics.Append(req.Arguments.GetArgument(ctx, 0, &arg0)...)

if resp.Diagnostics.HasError() {
return
}

if arg0 == "" {
resp.Diagnostics.AddArgumentError(
0,
noMatchesErrorSummary,
"The input string cannot be empty.",
)
return
}

if arg0[len(arg0)-2] != '-' {
resp.Diagnostics.AddArgumentError(
0,
noMatchesErrorSummary,
fmt.Sprintf("The input string \"%s\" is not a valid zone name.", arg0),
)
return
}

resp.Diagnostics.Append(resp.Result.Set(ctx, arg0[:len(arg0)-2])...)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package functions

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)

func TestFunctionRun_region_from_zone(t *testing.T) {
t.Parallel()

region := "us-central1"

testCases := map[string]struct {
request function.RunRequest
expected function.RunResponse
}{
"it returns the expected output value when given a valid zone input": {
request: function.RunRequest{
Arguments: function.NewArgumentsData([]attr.Value{types.StringValue("us-central1-b")}),
},
expected: function.RunResponse{
Result: function.NewResultData(types.StringValue(region)),
},
},
"it returns an error when given input is empty": {
request: function.RunRequest{
Arguments: function.NewArgumentsData([]attr.Value{types.StringValue("")}),
},
expected: function.RunResponse{
Result: function.NewResultData(types.StringNull()),
Diagnostics: diag.Diagnostics{
diag.NewArgumentErrorDiagnostic(
0,
noMatchesErrorSummary,
"The input string cannot be empty.",
),
},
},
},
"it returns an error when given input is not a zone": {
request: function.RunRequest{
Arguments: function.NewArgumentsData([]attr.Value{types.StringValue("foobar")}),
},
expected: function.RunResponse{
Result: function.NewResultData(types.StringNull()),
Diagnostics: diag.Diagnostics{
diag.NewArgumentErrorDiagnostic(
0,
noMatchesErrorSummary,
"The input string \"foobar\" is not a valid zone name.",
),
},
},
},
}

for name, testCase := range testCases {
tn, tc := name, testCase

t.Run(tn, func(t *testing.T) {
t.Parallel()

// Arrange
got := function.RunResponse{
Result: function.NewResultData(basetypes.StringValue{}),
}

// Act
NewRegionFromZoneFunction().Run(context.Background(), tc.request, &got)

// Assert
if diff := cmp.Diff(got.Result, tc.expected.Result); diff != "" {
t.Errorf("unexpected diff between expected and received result: %s", diff)
}
if diff := cmp.Diff(got.Diagnostics, tc.expected.Diagnostics); diff != "" {
t.Errorf("unexpected diff between expected and received diagnostics: %s", diff)
}
})
}
}
66 changes: 66 additions & 0 deletions mmv1/third_party/terraform/functions/region_from_zone_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package functions_test

import (
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-provider-google/google/acctest"
)

func TestAccProviderFunction_region_from_zone(t *testing.T) {
t.Parallel()
// Skipping due to requiring TF 1.8.0 in VCR systems : https://github.com/hashicorp/terraform-provider-google/issues/17451
acctest.SkipIfVcr(t)
projectZone := "us-central1-a"
projectRegion := "us-central1"
projectRegionRegex := regexp.MustCompile(fmt.Sprintf("^%s$", projectRegion))

context := map[string]interface{}{
"function_name": "region_from_zone",
"output_name": "zone",
"resource_name": fmt.Sprintf("tf-test-region-from-zone-func-%s", acctest.RandString(t, 10)),
"resource_location": projectZone,
}

acctest.VcrTest(t, resource.TestCase{
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
Steps: []resource.TestStep{
{
Config: testProviderFunction_get_region_from_zone(context),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchOutput(context["output_name"].(string), projectRegionRegex),
),
},
},
})
}

func testProviderFunction_get_region_from_zone(context map[string]interface{}) string {
return acctest.Nprintf(`
# terraform block required for provider function to be found
terraform {
required_providers {
google = {
source = "hashicorp/google"
}
}
}
resource "google_compute_disk" "default" {
name = "%{resource_name}"
type = "pd-ssd"
zone = "%{resource_location}"
image = "debian-11-bullseye-v20220719"
labels = {
environment = "dev"
}
physical_block_size_bytes = 4096
}
output "%{output_name}" {
value = provider::google::%{function_name}(google_compute_disk.default.zone)
}
`, context)
}
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ func (p *FrameworkProvider) Resources(_ context.Context) []func() resource.Resou
func (p *FrameworkProvider) Functions(_ context.Context) []func() function.Function {
return []func() function.Function{
functions.NewProjectFromIdFunction,
functions.NewRegionFromZoneFunction,
functions.NewLocationFromIdFunction,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
page_title: region_from_zone Function - terraform-provider-google
description: |-
Returns the region within a provided zone.
---

# Function: region_from_zone

Returns a region name derived from a provided zone.

For more information about using provider-defined functions with Terraform [see the official documentation](https://developer.hashicorp.com/terraform/plugin/framework/functions/concepts).

## Example Usage

### Use with the `google` provider

```terraform
terraform {
required_providers {
google = {
source = "hashicorp/google"
}
}
}
# Value is "us-central1"
output "function_output" {
value = provider::google::region_from_zone("us-central1-b")
}
```

### Use with the `google-beta` provider

```terraform
terraform {
required_providers {
google-beta = {
source = "hashicorp/google-beta"
}
}
}
# Value is "us-central1"
output "function_output" {
value = provider::google-beta::region_from_zone("us-central1-b")
}
```

## Signature

```text
region_from_zone(zone string) string
```

## Arguments

1. `zone` (String) A string of a resource's zone

0 comments on commit 5e798ac

Please sign in to comment.