-
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 resource
name_from_id
provider-defined function (#10106)
* Add `name_from_id` function, tests, docs * Fix whitespace in acc tests' HCL config * Skip provider-defined acceptance test in VCR, as VCR system doesn't use required Terraform version yet * Update documentation examples to meet feedback given in #10060 * Update `name_from_id` acc tests to not use networking-related resources * Update mmv1/third_party/terraform/functions/name_from_id_test.go
- Loading branch information
1 parent
533afb6
commit f6c50c4
Showing
5 changed files
with
317 additions
and
2 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 = NameFromIdFunction{} | ||
|
||
func NewNameFromIdFunction() function.Function { | ||
return &NameFromIdFunction{} | ||
} | ||
|
||
type NameFromIdFunction struct{} | ||
|
||
func (f NameFromIdFunction) Metadata(ctx context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { | ||
resp.Name = "name_from_id" | ||
} | ||
|
||
func (f NameFromIdFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { | ||
resp.Definition = function.Definition{ | ||
Summary: "Returns the short-form name of a resource within a provided resource's id, resource URI, self link, or full resource name.", | ||
Description: "Takes a single string argument, which should be a resource's id, resource URI, self link, or full resource name. This function will return the short-form name of a resource from the input string, or raise an error due to a problem with the input string. The function returns the final element in the input string as the resource's name, e.g. when the function is passed the id \"projects/my-project/zones/us-central1-c/instances/my-instance\" as an argument it will return \"my-instance\".", | ||
Parameters: []function.Parameter{ | ||
function.StringParameter{ | ||
Name: "id", | ||
Description: "A string of a resource's id, resource URI, self link, or full resource name. For example, \"projects/my-project/zones/us-central1-c/instances/my-instance\", \"https://www.googleapis.com/compute/v1/projects/my-project/zones/us-central1-c/instances/my-instance\" and \"//gkehub.googleapis.com/projects/my-project/locations/us-central1/memberships/my-membership\" are valid values", | ||
}, | ||
}, | ||
Return: function.StringReturn{}, | ||
} | ||
} | ||
|
||
func (f NameFromIdFunction) 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 resource name from input string | ||
regex := regexp.MustCompile("/(?P<ResourceName>[^/]+)$") // Should match the pattern below | ||
template := "$ResourceName" // Should match the submatch identifier in the regex | ||
pattern := "resourceType/{name}$" // 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 | ||
name := GetElementFromId(arg0, regex, template) | ||
resp.Diagnostics.Append(resp.Result.Set(ctx, name)...) | ||
} |
97 changes: 97 additions & 0 deletions
97
mmv1/third_party/terraform/functions/name_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,97 @@ | ||
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_name_from_id(t *testing.T) { | ||
t.Parallel() | ||
|
||
name := "foobar" | ||
|
||
// Happy path inputs | ||
validId := fmt.Sprintf("projects/my-project/zones/us-central1-c/instances/%s", name) | ||
validSelfLink := fmt.Sprintf("https://www.googleapis.com/compute/v1/%s", validId) | ||
validOpStyleResourceName := fmt.Sprintf("//gkehub.googleapis.com/projects/my-project/locations/us-central1/memberships/%s", name) | ||
|
||
// Unhappy path inputs | ||
invalidInput := "this isn't a URI or id" | ||
|
||
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(name)), | ||
}, | ||
}, | ||
"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(name)), | ||
}, | ||
}, | ||
"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(name)), | ||
}, | ||
}, | ||
"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 \"resourceType/{name}$\".", 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 | ||
NewNameFromIdFunction().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) | ||
} | ||
}) | ||
} | ||
} |
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,88 @@ | ||
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_name_from_id(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) | ||
|
||
context := map[string]interface{}{ | ||
"function_name": "name_from_id", | ||
"output_name": "name", | ||
"resource_name": fmt.Sprintf("tf-test-name-id-func-%s", acctest.RandString(t, 10)), | ||
} | ||
|
||
nameRegex := regexp.MustCompile(fmt.Sprintf("^%s$", context["resource_name"])) | ||
|
||
acctest.VcrTest(t, resource.TestCase{ | ||
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), | ||
Steps: []resource.TestStep{ | ||
{ | ||
// Can get the project from a resource's id in one step | ||
// Uses google_pubsub_topic resource's id attribute with format projects/{{project}}/topics/{{name}} | ||
Config: testProviderFunction_get_project_from_resource_id(context), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestMatchOutput(context["output_name"].(string), nameRegex), | ||
), | ||
}, | ||
{ | ||
// Can get the project from a resource's self_link in one step | ||
// Uses google_compute_disk resource's self_link attribute | ||
Config: testProviderFunction_get_project_from_resource_self_link(context), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestMatchOutput(context["output_name"].(string), nameRegex), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testProviderFunction_get_name_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_pubsub_topic" "default" { | ||
name = "%{resource_name}" | ||
} | ||
output "%{output_name}" { | ||
value = provider::google::%{function_name}(google_pubsub_topic.default.id) | ||
} | ||
`, context) | ||
} | ||
|
||
func testProviderFunction_get_name_from_resource_self_link(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}" | ||
} | ||
output "%{output_name}" { | ||
value = provider::google::%{function_name}(google_compute_disk.default.self_link) | ||
} | ||
`, 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
70 changes: 70 additions & 0 deletions
70
mmv1/third_party/terraform/website/docs/functions/name_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,70 @@ | ||
--- | ||
page_title: name_from_id Function - terraform-provider-google | ||
description: |- | ||
Returns the project within a provided resource id, self link, or OP style resource name. | ||
--- | ||
|
||
# Function: name_from_id | ||
|
||
Returns the short-form name 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" | ||
} | ||
} | ||
} | ||
resource "google_pubsub_topic" "default" { | ||
name = "my-topic" | ||
} | ||
# Value is "my-topic" | ||
output "function_output" { | ||
value = provider::google::name_from_id(google_pubsub_topic.default.id) | ||
} | ||
``` | ||
|
||
### Use with the `google-beta` provider | ||
|
||
```terraform | ||
terraform { | ||
required_providers { | ||
google-beta = { | ||
source = "hashicorp/google-beta" | ||
} | ||
} | ||
} | ||
resource "google_pubsub_topic" "default" { | ||
# provider argument omitted - provisioning by google or google-beta doesn't impact this example | ||
name = "my-topic" | ||
} | ||
# Value is "my-topic" | ||
output "function_output" { | ||
value = provider::google-beta::name_from_id(google_pubsub_topic.default.id) | ||
} | ||
``` | ||
|
||
## Signature | ||
|
||
```text | ||
name_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/zones/us-central1-c/instances/my-instance"` | ||
* `"https://www.googleapis.com/compute/v1/projects/my-project/zones/us-central1-c/instances/my-instance"` | ||
* `"//gkehub.googleapis.com/projects/my-project/locations/us-central1/memberships/my-membership"` |