@ -1269,22 +1269,75 @@ func (runner *TestFileRunner) prepareInputVariablesForAssertions(config *configs
func ( runner * TestFileRunner ) ctx ( run * moduletest . Run , file * moduletest . File , availableVariables terraform . InputValues ) ( * hcl . EvalContext , tfdiags . Diagnostics ) {
var diags tfdiags . Diagnostics
availableRunBlocks := make ( map [ string ] bool )
// First, let's build the set of available run blocks.
availableRunBlocks := make ( map [ string ] * terraform . TestContext )
runs := make ( map [ string ] cty . Value )
for _ , run := range file . Runs {
name := run . Name
if _ , exists := runner . PriorStates [ name ] ; exists {
attrs := make ( map [ string ] cty . Value )
if ctx , exists := runner . PriorStates [ name ] ; exists {
// We have executed this run block previously, therefore it is
// available as a reference at this point in time.
availableRunBlocks [ name ] = true
availableRunBlocks [ name ] = ctx
for name , config := range ctx . Config . Module . Outputs {
output := ctx . State . OutputValue ( addrs . AbsOutputValue {
OutputValue : addrs . OutputValue {
Name : name ,
} ,
Module : addrs . RootModuleInstance ,
} )
var value cty . Value
switch {
case output == nil :
// This means the run block returned null for this output.
// It is likely this will produce an error later if it is
// referenced, but users can actually specify that null
// is an acceptable value for an input variable so we won't
// actually raise a fuss about this at all.
value = cty . NullVal ( cty . DynamicPseudoType )
case output . Value . IsNull ( ) || output . Value == cty . NilVal :
// This means the output value was returned as (known after
// apply). If this is referenced it always an error, we
// can't handle this in an appropriate way at all. For now,
// we just mark it as unknown and then later we check and
// resolve all the references. We'll raise an error at that
// point if the user actually attempts to reference a value
// that is unknown.
value = cty . DynamicVal
default :
value = output . Value
}
if config . Sensitive || ( output != nil && output . Sensitive ) {
value = value . Mark ( marks . Sensitive )
}
attrs [ name ] = value
}
runs [ name ] = cty . ObjectVal ( attrs )
continue
}
// We haven't executed this run block yet, therefore it is not available
// as a reference at this point in time.
availableRunBlocks [ name ] = false
availableRunBlocks [ name ] = nil
}
// Second, let's build the set of available variables.
vars := make ( map [ string ] cty . Value )
for name , variable := range availableVariables {
vars [ name ] = variable . Value
}
// Third, let's do some basic validation over the references.
for _ , value := range run . Config . Variables {
refs , refDiags := lang . ReferencesInExpr ( addrs . ParseRefFromTestingScope , value )
diags = diags . Append ( refDiags )
@ -1294,7 +1347,7 @@ func (runner *TestFileRunner) ctx(run *moduletest.Run, file *moduletest.File, av
for _ , ref := range refs {
if addr , ok := ref . Subject . ( addrs . Run ) ; ok {
available , exists := availableRunBlocks [ addr . Name ]
ctx , exists := availableRunBlocks [ addr . Name ]
if ! exists {
// Then this is a made up run block.
@ -1308,7 +1361,7 @@ func (runner *TestFileRunner) ctx(run *moduletest.Run, file *moduletest.File, av
continue
}
if ! available {
if ctx == nil {
// This run block exists, but it is after the current run block.
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
@ -1320,12 +1373,50 @@ func (runner *TestFileRunner) ctx(run *moduletest.Run, file *moduletest.File, av
continue
}
// Otherwise, we're good. This is an acceptable reference.
value , valueDiags := ref . Remaining . TraverseRel ( runs [ addr . Name ] )
diags = diags . Append ( valueDiags )
if valueDiags . HasErrors ( ) {
// This means the reference was invalid somehow, we've
// already added the errors to our diagnostics though so
// we'll just carry on.
continue
}
if ! value . IsWhollyKnown ( ) {
// This is not valid, we cannot allow users to pass unknown
// values into run blocks. There's just going to be
// difficult and confusing errors later if this happens.
if ctx . Run . Config . Command == configs . PlanTestCommand {
// Then the user has likely attempted to use an output
// that is (known after apply) due to the referenced
// run block only being a plan command.
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Reference to unknown value" ,
Detail : fmt . Sprintf ( "The value for %s is unknown. Run block %q is executing a \"plan\" operation, and the specified output value is only known after apply." , ref . DisplayString ( ) , addr . Name ) ,
Subject : ref . SourceRange . ToHCL ( ) . Ptr ( ) ,
} )
continue
}
// Otherwise, this is a bug in Terraform. We shouldn't be
// producing (known after apply) values during apply
// operations.
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Reference to unknown value" ,
Detail : fmt . Sprintf ( "The value for %s is unknown; This is a bug in Terraform, please report it." , ref . DisplayString ( ) ) ,
Subject : ref . SourceRange . ToHCL ( ) . Ptr ( ) ,
} )
}
continue
}
if addr , ok := ref . Subject . ( addrs . InputVariable ) ; ok {
if _ , exists := availableVariables [ addr . Name ] ; ! exists {
if _ , exists := vars[ addr . Name ] ; ! exists {
// This variable reference doesn't exist.
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
@ -1352,66 +1443,12 @@ func (runner *TestFileRunner) ctx(run *moduletest.Run, file *moduletest.File, av
}
}
return & hcl . EvalContext {
Variables : func ( ) map [ string ] cty . Value {
blocks := make ( map [ string ] cty . Value )
for run , ctx := range runner . PriorStates {
outputs := make ( map [ string ] cty . Value )
for _ , output := range ctx . Config . Module . Outputs {
value := ctx . State . OutputValue ( addrs . AbsOutputValue {
Module : addrs . RootModuleInstance ,
OutputValue : addrs . OutputValue {
Name : output . Name ,
} ,
} )
if value == nil {
// Then this output returned null when the configuration
// executed. For now, we'll just skip this output.
//
// There are several things we could try to do, like
// figure out the type based on the variable that it
// is referencing and wrap it up as cty.Val(...) or we
// could not try and work anything out and return it as
// a cty.NilVal.
//
// Both of these mean the error would be raised later
// as non-optional variables would say they don't have
// a value. By just ignoring it here, we get an error
// quicker that says this output doesn't exist. I think
// that would prompt users to go look at the output and
// realise it might be returning null and make the
// connection. With the other approaches they'd look at
// their variable definitions and think they are
// assigning it a value since we would be telling them
// the output does exist.
//
// Let's do the simple thing now, and see what the
// future holds.
continue
}
// Finally, we can just populate our hcl.EvalContext.
if value . Sensitive || output . Sensitive {
outputs [ output . Name ] = value . Value . Mark ( marks . Sensitive )
continue
}
outputs [ output . Name ] = value . Value
}
blocks [ run ] = cty . ObjectVal ( outputs )
}
variables := make ( map [ string ] cty . Value )
for name , variable := range availableVariables {
variables [ name ] = variable . Value
}
return map [ string ] cty . Value {
"run" : cty . ObjectVal ( blocks ) ,
"var" : cty . ObjectVal ( variables ) ,
}
} ( ) ,
return & hcl . EvalContext {
Variables : map [ string ] cty . Value {
"run" : cty . ObjectVal ( runs ) ,
"var" : cty . ObjectVal ( vars ) ,
} ,
} , diags
}