From 9270aa69723c73a60d5a05ccab8be648cfddc6ca Mon Sep 17 00:00:00 2001 From: Bruno Schaatsbergen Date: Sat, 6 Apr 2024 01:23:07 +0200 Subject: [PATCH] feat: add value function --- docs/functions/key.md | 2 +- docs/functions/value.md | 53 +++++++++ examples/functions/value/function.tf | 8 ++ examples/functions/value/variable.tf | 7 ++ internal/provider/key_function.go | 4 + internal/provider/provider.go | 1 + internal/provider/value_function.go | 73 ++++++++++++ internal/provider/value_function_test.go | 145 +++++++++++++++++++++++ templates/functions/key.md.tmpl | 2 +- templates/functions/value.md.tmpl | 35 ++++++ 10 files changed, 328 insertions(+), 2 deletions(-) create mode 100644 docs/functions/value.md create mode 100644 examples/functions/value/function.tf create mode 100644 examples/functions/value/variable.tf create mode 100644 internal/provider/value_function.go create mode 100644 internal/provider/value_function_test.go create mode 100644 templates/functions/value.md.tmpl diff --git a/docs/functions/key.md b/docs/functions/key.md index 03d07f0..676bbd4 100644 --- a/docs/functions/key.md +++ b/docs/functions/key.md @@ -1,6 +1,6 @@ --- page_title: "key function - terraform-provider-assert" -subcategory: "IP Address Functions" +subcategory: "Map Functions" description: |- Checks whether a key exists in a map --- diff --git a/docs/functions/value.md b/docs/functions/value.md new file mode 100644 index 0000000..b022950 --- /dev/null +++ b/docs/functions/value.md @@ -0,0 +1,53 @@ +--- +page_title: "value function - terraform-provider-assert" +subcategory: "Map Functions" +description: |- + Checks whether a value exists in a map +--- + +# function: value + + + +## Terraform Test Example + +```terraform +run "check_if_lambda_function_tags_has_value" { + command = plan + + assert { + condition = provider::assert::value(aws_lambda_function.example.tags, "value1") + error_message = "The tags map must contain the value 'value1'" + } +} +``` + +## Variable Validation Example + +```terraform +variable "tags" { + type = map(string) + validation { + condition = provider::assert::value(var.tags, "value1") + error_message = "The tags map must contain the value 'value1'" + } +} +``` + +## Signature + + +```text +value(map map of string, value string) bool +``` + +## Arguments + + +1. `map` (Map of String) The map to check +1. `value` (String) The value to check + + +## Return Type + +The return type of `value` is a boolean. diff --git a/examples/functions/value/function.tf b/examples/functions/value/function.tf new file mode 100644 index 0000000..7febf4d --- /dev/null +++ b/examples/functions/value/function.tf @@ -0,0 +1,8 @@ +run "check_if_lambda_function_tags_has_value" { + command = plan + + assert { + condition = provider::assert::value(aws_lambda_function.example.tags, "value1") + error_message = "The tags map must contain the value 'value1'" + } +} diff --git a/examples/functions/value/variable.tf b/examples/functions/value/variable.tf new file mode 100644 index 0000000..55d17ac --- /dev/null +++ b/examples/functions/value/variable.tf @@ -0,0 +1,7 @@ +variable "tags" { + type = map(string) + validation { + condition = provider::assert::value(var.tags, "value1") + error_message = "The tags map must contain the value 'value1'" + } +} diff --git a/internal/provider/key_function.go b/internal/provider/key_function.go index f6c649a..d85e352 100644 --- a/internal/provider/key_function.go +++ b/internal/provider/key_function.go @@ -59,6 +59,10 @@ func (r KeyFunction) Run(ctx context.Context, req function.RunRequest, resp *fun } func hasKey(mapValue *map[string]string, key *string) bool { + if mapValue == nil { + return false + } + _, ok := (*mapValue)[*key] return ok } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 5e14549..80741a9 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -77,6 +77,7 @@ func (p *AssertProvider) Functions(ctx context.Context) []func() function.Functi NewNegativeFunction, NewPositiveFunction, NewKeyFunction, + NewValueFunction, } } diff --git a/internal/provider/value_function.go b/internal/provider/value_function.go new file mode 100644 index 0000000..6210586 --- /dev/null +++ b/internal/provider/value_function.go @@ -0,0 +1,73 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +var ( + _ function.Function = ValueFunction{} +) + +func NewValueFunction() function.Function { + return ValueFunction{} +} + +type ValueFunction struct{} + +func (r ValueFunction) Metadata(_ context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { + resp.Name = "value" +} + +func (r ValueFunction) Definition(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + Summary: "Checks whether a value exists in a map", + Parameters: []function.Parameter{ + function.MapParameter{ + AllowNullValue: false, + AllowUnknownValues: true, + Description: "The map to check", + Name: "map", + ElementType: basetypes.StringType{}, + }, + function.StringParameter{ + AllowNullValue: false, + AllowUnknownValues: true, + Description: "The value to check", + Name: "value", + }, + }, + Return: function.BoolReturn{}, + } +} + +func (r ValueFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var mapValue *map[string]string + var value *string + + resp.Error = function.ConcatFuncErrors(req.Arguments.Get(ctx, &mapValue, &value)) + if resp.Error != nil { + return + } + + resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, hasValue(mapValue, value))) +} + +func hasValue(mapValue *map[string]string, value *string) bool { + if mapValue == nil { + return false + } + + for _, v := range *mapValue { + if v == *value { + return true + } + } + + return false +} diff --git a/internal/provider/value_function_test.go b/internal/provider/value_function_test.go new file mode 100644 index 0000000..c58f925 --- /dev/null +++ b/internal/provider/value_function_test.go @@ -0,0 +1,145 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "testing" + + "github.com/hashicorp/go-version" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestValueFunction(t *testing.T) { + t.Parallel() + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(version.Must(version.NewVersion(MinimalRequiredTerraformVersion))), + }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +locals { + my_map = { + "key1" = "value1" + "key2" = "value2" + } +} +output "test" { + value = provider::assert::value(local.my_map, "value1") +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "true"), + ), + }, + }, + }) +} + +func TestValueFunction_mixed(t *testing.T) { + t.Parallel() + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(version.Must(version.NewVersion(MinimalRequiredTerraformVersion))), + }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +locals { + my_map = { + "key1" = true + "key2" = "value2" + 3 = "value3" + "4" = "value4" + 5 = 5 + "6" = false + } +} +output "test" { + value = provider::assert::value(local.my_map, "5") +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "true"), + ), + }, + }, + }) +} + +func TestValueFunction_nested_map(t *testing.T) { + t.Parallel() + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(version.Must(version.NewVersion(MinimalRequiredTerraformVersion))), + }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +locals { + my_map = { + "key1" = { + "nested" = "value2" + } + } +} +output "test" { + value = provider::assert::value(local.my_map["key1"], "value2") +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "true"), + ), + }, + }, + }) +} + +func TestValueFunction_falseCases(t *testing.T) { + t.Parallel() + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(version.Must(version.NewVersion(MinimalRequiredTerraformVersion))), + }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +locals { + my_map = { + "key1" = "value1" + } +} +output "test" { + value = provider::assert::value(local.my_map, "value2") +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "false"), + ), + }, + { + Config: ` +locals { + my_map = { + "key1" = { + "nested" = "value2" + } + } +} +output "test" { + value = provider::assert::value(local.my_map["key1"], "nested2") +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "false"), + ), + }, + }, + }) +} diff --git a/templates/functions/key.md.tmpl b/templates/functions/key.md.tmpl index 337fa57..6fa33ed 100644 --- a/templates/functions/key.md.tmpl +++ b/templates/functions/key.md.tmpl @@ -1,6 +1,6 @@ --- page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" -subcategory: "IP Address Functions" +subcategory: "Map Functions" description: |- {{ .Summary | plainmarkdown | trimspace | prefixlines " " }} --- diff --git a/templates/functions/value.md.tmpl b/templates/functions/value.md.tmpl new file mode 100644 index 0000000..6fa33ed --- /dev/null +++ b/templates/functions/value.md.tmpl @@ -0,0 +1,35 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "Map Functions" +description: |- +{{ .Summary | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Type}}: {{.Name}} + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Terraform Test Example + +{{tffile .ExampleFile }} +{{- end }} + +## Variable Validation Example + +{{ tffile (printf "examples/functions/%s/variable.tf" .Name)}} + +## Signature + +{{ .FunctionSignatureMarkdown }} + +## Arguments + +{{ .FunctionArgumentsMarkdown }} +{{ if .HasVariadic -}} +{{ .FunctionVariadicArgumentMarkdown }} +{{- end }} + +## Return Type + +The return type of `{{.Name}}` is a boolean.