Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reflect upcoming v1.1 plan/state format changes #37

Merged
merged 1 commit into from
Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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