config: parse and validate terraform.required_version
This commit is contained in:
parent
8d993d9edd
commit
85d3439fa0
|
@ -9,6 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/hil"
|
"github.com/hashicorp/hil"
|
||||||
"github.com/hashicorp/hil/ast"
|
"github.com/hashicorp/hil/ast"
|
||||||
"github.com/hashicorp/terraform/helper/hilmapstructure"
|
"github.com/hashicorp/terraform/helper/hilmapstructure"
|
||||||
|
@ -27,6 +28,7 @@ type Config struct {
|
||||||
// any meaningful directory.
|
// any meaningful directory.
|
||||||
Dir string
|
Dir string
|
||||||
|
|
||||||
|
Terraform *Terraform
|
||||||
Atlas *AtlasConfig
|
Atlas *AtlasConfig
|
||||||
Modules []*Module
|
Modules []*Module
|
||||||
ProviderConfigs []*ProviderConfig
|
ProviderConfigs []*ProviderConfig
|
||||||
|
@ -39,6 +41,12 @@ type Config struct {
|
||||||
unknownKeys []string
|
unknownKeys []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Terraform is the Terraform meta-configuration that can be present
|
||||||
|
// in configuration files for configuring Terraform itself.
|
||||||
|
type Terraform struct {
|
||||||
|
RequiredVersion string `hcl:"required_version"` // Required Terraform version (constraint)
|
||||||
|
}
|
||||||
|
|
||||||
// AtlasConfig is the configuration for building in HashiCorp's Atlas.
|
// AtlasConfig is the configuration for building in HashiCorp's Atlas.
|
||||||
type AtlasConfig struct {
|
type AtlasConfig struct {
|
||||||
Name string
|
Name string
|
||||||
|
@ -236,6 +244,30 @@ func (c *Config) Validate() error {
|
||||||
"Unknown root level key: %s", k))
|
"Unknown root level key: %s", k))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate the Terraform config
|
||||||
|
if tf := c.Terraform; tf != nil {
|
||||||
|
if raw := tf.RequiredVersion; raw != "" {
|
||||||
|
// Check that the value has no interpolations
|
||||||
|
rc, err := NewRawConfig(map[string]interface{}{
|
||||||
|
"root": raw,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf(
|
||||||
|
"terraform.required_version: %s", err))
|
||||||
|
} else if len(rc.Interpolations) > 0 {
|
||||||
|
errs = append(errs, fmt.Errorf(
|
||||||
|
"terraform.required_version: cannot contain interpolations"))
|
||||||
|
} else {
|
||||||
|
// Check it is valid
|
||||||
|
_, err := version.NewConstraint(raw)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf(
|
||||||
|
"terraform.required_version: invalid syntax: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
vars := c.InterpolatedVariables()
|
vars := c.InterpolatedVariables()
|
||||||
varMap := make(map[string]*Variable)
|
varMap := make(map[string]*Variable)
|
||||||
for _, v := range c.Variables {
|
for _, v := range c.Variables {
|
||||||
|
|
|
@ -137,6 +137,27 @@ func TestConfigValidate(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigValidate_tfVersion(t *testing.T) {
|
||||||
|
c := testConfig(t, "validate-tf-version")
|
||||||
|
if err := c.Validate(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigValidate_tfVersionBad(t *testing.T) {
|
||||||
|
c := testConfig(t, "validate-bad-tf-version")
|
||||||
|
if err := c.Validate(); err == nil {
|
||||||
|
t.Fatal("should not be valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigValidate_tfVersionInterpolations(t *testing.T) {
|
||||||
|
c := testConfig(t, "validate-tf-version-interp")
|
||||||
|
if err := c.Validate(); err == nil {
|
||||||
|
t.Fatal("should not be valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConfigValidate_badDependsOn(t *testing.T) {
|
func TestConfigValidate_badDependsOn(t *testing.T) {
|
||||||
c := testConfig(t, "validate-bad-depends-on")
|
c := testConfig(t, "validate-bad-depends-on")
|
||||||
if err := c.Validate(); err == nil {
|
if err := c.Validate(); err == nil {
|
||||||
|
|
|
@ -19,13 +19,14 @@ type hclConfigurable struct {
|
||||||
|
|
||||||
func (t *hclConfigurable) Config() (*Config, error) {
|
func (t *hclConfigurable) Config() (*Config, error) {
|
||||||
validKeys := map[string]struct{}{
|
validKeys := map[string]struct{}{
|
||||||
"atlas": struct{}{},
|
"atlas": struct{}{},
|
||||||
"data": struct{}{},
|
"data": struct{}{},
|
||||||
"module": struct{}{},
|
"module": struct{}{},
|
||||||
"output": struct{}{},
|
"output": struct{}{},
|
||||||
"provider": struct{}{},
|
"provider": struct{}{},
|
||||||
"resource": struct{}{},
|
"resource": struct{}{},
|
||||||
"variable": struct{}{},
|
"terraform": struct{}{},
|
||||||
|
"variable": struct{}{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Top-level item should be the object list
|
// Top-level item should be the object list
|
||||||
|
@ -37,6 +38,15 @@ func (t *hclConfigurable) Config() (*Config, error) {
|
||||||
// Start building up the actual configuration.
|
// Start building up the actual configuration.
|
||||||
config := new(Config)
|
config := new(Config)
|
||||||
|
|
||||||
|
// Terraform config
|
||||||
|
if o := list.Filter("terraform"); len(o.Items) > 0 {
|
||||||
|
var err error
|
||||||
|
config.Terraform, err = loadTerraformHcl(o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build the variables
|
// Build the variables
|
||||||
if vars := list.Filter("variable"); len(vars.Items) > 0 {
|
if vars := list.Filter("variable"); len(vars.Items) > 0 {
|
||||||
var err error
|
var err error
|
||||||
|
@ -190,6 +200,32 @@ func loadFileHcl(root string) (configurable, []string, error) {
|
||||||
return result, nil, nil
|
return result, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given a handle to a HCL object, this transforms it into the Terraform config
|
||||||
|
func loadTerraformHcl(list *ast.ObjectList) (*Terraform, error) {
|
||||||
|
if len(list.Items) > 1 {
|
||||||
|
return nil, fmt.Errorf("only one 'terraform' block allowed per module")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get our one item
|
||||||
|
item := list.Items[0]
|
||||||
|
|
||||||
|
// NOTE: We purposely don't validate unknown HCL keys here so that
|
||||||
|
// we can potentially read _future_ Terraform version config (to
|
||||||
|
// still be able to validate the required version).
|
||||||
|
//
|
||||||
|
// We should still keep track of unknown keys to validate later, but
|
||||||
|
// HCL doesn't currently support that.
|
||||||
|
|
||||||
|
var config Terraform
|
||||||
|
if err := hcl.DecodeObject(&config, item.Val); err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Error reading terraform config: %s",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Given a handle to a HCL object, this transforms it into the Atlas
|
// Given a handle to a HCL object, this transforms it into the Atlas
|
||||||
// configuration.
|
// configuration.
|
||||||
func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) {
|
func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) {
|
||||||
|
|
|
@ -160,6 +160,11 @@ func TestLoadFileBasic(t *testing.T) {
|
||||||
t.Fatalf("bad: %#v", c.Dir)
|
t.Fatalf("bad: %#v", c.Dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expectedTF := &Terraform{RequiredVersion: "foo"}
|
||||||
|
if !reflect.DeepEqual(c.Terraform, expectedTF) {
|
||||||
|
t.Fatalf("bad: %#v", c.Terraform)
|
||||||
|
}
|
||||||
|
|
||||||
expectedAtlas := &AtlasConfig{Name: "mitchellh/foo"}
|
expectedAtlas := &AtlasConfig{Name: "mitchellh/foo"}
|
||||||
if !reflect.DeepEqual(c.Atlas, expectedAtlas) {
|
if !reflect.DeepEqual(c.Atlas, expectedAtlas) {
|
||||||
t.Fatalf("bad: %#v", c.Atlas)
|
t.Fatalf("bad: %#v", c.Atlas)
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
terraform {
|
||||||
|
required_version = "foo"
|
||||||
|
}
|
||||||
|
|
||||||
variable "foo" {
|
variable "foo" {
|
||||||
default = "bar"
|
default = "bar"
|
||||||
description = "bar"
|
description = "bar"
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
terraform {
|
||||||
|
required_version = "nope"
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
terraform {
|
||||||
|
required_version = "${var.foo}"
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
terraform {
|
||||||
|
required_version = "> 0.7.0"
|
||||||
|
}
|
Loading…
Reference in New Issue