@@ -2095,3 +2095,293 @@ func TestContext2Validate_nonNullableVariableDefaultValidation(t *testing.T) {
20952095 t .Fatal (diags .ErrWithWarnings ())
20962096 }
20972097}
2098+
2099+ func TestContext2Validate_precondition_good (t * testing.T ) {
2100+ p := testProvider ("aws" )
2101+ p .GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema (& ProviderSchema {
2102+ ResourceTypes : map [string ]* configschema.Block {
2103+ "aws_instance" : {
2104+ Attributes : map [string ]* configschema.Attribute {
2105+ "foo" : {Type : cty .String , Optional : true },
2106+ },
2107+ },
2108+ },
2109+ })
2110+ m := testModuleInline (t , map [string ]string {
2111+ "main.tf" : `
2112+ terraform {
2113+ experiments = [preconditions_postconditions]
2114+ }
2115+
2116+ variable "input" {
2117+ type = string
2118+ default = "foo"
2119+ }
2120+
2121+ resource "aws_instance" "test" {
2122+ foo = var.input
2123+
2124+ lifecycle {
2125+ precondition {
2126+ condition = length(var.input) > 0
2127+ error_message = "Input cannot be empty."
2128+ }
2129+ }
2130+ }
2131+ ` ,
2132+ })
2133+
2134+ ctx := testContext2 (t , & ContextOpts {
2135+ Providers : map [addrs.Provider ]providers.Factory {
2136+ addrs .NewDefaultProvider ("aws" ): testProviderFuncFixed (p ),
2137+ },
2138+ })
2139+
2140+ diags := ctx .Validate (m )
2141+ if diags .HasErrors () {
2142+ t .Fatal (diags .ErrWithWarnings ())
2143+ }
2144+ }
2145+
2146+ func TestContext2Validate_precondition_badCondition (t * testing.T ) {
2147+ p := testProvider ("aws" )
2148+ p .GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema (& ProviderSchema {
2149+ ResourceTypes : map [string ]* configschema.Block {
2150+ "aws_instance" : {
2151+ Attributes : map [string ]* configschema.Attribute {
2152+ "foo" : {Type : cty .String , Optional : true },
2153+ },
2154+ },
2155+ },
2156+ })
2157+ m := testModuleInline (t , map [string ]string {
2158+ "main.tf" : `
2159+ terraform {
2160+ experiments = [preconditions_postconditions]
2161+ }
2162+
2163+ variable "input" {
2164+ type = string
2165+ default = "foo"
2166+ }
2167+
2168+ resource "aws_instance" "test" {
2169+ foo = var.input
2170+
2171+ lifecycle {
2172+ precondition {
2173+ condition = length(one(var.input)) == 1
2174+ error_message = "You can't do that."
2175+ }
2176+ }
2177+ }
2178+ ` ,
2179+ })
2180+
2181+ ctx := testContext2 (t , & ContextOpts {
2182+ Providers : map [addrs.Provider ]providers.Factory {
2183+ addrs .NewDefaultProvider ("aws" ): testProviderFuncFixed (p ),
2184+ },
2185+ })
2186+
2187+ diags := ctx .Validate (m )
2188+ if ! diags .HasErrors () {
2189+ t .Fatalf ("succeeded; want error" )
2190+ }
2191+ if got , want := diags .Err ().Error (), "Invalid function argument" ; ! strings .Contains (got , want ) {
2192+ t .Errorf ("unexpected error.\n got: %s\n should contain: %q" , got , want )
2193+ }
2194+ }
2195+
2196+ func TestContext2Validate_precondition_badErrorMessage (t * testing.T ) {
2197+ p := testProvider ("aws" )
2198+ p .GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema (& ProviderSchema {
2199+ ResourceTypes : map [string ]* configschema.Block {
2200+ "aws_instance" : {
2201+ Attributes : map [string ]* configschema.Attribute {
2202+ "foo" : {Type : cty .String , Optional : true },
2203+ },
2204+ },
2205+ },
2206+ })
2207+ m := testModuleInline (t , map [string ]string {
2208+ "main.tf" : `
2209+ terraform {
2210+ experiments = [preconditions_postconditions]
2211+ }
2212+
2213+ variable "input" {
2214+ type = string
2215+ default = "foo"
2216+ }
2217+
2218+ resource "aws_instance" "test" {
2219+ foo = var.input
2220+
2221+ lifecycle {
2222+ precondition {
2223+ condition = var.input != "foo"
2224+ error_message = "This is a bad use of a function: ${one(var.input)}."
2225+ }
2226+ }
2227+ }
2228+ ` ,
2229+ })
2230+
2231+ ctx := testContext2 (t , & ContextOpts {
2232+ Providers : map [addrs.Provider ]providers.Factory {
2233+ addrs .NewDefaultProvider ("aws" ): testProviderFuncFixed (p ),
2234+ },
2235+ })
2236+
2237+ diags := ctx .Validate (m )
2238+ if ! diags .HasErrors () {
2239+ t .Fatalf ("succeeded; want error" )
2240+ }
2241+ if got , want := diags .Err ().Error (), "Invalid function argument" ; ! strings .Contains (got , want ) {
2242+ t .Errorf ("unexpected error.\n got: %s\n should contain: %q" , got , want )
2243+ }
2244+ }
2245+
2246+ func TestContext2Validate_postcondition_good (t * testing.T ) {
2247+ p := testProvider ("aws" )
2248+ p .GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema (& ProviderSchema {
2249+ ResourceTypes : map [string ]* configschema.Block {
2250+ "aws_instance" : {
2251+ Attributes : map [string ]* configschema.Attribute {
2252+ "foo" : {Type : cty .String , Optional : true },
2253+ },
2254+ },
2255+ },
2256+ })
2257+ m := testModuleInline (t , map [string ]string {
2258+ "main.tf" : `
2259+ terraform {
2260+ experiments = [preconditions_postconditions]
2261+ }
2262+
2263+ resource "aws_instance" "test" {
2264+ foo = "foo"
2265+
2266+ lifecycle {
2267+ postcondition {
2268+ condition = length(self.foo) > 0
2269+ error_message = "Input cannot be empty."
2270+ }
2271+ }
2272+ }
2273+ ` ,
2274+ })
2275+
2276+ ctx := testContext2 (t , & ContextOpts {
2277+ Providers : map [addrs.Provider ]providers.Factory {
2278+ addrs .NewDefaultProvider ("aws" ): testProviderFuncFixed (p ),
2279+ },
2280+ })
2281+
2282+ diags := ctx .Validate (m )
2283+ if diags .HasErrors () {
2284+ t .Fatal (diags .ErrWithWarnings ())
2285+ }
2286+ }
2287+
2288+ func TestContext2Validate_postcondition_badCondition (t * testing.T ) {
2289+ p := testProvider ("aws" )
2290+ p .GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema (& ProviderSchema {
2291+ ResourceTypes : map [string ]* configschema.Block {
2292+ "aws_instance" : {
2293+ Attributes : map [string ]* configschema.Attribute {
2294+ "foo" : {Type : cty .String , Optional : true },
2295+ },
2296+ },
2297+ },
2298+ })
2299+ // This postcondition's condition expression does not refer to self, which
2300+ // is unrealistic. This is because at the time of writing the test, self is
2301+ // always an unknown value of dynamic type during validation. As a result,
2302+ // validation of conditions which refer to resource arguments is not
2303+ // possible until plan time. For now we exercise the code by referring to
2304+ // an input variable.
2305+ m := testModuleInline (t , map [string ]string {
2306+ "main.tf" : `
2307+ terraform {
2308+ experiments = [preconditions_postconditions]
2309+ }
2310+
2311+ variable "input" {
2312+ type = string
2313+ default = "foo"
2314+ }
2315+
2316+ resource "aws_instance" "test" {
2317+ foo = var.input
2318+
2319+ lifecycle {
2320+ postcondition {
2321+ condition = length(one(var.input)) == 1
2322+ error_message = "You can't do that."
2323+ }
2324+ }
2325+ }
2326+ ` ,
2327+ })
2328+
2329+ ctx := testContext2 (t , & ContextOpts {
2330+ Providers : map [addrs.Provider ]providers.Factory {
2331+ addrs .NewDefaultProvider ("aws" ): testProviderFuncFixed (p ),
2332+ },
2333+ })
2334+
2335+ diags := ctx .Validate (m )
2336+ if ! diags .HasErrors () {
2337+ t .Fatalf ("succeeded; want error" )
2338+ }
2339+ if got , want := diags .Err ().Error (), "Invalid function argument" ; ! strings .Contains (got , want ) {
2340+ t .Errorf ("unexpected error.\n got: %s\n should contain: %q" , got , want )
2341+ }
2342+ }
2343+
2344+ func TestContext2Validate_postcondition_badErrorMessage (t * testing.T ) {
2345+ p := testProvider ("aws" )
2346+ p .GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema (& ProviderSchema {
2347+ ResourceTypes : map [string ]* configschema.Block {
2348+ "aws_instance" : {
2349+ Attributes : map [string ]* configschema.Attribute {
2350+ "foo" : {Type : cty .String , Optional : true },
2351+ },
2352+ },
2353+ },
2354+ })
2355+ m := testModuleInline (t , map [string ]string {
2356+ "main.tf" : `
2357+ terraform {
2358+ experiments = [preconditions_postconditions]
2359+ }
2360+
2361+ resource "aws_instance" "test" {
2362+ foo = "foo"
2363+
2364+ lifecycle {
2365+ postcondition {
2366+ condition = self.foo != "foo"
2367+ error_message = "This is a bad use of a function: ${one("foo")}."
2368+ }
2369+ }
2370+ }
2371+ ` ,
2372+ })
2373+
2374+ ctx := testContext2 (t , & ContextOpts {
2375+ Providers : map [addrs.Provider ]providers.Factory {
2376+ addrs .NewDefaultProvider ("aws" ): testProviderFuncFixed (p ),
2377+ },
2378+ })
2379+
2380+ diags := ctx .Validate (m )
2381+ if ! diags .HasErrors () {
2382+ t .Fatalf ("succeeded; want error" )
2383+ }
2384+ if got , want := diags .Err ().Error (), "Invalid function argument" ; ! strings .Contains (got , want ) {
2385+ t .Errorf ("unexpected error.\n got: %s\n should contain: %q" , got , want )
2386+ }
2387+ }
0 commit comments