Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add region_from_zone provider function #17544

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/10073.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
provider: added provider-defined function `region_from_zone` for retrieving the region from a resource's location/zone
```
66 changes: 66 additions & 0 deletions google/functions/region_from_zone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
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])...)
}
89 changes: 89 additions & 0 deletions google/functions/region_from_zone_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
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)
}
})
}
}
68 changes: 68 additions & 0 deletions google/functions/region_from_zone_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
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)
}
1 change: 1 addition & 0 deletions google/fwprovider/framework_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,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,
}
}
57 changes: 57 additions & 0 deletions website/docs/functions/region_from_zone.html.markdown
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