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

Add support for format versions 1.0 and above #41

Merged
merged 1 commit into from
Sep 14, 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.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.13
require (
github.com/davecgh/go-spew v1.1.1
github.com/google/go-cmp v0.5.6
github.com/hashicorp/go-version v1.3.0
github.com/mitchellh/copystructure v1.2.0
github.com/sebdah/goldie v1.0.0
github.com/zclconf/go-cty v1.9.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw=
github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
Expand Down
24 changes: 18 additions & 6 deletions plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
"encoding/json"
"errors"
"fmt"

"github.com/hashicorp/go-version"
)

// PlanFormatVersions represents versions of the JSON plan format that
// are supported by this package.
var PlanFormatVersions = []string{"0.1", "0.2"}
// PlanFormatVersionConstraints defines the versions of the JSON plan format
// that are supported by this package.
var PlanFormatVersionConstraints = ">= 0.1, < 2.0"

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

if !isStringInSlice(PlanFormatVersions, p.FormatVersion) {
return fmt.Errorf("unsupported plan format version: expected %q, got %q",
PlanFormatVersions, p.FormatVersion)
constraint, err := version.NewConstraint(PlanFormatVersionConstraints)
if err != nil {
return fmt.Errorf("invalid version constraint: %w", err)
}

version, err := version.NewVersion(p.FormatVersion)
if err != nil {
return fmt.Errorf("invalid format version %q: %w", p.FormatVersion, err)
}

if !constraint.Check(version) {
return fmt.Errorf("unsupported plan format version: %q does not satisfy %q",
version, constraint)
}

return nil
Expand Down
23 changes: 17 additions & 6 deletions schemas.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import (
"errors"
"fmt"

"github.com/hashicorp/go-version"
"github.com/zclconf/go-cty/cty"
)

// ProviderSchemasFormatVersions represents the versions of
// the JSON provider schema format that are supported by this package.
var ProviderSchemasFormatVersions = []string{"0.1", "0.2"}
// ProviderSchemasFormatVersionConstraints defines the versions of the JSON
// provider schema format that are supported by this package.
var ProviderSchemasFormatVersionConstraints = ">= 0.1, < 2.0"

// ProviderSchemas represents the schemas of all providers and
// resources in use by the configuration.
Expand Down Expand Up @@ -38,9 +39,19 @@ func (p *ProviderSchemas) Validate() error {
return errors.New("unexpected provider schema data, format version is missing")
}

if !isStringInSlice(ProviderSchemasFormatVersions, p.FormatVersion) {
return fmt.Errorf("unsupported provider schema data format version: expected %q, got %q",
ProviderSchemasFormatVersions, p.FormatVersion)
constraint, err := version.NewConstraint(PlanFormatVersionConstraints)
if err != nil {
return fmt.Errorf("invalid version constraint: %w", err)
}

version, err := version.NewVersion(p.FormatVersion)
if err != nil {
return fmt.Errorf("invalid format version %q: %w", p.FormatVersion, err)
}

if !constraint.Check(version) {
return fmt.Errorf("unsupported provider schema format version: %q does not satisfy %q",
version, constraint)
}

return nil
Expand Down
22 changes: 17 additions & 5 deletions state.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import (
"encoding/json"
"errors"
"fmt"

"github.com/hashicorp/go-version"
)

// StateFormatVersions represents the versions of the JSON state format
// StateFormatVersionConstraints defines the versions of the JSON state format
// that are supported by this package.
var StateFormatVersions = []string{"0.1", "0.2"}
var StateFormatVersionConstraints = ">= 0.1, < 2.0"

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

if !isStringInSlice(StateFormatVersions, s.FormatVersion) {
return fmt.Errorf("unsupported state format version: expected %q, got %q",
StateFormatVersions, s.FormatVersion)
constraint, err := version.NewConstraint(StateFormatVersionConstraints)
if err != nil {
return fmt.Errorf("invalid version constraint: %w", err)
}

version, err := version.NewVersion(s.FormatVersion)
if err != nil {
return fmt.Errorf("invalid format version %q: %w", s.FormatVersion, err)
}

if !constraint.Check(version) {
return fmt.Errorf("unsupported state format version: %q does not satisfy %q",
version, constraint)
}

return nil
Expand Down
17 changes: 17 additions & 0 deletions state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,20 @@ func TestStateValidate_fromPlan(t *testing.T) {
t.Fatal(err)
}
}

func TestStateValidate_fromPlan110(t *testing.T) {
f, err := os.Open("testdata/110_basic/plan.json")
if err != nil {
t.Fatal(err)
}
defer f.Close()

var plan *Plan
if err := json.NewDecoder(f).Decode(&plan); err != nil {
t.Fatal(err)
}

if err := plan.PriorState.Validate(); err != nil {
t.Fatal(err)
}
}
42 changes: 42 additions & 0 deletions testdata/110_basic/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions testdata/110_basic/foo/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
terraform {
required_providers {
null = {
source = "hashicorp/null"
configuration_aliases = [null.aliased]
}
}
}

variable "bar" {
type = string
}

variable "one" {
type = string
}

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_basic/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_basic/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_basic/plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"format_version":"1.0","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":"1.0","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}}}}}
20 changes: 20 additions & 0 deletions testdata/110_basic/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
terraform {
required_providers {
null = {
source = "hashicorp/null"
}

aws = {
source = "hashicorp/aws"
}
}
}

provider "aws" {
region = "us-west-2"
}

provider "aws" {
alias = "east"
region = "us-east-1"
}
23 changes: 23 additions & 0 deletions testdata/110_basic/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_basic/schemas.json

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions testdata/110_basic/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
variable "foo" {
description = "foobar"
default = "bar"
}

variable "number" {
default = 42
}

variable "map" {
default = {
foo = "bar"
number = 42
}
}
23 changes: 19 additions & 4 deletions validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ import (
"encoding/json"
"errors"
"fmt"

"github.com/hashicorp/go-version"
)

// ValidateFormatVersionConstraints defines the versions of the JSON
// validate format that are supported by this package.
var ValidateFormatVersionConstraints = ">= 0.1, < 2.0"

// Pos represents a position in a config file
type Pos struct {
Line int `json:"line"`
Expand Down Expand Up @@ -110,10 +116,19 @@ func (vo *ValidateOutput) Validate() error {
return nil
}

supportedVersion := "0.1"
if vo.FormatVersion != supportedVersion {
return fmt.Errorf("unsupported validation output format version: expected %q, got %q",
supportedVersion, vo.FormatVersion)
constraint, err := version.NewConstraint(ValidateFormatVersionConstraints)
if err != nil {
return fmt.Errorf("invalid version constraint: %w", err)
}

version, err := version.NewVersion(vo.FormatVersion)
if err != nil {
return fmt.Errorf("invalid format version %q: %w", vo.FormatVersion, err)
}

if !constraint.Check(version) {
return fmt.Errorf("unsupported validation output format version: %q does not satisfy %q",
version, constraint)
}

return nil
Expand Down
Loading