diff --git a/tfexec/internal/e2etest/testdata/pre_011_syntax/file1.golden.txt b/tfexec/internal/e2etest/testdata/pre_011_syntax/file1.golden.txt new file mode 100644 index 00000000..1b6a3631 --- /dev/null +++ b/tfexec/internal/e2etest/testdata/pre_011_syntax/file1.golden.txt @@ -0,0 +1,31 @@ +resource "aws_instance" "web" { + ami = data.aws_ami.amazon.id + instance_type = "t3.micro" + count = 2 + + tags = { + Name = "HelloWorld" + } +} + +resource "aws_elb" "web" { + instances = aws_instance.web.*.id + subnets = aws_subnet.test.*.id + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } +} + +data "aws_ami" "amazon" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amzn-ami-hvm-*-x86_64-gp2"] + } +} + diff --git a/tfexec/internal/e2etest/testdata/pre_011_syntax/file1.tf b/tfexec/internal/e2etest/testdata/pre_011_syntax/file1.tf new file mode 100644 index 00000000..b659870f --- /dev/null +++ b/tfexec/internal/e2etest/testdata/pre_011_syntax/file1.tf @@ -0,0 +1,30 @@ +resource "aws_instance" "web" { + ami = "${data.aws_ami.amazon.id}" + instance_type = "t3.micro" + count = 2 + + tags = { + Name = "HelloWorld" + } +} + +resource "aws_elb" "web" { + instances = ["${aws_instance.web.*.id}"] + subnets = ["${aws_subnet.test.*.id}"] + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } +} + +data "aws_ami" "amazon" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amzn-ami-hvm-*-x86_64-gp2"] + } +} diff --git a/tfexec/internal/e2etest/testdata/pre_011_syntax/file2.golden.txt b/tfexec/internal/e2etest/testdata/pre_011_syntax/file2.golden.txt new file mode 100644 index 00000000..64b58def --- /dev/null +++ b/tfexec/internal/e2etest/testdata/pre_011_syntax/file2.golden.txt @@ -0,0 +1,26 @@ +data "aws_availability_zones" "available" { + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_vpc" "test" { + cidr_block = var.vpc_cidr + enable_dns_hostnames = true +} + +resource "aws_subnet" "test" { + count = 2 + vpc_id = aws_vpc.test.id + + cidr_block = cidrsubnet(var.vpc_cidr, 2, count.index) + availability_zone = data.aws_availability_zones.available.names[count.index] +} + +variable "vpc_cidr" { + default = "10.1.0.0/16" +} + diff --git a/tfexec/internal/e2etest/testdata/pre_011_syntax/file2.tf b/tfexec/internal/e2etest/testdata/pre_011_syntax/file2.tf new file mode 100644 index 00000000..e263412f --- /dev/null +++ b/tfexec/internal/e2etest/testdata/pre_011_syntax/file2.tf @@ -0,0 +1,25 @@ +data "aws_availability_zones" "available" { + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_vpc" "test" { + cidr_block = "${var.vpc_cidr}" + enable_dns_hostnames = true +} + +resource "aws_subnet" "test" { + count = 2 + vpc_id = "${aws_vpc.test.id}" + + cidr_block = "${cidrsubnet(var.vpc_cidr, 2, count.index)}" + availability_zone = "${data.aws_availability_zones.available.names[count.index]}" +} + +variable "vpc_cidr" { + default = "10.1.0.0/16" +} diff --git a/tfexec/internal/e2etest/upgrade012_test.go b/tfexec/internal/e2etest/upgrade012_test.go new file mode 100644 index 00000000..c40de0c0 --- /dev/null +++ b/tfexec/internal/e2etest/upgrade012_test.go @@ -0,0 +1,33 @@ +package e2etest + +import ( + "context" + "path/filepath" + "testing" + + "github.com/hashicorp/go-version" + + "github.com/hashicorp/terraform-exec/tfexec" + "github.com/hashicorp/terraform-exec/tfexec/internal/testutil" +) + +func TestUpgrade012(t *testing.T) { + runTestVersions(t, []string{testutil.Latest012}, "pre_011_syntax", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) { + err := tf.Init(context.Background()) + if err != nil { + t.Fatalf("error running Init in test directory: %s", err) + } + + err = tf.Upgrade012(context.Background()) + if err != nil { + t.Fatalf("error from FormatWrite: %T %q", err, err) + } + + for file, golden := range map[string]string{ + "file1.tf": "file1.golden.txt", + "file2.tf": "file2.golden.txt", + } { + textFilesEqual(t, filepath.Join(tf.WorkingDir(), golden), filepath.Join(tf.WorkingDir(), file)) + } + }) +} diff --git a/tfexec/internal/e2etest/util_test.go b/tfexec/internal/e2etest/util_test.go index d5232a92..a74e30ce 100644 --- a/tfexec/internal/e2etest/util_test.go +++ b/tfexec/internal/e2etest/util_test.go @@ -72,7 +72,7 @@ func runTestVersions(t *testing.T, versions []string, fixtureName string, cb fun runningVersion, _, err := tf.Version(context.Background(), false) if err != nil { - t.Fatalf("unable to determin running version (expected %q): %s", tfv, err) + t.Fatalf("unable to determine running version (expected %q): %s", tfv, err) } if fixtureName != "" { diff --git a/tfexec/options.go b/tfexec/options.go index f5baf5d4..3f0fd00c 100644 --- a/tfexec/options.go +++ b/tfexec/options.go @@ -99,6 +99,14 @@ func Destroy(destroy bool) *DestroyFlagOption { return &DestroyFlagOption{destroy} } +type ForceOption struct { + force bool +} + +func Force(force bool) *ForceOption { + return &ForceOption{force} +} + type ForceCopyOption struct { forceCopy bool } diff --git a/tfexec/upgrade012.go b/tfexec/upgrade012.go new file mode 100644 index 00000000..5af05b7e --- /dev/null +++ b/tfexec/upgrade012.go @@ -0,0 +1,80 @@ +package tfexec + +import ( + "context" + "fmt" + "os/exec" +) + +type upgrade012Config struct { + dir string + force bool + + reattachInfo ReattachInfo +} + +var defaultUpgrade012Options = upgrade012Config{ + force: false, +} + +// Upgrade012Option represents options used in the Destroy method. +type Upgrade012Option interface { + configureUpgrade012(*upgrade012Config) +} + +func (opt *DirOption) configureUpgrade012(conf *upgrade012Config) { + conf.dir = opt.path +} + +func (opt *ForceOption) configureUpgrade012(conf *upgrade012Config) { + conf.force = opt.force +} + +func (opt *ReattachOption) configureUpgrade012(conf *upgrade012Config) { + conf.reattachInfo = opt.info +} + +// Upgrade012 represents the terraform 0.12upgrade subcommand. +func (tf *Terraform) Upgrade012(ctx context.Context, opts ...Upgrade012Option) error { + cmd, err := tf.upgrade012Cmd(ctx, opts...) + if err != nil { + return err + } + return tf.runTerraformCmd(cmd) +} + +func (tf *Terraform) upgrade012Cmd(ctx context.Context, opts ...Upgrade012Option) (*exec.Cmd, error) { + err := tf.compatible(ctx, tf0_12_0, tf0_13_0) + if err != nil { + return nil, fmt.Errorf("terraform 0.12upgrade is only supported in 0.12 releases: %w", err) + } + + c := defaultUpgrade012Options + + for _, o := range opts { + o.configureUpgrade012(&c) + } + + args := []string{"0.12upgrade", "-no-color", "-yes"} + + // boolean opts: only pass if set + if c.force { + args = append(args, "-force") + } + + // optional positional argument + if c.dir != "" { + args = append(args, c.dir) + } + + mergeEnv := map[string]string{} + if c.reattachInfo != nil { + reattachStr, err := c.reattachInfo.marshalString() + if err != nil { + return nil, err + } + mergeEnv[reattachEnvVar] = reattachStr + } + + return tf.buildTerraformCmd(ctx, mergeEnv, args...), nil +} diff --git a/tfexec/upgrade012_test.go b/tfexec/upgrade012_test.go new file mode 100644 index 00000000..958d0d24 --- /dev/null +++ b/tfexec/upgrade012_test.go @@ -0,0 +1,84 @@ +package tfexec + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/hashicorp/terraform-exec/tfexec/internal/testutil" +) + +func TestUpgrade012(t *testing.T) { + td := testTempDir(t) + + t.Run("defaults", func(t *testing.T) { + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest012)) + if err != nil { + t.Fatal(err) + } + + // empty env, to avoid environ mismatch in testing + tf.SetEnv(map[string]string{}) + + upgrade012Cmd, err := tf.upgrade012Cmd(context.Background()) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "0.12upgrade", + "-no-color", + "-yes", + }, nil, upgrade012Cmd) + }) + + t.Run("override all defaults", func(t *testing.T) { + tf, err := NewTerraform(td, tfVersion(t, testutil.Latest012)) + if err != nil { + t.Fatal(err) + } + + // empty env, to avoid environ mismatch in testing + tf.SetEnv(map[string]string{}) + + upgrade012Cmd, err := tf.upgrade012Cmd(context.Background(), Force(true), Dir("upgrade012dir")) + if err != nil { + t.Fatal(err) + } + + assertCmd(t, []string{ + "0.12upgrade", + "-no-color", + "-yes", + "-force", + "upgrade012dir", + }, nil, upgrade012Cmd) + }) + + unsupportedVersions := []string{ + testutil.Latest011, + testutil.Latest013, + } + for _, tfv := range unsupportedVersions { + t.Run(fmt.Sprintf("unsupported on %s", tfv), func(t *testing.T) { + tf, err := NewTerraform(td, tfVersion(t, tfv)) + if err != nil { + t.Fatal(err) + } + + // empty env, to avoid environ mismatch in testing + tf.SetEnv(map[string]string{}) + + _, err = tf.upgrade012Cmd(context.Background()) + if err == nil { + t.Fatalf("expected unsupported version %s to fail", tfv) + } + + var expectedErr *ErrVersionMismatch + if !errors.As(err, &expectedErr) { + t.Fatalf("error doesn't match: %#v", err) + } + }) + } +}