github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/meta_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package command
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"reflect"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/google/go-cmp/cmp"
    16  
    17  	"github.com/mitchellh/cli"
    18  	"github.com/terramate-io/tf/backend"
    19  	"github.com/terramate-io/tf/backend/local"
    20  	"github.com/terramate-io/tf/terraform"
    21  )
    22  
    23  func TestMetaColorize(t *testing.T) {
    24  	var m *Meta
    25  	var args, args2 []string
    26  
    27  	// Test basic, color
    28  	m = new(Meta)
    29  	m.Color = true
    30  	args = []string{"foo", "bar"}
    31  	args2 = []string{"foo", "bar"}
    32  	args = m.process(args)
    33  	if !reflect.DeepEqual(args, args2) {
    34  		t.Fatalf("bad: %#v", args)
    35  	}
    36  	if m.Colorize().Disable {
    37  		t.Fatal("should not be disabled")
    38  	}
    39  
    40  	// Test basic, no change
    41  	m = new(Meta)
    42  	args = []string{"foo", "bar"}
    43  	args2 = []string{"foo", "bar"}
    44  	args = m.process(args)
    45  	if !reflect.DeepEqual(args, args2) {
    46  		t.Fatalf("bad: %#v", args)
    47  	}
    48  	if !m.Colorize().Disable {
    49  		t.Fatal("should be disabled")
    50  	}
    51  
    52  	// Test disable #1
    53  	m = new(Meta)
    54  	m.Color = true
    55  	args = []string{"foo", "-no-color", "bar"}
    56  	args2 = []string{"foo", "bar"}
    57  	args = m.process(args)
    58  	if !reflect.DeepEqual(args, args2) {
    59  		t.Fatalf("bad: %#v", args)
    60  	}
    61  	if !m.Colorize().Disable {
    62  		t.Fatal("should be disabled")
    63  	}
    64  
    65  	// Test disable #2
    66  	// Verify multiple -no-color options are removed from args slice.
    67  	// E.g. an additional -no-color arg could be added by TF_CLI_ARGS.
    68  	m = new(Meta)
    69  	m.Color = true
    70  	args = []string{"foo", "-no-color", "bar", "-no-color"}
    71  	args2 = []string{"foo", "bar"}
    72  	args = m.process(args)
    73  	if !reflect.DeepEqual(args, args2) {
    74  		t.Fatalf("bad: %#v", args)
    75  	}
    76  	if !m.Colorize().Disable {
    77  		t.Fatal("should be disabled")
    78  	}
    79  }
    80  
    81  func TestMetaInputMode(t *testing.T) {
    82  	test = false
    83  	defer func() { test = true }()
    84  
    85  	m := new(Meta)
    86  	args := []string{}
    87  
    88  	fs := m.extendedFlagSet("foo")
    89  	if err := fs.Parse(args); err != nil {
    90  		t.Fatalf("err: %s", err)
    91  	}
    92  
    93  	if m.InputMode() != terraform.InputModeStd {
    94  		t.Fatalf("bad: %#v", m.InputMode())
    95  	}
    96  }
    97  
    98  func TestMetaInputMode_envVar(t *testing.T) {
    99  	test = false
   100  	defer func() { test = true }()
   101  	old := os.Getenv(InputModeEnvVar)
   102  	defer os.Setenv(InputModeEnvVar, old)
   103  
   104  	m := new(Meta)
   105  	args := []string{}
   106  
   107  	fs := m.extendedFlagSet("foo")
   108  	if err := fs.Parse(args); err != nil {
   109  		t.Fatalf("err: %s", err)
   110  	}
   111  
   112  	off := terraform.InputMode(0)
   113  	on := terraform.InputModeStd
   114  	cases := []struct {
   115  		EnvVar   string
   116  		Expected terraform.InputMode
   117  	}{
   118  		{"false", off},
   119  		{"0", off},
   120  		{"true", on},
   121  		{"1", on},
   122  	}
   123  
   124  	for _, tc := range cases {
   125  		os.Setenv(InputModeEnvVar, tc.EnvVar)
   126  		if m.InputMode() != tc.Expected {
   127  			t.Fatalf("expected InputMode: %#v, got: %#v", tc.Expected, m.InputMode())
   128  		}
   129  	}
   130  }
   131  
   132  func TestMetaInputMode_disable(t *testing.T) {
   133  	test = false
   134  	defer func() { test = true }()
   135  
   136  	m := new(Meta)
   137  	args := []string{"-input=false"}
   138  
   139  	fs := m.extendedFlagSet("foo")
   140  	if err := fs.Parse(args); err != nil {
   141  		t.Fatalf("err: %s", err)
   142  	}
   143  
   144  	if m.InputMode() > 0 {
   145  		t.Fatalf("bad: %#v", m.InputMode())
   146  	}
   147  }
   148  
   149  func TestMeta_initStatePaths(t *testing.T) {
   150  	m := new(Meta)
   151  	m.initStatePaths()
   152  
   153  	if m.statePath != DefaultStateFilename {
   154  		t.Fatalf("bad: %#v", m)
   155  	}
   156  	if m.stateOutPath != DefaultStateFilename {
   157  		t.Fatalf("bad: %#v", m)
   158  	}
   159  	if m.backupPath != DefaultStateFilename+DefaultBackupExtension {
   160  		t.Fatalf("bad: %#v", m)
   161  	}
   162  
   163  	m = new(Meta)
   164  	m.statePath = "foo"
   165  	m.initStatePaths()
   166  
   167  	if m.stateOutPath != "foo" {
   168  		t.Fatalf("bad: %#v", m)
   169  	}
   170  	if m.backupPath != "foo"+DefaultBackupExtension {
   171  		t.Fatalf("bad: %#v", m)
   172  	}
   173  
   174  	m = new(Meta)
   175  	m.stateOutPath = "foo"
   176  	m.initStatePaths()
   177  
   178  	if m.statePath != DefaultStateFilename {
   179  		t.Fatalf("bad: %#v", m)
   180  	}
   181  	if m.backupPath != "foo"+DefaultBackupExtension {
   182  		t.Fatalf("bad: %#v", m)
   183  	}
   184  }
   185  
   186  func TestMeta_Env(t *testing.T) {
   187  	td := t.TempDir()
   188  	os.MkdirAll(td, 0755)
   189  	defer testChdir(t, td)()
   190  
   191  	m := new(Meta)
   192  
   193  	env, err := m.Workspace()
   194  	if err != nil {
   195  		t.Fatal(err)
   196  	}
   197  
   198  	if env != backend.DefaultStateName {
   199  		t.Fatalf("expected env %q, got env %q", backend.DefaultStateName, env)
   200  	}
   201  
   202  	testEnv := "test_env"
   203  	if err := m.SetWorkspace(testEnv); err != nil {
   204  		t.Fatal("error setting env:", err)
   205  	}
   206  
   207  	env, _ = m.Workspace()
   208  	if env != testEnv {
   209  		t.Fatalf("expected env %q, got env %q", testEnv, env)
   210  	}
   211  
   212  	if err := m.SetWorkspace(backend.DefaultStateName); err != nil {
   213  		t.Fatal("error setting env:", err)
   214  	}
   215  
   216  	env, _ = m.Workspace()
   217  	if env != backend.DefaultStateName {
   218  		t.Fatalf("expected env %q, got env %q", backend.DefaultStateName, env)
   219  	}
   220  }
   221  
   222  func TestMeta_Workspace_override(t *testing.T) {
   223  	defer func(value string) {
   224  		os.Setenv(WorkspaceNameEnvVar, value)
   225  	}(os.Getenv(WorkspaceNameEnvVar))
   226  
   227  	m := new(Meta)
   228  
   229  	testCases := map[string]struct {
   230  		workspace string
   231  		err       error
   232  	}{
   233  		"": {
   234  			"default",
   235  			nil,
   236  		},
   237  		"development": {
   238  			"development",
   239  			nil,
   240  		},
   241  		"invalid name": {
   242  			"",
   243  			errInvalidWorkspaceNameEnvVar,
   244  		},
   245  	}
   246  
   247  	for name, tc := range testCases {
   248  		t.Run(name, func(t *testing.T) {
   249  			os.Setenv(WorkspaceNameEnvVar, name)
   250  			workspace, err := m.Workspace()
   251  			if workspace != tc.workspace {
   252  				t.Errorf("Unexpected workspace\n got: %s\nwant: %s\n", workspace, tc.workspace)
   253  			}
   254  			if err != tc.err {
   255  				t.Errorf("Unexpected error\n got: %s\nwant: %s\n", err, tc.err)
   256  			}
   257  		})
   258  	}
   259  }
   260  
   261  func TestMeta_Workspace_invalidSelected(t *testing.T) {
   262  	td := t.TempDir()
   263  	os.MkdirAll(td, 0755)
   264  	defer testChdir(t, td)()
   265  
   266  	// this is an invalid workspace name
   267  	workspace := "test workspace"
   268  
   269  	// create the workspace directories
   270  	if err := os.MkdirAll(filepath.Join(local.DefaultWorkspaceDir, workspace), 0755); err != nil {
   271  		t.Fatal(err)
   272  	}
   273  
   274  	// create the workspace file to select it
   275  	if err := os.MkdirAll(DefaultDataDir, 0755); err != nil {
   276  		t.Fatal(err)
   277  	}
   278  	if err := ioutil.WriteFile(filepath.Join(DefaultDataDir, local.DefaultWorkspaceFile), []byte(workspace), 0644); err != nil {
   279  		t.Fatal(err)
   280  	}
   281  
   282  	m := new(Meta)
   283  
   284  	ws, err := m.Workspace()
   285  	if ws != workspace {
   286  		t.Errorf("Unexpected workspace\n got: %s\nwant: %s\n", ws, workspace)
   287  	}
   288  	if err != nil {
   289  		t.Errorf("Unexpected error: %s", err)
   290  	}
   291  }
   292  
   293  func TestMeta_process(t *testing.T) {
   294  	test = false
   295  	defer func() { test = true }()
   296  
   297  	// Create a temporary directory for our cwd
   298  	d := t.TempDir()
   299  	os.MkdirAll(d, 0755)
   300  	defer testChdir(t, d)()
   301  
   302  	// At one point it was the responsibility of this process function to
   303  	// insert fake additional -var-file options into the command line
   304  	// if the automatic tfvars files were present. This is no longer the
   305  	// responsibility of process (it happens in collectVariableValues instead)
   306  	// but we're still testing with these files in place to verify that
   307  	// they _aren't_ being interpreted by process, since that could otherwise
   308  	// cause them to be added more than once and mess up the precedence order.
   309  	defaultVarsfile := "terraform.tfvars"
   310  	err := ioutil.WriteFile(
   311  		filepath.Join(d, defaultVarsfile),
   312  		[]byte(""),
   313  		0644)
   314  	if err != nil {
   315  		t.Fatalf("err: %s", err)
   316  	}
   317  	fileFirstAlphabetical := "a-file.auto.tfvars"
   318  	err = ioutil.WriteFile(
   319  		filepath.Join(d, fileFirstAlphabetical),
   320  		[]byte(""),
   321  		0644)
   322  	if err != nil {
   323  		t.Fatalf("err: %s", err)
   324  	}
   325  	fileLastAlphabetical := "z-file.auto.tfvars"
   326  	err = ioutil.WriteFile(
   327  		filepath.Join(d, fileLastAlphabetical),
   328  		[]byte(""),
   329  		0644)
   330  	if err != nil {
   331  		t.Fatalf("err: %s", err)
   332  	}
   333  	// Regular tfvars files will not be autoloaded
   334  	fileIgnored := "ignored.tfvars"
   335  	err = ioutil.WriteFile(
   336  		filepath.Join(d, fileIgnored),
   337  		[]byte(""),
   338  		0644)
   339  	if err != nil {
   340  		t.Fatalf("err: %s", err)
   341  	}
   342  
   343  	tests := []struct {
   344  		GivenArgs    []string
   345  		FilteredArgs []string
   346  		ExtraCheck   func(*testing.T, *Meta)
   347  	}{
   348  		{
   349  			[]string{},
   350  			[]string{},
   351  			func(t *testing.T, m *Meta) {
   352  				if got, want := m.color, true; got != want {
   353  					t.Errorf("wrong m.color value %#v; want %#v", got, want)
   354  				}
   355  				if got, want := m.Color, true; got != want {
   356  					t.Errorf("wrong m.Color value %#v; want %#v", got, want)
   357  				}
   358  			},
   359  		},
   360  		{
   361  			[]string{"-no-color"},
   362  			[]string{},
   363  			func(t *testing.T, m *Meta) {
   364  				if got, want := m.color, false; got != want {
   365  					t.Errorf("wrong m.color value %#v; want %#v", got, want)
   366  				}
   367  				if got, want := m.Color, false; got != want {
   368  					t.Errorf("wrong m.Color value %#v; want %#v", got, want)
   369  				}
   370  			},
   371  		},
   372  	}
   373  
   374  	for _, test := range tests {
   375  		t.Run(fmt.Sprintf("%s", test.GivenArgs), func(t *testing.T) {
   376  			m := new(Meta)
   377  			m.Color = true // this is the default also for normal use, overridden by -no-color
   378  			args := test.GivenArgs
   379  			args = m.process(args)
   380  
   381  			if !cmp.Equal(test.FilteredArgs, args) {
   382  				t.Errorf("wrong filtered arguments\n%s", cmp.Diff(test.FilteredArgs, args))
   383  			}
   384  
   385  			if test.ExtraCheck != nil {
   386  				test.ExtraCheck(t, m)
   387  			}
   388  		})
   389  	}
   390  }
   391  
   392  func TestCommand_checkRequiredVersion(t *testing.T) {
   393  	// Create a temporary working directory that is empty
   394  	td := t.TempDir()
   395  	testCopyDir(t, testFixturePath("command-check-required-version"), td)
   396  	defer testChdir(t, td)()
   397  
   398  	ui := cli.NewMockUi()
   399  	meta := Meta{
   400  		Ui: ui,
   401  	}
   402  
   403  	diags := meta.checkRequiredVersion()
   404  	if diags == nil {
   405  		t.Fatalf("diagnostics should contain unmet version constraint, but is nil")
   406  	}
   407  
   408  	meta.showDiagnostics(diags)
   409  
   410  	// Required version diags are correct
   411  	errStr := ui.ErrorWriter.String()
   412  	if !strings.Contains(errStr, `required_version = "~> 0.9.0"`) {
   413  		t.Fatalf("output should point to unmet version constraint, but is:\n\n%s", errStr)
   414  	}
   415  	if strings.Contains(errStr, `required_version = ">= 0.13.0"`) {
   416  		t.Fatalf("output should not point to met version constraint, but is:\n\n%s", errStr)
   417  	}
   418  }