MakeStaticTimestampFunc now returns unknown value if the given time.Time is zero (#2413)

Signed-off-by: yottta <andpectech@gmail.com>
Co-authored-by: yottta <andpectech@gmail.com>
This commit is contained in:
Andrei Ciobanu 2025-01-27 17:07:40 +02:00 committed by GitHub
parent f5eac16b00
commit 8fe739dc83
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 82 additions and 0 deletions

View File

@ -22,6 +22,7 @@ BUG FIXES:
- When assigning an empty map to a variable that is declared as a map of an object type with at least one optional attribute, OpenTofu will no longer create a subtly-broken value. ([#2371](https://github.com/opentofu/opentofu/pull/2371)) - When assigning an empty map to a variable that is declared as a map of an object type with at least one optional attribute, OpenTofu will no longer create a subtly-broken value. ([#2371](https://github.com/opentofu/opentofu/pull/2371))
- The `format` and `formatlist` functions can now accept `null` as one of the arguments without causing problems during the apply phase. Previously these functions would incorrectly return an unknown value when given `null` and so could cause a failure during the apply phase where no unknown values are allowed. ([#2371](https://github.com/opentofu/opentofu/pull/2371)) - The `format` and `formatlist` functions can now accept `null` as one of the arguments without causing problems during the apply phase. Previously these functions would incorrectly return an unknown value when given `null` and so could cause a failure during the apply phase where no unknown values are allowed. ([#2371](https://github.com/opentofu/opentofu/pull/2371))
- Provider used in import is correctly identified. ([#2336](https://github.com/opentofu/opentofu/pull/2336)) - Provider used in import is correctly identified. ([#2336](https://github.com/opentofu/opentofu/pull/2336))
- `plantimestamp()` now returns unknown value during validation ([#2397](https://github.com/opentofu/opentofu/issues/2397))
## Previous Releases ## Previous Releases

View File

@ -30,6 +30,11 @@ func MakeStaticTimestampFunc(static time.Time) function.Function {
Params: []function.Parameter{}, Params: []function.Parameter{},
Type: function.StaticReturnType(cty.String), Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
// During validation phase, the planTimestamp is zero. By returning unknown value, it's forcing the
// HCL parser to skip any evaluation of other expressions that could use this.
if static.IsZero() {
return cty.UnknownVal(cty.String), nil
}
return cty.StringVal(static.Format(time.RFC3339)), nil return cty.StringVal(static.Format(time.RFC3339)), nil
}, },
}) })

View File

@ -185,3 +185,49 @@ func TestTimeCmp(t *testing.T) {
}) })
} }
} }
func TestMakeStaticTimestampFunc(t *testing.T) {
tests := []struct {
Name string
// Setup made like this to bind the generated time value to the wanted value.
Setup func() (time.Time, cty.Value)
}{
{
Name: "zero",
Setup: func() (time.Time, cty.Value) {
in := time.Time{}
out := cty.UnknownVal(cty.String)
return in, out
},
},
{
Name: "now",
Setup: func() (time.Time, cty.Value) {
in := time.Now()
out := cty.StringVal(in.Format(time.RFC3339))
return in, out
},
},
{
Name: "one year later",
Setup: func() (time.Time, cty.Value) {
in := time.Now().Add(8766 * time.Hour) // 1 year later
out := cty.StringVal(in.Format(time.RFC3339))
return in, out
},
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("MakeStaticTimestampFunc(%s)", test.Name), func(t *testing.T) {
in, want := test.Setup()
got, err := MakeStaticTimestampFunc(in).Call(nil)
if err != nil {
t.Fatalf("MakeStaticTimestampFunc is not meant to return error but got one: %v", err)
}
if !got.RawEquals(want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want)
}
})
}
}

View File

@ -2488,3 +2488,21 @@ locals {
t.Fatalf("expected deprecated warning, got: %q\n", warn) t.Fatalf("expected deprecated warning, got: %q\n", warn)
} }
} }
// Ensure that the plantimestamp() call is not affecting the validation step.
func TestContext2Validate_rangeOverZeroPlanTimestamp(t *testing.T) {
p := testProvider("test")
m := testModule(t, "plan_range_over_plan_timestamp")
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
diags := ctx.Validate(context.Background(), m)
if diags.HasErrors() {
t.Fatal(diags.ErrWithWarnings())
}
}

View File

@ -0,0 +1,12 @@
locals {
first_year = 2024
current_timestamp = plantimestamp()
}
output "table_years" {
value = toset(
[
for year in range(local.first_year, tonumber(formatdate("YYYY", local.current_timestamp)) + 2) : tostring(year)
]
)
}