github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/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/hashicorp/terraform/internal/addrs" 10 "github.com/hashicorp/terraform/internal/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 }