diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 46681b3..7f380f3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,6 +34,7 @@ jobs: terraform: - '1.8.*' - '1.9.*' + - '1.10.*' steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 @@ -80,6 +81,7 @@ jobs: terraform: - '1.8.*' - '1.9.*' + - '1.10.*' steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 diff --git a/internal/provider/not_null_function.go b/internal/provider/not_null_function.go index 6a4b356..84240eb 100644 --- a/internal/provider/not_null_function.go +++ b/internal/provider/not_null_function.go @@ -30,7 +30,7 @@ func (r NotNullFunction) Definition(_ context.Context, _ function.DefinitionRequ Parameters: []function.Parameter{ function.DynamicParameter{ AllowNullValue: true, - AllowUnknownValues: true, + AllowUnknownValues: false, Description: "The argument to check", Name: "argument", }, @@ -47,5 +47,15 @@ func (r NotNullFunction) Run(ctx context.Context, req function.RunRequest, resp return } - resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, !argument.IsNull())) + if argument.IsNull() { + resp.Error = resp.Result.Set(ctx, false) + return + } + + if !argument.IsUnderlyingValueNull() { + resp.Error = resp.Result.Set(ctx, true) + return + } + + resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, false)) } diff --git a/internal/provider/not_null_function_test.go b/internal/provider/not_null_function_test.go index 698de0e..65488f5 100644 --- a/internal/provider/not_null_function_test.go +++ b/internal/provider/not_null_function_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/hashicorp/go-version" + "github.com/hashicorp/terraform-plugin-testing/config" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/tfversion" ) @@ -220,6 +221,43 @@ output "test" { }) } +func TestNotNullFunction_compoundValidation(t *testing.T) { + t.Parallel() + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(version.Must(version.NewVersion("1.2.0"))), + }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +variable "example_value_a" { + default = null + description = "Example input A for validation." + type = string +} +variable "example_value_b" { + default = null + description = "Example input B for validation." + type = string + validation { + condition = anytrue([ + provider::assert::not_null(var.example_value_a), + provider::assert::not_null(var.example_value_b) + ]) + error_message = "At least one of example_value_a or example_value_b must be provided." + } +} + `, + ConfigVariables: config.Variables{ + "example_value_b": config.StringVariable("example-format-value"), + }, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + }, + }) +} + func TestNotNullFunction_falseCases(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ @@ -227,6 +265,12 @@ func TestNotNullFunction_falseCases(t *testing.T) { tfversion.SkipBelow(version.Must(version.NewVersion(MinimalRequiredTerraformVersion))), }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ExternalProviders: map[string]resource.ExternalProvider{ + "wireguard": { + Source: "OJFord/wireguard", + VersionConstraint: "0.3.1", + }, + }, Steps: []resource.TestStep{ { Config: ` @@ -235,6 +279,23 @@ locals { } output "test" { value = provider::assert::not_null(local.object) +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "false"), + ), + }, + { + Config: ` +resource "wireguard_asymmetric_key" "main" {} + +data "wireguard_config_document" "main" { + private_key = wireguard_asymmetric_key.main.private_key +} + +output "test" { + // .addresses is always null in this configuration + value = provider::assert::not_null(data.wireguard_config_document.main.addresses) } `, Check: resource.ComposeAggregateTestCheckFunc( diff --git a/internal/provider/null_function.go b/internal/provider/null_function.go index 3b4622e..7be6c30 100644 --- a/internal/provider/null_function.go +++ b/internal/provider/null_function.go @@ -30,7 +30,7 @@ func (r IsNullFunction) Definition(_ context.Context, _ function.DefinitionReque Parameters: []function.Parameter{ function.DynamicParameter{ AllowNullValue: true, - AllowUnknownValues: true, + AllowUnknownValues: false, Description: "The argument to check", Name: "argument", }, @@ -47,10 +47,13 @@ func (r IsNullFunction) Run(ctx context.Context, req function.RunRequest, resp * return } - if argument.UnderlyingValue() == nil { - if err := resp.Result.Set(ctx, true); err == nil { - return - } + if argument.IsNull() { + resp.Error = resp.Result.Set(ctx, true) + return + } + + if argument.IsUnderlyingValueNull() { + resp.Error = resp.Result.Set(ctx, true) return } diff --git a/internal/provider/null_function_test.go b/internal/provider/null_function_test.go index 8704d12..bc35576 100644 --- a/internal/provider/null_function_test.go +++ b/internal/provider/null_function_test.go @@ -7,11 +7,12 @@ import ( "testing" "github.com/hashicorp/go-version" + "github.com/hashicorp/terraform-plugin-testing/config" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/tfversion" ) -func TestIsNullFunction(t *testing.T) { +func TestNullFunction(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ @@ -36,7 +37,81 @@ output "test" { }) } -func TestIsNullFunction_falseCases(t *testing.T) { +func TestNullFunction_crossObjectValidation(t *testing.T) { + t.Parallel() + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(version.Must(version.NewVersion(MinimalRequiredTerraformVersion))), + }, + ExternalProviders: map[string]resource.ExternalProvider{ + "wireguard": { + Source: "OJFord/wireguard", + VersionConstraint: "0.3.1", + }, + }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +resource "wireguard_asymmetric_key" "main" {} + +data "wireguard_config_document" "main" { + private_key = wireguard_asymmetric_key.main.private_key +} + +output "test" { + // .addresses is always null in this configuration + value = provider::assert::null(data.wireguard_config_document.main.addresses) +} + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("test", "true"), + ), + }, + }, + }) +} + +func TestNullFunction_compoundValidation(t *testing.T) { + t.Parallel() + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(version.Must(version.NewVersion("1.2.0"))), + }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +variable "example_value_a" { + default = null + description = "Example option A for testing null validation." + type = string +} + +variable "example_value_b" { + default = null + description = "Example option B for testing null validation." + type = string + + validation { + condition = anytrue([ + !provider::assert::null(var.example_value_a), + !provider::assert::null(var.example_value_b) + ]) + error_message = "Exactly one of example_value_a or example_value_b must be provided." + } +} + `, + ConfigVariables: config.Variables{ + "example_value_b": config.StringVariable("example-value"), + }, + Check: resource.ComposeAggregateTestCheckFunc(), + }, + }, + }) +} + +func TestNullFunction_falseCases(t *testing.T) { t.Parallel() resource.UnitTest(t, resource.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{