Skip to content

Commit 80bd093

Browse files
mildwonkeyalisdair
authored andcommitted
jsonplan and jsonstate: include sensitive_values in state representations (#28889)
* jsonplan and jsonstate: include sensitive_values in state representations A sensitive_values field has been added to the resource in state and planned values which is a map of all sensitive attributes with the values set to true. It wasn't entirely clear to me if the values in state would suffice, or if we also need to consult the schema - I believe that this is sufficient for state files written since v0.15, and if that's incorrect or insufficient, I'll add in the provider schema check as well. I also updated the documentation, and, since we've considered this before, bumped the FormatVersions for both jsonstate and jsonplan.
1 parent 0a3968d commit 80bd093

File tree

25 files changed

+497
-378
lines changed

25 files changed

+497
-378
lines changed

internal/command/jsonplan/plan.go

Lines changed: 5 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
// FormatVersion represents the version of the json format and will be
2323
// incremented for any change to this format that requires changes to a
2424
// consuming parser.
25-
const FormatVersion = "0.1"
25+
const FormatVersion = "0.2"
2626

2727
// Plan is the top-level representation of the json format of a plan. It includes
2828
// the complete config and current state.
@@ -259,8 +259,8 @@ func (p *plan) marshalResourceDrift(oldState, newState *states.State, schemas *t
259259
} else {
260260
newVal = cty.NullVal(ty)
261261
}
262-
oldSensitive := sensitiveAsBool(oldVal)
263-
newSensitive := sensitiveAsBool(newVal)
262+
oldSensitive := jsonstate.SensitiveAsBool(oldVal)
263+
newSensitive := jsonstate.SensitiveAsBool(newVal)
264264
oldVal, _ = oldVal.UnmarkDeep()
265265
newVal, _ = newVal.UnmarkDeep()
266266

@@ -367,7 +367,7 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform
367367
if schema.ContainsSensitive() {
368368
marks = append(marks, schema.ValueMarks(changeV.Before, nil)...)
369369
}
370-
bs := sensitiveAsBool(changeV.Before.MarkWithPaths(marks))
370+
bs := jsonstate.SensitiveAsBool(changeV.Before.MarkWithPaths(marks))
371371
beforeSensitive, err = ctyjson.Marshal(bs, bs.Type())
372372
if err != nil {
373373
return err
@@ -396,7 +396,7 @@ func (p *plan) marshalResourceChanges(changes *plans.Changes, schemas *terraform
396396
if schema.ContainsSensitive() {
397397
marks = append(marks, schema.ValueMarks(changeV.After, nil)...)
398398
}
399-
as := sensitiveAsBool(changeV.After.MarkWithPaths(marks))
399+
as := jsonstate.SensitiveAsBool(changeV.After.MarkWithPaths(marks))
400400
afterSensitive, err = ctyjson.Marshal(as, as.Type())
401401
if err != nil {
402402
return err
@@ -682,88 +682,6 @@ func unknownAsBool(val cty.Value) cty.Value {
682682
}
683683
}
684684

685-
// recursively iterate through a marked cty.Value, replacing sensitive values
686-
// with cty.True and non-sensitive values with cty.False.
687-
//
688-
// The result also normalizes some types: all sequence types are turned into
689-
// tuple types and all mapping types are converted to object types, since we
690-
// assume the result of this is just going to be serialized as JSON (and thus
691-
// lose those distinctions) anyway.
692-
//
693-
// For map/object values, all non-sensitive attribute values will be omitted
694-
// instead of returning false, as this results in a more compact serialization.
695-
func sensitiveAsBool(val cty.Value) cty.Value {
696-
if val.HasMark("sensitive") {
697-
return cty.True
698-
}
699-
700-
ty := val.Type()
701-
switch {
702-
case val.IsNull(), ty.IsPrimitiveType(), ty.Equals(cty.DynamicPseudoType):
703-
return cty.False
704-
case ty.IsListType() || ty.IsTupleType() || ty.IsSetType():
705-
if !val.IsKnown() {
706-
// If the collection is unknown we can't say anything about the
707-
// sensitivity of its contents
708-
return cty.EmptyTupleVal
709-
}
710-
length := val.LengthInt()
711-
if length == 0 {
712-
// If there are no elements then we can't have sensitive values
713-
return cty.EmptyTupleVal
714-
}
715-
vals := make([]cty.Value, 0, length)
716-
it := val.ElementIterator()
717-
for it.Next() {
718-
_, v := it.Element()
719-
vals = append(vals, sensitiveAsBool(v))
720-
}
721-
// The above transform may have changed the types of some of the
722-
// elements, so we'll always use a tuple here in case we've now made
723-
// different elements have different types. Our ultimate goal is to
724-
// marshal to JSON anyway, and all of these sequence types are
725-
// indistinguishable in JSON.
726-
return cty.TupleVal(vals)
727-
case ty.IsMapType() || ty.IsObjectType():
728-
if !val.IsKnown() {
729-
// If the map/object is unknown we can't say anything about the
730-
// sensitivity of its attributes
731-
return cty.EmptyObjectVal
732-
}
733-
var length int
734-
switch {
735-
case ty.IsMapType():
736-
length = val.LengthInt()
737-
default:
738-
length = len(val.Type().AttributeTypes())
739-
}
740-
if length == 0 {
741-
// If there are no elements then we can't have sensitive values
742-
return cty.EmptyObjectVal
743-
}
744-
vals := make(map[string]cty.Value)
745-
it := val.ElementIterator()
746-
for it.Next() {
747-
k, v := it.Element()
748-
s := sensitiveAsBool(v)
749-
// Omit all of the "false"s for non-sensitive values for more
750-
// compact serialization
751-
if !s.RawEquals(cty.False) {
752-
vals[k.AsString()] = s
753-
}
754-
}
755-
// The above transform may have changed the types of some of the
756-
// elements, so we'll always use an object here in case we've now made
757-
// different elements have different types. Our ultimate goal is to
758-
// marshal to JSON anyway, and all of these mapping types are
759-
// indistinguishable in JSON.
760-
return cty.ObjectVal(vals)
761-
default:
762-
// Should never happen, since the above should cover all types
763-
panic(fmt.Sprintf("sensitiveAsBool cannot handle %#v", val))
764-
}
765-
}
766-
767685
func actionString(action string) []string {
768686
switch {
769687
case action == "NoOp":

internal/command/jsonplan/plan_test.go

Lines changed: 0 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -265,219 +265,6 @@ func TestUnknownAsBool(t *testing.T) {
265265
}
266266
}
267267

268-
func TestSensitiveAsBool(t *testing.T) {
269-
sensitive := "sensitive"
270-
tests := []struct {
271-
Input cty.Value
272-
Want cty.Value
273-
}{
274-
{
275-
cty.StringVal("hello"),
276-
cty.False,
277-
},
278-
{
279-
cty.NullVal(cty.String),
280-
cty.False,
281-
},
282-
{
283-
cty.StringVal("hello").Mark(sensitive),
284-
cty.True,
285-
},
286-
{
287-
cty.NullVal(cty.String).Mark(sensitive),
288-
cty.True,
289-
},
290-
291-
{
292-
cty.NullVal(cty.DynamicPseudoType).Mark(sensitive),
293-
cty.True,
294-
},
295-
{
296-
cty.NullVal(cty.Object(map[string]cty.Type{"test": cty.String})),
297-
cty.False,
298-
},
299-
{
300-
cty.NullVal(cty.Object(map[string]cty.Type{"test": cty.String})).Mark(sensitive),
301-
cty.True,
302-
},
303-
{
304-
cty.DynamicVal,
305-
cty.False,
306-
},
307-
{
308-
cty.DynamicVal.Mark(sensitive),
309-
cty.True,
310-
},
311-
312-
{
313-
cty.ListValEmpty(cty.String),
314-
cty.EmptyTupleVal,
315-
},
316-
{
317-
cty.ListValEmpty(cty.String).Mark(sensitive),
318-
cty.True,
319-
},
320-
{
321-
cty.ListVal([]cty.Value{
322-
cty.StringVal("hello"),
323-
cty.StringVal("friend").Mark(sensitive),
324-
}),
325-
cty.TupleVal([]cty.Value{
326-
cty.False,
327-
cty.True,
328-
}),
329-
},
330-
{
331-
cty.SetValEmpty(cty.String),
332-
cty.EmptyTupleVal,
333-
},
334-
{
335-
cty.SetValEmpty(cty.String).Mark(sensitive),
336-
cty.True,
337-
},
338-
{
339-
cty.SetVal([]cty.Value{cty.StringVal("hello")}),
340-
cty.TupleVal([]cty.Value{cty.False}),
341-
},
342-
{
343-
cty.SetVal([]cty.Value{cty.StringVal("hello").Mark(sensitive)}),
344-
cty.True,
345-
},
346-
{
347-
cty.EmptyTupleVal.Mark(sensitive),
348-
cty.True,
349-
},
350-
{
351-
cty.TupleVal([]cty.Value{
352-
cty.StringVal("hello"),
353-
cty.StringVal("friend").Mark(sensitive),
354-
}),
355-
cty.TupleVal([]cty.Value{
356-
cty.False,
357-
cty.True,
358-
}),
359-
},
360-
{
361-
cty.MapValEmpty(cty.String),
362-
cty.EmptyObjectVal,
363-
},
364-
{
365-
cty.MapValEmpty(cty.String).Mark(sensitive),
366-
cty.True,
367-
},
368-
{
369-
cty.MapVal(map[string]cty.Value{
370-
"greeting": cty.StringVal("hello"),
371-
"animal": cty.StringVal("horse"),
372-
}),
373-
cty.EmptyObjectVal,
374-
},
375-
{
376-
cty.MapVal(map[string]cty.Value{
377-
"greeting": cty.StringVal("hello"),
378-
"animal": cty.StringVal("horse").Mark(sensitive),
379-
}),
380-
cty.ObjectVal(map[string]cty.Value{
381-
"animal": cty.True,
382-
}),
383-
},
384-
{
385-
cty.MapVal(map[string]cty.Value{
386-
"greeting": cty.StringVal("hello"),
387-
"animal": cty.StringVal("horse").Mark(sensitive),
388-
}).Mark(sensitive),
389-
cty.True,
390-
},
391-
{
392-
cty.EmptyObjectVal,
393-
cty.EmptyObjectVal,
394-
},
395-
{
396-
cty.ObjectVal(map[string]cty.Value{
397-
"greeting": cty.StringVal("hello"),
398-
"animal": cty.StringVal("horse"),
399-
}),
400-
cty.EmptyObjectVal,
401-
},
402-
{
403-
cty.ObjectVal(map[string]cty.Value{
404-
"greeting": cty.StringVal("hello"),
405-
"animal": cty.StringVal("horse").Mark(sensitive),
406-
}),
407-
cty.ObjectVal(map[string]cty.Value{
408-
"animal": cty.True,
409-
}),
410-
},
411-
{
412-
cty.ObjectVal(map[string]cty.Value{
413-
"greeting": cty.StringVal("hello"),
414-
"animal": cty.StringVal("horse").Mark(sensitive),
415-
}).Mark(sensitive),
416-
cty.True,
417-
},
418-
{
419-
cty.ListVal([]cty.Value{
420-
cty.ObjectVal(map[string]cty.Value{
421-
"a": cty.UnknownVal(cty.String),
422-
}),
423-
cty.ObjectVal(map[string]cty.Value{
424-
"a": cty.StringVal("known").Mark(sensitive),
425-
}),
426-
}),
427-
cty.TupleVal([]cty.Value{
428-
cty.EmptyObjectVal,
429-
cty.ObjectVal(map[string]cty.Value{
430-
"a": cty.True,
431-
}),
432-
}),
433-
},
434-
{
435-
cty.ListVal([]cty.Value{
436-
cty.MapValEmpty(cty.String),
437-
cty.MapVal(map[string]cty.Value{
438-
"a": cty.StringVal("known").Mark(sensitive),
439-
}),
440-
cty.MapVal(map[string]cty.Value{
441-
"a": cty.UnknownVal(cty.String),
442-
}),
443-
}),
444-
cty.TupleVal([]cty.Value{
445-
cty.EmptyObjectVal,
446-
cty.ObjectVal(map[string]cty.Value{
447-
"a": cty.True,
448-
}),
449-
cty.EmptyObjectVal,
450-
}),
451-
},
452-
{
453-
cty.ObjectVal(map[string]cty.Value{
454-
"list": cty.UnknownVal(cty.List(cty.String)),
455-
"set": cty.UnknownVal(cty.Set(cty.Bool)),
456-
"tuple": cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.Number})),
457-
"map": cty.UnknownVal(cty.Map(cty.String)),
458-
"object": cty.UnknownVal(cty.Object(map[string]cty.Type{"a": cty.String})),
459-
}),
460-
cty.ObjectVal(map[string]cty.Value{
461-
"list": cty.EmptyTupleVal,
462-
"set": cty.EmptyTupleVal,
463-
"tuple": cty.EmptyTupleVal,
464-
"map": cty.EmptyObjectVal,
465-
"object": cty.EmptyObjectVal,
466-
}),
467-
},
468-
}
469-
470-
for _, test := range tests {
471-
got := sensitiveAsBool(test.Input)
472-
if !reflect.DeepEqual(got, test.Want) {
473-
t.Errorf(
474-
"wrong result\ninput: %#v\ngot: %#v\nwant: %#v",
475-
test.Input, got, test.Want,
476-
)
477-
}
478-
}
479-
}
480-
481268
func TestEncodePaths(t *testing.T) {
482269
tests := map[string]struct {
483270
Input cty.PathSet

internal/command/jsonplan/resource.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package jsonplan
22

33
import (
4+
"encoding/json"
5+
46
"github.com/hashicorp/terraform/internal/addrs"
57
)
68

@@ -33,6 +35,10 @@ type resource struct {
3335
// unknown values are omitted or set to null, making them indistinguishable
3436
// from absent values.
3537
AttributeValues attributeValues `json:"values,omitempty"`
38+
39+
// SensitiveValues is similar to AttributeValues, but with all sensitive
40+
// values replaced with true, and all non-sensitive leaf values omitted.
41+
SensitiveValues json.RawMessage `json:"sensitive_values,omitempty"`
3642
}
3743

3844
// resourceChange is a description of an individual change action that Terraform

0 commit comments

Comments
 (0)