From 1ad57baf3c2dc5c3550f1726ed4b3ae036a36c31 Mon Sep 17 00:00:00 2001 From: Mathijs van Mourick <14082160+mmourick@users.noreply.github.com> Date: Thu, 22 Aug 2024 22:37:02 +0200 Subject: [PATCH 01/11] feat: implement empty function --- internal/provider/empty_function.go | 56 ++++++++++++++ internal/provider/empty_function_test.go | 81 ++++++++++++++++++++ internal/provider/not_empty_function_test.go | 81 ++++++++++++++++++++ internal/provider/provider.go | 1 + 4 files changed, 219 insertions(+) create mode 100644 internal/provider/empty_function.go create mode 100644 internal/provider/empty_function_test.go create mode 100644 internal/provider/not_empty_function_test.go diff --git a/internal/provider/empty_function.go b/internal/provider/empty_function.go new file mode 100644 index 0000000..2b7ec3f --- /dev/null +++ b/internal/provider/empty_function.go @@ -0,0 +1,56 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/function" +) + +var ( + _ function.Function = EmptyFunction{} +) + +func NewEmptyFunction() function.Function { + return EmptyFunction{} +} + +type EmptyFunction struct{} + +func (r EmptyFunction) Metadata(_ context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { + resp.Name = "empty" +} + +func (r EmptyFunction) Definition(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + Summary: "Checks wether a given string is empty", + Parameters: []function.Parameter{ + function.StringParameter{ + AllowNullValue: true, + AllowUnknownValues: false, + Description: "The argument to check", + Name: "argument", + }, + }, + Return: function.BoolReturn{}, + } +} + +func (r EmptyFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var argument *string + + resp.Error = function.ConcatFuncErrors(req.Arguments.Get(ctx, &argument)) + if resp.Error != nil { + return + } + resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, isEmpty(argument))) +} + +func isEmpty(argument *string) bool { + if argument == nil { + return true + } + return len(*argument) == 0 +} diff --git a/internal/provider/empty_function_test.go b/internal/provider/empty_function_test.go new file mode 100644 index 0000000..5a1444d --- /dev/null +++ b/internal/provider/empty_function_test.go @@ -0,0 +1,81 @@ +// 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 TestEmptyFunction_stringEmpty(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: ` +output "test" { + value = provider::assert::empty("") +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "true"), + ), + }, + }, + }) +} + +func TestEmptyFunction_stringNotEmpty(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: ` +output "test" { + value = provider::assert::empty("hashicups") +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "false"), + ), + }, + }, + }) +} + +func TestEmptyFunction_nil(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 { + foo = null +} +output "test" { + value = provider::assert::empty(local.foo) +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "true"), + ), + }, + }, + }) +} diff --git a/internal/provider/not_empty_function_test.go b/internal/provider/not_empty_function_test.go new file mode 100644 index 0000000..544bcf3 --- /dev/null +++ b/internal/provider/not_empty_function_test.go @@ -0,0 +1,81 @@ +// 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 TestNotEmptyFunction_stringEmpty(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: ` +output "test" { + value = provider::assert::not_empty("") +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "false"), + ), + }, + }, + }) +} + +func TestNotEmptyFunction_stringNotEmpty(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: ` +output "test" { + value = provider::assert::not_empty("hashicups") +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "true"), + ), + }, + }, + }) +} + +func TestNotEmptyFunction_nil(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 { + foo = null +} +output "test" { + value = provider::assert::not_empty(local.foo) +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "false"), + ), + }, + }, + }) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 9f90801..005d8cc 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -82,6 +82,7 @@ func (p *AssertProvider) Functions(ctx context.Context) []func() function.Functi NewKeyFunction, NewValueFunction, NewExpiredFunction, + NewEmptyFunction, } } From 0b6ef347e581d3598ab2ab1e4fe9c4b38168113e Mon Sep 17 00:00:00 2001 From: Mathijs van Mourick <14082160+mmourick@users.noreply.github.com> Date: Thu, 22 Aug 2024 22:37:20 +0200 Subject: [PATCH 02/11] feat: implement `not_empty` function --- internal/provider/not_empty_function.go | 56 +++++++++++++++++++++++++ internal/provider/provider.go | 1 + 2 files changed, 57 insertions(+) create mode 100644 internal/provider/not_empty_function.go diff --git a/internal/provider/not_empty_function.go b/internal/provider/not_empty_function.go new file mode 100644 index 0000000..ba6e40f --- /dev/null +++ b/internal/provider/not_empty_function.go @@ -0,0 +1,56 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/function" +) + +var ( + _ function.Function = EmptyFunction{} +) + +func NewNotEmptyFunction() function.Function { + return NotEmptyFunction{} +} + +type NotEmptyFunction struct{} + +func (r NotEmptyFunction) Metadata(_ context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { + resp.Name = "not_empty" +} + +func (r NotEmptyFunction) Definition(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + Summary: "Checks wether a given string is not empty", + Parameters: []function.Parameter{ + function.StringParameter{ + AllowNullValue: true, + AllowUnknownValues: false, + Description: "The argument to check", + Name: "argument", + }, + }, + Return: function.BoolReturn{}, + } +} + +func (r NotEmptyFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var argument *string + + resp.Error = function.ConcatFuncErrors(req.Arguments.Get(ctx, &argument)) + if resp.Error != nil { + return + } + resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, isNotEmpty(argument))) +} + +func isNotEmpty(argument *string) bool { + if argument == nil { + return false + } + return len(*argument) > 0 +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 005d8cc..a44f94c 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -83,6 +83,7 @@ func (p *AssertProvider) Functions(ctx context.Context) []func() function.Functi NewValueFunction, NewExpiredFunction, NewEmptyFunction, + NewNotEmptyFunction, } } From 9490b7750041ff99165cb38c152d144c86da359e Mon Sep 17 00:00:00 2001 From: Mathijs van Mourick <14082160+mmourick@users.noreply.github.com> Date: Thu, 22 Aug 2024 23:41:49 +0200 Subject: [PATCH 03/11] docs: add templates and examples --- docs/functions/empty.md | 42 ++++++++++++++++++++++++ docs/functions/not_empty.md | 42 ++++++++++++++++++++++++ examples/functions/empty/variable.tf | 8 +++++ examples/functions/not_empty/variable.tf | 8 +++++ templates/functions/empty.md.tmpl | 35 ++++++++++++++++++++ templates/functions/not_empty.md.tmpl | 35 ++++++++++++++++++++ 6 files changed, 170 insertions(+) create mode 100644 docs/functions/empty.md create mode 100644 docs/functions/not_empty.md create mode 100644 examples/functions/empty/variable.tf create mode 100644 examples/functions/not_empty/variable.tf create mode 100644 templates/functions/empty.md.tmpl create mode 100644 templates/functions/not_empty.md.tmpl diff --git a/docs/functions/empty.md b/docs/functions/empty.md new file mode 100644 index 0000000..8257c1e --- /dev/null +++ b/docs/functions/empty.md @@ -0,0 +1,42 @@ +--- +page_title: "empty function - terraform-provider-assert" +subcategory: "String Functions" +description: |- + Checks wether a given string is empty +--- + +# function: empty + + + + + +## Variable Validation Example + +```terraform +variable "example" { + type = string + + validation { + condition = provider::assert::empty(var.example) + error_message = "Value must be empty" + } +} +``` + +## Signature + + +```text +empty(argument string) bool +``` + +## Arguments + + +1. `argument` (String, Nullable) The argument to check + + +## Return Type + +The return type of `empty` is a boolean. diff --git a/docs/functions/not_empty.md b/docs/functions/not_empty.md new file mode 100644 index 0000000..86238fd --- /dev/null +++ b/docs/functions/not_empty.md @@ -0,0 +1,42 @@ +--- +page_title: "not_empty function - terraform-provider-assert" +subcategory: "String Functions" +description: |- + Checks wether a given string is not empty +--- + +# function: not_empty + + + + + +## Variable Validation Example + +```terraform +variable "example" { + type = string + + validation { + condition = provider::assert::not_empty(var.example) + error_message = "Value must not be empty" + } +} +``` + +## Signature + + +```text +not_empty(argument string) bool +``` + +## Arguments + + +1. `argument` (String, Nullable) The argument to check + + +## Return Type + +The return type of `not_empty` is a boolean. diff --git a/examples/functions/empty/variable.tf b/examples/functions/empty/variable.tf new file mode 100644 index 0000000..6459b7b --- /dev/null +++ b/examples/functions/empty/variable.tf @@ -0,0 +1,8 @@ +variable "example" { + type = string + + validation { + condition = provider::assert::empty(var.example) + error_message = "Value must be empty" + } +} diff --git a/examples/functions/not_empty/variable.tf b/examples/functions/not_empty/variable.tf new file mode 100644 index 0000000..4e2d590 --- /dev/null +++ b/examples/functions/not_empty/variable.tf @@ -0,0 +1,8 @@ +variable "example" { + type = string + + validation { + condition = provider::assert::not_empty(var.example) + error_message = "Value must not be empty" + } +} diff --git a/templates/functions/empty.md.tmpl b/templates/functions/empty.md.tmpl new file mode 100644 index 0000000..5649efe --- /dev/null +++ b/templates/functions/empty.md.tmpl @@ -0,0 +1,35 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "String 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. diff --git a/templates/functions/not_empty.md.tmpl b/templates/functions/not_empty.md.tmpl new file mode 100644 index 0000000..5649efe --- /dev/null +++ b/templates/functions/not_empty.md.tmpl @@ -0,0 +1,35 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "String 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. From 6de2dd9703a8ebf237afb231e4cf334490c2a835 Mon Sep 17 00:00:00 2001 From: Mathijs van Mourick <14082160+mmourick@users.noreply.github.com> Date: Tue, 27 Aug 2024 22:13:14 +0200 Subject: [PATCH 04/11] chore: update string --- internal/provider/empty_function_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/empty_function_test.go b/internal/provider/empty_function_test.go index 5a1444d..1d6a036 100644 --- a/internal/provider/empty_function_test.go +++ b/internal/provider/empty_function_test.go @@ -44,7 +44,7 @@ func TestEmptyFunction_stringNotEmpty(t *testing.T) { { Config: ` output "test" { - value = provider::assert::empty("hashicups") + value = provider::assert::empty("notempty") } `, Check: resource.ComposeAggregateTestCheckFunc( From be0c85cee0bfd20596aec5800957de9afd672afa Mon Sep 17 00:00:00 2001 From: Mathijs van Mourick <14082160+mmourick@users.noreply.github.com> Date: Tue, 27 Aug 2024 22:27:53 +0200 Subject: [PATCH 05/11] chore: don't allow null values in `empty` func --- docs/functions/empty.md | 4 ++-- internal/provider/empty_function.go | 15 ++++++-------- internal/provider/empty_function_test.go | 25 ------------------------ 3 files changed, 8 insertions(+), 36 deletions(-) diff --git a/docs/functions/empty.md b/docs/functions/empty.md index 8257c1e..d038564 100644 --- a/docs/functions/empty.md +++ b/docs/functions/empty.md @@ -28,13 +28,13 @@ variable "example" { ```text -empty(argument string) bool +empty(string string) bool ``` ## Arguments -1. `argument` (String, Nullable) The argument to check +1. `string` (String) The string to check ## Return Type diff --git a/internal/provider/empty_function.go b/internal/provider/empty_function.go index 2b7ec3f..23a4c61 100644 --- a/internal/provider/empty_function.go +++ b/internal/provider/empty_function.go @@ -28,10 +28,10 @@ func (r EmptyFunction) Definition(_ context.Context, _ function.DefinitionReques Summary: "Checks wether a given string is empty", Parameters: []function.Parameter{ function.StringParameter{ - AllowNullValue: true, + AllowNullValue: false, AllowUnknownValues: false, - Description: "The argument to check", - Name: "argument", + Description: "The string to check", + Name: "string", }, }, Return: function.BoolReturn{}, @@ -39,7 +39,7 @@ func (r EmptyFunction) Definition(_ context.Context, _ function.DefinitionReques } func (r EmptyFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { - var argument *string + var argument string resp.Error = function.ConcatFuncErrors(req.Arguments.Get(ctx, &argument)) if resp.Error != nil { @@ -48,9 +48,6 @@ func (r EmptyFunction) Run(ctx context.Context, req function.RunRequest, resp *f resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, isEmpty(argument))) } -func isEmpty(argument *string) bool { - if argument == nil { - return true - } - return len(*argument) == 0 +func isEmpty(argument string) bool { + return len(argument) == 0 } diff --git a/internal/provider/empty_function_test.go b/internal/provider/empty_function_test.go index 1d6a036..eeeb407 100644 --- a/internal/provider/empty_function_test.go +++ b/internal/provider/empty_function_test.go @@ -54,28 +54,3 @@ output "test" { }, }) } - -func TestEmptyFunction_nil(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 { - foo = null -} -output "test" { - value = provider::assert::empty(local.foo) -} - `, - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckOutput("test", "true"), - ), - }, - }, - }) -} From e9afaa3815bb2916bbb595211653f4e8dbd21990 Mon Sep 17 00:00:00 2001 From: Mathijs van Mourick <14082160+mmourick@users.noreply.github.com> Date: Tue, 27 Aug 2024 22:33:12 +0200 Subject: [PATCH 06/11] chore: don't allow null values in `not_empty` func --- docs/functions/not_empty.md | 4 +-- internal/provider/not_empty_function.go | 15 +++++------ internal/provider/not_empty_function_test.go | 27 +------------------- 3 files changed, 9 insertions(+), 37 deletions(-) diff --git a/docs/functions/not_empty.md b/docs/functions/not_empty.md index 86238fd..e87f798 100644 --- a/docs/functions/not_empty.md +++ b/docs/functions/not_empty.md @@ -28,13 +28,13 @@ variable "example" { ```text -not_empty(argument string) bool +not_empty(string string) bool ``` ## Arguments -1. `argument` (String, Nullable) The argument to check +1. `string` (String) The string to check ## Return Type diff --git a/internal/provider/not_empty_function.go b/internal/provider/not_empty_function.go index ba6e40f..5e6ad1a 100644 --- a/internal/provider/not_empty_function.go +++ b/internal/provider/not_empty_function.go @@ -28,10 +28,10 @@ func (r NotEmptyFunction) Definition(_ context.Context, _ function.DefinitionReq Summary: "Checks wether a given string is not empty", Parameters: []function.Parameter{ function.StringParameter{ - AllowNullValue: true, + AllowNullValue: false, AllowUnknownValues: false, - Description: "The argument to check", - Name: "argument", + Description: "The string to check", + Name: "string", }, }, Return: function.BoolReturn{}, @@ -39,7 +39,7 @@ func (r NotEmptyFunction) Definition(_ context.Context, _ function.DefinitionReq } func (r NotEmptyFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { - var argument *string + var argument string resp.Error = function.ConcatFuncErrors(req.Arguments.Get(ctx, &argument)) if resp.Error != nil { @@ -48,9 +48,6 @@ func (r NotEmptyFunction) Run(ctx context.Context, req function.RunRequest, resp resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, isNotEmpty(argument))) } -func isNotEmpty(argument *string) bool { - if argument == nil { - return false - } - return len(*argument) > 0 +func isNotEmpty(argument string) bool { + return len(argument) > 0 } diff --git a/internal/provider/not_empty_function_test.go b/internal/provider/not_empty_function_test.go index 544bcf3..7fb50f0 100644 --- a/internal/provider/not_empty_function_test.go +++ b/internal/provider/not_empty_function_test.go @@ -44,7 +44,7 @@ func TestNotEmptyFunction_stringNotEmpty(t *testing.T) { { Config: ` output "test" { - value = provider::assert::not_empty("hashicups") + value = provider::assert::not_empty("notempty") } `, Check: resource.ComposeAggregateTestCheckFunc( @@ -54,28 +54,3 @@ output "test" { }, }) } - -func TestNotEmptyFunction_nil(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 { - foo = null -} -output "test" { - value = provider::assert::not_empty(local.foo) -} - `, - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckOutput("test", "false"), - ), - }, - }, - }) -} From f2c414c12c7484450b2ec1b78d0c6a4046e8c356 Mon Sep 17 00:00:00 2001 From: Mathijs van Mourick <14082160+mmourick@users.noreply.github.com> Date: Tue, 27 Aug 2024 23:11:23 +0200 Subject: [PATCH 07/11] docs: add terraform test example for `not_empty` --- docs/functions/not_empty.md | 18 +++++++++++++++--- examples/functions/not_empty/function.tf | 9 +++++++++ examples/functions/not_empty/variable.tf | 6 +++--- 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 examples/functions/not_empty/function.tf diff --git a/docs/functions/not_empty.md b/docs/functions/not_empty.md index e87f798..48f9bc6 100644 --- a/docs/functions/not_empty.md +++ b/docs/functions/not_empty.md @@ -9,17 +9,29 @@ description: |- +## Terraform Test Example +```terraform +run "check_security_group_description" { + + command = plan + + assert { + condition = provider::assert::not_empty(aws_security_group.example.description) + error_message = "Description can not be empty" + } +} +``` ## Variable Validation Example ```terraform -variable "example" { +variable "bucket_name" { type = string validation { - condition = provider::assert::not_empty(var.example) - error_message = "Value must not be empty" + condition = provider::assert::not_empty(var.bucket_name) + error_message = "Bucket name must not be empty" } } ``` diff --git a/examples/functions/not_empty/function.tf b/examples/functions/not_empty/function.tf new file mode 100644 index 0000000..35559ad --- /dev/null +++ b/examples/functions/not_empty/function.tf @@ -0,0 +1,9 @@ +run "check_security_group_description" { + + command = plan + + assert { + condition = provider::assert::not_empty(aws_security_group.example.description) + error_message = "Description can not be empty" + } +} diff --git a/examples/functions/not_empty/variable.tf b/examples/functions/not_empty/variable.tf index 4e2d590..b4e2fab 100644 --- a/examples/functions/not_empty/variable.tf +++ b/examples/functions/not_empty/variable.tf @@ -1,8 +1,8 @@ -variable "example" { +variable "bucket_name" { type = string validation { - condition = provider::assert::not_empty(var.example) - error_message = "Value must not be empty" + condition = provider::assert::not_empty(var.bucket_name) + error_message = "Bucket name must not be empty" } } From a94d7ddd20394fb6db20724976ee5917da12a0aa Mon Sep 17 00:00:00 2001 From: Mathijs van Mourick <14082160+mmourick@users.noreply.github.com> Date: Tue, 27 Aug 2024 23:22:01 +0200 Subject: [PATCH 08/11] docs: add terraform test example to `empty` --- docs/functions/empty.md | 12 ++++++++++++ examples/functions/empty/function.tf | 9 +++++++++ 2 files changed, 21 insertions(+) create mode 100644 examples/functions/empty/function.tf diff --git a/docs/functions/empty.md b/docs/functions/empty.md index d038564..2dde055 100644 --- a/docs/functions/empty.md +++ b/docs/functions/empty.md @@ -9,7 +9,19 @@ description: |- +## Terraform Test Example +```terraform +run "check_cloudwatch_log_subscription_match_all" { + + command = plan + + assert { + condition = provider::assert::empty(aws_cloudwatch_log_subscription_filter.example.filter_pattern) + error_message = "Filter pattern must be empty to match all log events" + } +} +``` ## Variable Validation Example diff --git a/examples/functions/empty/function.tf b/examples/functions/empty/function.tf new file mode 100644 index 0000000..8d56544 --- /dev/null +++ b/examples/functions/empty/function.tf @@ -0,0 +1,9 @@ +run "check_cloudwatch_log_subscription_match_all" { + + command = plan + + assert { + condition = provider::assert::empty(aws_cloudwatch_log_subscription_filter.example.filter_pattern) + error_message = "Filter pattern must be empty to match all log events" + } +} From 4896b5aa9523025b5fcf8df5a09c18e639b643f9 Mon Sep 17 00:00:00 2001 From: Mathijs van Mourick <14082160+mmourick@users.noreply.github.com> Date: Wed, 28 Aug 2024 00:07:38 +0200 Subject: [PATCH 09/11] test: add tests for null values --- internal/provider/empty_function_test.go | 25 ++++++++++++++++++++ internal/provider/not_empty_function_test.go | 25 ++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/internal/provider/empty_function_test.go b/internal/provider/empty_function_test.go index eeeb407..8b2d38f 100644 --- a/internal/provider/empty_function_test.go +++ b/internal/provider/empty_function_test.go @@ -4,6 +4,7 @@ package provider import ( + "regexp" "testing" "github.com/hashicorp/go-version" @@ -54,3 +55,27 @@ output "test" { }, }) } + +func TestEmptyFunction_null(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 { + example = null +} + +output "test" { + value = provider::assert::empty(local.example) +} + `, + ExpectError: regexp.MustCompile(`Invalid value for "string" parameter: argument must not be null`), + }, + }, + }) +} diff --git a/internal/provider/not_empty_function_test.go b/internal/provider/not_empty_function_test.go index 7fb50f0..726edd2 100644 --- a/internal/provider/not_empty_function_test.go +++ b/internal/provider/not_empty_function_test.go @@ -4,6 +4,7 @@ package provider import ( + "regexp" "testing" "github.com/hashicorp/go-version" @@ -54,3 +55,27 @@ output "test" { }, }) } + +func TestNotEmptyFunction_null(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 { + example = null +} + +output "test" { + value = provider::assert::not_empty(local.example) +} + `, + ExpectError: regexp.MustCompile(`Invalid value for "string" parameter: argument must not be null`), + }, + }, + }) +} From c1a0dd88c9e6ddd79af287b257591715b007bb29 Mon Sep 17 00:00:00 2001 From: Bruno Schaatsbergen Date: Thu, 29 Aug 2024 11:33:12 +0200 Subject: [PATCH 10/11] chore: improve naming in examples and expand tests Signed-off-by: Bruno Schaatsbergen --- docs/functions/empty.md | 4 +- docs/functions/not_empty.md | 8 +- examples/functions/empty/function.tf | 2 +- examples/functions/empty/variable.tf | 2 +- examples/functions/not_empty/function.tf | 2 +- examples/functions/not_empty/variable.tf | 6 +- internal/provider/empty_function_test.go | 90 ++++++++ internal/provider/not_empty_function_test.go | 210 +++++++++++++++++++ 8 files changed, 312 insertions(+), 12 deletions(-) diff --git a/docs/functions/empty.md b/docs/functions/empty.md index 2dde055..5964059 100644 --- a/docs/functions/empty.md +++ b/docs/functions/empty.md @@ -18,7 +18,7 @@ run "check_cloudwatch_log_subscription_match_all" { assert { condition = provider::assert::empty(aws_cloudwatch_log_subscription_filter.example.filter_pattern) - error_message = "Filter pattern must be empty to match all log events" + error_message = "CloudWatch log subscription filter pattern must be empty, as it is a match all." } } ``` @@ -31,7 +31,7 @@ variable "example" { validation { condition = provider::assert::empty(var.example) - error_message = "Value must be empty" + error_message = "Variable 'example' must be empty." } } ``` diff --git a/docs/functions/not_empty.md b/docs/functions/not_empty.md index 48f9bc6..954d8ba 100644 --- a/docs/functions/not_empty.md +++ b/docs/functions/not_empty.md @@ -18,7 +18,7 @@ run "check_security_group_description" { assert { condition = provider::assert::not_empty(aws_security_group.example.description) - error_message = "Description can not be empty" + error_message = "Security group description must not be empty." } } ``` @@ -26,12 +26,12 @@ run "check_security_group_description" { ## Variable Validation Example ```terraform -variable "bucket_name" { +variable "example" { type = string validation { - condition = provider::assert::not_empty(var.bucket_name) - error_message = "Bucket name must not be empty" + condition = provider::assert::not_empty(var.example) + error_message = "Variable 'example' must not be empty." } } ``` diff --git a/examples/functions/empty/function.tf b/examples/functions/empty/function.tf index 8d56544..8b997fb 100644 --- a/examples/functions/empty/function.tf +++ b/examples/functions/empty/function.tf @@ -4,6 +4,6 @@ run "check_cloudwatch_log_subscription_match_all" { assert { condition = provider::assert::empty(aws_cloudwatch_log_subscription_filter.example.filter_pattern) - error_message = "Filter pattern must be empty to match all log events" + error_message = "CloudWatch log subscription filter pattern must be empty, as it is a match all." } } diff --git a/examples/functions/empty/variable.tf b/examples/functions/empty/variable.tf index 6459b7b..ebf458d 100644 --- a/examples/functions/empty/variable.tf +++ b/examples/functions/empty/variable.tf @@ -3,6 +3,6 @@ variable "example" { validation { condition = provider::assert::empty(var.example) - error_message = "Value must be empty" + error_message = "Variable 'example' must be empty." } } diff --git a/examples/functions/not_empty/function.tf b/examples/functions/not_empty/function.tf index 35559ad..95f904a 100644 --- a/examples/functions/not_empty/function.tf +++ b/examples/functions/not_empty/function.tf @@ -4,6 +4,6 @@ run "check_security_group_description" { assert { condition = provider::assert::not_empty(aws_security_group.example.description) - error_message = "Description can not be empty" + error_message = "Security group description must not be empty." } } diff --git a/examples/functions/not_empty/variable.tf b/examples/functions/not_empty/variable.tf index b4e2fab..e5b7f8a 100644 --- a/examples/functions/not_empty/variable.tf +++ b/examples/functions/not_empty/variable.tf @@ -1,8 +1,8 @@ -variable "bucket_name" { +variable "example" { type = string validation { - condition = provider::assert::not_empty(var.bucket_name) - error_message = "Bucket name must not be empty" + condition = provider::assert::not_empty(var.example) + error_message = "Variable 'example' must not be empty." } } diff --git a/internal/provider/empty_function_test.go b/internal/provider/empty_function_test.go index 8b2d38f..b1490f3 100644 --- a/internal/provider/empty_function_test.go +++ b/internal/provider/empty_function_test.go @@ -56,6 +56,28 @@ output "test" { }) } +func TestEmptyFunction_whitespaceString(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: ` +output "test" { + value = provider::assert::empty(" ") +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "false"), + ), + }, + }, + }) +} + func TestEmptyFunction_null(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ @@ -79,3 +101,71 @@ output "test" { }, }) } + +func TestEmptyFunction_listError(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: ` +output "test" { + value = provider::assert::empty([]) +} + `, + ExpectError: regexp.MustCompile(`Invalid value for "string" parameter: string required`), + }, + }, + }) +} + +func TestEmptyFunction_trimmedWhitespaceTrue(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 { + example = "" +} + +output "test" { + value = provider::assert::empty(trimspace(local.example)) +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "true"), + ), + }, + }, + }) +} + +func TestEmptyFunction_specialCharacters(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: ` +output "test" { + value = provider::assert::empty("!@#$%^&*()") +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "false"), + ), + }, + }, + }) +} diff --git a/internal/provider/not_empty_function_test.go b/internal/provider/not_empty_function_test.go index 726edd2..2f00a55 100644 --- a/internal/provider/not_empty_function_test.go +++ b/internal/provider/not_empty_function_test.go @@ -56,6 +56,102 @@ output "test" { }) } +func TestNotEmptyFunction_whitespaceString(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: ` +output "test" { + value = provider::assert::not_empty(" ") +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "true"), + ), + }, + }, + }) +} + +func TestNotEmptyFunction_integerConversion(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 { + example = 123 +} + +output "test" { + value = provider::assert::not_empty(tostring(local.example)) +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "true"), + ), + }, + }, + }) +} + +func TestNotEmptyFunction_nonEmptyList(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 { + example = ["item"] +} + +output "test" { + value = provider::assert::not_empty(local.example[0]) +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "true"), + ), + }, + }, + }) +} + +func TestNotEmptyFunction_specialCharacters(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: ` +output "test" { + value = provider::assert::not_empty("!@#$%^&*()") +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "true"), + ), + }, + }, + }) +} + func TestNotEmptyFunction_null(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ @@ -79,3 +175,117 @@ output "test" { }, }) } + +func TestNotEmptyFunction_listError(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: ` +output "test" { + value = provider::assert::not_empty([]) +} + `, + ExpectError: regexp.MustCompile(`Invalid value for "string" parameter: string required`), + }, + }, + }) +} + +func TestNotEmptyFunction_trimmedWhitespaceFalse(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 { + example = "" +} + +output "test" { + value = provider::assert::not_empty(trimspace(local.example)) +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "false"), + ), + }, + }, + }) +} + +func TestNotEmptyFunction_knownAfterApplyValue(t *testing.T) { + t.Parallel() + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(version.Must(version.NewVersion(MinimalRequiredTerraformVersion))), + }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + Source: "registry.terraform.io/hashicorp/random", + }, + }, + Steps: []resource.TestStep{ + { + Config: ` +resource "random_integer" "test" { + min = 1 + max = 50000 +} + +output "test" { + value = provider::assert::not_empty(random_integer.test.result) +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "true"), + ), + }, + }, + }) +} + +func TestNotEmptyFunction_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: ` +output "test" { + value = provider::assert::not_empty("") +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "false"), + ), + }, + { + Config: ` +locals { + example = "" +} + +output "test" { + value = provider::assert::not_empty(local.example) +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "false"), + ), + }, + }, + }) +} From 674110d7ab9a1e191411912fddbd3b99ca594859 Mon Sep 17 00:00:00 2001 From: Bruno Schaatsbergen Date: Thu, 29 Aug 2024 11:44:29 +0200 Subject: [PATCH 11/11] chore: improve param name Signed-off-by: Bruno Schaatsbergen --- docs/functions/empty.md | 4 ++-- docs/functions/not_empty.md | 4 ++-- internal/provider/empty_function.go | 2 +- internal/provider/not_empty_function.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/functions/empty.md b/docs/functions/empty.md index 5964059..a68be3a 100644 --- a/docs/functions/empty.md +++ b/docs/functions/empty.md @@ -40,13 +40,13 @@ variable "example" { ```text -empty(string string) bool +empty(s string) bool ``` ## Arguments -1. `string` (String) The string to check +1. `s` (String) The string to check ## Return Type diff --git a/docs/functions/not_empty.md b/docs/functions/not_empty.md index 954d8ba..41ab079 100644 --- a/docs/functions/not_empty.md +++ b/docs/functions/not_empty.md @@ -40,13 +40,13 @@ variable "example" { ```text -not_empty(string string) bool +not_empty(s string) bool ``` ## Arguments -1. `string` (String) The string to check +1. `s` (String) The string to check ## Return Type diff --git a/internal/provider/empty_function.go b/internal/provider/empty_function.go index 23a4c61..636ec98 100644 --- a/internal/provider/empty_function.go +++ b/internal/provider/empty_function.go @@ -31,7 +31,7 @@ func (r EmptyFunction) Definition(_ context.Context, _ function.DefinitionReques AllowNullValue: false, AllowUnknownValues: false, Description: "The string to check", - Name: "string", + Name: "s", }, }, Return: function.BoolReturn{}, diff --git a/internal/provider/not_empty_function.go b/internal/provider/not_empty_function.go index 5e6ad1a..f61cfd0 100644 --- a/internal/provider/not_empty_function.go +++ b/internal/provider/not_empty_function.go @@ -31,7 +31,7 @@ func (r NotEmptyFunction) Definition(_ context.Context, _ function.DefinitionReq AllowNullValue: false, AllowUnknownValues: false, Description: "The string to check", - Name: "string", + Name: "s", }, }, Return: function.BoolReturn{},