Skip to content

Commit

Permalink
Reflect upcoming v1.1 plan/state format changes
Browse files Browse the repository at this point in the history
  • Loading branch information
radeksimko committed Jun 17, 2021
1 parent 990dae7 commit be0ac34
Show file tree
Hide file tree
Showing 14 changed files with 428 additions and 55 deletions.
1 change: 1 addition & 0 deletions .go-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.16
45 changes: 11 additions & 34 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ package tfjson
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
)

const testFixtureDir = "testdata"
const testGoldenPlanFileName = "plan.json"
const testGoldenStateFileName = "state.json"
const testGoldenSchemasFileName = "schemas.json"

func testParse(t *testing.T, filename string, typ reflect.Type) {
Expand All @@ -30,6 +31,9 @@ func testParse(t *testing.T, filename string, typ reflect.Type) {
t.Run(e.Name(), func(t *testing.T) {
expected, err := ioutil.ReadFile(filepath.Join(testFixtureDir, e.Name(), filename))
if err != nil {
if os.IsNotExist(err) {
t.Skip(err.Error())
}
t.Fatal(err)
}

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

if err := testDiff(actual, expected); err != nil {
t.Fatal(err)
if diff := cmp.Diff(expected, actual); diff != "" {
t.Fatalf("unexpected: %s", diff)
}
})
}
Expand All @@ -63,35 +67,8 @@ func TestParseSchemas(t *testing.T) {
testParse(t, testGoldenSchemasFileName, reflect.TypeOf(ProviderSchemas{}))
}

func testDiff(out, gld []byte) error {
var b strings.Builder // holding long error message

// compare lengths
if len(out) != len(gld) {
fmt.Fprintf(&b, "\nlength changed: len(output) = %d, len(golden) = %d", len(out), len(gld))
}

// compare contents
line := 1
offs := 1
for i := 0; i < len(out) && i < len(gld); i++ {
ch := out[i]
if ch != gld[i] {
fmt.Fprintf(&b, "\noutput:%d:%d: %s", line, i-offs+1, lineAt(out, offs))
fmt.Fprintf(&b, "\ngolden:%d:%d: %s", line, i-offs+1, lineAt(gld, offs))
fmt.Fprintf(&b, "\n\n")
break
}
if ch == '\n' {
line++
offs = i + 1
}
}

if b.Len() > 0 {
return errors.New(b.String())
}
return nil
func TestParseState(t *testing.T) {
testParse(t, testGoldenStateFileName, reflect.TypeOf(State{}))
}

func lineAt(text []byte, offs int) []byte {
Expand Down
20 changes: 15 additions & 5 deletions plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (
"fmt"
)

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

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

if PlanFormatVersion != p.FormatVersion {
return fmt.Errorf("unsupported plan format version: expected %q, got %q", PlanFormatVersion, p.FormatVersion)
if !isStringInSlice(PlanFormatVersions, p.FormatVersion) {
return fmt.Errorf("unsupported plan format version: expected %q, got %q",
PlanFormatVersions, p.FormatVersion)
}

return nil
}

func isStringInSlice(slice []string, s string) bool {
for _, el := range slice {
if el == s {
return true
}
}
return false
}

func (p *Plan) UnmarshalJSON(b []byte) error {
type rawPlan Plan
var plan rawPlan
Expand Down
17 changes: 8 additions & 9 deletions schemas.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import (
"github.com/zclconf/go-cty/cty"
)

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

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

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

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

return nil
Expand Down
20 changes: 13 additions & 7 deletions state.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (
"fmt"
)

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

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

if StateFormatVersion != s.FormatVersion {
return fmt.Errorf("unsupported state format version: expected %q, got %q", StateFormatVersion, s.FormatVersion)
if !isStringInSlice(StateFormatVersions, s.FormatVersion) {
return fmt.Errorf("unsupported state format version: expected %q, got %q",
StateFormatVersions, s.FormatVersion)
}

return nil
Expand Down Expand Up @@ -127,8 +128,8 @@ type StateResource struct {
// provider offering "google_compute_instance".
ProviderName string `json:"provider_name,omitempty"`

// The version of the resource type schema the "values" property
// conforms to.
// The version of the resource type schema the "values" property
// conforms to.
SchemaVersion uint64 `json:"schema_version,"`

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

// The JSON representation of the sensitivity of the resource's
// attribute values. Only attributes which are sensitive
// are included in this structure.
SensitiveValues json.RawMessage `json:"sensitive_values,omitempty"`

// The addresses of the resources that this resource depends on.
DependsOn []string `json:"depends_on,omitempty"`

Expand Down
30 changes: 30 additions & 0 deletions testdata/110_sensitive_values/foo/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
variable "bar" {
type = string
}

variable "one" {
type = string
}

terraform {
required_providers {
null = {
source = "hashicorp/null"
configuration_aliases = [null.aliased]
}
}
}

resource "null_resource" "foo" {
triggers = {
foo = "bar"
}
}

resource "null_resource" "aliased" {
provider = null.aliased
}

output "foo" {
value = "bar"
}
10 changes: 10 additions & 0 deletions testdata/110_sensitive_values/module.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module "foo" {
source = "./foo"

bar = "baz"
one = "two"

providers = {
null.aliased = null
}
}
52 changes: 52 additions & 0 deletions testdata/110_sensitive_values/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
output "foo" {
sensitive = true
value = "bar"
}

output "string" {
value = "foo"
}

output "list" {
value = [
"foo",
"bar",
]
}

output "map" {
value = {
foo = "bar"
number = 42
}
}

output "referenced" {
value = null_resource.foo.id
}

output "interpolated" {
value = "${null_resource.foo.id}"
}

output "referenced_deep" {
value = {
foo = "bar"
number = 42
map = {
bar = "baz"
id = null_resource.foo.id
}
}
}

output "interpolated_deep" {
value = {
foo = "bar"
number = 42
map = {
bar = "baz"
id = "${null_resource.foo.id}"
}
}
}
1 change: 1 addition & 0 deletions testdata/110_sensitive_values/plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +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}}}}}
15 changes: 15 additions & 0 deletions testdata/110_sensitive_values/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
terraform {
required_providers {
null = {
source = "hashicorp/null"
}
}
}
provider "aws" {
region = "us-west-2"
}

provider "aws" {
alias = "east"
region = "us-east-1"
}
23 changes: 23 additions & 0 deletions testdata/110_sensitive_values/resources.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
resource "null_resource" "foo" {
triggers = {
foo = "bar"
}

provisioner "local-exec" {
command = "echo hello"
}
}

resource "null_resource" "bar" {
triggers = {
foo_id = "${null_resource.foo.id}"
}
}

resource "null_resource" "baz" {
count = 3

triggers = {
foo_id = "${null_resource.foo.id}"
}
}
1 change: 1 addition & 0 deletions testdata/110_sensitive_values/state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"format_version":"0.2","terraform_version":"1.1.0","values":{"outputs":{"foo":{"sensitive":true,"value":"bar"},"interpolated":{"sensitive":false,"value":"7914344597979736746"},"interpolated_deep":{"sensitive":false,"value":{"foo":"bar","map":{"bar":"baz","id":"7914344597979736746"},"number":42}},"list":{"sensitive":false,"value":["foo","bar"]},"map":{"sensitive":false,"value":{"foo":"bar","number":42}},"referenced":{"sensitive":false,"value":"7914344597979736746"},"referenced_deep":{"sensitive":false,"value":{"foo":"bar","map":{"bar":"baz","id":"7914344597979736746"},"number":42}},"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,"values":{"id":"346205755248437621","triggers":{"foo_id":"7914344597979736746"}},"sensitive_values":{"triggers":{}},"depends_on":["null_resource.foo"]},{"address":"null_resource.baz[0]","mode":"managed","type":"null_resource","name":"baz","index":0,"provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"values":{"id":"8125409023088484730","triggers":{"foo_id":"7914344597979736746"}},"sensitive_values":{"triggers":{}},"depends_on":["null_resource.foo"]},{"address":"null_resource.baz[1]","mode":"managed","type":"null_resource","name":"baz","index":1,"provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"values":{"id":"4055263173373670778","triggers":{"foo_id":"7914344597979736746"}},"sensitive_values":{"triggers":{}},"depends_on":["null_resource.foo"]},{"address":"null_resource.baz[2]","mode":"managed","type":"null_resource","name":"baz","index":2,"provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"values":{"id":"7188960170253950057","triggers":{"foo_id":"7914344597979736746"}},"sensitive_values":{"triggers":{}},"depends_on":["null_resource.foo"]},{"address":"null_resource.foo","mode":"managed","type":"null_resource","name":"foo","provider_name":"registry.terraform.io/hashicorp/null","schema_version":0,"values":{"id":"7914344597979736746","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":{"id":"8187802253811954885","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":{"id":"712346592830392361","triggers":{"foo":"bar"}},"sensitive_values":{"triggers":{}}}],"address":"module.foo"}]}}}
Loading

0 comments on commit be0ac34

Please sign in to comment.