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

Update hashicorp/terraform-plugin-framework, terraform-plugin-mux, terraform-plugin-sdk/v2 to navigate breaking change in provider-defined functions #10109

Merged
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
25 changes: 9 additions & 16 deletions mmv1/third_party/terraform/functions/element_from_id.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,29 @@
package functions

import (
"context"
"fmt"
"log"
"regexp"

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

const noMatchesErrorSummary string = "No matches present in the input string"
const ambiguousMatchesWarningSummary string = "Ambiguous input string could contain more than one match"

// ValidateElementFromIdArguments is reusable validation logic used in provider-defined functions that use the getElementFromId function
func ValidateElementFromIdArguments(input string, regex *regexp.Regexp, pattern string, resp *function.RunResponse) {
// ValidateElementFromIdArguments is reusable validation logic used in provider-defined functions that use the GetElementFromId function
func ValidateElementFromIdArguments(ctx context.Context, input string, regex *regexp.Regexp, pattern string, functionName string) *function.FuncError {
submatches := regex.FindAllStringSubmatchIndex(input, -1)

// Zero matches means unusable input; error returned
if len(submatches) == 0 {
resp.Diagnostics.AddArgumentError(
0,
noMatchesErrorSummary,
fmt.Sprintf("The input string \"%s\" doesn't contain the expected pattern \"%s\".", input, pattern),
)
return function.NewArgumentFuncError(0, fmt.Sprintf("The input string \"%s\" doesn't contain the expected pattern \"%s\".", input, pattern))
}

// >1 matches means input usable but not ideal; issue warning
// >1 matches means input usable but not ideal; debug log
if len(submatches) > 1 {
resp.Diagnostics.AddArgumentWarning(
0,
ambiguousMatchesWarningSummary,
fmt.Sprintf("The input string \"%s\" contains more than one match for the pattern \"%s\". Terraform will use the first found match.", input, pattern),
)
log.Printf("[DEBUG] Provider-defined function %s was called with input string: %s. This contains more than one match for the pattern %s. Terraform will use the first found match.", functionName, input, pattern)
}

return nil
}

// GetElementFromId is reusable logic that is used in multiple provider-defined functions for pulling elements out of self links and ids of resources and data sources
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package functions_test

import (
"context"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
tpg_functions "github.com/hashicorp/terraform-provider-google/google/functions"
)

Expand All @@ -19,43 +18,33 @@ func TestFunctionInternals_ValidateElementFromIdArguments(t *testing.T) {
Input string
ExpectedElement string
ExpectError bool
ExpectWarning bool
}{
"it sets an error in diags if no match is found": {
"it sets an error if no match is found": {
Input: "one/element-1/three/element-3",
ExpectError: true,
},
"it sets a warning in diags if more than one match is found": {
"it doesn't set an error if more than one match is found": {
Input: "two/element-2/two/element-2/two/element-2",
ExpectedElement: "element-2",
ExpectWarning: true,
},
}

for tn, tc := range cases {
t.Run(tn, func(t *testing.T) {

// Arrange
resp := function.RunResponse{
Result: function.NewResultData(basetypes.StringValue{}),
}
ctx := context.Background()

// Act
tpg_functions.ValidateElementFromIdArguments(tc.Input, regex, pattern, &resp)
err := tpg_functions.ValidateElementFromIdArguments(ctx, tc.Input, regex, pattern, "function-name-here") // last arg value is inconsequential for this test

// Assert
if resp.Diagnostics.HasError() && !tc.ExpectError {
t.Fatalf("Unexpected error(s) were set in response diags: %s", resp.Diagnostics.Errors())
if err != nil && !tc.ExpectError {
t.Fatalf("Unexpected error(s) were set in response diags: %s", err.Text)
}
if !resp.Diagnostics.HasError() && tc.ExpectError {
if err == nil && tc.ExpectError {
t.Fatal("Expected error(s) to be set in response diags, but there were none.")
}
if (resp.Diagnostics.WarningsCount() > 0) && !tc.ExpectWarning {
t.Fatalf("Unexpected warning(s) were set in response diags: %s", resp.Diagnostics.Warnings())
}
if (resp.Diagnostics.WarningsCount() == 0) && tc.ExpectWarning {
t.Fatal("Expected warning(s) to be set in response diags, but there were none.")
}
})
}
}
Expand Down
21 changes: 12 additions & 9 deletions mmv1/third_party/terraform/functions/location_from_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ import (
var _ function.Function = LocationFromIdFunction{}

func NewLocationFromIdFunction() function.Function {
return &LocationFromIdFunction{}
return &LocationFromIdFunction{
name: "location_from_id",
}
}

type LocationFromIdFunction struct{}
type LocationFromIdFunction struct {
name string // Makes function name available in Run logic for logging purposes
}

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

func (f LocationFromIdFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) {
Expand All @@ -36,9 +40,8 @@ func (f LocationFromIdFunction) Definition(ctx context.Context, req function.Def
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() {
resp.Error = function.ConcatFuncErrors(req.Arguments.GetArgument(ctx, 0, &arg0))
if resp.Error != nil {
return
}

Expand All @@ -48,12 +51,12 @@ func (f LocationFromIdFunction) Run(ctx context.Context, req function.RunRequest
pattern := "locations/{location}/" // Human-readable pseudo-regex pattern used in errors and warnings

// Validate input
ValidateElementFromIdArguments(arg0, regex, pattern, resp)
if resp.Diagnostics.HasError() {
resp.Error = function.ConcatFuncErrors(ValidateElementFromIdArguments(ctx, arg0, regex, pattern, f.name))
if resp.Error != nil {
return
}

// Get and return element from input string
location := GetElementFromId(arg0, regex, template)
resp.Diagnostics.Append(resp.Result.Set(ctx, location)...)
resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, location))
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

"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"
Expand Down Expand Up @@ -55,19 +54,12 @@ func TestFunctionRun_location_from_id(t *testing.T) {
Result: function.NewResultData(types.StringValue(location)),
},
},
"it returns a warning and the first submatch when given repetitive input": {
"it returns the first submatch (with no error) 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": {
Expand All @@ -76,13 +68,7 @@ func TestFunctionRun_location_from_id(t *testing.T) {
},
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),
),
},
Error: function.NewArgumentFuncError(0, fmt.Sprintf("The input string \"%s\" doesn't contain the expected pattern \"locations/{location}/\".", invalidInput)),
},
},
}
Expand All @@ -105,8 +91,8 @@ func TestFunctionRun_location_from_id(t *testing.T) {
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)
if diff := cmp.Diff(got.Error, tc.expected.Error); diff != "" {
t.Errorf("unexpected diff between expected and received errors: %s", diff)
}
})
}
Expand Down
21 changes: 12 additions & 9 deletions mmv1/third_party/terraform/functions/name_from_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ import (
var _ function.Function = NameFromIdFunction{}

func NewNameFromIdFunction() function.Function {
return &NameFromIdFunction{}
return &NameFromIdFunction{
name: "name_from_id",
}
}

type NameFromIdFunction struct{}
type NameFromIdFunction struct {
name string
}

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

func (f NameFromIdFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) {
Expand All @@ -36,9 +40,8 @@ func (f NameFromIdFunction) Definition(ctx context.Context, req function.Definit
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() {
resp.Error = function.ConcatFuncErrors(req.Arguments.GetArgument(ctx, 0, &arg0))
if resp.Error != nil {
return
}

Expand All @@ -48,12 +51,12 @@ func (f NameFromIdFunction) Run(ctx context.Context, req function.RunRequest, re
pattern := "resourceType/{name}$" // Human-readable pseudo-regex pattern used in errors and warnings

// Validate input
ValidateElementFromIdArguments(arg0, regex, pattern, resp)
if resp.Diagnostics.HasError() {
resp.Error = function.ConcatFuncErrors(ValidateElementFromIdArguments(ctx, arg0, regex, pattern, f.name))
if resp.Error != nil {
return
}

// Get and return element from input string
name := GetElementFromId(arg0, regex, template)
resp.Diagnostics.Append(resp.Result.Set(ctx, name)...)
resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, name))
}
13 changes: 3 additions & 10 deletions mmv1/third_party/terraform/functions/name_from_id_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

"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"
Expand Down Expand Up @@ -60,13 +59,7 @@ func TestFunctionRun_name_from_id(t *testing.T) {
},
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),
),
},
Error: function.NewArgumentFuncError(0, fmt.Sprintf("The input string \"%s\" doesn't contain the expected pattern \"resourceType/{name}$\".", invalidInput)),
},
},
}
Expand All @@ -89,8 +82,8 @@ func TestFunctionRun_name_from_id(t *testing.T) {
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)
if diff := cmp.Diff(got.Error, tc.expected.Error); diff != "" {
t.Errorf("unexpected diff between expected and received errors: %s", diff)
}
})
}
Expand Down
8 changes: 4 additions & 4 deletions mmv1/third_party/terraform/functions/name_from_id_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ func TestAccProviderFunction_name_from_id(t *testing.T) {
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
Steps: []resource.TestStep{
{
// Can get the project from a resource's id in one step
// Can get the name 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),
Config: testProviderFunction_get_name_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
// Can get the name 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),
Config: testProviderFunction_get_name_from_resource_self_link(context),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchOutput(context["output_name"].(string), nameRegex),
),
Expand Down
21 changes: 12 additions & 9 deletions mmv1/third_party/terraform/functions/project_from_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ import (
var _ function.Function = ProjectFromIdFunction{}

func NewProjectFromIdFunction() function.Function {
return &ProjectFromIdFunction{}
return &ProjectFromIdFunction{
name: "project_from_id",
}
}

type ProjectFromIdFunction struct{}
type ProjectFromIdFunction struct {
name string // Makes function name available in Run logic for logging purposes
}

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

func (f ProjectFromIdFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) {
Expand All @@ -36,9 +40,8 @@ func (f ProjectFromIdFunction) Definition(ctx context.Context, req function.Defi
func (f ProjectFromIdFunction) 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() {
resp.Error = function.ConcatFuncErrors(req.Arguments.GetArgument(ctx, 0, &arg0))
if resp.Error != nil {
return
}

Expand All @@ -48,12 +51,12 @@ func (f ProjectFromIdFunction) Run(ctx context.Context, req function.RunRequest,
pattern := "projects/{project}/" // Human-readable pseudo-regex pattern used in errors and warnings

// Validate input
ValidateElementFromIdArguments(arg0, regex, pattern, resp)
if resp.Diagnostics.HasError() {
resp.Error = function.ConcatFuncErrors(ValidateElementFromIdArguments(ctx, arg0, regex, pattern, f.name))
if resp.Error != nil {
return
}

// Get and return element from input string
projectId := GetElementFromId(arg0, regex, template)
resp.Diagnostics.Append(resp.Result.Set(ctx, projectId)...)
resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, projectId))
}
Loading