github.com/trawler/terraform@v0.10.8-0.20171106022149-4b1c7a1d9b48/config/loader_test.go (about)

     1  package config
     2  
     3  import (
     4  	"io/ioutil"
     5  	"path/filepath"
     6  	"reflect"
     7  	"strings"
     8  	"testing"
     9  )
    10  
    11  func TestErrNoConfigsFound_impl(t *testing.T) {
    12  	var _ error = new(ErrNoConfigsFound)
    13  }
    14  
    15  func TestIsEmptyDir(t *testing.T) {
    16  	val, err := IsEmptyDir(fixtureDir)
    17  	if err != nil {
    18  		t.Fatalf("err: %s", err)
    19  	}
    20  	if val {
    21  		t.Fatal("should not be empty")
    22  	}
    23  }
    24  
    25  func TestIsEmptyDir_noExist(t *testing.T) {
    26  	val, err := IsEmptyDir(filepath.Join(fixtureDir, "nopenopenope"))
    27  	if err != nil {
    28  		t.Fatalf("err: %s", err)
    29  	}
    30  	if !val {
    31  		t.Fatal("should be empty")
    32  	}
    33  }
    34  
    35  func TestIsEmptyDir_noConfigs(t *testing.T) {
    36  	val, err := IsEmptyDir(filepath.Join(fixtureDir, "dir-empty"))
    37  	if err != nil {
    38  		t.Fatalf("err: %s", err)
    39  	}
    40  	if !val {
    41  		t.Fatal("should be empty")
    42  	}
    43  }
    44  
    45  func TestLoadFile_badType(t *testing.T) {
    46  	_, err := LoadFile(filepath.Join(fixtureDir, "bad_type.tf.nope"))
    47  	if err == nil {
    48  		t.Fatal("should have error")
    49  	}
    50  }
    51  
    52  func TestLoadFile_gitCrypt(t *testing.T) {
    53  	_, err := LoadFile(filepath.Join(fixtureDir, "git-crypt.tf"))
    54  	if err == nil {
    55  		t.Fatal("should have error")
    56  	}
    57  
    58  	t.Logf("err: %s", err)
    59  }
    60  
    61  func TestLoadFile_lifecycleKeyCheck(t *testing.T) {
    62  	_, err := LoadFile(filepath.Join(fixtureDir, "lifecycle_cbd_typo.tf"))
    63  	if err == nil {
    64  		t.Fatal("should have error")
    65  	}
    66  
    67  	t.Logf("err: %s", err)
    68  }
    69  
    70  func TestLoadFile_varInvalidKey(t *testing.T) {
    71  	_, err := LoadFile(filepath.Join(fixtureDir, "var-invalid-key.tf"))
    72  	if err == nil {
    73  		t.Fatal("should have error")
    74  	}
    75  }
    76  
    77  func TestLoadFile_resourceArityMistake(t *testing.T) {
    78  	_, err := LoadFile(filepath.Join(fixtureDir, "resource-arity-mistake.tf"))
    79  	if err == nil {
    80  		t.Fatal("should have error")
    81  	}
    82  	expected := "Error loading test-fixtures/resource-arity-mistake.tf: position 2:10: resource must be followed by exactly two strings, a type and a name"
    83  	if err.Error() != expected {
    84  		t.Fatalf("expected:\n%s\ngot:\n%s", expected, err)
    85  	}
    86  }
    87  
    88  func TestLoadFile_resourceMultiLifecycle(t *testing.T) {
    89  	_, err := LoadFile(filepath.Join(fixtureDir, "resource-multi-lifecycle.tf"))
    90  	if err == nil {
    91  		t.Fatal("should have error")
    92  	}
    93  }
    94  
    95  func TestLoadFile_dataSourceArityMistake(t *testing.T) {
    96  	_, err := LoadFile(filepath.Join(fixtureDir, "data-source-arity-mistake.tf"))
    97  	if err == nil {
    98  		t.Fatal("should have error")
    99  	}
   100  	expected := "Error loading test-fixtures/data-source-arity-mistake.tf: position 2:6: 'data' must be followed by exactly two strings: a type and a name"
   101  	if err.Error() != expected {
   102  		t.Fatalf("expected:\n%s\ngot:\n%s", expected, err)
   103  	}
   104  }
   105  
   106  func TestLoadFileWindowsLineEndings(t *testing.T) {
   107  	testFile := filepath.Join(fixtureDir, "windows-line-endings.tf")
   108  
   109  	contents, err := ioutil.ReadFile(testFile)
   110  	if err != nil {
   111  		t.Fatalf("err: %s", err)
   112  	}
   113  	if !strings.Contains(string(contents), "\r\n") {
   114  		t.Fatalf("Windows line endings test file %s contains no windows line endings - this may be an autocrlf related issue.", testFile)
   115  	}
   116  
   117  	c, err := LoadFile(testFile)
   118  	if err != nil {
   119  		t.Fatalf("err: %s", err)
   120  	}
   121  
   122  	if c == nil {
   123  		t.Fatal("config should not be nil")
   124  	}
   125  
   126  	if c.Dir != "" {
   127  		t.Fatalf("bad: %#v", c.Dir)
   128  	}
   129  
   130  	actual := resourcesStr(c.Resources)
   131  	if actual != strings.TrimSpace(windowsHeredocResourcesStr) {
   132  		t.Fatalf("bad:\n%s", actual)
   133  	}
   134  }
   135  
   136  func TestLoadFileHeredoc(t *testing.T) {
   137  	c, err := LoadFile(filepath.Join(fixtureDir, "heredoc.tf"))
   138  	if err != nil {
   139  		t.Fatalf("err: %s", err)
   140  	}
   141  
   142  	if c == nil {
   143  		t.Fatal("config should not be nil")
   144  	}
   145  
   146  	if c.Dir != "" {
   147  		t.Fatalf("bad: %#v", c.Dir)
   148  	}
   149  
   150  	actual := providerConfigsStr(c.ProviderConfigs)
   151  	if actual != strings.TrimSpace(heredocProvidersStr) {
   152  		t.Fatalf("bad:\n%s", actual)
   153  	}
   154  
   155  	actual = resourcesStr(c.Resources)
   156  	if actual != strings.TrimSpace(heredocResourcesStr) {
   157  		t.Fatalf("bad:\n%s", actual)
   158  	}
   159  }
   160  
   161  func TestLoadFileEscapedQuotes(t *testing.T) {
   162  	_, err := LoadFile(filepath.Join(fixtureDir, "escapedquotes.tf"))
   163  	if err == nil {
   164  		t.Fatalf("expected syntax error as escaped quotes are no longer supported")
   165  	}
   166  
   167  	if !strings.Contains(err.Error(), "parse error") {
   168  		t.Fatalf("expected \"syntax error\", got: %s", err)
   169  	}
   170  }
   171  
   172  func TestLoadFileBasic(t *testing.T) {
   173  	c, err := LoadFile(filepath.Join(fixtureDir, "basic.tf"))
   174  	if err != nil {
   175  		t.Fatalf("err: %s", err)
   176  	}
   177  
   178  	if c == nil {
   179  		t.Fatal("config should not be nil")
   180  	}
   181  
   182  	if c.Dir != "" {
   183  		t.Fatalf("wrong dir %#v; want %#v", c.Dir, "")
   184  	}
   185  
   186  	expectedTF := &Terraform{RequiredVersion: "foo"}
   187  	if !reflect.DeepEqual(c.Terraform, expectedTF) {
   188  		t.Fatalf("wrong terraform block %#v; want %#v", c.Terraform, expectedTF)
   189  	}
   190  
   191  	expectedAtlas := &AtlasConfig{Name: "mitchellh/foo"}
   192  	if !reflect.DeepEqual(c.Atlas, expectedAtlas) {
   193  		t.Fatalf("wrong atlas config %#v; want %#v", c.Atlas, expectedAtlas)
   194  	}
   195  
   196  	actual := variablesStr(c.Variables)
   197  	if actual != strings.TrimSpace(basicVariablesStr) {
   198  		t.Fatalf("bad:\n%s", actual)
   199  	}
   200  
   201  	actual = providerConfigsStr(c.ProviderConfigs)
   202  	if actual != strings.TrimSpace(basicProvidersStr) {
   203  		t.Fatalf("bad:\n%s", actual)
   204  	}
   205  
   206  	actual = resourcesStr(c.Resources)
   207  	if actual != strings.TrimSpace(basicResourcesStr) {
   208  		t.Fatalf("bad:\n%s", actual)
   209  	}
   210  
   211  	if actual, want := localsStr(c.Locals), strings.TrimSpace(basicLocalsStr); actual != want {
   212  		t.Fatalf("wrong locals:\n%s\nwant:\n%s", actual, want)
   213  	}
   214  
   215  	actual = outputsStr(c.Outputs)
   216  	if actual != strings.TrimSpace(basicOutputsStr) {
   217  		t.Fatalf("bad:\n%s", actual)
   218  	}
   219  }
   220  
   221  func TestLoadFileBasic_empty(t *testing.T) {
   222  	c, err := LoadFile(filepath.Join(fixtureDir, "empty.tf"))
   223  	if err != nil {
   224  		t.Fatalf("err: %s", err)
   225  	}
   226  
   227  	if c == nil {
   228  		t.Fatal("config should not be nil")
   229  	}
   230  }
   231  
   232  func TestLoadFileBasic_import(t *testing.T) {
   233  	// Skip because we disabled importing
   234  	t.Skip()
   235  
   236  	c, err := LoadFile(filepath.Join(fixtureDir, "import.tf"))
   237  	if err != nil {
   238  		t.Fatalf("err: %s", err)
   239  	}
   240  
   241  	if c == nil {
   242  		t.Fatal("config should not be nil")
   243  	}
   244  
   245  	actual := variablesStr(c.Variables)
   246  	if actual != strings.TrimSpace(importVariablesStr) {
   247  		t.Fatalf("bad:\n%s", actual)
   248  	}
   249  
   250  	actual = providerConfigsStr(c.ProviderConfigs)
   251  	if actual != strings.TrimSpace(importProvidersStr) {
   252  		t.Fatalf("bad:\n%s", actual)
   253  	}
   254  
   255  	actual = resourcesStr(c.Resources)
   256  	if actual != strings.TrimSpace(importResourcesStr) {
   257  		t.Fatalf("bad:\n%s", actual)
   258  	}
   259  }
   260  
   261  func TestLoadFileBasic_json(t *testing.T) {
   262  	c, err := LoadFile(filepath.Join(fixtureDir, "basic.tf.json"))
   263  	if err != nil {
   264  		t.Fatalf("err: %s", err)
   265  	}
   266  
   267  	if c == nil {
   268  		t.Fatal("config should not be nil")
   269  	}
   270  
   271  	if c.Dir != "" {
   272  		t.Fatalf("bad: %#v", c.Dir)
   273  	}
   274  
   275  	expectedAtlas := &AtlasConfig{Name: "mitchellh/foo"}
   276  	if !reflect.DeepEqual(c.Atlas, expectedAtlas) {
   277  		t.Fatalf("bad: %#v", c.Atlas)
   278  	}
   279  
   280  	actual := variablesStr(c.Variables)
   281  	if actual != strings.TrimSpace(basicVariablesStr) {
   282  		t.Fatalf("bad:\n%s", actual)
   283  	}
   284  
   285  	actual = providerConfigsStr(c.ProviderConfigs)
   286  	if actual != strings.TrimSpace(basicProvidersStr) {
   287  		t.Fatalf("bad:\n%s", actual)
   288  	}
   289  
   290  	actual = resourcesStr(c.Resources)
   291  	if actual != strings.TrimSpace(basicResourcesStr) {
   292  		t.Fatalf("bad:\n%s", actual)
   293  	}
   294  
   295  	if actual, want := localsStr(c.Locals), strings.TrimSpace(basicLocalsStr); actual != want {
   296  		t.Fatalf("wrong locals:\n%s\nwant:\n%s", actual, want)
   297  	}
   298  
   299  	actual = outputsStr(c.Outputs)
   300  	if actual != strings.TrimSpace(basicOutputsStr) {
   301  		t.Fatalf("bad:\n%s", actual)
   302  	}
   303  }
   304  
   305  func TestLoadFileBasic_modules(t *testing.T) {
   306  	c, err := LoadFile(filepath.Join(fixtureDir, "modules.tf"))
   307  	if err != nil {
   308  		t.Fatalf("err: %s", err)
   309  	}
   310  
   311  	if c == nil {
   312  		t.Fatal("config should not be nil")
   313  	}
   314  
   315  	if c.Dir != "" {
   316  		t.Fatalf("bad: %#v", c.Dir)
   317  	}
   318  
   319  	actual := modulesStr(c.Modules)
   320  	if actual != strings.TrimSpace(modulesModulesStr) {
   321  		t.Fatalf("bad:\n%s", actual)
   322  	}
   323  }
   324  
   325  func TestLoadFile_unnamedModule(t *testing.T) {
   326  	_, err := LoadFile(filepath.Join(fixtureDir, "module-unnamed.tf"))
   327  	if err == nil {
   328  		t.Fatalf("bad: expected error")
   329  	}
   330  
   331  	errorStr := err.Error()
   332  	if !strings.Contains(errorStr, `"module" must be followed`) {
   333  		t.Fatalf("bad: expected error has wrong text: %s", errorStr)
   334  	}
   335  }
   336  
   337  func TestLoadFile_outputDependsOn(t *testing.T) {
   338  	c, err := LoadFile(filepath.Join(fixtureDir, "output-depends-on.tf"))
   339  	if err != nil {
   340  		t.Fatalf("err: %s", err)
   341  	}
   342  
   343  	if c == nil {
   344  		t.Fatal("config should not be nil")
   345  	}
   346  
   347  	if c.Dir != "" {
   348  		t.Fatalf("bad: %#v", c.Dir)
   349  	}
   350  
   351  	actual := outputsStr(c.Outputs)
   352  	if actual != strings.TrimSpace(outputDependsOnStr) {
   353  		t.Fatalf("bad:\n%s", actual)
   354  	}
   355  }
   356  
   357  func TestLoadFile_terraformBackend(t *testing.T) {
   358  	c, err := LoadFile(filepath.Join(fixtureDir, "terraform-backend.tf"))
   359  	if err != nil {
   360  		t.Fatalf("err: %s", err)
   361  	}
   362  
   363  	if c == nil {
   364  		t.Fatal("config should not be nil")
   365  	}
   366  
   367  	if c.Dir != "" {
   368  		t.Fatalf("bad: %#v", c.Dir)
   369  	}
   370  
   371  	{
   372  		actual := terraformStr(c.Terraform)
   373  		expected := strings.TrimSpace(`
   374  backend (s3)
   375    foo`)
   376  		if actual != expected {
   377  			t.Fatalf("bad:\n%s", actual)
   378  		}
   379  	}
   380  }
   381  
   382  func TestLoadFile_terraformBackendJSON(t *testing.T) {
   383  	c, err := LoadFile(filepath.Join(fixtureDir, "terraform-backend.tf.json"))
   384  	if err != nil {
   385  		t.Fatalf("err: %s", err)
   386  	}
   387  
   388  	if c == nil {
   389  		t.Fatal("config should not be nil")
   390  	}
   391  
   392  	if c.Dir != "" {
   393  		t.Fatalf("bad: %#v", c.Dir)
   394  	}
   395  
   396  	{
   397  		actual := terraformStr(c.Terraform)
   398  		expected := strings.TrimSpace(`
   399  backend (s3)
   400    foo`)
   401  		if actual != expected {
   402  			t.Fatalf("bad:\n%s", actual)
   403  		}
   404  	}
   405  }
   406  
   407  // test that the alternate, more obvious JSON format also decodes properly
   408  func TestLoadFile_terraformBackendJSON2(t *testing.T) {
   409  	c, err := LoadFile(filepath.Join(fixtureDir, "terraform-backend-2.tf.json"))
   410  	if err != nil {
   411  		t.Fatalf("err: %s", err)
   412  	}
   413  
   414  	if c == nil {
   415  		t.Fatal("config should not be nil")
   416  	}
   417  
   418  	if c.Dir != "" {
   419  		t.Fatalf("bad: %#v", c.Dir)
   420  	}
   421  
   422  	{
   423  		actual := terraformStr(c.Terraform)
   424  		expected := strings.TrimSpace(`
   425  backend (s3)
   426    foo`)
   427  		if actual != expected {
   428  			t.Fatalf("bad:\n%s", actual)
   429  		}
   430  	}
   431  }
   432  
   433  func TestLoadFile_terraformBackendMulti(t *testing.T) {
   434  	_, err := LoadFile(filepath.Join(fixtureDir, "terraform-backend-multi.tf"))
   435  	if err == nil {
   436  		t.Fatal("expected error")
   437  	}
   438  
   439  	errorStr := err.Error()
   440  	if !strings.Contains(errorStr, "only one 'backend'") {
   441  		t.Fatalf("bad: expected error has wrong text: %s", errorStr)
   442  	}
   443  }
   444  
   445  func TestLoadJSONBasic(t *testing.T) {
   446  	raw, err := ioutil.ReadFile(filepath.Join(fixtureDir, "basic.tf.json"))
   447  	if err != nil {
   448  		t.Fatalf("err: %s", err)
   449  	}
   450  
   451  	c, err := LoadJSON(raw)
   452  	if err != nil {
   453  		t.Fatalf("err: %s", err)
   454  	}
   455  
   456  	if c == nil {
   457  		t.Fatal("config should not be nil")
   458  	}
   459  
   460  	if c.Dir != "" {
   461  		t.Fatalf("bad: %#v", c.Dir)
   462  	}
   463  
   464  	expectedAtlas := &AtlasConfig{Name: "mitchellh/foo"}
   465  	if !reflect.DeepEqual(c.Atlas, expectedAtlas) {
   466  		t.Fatalf("bad: %#v", c.Atlas)
   467  	}
   468  
   469  	actual := variablesStr(c.Variables)
   470  	if actual != strings.TrimSpace(basicVariablesStr) {
   471  		t.Fatalf("bad:\n%s", actual)
   472  	}
   473  
   474  	actual = providerConfigsStr(c.ProviderConfigs)
   475  	if actual != strings.TrimSpace(basicProvidersStr) {
   476  		t.Fatalf("bad:\n%s", actual)
   477  	}
   478  
   479  	actual = resourcesStr(c.Resources)
   480  	if actual != strings.TrimSpace(basicResourcesStr) {
   481  		t.Fatalf("bad:\n%s", actual)
   482  	}
   483  
   484  	actual = outputsStr(c.Outputs)
   485  	if actual != strings.TrimSpace(basicOutputsStr) {
   486  		t.Fatalf("bad:\n%s", actual)
   487  	}
   488  }
   489  
   490  func TestLoadJSONAmbiguous(t *testing.T) {
   491  	js := `
   492  {
   493    "variable": {
   494      "first": {
   495        "default": {
   496          "key": "val"
   497        }
   498      },
   499      "second": {
   500        "description": "Described",
   501        "default": {
   502          "key": "val"
   503        }
   504      }
   505    }
   506  }
   507  `
   508  
   509  	c, err := LoadJSON([]byte(js))
   510  	if err != nil {
   511  		t.Fatalf("err: %s", err)
   512  	}
   513  
   514  	if len(c.Variables) != 2 {
   515  		t.Fatal("config should have 2 variables, found", len(c.Variables))
   516  	}
   517  
   518  	first := &Variable{
   519  		Name:    "first",
   520  		Default: map[string]interface{}{"key": "val"},
   521  	}
   522  	second := &Variable{
   523  		Name:        "second",
   524  		Description: "Described",
   525  		Default:     map[string]interface{}{"key": "val"},
   526  	}
   527  
   528  	if !reflect.DeepEqual(first, c.Variables[0]) {
   529  		t.Fatalf("\nexpected: %#v\ngot:      %#v", first, c.Variables[0])
   530  	}
   531  
   532  	if !reflect.DeepEqual(second, c.Variables[1]) {
   533  		t.Fatalf("\nexpected: %#v\ngot:      %#v", second, c.Variables[1])
   534  	}
   535  }
   536  
   537  func TestLoadFileBasic_jsonNoName(t *testing.T) {
   538  	c, err := LoadFile(filepath.Join(fixtureDir, "resource-no-name.tf.json"))
   539  	if err != nil {
   540  		t.Fatalf("err: %s", err)
   541  	}
   542  
   543  	if c == nil {
   544  		t.Fatal("config should not be nil")
   545  	}
   546  
   547  	actual := resourcesStr(c.Resources)
   548  	if actual != strings.TrimSpace(basicJsonNoNameResourcesStr) {
   549  		t.Fatalf("bad:\n%s", actual)
   550  	}
   551  }
   552  
   553  func TestLoadFile_variables(t *testing.T) {
   554  	c, err := LoadFile(filepath.Join(fixtureDir, "variables.tf"))
   555  	if err != nil {
   556  		t.Fatalf("err: %s", err)
   557  	}
   558  	if c == nil {
   559  		t.Fatal("config should not be nil")
   560  	}
   561  
   562  	if c.Dir != "" {
   563  		t.Fatalf("bad: %#v", c.Dir)
   564  	}
   565  
   566  	actual := variablesStr(c.Variables)
   567  	if actual != strings.TrimSpace(variablesVariablesStr) {
   568  		t.Fatalf("bad:\n%s", actual)
   569  	}
   570  }
   571  
   572  func TestLoadDir_basic(t *testing.T) {
   573  	dir := filepath.Join(fixtureDir, "dir-basic")
   574  	c, err := LoadDir(dir)
   575  	if err != nil {
   576  		t.Fatalf("err: %s", err)
   577  	}
   578  
   579  	if c == nil {
   580  		t.Fatal("config should not be nil")
   581  	}
   582  
   583  	dirAbs, err := filepath.Abs(dir)
   584  	if err != nil {
   585  		t.Fatalf("err: %s", err)
   586  	}
   587  	if c.Dir != dirAbs {
   588  		t.Fatalf("bad: %#v", c.Dir)
   589  	}
   590  
   591  	actual := variablesStr(c.Variables)
   592  	if actual != strings.TrimSpace(dirBasicVariablesStr) {
   593  		t.Fatalf("bad:\n%s", actual)
   594  	}
   595  
   596  	actual = providerConfigsStr(c.ProviderConfigs)
   597  	if actual != strings.TrimSpace(dirBasicProvidersStr) {
   598  		t.Fatalf("bad:\n%s", actual)
   599  	}
   600  
   601  	actual = resourcesStr(c.Resources)
   602  	if actual != strings.TrimSpace(dirBasicResourcesStr) {
   603  		t.Fatalf("bad:\n%s", actual)
   604  	}
   605  
   606  	actual = outputsStr(c.Outputs)
   607  	if actual != strings.TrimSpace(dirBasicOutputsStr) {
   608  		t.Fatalf("bad:\n%s", actual)
   609  	}
   610  }
   611  
   612  func TestLoadDir_file(t *testing.T) {
   613  	_, err := LoadDir(filepath.Join(fixtureDir, "variables.tf"))
   614  	if err == nil {
   615  		t.Fatal("should error")
   616  	}
   617  }
   618  
   619  func TestLoadDir_noConfigs(t *testing.T) {
   620  	_, err := LoadDir(filepath.Join(fixtureDir, "dir-empty"))
   621  	if err == nil {
   622  		t.Fatal("should error")
   623  	}
   624  }
   625  
   626  func TestLoadDir_noMerge(t *testing.T) {
   627  	c, err := LoadDir(filepath.Join(fixtureDir, "dir-merge"))
   628  	if err != nil {
   629  		t.Fatalf("err: %s", err)
   630  	}
   631  
   632  	if c == nil {
   633  		t.Fatal("config should not be nil")
   634  	}
   635  
   636  	if err := c.Validate(); err == nil {
   637  		t.Fatal("should not be valid")
   638  	}
   639  }
   640  
   641  func TestLoadDir_override(t *testing.T) {
   642  	c, err := LoadDir(filepath.Join(fixtureDir, "dir-override"))
   643  	if err != nil {
   644  		t.Fatalf("err: %s", err)
   645  	}
   646  
   647  	if c == nil {
   648  		t.Fatal("config should not be nil")
   649  	}
   650  
   651  	actual := variablesStr(c.Variables)
   652  	if actual != strings.TrimSpace(dirOverrideVariablesStr) {
   653  		t.Fatalf("bad:\n%s", actual)
   654  	}
   655  
   656  	actual = providerConfigsStr(c.ProviderConfigs)
   657  	if actual != strings.TrimSpace(dirOverrideProvidersStr) {
   658  		t.Fatalf("bad:\n%s", actual)
   659  	}
   660  
   661  	actual = resourcesStr(c.Resources)
   662  	if actual != strings.TrimSpace(dirOverrideResourcesStr) {
   663  		t.Fatalf("bad:\n%s", actual)
   664  	}
   665  
   666  	actual = outputsStr(c.Outputs)
   667  	if actual != strings.TrimSpace(dirOverrideOutputsStr) {
   668  		t.Fatalf("bad:\n%s", actual)
   669  	}
   670  }
   671  
   672  func TestLoadDir_overrideVar(t *testing.T) {
   673  	c, err := LoadDir(filepath.Join(fixtureDir, "dir-override-var"))
   674  	if err != nil {
   675  		t.Fatalf("err: %s", err)
   676  	}
   677  
   678  	if c == nil {
   679  		t.Fatal("config should not be nil")
   680  	}
   681  
   682  	actual := variablesStr(c.Variables)
   683  	if actual != strings.TrimSpace(dirOverrideVarsVariablesStr) {
   684  		t.Fatalf("bad:\n%s", actual)
   685  	}
   686  }
   687  
   688  func TestLoadFile_mismatchedVariableTypes(t *testing.T) {
   689  	_, err := LoadFile(filepath.Join(fixtureDir, "variable-mismatched-type.tf"))
   690  	if err == nil {
   691  		t.Fatalf("bad: expected error")
   692  	}
   693  
   694  	errorStr := err.Error()
   695  	if !strings.Contains(errorStr, "'not_a_map' has a default value which is not of type 'string'") {
   696  		t.Fatalf("bad: expected error has wrong text: %s", errorStr)
   697  	}
   698  }
   699  
   700  func TestLoadFile_badVariableTypes(t *testing.T) {
   701  	_, err := LoadFile(filepath.Join(fixtureDir, "bad-variable-type.tf"))
   702  	if err == nil {
   703  		t.Fatalf("bad: expected error")
   704  	}
   705  
   706  	errorStr := err.Error()
   707  	if !strings.Contains(errorStr, "'bad_type' type must be one of") {
   708  		t.Fatalf("bad: expected error has wrong text: %s", errorStr)
   709  	}
   710  }
   711  
   712  func TestLoadFile_variableNoName(t *testing.T) {
   713  	_, err := LoadFile(filepath.Join(fixtureDir, "variable-no-name.tf"))
   714  	if err == nil {
   715  		t.Fatalf("bad: expected error")
   716  	}
   717  
   718  	errorStr := err.Error()
   719  	if !strings.Contains(errorStr, `"variable" must be followed`) {
   720  		t.Fatalf("bad: expected error has wrong text: %s", errorStr)
   721  	}
   722  }
   723  
   724  func TestLoadFile_provisioners(t *testing.T) {
   725  	c, err := LoadFile(filepath.Join(fixtureDir, "provisioners.tf"))
   726  	if err != nil {
   727  		t.Fatalf("err: %s", err)
   728  	}
   729  
   730  	if c == nil {
   731  		t.Fatal("config should not be nil")
   732  	}
   733  
   734  	actual := resourcesStr(c.Resources)
   735  	if actual != strings.TrimSpace(provisionerResourcesStr) {
   736  		t.Fatalf("bad:\n%s", actual)
   737  	}
   738  }
   739  
   740  func TestLoadFile_provisionersDestroy(t *testing.T) {
   741  	c, err := LoadFile(filepath.Join(fixtureDir, "provisioners-destroy.tf"))
   742  	if err != nil {
   743  		t.Fatalf("err: %s", err)
   744  	}
   745  
   746  	if c == nil {
   747  		t.Fatal("config should not be nil")
   748  	}
   749  
   750  	actual := resourcesStr(c.Resources)
   751  	if actual != strings.TrimSpace(provisionerDestroyResourcesStr) {
   752  		t.Fatalf("bad:\n%s", actual)
   753  	}
   754  }
   755  
   756  func TestLoadFile_unnamedOutput(t *testing.T) {
   757  	_, err := LoadFile(filepath.Join(fixtureDir, "output-unnamed.tf"))
   758  	if err == nil {
   759  		t.Fatalf("bad: expected error")
   760  	}
   761  
   762  	errorStr := err.Error()
   763  	if !strings.Contains(errorStr, `"output" must be followed`) {
   764  		t.Fatalf("bad: expected error has wrong text: %s", errorStr)
   765  	}
   766  }
   767  
   768  func TestLoadFile_connections(t *testing.T) {
   769  	c, err := LoadFile(filepath.Join(fixtureDir, "connection.tf"))
   770  	if err != nil {
   771  		t.Fatalf("err: %s", err)
   772  	}
   773  
   774  	if c == nil {
   775  		t.Fatal("config should not be nil")
   776  	}
   777  
   778  	actual := resourcesStr(c.Resources)
   779  	if actual != strings.TrimSpace(connectionResourcesStr) {
   780  		t.Fatalf("bad:\n%s", actual)
   781  	}
   782  
   783  	// Check for the connection info
   784  	r := c.Resources[0]
   785  	if r.Name != "web" && r.Type != "aws_instance" {
   786  		t.Fatalf("Bad: %#v", r)
   787  	}
   788  
   789  	p1 := r.Provisioners[0]
   790  	if p1.ConnInfo == nil || len(p1.ConnInfo.Raw) != 2 {
   791  		t.Fatalf("Bad: %#v", p1.ConnInfo)
   792  	}
   793  	if p1.ConnInfo.Raw["user"] != "nobody" {
   794  		t.Fatalf("Bad: %#v", p1.ConnInfo)
   795  	}
   796  
   797  	p2 := r.Provisioners[1]
   798  	if p2.ConnInfo == nil || len(p2.ConnInfo.Raw) != 2 {
   799  		t.Fatalf("Bad: %#v", p2.ConnInfo)
   800  	}
   801  	if p2.ConnInfo.Raw["user"] != "root" {
   802  		t.Fatalf("Bad: %#v", p2.ConnInfo)
   803  	}
   804  }
   805  
   806  func TestLoadFile_createBeforeDestroy(t *testing.T) {
   807  	c, err := LoadFile(filepath.Join(fixtureDir, "create-before-destroy.tf"))
   808  	if err != nil {
   809  		t.Fatalf("err: %s", err)
   810  	}
   811  
   812  	if c == nil {
   813  		t.Fatal("config should not be nil")
   814  	}
   815  
   816  	actual := resourcesStr(c.Resources)
   817  	if actual != strings.TrimSpace(createBeforeDestroyResourcesStr) {
   818  		t.Fatalf("bad:\n%s", actual)
   819  	}
   820  
   821  	// Check for the flag value
   822  	r := c.Resources[0]
   823  	if r.Name != "web" && r.Type != "aws_instance" {
   824  		t.Fatalf("Bad: %#v", r)
   825  	}
   826  
   827  	// Should enable create before destroy
   828  	if !r.Lifecycle.CreateBeforeDestroy {
   829  		t.Fatalf("Bad: %#v", r)
   830  	}
   831  
   832  	r = c.Resources[1]
   833  	if r.Name != "bar" && r.Type != "aws_instance" {
   834  		t.Fatalf("Bad: %#v", r)
   835  	}
   836  
   837  	// Should not enable create before destroy
   838  	if r.Lifecycle.CreateBeforeDestroy {
   839  		t.Fatalf("Bad: %#v", r)
   840  	}
   841  }
   842  
   843  func TestLoadFile_ignoreChanges(t *testing.T) {
   844  	c, err := LoadFile(filepath.Join(fixtureDir, "ignore-changes.tf"))
   845  	if err != nil {
   846  		t.Fatalf("err: %s", err)
   847  	}
   848  
   849  	if c == nil {
   850  		t.Fatal("config should not be nil")
   851  	}
   852  
   853  	actual := resourcesStr(c.Resources)
   854  	print(actual)
   855  	if actual != strings.TrimSpace(ignoreChangesResourcesStr) {
   856  		t.Fatalf("bad:\n%s", actual)
   857  	}
   858  
   859  	// Check for the flag value
   860  	r := c.Resources[0]
   861  	if r.Name != "web" && r.Type != "aws_instance" {
   862  		t.Fatalf("Bad: %#v", r)
   863  	}
   864  
   865  	// Should populate ignore changes
   866  	if len(r.Lifecycle.IgnoreChanges) == 0 {
   867  		t.Fatalf("Bad: %#v", r)
   868  	}
   869  
   870  	r = c.Resources[1]
   871  	if r.Name != "bar" && r.Type != "aws_instance" {
   872  		t.Fatalf("Bad: %#v", r)
   873  	}
   874  
   875  	// Should not populate ignore changes
   876  	if len(r.Lifecycle.IgnoreChanges) > 0 {
   877  		t.Fatalf("Bad: %#v", r)
   878  	}
   879  
   880  	r = c.Resources[2]
   881  	if r.Name != "baz" && r.Type != "aws_instance" {
   882  		t.Fatalf("Bad: %#v", r)
   883  	}
   884  
   885  	// Should not populate ignore changes
   886  	if len(r.Lifecycle.IgnoreChanges) > 0 {
   887  		t.Fatalf("Bad: %#v", r)
   888  	}
   889  }
   890  
   891  func TestLoad_preventDestroyString(t *testing.T) {
   892  	c, err := LoadFile(filepath.Join(fixtureDir, "prevent-destroy-string.tf"))
   893  	if err != nil {
   894  		t.Fatalf("err: %s", err)
   895  	}
   896  
   897  	if c == nil {
   898  		t.Fatal("config should not be nil")
   899  	}
   900  
   901  	actual := resourcesStr(c.Resources)
   902  	if actual != strings.TrimSpace(createBeforeDestroyResourcesStr) {
   903  		t.Fatalf("bad:\n%s", actual)
   904  	}
   905  
   906  	// Check for the flag value
   907  	r := c.Resources[0]
   908  	if r.Name != "web" && r.Type != "aws_instance" {
   909  		t.Fatalf("Bad: %#v", r)
   910  	}
   911  
   912  	// Should enable create before destroy
   913  	if !r.Lifecycle.PreventDestroy {
   914  		t.Fatalf("Bad: %#v", r)
   915  	}
   916  
   917  	r = c.Resources[1]
   918  	if r.Name != "bar" && r.Type != "aws_instance" {
   919  		t.Fatalf("Bad: %#v", r)
   920  	}
   921  
   922  	// Should not enable create before destroy
   923  	if r.Lifecycle.PreventDestroy {
   924  		t.Fatalf("Bad: %#v", r)
   925  	}
   926  }
   927  
   928  func TestLoad_temporary_files(t *testing.T) {
   929  	_, err := LoadDir(filepath.Join(fixtureDir, "dir-temporary-files"))
   930  	if err == nil {
   931  		t.Fatalf("Expected to see an error stating no config files found")
   932  	}
   933  }
   934  
   935  func TestLoad_hclAttributes(t *testing.T) {
   936  	c, err := LoadFile(filepath.Join(fixtureDir, "attributes.tf"))
   937  	if err != nil {
   938  		t.Fatalf("Bad: %s", err)
   939  	}
   940  
   941  	if c == nil {
   942  		t.Fatal("config should not be nil")
   943  	}
   944  
   945  	actual := resourcesStr(c.Resources)
   946  	print(actual)
   947  	if actual != strings.TrimSpace(jsonAttributeStr) {
   948  		t.Fatalf("bad:\n%s", actual)
   949  	}
   950  
   951  	r := c.Resources[0]
   952  	if r.Name != "test" && r.Type != "cloudstack_firewall" {
   953  		t.Fatalf("Bad: %#v", r)
   954  	}
   955  
   956  	raw := r.RawConfig
   957  	if raw.Raw["ipaddress"] != "192.168.0.1" {
   958  		t.Fatalf("Bad: %s", raw.Raw["ipAddress"])
   959  	}
   960  
   961  	rule := raw.Raw["rule"].([]map[string]interface{})[0]
   962  	if rule["protocol"] != "tcp" {
   963  		t.Fatalf("Bad: %s", rule["protocol"])
   964  	}
   965  
   966  	if rule["source_cidr"] != "10.0.0.0/8" {
   967  		t.Fatalf("Bad: %s", rule["source_cidr"])
   968  	}
   969  
   970  	ports := rule["ports"].([]interface{})
   971  
   972  	if ports[0] != "80" {
   973  		t.Fatalf("Bad ports: %s", ports[0])
   974  	}
   975  	if ports[1] != "1000-2000" {
   976  		t.Fatalf("Bad ports: %s", ports[1])
   977  	}
   978  }
   979  
   980  func TestLoad_jsonAttributes(t *testing.T) {
   981  	c, err := LoadFile(filepath.Join(fixtureDir, "attributes.tf.json"))
   982  	if err != nil {
   983  		t.Fatalf("Bad: %s", err)
   984  	}
   985  
   986  	if c == nil {
   987  		t.Fatal("config should not be nil")
   988  	}
   989  
   990  	actual := resourcesStr(c.Resources)
   991  	print(actual)
   992  	if actual != strings.TrimSpace(jsonAttributeStr) {
   993  		t.Fatalf("bad:\n%s", actual)
   994  	}
   995  
   996  	r := c.Resources[0]
   997  	if r.Name != "test" && r.Type != "cloudstack_firewall" {
   998  		t.Fatalf("Bad: %#v", r)
   999  	}
  1000  
  1001  	raw := r.RawConfig
  1002  	if raw.Raw["ipaddress"] != "192.168.0.1" {
  1003  		t.Fatalf("Bad: %s", raw.Raw["ipAddress"])
  1004  	}
  1005  
  1006  	rule := raw.Raw["rule"].([]map[string]interface{})[0]
  1007  	if rule["protocol"] != "tcp" {
  1008  		t.Fatalf("Bad: %s", rule["protocol"])
  1009  	}
  1010  
  1011  	if rule["source_cidr"] != "10.0.0.0/8" {
  1012  		t.Fatalf("Bad: %s", rule["source_cidr"])
  1013  	}
  1014  
  1015  	ports := rule["ports"].([]interface{})
  1016  
  1017  	if ports[0] != "80" {
  1018  		t.Fatalf("Bad ports: %s", ports[0])
  1019  	}
  1020  	if ports[1] != "1000-2000" {
  1021  		t.Fatalf("Bad ports: %s", ports[1])
  1022  	}
  1023  }
  1024  
  1025  func TestLoad_onlyOverride(t *testing.T) {
  1026  	c, err := LoadDir(filepath.Join(fixtureDir, "dir-only-override"))
  1027  	if err != nil {
  1028  		t.Fatalf("err: %s", err)
  1029  	}
  1030  
  1031  	if c == nil {
  1032  		t.Fatal("config should not be nil")
  1033  	}
  1034  
  1035  	actual := variablesStr(c.Variables)
  1036  	if actual != strings.TrimSpace(dirOnlyOverrideVariablesStr) {
  1037  		t.Fatalf("bad:\n%s", actual)
  1038  	}
  1039  }
  1040  
  1041  const jsonAttributeStr = `
  1042  cloudstack_firewall.test (x1)
  1043    ipaddress
  1044    rule
  1045  `
  1046  
  1047  const windowsHeredocResourcesStr = `
  1048  aws_instance.test (x1)
  1049    user_data
  1050  `
  1051  
  1052  const heredocProvidersStr = `
  1053  aws
  1054    access_key
  1055    secret_key
  1056  `
  1057  
  1058  const heredocResourcesStr = `
  1059  aws_iam_policy.policy (x1)
  1060    description
  1061    name
  1062    path
  1063    policy
  1064  aws_instance.heredocwithnumbers (x1)
  1065    ami
  1066    provisioners
  1067      local-exec
  1068        command
  1069  aws_instance.test (x1)
  1070    ami
  1071    provisioners
  1072      remote-exec
  1073        inline
  1074  `
  1075  
  1076  const basicOutputsStr = `
  1077  web_id
  1078    vars
  1079      resource: aws_instance.web.id
  1080    description
  1081      The ID
  1082  web_ip
  1083    vars
  1084      resource: aws_instance.web.private_ip
  1085  `
  1086  
  1087  const basicLocalsStr = `
  1088  literal
  1089  literal_list
  1090  literal_map
  1091  security_group_ids
  1092    vars
  1093      resource: aws_security_group.firewall.*.id
  1094  web_ip
  1095    vars
  1096      resource: aws_instance.web.private_ip
  1097  `
  1098  
  1099  const basicProvidersStr = `
  1100  aws
  1101    access_key
  1102    secret_key
  1103  do
  1104    api_key
  1105    vars
  1106      user: var.foo
  1107  `
  1108  
  1109  const basicResourcesStr = `
  1110  aws_instance.db (x1)
  1111    VPC
  1112    security_groups
  1113    provisioners
  1114      file
  1115        destination
  1116        source
  1117    dependsOn
  1118      aws_instance.web
  1119    vars
  1120      resource: aws_security_group.firewall.*.id
  1121  aws_instance.web (x1)
  1122    ami
  1123    network_interface
  1124    security_groups
  1125    provisioners
  1126      file
  1127        destination
  1128        source
  1129    vars
  1130      resource: aws_security_group.firewall.foo
  1131      user: var.foo
  1132  aws_security_group.firewall (x5)
  1133  data.do.depends (x1)
  1134    dependsOn
  1135      data.do.simple
  1136  data.do.simple (x1)
  1137    foo
  1138  `
  1139  
  1140  const basicVariablesStr = `
  1141  bar (required) (string)
  1142    <>
  1143    <>
  1144  baz (map)
  1145    map[key:value]
  1146    <>
  1147  foo
  1148    bar
  1149    bar
  1150  `
  1151  
  1152  const basicJsonNoNameResourcesStr = `
  1153  aws_security_group.allow_external_http_https (x1)
  1154    tags
  1155  `
  1156  
  1157  const dirBasicOutputsStr = `
  1158  web_ip
  1159    vars
  1160      resource: aws_instance.web.private_ip
  1161  `
  1162  
  1163  const dirBasicProvidersStr = `
  1164  aws
  1165    access_key
  1166    secret_key
  1167  do
  1168    api_key
  1169    vars
  1170      user: var.foo
  1171  `
  1172  
  1173  const dirBasicResourcesStr = `
  1174  aws_instance.db (x1)
  1175    security_groups
  1176    vars
  1177      resource: aws_security_group.firewall.*.id
  1178  aws_instance.web (x1)
  1179    ami
  1180    network_interface
  1181    security_groups
  1182    vars
  1183      resource: aws_security_group.firewall.foo
  1184      user: var.foo
  1185  aws_security_group.firewall (x5)
  1186  data.do.depends (x1)
  1187    dependsOn
  1188      data.do.simple
  1189  data.do.simple (x1)
  1190    foo
  1191  `
  1192  
  1193  const dirBasicVariablesStr = `
  1194  foo
  1195    bar
  1196    bar
  1197  `
  1198  
  1199  const dirOverrideOutputsStr = `
  1200  web_ip
  1201    vars
  1202      resource: aws_instance.web.private_ip
  1203  `
  1204  
  1205  const dirOverrideProvidersStr = `
  1206  aws
  1207    access_key
  1208    secret_key
  1209  do
  1210    api_key
  1211    vars
  1212      user: var.foo
  1213  `
  1214  
  1215  const dirOverrideResourcesStr = `
  1216  aws_instance.db (x1)
  1217    ami
  1218    security_groups
  1219  aws_instance.web (x1)
  1220    ami
  1221    foo
  1222    network_interface
  1223    security_groups
  1224    vars
  1225      resource: aws_security_group.firewall.foo
  1226      user: var.foo
  1227  aws_security_group.firewall (x5)
  1228  data.do.depends (x1)
  1229    hello
  1230    dependsOn
  1231      data.do.simple
  1232  data.do.simple (x1)
  1233    foo
  1234  `
  1235  
  1236  const dirOverrideVariablesStr = `
  1237  foo
  1238    bar
  1239    bar
  1240  `
  1241  
  1242  const dirOverrideVarsVariablesStr = `
  1243  foo
  1244    baz
  1245    bar
  1246  `
  1247  
  1248  const dirOnlyOverrideVariablesStr = `
  1249  foo
  1250    bar
  1251    bar
  1252  `
  1253  
  1254  const importProvidersStr = `
  1255  aws
  1256    bar
  1257    foo
  1258  `
  1259  
  1260  const importResourcesStr = `
  1261  aws_security_group.db (x1)
  1262  aws_security_group.web (x1)
  1263  `
  1264  
  1265  const importVariablesStr = `
  1266  bar (required)
  1267    <>
  1268    <>
  1269  foo
  1270    bar
  1271    bar
  1272  `
  1273  
  1274  const modulesModulesStr = `
  1275  bar
  1276    source = baz
  1277    memory
  1278  `
  1279  
  1280  const provisionerResourcesStr = `
  1281  aws_instance.web (x1)
  1282    ami
  1283    security_groups
  1284    provisioners
  1285      shell
  1286        path
  1287    vars
  1288      resource: aws_security_group.firewall.foo
  1289      user: var.foo
  1290  `
  1291  
  1292  const provisionerDestroyResourcesStr = `
  1293  aws_instance.web (x1)
  1294    provisioners
  1295      shell
  1296      shell (destroy)
  1297        path
  1298      shell (destroy)
  1299        on_failure = continue
  1300        path
  1301  `
  1302  
  1303  const connectionResourcesStr = `
  1304  aws_instance.web (x1)
  1305    ami
  1306    security_groups
  1307    provisioners
  1308      shell
  1309        path
  1310      shell
  1311        path
  1312    vars
  1313      resource: aws_security_group.firewall.foo
  1314      user: var.foo
  1315  `
  1316  
  1317  const outputDependsOnStr = `
  1318  value
  1319    dependsOn
  1320      foo
  1321  `
  1322  
  1323  const variablesVariablesStr = `
  1324  bar
  1325    <>
  1326    <>
  1327  baz
  1328    foo
  1329    <>
  1330  foo (required)
  1331    <>
  1332    <>
  1333  `
  1334  
  1335  const createBeforeDestroyResourcesStr = `
  1336  aws_instance.bar (x1)
  1337    ami
  1338  aws_instance.web (x1)
  1339    ami
  1340  `
  1341  
  1342  const ignoreChangesResourcesStr = `
  1343  aws_instance.bar (x1)
  1344    ami
  1345  aws_instance.baz (x1)
  1346    ami
  1347  aws_instance.web (x1)
  1348    ami
  1349  `