change evaluation to use whole modules
The evaluationStateData needs the change to the GetModule method to work with the new evaluator. This is using a deep copy of module instances, which we will clean up after some changes to the states package.
This commit is contained in:
parent
323d9fb69f
commit
27cc2aeb9c
|
@ -336,54 +336,184 @@ func (d *evaluationStateData) GetLocalValue(addr addrs.LocalValue, rng tfdiags.S
|
||||||
return val, diags
|
return val, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *evaluationStateData) GetModuleInstance(addr addrs.ModuleCallInstance, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
// Output results live in the module that declares them, which is one of
|
// Output results live in the module that declares them, which is one of
|
||||||
// the child module instances of our current module path.
|
// the child module instances of our current module path.
|
||||||
moduleAddr := addr.ModuleInstance(d.ModulePath)
|
moduleAddr := d.ModulePath.Module().Child(addr.Name)
|
||||||
|
|
||||||
|
parentCfg := d.Evaluator.Config.DescendentForInstance(d.ModulePath)
|
||||||
|
callConfig, ok := parentCfg.Module.ModuleCalls[addr.Name]
|
||||||
|
if !ok {
|
||||||
|
diags = diags.Append(&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: `Reference to undeclared module`,
|
||||||
|
Detail: fmt.Sprintf(`The configuration contains no %s.`, moduleAddr),
|
||||||
|
Subject: rng.ToHCL().Ptr(),
|
||||||
|
})
|
||||||
|
return cty.DynamicVal, diags
|
||||||
|
}
|
||||||
|
|
||||||
// We'll consult the configuration to see what output names we are
|
// We'll consult the configuration to see what output names we are
|
||||||
// expecting, so we can ensure the resulting object is of the expected
|
// expecting, so we can ensure the resulting object is of the expected
|
||||||
// type even if our data is incomplete for some reason.
|
// type even if our data is incomplete for some reason.
|
||||||
moduleConfig := d.Evaluator.Config.DescendentForInstance(moduleAddr)
|
moduleConfig := d.Evaluator.Config.Descendent(moduleAddr)
|
||||||
if moduleConfig == nil {
|
if moduleConfig == nil {
|
||||||
// should never happen, since this should've been caught during
|
// should never happen, since we have a valid module call above, this
|
||||||
// static validation.
|
// should be caught during static validation.
|
||||||
panic(fmt.Sprintf("output value read from %s, which has no configuration", moduleAddr))
|
panic(fmt.Sprintf("output value read from %s, which has no configuration", moduleAddr))
|
||||||
}
|
}
|
||||||
outputConfigs := moduleConfig.Module.Outputs
|
outputConfigs := moduleConfig.Module.Outputs
|
||||||
|
|
||||||
vals := map[string]cty.Value{}
|
// Collect all the relevant outputs that current exist in the state.
|
||||||
for n := range outputConfigs {
|
// We know the instance path up to this point, and the child module name,
|
||||||
addr := addrs.OutputValue{Name: n}.Absolute(moduleAddr)
|
// so we only need to store these by instance key.
|
||||||
|
stateMap := map[addrs.InstanceKey]map[string]cty.Value{}
|
||||||
|
for _, m := range d.Evaluator.State.ModuleInstances(moduleAddr) {
|
||||||
|
// skip module instances that aren't a child of our particular parent
|
||||||
|
// module instance.
|
||||||
|
if !d.ModulePath.Equal(m.Addr.Parent()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, callInstance := m.Addr.CallInstance()
|
||||||
|
instance, ok := stateMap[callInstance.Key]
|
||||||
|
if !ok {
|
||||||
|
instance = map[string]cty.Value{}
|
||||||
|
stateMap[callInstance.Key] = instance
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, output := range m.OutputValues {
|
||||||
|
instance[name] = output.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all changes that reside for this module call within our path.
|
||||||
|
// The change contains the full addr, so we can key these with strings.
|
||||||
|
changesMap := map[addrs.InstanceKey]map[string]*plans.OutputChangeSrc{}
|
||||||
|
for _, change := range d.Evaluator.Changes.GetOutputChanges(d.ModulePath, addr) {
|
||||||
|
_, callInstance := change.Addr.Module.CallInstance()
|
||||||
|
instance, ok := changesMap[callInstance.Key]
|
||||||
|
if !ok {
|
||||||
|
instance = map[string]*plans.OutputChangeSrc{}
|
||||||
|
changesMap[callInstance.Key] = instance
|
||||||
|
}
|
||||||
|
|
||||||
|
instance[change.Addr.OutputValue.Name] = change
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up all the module objects, creating a map of values for each
|
||||||
|
// module instance.
|
||||||
|
moduleInstances := map[addrs.InstanceKey]map[string]cty.Value{}
|
||||||
|
|
||||||
|
// the structure is based on the configuration, so iterate through all the
|
||||||
|
// defined outputs, and add any instance state or changes we find.
|
||||||
|
for _, cfg := range outputConfigs {
|
||||||
|
// get all instance output for this path from the state
|
||||||
|
for key, states := range stateMap {
|
||||||
|
outputState, ok := states[cfg.Name]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
instance, ok := moduleInstances[key]
|
||||||
|
if !ok {
|
||||||
|
instance = map[string]cty.Value{}
|
||||||
|
moduleInstances[key] = instance
|
||||||
|
}
|
||||||
|
|
||||||
|
instance[cfg.Name] = outputState
|
||||||
|
}
|
||||||
|
|
||||||
|
// any pending changes override the state state values
|
||||||
|
for key, changes := range changesMap {
|
||||||
|
changeSrc, ok := changes[cfg.Name]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
instance, ok := moduleInstances[key]
|
||||||
|
if !ok {
|
||||||
|
instance = map[string]cty.Value{}
|
||||||
|
moduleInstances[key] = instance
|
||||||
|
}
|
||||||
|
|
||||||
// If a pending change is present in our current changeset then its value
|
|
||||||
// takes priority over what's in state. (It will usually be the same but
|
|
||||||
// will differ if the new value is unknown during planning.)
|
|
||||||
if changeSrc := d.Evaluator.Changes.GetOutputChange(addr); changeSrc != nil {
|
|
||||||
change, err := changeSrc.Decode()
|
change, err := changeSrc.Decode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// This should happen only if someone has tampered with a plan
|
// This should happen only if someone has tampered with a plan
|
||||||
// file, so we won't bother with a pretty error for it.
|
// file, so we won't bother with a pretty error for it.
|
||||||
diags = diags.Append(fmt.Errorf("planned change for %s could not be decoded: %s", addr, err))
|
diags = diags.Append(fmt.Errorf("planned change for %s could not be decoded: %s", addr, err))
|
||||||
vals[n] = cty.DynamicVal
|
instance[cfg.Name] = cty.DynamicVal
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// We care only about the "after" value, which is the value this output
|
|
||||||
// will take on after the plan is applied.
|
instance[cfg.Name] = change.After
|
||||||
vals[n] = change.After
|
}
|
||||||
} else {
|
}
|
||||||
os := d.Evaluator.State.OutputValue(addr)
|
|
||||||
if os == nil {
|
// ensure all defined outputs names are present in the module value, even
|
||||||
// Not evaluated yet?
|
// if they are not known yet.
|
||||||
vals[n] = cty.DynamicVal
|
for _, instance := range moduleInstances {
|
||||||
|
for configKey := range outputConfigs {
|
||||||
|
if _, ok := instance[configKey]; !ok {
|
||||||
|
instance[configKey] = cty.DynamicVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compile the outputs into the correct value type for the each mode
|
||||||
|
switch {
|
||||||
|
case callConfig.Count != nil:
|
||||||
|
vals := make([]cty.Value, len(moduleInstances))
|
||||||
|
for key, instance := range moduleInstances {
|
||||||
|
intKey, ok := key.(addrs.IntKey)
|
||||||
|
if !ok {
|
||||||
|
// old key from state which is being dropped
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
vals[n] = os.Value
|
|
||||||
|
vals[int(intKey)] = cty.ObjectVal(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we shouldn't have any holes, but insert real values just in case,
|
||||||
|
// while trimming off any extra values that may have there from old
|
||||||
|
// entries.
|
||||||
|
last := 0
|
||||||
|
for i, v := range vals {
|
||||||
|
if v.IsNull() {
|
||||||
|
vals[i] = cty.DynamicVal
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
last = i
|
||||||
|
}
|
||||||
|
vals = vals[:last+1]
|
||||||
|
return cty.TupleVal(vals), diags
|
||||||
|
|
||||||
|
case callConfig.ForEach != nil:
|
||||||
|
vals := make(map[string]cty.Value)
|
||||||
|
for key, instance := range moduleInstances {
|
||||||
|
strKey, ok := key.(addrs.StringKey)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
vals[string(strKey)] = cty.ObjectVal(instance)
|
||||||
}
|
}
|
||||||
return cty.ObjectVal(vals), diags
|
return cty.ObjectVal(vals), diags
|
||||||
|
|
||||||
|
default:
|
||||||
|
val, ok := moduleInstances[addrs.NoKey]
|
||||||
|
if !ok {
|
||||||
|
// create the object is there wasn't one known
|
||||||
|
val = map[string]cty.Value{}
|
||||||
|
for k := range outputConfigs {
|
||||||
|
val[k] = cty.DynamicVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cty.ObjectVal(val), diags
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *evaluationStateData) GetModuleInstanceOutput(addr addrs.AbsModuleCallOutput, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
func (d *evaluationStateData) GetModuleInstanceOutput(addr addrs.AbsModuleCallOutput, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||||
|
|
Loading…
Reference in New Issue