github.com/opentofu/opentofu@v1.7.1/internal/command/meta_test.go (about)

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