github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/lang/funcs/filesystem_test.go (about) 1 package funcs 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "testing" 8 9 "github.com/hashicorp/terraform/internal/lang/marks" 10 homedir "github.com/mitchellh/go-homedir" 11 "github.com/zclconf/go-cty/cty" 12 "github.com/zclconf/go-cty/cty/function" 13 "github.com/zclconf/go-cty/cty/function/stdlib" 14 ) 15 16 func TestFile(t *testing.T) { 17 tests := []struct { 18 Path cty.Value 19 Want cty.Value 20 Err string 21 }{ 22 { 23 cty.StringVal("testdata/hello.txt"), 24 cty.StringVal("Hello World"), 25 ``, 26 }, 27 { 28 cty.StringVal("testdata/icon.png"), 29 cty.NilVal, 30 `contents of "testdata/icon.png" are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead`, 31 }, 32 { 33 cty.StringVal("testdata/icon.png").Mark(marks.Sensitive), 34 cty.NilVal, 35 `contents of (sensitive value) are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead`, 36 }, 37 { 38 cty.StringVal("testdata/missing"), 39 cty.NilVal, 40 `no file exists at "testdata/missing"; this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource`, 41 }, 42 { 43 cty.StringVal("testdata/missing").Mark(marks.Sensitive), 44 cty.NilVal, 45 `no file exists at (sensitive value); this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource`, 46 }, 47 } 48 49 for _, test := range tests { 50 t.Run(fmt.Sprintf("File(\".\", %#v)", test.Path), func(t *testing.T) { 51 got, err := File(".", test.Path) 52 53 if test.Err != "" { 54 if err == nil { 55 t.Fatal("succeeded; want error") 56 } 57 if got, want := err.Error(), test.Err; got != want { 58 t.Errorf("wrong error\ngot: %s\nwant: %s", got, want) 59 } 60 return 61 } else if err != nil { 62 t.Fatalf("unexpected error: %s", err) 63 } 64 65 if !got.RawEquals(test.Want) { 66 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 67 } 68 }) 69 } 70 } 71 72 func TestTemplateFile(t *testing.T) { 73 tests := []struct { 74 Path cty.Value 75 Vars cty.Value 76 Want cty.Value 77 Err string 78 }{ 79 { 80 cty.StringVal("testdata/hello.txt"), 81 cty.EmptyObjectVal, 82 cty.StringVal("Hello World"), 83 ``, 84 }, 85 { 86 cty.StringVal("testdata/icon.png"), 87 cty.EmptyObjectVal, 88 cty.NilVal, 89 `contents of "testdata/icon.png" are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead`, 90 }, 91 { 92 cty.StringVal("testdata/missing"), 93 cty.EmptyObjectVal, 94 cty.NilVal, 95 `no file exists at "testdata/missing"; this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource`, 96 }, 97 { 98 cty.StringVal("testdata/secrets.txt").Mark(marks.Sensitive), 99 cty.EmptyObjectVal, 100 cty.NilVal, 101 `no file exists at (sensitive value); this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource`, 102 }, 103 { 104 cty.StringVal("testdata/hello.tmpl"), 105 cty.MapVal(map[string]cty.Value{ 106 "name": cty.StringVal("Jodie"), 107 }), 108 cty.StringVal("Hello, Jodie!"), 109 ``, 110 }, 111 { 112 cty.StringVal("testdata/hello.tmpl"), 113 cty.MapVal(map[string]cty.Value{ 114 "name!": cty.StringVal("Jodie"), 115 }), 116 cty.NilVal, 117 `invalid template variable name "name!": must start with a letter, followed by zero or more letters, digits, and underscores`, 118 }, 119 { 120 cty.StringVal("testdata/hello.tmpl"), 121 cty.ObjectVal(map[string]cty.Value{ 122 "name": cty.StringVal("Jimbo"), 123 }), 124 cty.StringVal("Hello, Jimbo!"), 125 ``, 126 }, 127 { 128 cty.StringVal("testdata/hello.tmpl"), 129 cty.EmptyObjectVal, 130 cty.NilVal, 131 `vars map does not contain key "name", referenced at testdata/hello.tmpl:1,10-14`, 132 }, 133 { 134 cty.StringVal("testdata/func.tmpl"), 135 cty.ObjectVal(map[string]cty.Value{ 136 "list": cty.ListVal([]cty.Value{ 137 cty.StringVal("a"), 138 cty.StringVal("b"), 139 cty.StringVal("c"), 140 }), 141 }), 142 cty.StringVal("The items are a, b, c"), 143 ``, 144 }, 145 { 146 cty.StringVal("testdata/recursive.tmpl"), 147 cty.MapValEmpty(cty.String), 148 cty.NilVal, 149 `testdata/recursive.tmpl:1,3-16: Error in function call; Call to function "templatefile" failed: cannot recursively call templatefile from inside templatefile call.`, 150 }, 151 { 152 cty.StringVal("testdata/list.tmpl"), 153 cty.ObjectVal(map[string]cty.Value{ 154 "list": cty.ListVal([]cty.Value{ 155 cty.StringVal("a"), 156 cty.StringVal("b"), 157 cty.StringVal("c"), 158 }), 159 }), 160 cty.StringVal("- a\n- b\n- c\n"), 161 ``, 162 }, 163 { 164 cty.StringVal("testdata/list.tmpl"), 165 cty.ObjectVal(map[string]cty.Value{ 166 "list": cty.True, 167 }), 168 cty.NilVal, 169 `testdata/list.tmpl:1,13-17: Iteration over non-iterable value; A value of type bool cannot be used as the collection in a 'for' expression.`, 170 }, 171 { 172 cty.StringVal("testdata/bare.tmpl"), 173 cty.ObjectVal(map[string]cty.Value{ 174 "val": cty.True, 175 }), 176 cty.True, // since this template contains only an interpolation, its true value shines through 177 ``, 178 }, 179 } 180 181 templateFileFn := MakeTemplateFileFunc(".", func() map[string]function.Function { 182 return map[string]function.Function{ 183 "join": stdlib.JoinFunc, 184 "templatefile": MakeFileFunc(".", false), // just a placeholder, since templatefile itself overrides this 185 } 186 }) 187 188 for _, test := range tests { 189 t.Run(fmt.Sprintf("TemplateFile(%#v, %#v)", test.Path, test.Vars), func(t *testing.T) { 190 got, err := templateFileFn.Call([]cty.Value{test.Path, test.Vars}) 191 192 if argErr, ok := err.(function.ArgError); ok { 193 if argErr.Index < 0 || argErr.Index > 1 { 194 t.Errorf("ArgError index %d is out of range for templatefile (must be 0 or 1)", argErr.Index) 195 } 196 } 197 198 if test.Err != "" { 199 if err == nil { 200 t.Fatal("succeeded; want error") 201 } 202 if got, want := err.Error(), test.Err; got != want { 203 t.Errorf("wrong error\ngot: %s\nwant: %s", got, want) 204 } 205 return 206 } else if err != nil { 207 t.Fatalf("unexpected error: %s", err) 208 } 209 210 if !got.RawEquals(test.Want) { 211 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 212 } 213 }) 214 } 215 } 216 217 func TestFileExists(t *testing.T) { 218 tests := []struct { 219 Path cty.Value 220 Want cty.Value 221 Err string 222 }{ 223 { 224 cty.StringVal("testdata/hello.txt"), 225 cty.BoolVal(true), 226 ``, 227 }, 228 { 229 cty.StringVal(""), 230 cty.BoolVal(false), 231 `"." is a directory, not a file`, 232 }, 233 { 234 cty.StringVal("testdata").Mark(marks.Sensitive), 235 cty.BoolVal(false), 236 `(sensitive value) is a directory, not a file`, 237 }, 238 { 239 cty.StringVal("testdata/missing"), 240 cty.BoolVal(false), 241 ``, 242 }, 243 { 244 cty.StringVal("testdata/unreadable/foobar"), 245 cty.BoolVal(false), 246 `failed to stat "testdata/unreadable/foobar"`, 247 }, 248 { 249 cty.StringVal("testdata/unreadable/foobar").Mark(marks.Sensitive), 250 cty.BoolVal(false), 251 `failed to stat (sensitive value)`, 252 }, 253 } 254 255 // Ensure "unreadable" directory cannot be listed during the test run 256 fi, err := os.Lstat("testdata/unreadable") 257 if err != nil { 258 t.Fatal(err) 259 } 260 os.Chmod("testdata/unreadable", 0000) 261 defer func(mode os.FileMode) { 262 os.Chmod("testdata/unreadable", mode) 263 }(fi.Mode()) 264 265 for _, test := range tests { 266 t.Run(fmt.Sprintf("FileExists(\".\", %#v)", test.Path), func(t *testing.T) { 267 got, err := FileExists(".", test.Path) 268 269 if test.Err != "" { 270 if err == nil { 271 t.Fatal("succeeded; want error") 272 } 273 if got, want := err.Error(), test.Err; got != want { 274 t.Errorf("wrong error\ngot: %s\nwant: %s", got, want) 275 } 276 return 277 } else if err != nil { 278 t.Fatalf("unexpected error: %s", err) 279 } 280 281 if !got.RawEquals(test.Want) { 282 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 283 } 284 }) 285 } 286 } 287 288 func TestFileSet(t *testing.T) { 289 tests := []struct { 290 Path cty.Value 291 Pattern cty.Value 292 Want cty.Value 293 Err string 294 }{ 295 { 296 cty.StringVal("."), 297 cty.StringVal("testdata*"), 298 cty.SetValEmpty(cty.String), 299 ``, 300 }, 301 { 302 cty.StringVal("."), 303 cty.StringVal("testdata"), 304 cty.SetValEmpty(cty.String), 305 ``, 306 }, 307 { 308 cty.StringVal("."), 309 cty.StringVal("{testdata,missing}"), 310 cty.SetValEmpty(cty.String), 311 ``, 312 }, 313 { 314 cty.StringVal("."), 315 cty.StringVal("testdata/missing"), 316 cty.SetValEmpty(cty.String), 317 ``, 318 }, 319 { 320 cty.StringVal("."), 321 cty.StringVal("testdata/missing*"), 322 cty.SetValEmpty(cty.String), 323 ``, 324 }, 325 { 326 cty.StringVal("."), 327 cty.StringVal("*/missing"), 328 cty.SetValEmpty(cty.String), 329 ``, 330 }, 331 { 332 cty.StringVal("."), 333 cty.StringVal("**/missing"), 334 cty.SetValEmpty(cty.String), 335 ``, 336 }, 337 { 338 cty.StringVal("."), 339 cty.StringVal("testdata/*.txt"), 340 cty.SetVal([]cty.Value{ 341 cty.StringVal("testdata/hello.txt"), 342 }), 343 ``, 344 }, 345 { 346 cty.StringVal("."), 347 cty.StringVal("testdata/hello.txt"), 348 cty.SetVal([]cty.Value{ 349 cty.StringVal("testdata/hello.txt"), 350 }), 351 ``, 352 }, 353 { 354 cty.StringVal("."), 355 cty.StringVal("testdata/hello.???"), 356 cty.SetVal([]cty.Value{ 357 cty.StringVal("testdata/hello.txt"), 358 }), 359 ``, 360 }, 361 { 362 cty.StringVal("."), 363 cty.StringVal("testdata/hello*"), 364 cty.SetVal([]cty.Value{ 365 cty.StringVal("testdata/hello.tmpl"), 366 cty.StringVal("testdata/hello.txt"), 367 }), 368 ``, 369 }, 370 { 371 cty.StringVal("."), 372 cty.StringVal("testdata/hello.{tmpl,txt}"), 373 cty.SetVal([]cty.Value{ 374 cty.StringVal("testdata/hello.tmpl"), 375 cty.StringVal("testdata/hello.txt"), 376 }), 377 ``, 378 }, 379 { 380 cty.StringVal("."), 381 cty.StringVal("*/hello.txt"), 382 cty.SetVal([]cty.Value{ 383 cty.StringVal("testdata/hello.txt"), 384 }), 385 ``, 386 }, 387 { 388 cty.StringVal("."), 389 cty.StringVal("*/*.txt"), 390 cty.SetVal([]cty.Value{ 391 cty.StringVal("testdata/hello.txt"), 392 }), 393 ``, 394 }, 395 { 396 cty.StringVal("."), 397 cty.StringVal("*/hello*"), 398 cty.SetVal([]cty.Value{ 399 cty.StringVal("testdata/hello.tmpl"), 400 cty.StringVal("testdata/hello.txt"), 401 }), 402 ``, 403 }, 404 { 405 cty.StringVal("."), 406 cty.StringVal("**/hello*"), 407 cty.SetVal([]cty.Value{ 408 cty.StringVal("testdata/hello.tmpl"), 409 cty.StringVal("testdata/hello.txt"), 410 }), 411 ``, 412 }, 413 { 414 cty.StringVal("."), 415 cty.StringVal("**/hello.{tmpl,txt}"), 416 cty.SetVal([]cty.Value{ 417 cty.StringVal("testdata/hello.tmpl"), 418 cty.StringVal("testdata/hello.txt"), 419 }), 420 ``, 421 }, 422 { 423 cty.StringVal("."), 424 cty.StringVal("["), 425 cty.SetValEmpty(cty.String), 426 `failed to glob pattern "[": syntax error in pattern`, 427 }, 428 { 429 cty.StringVal("."), 430 cty.StringVal("[").Mark(marks.Sensitive), 431 cty.SetValEmpty(cty.String), 432 `failed to glob pattern (sensitive value): syntax error in pattern`, 433 }, 434 { 435 cty.StringVal("."), 436 cty.StringVal("\\"), 437 cty.SetValEmpty(cty.String), 438 `failed to glob pattern "\\": syntax error in pattern`, 439 }, 440 { 441 cty.StringVal("testdata"), 442 cty.StringVal("missing"), 443 cty.SetValEmpty(cty.String), 444 ``, 445 }, 446 { 447 cty.StringVal("testdata"), 448 cty.StringVal("missing*"), 449 cty.SetValEmpty(cty.String), 450 ``, 451 }, 452 { 453 cty.StringVal("testdata"), 454 cty.StringVal("*.txt"), 455 cty.SetVal([]cty.Value{ 456 cty.StringVal("hello.txt"), 457 }), 458 ``, 459 }, 460 { 461 cty.StringVal("testdata"), 462 cty.StringVal("hello.txt"), 463 cty.SetVal([]cty.Value{ 464 cty.StringVal("hello.txt"), 465 }), 466 ``, 467 }, 468 { 469 cty.StringVal("testdata"), 470 cty.StringVal("hello.???"), 471 cty.SetVal([]cty.Value{ 472 cty.StringVal("hello.txt"), 473 }), 474 ``, 475 }, 476 { 477 cty.StringVal("testdata"), 478 cty.StringVal("hello*"), 479 cty.SetVal([]cty.Value{ 480 cty.StringVal("hello.tmpl"), 481 cty.StringVal("hello.txt"), 482 }), 483 ``, 484 }, 485 } 486 487 for _, test := range tests { 488 t.Run(fmt.Sprintf("FileSet(\".\", %#v, %#v)", test.Path, test.Pattern), func(t *testing.T) { 489 got, err := FileSet(".", test.Path, test.Pattern) 490 491 if test.Err != "" { 492 if err == nil { 493 t.Fatal("succeeded; want error") 494 } 495 if got, want := err.Error(), test.Err; got != want { 496 t.Errorf("wrong error\ngot: %s\nwant: %s", got, want) 497 } 498 return 499 } else if err != nil { 500 t.Fatalf("unexpected error: %s", err) 501 } 502 503 if !got.RawEquals(test.Want) { 504 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 505 } 506 }) 507 } 508 } 509 510 func TestFileBase64(t *testing.T) { 511 tests := []struct { 512 Path cty.Value 513 Want cty.Value 514 Err bool 515 }{ 516 { 517 cty.StringVal("testdata/hello.txt"), 518 cty.StringVal("SGVsbG8gV29ybGQ="), 519 false, 520 }, 521 { 522 cty.StringVal("testdata/icon.png"), 523 cty.StringVal("iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAq1BMVEX///9cTuVeUeRcTuZcTuZcT+VbSe1cTuVdT+MAAP9JSbZcT+VcTuZAQLFAQLJcTuVcTuZcUuBBQbA/P7JAQLJaTuRcT+RcTuVGQ7xAQLJVVf9cTuVcTuVGRMFeUeRbTeJcTuU/P7JeTeZbTOVcTeZAQLJBQbNAQLNaUORcTeZbT+VcTuRAQLNAQLRdTuRHR8xgUOdgUN9cTuVdTeRdT+VZTulcTuVAQLL///8+GmETAAAANnRSTlMApibw+osO6DcBB3fIX87+oRk3yehB0/Nj/gNs7nsTRv3dHmu//JYUMLVr3bssjxkgEK5CaxeK03nIAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAADoQAAA6EBvJf9gwAAAAd0SU1FB+EEBRIQDxZNTKsAAACCSURBVBjTfc7JFsFQEATQQpCYxyBEzJ55rvf/f0ZHcyQLvelTd1GngEwWycs5+UISyKLraSi9geWKK9Gr1j7AeqOJVtt2XtD1Bchef2BjQDAcCTC0CsA4mihMtXw2XwgsV2sFw812F+4P3y2GdI6nn3FGSs//4HJNAXDzU4Dg/oj/E+bsEbhf5cMsAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA0LTA1VDE4OjE2OjE1KzAyOjAws5bLVQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNC0wNVQxODoxNjoxNSswMjowMMLLc+kAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAC3RFWHRUaXRsZQBHcm91cJYfIowAAABXelRYdFJhdyBwcm9maWxlIHR5cGUgaXB0YwAAeJzj8gwIcVYoKMpPy8xJ5VIAAyMLLmMLEyMTS5MUAxMgRIA0w2QDI7NUIMvY1MjEzMQcxAfLgEigSi4A6hcRdPJCNZUAAAAASUVORK5CYII="), 524 false, 525 }, 526 { 527 cty.StringVal("testdata/missing"), 528 cty.NilVal, 529 true, // no file exists 530 }, 531 } 532 533 for _, test := range tests { 534 t.Run(fmt.Sprintf("FileBase64(\".\", %#v)", test.Path), func(t *testing.T) { 535 got, err := FileBase64(".", test.Path) 536 537 if test.Err { 538 if err == nil { 539 t.Fatal("succeeded; want error") 540 } 541 return 542 } else if err != nil { 543 t.Fatalf("unexpected error: %s", err) 544 } 545 546 if !got.RawEquals(test.Want) { 547 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 548 } 549 }) 550 } 551 } 552 553 func TestBasename(t *testing.T) { 554 tests := []struct { 555 Path cty.Value 556 Want cty.Value 557 Err bool 558 }{ 559 { 560 cty.StringVal("testdata/hello.txt"), 561 cty.StringVal("hello.txt"), 562 false, 563 }, 564 { 565 cty.StringVal("hello.txt"), 566 cty.StringVal("hello.txt"), 567 false, 568 }, 569 { 570 cty.StringVal(""), 571 cty.StringVal("."), 572 false, 573 }, 574 } 575 576 for _, test := range tests { 577 t.Run(fmt.Sprintf("Basename(%#v)", test.Path), func(t *testing.T) { 578 got, err := Basename(test.Path) 579 580 if test.Err { 581 if err == nil { 582 t.Fatal("succeeded; want error") 583 } 584 return 585 } else if err != nil { 586 t.Fatalf("unexpected error: %s", err) 587 } 588 589 if !got.RawEquals(test.Want) { 590 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 591 } 592 }) 593 } 594 } 595 596 func TestDirname(t *testing.T) { 597 tests := []struct { 598 Path cty.Value 599 Want cty.Value 600 Err bool 601 }{ 602 { 603 cty.StringVal("testdata/hello.txt"), 604 cty.StringVal("testdata"), 605 false, 606 }, 607 { 608 cty.StringVal("testdata/foo/hello.txt"), 609 cty.StringVal("testdata/foo"), 610 false, 611 }, 612 { 613 cty.StringVal("hello.txt"), 614 cty.StringVal("."), 615 false, 616 }, 617 { 618 cty.StringVal(""), 619 cty.StringVal("."), 620 false, 621 }, 622 } 623 624 for _, test := range tests { 625 t.Run(fmt.Sprintf("Dirname(%#v)", test.Path), func(t *testing.T) { 626 got, err := Dirname(test.Path) 627 628 if test.Err { 629 if err == nil { 630 t.Fatal("succeeded; want error") 631 } 632 return 633 } else if err != nil { 634 t.Fatalf("unexpected error: %s", err) 635 } 636 637 if !got.RawEquals(test.Want) { 638 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 639 } 640 }) 641 } 642 } 643 644 func TestPathExpand(t *testing.T) { 645 homePath, err := homedir.Dir() 646 if err != nil { 647 t.Fatalf("Error getting home directory: %v", err) 648 } 649 650 tests := []struct { 651 Path cty.Value 652 Want cty.Value 653 Err bool 654 }{ 655 { 656 cty.StringVal("~/test-file"), 657 cty.StringVal(filepath.Join(homePath, "test-file")), 658 false, 659 }, 660 { 661 cty.StringVal("~/another/test/file"), 662 cty.StringVal(filepath.Join(homePath, "another/test/file")), 663 false, 664 }, 665 { 666 cty.StringVal("/root/file"), 667 cty.StringVal("/root/file"), 668 false, 669 }, 670 { 671 cty.StringVal("/"), 672 cty.StringVal("/"), 673 false, 674 }, 675 } 676 677 for _, test := range tests { 678 t.Run(fmt.Sprintf("Dirname(%#v)", test.Path), func(t *testing.T) { 679 got, err := Pathexpand(test.Path) 680 681 if test.Err { 682 if err == nil { 683 t.Fatal("succeeded; want error") 684 } 685 return 686 } else if err != nil { 687 t.Fatalf("unexpected error: %s", err) 688 } 689 690 if !got.RawEquals(test.Want) { 691 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 692 } 693 }) 694 } 695 }