-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
location_from_id
provider-defined function (#10061)
* Add `location_from_id` function, tests, docs * Fix whitespace in acc test HCL config
- Loading branch information
1 parent
8c31f79
commit 880aad8
Showing
5 changed files
with
308 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package functions | ||
|
||
import ( | ||
"context" | ||
"regexp" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/function" | ||
) | ||
|
||
var _ function.Function = LocationFromIdFunction{} | ||
|
||
func NewLocationFromIdFunction() function.Function { | ||
return &LocationFromIdFunction{} | ||
} | ||
|
||
type LocationFromIdFunction struct{} | ||
|
||
func (f LocationFromIdFunction) Metadata(ctx context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { | ||
resp.Name = "location_from_id" | ||
} | ||
|
||
func (f LocationFromIdFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { | ||
resp.Definition = function.Definition{ | ||
Summary: "Returns the location name within a provided resource id, self link, or OP style resource name.", | ||
Description: "Takes a single string argument, which should be a resource id, self link, or OP style resource name. This function will either return the location name from the input string or raise an error due to no location being present in the string. The function uses the presence of \"locations/{{location}}/\" in the input string to identify the location name, e.g. when the function is passed the id \"projects/my-project/locations/us-central1/services/my-service\" as an argument it will return \"us-central1\".", | ||
Parameters: []function.Parameter{ | ||
function.StringParameter{ | ||
Name: "id", | ||
Description: "A string of a resource's id, a resource's self link, or an OP style resource name. For example, \"projects/my-project/locations/us-central1/services/my-service\" and \"https://run.googleapis.com/v2/projects/my-project/locations/us-central1/services/my-service\" are valid values containing locations", | ||
}, | ||
}, | ||
Return: function.StringReturn{}, | ||
} | ||
} | ||
|
||
func (f LocationFromIdFunction) 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 | ||
} | ||
|
||
// Prepare how we'll identify location name from input string | ||
regex := regexp.MustCompile("locations/(?P<LocationName>[^/]+)/") // Should match the pattern below | ||
template := "$LocationName" // Should match the submatch identifier in the regex | ||
pattern := "locations/{location}/" // Human-readable pseudo-regex pattern used in errors and warnings | ||
|
||
// Validate input | ||
ValidateElementFromIdArguments(arg0, regex, pattern, resp) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
// Get and return element from input string | ||
location := GetElementFromId(arg0, regex, template) | ||
resp.Diagnostics.Append(resp.Result.Set(ctx, location)...) | ||
} |
113 changes: 113 additions & 0 deletions
113
mmv1/third_party/terraform/functions/location_from_id_internal_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package functions | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"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_location_from_id(t *testing.T) { | ||
t.Parallel() | ||
|
||
location := "us-central1" | ||
|
||
// Happy path inputs | ||
validId := fmt.Sprintf("projects/my-project/locations/%s/services/my-service", location) | ||
validSelfLink := fmt.Sprintf("https://run.googleapis.com/v2/%s", validId) | ||
validOpStyleResourceName := fmt.Sprintf("//run.googleapis.com/v2/%s", validId) | ||
|
||
// Unhappy path inputs | ||
repetitiveInput := fmt.Sprintf("https://run.googleapis.com/v2/projects/my-project/locations/%s/locations/not-this-one/services/my-service", location) // Multiple /locations/{{location}}/ | ||
invalidInput := "zones/us-central1-c/instances/my-instance" | ||
|
||
testCases := map[string]struct { | ||
request function.RunRequest | ||
expected function.RunResponse | ||
}{ | ||
"it returns the expected output value when given a valid resource id input": { | ||
request: function.RunRequest{ | ||
Arguments: function.NewArgumentsData([]attr.Value{types.StringValue(validId)}), | ||
}, | ||
expected: function.RunResponse{ | ||
Result: function.NewResultData(types.StringValue(location)), | ||
}, | ||
}, | ||
"it returns the expected output value when given a valid resource self_link input": { | ||
request: function.RunRequest{ | ||
Arguments: function.NewArgumentsData([]attr.Value{types.StringValue(validSelfLink)}), | ||
}, | ||
expected: function.RunResponse{ | ||
Result: function.NewResultData(types.StringValue(location)), | ||
}, | ||
}, | ||
"it returns the expected output value when given a valid OP style resource name input": { | ||
request: function.RunRequest{ | ||
Arguments: function.NewArgumentsData([]attr.Value{types.StringValue(validOpStyleResourceName)}), | ||
}, | ||
expected: function.RunResponse{ | ||
Result: function.NewResultData(types.StringValue(location)), | ||
}, | ||
}, | ||
"it returns a warning and the first submatch when given repetitive input": { | ||
request: function.RunRequest{ | ||
Arguments: function.NewArgumentsData([]attr.Value{types.StringValue(repetitiveInput)}), | ||
}, | ||
expected: function.RunResponse{ | ||
Result: function.NewResultData(types.StringValue(location)), | ||
Diagnostics: diag.Diagnostics{ | ||
diag.NewArgumentWarningDiagnostic( | ||
0, | ||
ambiguousMatchesWarningSummary, | ||
fmt.Sprintf("The input string \"%s\" contains more than one match for the pattern \"locations/{location}/\". Terraform will use the first found match.", repetitiveInput), | ||
), | ||
}, | ||
}, | ||
}, | ||
"it returns an error when given input with no submatches": { | ||
request: function.RunRequest{ | ||
Arguments: function.NewArgumentsData([]attr.Value{types.StringValue(invalidInput)}), | ||
}, | ||
expected: function.RunResponse{ | ||
Result: function.NewResultData(types.StringNull()), | ||
Diagnostics: diag.Diagnostics{ | ||
diag.NewArgumentErrorDiagnostic( | ||
0, | ||
noMatchesErrorSummary, | ||
fmt.Sprintf("The input string \"%s\" doesn't contain the expected pattern \"locations/{location}/\".", invalidInput), | ||
), | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
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 | ||
NewLocationFromIdFunction().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) | ||
} | ||
}) | ||
} | ||
} |
74 changes: 74 additions & 0 deletions
74
mmv1/third_party/terraform/functions/location_from_id_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package functions_test | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
"github.com/hashicorp/terraform-provider-google/google/acctest" | ||
"github.com/hashicorp/terraform-provider-google/google/envvar" | ||
) | ||
|
||
func TestAccProviderFunction_location_from_id(t *testing.T) { | ||
t.Parallel() | ||
|
||
location := envvar.GetTestRegionFromEnv() | ||
locationRegex := regexp.MustCompile(fmt.Sprintf("^%s$", location)) | ||
|
||
context := map[string]interface{}{ | ||
"function_name": "location_from_id", | ||
"output_name": "location", | ||
"resource_name": fmt.Sprintf("tf-test-location-id-func-%s", acctest.RandString(t, 10)), | ||
"resource_location": location, | ||
} | ||
|
||
acctest.VcrTest(t, resource.TestCase{ | ||
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), | ||
Steps: []resource.TestStep{ | ||
{ | ||
// Can get the location from a resource's id in one step | ||
// Uses google_cloud_run_service resource's id attribute with format projects/{project}/locations/{location}/services/{service}. | ||
Config: testProviderFunction_get_location_from_resource_id(context), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestMatchOutput(context["output_name"].(string), locationRegex), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testProviderFunction_get_location_from_resource_id(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_cloud_run_service" "default" { | ||
name = "%{resource_name}" | ||
location = "%{resource_location}" | ||
template { | ||
spec { | ||
containers { | ||
image = "us-docker.pkg.dev/cloudrun/container/hello" | ||
} | ||
} | ||
} | ||
traffic { | ||
percent = 100 | ||
latest_revision = true | ||
} | ||
} | ||
output "%{output_name}" { | ||
value = provider::google::%{function_name}(google_cloud_run_service.default.id) | ||
} | ||
`, context) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
mmv1/third_party/terraform/website/docs/functions/location_from_id.html.markdown
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
--- | ||
page_title: location_from_id Function - terraform-provider-google | ||
description: |- | ||
Returns the location within a provided resource id, self link, or OP style resource name. | ||
--- | ||
|
||
# Function: location_from_id | ||
|
||
Returns the location within a provided resource's id, resource URI, self link, or full resource name. | ||
|
||
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::location_from_id("https://run.googleapis.com/v2/projects/my-project/locations/us-central1/services/my-service") | ||
} | ||
``` | ||
|
||
### 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::location_from_id("https://run.googleapis.com/v2/projects/my-project/locations/us-central1/services/my-service") | ||
} | ||
``` | ||
|
||
## Signature | ||
|
||
```text | ||
location_from_id(id string) string | ||
``` | ||
|
||
## Arguments | ||
|
||
1. `id` (String) A string of a resource's id, resource URI, self link, or full resource name. For example, these are all valid values: | ||
|
||
* `"projects/my-project/locations/us-central1/services/my-service"` | ||
* `"https://run.googleapis.com/v2/projects/my-project/locations/us-central1/services/my-service"` | ||
* `"//run.googleapis.com/v2/projects/my-project/locations/us-central1/services/my-service"` |