terraform: verify version requirements from configuration
This commit is contained in:
parent
85d3439fa0
commit
2c467e0f74
|
@ -102,6 +102,13 @@ type Context struct {
|
||||||
// should not be mutated in any way, since the pointers are copied, not
|
// should not be mutated in any way, since the pointers are copied, not
|
||||||
// the values themselves.
|
// the values themselves.
|
||||||
func NewContext(opts *ContextOpts) (*Context, error) {
|
func NewContext(opts *ContextOpts) (*Context, error) {
|
||||||
|
// Validate the version requirement if it is given
|
||||||
|
if opts.Module != nil {
|
||||||
|
if err := checkRequiredVersion(opts.Module); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Copy all the hooks and add our stop hook. We don't append directly
|
// Copy all the hooks and add our stop hook. We don't append directly
|
||||||
// to the Config so that we're not modifying that in-place.
|
// to the Config so that we're not modifying that in-place.
|
||||||
sh := new(stopHook)
|
sh := new(stopHook)
|
||||||
|
|
|
@ -6,9 +6,87 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/terraform/flatmap"
|
"github.com/hashicorp/terraform/flatmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestNewContextRequiredVersion(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Name string
|
||||||
|
Module string
|
||||||
|
Version string
|
||||||
|
Value string
|
||||||
|
Err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"no requirement",
|
||||||
|
"",
|
||||||
|
"0.1.0",
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"doesn't match",
|
||||||
|
"",
|
||||||
|
"0.1.0",
|
||||||
|
"> 0.6.0",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"matches",
|
||||||
|
"",
|
||||||
|
"0.7.0",
|
||||||
|
"> 0.6.0",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"module matches",
|
||||||
|
"context-required-version-module",
|
||||||
|
"0.5.0",
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"module doesn't match",
|
||||||
|
"context-required-version-module",
|
||||||
|
"0.4.0",
|
||||||
|
"",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range cases {
|
||||||
|
t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
|
||||||
|
// Reset the version for the tests
|
||||||
|
old := SemVersion
|
||||||
|
SemVersion = version.Must(version.NewVersion(tc.Version))
|
||||||
|
defer func() { SemVersion = old }()
|
||||||
|
|
||||||
|
name := "context-required-version"
|
||||||
|
if tc.Module != "" {
|
||||||
|
name = tc.Module
|
||||||
|
}
|
||||||
|
mod := testModule(t, name)
|
||||||
|
if tc.Value != "" {
|
||||||
|
mod.Config().Terraform.RequiredVersion = tc.Value
|
||||||
|
}
|
||||||
|
_, err := NewContext(&ContextOpts{
|
||||||
|
Module: mod,
|
||||||
|
})
|
||||||
|
if (err != nil) != tc.Err {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewContextState(t *testing.T) {
|
func TestNewContextState(t *testing.T) {
|
||||||
cases := map[string]struct {
|
cases := map[string]struct {
|
||||||
Input *ContextOpts
|
Input *ContextOpts
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
terraform { required_version = ">= 0.5.0" }
|
|
@ -0,0 +1,3 @@
|
||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
terraform {}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-version"
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
)
|
||||||
|
|
||||||
|
// checkRequiredVersion verifies that any version requirements specified by
|
||||||
|
// the configuration are met.
|
||||||
|
//
|
||||||
|
// This checks the root module as well as any additional version requirements
|
||||||
|
// from child modules.
|
||||||
|
//
|
||||||
|
// This is tested in context_test.go.
|
||||||
|
func checkRequiredVersion(m *module.Tree) error {
|
||||||
|
// Check any children
|
||||||
|
for _, c := range m.Children() {
|
||||||
|
if err := checkRequiredVersion(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tf *config.Terraform
|
||||||
|
if c := m.Config(); c != nil {
|
||||||
|
tf = c.Terraform
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no Terraform config or the required version isn't set,
|
||||||
|
// we move on.
|
||||||
|
if tf == nil || tf.RequiredVersion == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path for errors
|
||||||
|
module := "root"
|
||||||
|
if path := normalizeModulePath(m.Path()); len(path) > 1 {
|
||||||
|
module = modulePrefixStr(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check this version requirement of this module
|
||||||
|
cs, err := version.NewConstraint(tf.RequiredVersion)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"%s: terraform.required_version %q syntax error: %s",
|
||||||
|
module,
|
||||||
|
tf.RequiredVersion, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cs.Check(SemVersion) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"The currently running version of Terraform doesn't meet the\n"+
|
||||||
|
"version requirements explicitly specified by the configuration.\n"+
|
||||||
|
"Please use the required version or update the configuration.\n"+
|
||||||
|
"Note that version requirements are usually set for a reason, so\n"+
|
||||||
|
"we recommend verifying with whoever set the version requirements\n"+
|
||||||
|
"prior to making any manual changes.\n\n"+
|
||||||
|
" Module: %s\n"+
|
||||||
|
" Required version: %s\n"+
|
||||||
|
" Current version: %s",
|
||||||
|
module,
|
||||||
|
tf.RequiredVersion,
|
||||||
|
SemVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue