mirror of https://github.com/hashicorp/terraform
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
506 lines
18 KiB
506 lines
18 KiB
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package stackstate
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/types/known/anypb"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/collections"
|
|
"github.com/hashicorp/terraform/internal/providers"
|
|
"github.com/hashicorp/terraform/internal/rpcapi/terraform1/stacks"
|
|
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
|
|
"github.com/hashicorp/terraform/internal/stacks/stackstate/statekeys"
|
|
"github.com/hashicorp/terraform/internal/stacks/stackutils"
|
|
"github.com/hashicorp/terraform/internal/stacks/tfstackdata1"
|
|
"github.com/hashicorp/terraform/internal/states"
|
|
)
|
|
|
|
// AppliedChange represents a single isolated change, emitted as
|
|
// part of a stream of applied changes during the ApplyStackChanges RPC API
|
|
// operation.
|
|
//
|
|
// Each AppliedChange becomes a single event in the RPC API, which itself
|
|
// has zero or more opaque raw plan messages that the caller must collect and
|
|
// provide verbatim during planning and zero or more "description" messages
|
|
// that are to give the caller realtime updates about the planning process.
|
|
type AppliedChange interface {
|
|
// AppliedChangeProto returns the protocol buffers representation of
|
|
// the change, ready to be sent verbatim to an RPC API client.
|
|
AppliedChangeProto() (*stacks.AppliedChange, error)
|
|
}
|
|
|
|
// 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
|
|
|
|
// PreviousResourceInstanceObjectAddr is the absolute address of the
|
|
// resource instance object within the component instance if this object
|
|
// was moved from another address. This will be nil if the object was not
|
|
// moved.
|
|
PreviousResourceInstanceObjectAddr *stackaddrs.AbsResourceInstanceObject
|
|
|
|
// Schema MUST be the same schema that was used to encode the dynamic
|
|
// values inside NewStateSrc. This can be left as empty if NewStateSrc
|
|
// is nil, which represents that the object has been deleted.
|
|
Schema providers.Schema
|
|
}
|
|
|
|
var _ AppliedChange = (*AppliedChangeResourceInstanceObject)(nil)
|
|
|
|
// AppliedChangeProto implements AppliedChange.
|
|
func (ac *AppliedChangeResourceInstanceObject) AppliedChangeProto() (*stacks.AppliedChange, error) {
|
|
descs, raws, err := ac.protosForObject()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("encoding %s: %w", ac.ResourceInstanceObjectAddr, err)
|
|
}
|
|
return &stacks.AppliedChange{
|
|
Raw: raws,
|
|
Descriptions: descs,
|
|
}, nil
|
|
}
|
|
|
|
func (ac *AppliedChangeResourceInstanceObject) protosForObject() ([]*stacks.AppliedChange_ChangeDescription, []*stacks.AppliedChange_RawChange, error) {
|
|
var descs []*stacks.AppliedChange_ChangeDescription
|
|
var raws []*stacks.AppliedChange_RawChange
|
|
|
|
var addr = ac.ResourceInstanceObjectAddr
|
|
var provider = ac.ProviderConfigAddr
|
|
var objSrc = ac.NewStateSrc
|
|
|
|
// For resource instance objects we use the same key format for both the
|
|
// raw and description representations, but callers MUST NOT rely on this.
|
|
objKey := statekeys.ResourceInstanceObject{
|
|
ResourceInstance: stackaddrs.AbsResourceInstance{
|
|
Component: addr.Component,
|
|
Item: addr.Item.ResourceInstance,
|
|
},
|
|
DeposedKey: addr.Item.DeposedKey,
|
|
}
|
|
objKeyRaw := statekeys.String(objKey)
|
|
|
|
if objSrc == nil {
|
|
// If the new object is nil then we'll emit a "deleted" description
|
|
// to ensure that any existing prior state value gets removed.
|
|
descs = append(descs, &stacks.AppliedChange_ChangeDescription{
|
|
Key: objKeyRaw,
|
|
Description: &stacks.AppliedChange_ChangeDescription_Deleted{
|
|
Deleted: &stacks.AppliedChange_Nothing{},
|
|
},
|
|
})
|
|
raws = append(raws, &stacks.AppliedChange_RawChange{
|
|
Key: objKeyRaw,
|
|
Value: nil, // unset Value field represents "delete" for raw changes
|
|
})
|
|
return descs, raws, nil
|
|
}
|
|
|
|
if ac.PreviousResourceInstanceObjectAddr != nil {
|
|
// If the object was moved, we need to emit a "deleted" description
|
|
// for the old address to ensure that any existing prior state value
|
|
// gets removed.
|
|
prevKey := statekeys.ResourceInstanceObject{
|
|
ResourceInstance: stackaddrs.AbsResourceInstance{
|
|
Component: ac.PreviousResourceInstanceObjectAddr.Component,
|
|
Item: ac.PreviousResourceInstanceObjectAddr.Item.ResourceInstance,
|
|
},
|
|
DeposedKey: ac.PreviousResourceInstanceObjectAddr.Item.DeposedKey,
|
|
}
|
|
prevKeyRaw := statekeys.String(prevKey)
|
|
|
|
descs = append(descs, &stacks.AppliedChange_ChangeDescription{
|
|
Key: prevKeyRaw,
|
|
Description: &stacks.AppliedChange_ChangeDescription_Moved{
|
|
Moved: &stacks.AppliedChange_Nothing{},
|
|
},
|
|
})
|
|
raws = append(raws, &stacks.AppliedChange_RawChange{
|
|
Key: prevKeyRaw,
|
|
Value: nil, // unset Value field represents "delete" for raw changes
|
|
})
|
|
|
|
// Don't return now - we'll still add the main change below.
|
|
}
|
|
|
|
// TRICKY: For historical reasons, a states.ResourceInstance
|
|
// contains pre-JSON-encoded dynamic data ready to be
|
|
// inserted verbatim into Terraform CLI's traditional
|
|
// JSON-based state file format. However, our RPC API
|
|
// exclusively uses MessagePack encoding for dynamic
|
|
// values, and so we will need to use the ac.Schema to
|
|
// transcode the data.
|
|
obj, err := objSrc.Decode(ac.Schema)
|
|
if err != nil {
|
|
// It would be _very_ strange to get here because we should just
|
|
// be reversing the same encoding operation done earlier to
|
|
// produce this object, using exactly the same schema.
|
|
return nil, nil, fmt.Errorf("cannot decode new state for %s in preparation for saving it: %w", addr, err)
|
|
}
|
|
|
|
protoValue, err := stacks.ToDynamicValue(obj.Value, ac.Schema.Body.ImpliedType())
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("cannot encode new state for %s in preparation for saving it: %w", addr, err)
|
|
}
|
|
|
|
descs = append(descs, &stacks.AppliedChange_ChangeDescription{
|
|
Key: objKeyRaw,
|
|
Description: &stacks.AppliedChange_ChangeDescription_ResourceInstance{
|
|
ResourceInstance: &stacks.AppliedChange_ResourceInstance{
|
|
Addr: stacks.NewResourceInstanceObjectInStackAddr(addr),
|
|
NewValue: protoValue,
|
|
ResourceMode: stackutils.ResourceModeForProto(addr.Item.ResourceInstance.Resource.Resource.Mode),
|
|
ResourceType: addr.Item.ResourceInstance.Resource.Resource.Type,
|
|
ProviderAddr: provider.Provider.String(),
|
|
},
|
|
},
|
|
})
|
|
|
|
rawMsg := tfstackdata1.ResourceInstanceObjectStateToTFStackData1(objSrc, ac.ProviderConfigAddr)
|
|
var raw anypb.Any
|
|
err = anypb.MarshalFrom(&raw, rawMsg, proto.MarshalOptions{})
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("encoding raw state object: %w", err)
|
|
}
|
|
raws = append(raws, &stacks.AppliedChange_RawChange{
|
|
Key: objKeyRaw,
|
|
Value: &raw,
|
|
})
|
|
|
|
return descs, raws, nil
|
|
}
|
|
|
|
// AppliedChangeComponentInstanceRemoved is the equivalent of
|
|
// AppliedChangeComponentInstance but it represents the component instance
|
|
// being removed from state instead of created or updated.
|
|
type AppliedChangeComponentInstanceRemoved struct {
|
|
ComponentAddr stackaddrs.AbsComponent
|
|
ComponentInstanceAddr stackaddrs.AbsComponentInstance
|
|
}
|
|
|
|
var _ AppliedChange = (*AppliedChangeComponentInstanceRemoved)(nil)
|
|
|
|
// AppliedChangeProto implements AppliedChange.
|
|
func (ac *AppliedChangeComponentInstanceRemoved) AppliedChangeProto() (*stacks.AppliedChange, error) {
|
|
stateKey := statekeys.String(statekeys.ComponentInstance{
|
|
ComponentInstanceAddr: ac.ComponentInstanceAddr,
|
|
})
|
|
return &stacks.AppliedChange{
|
|
Raw: []*stacks.AppliedChange_RawChange{
|
|
{
|
|
Key: stateKey,
|
|
Value: nil,
|
|
},
|
|
},
|
|
Descriptions: []*stacks.AppliedChange_ChangeDescription{
|
|
{
|
|
Key: stateKey,
|
|
Description: &stacks.AppliedChange_ChangeDescription_Deleted{
|
|
Deleted: &stacks.AppliedChange_Nothing{},
|
|
},
|
|
},
|
|
},
|
|
}, 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 {
|
|
ComponentAddr stackaddrs.AbsComponent
|
|
ComponentInstanceAddr stackaddrs.AbsComponentInstance
|
|
|
|
// Dependencies "remembers" the set of component instances that were
|
|
// required by the most recent apply of this component instance.
|
|
//
|
|
// This will be used by the stacks runtime to determine the order in
|
|
// which components should be destroyed when the original component block
|
|
// is no longer available.
|
|
Dependencies collections.Set[stackaddrs.AbsComponent]
|
|
|
|
// Dependents "remembers" the set of component instances that depended on
|
|
// this component instance at the most recent apply of this component
|
|
// instance.
|
|
//
|
|
// This will be used by the stacks runtime to determine the order in
|
|
// which components should be destroyed when the original component block
|
|
// is no longer available.
|
|
Dependents collections.Set[stackaddrs.AbsComponent]
|
|
|
|
// 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
|
|
|
|
// InputVariables "remembers" the input values from the most recent
|
|
// apply of the component instance. We store this primarily for usage
|
|
// within the removed blocks in which the input values from the last
|
|
// applied state are required to destroy the existing resources.
|
|
InputVariables map[addrs.InputVariable]cty.Value
|
|
}
|
|
|
|
var _ AppliedChange = (*AppliedChangeComponentInstance)(nil)
|
|
|
|
// AppliedChangeProto implements AppliedChange.
|
|
func (ac *AppliedChangeComponentInstance) AppliedChangeProto() (*stacks.AppliedChange, error) {
|
|
stateKey := statekeys.String(statekeys.ComponentInstance{
|
|
ComponentInstanceAddr: ac.ComponentInstanceAddr,
|
|
})
|
|
|
|
outputDescs := make(map[string]*stacks.DynamicValue, len(ac.OutputValues))
|
|
for addr, val := range ac.OutputValues {
|
|
protoValue, err := stacks.ToDynamicValue(val, 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)
|
|
}
|
|
outputDescs[addr.Name] = protoValue
|
|
}
|
|
|
|
inputDescs := make(map[string]*stacks.DynamicValue, len(ac.InputVariables))
|
|
for addr, val := range ac.InputVariables {
|
|
protoValue, err := stacks.ToDynamicValue(val, 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)
|
|
}
|
|
inputDescs[addr.Name] = protoValue
|
|
}
|
|
|
|
var raw anypb.Any
|
|
if err := anypb.MarshalFrom(&raw, &tfstackdata1.StateComponentInstanceV1{
|
|
OutputValues: func() map[string]*tfstackdata1.DynamicValue {
|
|
outputs := make(map[string]*tfstackdata1.DynamicValue, len(outputDescs))
|
|
for name, value := range outputDescs {
|
|
outputs[name] = tfstackdata1.Terraform1ToStackDataDynamicValue(value)
|
|
}
|
|
return outputs
|
|
}(),
|
|
InputVariables: func() map[string]*tfstackdata1.DynamicValue {
|
|
inputs := make(map[string]*tfstackdata1.DynamicValue, len(inputDescs))
|
|
for name, value := range inputDescs {
|
|
inputs[name] = tfstackdata1.Terraform1ToStackDataDynamicValue(value)
|
|
}
|
|
return inputs
|
|
}(),
|
|
DependencyAddrs: func() []string {
|
|
var dependencies []string
|
|
for dependency := range ac.Dependencies.All() {
|
|
dependencies = append(dependencies, dependency.String())
|
|
}
|
|
return dependencies
|
|
}(),
|
|
DependentAddrs: func() []string {
|
|
var dependents []string
|
|
for dependent := range ac.Dependents.All() {
|
|
dependents = append(dependents, dependent.String())
|
|
}
|
|
return dependents
|
|
}(),
|
|
}, proto.MarshalOptions{}); err != nil {
|
|
return nil, fmt.Errorf("encoding raw state for %s: %w", ac.ComponentInstanceAddr, err)
|
|
}
|
|
|
|
return &stacks.AppliedChange{
|
|
Raw: []*stacks.AppliedChange_RawChange{
|
|
{
|
|
Key: stateKey,
|
|
Value: &raw,
|
|
},
|
|
},
|
|
Descriptions: []*stacks.AppliedChange_ChangeDescription{
|
|
{
|
|
Key: stateKey,
|
|
Description: &stacks.AppliedChange_ChangeDescription_ComponentInstance{
|
|
ComponentInstance: &stacks.AppliedChange_ComponentInstance{
|
|
ComponentAddr: ac.ComponentAddr.String(),
|
|
ComponentInstanceAddr: ac.ComponentInstanceAddr.String(),
|
|
OutputValues: outputDescs,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
type AppliedChangeInputVariable struct {
|
|
Addr stackaddrs.InputVariable
|
|
Value cty.Value
|
|
}
|
|
|
|
var _ AppliedChange = (*AppliedChangeInputVariable)(nil)
|
|
|
|
func (ac *AppliedChangeInputVariable) AppliedChangeProto() (*stacks.AppliedChange, error) {
|
|
key := statekeys.String(statekeys.Variable{
|
|
VariableAddr: ac.Addr,
|
|
})
|
|
|
|
if ac.Value == cty.NilVal {
|
|
// Then we're deleting this input variable from the state.
|
|
return &stacks.AppliedChange{
|
|
Raw: []*stacks.AppliedChange_RawChange{
|
|
{
|
|
Key: key,
|
|
Value: nil,
|
|
},
|
|
},
|
|
Descriptions: []*stacks.AppliedChange_ChangeDescription{
|
|
{
|
|
Key: key,
|
|
Description: &stacks.AppliedChange_ChangeDescription_Deleted{
|
|
Deleted: &stacks.AppliedChange_Nothing{},
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
var raw anypb.Any
|
|
description := &stacks.AppliedChange_InputVariable{
|
|
Name: ac.Addr.Name,
|
|
}
|
|
|
|
value, err := stacks.ToDynamicValue(ac.Value, cty.DynamicPseudoType)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("encoding new state for %s in preparation for saving it: %w", ac.Addr, err)
|
|
}
|
|
description.NewValue = value
|
|
if err := anypb.MarshalFrom(&raw, tfstackdata1.Terraform1ToStackDataDynamicValue(value), proto.MarshalOptions{}); err != nil {
|
|
return nil, fmt.Errorf("encoding raw state for %s: %w", ac.Addr, err)
|
|
}
|
|
|
|
return &stacks.AppliedChange{
|
|
Raw: []*stacks.AppliedChange_RawChange{
|
|
{
|
|
Key: key,
|
|
Value: &raw,
|
|
},
|
|
},
|
|
Descriptions: []*stacks.AppliedChange_ChangeDescription{
|
|
{
|
|
Key: key,
|
|
Description: &stacks.AppliedChange_ChangeDescription_InputVariable{
|
|
InputVariable: description,
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
type AppliedChangeOutputValue struct {
|
|
Addr stackaddrs.OutputValue
|
|
Value cty.Value
|
|
}
|
|
|
|
var _ AppliedChange = (*AppliedChangeOutputValue)(nil)
|
|
|
|
func (ac *AppliedChangeOutputValue) AppliedChangeProto() (*stacks.AppliedChange, error) {
|
|
key := statekeys.String(statekeys.Output{
|
|
OutputAddr: ac.Addr,
|
|
})
|
|
|
|
if ac.Value == cty.NilVal {
|
|
// Then we're deleting this output value from the state.
|
|
return &stacks.AppliedChange{
|
|
Raw: []*stacks.AppliedChange_RawChange{
|
|
{
|
|
Key: key,
|
|
Value: nil,
|
|
},
|
|
},
|
|
Descriptions: []*stacks.AppliedChange_ChangeDescription{
|
|
{
|
|
Key: key,
|
|
Description: &stacks.AppliedChange_ChangeDescription_Deleted{
|
|
Deleted: &stacks.AppliedChange_Nothing{},
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
value, err := stacks.ToDynamicValue(ac.Value, cty.DynamicPseudoType)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("encoding new state for %s in preparation for saving it: %w", ac.Addr, err)
|
|
}
|
|
|
|
var raw anypb.Any
|
|
if err := anypb.MarshalFrom(&raw, tfstackdata1.Terraform1ToStackDataDynamicValue(value), proto.MarshalOptions{}); err != nil {
|
|
return nil, fmt.Errorf("encoding raw state for %s: %w", ac.Addr, err)
|
|
}
|
|
|
|
return &stacks.AppliedChange{
|
|
Raw: []*stacks.AppliedChange_RawChange{
|
|
{
|
|
Key: key,
|
|
Value: &raw,
|
|
},
|
|
},
|
|
Descriptions: []*stacks.AppliedChange_ChangeDescription{
|
|
{
|
|
Key: key,
|
|
Description: &stacks.AppliedChange_ChangeDescription_OutputValue{
|
|
OutputValue: &stacks.AppliedChange_OutputValue{
|
|
Name: ac.Addr.Name,
|
|
NewValue: value,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
type AppliedChangeDiscardKeys struct {
|
|
DiscardRawKeys collections.Set[statekeys.Key]
|
|
DiscardDescKeys collections.Set[statekeys.Key]
|
|
}
|
|
|
|
var _ AppliedChange = (*AppliedChangeDiscardKeys)(nil)
|
|
|
|
// AppliedChangeProto implements AppliedChange.
|
|
func (ac *AppliedChangeDiscardKeys) AppliedChangeProto() (*stacks.AppliedChange, error) {
|
|
ret := &stacks.AppliedChange{
|
|
Raw: make([]*stacks.AppliedChange_RawChange, 0, ac.DiscardRawKeys.Len()),
|
|
Descriptions: make([]*stacks.AppliedChange_ChangeDescription, 0, ac.DiscardDescKeys.Len()),
|
|
}
|
|
for key := range ac.DiscardRawKeys.All() {
|
|
ret.Raw = append(ret.Raw, &stacks.AppliedChange_RawChange{
|
|
Key: statekeys.String(key),
|
|
Value: nil, // nil represents deletion
|
|
})
|
|
}
|
|
for key := range ac.DiscardDescKeys.All() {
|
|
ret.Descriptions = append(ret.Descriptions, &stacks.AppliedChange_ChangeDescription{
|
|
Key: statekeys.String(key),
|
|
Description: &stacks.AppliedChange_ChangeDescription_Deleted{
|
|
// Selection of this empty variant represents deletion
|
|
},
|
|
})
|
|
}
|
|
return ret, nil
|
|
}
|