config: parse and validate terraform.required_version
This commit is contained in:
parent
8d993d9edd
commit
85d3439fa0
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/hil"
|
||||
"github.com/hashicorp/hil/ast"
|
||||
"github.com/hashicorp/terraform/helper/hilmapstructure"
|
||||
|
@ -27,6 +28,7 @@ type Config struct {
|
|||
// any meaningful directory.
|
||||
Dir string
|
||||
|
||||
Terraform *Terraform
|
||||
Atlas *AtlasConfig
|
||||
Modules []*Module
|
||||
ProviderConfigs []*ProviderConfig
|
||||
|
@ -39,6 +41,12 @@ type Config struct {
|
|||
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.
|
||||
type AtlasConfig struct {
|
||||
Name string
|
||||
|
@ -236,6 +244,30 @@ func (c *Config) Validate() error {
|
|||
"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()
|
||||
varMap := make(map[string]*Variable)
|
||||
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) {
|
||||
c := testConfig(t, "validate-bad-depends-on")
|
||||
if err := c.Validate(); err == nil {
|
||||
|
|
|
@ -19,13 +19,14 @@ type hclConfigurable struct {
|
|||
|
||||
func (t *hclConfigurable) Config() (*Config, error) {
|
||||
validKeys := map[string]struct{}{
|
||||
"atlas": struct{}{},
|
||||
"data": struct{}{},
|
||||
"module": struct{}{},
|
||||
"output": struct{}{},
|
||||
"provider": struct{}{},
|
||||
"resource": struct{}{},
|
||||
"variable": struct{}{},
|
||||
"atlas": struct{}{},
|
||||
"data": struct{}{},
|
||||
"module": struct{}{},
|
||||
"output": struct{}{},
|
||||
"provider": struct{}{},
|
||||
"resource": struct{}{},
|
||||
"terraform": struct{}{},
|
||||
"variable": struct{}{},
|
||||
}
|
||||
|
||||
// Top-level item should be the object list
|
||||
|
@ -37,6 +38,15 @@ func (t *hclConfigurable) Config() (*Config, error) {
|
|||
// Start building up the actual configuration.
|
||||
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
|
||||
if vars := list.Filter("variable"); len(vars.Items) > 0 {
|
||||
var err error
|
||||
|
@ -190,6 +200,32 @@ func loadFileHcl(root string) (configurable, []string, error) {
|
|||
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
|
||||
// configuration.
|
||||
func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) {
|
||||
|
|
|
@ -160,6 +160,11 @@ func TestLoadFileBasic(t *testing.T) {
|
|||
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"}
|
||||
if !reflect.DeepEqual(c.Atlas, expectedAtlas) {
|
||||
t.Fatalf("bad: %#v", c.Atlas)
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
terraform {
|
||||
required_version = "foo"
|
||||
}
|
||||
|
||||
variable "foo" {
|
||||
default = "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