stacks: Track component instance existence in the state

Although we can often use the presence of resource instances in a
component instance as an implication of the component instance's existence,
it's possible (albeit rare) for a component instance to have no resources
in it at all, and thus it would be ambiguous whether it exists or not.

Now we'll track the existence of the component instances themselves as
extra objects in both the raw state and the state description. Along with
their existence we'll also track a snapshot of the output values as they
were at the most recent apply, which for now is primarily for external
consumption but we'll also include them in the raw state in case it ends up
being useful for something down the road.
pull/34393/head
Martin Atkins 2 years ago
parent bcccd67e32
commit 2aba28eb1a

File diff suppressed because it is too large Load Diff

@ -868,6 +868,7 @@ message AppliedChange {
Nothing deleted = 4; // explicitly represents the absense of a description
ResourceInstance resource_instance = 2;
OutputValue output_value = 3;
ComponentInstance component_instance = 5;
}
// Field number 20000 is reserved as a field number that will
// always be unknown to any client, to allow clients to test
@ -904,6 +905,10 @@ message AppliedChange {
// in.
bool interim = 3;
}
message ComponentInstance {
string component_instance_addr = 1;
map<string,DynamicValue> output_values = 2;
}
message OutputValue {
string name = 1;
DynamicValue new_value = 2;

@ -14,6 +14,7 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/promising"
"github.com/hashicorp/terraform/internal/providers"
@ -1118,6 +1119,20 @@ func (c *ComponentInstance) CheckApply(ctx context.Context) ([]stackstate.Applie
if applyResult != nil {
newState := applyResult.FinalState
ourChange := &stackstate.AppliedChangeComponentInstance{
ComponentInstanceAddr: c.Addr(),
OutputValues: make(map[addrs.OutputValue]cty.Value, len(newState.RootOutputValues)),
}
for name, os := range newState.RootOutputValues {
val := os.Value
if os.Sensitive {
val = val.Mark(marks.Sensitive)
}
ourChange.OutputValues[addrs.OutputValue{Name: name}] = val
}
changes = append(changes, ourChange)
for _, rioAddr := range applyResult.AffectedResourceInstanceObjects {
os := newState.ResourceInstanceObjectSrc(rioAddr)
var providerConfigAddr addrs.AbsProviderConfig

@ -16,6 +16,7 @@ import (
"github.com/hashicorp/terraform/internal/stacks/stackutils"
"github.com/hashicorp/terraform/internal/stacks/tfstackdata1"
"github.com/hashicorp/terraform/internal/states"
"github.com/zclconf/go-cty/cty"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
)
@ -37,6 +38,16 @@ type AppliedChange interface {
// AppliedChangeResourceInstanceObject announces the result of applying changes to
// a particular resource instance object.
type AppliedChangeResourceInstanceObject struct {
// ResourceInstanceObjectAddr is the absolute address of the resource
// instance object within the component instance that declared it.
//
// Typically a stream of applied changes with a resource instance object
// will also include a separate description of the component instance
// that the resource instance belongs to, but that isn't guaranteed in
// cases where problems occur during the apply phase and so consumers
// should tolerate seeing a resource instance for a component instance
// they don't know about yet, and should behave as if that component
// instance had been previously announced.
ResourceInstanceObjectAddr stackaddrs.AbsResourceInstanceObject
NewStateSrc *states.ResourceInstanceObjectSrc
ProviderConfigAddr addrs.AbsProviderConfig
@ -148,6 +159,75 @@ func (ac *AppliedChangeResourceInstanceObject) protosForObject() ([]*terraform1.
return descs, raws, nil
}
// AppliedChangeComponentInstance announces the result of applying changes to
// an overall component instance.
//
// This deals with external-facing metadata about component instances, but
// does not directly track any resource instances inside. Those are tracked
// using individual [AppliedChangeResourceInstanceObject] objects for each.
type AppliedChangeComponentInstance struct {
ComponentInstanceAddr stackaddrs.AbsComponentInstance
// OutputValues "remembers" the output values from the most recent
// apply of the component instance. We store this primarily for external
// consumption, since the stacks runtime is able to recalculate the
// output values based on the prior state when needed, but we do have
// the option of using this internally in certain special cases where it
// would be too expensive to recalculate.
//
// If any output values are declared as sensitive then they should be
// marked as such here using the usual cty marking strategy.
OutputValues map[addrs.OutputValue]cty.Value
}
var _ AppliedChange = (*AppliedChangeComponentInstance)(nil)
// AppliedChangeProto implements AppliedChange.
func (ac *AppliedChangeComponentInstance) AppliedChangeProto() (*terraform1.AppliedChange, error) {
ret := &terraform1.AppliedChange{
Raw: make([]*terraform1.AppliedChange_RawChange, 0, 1),
Descriptions: make([]*terraform1.AppliedChange_ChangeDescription, 0, 1),
}
stateKey := statekeys.ComponentInstance{
ComponentInstanceAddr: ac.ComponentInstanceAddr,
}
rawMsg, err := tfstackdata1.ComponentInstanceResultsToTFStackData1(ac.OutputValues)
if err != nil {
return nil, fmt.Errorf("encoding raw state for %s: %w", ac.ComponentInstanceAddr, err)
}
var raw anypb.Any
err = anypb.MarshalFrom(&raw, rawMsg, proto.MarshalOptions{})
if err != nil {
return nil, fmt.Errorf("encoding raw state for %s: %w", ac.ComponentInstanceAddr, err)
}
outputDescs := make(map[string]*terraform1.DynamicValue, len(ac.OutputValues))
for addr, val := range ac.OutputValues {
unmarkedValue, sensitivePaths := val.UnmarkDeepWithPaths()
encValue, err := plans.NewDynamicValue(unmarkedValue, cty.DynamicPseudoType)
if err != nil {
return nil, fmt.Errorf("encoding new state for %s in %s in preparation for saving it: %w", addr, ac.ComponentInstanceAddr, err)
}
protoValue := terraform1.NewDynamicValue(encValue, sensitivePaths)
outputDescs[addr.Name] = protoValue
}
ret.Raw = append(ret.Raw, &terraform1.AppliedChange_RawChange{
Key: statekeys.String(stateKey),
Value: &raw,
})
ret.Descriptions = append(ret.Descriptions, &terraform1.AppliedChange_ChangeDescription{
Key: statekeys.String(stateKey),
Description: &terraform1.AppliedChange_ChangeDescription_ComponentInstance{
ComponentInstance: &terraform1.AppliedChange_ComponentInstance{
ComponentInstanceAddr: ac.ComponentInstanceAddr.String(),
},
},
})
return ret, nil
}
type AppliedChangeDiscardKeys struct {
DiscardRawKeys collections.Set[statekeys.Key]
DiscardDescKeys collections.Set[statekeys.Key]

Loading…
Cancel
Save