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

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package cliconfig
     5  
     6  import (
     7  	"os"
     8  	"path/filepath"
     9  	"reflect"
    10  	"testing"
    11  
    12  	"github.com/davecgh/go-spew/spew"
    13  	"github.com/google/go-cmp/cmp"
    14  	"github.com/terramate-io/tf/tfdiags"
    15  )
    16  
    17  // This is the directory where our test fixtures are.
    18  const fixtureDir = "./testdata"
    19  
    20  func TestLoadConfig(t *testing.T) {
    21  	c, err := loadConfigFile(filepath.Join(fixtureDir, "config"))
    22  	if err != nil {
    23  		t.Fatalf("err: %s", err)
    24  	}
    25  
    26  	expected := &Config{
    27  		Providers: map[string]string{
    28  			"aws": "foo",
    29  			"do":  "bar",
    30  		},
    31  	}
    32  
    33  	if !reflect.DeepEqual(c, expected) {
    34  		t.Fatalf("bad: %#v", c)
    35  	}
    36  }
    37  
    38  func TestLoadConfig_envSubst(t *testing.T) {
    39  	defer os.Unsetenv("TFTEST")
    40  	os.Setenv("TFTEST", "hello")
    41  
    42  	c, err := loadConfigFile(filepath.Join(fixtureDir, "config-env"))
    43  	if err != nil {
    44  		t.Fatalf("err: %s", err)
    45  	}
    46  
    47  	expected := &Config{
    48  		Providers: map[string]string{
    49  			"aws":    "hello",
    50  			"google": "bar",
    51  		},
    52  		Provisioners: map[string]string{
    53  			"local": "hello",
    54  		},
    55  	}
    56  
    57  	if !reflect.DeepEqual(c, expected) {
    58  		t.Fatalf("bad: %#v", c)
    59  	}
    60  }
    61  
    62  func TestLoadConfig_non_existing_file(t *testing.T) {
    63  	tmpDir := os.TempDir()
    64  	cliTmpFile := filepath.Join(tmpDir, "dev.tfrc")
    65  
    66  	os.Setenv("TF_CLI_CONFIG_FILE", cliTmpFile)
    67  	defer os.Unsetenv("TF_CLI_CONFIG_FILE")
    68  
    69  	c, errs := LoadConfig()
    70  	if errs.HasErrors() || c.Validate().HasErrors() {
    71  		t.Fatalf("err: %s", errs)
    72  	}
    73  
    74  	hasOpenFileWarn := false
    75  	for _, err := range errs {
    76  		if err.Severity() == tfdiags.Warning && err.Description().Summary == "Unable to open CLI configuration file" {
    77  			hasOpenFileWarn = true
    78  			break
    79  		}
    80  	}
    81  
    82  	if !hasOpenFileWarn {
    83  		t.Fatal("expecting a warning message because of nonexisting CLI configuration file")
    84  	}
    85  }
    86  
    87  func TestEnvConfig(t *testing.T) {
    88  	tests := map[string]struct {
    89  		env  map[string]string
    90  		want *Config
    91  	}{
    92  		"no environment variables": {
    93  			nil,
    94  			&Config{},
    95  		},
    96  		"TF_PLUGIN_CACHE_DIR=boop": {
    97  			map[string]string{
    98  				"TF_PLUGIN_CACHE_DIR": "boop",
    99  			},
   100  			&Config{
   101  				PluginCacheDir: "boop",
   102  			},
   103  		},
   104  		"TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE=anything_except_zero": {
   105  			map[string]string{
   106  				"TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE": "anything_except_zero",
   107  			},
   108  			&Config{
   109  				PluginCacheMayBreakDependencyLockFile: true,
   110  			},
   111  		},
   112  		"TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE=0": {
   113  			map[string]string{
   114  				"TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE": "0",
   115  			},
   116  			&Config{},
   117  		},
   118  		"TF_PLUGIN_CACHE_DIR and TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE": {
   119  			map[string]string{
   120  				"TF_PLUGIN_CACHE_DIR":                            "beep",
   121  				"TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE": "1",
   122  			},
   123  			&Config{
   124  				PluginCacheDir:                        "beep",
   125  				PluginCacheMayBreakDependencyLockFile: true,
   126  			},
   127  		},
   128  	}
   129  
   130  	for name, test := range tests {
   131  		t.Run(name, func(t *testing.T) {
   132  			got := envConfig(test.env)
   133  			want := test.want
   134  
   135  			if diff := cmp.Diff(want, got); diff != "" {
   136  				t.Errorf("wrong result\n%s", diff)
   137  			}
   138  		})
   139  	}
   140  }
   141  
   142  func TestMakeEnvMap(t *testing.T) {
   143  	tests := map[string]struct {
   144  		environ []string
   145  		want    map[string]string
   146  	}{
   147  		"nil": {
   148  			nil,
   149  			nil,
   150  		},
   151  		"one": {
   152  			[]string{
   153  				"FOO=bar",
   154  			},
   155  			map[string]string{
   156  				"FOO": "bar",
   157  			},
   158  		},
   159  		"many": {
   160  			[]string{
   161  				"FOO=1",
   162  				"BAR=2",
   163  				"BAZ=3",
   164  			},
   165  			map[string]string{
   166  				"FOO": "1",
   167  				"BAR": "2",
   168  				"BAZ": "3",
   169  			},
   170  		},
   171  		"conflict": {
   172  			[]string{
   173  				"FOO=1",
   174  				"BAR=1",
   175  				"FOO=2",
   176  			},
   177  			map[string]string{
   178  				"BAR": "1",
   179  				"FOO": "2", // Last entry of each name wins
   180  			},
   181  		},
   182  		"empty_val": {
   183  			[]string{
   184  				"FOO=",
   185  			},
   186  			map[string]string{
   187  				"FOO": "",
   188  			},
   189  		},
   190  		"no_equals": {
   191  			[]string{
   192  				"FOO=bar",
   193  				"INVALID",
   194  			},
   195  			map[string]string{
   196  				"FOO": "bar",
   197  			},
   198  		},
   199  		"multi_equals": {
   200  			[]string{
   201  				"FOO=bar=baz=boop",
   202  			},
   203  			map[string]string{
   204  				"FOO": "bar=baz=boop",
   205  			},
   206  		},
   207  	}
   208  
   209  	for name, test := range tests {
   210  		t.Run(name, func(t *testing.T) {
   211  			got := makeEnvMap(test.environ)
   212  			want := test.want
   213  
   214  			if diff := cmp.Diff(want, got); diff != "" {
   215  				t.Errorf("wrong result\n%s", diff)
   216  			}
   217  		})
   218  	}
   219  
   220  }
   221  
   222  func TestLoadConfig_hosts(t *testing.T) {
   223  	got, diags := loadConfigFile(filepath.Join(fixtureDir, "hosts"))
   224  	if len(diags) != 0 {
   225  		t.Fatalf("%s", diags.Err())
   226  	}
   227  
   228  	want := &Config{
   229  		Hosts: map[string]*ConfigHost{
   230  			"example.com": {
   231  				Services: map[string]interface{}{
   232  					"modules.v1": "https://example.com/",
   233  				},
   234  			},
   235  		},
   236  	}
   237  
   238  	if !reflect.DeepEqual(got, want) {
   239  		t.Errorf("wrong result\ngot:  %swant: %s", spew.Sdump(got), spew.Sdump(want))
   240  	}
   241  }
   242  
   243  func TestLoadConfig_credentials(t *testing.T) {
   244  	got, err := loadConfigFile(filepath.Join(fixtureDir, "credentials"))
   245  	if err != nil {
   246  		t.Fatal(err)
   247  	}
   248  
   249  	want := &Config{
   250  		Credentials: map[string]map[string]interface{}{
   251  			"example.com": map[string]interface{}{
   252  				"token": "foo the bar baz",
   253  			},
   254  			"example.net": map[string]interface{}{
   255  				"username": "foo",
   256  				"password": "baz",
   257  			},
   258  		},
   259  		CredentialsHelpers: map[string]*ConfigCredentialsHelper{
   260  			"foo": &ConfigCredentialsHelper{
   261  				Args: []string{"bar", "baz"},
   262  			},
   263  		},
   264  	}
   265  
   266  	if !reflect.DeepEqual(got, want) {
   267  		t.Errorf("wrong result\ngot:  %swant: %s", spew.Sdump(got), spew.Sdump(want))
   268  	}
   269  }
   270  
   271  func TestConfigValidate(t *testing.T) {
   272  	tests := map[string]struct {
   273  		Config    *Config
   274  		DiagCount int
   275  	}{
   276  		"nil": {
   277  			nil,
   278  			0,
   279  		},
   280  		"empty": {
   281  			&Config{},
   282  			0,
   283  		},
   284  		"host good": {
   285  			&Config{
   286  				Hosts: map[string]*ConfigHost{
   287  					"example.com": {},
   288  				},
   289  			},
   290  			0,
   291  		},
   292  		"host with bad hostname": {
   293  			&Config{
   294  				Hosts: map[string]*ConfigHost{
   295  					"example..com": {},
   296  				},
   297  			},
   298  			1, // host block has invalid hostname
   299  		},
   300  		"credentials good": {
   301  			&Config{
   302  				Credentials: map[string]map[string]interface{}{
   303  					"example.com": map[string]interface{}{
   304  						"token": "foo",
   305  					},
   306  				},
   307  			},
   308  			0,
   309  		},
   310  		"credentials with bad hostname": {
   311  			&Config{
   312  				Credentials: map[string]map[string]interface{}{
   313  					"example..com": map[string]interface{}{
   314  						"token": "foo",
   315  					},
   316  				},
   317  			},
   318  			1, // credentials block has invalid hostname
   319  		},
   320  		"credentials helper good": {
   321  			&Config{
   322  				CredentialsHelpers: map[string]*ConfigCredentialsHelper{
   323  					"foo": {},
   324  				},
   325  			},
   326  			0,
   327  		},
   328  		"credentials helper too many": {
   329  			&Config{
   330  				CredentialsHelpers: map[string]*ConfigCredentialsHelper{
   331  					"foo": {},
   332  					"bar": {},
   333  				},
   334  			},
   335  			1, // no more than one credentials_helper block allowed
   336  		},
   337  		"provider_installation good none": {
   338  			&Config{
   339  				ProviderInstallation: nil,
   340  			},
   341  			0,
   342  		},
   343  		"provider_installation good one": {
   344  			&Config{
   345  				ProviderInstallation: []*ProviderInstallation{
   346  					{},
   347  				},
   348  			},
   349  			0,
   350  		},
   351  		"provider_installation too many": {
   352  			&Config{
   353  				ProviderInstallation: []*ProviderInstallation{
   354  					{},
   355  					{},
   356  				},
   357  			},
   358  			1, // no more than one provider_installation block allowed
   359  		},
   360  		"plugin_cache_dir does not exist": {
   361  			&Config{
   362  				PluginCacheDir: "fake",
   363  			},
   364  			1, // The specified plugin cache dir %s cannot be opened
   365  		},
   366  	}
   367  
   368  	for name, test := range tests {
   369  		t.Run(name, func(t *testing.T) {
   370  			diags := test.Config.Validate()
   371  			if len(diags) != test.DiagCount {
   372  				t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
   373  				for _, diag := range diags {
   374  					t.Logf("- %#v", diag.Description())
   375  				}
   376  			}
   377  		})
   378  	}
   379  }
   380  
   381  func TestConfig_Merge(t *testing.T) {
   382  	c1 := &Config{
   383  		Providers: map[string]string{
   384  			"foo": "bar",
   385  			"bar": "blah",
   386  		},
   387  		Provisioners: map[string]string{
   388  			"local":  "local",
   389  			"remote": "bad",
   390  		},
   391  		Hosts: map[string]*ConfigHost{
   392  			"example.com": {
   393  				Services: map[string]interface{}{
   394  					"modules.v1": "http://example.com/",
   395  				},
   396  			},
   397  		},
   398  		Credentials: map[string]map[string]interface{}{
   399  			"foo": {
   400  				"bar": "baz",
   401  			},
   402  		},
   403  		CredentialsHelpers: map[string]*ConfigCredentialsHelper{
   404  			"buz": {},
   405  		},
   406  		ProviderInstallation: []*ProviderInstallation{
   407  			{
   408  				Methods: []*ProviderInstallationMethod{
   409  					{Location: ProviderInstallationFilesystemMirror("a")},
   410  					{Location: ProviderInstallationFilesystemMirror("b")},
   411  				},
   412  			},
   413  			{
   414  				Methods: []*ProviderInstallationMethod{
   415  					{Location: ProviderInstallationFilesystemMirror("c")},
   416  				},
   417  			},
   418  		},
   419  	}
   420  
   421  	c2 := &Config{
   422  		Providers: map[string]string{
   423  			"bar": "baz",
   424  			"baz": "what",
   425  		},
   426  		Provisioners: map[string]string{
   427  			"remote": "remote",
   428  		},
   429  		Hosts: map[string]*ConfigHost{
   430  			"example.net": {
   431  				Services: map[string]interface{}{
   432  					"modules.v1": "https://example.net/",
   433  				},
   434  			},
   435  		},
   436  		Credentials: map[string]map[string]interface{}{
   437  			"fee": {
   438  				"bur": "bez",
   439  			},
   440  		},
   441  		CredentialsHelpers: map[string]*ConfigCredentialsHelper{
   442  			"biz": {},
   443  		},
   444  		ProviderInstallation: []*ProviderInstallation{
   445  			{
   446  				Methods: []*ProviderInstallationMethod{
   447  					{Location: ProviderInstallationFilesystemMirror("d")},
   448  				},
   449  			},
   450  		},
   451  		PluginCacheMayBreakDependencyLockFile: true,
   452  	}
   453  
   454  	expected := &Config{
   455  		Providers: map[string]string{
   456  			"foo": "bar",
   457  			"bar": "baz",
   458  			"baz": "what",
   459  		},
   460  		Provisioners: map[string]string{
   461  			"local":  "local",
   462  			"remote": "remote",
   463  		},
   464  		Hosts: map[string]*ConfigHost{
   465  			"example.com": {
   466  				Services: map[string]interface{}{
   467  					"modules.v1": "http://example.com/",
   468  				},
   469  			},
   470  			"example.net": {
   471  				Services: map[string]interface{}{
   472  					"modules.v1": "https://example.net/",
   473  				},
   474  			},
   475  		},
   476  		Credentials: map[string]map[string]interface{}{
   477  			"foo": {
   478  				"bar": "baz",
   479  			},
   480  			"fee": {
   481  				"bur": "bez",
   482  			},
   483  		},
   484  		CredentialsHelpers: map[string]*ConfigCredentialsHelper{
   485  			"buz": {},
   486  			"biz": {},
   487  		},
   488  		ProviderInstallation: []*ProviderInstallation{
   489  			{
   490  				Methods: []*ProviderInstallationMethod{
   491  					{Location: ProviderInstallationFilesystemMirror("a")},
   492  					{Location: ProviderInstallationFilesystemMirror("b")},
   493  				},
   494  			},
   495  			{
   496  				Methods: []*ProviderInstallationMethod{
   497  					{Location: ProviderInstallationFilesystemMirror("c")},
   498  				},
   499  			},
   500  			{
   501  				Methods: []*ProviderInstallationMethod{
   502  					{Location: ProviderInstallationFilesystemMirror("d")},
   503  				},
   504  			},
   505  		},
   506  		PluginCacheMayBreakDependencyLockFile: true,
   507  	}
   508  
   509  	actual := c1.Merge(c2)
   510  	if diff := cmp.Diff(expected, actual); diff != "" {
   511  		t.Fatalf("wrong result\n%s", diff)
   512  	}
   513  }
   514  
   515  func TestConfig_Merge_disableCheckpoint(t *testing.T) {
   516  	c1 := &Config{
   517  		DisableCheckpoint: true,
   518  	}
   519  
   520  	c2 := &Config{}
   521  
   522  	expected := &Config{
   523  		Providers:         map[string]string{},
   524  		Provisioners:      map[string]string{},
   525  		DisableCheckpoint: true,
   526  	}
   527  
   528  	actual := c1.Merge(c2)
   529  	if !reflect.DeepEqual(actual, expected) {
   530  		t.Fatalf("bad: %#v", actual)
   531  	}
   532  }
   533  
   534  func TestConfig_Merge_disableCheckpointSignature(t *testing.T) {
   535  	c1 := &Config{
   536  		DisableCheckpointSignature: true,
   537  	}
   538  
   539  	c2 := &Config{}
   540  
   541  	expected := &Config{
   542  		Providers:                  map[string]string{},
   543  		Provisioners:               map[string]string{},
   544  		DisableCheckpointSignature: true,
   545  	}
   546  
   547  	actual := c1.Merge(c2)
   548  	if !reflect.DeepEqual(actual, expected) {
   549  		t.Fatalf("bad: %#v", actual)
   550  	}
   551  }