Skip to content

Commit

Permalink
Merge pull request #28201 from hashicorp/alisdair/planfile-sensitive-…
Browse files Browse the repository at this point in the history
…required-replace

Add sensitivity and required-replace fields to plan file, and expose sensitivity to JSON plan
  • Loading branch information
alisdair authored Mar 29, 2021
2 parents e3bd661 + 5e30d58 commit 340543d
Show file tree
Hide file tree
Showing 19 changed files with 811 additions and 129 deletions.
2 changes: 2 additions & 0 deletions command/jsonconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type variables map[string]*variable
type variable struct {
Default json.RawMessage `json:"default,omitempty"`
Description string `json:"description,omitempty"`
Sensitive bool `json:"sensitive,omitempty"`
}

// Resource is the representation of a resource in the config
Expand Down Expand Up @@ -263,6 +264,7 @@ func marshalModule(c *configs.Config, schemas *terraform.Schemas, addr string) (
vars[k] = &variable{
Default: defaultValJSON,
Description: v.Description,
Sensitive: v.Sensitive,
}
}
module.Variables = vars
Expand Down
141 changes: 130 additions & 11 deletions command/jsonplan/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,23 @@ type change struct {
// or "after" is unset (respectively). For ["no-op"], the before and after
// values are identical. The "after" value will be incomplete if there are
// values within it that won't be known until after apply.
Before json.RawMessage `json:"before,omitempty"`
After json.RawMessage `json:"after,omitempty"`
Before json.RawMessage `json:"before,omitempty"`
After json.RawMessage `json:"after,omitempty"`

// AfterUnknown is an object value with similar structure to After, but
// with all unknown leaf values replaced with true, and all known leaf
// values omitted. This can be combined with After to reconstruct a full
// value after the action, including values which will only be known after
// apply.
AfterUnknown json.RawMessage `json:"after_unknown,omitempty"`

// BeforeSensitive and AfterSensitive are object values with similar
// structure to Before and After, but with all sensitive leaf values
// replaced with true, and all non-sensitive leaf values omitted. These
// objects should be combined with Before and After to prevent accidental
// display of sensitive values in user interfaces.
BeforeSensitive json.RawMessage `json:"before_sensitive,omitempty"`
AfterSensitive json.RawMessage `json:"after_sensitive,omitempty"`
}

type output struct {
Expand Down Expand Up @@ -192,12 +206,18 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform
}

var before, after []byte
var beforeSensitive, afterSensitive []byte
var afterUnknown cty.Value
if changeV.Before != cty.NilVal {
before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type())
if err != nil {
return err
}
bs := sensitiveAsBool(changeV.Before.MarkWithPaths(rc.BeforeValMarks))
beforeSensitive, err = ctyjson.Marshal(bs, bs.Type())
if err != nil {
return err
}
}
if changeV.After != cty.NilVal {
if changeV.After.IsWhollyKnown() {
Expand All @@ -218,6 +238,11 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform
}
afterUnknown = unknownAsBool(changeV.After)
}
as := sensitiveAsBool(changeV.After.MarkWithPaths(rc.AfterValMarks))
afterSensitive, err = ctyjson.Marshal(as, as.Type())
if err != nil {
return err
}
}

a, err := ctyjson.Marshal(afterUnknown, afterUnknown.Type())
Expand All @@ -226,10 +251,12 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform
}

r.Change = change{
Actions: actionString(rc.Action.String()),
Before: json.RawMessage(before),
After: json.RawMessage(after),
AfterUnknown: a,
Actions: actionString(rc.Action.String()),
Before: json.RawMessage(before),
After: json.RawMessage(after),
AfterUnknown: a,
BeforeSensitive: json.RawMessage(beforeSensitive),
AfterSensitive: json.RawMessage(afterSensitive),
}

if rc.DeposedKey != states.NotDeposed {
Expand Down Expand Up @@ -297,13 +324,28 @@ func (p *plan) marshalOutputChanges(changes *plans.Changes) error {
}
}

// The only information we have in the plan about output sensitivity is
// a boolean which is true if the output was or is marked sensitive. As
// a result, BeforeSensitive and AfterSensitive will be identical, and
// either false or true.
outputSensitive := cty.False
if oc.Sensitive {
outputSensitive = cty.True
}
sensitive, err := ctyjson.Marshal(outputSensitive, outputSensitive.Type())
if err != nil {
return err
}

a, _ := ctyjson.Marshal(afterUnknown, afterUnknown.Type())

c := change{
Actions: actionString(oc.Action.String()),
Before: json.RawMessage(before),
After: json.RawMessage(after),
AfterUnknown: a,
Actions: actionString(oc.Action.String()),
Before: json.RawMessage(before),
After: json.RawMessage(after),
AfterUnknown: a,
BeforeSensitive: json.RawMessage(sensitive),
AfterSensitive: json.RawMessage(sensitive),
}

p.OutputChanges[oc.Addr.OutputValue.Name] = c
Expand Down Expand Up @@ -392,6 +434,9 @@ func omitUnknowns(val cty.Value) cty.Value {
// tuple types and all mapping types are converted to object types, since we
// assume the result of this is just going to be serialized as JSON (and thus
// lose those distinctions) anyway.
//
// For map/object values, all known attribute values will be omitted instead of
// returning false, as this results in a more compact serialization.
func unknownAsBool(val cty.Value) cty.Value {
ty := val.Type()
switch {
Expand Down Expand Up @@ -439,7 +484,9 @@ func unknownAsBool(val cty.Value) cty.Value {
for it.Next() {
k, v := it.Element()
vAsBool := unknownAsBool(v)
if !vAsBool.RawEquals(cty.False) { // all of the "false"s for known values for more compact serialization
// Omit all of the "false"s for known values for more compact
// serialization
if !vAsBool.RawEquals(cty.False) {
vals[k.AsString()] = unknownAsBool(v)
}
}
Expand All @@ -455,6 +502,78 @@ func unknownAsBool(val cty.Value) cty.Value {
}
}

// recursively iterate through a marked cty.Value, replacing sensitive values
// with cty.True and non-sensitive values with cty.False.
//
// The result also normalizes some types: all sequence types are turned into
// tuple types and all mapping types are converted to object types, since we
// assume the result of this is just going to be serialized as JSON (and thus
// lose those distinctions) anyway.
//
// For map/object values, all non-sensitive attribute values will be omitted
// instead of returning false, as this results in a more compact serialization.
func sensitiveAsBool(val cty.Value) cty.Value {
if val.HasMark("sensitive") {
return cty.True
}

ty := val.Type()
switch {
case val.IsNull(), ty.IsPrimitiveType(), ty.Equals(cty.DynamicPseudoType):
return cty.False
case ty.IsListType() || ty.IsTupleType() || ty.IsSetType():
length := val.LengthInt()
if length == 0 {
// If there are no elements then we can't have sensitive values
return cty.EmptyTupleVal
}
vals := make([]cty.Value, 0, length)
it := val.ElementIterator()
for it.Next() {
_, v := it.Element()
vals = append(vals, sensitiveAsBool(v))
}
// The above transform may have changed the types of some of the
// elements, so we'll always use a tuple here in case we've now made
// different elements have different types. Our ultimate goal is to
// marshal to JSON anyway, and all of these sequence types are
// indistinguishable in JSON.
return cty.TupleVal(vals)
case ty.IsMapType() || ty.IsObjectType():
var length int
switch {
case ty.IsMapType():
length = val.LengthInt()
default:
length = len(val.Type().AttributeTypes())
}
if length == 0 {
// If there are no elements then we can't have sensitive values
return cty.EmptyObjectVal
}
vals := make(map[string]cty.Value)
it := val.ElementIterator()
for it.Next() {
k, v := it.Element()
s := sensitiveAsBool(v)
// Omit all of the "false"s for non-sensitive values for more
// compact serialization
if !s.RawEquals(cty.False) {
vals[k.AsString()] = s
}
}
// The above transform may have changed the types of some of the
// elements, so we'll always use an object here in case we've now made
// different elements have different types. Our ultimate goal is to
// marshal to JSON anyway, and all of these mapping types are
// indistinguishable in JSON.
return cty.ObjectVal(vals)
default:
// Should never happen, since the above should cover all types
panic(fmt.Sprintf("sensitiveAsBool cannot handle %#v", val))
}
}

func actionString(action string) []string {
switch {
case action == "NoOp":
Expand Down
Loading

0 comments on commit 340543d

Please sign in to comment.