github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/command/arguments/apply_test.go (about)

     1  package arguments
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  
     7  	"github.com/google/go-cmp/cmp"
     8  	"github.com/google/go-cmp/cmp/cmpopts"
     9  	"github.com/cycloidio/terraform/addrs"
    10  	"github.com/cycloidio/terraform/plans"
    11  )
    12  
    13  func TestParseApply_basicValid(t *testing.T) {
    14  	testCases := map[string]struct {
    15  		args []string
    16  		want *Apply
    17  	}{
    18  		"defaults": {
    19  			nil,
    20  			&Apply{
    21  				AutoApprove:  false,
    22  				InputEnabled: true,
    23  				PlanPath:     "",
    24  				ViewType:     ViewHuman,
    25  				State:        &State{Lock: true},
    26  				Vars:         &Vars{},
    27  				Operation: &Operation{
    28  					PlanMode:    plans.NormalMode,
    29  					Parallelism: 10,
    30  					Refresh:     true,
    31  				},
    32  			},
    33  		},
    34  		"auto-approve, disabled input, and plan path": {
    35  			[]string{"-auto-approve", "-input=false", "saved.tfplan"},
    36  			&Apply{
    37  				AutoApprove:  true,
    38  				InputEnabled: false,
    39  				PlanPath:     "saved.tfplan",
    40  				ViewType:     ViewHuman,
    41  				State:        &State{Lock: true},
    42  				Vars:         &Vars{},
    43  				Operation: &Operation{
    44  					PlanMode:    plans.NormalMode,
    45  					Parallelism: 10,
    46  					Refresh:     true,
    47  				},
    48  			},
    49  		},
    50  		"destroy mode": {
    51  			[]string{"-destroy"},
    52  			&Apply{
    53  				AutoApprove:  false,
    54  				InputEnabled: true,
    55  				PlanPath:     "",
    56  				ViewType:     ViewHuman,
    57  				State:        &State{Lock: true},
    58  				Vars:         &Vars{},
    59  				Operation: &Operation{
    60  					PlanMode:    plans.DestroyMode,
    61  					Parallelism: 10,
    62  					Refresh:     true,
    63  				},
    64  			},
    65  		},
    66  		"JSON view disables input": {
    67  			[]string{"-json", "-auto-approve"},
    68  			&Apply{
    69  				AutoApprove:  true,
    70  				InputEnabled: false,
    71  				PlanPath:     "",
    72  				ViewType:     ViewJSON,
    73  				State:        &State{Lock: true},
    74  				Vars:         &Vars{},
    75  				Operation: &Operation{
    76  					PlanMode:    plans.NormalMode,
    77  					Parallelism: 10,
    78  					Refresh:     true,
    79  				},
    80  			},
    81  		},
    82  	}
    83  
    84  	cmpOpts := cmpopts.IgnoreUnexported(Operation{}, Vars{}, State{})
    85  
    86  	for name, tc := range testCases {
    87  		t.Run(name, func(t *testing.T) {
    88  			got, diags := ParseApply(tc.args)
    89  			if len(diags) > 0 {
    90  				t.Fatalf("unexpected diags: %v", diags)
    91  			}
    92  			if diff := cmp.Diff(tc.want, got, cmpOpts); diff != "" {
    93  				t.Errorf("unexpected result\n%s", diff)
    94  			}
    95  		})
    96  	}
    97  }
    98  
    99  func TestParseApply_json(t *testing.T) {
   100  	testCases := map[string]struct {
   101  		args        []string
   102  		wantSuccess bool
   103  	}{
   104  		"-json": {
   105  			[]string{"-json"},
   106  			false,
   107  		},
   108  		"-json -auto-approve": {
   109  			[]string{"-json", "-auto-approve"},
   110  			true,
   111  		},
   112  		"-json saved.tfplan": {
   113  			[]string{"-json", "saved.tfplan"},
   114  			true,
   115  		},
   116  	}
   117  
   118  	for name, tc := range testCases {
   119  		t.Run(name, func(t *testing.T) {
   120  			got, diags := ParseApply(tc.args)
   121  
   122  			if tc.wantSuccess {
   123  				if len(diags) > 0 {
   124  					t.Errorf("unexpected diags: %v", diags)
   125  				}
   126  			} else {
   127  				if got, want := diags.Err().Error(), "Plan file or auto-approve required"; !strings.Contains(got, want) {
   128  					t.Errorf("wrong diags\n got: %s\nwant: %s", got, want)
   129  				}
   130  			}
   131  
   132  			if got.ViewType != ViewJSON {
   133  				t.Errorf("unexpected view type. got: %#v, want: %#v", got.ViewType, ViewJSON)
   134  			}
   135  		})
   136  	}
   137  }
   138  
   139  func TestParseApply_invalid(t *testing.T) {
   140  	got, diags := ParseApply([]string{"-frob"})
   141  	if len(diags) == 0 {
   142  		t.Fatal("expected diags but got none")
   143  	}
   144  	if got, want := diags.Err().Error(), "flag provided but not defined"; !strings.Contains(got, want) {
   145  		t.Fatalf("wrong diags\n got: %s\nwant: %s", got, want)
   146  	}
   147  	if got.ViewType != ViewHuman {
   148  		t.Fatalf("wrong view type, got %#v, want %#v", got.ViewType, ViewHuman)
   149  	}
   150  }
   151  
   152  func TestParseApply_tooManyArguments(t *testing.T) {
   153  	got, diags := ParseApply([]string{"saved.tfplan", "please"})
   154  	if len(diags) == 0 {
   155  		t.Fatal("expected diags but got none")
   156  	}
   157  	if got, want := diags.Err().Error(), "Too many command line arguments"; !strings.Contains(got, want) {
   158  		t.Fatalf("wrong diags\n got: %s\nwant: %s", got, want)
   159  	}
   160  	if got.ViewType != ViewHuman {
   161  		t.Fatalf("wrong view type, got %#v, want %#v", got.ViewType, ViewHuman)
   162  	}
   163  }
   164  
   165  func TestParseApply_targets(t *testing.T) {
   166  	foobarbaz, _ := addrs.ParseTargetStr("foo_bar.baz")
   167  	boop, _ := addrs.ParseTargetStr("module.boop")
   168  	testCases := map[string]struct {
   169  		args    []string
   170  		want    []addrs.Targetable
   171  		wantErr string
   172  	}{
   173  		"no targets by default": {
   174  			args: nil,
   175  			want: nil,
   176  		},
   177  		"one target": {
   178  			args: []string{"-target=foo_bar.baz"},
   179  			want: []addrs.Targetable{foobarbaz.Subject},
   180  		},
   181  		"two targets": {
   182  			args: []string{"-target=foo_bar.baz", "-target", "module.boop"},
   183  			want: []addrs.Targetable{foobarbaz.Subject, boop.Subject},
   184  		},
   185  		"invalid traversal": {
   186  			args:    []string{"-target=foo."},
   187  			want:    nil,
   188  			wantErr: "Dot must be followed by attribute name",
   189  		},
   190  		"invalid target": {
   191  			args:    []string{"-target=data[0].foo"},
   192  			want:    nil,
   193  			wantErr: "A data source name is required",
   194  		},
   195  	}
   196  
   197  	for name, tc := range testCases {
   198  		t.Run(name, func(t *testing.T) {
   199  			got, diags := ParseApply(tc.args)
   200  			if len(diags) > 0 {
   201  				if tc.wantErr == "" {
   202  					t.Fatalf("unexpected diags: %v", diags)
   203  				} else if got := diags.Err().Error(); !strings.Contains(got, tc.wantErr) {
   204  					t.Fatalf("wrong diags\n got: %s\nwant: %s", got, tc.wantErr)
   205  				}
   206  			}
   207  			if !cmp.Equal(got.Operation.Targets, tc.want) {
   208  				t.Fatalf("unexpected result\n%s", cmp.Diff(got.Operation.Targets, tc.want))
   209  			}
   210  		})
   211  	}
   212  }
   213  
   214  func TestParseApply_replace(t *testing.T) {
   215  	foobarbaz, _ := addrs.ParseAbsResourceInstanceStr("foo_bar.baz")
   216  	foobarbeep, _ := addrs.ParseAbsResourceInstanceStr("foo_bar.beep")
   217  	testCases := map[string]struct {
   218  		args    []string
   219  		want    []addrs.AbsResourceInstance
   220  		wantErr string
   221  	}{
   222  		"no addresses by default": {
   223  			args: nil,
   224  			want: nil,
   225  		},
   226  		"one address": {
   227  			args: []string{"-replace=foo_bar.baz"},
   228  			want: []addrs.AbsResourceInstance{foobarbaz},
   229  		},
   230  		"two addresses": {
   231  			args: []string{"-replace=foo_bar.baz", "-replace", "foo_bar.beep"},
   232  			want: []addrs.AbsResourceInstance{foobarbaz, foobarbeep},
   233  		},
   234  		"non-resource-instance address": {
   235  			args:    []string{"-replace=module.boop"},
   236  			want:    nil,
   237  			wantErr: "A resource instance address is required here.",
   238  		},
   239  		"data resource address": {
   240  			args:    []string{"-replace=data.foo.bar"},
   241  			want:    nil,
   242  			wantErr: "Only managed resources can be used",
   243  		},
   244  		"invalid traversal": {
   245  			args:    []string{"-replace=foo."},
   246  			want:    nil,
   247  			wantErr: "Dot must be followed by attribute name",
   248  		},
   249  		"invalid address": {
   250  			args:    []string{"-replace=data[0].foo"},
   251  			want:    nil,
   252  			wantErr: "A data source name is required",
   253  		},
   254  	}
   255  
   256  	for name, tc := range testCases {
   257  		t.Run(name, func(t *testing.T) {
   258  			got, diags := ParseApply(tc.args)
   259  			if len(diags) > 0 {
   260  				if tc.wantErr == "" {
   261  					t.Fatalf("unexpected diags: %v", diags)
   262  				} else if got := diags.Err().Error(); !strings.Contains(got, tc.wantErr) {
   263  					t.Fatalf("wrong diags\n got: %s\nwant: %s", got, tc.wantErr)
   264  				}
   265  			}
   266  			if !cmp.Equal(got.Operation.ForceReplace, tc.want) {
   267  				t.Fatalf("unexpected result\n%s", cmp.Diff(got.Operation.Targets, tc.want))
   268  			}
   269  		})
   270  	}
   271  }
   272  
   273  func TestParseApply_vars(t *testing.T) {
   274  	testCases := map[string]struct {
   275  		args []string
   276  		want []FlagNameValue
   277  	}{
   278  		"no var flags by default": {
   279  			args: nil,
   280  			want: nil,
   281  		},
   282  		"one var": {
   283  			args: []string{"-var", "foo=bar"},
   284  			want: []FlagNameValue{
   285  				{Name: "-var", Value: "foo=bar"},
   286  			},
   287  		},
   288  		"one var-file": {
   289  			args: []string{"-var-file", "cool.tfvars"},
   290  			want: []FlagNameValue{
   291  				{Name: "-var-file", Value: "cool.tfvars"},
   292  			},
   293  		},
   294  		"ordering preserved": {
   295  			args: []string{
   296  				"-var", "foo=bar",
   297  				"-var-file", "cool.tfvars",
   298  				"-var", "boop=beep",
   299  			},
   300  			want: []FlagNameValue{
   301  				{Name: "-var", Value: "foo=bar"},
   302  				{Name: "-var-file", Value: "cool.tfvars"},
   303  				{Name: "-var", Value: "boop=beep"},
   304  			},
   305  		},
   306  	}
   307  
   308  	for name, tc := range testCases {
   309  		t.Run(name, func(t *testing.T) {
   310  			got, diags := ParseApply(tc.args)
   311  			if len(diags) > 0 {
   312  				t.Fatalf("unexpected diags: %v", diags)
   313  			}
   314  			if vars := got.Vars.All(); !cmp.Equal(vars, tc.want) {
   315  				t.Fatalf("unexpected result\n%s", cmp.Diff(vars, tc.want))
   316  			}
   317  			if got, want := got.Vars.Empty(), len(tc.want) == 0; got != want {
   318  				t.Fatalf("expected Empty() to return %t, but was %t", want, got)
   319  			}
   320  		})
   321  	}
   322  }
   323  
   324  func TestParseApplyDestroy_basicValid(t *testing.T) {
   325  	testCases := map[string]struct {
   326  		args []string
   327  		want *Apply
   328  	}{
   329  		"defaults": {
   330  			nil,
   331  			&Apply{
   332  				AutoApprove:  false,
   333  				InputEnabled: true,
   334  				ViewType:     ViewHuman,
   335  				State:        &State{Lock: true},
   336  				Vars:         &Vars{},
   337  				Operation: &Operation{
   338  					PlanMode:    plans.DestroyMode,
   339  					Parallelism: 10,
   340  					Refresh:     true,
   341  				},
   342  			},
   343  		},
   344  		"auto-approve and disabled input": {
   345  			[]string{"-auto-approve", "-input=false"},
   346  			&Apply{
   347  				AutoApprove:  true,
   348  				InputEnabled: false,
   349  				ViewType:     ViewHuman,
   350  				State:        &State{Lock: true},
   351  				Vars:         &Vars{},
   352  				Operation: &Operation{
   353  					PlanMode:    plans.DestroyMode,
   354  					Parallelism: 10,
   355  					Refresh:     true,
   356  				},
   357  			},
   358  		},
   359  	}
   360  
   361  	cmpOpts := cmpopts.IgnoreUnexported(Operation{}, Vars{}, State{})
   362  
   363  	for name, tc := range testCases {
   364  		t.Run(name, func(t *testing.T) {
   365  			got, diags := ParseApplyDestroy(tc.args)
   366  			if len(diags) > 0 {
   367  				t.Fatalf("unexpected diags: %v", diags)
   368  			}
   369  			if diff := cmp.Diff(tc.want, got, cmpOpts); diff != "" {
   370  				t.Errorf("unexpected result\n%s", diff)
   371  			}
   372  		})
   373  	}
   374  }
   375  
   376  func TestParseApplyDestroy_invalid(t *testing.T) {
   377  	t.Run("explicit destroy mode", func(t *testing.T) {
   378  		got, diags := ParseApplyDestroy([]string{"-destroy"})
   379  		if len(diags) == 0 {
   380  			t.Fatal("expected diags but got none")
   381  		}
   382  		if got, want := diags.Err().Error(), "Invalid mode option:"; !strings.Contains(got, want) {
   383  			t.Fatalf("wrong diags\n got: %s\nwant: %s", got, want)
   384  		}
   385  		if got.ViewType != ViewHuman {
   386  			t.Fatalf("wrong view type, got %#v, want %#v", got.ViewType, ViewHuman)
   387  		}
   388  	})
   389  }