Skip to content

Commit 7b6375c

Browse files
authored
Reflect upcoming v1.1 plan/state format changes (#37)
1 parent 990dae7 commit 7b6375c

File tree

14 files changed

+428
-55
lines changed

14 files changed

+428
-55
lines changed

.go-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.16

parse_test.go

Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@ package tfjson
33
import (
44
"bytes"
55
"encoding/json"
6-
"errors"
7-
"fmt"
86
"io/ioutil"
7+
"os"
98
"path/filepath"
109
"reflect"
11-
"strings"
1210
"testing"
11+
12+
"github.com/google/go-cmp/cmp"
1313
)
1414

1515
const testFixtureDir = "testdata"
1616
const testGoldenPlanFileName = "plan.json"
17+
const testGoldenStateFileName = "state.json"
1718
const testGoldenSchemasFileName = "schemas.json"
1819

1920
func testParse(t *testing.T, filename string, typ reflect.Type) {
@@ -30,6 +31,9 @@ func testParse(t *testing.T, filename string, typ reflect.Type) {
3031
t.Run(e.Name(), func(t *testing.T) {
3132
expected, err := ioutil.ReadFile(filepath.Join(testFixtureDir, e.Name(), filename))
3233
if err != nil {
34+
if os.IsNotExist(err) {
35+
t.Skip(err.Error())
36+
}
3337
t.Fatal(err)
3438
}
3539

@@ -48,8 +52,8 @@ func testParse(t *testing.T, filename string, typ reflect.Type) {
4852
// Add a newline at the end
4953
actual = append(actual, byte('\n'))
5054

51-
if err := testDiff(actual, expected); err != nil {
52-
t.Fatal(err)
55+
if diff := cmp.Diff(expected, actual); diff != "" {
56+
t.Fatalf("unexpected: %s", diff)
5357
}
5458
})
5559
}
@@ -63,35 +67,8 @@ func TestParseSchemas(t *testing.T) {
6367
testParse(t, testGoldenSchemasFileName, reflect.TypeOf(ProviderSchemas{}))
6468
}
6569

66-
func testDiff(out, gld []byte) error {
67-
var b strings.Builder // holding long error message
68-
69-
// compare lengths
70-
if len(out) != len(gld) {
71-
fmt.Fprintf(&b, "\nlength changed: len(output) = %d, len(golden) = %d", len(out), len(gld))
72-
}
73-
74-
// compare contents
75-
line := 1
76-
offs := 1
77-
for i := 0; i < len(out) && i < len(gld); i++ {
78-
ch := out[i]
79-
if ch != gld[i] {
80-
fmt.Fprintf(&b, "\noutput:%d:%d: %s", line, i-offs+1, lineAt(out, offs))
81-
fmt.Fprintf(&b, "\ngolden:%d:%d: %s", line, i-offs+1, lineAt(gld, offs))
82-
fmt.Fprintf(&b, "\n\n")
83-
break
84-
}
85-
if ch == '\n' {
86-
line++
87-
offs = i + 1
88-
}
89-
}
90-
91-
if b.Len() > 0 {
92-
return errors.New(b.String())
93-
}
94-
return nil
70+
func TestParseState(t *testing.T) {
71+
testParse(t, testGoldenStateFileName, reflect.TypeOf(State{}))
9572
}
9673

9774
func lineAt(text []byte, offs int) []byte {

plan.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import (
66
"fmt"
77
)
88

9-
// PlanFormatVersion is the version of the JSON plan format that is
10-
// supported by this package.
11-
const PlanFormatVersion = "0.1"
9+
// PlanFormatVersions represents versions of the JSON plan format that
10+
// are supported by this package.
11+
var PlanFormatVersions = []string{"0.1", "0.2"}
1212

1313
// ResourceMode is a string representation of the resource type found
1414
// in certain fields in the plan.
@@ -66,13 +66,23 @@ func (p *Plan) Validate() error {
6666
return errors.New("unexpected plan input, format version is missing")
6767
}
6868

69-
if PlanFormatVersion != p.FormatVersion {
70-
return fmt.Errorf("unsupported plan format version: expected %q, got %q", PlanFormatVersion, p.FormatVersion)
69+
if !isStringInSlice(PlanFormatVersions, p.FormatVersion) {
70+
return fmt.Errorf("unsupported plan format version: expected %q, got %q",
71+
PlanFormatVersions, p.FormatVersion)
7172
}
7273

7374
return nil
7475
}
7576

77+
func isStringInSlice(slice []string, s string) bool {
78+
for _, el := range slice {
79+
if el == s {
80+
return true
81+
}
82+
}
83+
return false
84+
}
85+
7686
func (p *Plan) UnmarshalJSON(b []byte) error {
7787
type rawPlan Plan
7888
var plan rawPlan

schemas.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ import (
88
"github.com/zclconf/go-cty/cty"
99
)
1010

11-
// ProviderSchemasFormatVersion is the version of the JSON provider
12-
// schema format that is supported by this package.
13-
const ProviderSchemasFormatVersion = "0.2"
11+
// ProviderSchemasFormatVersions represents the versions of
12+
// the JSON provider schema format that are supported by this package.
13+
var ProviderSchemasFormatVersions = []string{"0.1", "0.2"}
1414

1515
// ProviderSchemas represents the schemas of all providers and
1616
// resources in use by the configuration.
1717
type ProviderSchemas struct {
18-
// The version of the plan format. This should always match the
19-
// ProviderSchemasFormatVersion constant in this package, or else
18+
// The version of the plan format. This should always match one of
19+
// ProviderSchemasFormatVersions in this package, or else
2020
// an unmarshal will be unstable.
2121
FormatVersion string `json:"format_version,omitempty"`
2222

@@ -38,10 +38,9 @@ func (p *ProviderSchemas) Validate() error {
3838
return errors.New("unexpected provider schema data, format version is missing")
3939
}
4040

41-
oldVersion := "0.1"
42-
if p.FormatVersion != ProviderSchemasFormatVersion && p.FormatVersion != oldVersion {
43-
return fmt.Errorf("unsupported provider schema data format version: expected %q or %q, got %q",
44-
PlanFormatVersion, oldVersion, p.FormatVersion)
41+
if !isStringInSlice(ProviderSchemasFormatVersions, p.FormatVersion) {
42+
return fmt.Errorf("unsupported provider schema data format version: expected %q, got %q",
43+
ProviderSchemasFormatVersions, p.FormatVersion)
4544
}
4645

4746
return nil

state.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import (
77
"fmt"
88
)
99

10-
// StateFormatVersion is the version of the JSON state format that is
11-
// supported by this package.
12-
const StateFormatVersion = "0.1"
10+
// StateFormatVersions represents the versions of the JSON state format
11+
// that are supported by this package.
12+
var StateFormatVersions = []string{"0.1", "0.2"}
1313

1414
// State is the top-level representation of a Terraform state.
1515
type State struct {
@@ -50,8 +50,9 @@ func (s *State) Validate() error {
5050
return errors.New("unexpected state input, format version is missing")
5151
}
5252

53-
if StateFormatVersion != s.FormatVersion {
54-
return fmt.Errorf("unsupported state format version: expected %q, got %q", StateFormatVersion, s.FormatVersion)
53+
if !isStringInSlice(StateFormatVersions, s.FormatVersion) {
54+
return fmt.Errorf("unsupported state format version: expected %q, got %q",
55+
StateFormatVersions, s.FormatVersion)
5556
}
5657

5758
return nil
@@ -127,8 +128,8 @@ type StateResource struct {
127128
// provider offering "google_compute_instance".
128129
ProviderName string `json:"provider_name,omitempty"`
129130

130-
// The version of the resource type schema the "values" property
131-
// conforms to.
131+
// The version of the resource type schema the "values" property
132+
// conforms to.
132133
SchemaVersion uint64 `json:"schema_version,"`
133134

134135
// The JSON representation of the attribute values of the resource,
@@ -137,6 +138,11 @@ type StateResource struct {
137138
// from absent values.
138139
AttributeValues map[string]interface{} `json:"values,omitempty"`
139140

141+
// The JSON representation of the sensitivity of the resource's
142+
// attribute values. Only attributes which are sensitive
143+
// are included in this structure.
144+
SensitiveValues json.RawMessage `json:"sensitive_values,omitempty"`
145+
140146
// The addresses of the resources that this resource depends on.
141147
DependsOn []string `json:"depends_on,omitempty"`
142148

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
variable "bar" {
2+
type = string
3+
}
4+
5+
variable "one" {
6+
type = string
7+
}
8+
9+
terraform {
10+
required_providers {
11+
null = {
12+
source = "hashicorp/null"
13+
configuration_aliases = [null.aliased]
14+
}
15+
}
16+
}
17+
18+
resource "null_resource" "foo" {
19+
triggers = {
20+
foo = "bar"
21+
}
22+
}
23+
24+
resource "null_resource" "aliased" {
25+
provider = null.aliased
26+
}
27+
28+
output "foo" {
29+
value = "bar"
30+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module "foo" {
2+
source = "./foo"
3+
4+
bar = "baz"
5+
one = "two"
6+
7+
providers = {
8+
null.aliased = null
9+
}
10+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
output "foo" {
2+
sensitive = true
3+
value = "bar"
4+
}
5+
6+
output "string" {
7+
value = "foo"
8+
}
9+
10+
output "list" {
11+
value = [
12+
"foo",
13+
"bar",
14+
]
15+
}
16+
17+
output "map" {
18+
value = {
19+
foo = "bar"
20+
number = 42
21+
}
22+
}
23+
24+
output "referenced" {
25+
value = null_resource.foo.id
26+
}
27+
28+
output "interpolated" {
29+
value = "${null_resource.foo.id}"
30+
}
31+
32+
output "referenced_deep" {
33+
value = {
34+
foo = "bar"
35+
number = 42
36+
map = {
37+
bar = "baz"
38+
id = null_resource.foo.id
39+
}
40+
}
41+
}
42+
43+
output "interpolated_deep" {
44+
value = {
45+
foo = "bar"
46+
number = 42
47+
map = {
48+
bar = "baz"
49+
id = "${null_resource.foo.id}"
50+
}
51+
}
52+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"format_version":"0.2","terraform_version":"1.1.0-dev","variables":{"foo":{"value":"bar"},"map":{"value":{"foo":"bar","number":42}},"number":{"value":42}},"planned_values":{"outputs":{"foo":{"sensitive":true,"value":"bar"},"interpolated":{"sensitive":false},"interpolated_deep":{"sensitive":false},"list":{"sensitive":false,"value":["foo","bar"]},"map":{"sensitive":false,"value":{"foo":"bar","number":42}},"referenced":{"sensitive":false},"referenced_deep":{"sensitive":false},"string":{"sensitive":false,"value":"foo"}},"root_module":{"resources":[{"address":"null_resource.bar","mode":"managed","type":"null_resource","name":"bar","provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"sensitive_values":{"triggers":{}}},{"address":"null_resource.baz[0]","mode":"managed","type":"null_resource","name":"baz","index":0,"provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"sensitive_values":{"triggers":{}}},{"address":"null_resource.baz[1]","mode":"managed","type":"null_resource","name":"baz","index":1,"provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"sensitive_values":{"triggers":{}}},{"address":"null_resource.baz[2]","mode":"managed","type":"null_resource","name":"baz","index":2,"provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"sensitive_values":{"triggers":{}}},{"address":"null_resource.foo","mode":"managed","type":"null_resource","name":"foo","provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"values":{"triggers":{"foo":"bar"}},"sensitive_values":{"triggers":{}}}],"child_modules":[{"resources":[{"address":"module.foo.null_resource.aliased","mode":"managed","type":"null_resource","name":"aliased","provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"values":{"triggers":null},"sensitive_values":{}},{"address":"module.foo.null_resource.foo","mode":"managed","type":"null_resource","name":"foo","provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"values":{"triggers":{"foo":"bar"}},"sensitive_values":{"triggers":{}}}],"address":"module.foo"}]}},"resource_changes":[{"address":"module.foo.null_resource.aliased","module_address":"module.foo","mode":"managed","type":"null_resource","name":"aliased","provider_name":"registry.terraform.io/hashicorp/null","change":{"actions":["create"],"before":null,"after":{"triggers":null},"after_unknown":{"id":true},"before_sensitive":false,"after_sensitive":{}}},{"address":"module.foo.null_resource.foo","module_address":"module.foo","mode":"managed","type":"null_resource","name":"foo","provider_name":"registry.terraform.io/hashicorp/null","change":{"actions":["create"],"before":null,"after":{"triggers":{"foo":"bar"}},"after_unknown":{"id":true,"triggers":{}},"before_sensitive":false,"after_sensitive":{"triggers":{}}}},{"address":"null_resource.bar","mode":"managed","type":"null_resource","name":"bar","provider_name":"registry.terraform.io/hashicorp/null","change":{"actions":["create"],"before":null,"after":{},"after_unknown":{"id":true,"triggers":true},"before_sensitive":false,"after_sensitive":{"triggers":{}}}},{"address":"null_resource.baz[0]","mode":"managed","type":"null_resource","name":"baz","index":0,"provider_name":"registry.terraform.io/hashicorp/null","change":{"actions":["create"],"before":null,"after":{},"after_unknown":{"id":true,"triggers":true},"before_sensitive":false,"after_sensitive":{"triggers":{}}}},{"address":"null_resource.baz[1]","mode":"managed","type":"null_resource","name":"baz","index":1,"provider_name":"registry.terraform.io/hashicorp/null","change":{"actions":["create"],"before":null,"after":{},"after_unknown":{"id":true,"triggers":true},"before_sensitive":false,"after_sensitive":{"triggers":{}}}},{"address":"null_resource.baz[2]","mode":"managed","type":"null_resource","name":"baz","index":2,"provider_name":"registry.terraform.io/hashicorp/null","change":{"actions":["create"],"before":null,"after":{},"after_unknown":{"id":true,"triggers":true},"before_sensitive":false,"after_sensitive":{"triggers":{}}}},{"address":"null_resource.foo","mode":"managed","type":"null_resource","name":"foo","provider_name":"registry.terraform.io/hashicorp/null","change":{"actions":["create"],"before":null,"after":{"triggers":{"foo":"bar"}},"after_unknown":{"id":true,"triggers":{}},"before_sensitive":false,"after_sensitive":{"triggers":{}}}}],"output_changes":{"foo":{"actions":["create"],"before":null,"after":"bar","after_unknown":false,"before_sensitive":true,"after_sensitive":true},"interpolated":{"actions":["create"],"before":null,"after_unknown":true,"before_sensitive":false,"after_sensitive":false},"interpolated_deep":{"actions":["create"],"before":null,"after_unknown":true,"before_sensitive":false,"after_sensitive":false},"list":{"actions":["create"],"before":null,"after":["foo","bar"],"after_unknown":false,"before_sensitive":false,"after_sensitive":false},"map":{"actions":["create"],"before":null,"after":{"foo":"bar","number":42},"after_unknown":false,"before_sensitive":false,"after_sensitive":false},"referenced":{"actions":["create"],"before":null,"after_unknown":true,"before_sensitive":false,"after_sensitive":false},"referenced_deep":{"actions":["create"],"before":null,"after_unknown":true,"before_sensitive":false,"after_sensitive":false},"string":{"actions":["create"],"before":null,"after":"foo","after_unknown":false,"before_sensitive":false,"after_sensitive":false}},"prior_state":{"format_version":"0.2","terraform_version":"1.1.0","values":{"outputs":{"foo":{"sensitive":true,"value":"bar"},"list":{"sensitive":false,"value":["foo","bar"]},"map":{"sensitive":false,"value":{"foo":"bar","number":42}},"string":{"sensitive":false,"value":"foo"}},"root_module":{}}},"configuration":{"provider_config":{"aws":{"name":"aws","expressions":{"region":{"constant_value":"us-west-2"}}},"aws.east":{"name":"aws","alias":"east","expressions":{"region":{"constant_value":"us-east-1"}}},"module.foo:null":{"name":"null","module_address":"module.foo"},"null":{"name":"null"}},"root_module":{"outputs":{"foo":{"sensitive":true,"expression":{"constant_value":"bar"}},"interpolated":{"expression":{"references":["null_resource.foo.id","null_resource.foo"]}},"interpolated_deep":{"expression":{"references":["null_resource.foo.id","null_resource.foo"]}},"list":{"expression":{"constant_value":["foo","bar"]}},"map":{"expression":{"constant_value":{"foo":"bar","number":42}}},"referenced":{"expression":{"references":["null_resource.foo.id","null_resource.foo"]}},"referenced_deep":{"expression":{"references":["null_resource.foo.id","null_resource.foo"]}},"string":{"expression":{"constant_value":"foo"}}},"resources":[{"address":"null_resource.bar","mode":"managed","type":"null_resource","name":"bar","provider_config_key":"null","expressions":{"triggers":{"references":["null_resource.foo.id","null_resource.foo"]}},"schema_version":0},{"address":"null_resource.baz","mode":"managed","type":"null_resource","name":"baz","provider_config_key":"null","expressions":{"triggers":{"references":["null_resource.foo.id","null_resource.foo"]}},"schema_version":0,"count_expression":{"constant_value":3}},{"address":"null_resource.foo","mode":"managed","type":"null_resource","name":"foo","provider_config_key":"null","provisioners":[{"type":"local-exec","expressions":{"command":{"constant_value":"echo hello"}}}],"expressions":{"triggers":{"constant_value":{"foo":"bar"}}},"schema_version":0}],"module_calls":{"foo":{"source":"./foo","expressions":{"bar":{"constant_value":"baz"},"one":{"constant_value":"two"}},"module":{"outputs":{"foo":{"expression":{"constant_value":"bar"}}},"resources":[{"address":"null_resource.aliased","mode":"managed","type":"null_resource","name":"aliased","provider_config_key":"foo:null.aliased","schema_version":0},{"address":"null_resource.foo","mode":"managed","type":"null_resource","name":"foo","provider_config_key":"foo:null","expressions":{"triggers":{"constant_value":{"foo":"bar"}}},"schema_version":0}],"variables":{"bar":{},"one":{}}}}},"variables":{"foo":{"default":"bar","description":"foobar"},"map":{"default":{"foo":"bar","number":42}},"number":{"default":42}}}}}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
terraform {
2+
required_providers {
3+
null = {
4+
source = "hashicorp/null"
5+
}
6+
}
7+
}
8+
provider "aws" {
9+
region = "us-west-2"
10+
}
11+
12+
provider "aws" {
13+
alias = "east"
14+
region = "us-east-1"
15+
}

0 commit comments

Comments
 (0)