github.com/diggerhq/digger/libs@v0.0.0-20240604170430-9d61cdf01cc5/digger_config/digger_config_test.go (about) 1 package digger_config 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "path" 8 "testing" 9 10 "github.com/dominikbraun/graph" 11 "github.com/go-git/go-git/v5" 12 13 "github.com/stretchr/testify/assert" 14 ) 15 16 func setUp() (string, func()) { 17 tempDir := createTempDir() 18 return tempDir, func() { 19 deleteTempDir(tempDir) 20 } 21 } 22 23 func TestDiggerConfigWhenMultipleConfigExist(t *testing.T) { 24 tempDir, teardown := setUp() 25 defer teardown() 26 27 _, err := os.Create(path.Join(tempDir, "digger.yaml")) 28 if err != nil { 29 t.Fatal(err) 30 } 31 32 _, err = os.Create(path.Join(tempDir, "digger.yml")) 33 if err != nil { 34 t.Fatal(err) 35 } 36 37 dg, _, _, err := LoadDiggerConfig(tempDir, true) 38 assert.Error(t, err, "expected error to be returned") 39 assert.ErrorContains(t, err, ErrDiggerConfigConflict.Error(), "expected error to match target error") 40 assert.Nil(t, dg, "expected diggerConfig to be nil") 41 } 42 43 func TestDiggerConfigWhenCustomFileName(t *testing.T) { 44 tempDir, teardown := setUp() 45 defer teardown() 46 47 os.Setenv("DIGGER_FILENAME", "digger-custom.yml") 48 49 _, err := os.Create(path.Join(tempDir, "digger-custom.yml")) 50 if err != nil { 51 t.Fatal(err) 52 } 53 54 configPath, err := retrieveConfigFile(tempDir) 55 fmt.Println(configPath) 56 57 assert.Nil(t, err) 58 assert.Equal(t, configPath, path.Join(tempDir, "digger-custom.yml")) 59 60 os.Unsetenv("DIGGER_FILENAME") 61 62 } 63 64 func TestDiggerConfigWhenOnlyYamlExists(t *testing.T) { 65 tempDir, teardown := setUp() 66 defer teardown() 67 68 diggerCfg := ` 69 projects: 70 - name: prod 71 branch: /main/ 72 dir: path/to/module/test 73 workspace: default 74 ` 75 deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg) 76 defer deleteFile() 77 78 dg, _, _, err := LoadDiggerConfig(tempDir, true) 79 assert.NoError(t, err, "expected error to be nil") 80 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 81 assert.Equal(t, "path/to/module/test", dg.GetDirectory("prod")) 82 } 83 84 func TestNoDiggerYaml(t *testing.T) { 85 tempDir, teardown := setUp() 86 defer teardown() 87 88 terraformFile := "" 89 deleteFile := createFile(path.Join(tempDir, "main.tf"), terraformFile) 90 defer deleteFile() 91 92 os.Chdir(tempDir) 93 dg, _, _, err := LoadDiggerConfig("./", true) 94 95 assert.NoError(t, err, "expected error to be nil") 96 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 97 assert.Equal(t, 1, len(dg.Projects)) 98 assert.Equal(t, false, dg.AutoMerge) 99 assert.Equal(t, true, dg.Telemetry) 100 assert.Equal(t, false, dg.TraverseToNestedProjects) 101 assert.Equal(t, 1, len(dg.Workflows)) 102 assert.Equal(t, "default", dg.Projects[0].Name) 103 assert.Equal(t, "./", dg.Projects[0].Dir) 104 105 workflow := dg.Workflows["default"] 106 assert.NotNil(t, workflow, "expected workflow to be not nil") 107 assert.NotNil(t, workflow.Plan) 108 assert.NotNil(t, workflow.Plan.Steps) 109 110 assert.NotNil(t, workflow.Apply) 111 assert.NotNil(t, workflow.Apply.Steps) 112 assert.NotNil(t, workflow.EnvVars) 113 assert.NotNil(t, workflow.Configuration) 114 } 115 116 func TestDefaultDiggerConfig(t *testing.T) { 117 tempDir, teardown := setUp() 118 defer teardown() 119 120 diggerCfg := ` 121 projects: 122 - name: prod 123 branch: /main/ 124 dir: path/to/module/test 125 aws_role_to_assume: 126 state: "arn://abc:xyz:state" 127 command: "arn://abc:xyz:cmd" 128 workspace: default 129 workflow_file: "test.yml" 130 ` 131 deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg) 132 defer deleteFile() 133 134 dg, _, _, err := LoadDiggerConfig(tempDir, true) 135 fmt.Printf("%v", err) 136 assert.NoError(t, err, "expected error to be nil") 137 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 138 assert.Equal(t, 1, len(dg.Projects)) 139 assert.Equal(t, false, dg.AutoMerge) 140 assert.Equal(t, true, dg.Telemetry) 141 assert.Equal(t, false, dg.TraverseToNestedProjects) 142 assert.Equal(t, 1, len(dg.Workflows)) 143 144 assert.Equal(t, "prod", dg.Projects[0].Name) 145 assert.Equal(t, "test.yml", dg.Projects[0].WorkflowFile) 146 assert.Equal(t, "path/to/module/test", dg.Projects[0].Dir) 147 assert.Equal(t, "arn://abc:xyz:cmd", dg.Projects[0].AwsRoleToAssume.Command) 148 assert.Equal(t, "arn://abc:xyz:state", dg.Projects[0].AwsRoleToAssume.State) 149 150 workflow := dg.Workflows["default"] 151 assert.NotNil(t, workflow, "expected workflow to be not nil") 152 assert.NotNil(t, workflow.Plan) 153 assert.NotNil(t, workflow.Plan.Steps) 154 155 assert.NotNil(t, workflow.Apply) 156 assert.NotNil(t, workflow.Apply.Steps) 157 assert.NotNil(t, workflow.EnvVars) 158 assert.NotNil(t, workflow.Configuration) 159 160 assert.Equal(t, "path/to/module/test", dg.GetDirectory("prod")) 161 } 162 163 func TestDiggerConfigOneRole(t *testing.T) { 164 tempDir, teardown := setUp() 165 defer teardown() 166 167 diggerCfg := ` 168 projects: 169 - name: prod 170 branch: /main/ 171 aws_role_to_assume: 172 command: "arn://abc:xyz:cmd" 173 ` 174 deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg) 175 defer deleteFile() 176 177 dg, _, _, err := LoadDiggerConfig(tempDir, true) 178 fmt.Printf("%v", err) 179 assert.NoError(t, err, "expected error to be nil") 180 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 181 assert.Equal(t, "arn://abc:xyz:cmd", dg.Projects[0].AwsRoleToAssume.Command) 182 assert.Equal(t, "arn://abc:xyz:cmd", dg.Projects[0].AwsRoleToAssume.State) 183 } 184 185 func TestDiggerConfigDefaultWorkflow(t *testing.T) { 186 tempDir, teardown := setUp() 187 defer teardown() 188 189 diggerCfg := ` 190 projects: 191 - name: prod 192 branch: /main/ 193 dir: path/to/module/test 194 ` 195 deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg) 196 defer deleteFile() 197 198 dg, _, _, err := LoadDiggerConfig(tempDir, true) 199 assert.NoError(t, err, "expected error to be nil") 200 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 201 assert.Equal(t, "default", dg.Projects[0].Workflow) 202 _, ok := dg.Workflows["default"] 203 assert.True(t, ok) 204 } 205 206 func TestDiggerConfigWhenOnlyYmlExists(t *testing.T) { 207 tempDir, teardown := setUp() 208 defer teardown() 209 210 diggerCfg := ` 211 projects: 212 - name: dev 213 branch: /main/ 214 dir: path/to/module 215 workspace: default 216 ` 217 deleteFile := createFile(path.Join(tempDir, "digger.yml"), diggerCfg) 218 defer deleteFile() 219 220 dg, _, _, err := LoadDiggerConfig(tempDir, true) 221 assert.NoError(t, err, "expected error to be nil") 222 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 223 assert.Equal(t, "path/to/module", dg.GetDirectory("dev")) 224 } 225 226 func TestCustomCommandsConfiguration(t *testing.T) { 227 tempDir, teardown := setUp() 228 defer teardown() 229 230 diggerCfg := ` 231 projects: 232 - name: dev 233 dir: infra/dev 234 workflow: myworkflow 235 236 workflows: 237 myworkflow: 238 plan: 239 steps: 240 - run: echo "hello" 241 ` 242 deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg) 243 defer deleteFile() 244 245 dg, _, _, err := LoadDiggerConfig(tempDir, true) 246 assert.NoError(t, err, "expected error to be nil") 247 assert.Equal(t, Step{Action: "run", Value: "echo \"hello\"", Shell: ""}, dg.Workflows["myworkflow"].Plan.Steps[0], "parsed struct does not match expected struct") 248 } 249 250 func TestEnvVarsConfiguration(t *testing.T) { 251 tempDir, teardown := setUp() 252 defer teardown() 253 254 diggerCfg := ` 255 projects: 256 - name: dev 257 branch: /main/ 258 dir: . 259 workspace: default 260 terragrunt: false 261 workflow: myworkflow 262 workflows: 263 myworkflow: 264 plan: 265 steps: 266 - init: 267 extra_args: ["-lock=false"] 268 - plan: 269 extra_args: ["-lock=false"] 270 - run: echo "hello" 271 apply: 272 steps: 273 - apply: 274 extra_args: ["-lock=false"] 275 workflow_configuration: 276 on_pull_request_pushed: [digger plan] 277 on_pull_request_closed: [digger unlock] 278 on_commit_to_default: [digger apply] 279 env_vars: 280 state: 281 - name: TF_VAR_state 282 value: s3://mybucket/terraform.tfstate 283 commands: 284 - name: TF_VAR_command 285 value: plan 286 ` 287 deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg) 288 defer deleteFile() 289 290 dg, _, _, err := LoadDiggerConfig(tempDir, true) 291 assert.NoError(t, err, "expected error to be nil") 292 assert.Equal(t, []EnvVar{ 293 {Name: "TF_VAR_state", Value: "s3://mybucket/terraform.tfstate"}, 294 }, dg.Workflows["myworkflow"].EnvVars.State, "parsed struct does not match expected struct") 295 assert.Equal(t, []EnvVar{ 296 {Name: "TF_VAR_command", Value: "plan"}, 297 }, dg.Workflows["myworkflow"].EnvVars.Commands, "parsed struct does not match expected struct") 298 } 299 300 func TestDefaultValuesForWorkflowConfiguration(t *testing.T) { 301 tempDir, teardown := setUp() 302 defer teardown() 303 304 diggerCfg := ` 305 projects: 306 - name: dev 307 dir: . 308 workflow: dev 309 310 workflows: 311 dev: 312 plan: 313 steps: 314 - run: rm -rf .terraform 315 - init 316 - plan: 317 extra_args: ["-var-file=vars/dev.tfvars"] 318 default: 319 plan: 320 steps: 321 - run: rm -rf .terraform 322 - init 323 - plan: 324 extra_args: ["-var-file=vars/dev.tfvars"] 325 326 ` 327 deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg) 328 defer deleteFile() 329 330 dg, _, _, err := LoadDiggerConfig(tempDir, true) 331 assert.NoError(t, err, "expected error to be nil") 332 assert.Equal(t, Step{Action: "run", Value: "rm -rf .terraform", Shell: ""}, dg.Workflows["dev"].Plan.Steps[0], "parsed struct does not match expected struct") 333 assert.Equal(t, Step{Action: "init", ExtraArgs: nil, Shell: ""}, dg.Workflows["dev"].Plan.Steps[1], "parsed struct does not match expected struct") 334 assert.Equal(t, Step{Action: "plan", ExtraArgs: []string{"-var-file=vars/dev.tfvars"}, Shell: ""}, dg.Workflows["dev"].Plan.Steps[2], "parsed struct does not match expected struct") 335 336 assert.Equal(t, Step{Action: "run", Value: "rm -rf .terraform", Shell: ""}, dg.Workflows["default"].Plan.Steps[0], "parsed struct does not match expected struct") 337 assert.Equal(t, Step{Action: "init", ExtraArgs: nil, Shell: ""}, dg.Workflows["default"].Plan.Steps[1], "parsed struct does not match expected struct") 338 assert.Equal(t, Step{Action: "plan", ExtraArgs: []string{"-var-file=vars/dev.tfvars"}, Shell: ""}, dg.Workflows["default"].Plan.Steps[2], "parsed struct does not match expected struct") 339 } 340 341 func TestDiggerGenerateProjects(t *testing.T) { 342 tempDir, teardown := setUp() 343 defer teardown() 344 345 diggerCfg := ` 346 generate_projects: 347 include: dev/* 348 exclude: dev/project 349 ` 350 deleteFile := createFile(path.Join(tempDir, "digger.yml"), diggerCfg) 351 defer deleteFile() 352 dirsToCreate := []string{"dev/test1", "dev/test2", "dev/project", "testtt"} 353 354 for _, dir := range dirsToCreate { 355 err := os.MkdirAll(path.Join(tempDir, dir), os.ModePerm) 356 defer createFile(path.Join(tempDir, dir, "main.tf"), "")() 357 assert.NoError(t, err, "expected error to be nil") 358 } 359 360 dg, _, _, err := LoadDiggerConfig(tempDir, true) 361 assert.NoError(t, err, "expected error to be nil") 362 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 363 assert.Equal(t, "dev_test1", dg.Projects[0].Name) 364 assert.Equal(t, "dev_test2", dg.Projects[1].Name) 365 assert.Equal(t, "dev/test1", dg.Projects[0].Dir) 366 assert.Equal(t, "dev/test2", dg.Projects[1].Dir) 367 assert.Equal(t, 2, len(dg.Projects)) 368 } 369 370 func TestGenerateProjectsWithoutDiggerConfig(t *testing.T) { 371 tempDir, teardown := setUp() 372 defer teardown() 373 374 dirsWithTfToCreate := []string{"dev/test1", "dev/test1/db", "dev/test1/vpc", "dev/test2", "dev/test2/db", "dev/test2/vpc", "dev/project", "prod/test1", "prod/test2", "prod/project", "test", "modules/test1", "modules/test2"} 375 376 for _, dir := range dirsWithTfToCreate { 377 err := os.MkdirAll(path.Join(tempDir, dir), os.ModePerm) 378 defer createFile(path.Join(tempDir, dir, "main.tf"), "")() 379 assert.NoError(t, err, "expected error to be nil") 380 } 381 382 dirtsWithoutTfToCreate := []string{"docs", "random", "docs/random"} 383 for _, dir := range dirtsWithoutTfToCreate { 384 err := os.MkdirAll(path.Join(tempDir, dir), os.ModePerm) 385 assert.NoError(t, err, "expected error to be nil") 386 } 387 388 dg, _, _, err := LoadDiggerConfig(tempDir, true) 389 assert.NoError(t, err, "expected error to be nil") 390 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 391 assert.Equal(t, "dev_project", dg.Projects[0].Name) 392 assert.Equal(t, "dev_test1", dg.Projects[1].Name) 393 assert.Equal(t, "dev_test2", dg.Projects[2].Name) 394 assert.Equal(t, "prod_project", dg.Projects[3].Name) 395 assert.Equal(t, "prod_test1", dg.Projects[4].Name) 396 assert.Equal(t, "prod_test2", dg.Projects[5].Name) 397 assert.Equal(t, "test", dg.Projects[6].Name) 398 assert.Equal(t, 7, len(dg.Projects)) 399 } 400 401 func TestDiggerGenerateProjectsWithSubDirs(t *testing.T) { 402 tempDir, teardown := setUp() 403 defer teardown() 404 405 diggerCfg := ` 406 generate_projects: 407 include: dev/** 408 exclude: dev/project 409 ` 410 deleteFile := createFile(path.Join(tempDir, "digger.yml"), diggerCfg) 411 defer deleteFile() 412 dirsToCreate := []string{ 413 "dev/test1/utils", 414 "dev/test2", 415 "dev/project", 416 "testtt", 417 } 418 for _, dir := range dirsToCreate { 419 err := os.MkdirAll(path.Join(tempDir, dir), os.ModePerm) 420 defer createFile(path.Join(tempDir, dir, "main.tf"), "")() 421 assert.NoError(t, err, "expected error to be nil") 422 } 423 424 dg, _, _, err := LoadDiggerConfig(tempDir, true) 425 assert.NoError(t, err, "expected error to be nil") 426 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 427 assert.Equal(t, "dev_test1_utils", dg.Projects[0].Name) 428 assert.Equal(t, "dev_test2", dg.Projects[1].Name) 429 assert.Equal(t, "dev/test1/utils", dg.Projects[0].Dir) 430 assert.Equal(t, "dev/test2", dg.Projects[1].Dir) 431 assert.Equal(t, 2, len(dg.Projects)) 432 } 433 434 // A .tfvars file should not be recognised as .tf and break parsing for projects nested deeper 435 // Issue: https://github.com/diggerhq/digger/issues/887 436 func TestDiggerGenerateProjectsWithTfvars(t *testing.T) { 437 tempDir, teardown := setUp() 438 defer teardown() 439 440 dirsWithTfToCreate := []string{"dev/us-east-1"} 441 442 for _, dir := range dirsWithTfToCreate { 443 err := os.MkdirAll(path.Join(tempDir, dir), os.ModePerm) 444 defer createFile(path.Join(tempDir, dir, "main.tf"), "")() 445 assert.NoError(t, err, "expected error to be nil") 446 } 447 448 defer createFile(path.Join(tempDir, "dev", "blank.tfvars"), "")() 449 450 dg, _, _, err := LoadDiggerConfig(tempDir, true) 451 assert.NoError(t, err, "expected error to be nil") 452 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 453 assert.Equal(t, 1, len(dg.Projects)) 454 } 455 456 func TestDiggerGenerateProjectsIgnoreSubdirs(t *testing.T) { 457 tempDir, teardown := setUp() 458 defer teardown() 459 460 diggerCfg := ` 461 generate_projects: 462 include: dev 463 ` 464 deleteFile := createFile(path.Join(tempDir, "digger.yml"), diggerCfg) 465 defer deleteFile() 466 dirsToCreate := []string{ 467 "dev", 468 "dev/test1", 469 "dev/test1/utils", 470 "dev/test2", 471 "dev/project", 472 "testtt", 473 } 474 for _, dir := range dirsToCreate { 475 err := os.MkdirAll(path.Join(tempDir, dir), os.ModePerm) 476 defer createFile(path.Join(tempDir, dir, "main.tf"), "")() 477 assert.NoError(t, err, "expected error to be nil") 478 } 479 dg, _, _, err := LoadDiggerConfig(tempDir, true) 480 assert.NoError(t, err, "expected error to be nil") 481 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 482 assert.Equal(t, "dev", dg.Projects[0].Name) 483 assert.Equal(t, 1, len(dg.Projects)) 484 } 485 486 func TestMissingProjectsReturnsError(t *testing.T) { 487 tempDir, teardown := setUp() 488 defer teardown() 489 490 diggerCfg := ` 491 ` 492 deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg) 493 defer deleteFile() 494 _, _, _, err := LoadDiggerConfig(tempDir, true) 495 assert.ErrorContains(t, err, "no projects digger_config found") 496 } 497 498 func TestDiggerConfigCustomWorkflow(t *testing.T) { 499 tempDir, teardown := setUp() 500 defer teardown() 501 502 diggerCfg := ` 503 projects: 504 - name: my-first-app 505 dir: app-one 506 workflow: my_custom_workflow 507 workflows: 508 my_custom_workflow: 509 steps: 510 - run: echo "run" 511 - init: terraform init 512 - plan: terraform plan 513 ` 514 deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg) 515 defer deleteFile() 516 517 dg, _, _, err := LoadDiggerConfig(tempDir, true) 518 assert.NoError(t, err, "expected error to be nil") 519 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 520 assert.Equal(t, "my_custom_workflow", dg.Projects[0].Workflow) 521 _, ok := dg.Workflows["my_custom_workflow"] 522 assert.True(t, ok) 523 } 524 525 func TestDiggerConfigCustomWorkflowMissingParams(t *testing.T) { 526 tempDir, teardown := setUp() 527 defer teardown() 528 529 // missing workflow digger_config 530 diggerCfg := ` 531 projects: 532 - name: my-first-app 533 dir: app-one 534 workflow: my_custom_workflow 535 ` 536 deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg) 537 defer deleteFile() 538 539 _, _, _, err := LoadDiggerConfig(tempDir, true) 540 assert.Error(t, err, "failed to find workflow digger_config 'my_custom_workflow' for project 'my-first-app'") 541 542 // steps block is missing for workflows 543 diggerCfg = ` 544 projects: 545 - name: my-first-app 546 dir: app-one 547 workflow: my_custom_workflow 548 workflows: 549 my_custom_workflow: 550 ` 551 deleteFile = createFile(path.Join(tempDir, "digger.yaml"), diggerCfg) 552 defer deleteFile() 553 554 diggerConfig, _, _, err := LoadDiggerConfig(tempDir, true) 555 assert.Equal(t, "my_custom_workflow", diggerConfig.Projects[0].Workflow) 556 workflow, ok := diggerConfig.Workflows["my_custom_workflow"] 557 assert.True(t, ok) 558 assert.NotNil(t, workflow) 559 assert.NotNil(t, workflow.Plan) 560 assert.NotNil(t, workflow.Apply) 561 562 } 563 564 func TestDiggerConfigMissingProjectsWorkflow(t *testing.T) { 565 tempDir, teardown := setUp() 566 defer teardown() 567 568 diggerCfg := ` 569 projects: 570 - name: my-first-app 571 dir: app-one 572 workflow: my_custom_workflow 573 workflows: 574 my_custom_workflow_no_one_use: 575 steps: 576 - run: echo "run" 577 - init: terraform init 578 - plan: terraform plan 579 ` 580 deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg) 581 defer deleteFile() 582 583 _, _, _, err := LoadDiggerConfig(tempDir, true) 584 assert.Equal(t, "failed to find workflow digger_config 'my_custom_workflow' for project 'my-first-app'", err.Error()) 585 586 } 587 588 func TestDiggerConfigWithEmptyInitBlock(t *testing.T) { 589 tempDir, teardown := setUp() 590 defer teardown() 591 592 diggerCfg := ` 593 projects: 594 - name: my-first-app 595 dir: app-one 596 workflow: default 597 workflows: 598 default: 599 plan: 600 steps: 601 - init: 602 - plan: 603 extra_args: ["-var-file=$ENV_NAME"] 604 ` 605 deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg) 606 defer deleteFile() 607 608 _, _, _, err := LoadDiggerConfig(tempDir, true) 609 assert.Nil(t, err) 610 } 611 612 func TestDiggerConfigDependencyGraph(t *testing.T) { 613 p1 := Project{ 614 Name: "A", 615 DependencyProjects: []string{"B", "C"}, 616 } 617 618 p2 := Project{ 619 Name: "B", 620 DependencyProjects: []string{"C"}, 621 } 622 623 p3 := Project{ 624 Name: "C", 625 } 626 627 p4 := Project{ 628 Name: "D", 629 } 630 631 p5 := Project{ 632 Name: "E", 633 DependencyProjects: []string{"A"}, 634 } 635 636 p6 := Project{ 637 Name: "F", 638 DependencyProjects: []string{"A", "B"}, 639 } 640 641 projects := []Project{p1, p2, p3, p4, p5, p6} 642 643 g, err := CreateProjectDependencyGraph(projects) 644 645 assert.NoError(t, err, "expected error to be nil") 646 647 orderedProjects, _ := graph.StableTopologicalSort(g, func(s string, s2 string) bool { 648 return s < s2 649 }) 650 651 assert.Equal(t, 6, len(orderedProjects)) 652 assert.Equal(t, []string{"C", "D", "B", "A", "E", "F"}, orderedProjects) 653 } 654 655 func TestDiggerYamlDependencyGraph(t *testing.T) { 656 diggerCfg := ` 657 projects: 658 - name: my-first-app 659 dir: app-one 660 workflow: default 661 - name: my-second-app 662 dir: app-two 663 workflow: default 664 depends_on: ["my-first-app"] 665 ` 666 dg, _, _, err := LoadDiggerConfigFromString(diggerCfg, "./") 667 assert.NoError(t, err, "expected error to be nil") 668 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 669 assert.Equal(t, "default", dg.Projects[0].Workflow) 670 671 assert.Equal(t, "my-first-app", dg.Projects[0].Name) 672 assert.Equal(t, "my-second-app", dg.Projects[1].Name) 673 674 assert.Equal(t, "my-first-app", dg.Projects[1].DependencyProjects[0]) 675 } 676 677 func TestDiggerConfigDependencyGraph2(t *testing.T) { 678 p1 := Project{ 679 Name: "A", 680 DependencyProjects: []string{"B", "C", "D"}, 681 } 682 683 p2 := Project{ 684 Name: "B", 685 DependencyProjects: []string{"E", "F"}, 686 } 687 688 p3 := Project{ 689 Name: "C", 690 DependencyProjects: []string{ 691 "G", 692 }, 693 } 694 695 p4 := Project{ 696 Name: "D", 697 DependencyProjects: []string{ 698 "H", "I", 699 }, 700 } 701 702 p5 := Project{ 703 Name: "E", 704 } 705 706 p6 := Project{ 707 Name: "F", 708 } 709 710 p7 := Project{ 711 Name: "G", 712 } 713 p8 := Project{ 714 Name: "H", 715 } 716 717 p9 := Project{ 718 Name: "I", 719 } 720 721 projects := []Project{p1, p2, p3, p4, p5, p6, p7, p8, p9} 722 723 g, err := CreateProjectDependencyGraph(projects) 724 725 assert.NoError(t, err, "expected error to be nil") 726 727 orderedProjects, _ := graph.StableTopologicalSort(g, func(s string, s2 string) bool { 728 return s > s2 729 }) 730 731 assert.Equal(t, 9, len(orderedProjects)) 732 assert.Equal(t, []string{"I", "H", "G", "F", "E", "D", "C", "B", "A"}, orderedProjects) 733 } 734 735 func TestDiggerConfigDependencyGraphWithCyclesFails(t *testing.T) { 736 p1 := Project{ 737 Name: "A", 738 DependencyProjects: []string{"B"}, 739 } 740 741 p2 := Project{ 742 Name: "B", 743 DependencyProjects: []string{"C"}, 744 } 745 746 p3 := Project{ 747 Name: "C", 748 DependencyProjects: []string{ 749 "A", 750 }, 751 } 752 753 projects := []Project{p1, p2, p3} 754 755 _, err := CreateProjectDependencyGraph(projects) 756 757 assert.Error(t, err, "expected error on cycle") 758 assert.Equal(t, "edge would create a cycle", err.Error()) 759 } 760 761 func TestLoadDiggerConfigYamlFromString(t *testing.T) { 762 diggerCfg := ` 763 projects: 764 - name: prod 765 branch: /main/ 766 dir: path/to/module/test 767 ` 768 769 dg, _, _, err := LoadDiggerConfigFromString(diggerCfg, "./") 770 assert.NoError(t, err, "expected error to be nil") 771 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 772 assert.Equal(t, "default", dg.Projects[0].Workflow) 773 _, ok := dg.Workflows["default"] 774 assert.True(t, ok) 775 } 776 777 func TestDiggerConfigMissingProjectsWorkflowConfiguration(t *testing.T) { 778 tempDir, teardown := setUp() 779 defer teardown() 780 tests := []struct { 781 name string 782 diggerCfg string 783 wantErr string 784 }{ 785 { 786 name: "on_pull_request_pushed empty", 787 diggerCfg: ` 788 projects: 789 - name: dev 790 branch: /main/ 791 dir: . 792 workspace: default 793 terragrunt: false 794 workflow: myworkflow 795 workflows: 796 myworkflow: 797 workflow_configuration: 798 on_pull_request_pushed: 799 on_pull_request_closed: [digger unlock] 800 on_commit_to_default: [digger apply] 801 `, 802 wantErr: "workflow_configuration.on_pull_request_pushed is required", 803 }, 804 { 805 name: "on_pull_request_closed empty", 806 diggerCfg: ` 807 projects: 808 - name: dev 809 branch: /main/ 810 dir: . 811 workspace: default 812 terragrunt: false 813 workflow: myworkflow 814 workflows: 815 myworkflow: 816 workflow_configuration: 817 on_pull_request_pushed: [digger plan] 818 on_pull_request_closed: 819 on_commit_to_default: [digger apply] 820 `, 821 wantErr: "workflow_configuration.on_pull_request_closed is required", 822 }, 823 { 824 name: "on_commit_to_default empty", 825 diggerCfg: ` 826 projects: 827 - name: dev 828 branch: /main/ 829 dir: . 830 workspace: default 831 terragrunt: false 832 workflow: myworkflow 833 workflows: 834 myworkflow: 835 workflow_configuration: 836 on_pull_request_pushed: [digger plan] 837 on_pull_request_closed: [digger unlock] 838 on_commit_to_default: 839 `, 840 wantErr: "workflow_configuration.on_commit_to_default is required", 841 }, 842 } 843 844 for _, tt := range tests { 845 t.Run(tt.name, func(t *testing.T) { 846 deleteFile := createFile(path.Join(tempDir, "digger.yaml"), tt.diggerCfg) 847 defer deleteFile() 848 _, _, _, err := LoadDiggerConfig(tempDir, true) 849 assert.ErrorContains(t, err, tt.wantErr) 850 }) 851 } 852 } 853 854 func createTempDir() string { 855 dir, err := os.MkdirTemp("", "tmp") 856 if err != nil { 857 log.Fatal(err) 858 } 859 return dir 860 } 861 862 func deleteTempDir(name string) { 863 err := os.RemoveAll(name) 864 if err != nil { 865 fmt.Printf("deleteTempDir error, %v", err.Error()) 866 log.Fatal(err) 867 } 868 } 869 870 func createFile(filepath string, content string) func() { 871 f, err := os.Create(filepath) 872 if err != nil { 873 log.Fatal(err) 874 } 875 876 _, err = f.WriteString(content) 877 if err != nil { 878 log.Fatal(err) 879 } 880 881 return func() { 882 err := f.Close() 883 if err != nil { 884 log.Fatal(err) 885 } 886 } 887 } 888 889 func createAndCloseFile(filepath string, content string) error { 890 f, err := os.Create(filepath) 891 if err != nil { 892 return err 893 } 894 895 _, err = f.WriteString(content) 896 if err != nil { 897 return err 898 } 899 900 defer func(f *os.File) { 901 err := f.Close() 902 if err != nil { 903 log.Printf("failed to close file %v\n", f.Name()) 904 } 905 }(f) 906 return nil 907 } 908 909 func TestDiggerGenerateProjectsMultiplePatterns(t *testing.T) { 910 tempDir, teardown := setUp() 911 defer teardown() 912 913 diggerCfg := ` 914 generate_projects: 915 blocks: 916 - include: dev/* 917 exclude: dev/project 918 workflow: dev_workflow 919 - include: prod/* 920 exclude: prod/project 921 workflow: prod_workflow 922 workflows: 923 dev_workflow: 924 steps: 925 - run: echo "run" 926 - init: terraform init 927 - plan: terraform plan 928 prod_workflow: 929 steps: 930 - run: echo "run" 931 - init: terraform init 932 - plan: terraform plan 933 ` 934 deleteFile := createFile(path.Join(tempDir, "digger.yml"), diggerCfg) 935 defer deleteFile() 936 dirsToCreate := []string{"dev/test1", "dev/test2", "dev/project", "testtt", "prod/one"} 937 938 for _, dir := range dirsToCreate { 939 err := os.MkdirAll(path.Join(tempDir, dir), os.ModePerm) 940 defer createFile(path.Join(tempDir, dir, "main.tf"), "")() 941 assert.NoError(t, err, "expected error to be nil") 942 } 943 944 dg, _, _, err := LoadDiggerConfig(tempDir, true) 945 assert.NoError(t, err, "expected error to be nil") 946 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 947 assert.Equal(t, "dev_test1", dg.Projects[0].Name) 948 assert.Equal(t, "dev_test2", dg.Projects[1].Name) 949 assert.Equal(t, "prod_one", dg.Projects[2].Name) 950 assert.Equal(t, "dev_workflow", dg.Projects[0].Workflow) 951 assert.Equal(t, "dev_workflow", dg.Projects[1].Workflow) 952 assert.Equal(t, "prod_workflow", dg.Projects[2].Workflow) 953 assert.Equal(t, "dev/test1", dg.Projects[0].Dir) 954 assert.Equal(t, "dev/test2", dg.Projects[1].Dir) 955 assert.Equal(t, "prod/one", dg.Projects[2].Dir) 956 assert.Equal(t, 3, len(dg.Projects)) 957 } 958 959 // TestDiggerGenerateProjectsEmptyParameters test if missing parameters for generate_projects are handled correctly 960 func TestDiggerGenerateProjectsEmptyParameters(t *testing.T) { 961 _, teardown := setUp() 962 defer teardown() 963 964 diggerCfg := ` 965 generate_projects: 966 ` 967 _, _, _, err := LoadDiggerConfigFromString(diggerCfg, "./") 968 assert.Error(t, err) 969 assert.Equal(t, "no projects digger_config found in 'loaded_yaml_string'", err.Error()) 970 } 971 972 // TestDiggerGenerateProjectsTooManyParameters include/exclude and blocks of include/exclude can't be used together 973 func TestDiggerGenerateProjectsTooManyParameters(t *testing.T) { 974 _, teardown := setUp() 975 defer teardown() 976 977 diggerCfg := ` 978 generate_projects: 979 include: dev/* 980 exclude: dev/project 981 blocks: 982 - include: dev/* 983 exclude: dev/project 984 workflow: default 985 - include: prod/* 986 exclude: prod/project 987 workflow: default 988 ` 989 _, _, _, err := LoadDiggerConfigFromString(diggerCfg, "./") 990 assert.Error(t, err) 991 assert.Equal(t, "if include/exclude patterns are used for project generation, blocks of include/exclude can't be used", err.Error()) 992 } 993 994 func TestDiggerTerragruntProjects(t *testing.T) { 995 tempDir, teardown := setUp() 996 defer teardown() 997 998 diggerCfg := ` 999 projects: 1000 - name: dev 1001 dir: . 1002 terragrunt: true 1003 ` 1004 defer createFile(path.Join(tempDir, "digger.yml"), diggerCfg)() 1005 defer createFile(path.Join(tempDir, "main.tf"), "resource \"null_resource\" \"test4\" {}")() 1006 defer createFile(path.Join(tempDir, "terragrunt.hcl"), "terraform {}")() 1007 1008 _, config, _, err := LoadDiggerConfig(tempDir, true) 1009 assert.NoError(t, err) 1010 1011 print(config) 1012 } 1013 1014 func TestDiggerTerragruntProjectGenerationChainedDependencies(t *testing.T) { 1015 // based on https://github.com/transcend-io/terragrunt-atlantis-config/tree/master/test_examples/chained_dependencies 1016 // TODO: this test is a bit slow because we are cloning the whole repo, maybe we can copy it to a smaller repo 1017 tempDir, teardown := setUp() 1018 defer teardown() 1019 1020 diggerCfg := ` 1021 generate_projects: 1022 terragrunt: true 1023 terragrunt_parsing: 1024 parallel: true 1025 createProjectName: true 1026 defaultWorkflow: default 1027 ` 1028 1029 repoUrl := "https://github.com/diggerhq/terragrunt-atlantis-config-examples.git" 1030 _, err := git.PlainClone(tempDir, false, &git.CloneOptions{ 1031 URL: repoUrl, 1032 Progress: os.Stdout, 1033 }) 1034 assert.NoError(t, err) 1035 1036 // example dir: /test_examples/chained_dependencies 1037 projectDir := tempDir + "/chained_dependencies" 1038 1039 err = createAndCloseFile(path.Join(projectDir, "digger.yml"), diggerCfg) 1040 assert.NoError(t, err) 1041 _, _, _, err = LoadDiggerConfig(projectDir, true) 1042 assert.NoError(t, err) 1043 } 1044 1045 func TestDiggerTerragruntProjectGenerationBasicModule(t *testing.T) { 1046 // based on https://github.com/transcend-io/terragrunt-atlantis-config/tree/master/test_examples/basic_module 1047 1048 tempDir, teardown := setUp() 1049 defer teardown() 1050 1051 diggerCfg := ` 1052 generate_projects: 1053 terragrunt: true 1054 terragrunt_parsing: 1055 parallel: true 1056 createProjectName: true 1057 createWorkspace: true 1058 defaultWorkflow: default 1059 1060 ` 1061 hclFile := `terraform { 1062 source = "git::git@github.com:transcend-io/terraform-aws-fargate-container?ref=v0.0.4" 1063 } 1064 1065 inputs = { 1066 foo = "bar" 1067 } 1068 ` 1069 defer createFile(path.Join(tempDir, "digger.yml"), diggerCfg)() 1070 defer createFile(path.Join(tempDir, "terragrunt.hcl"), hclFile)() 1071 1072 _, config, _, err := LoadDiggerConfig(tempDir, true) 1073 assert.NoError(t, err) 1074 1075 print(config) 1076 } 1077 1078 func TestDiggerTerragruntInfrastructureLiveExample(t *testing.T) { 1079 tempDir, teardown := setUp() 1080 defer teardown() 1081 1082 diggerCfg := ` 1083 generate_projects: 1084 terragrunt: true 1085 terragrunt_parsing: 1086 parallel: true 1087 createProjectName: true 1088 createWorkspace: true 1089 defaultWorkflow: default 1090 ` 1091 1092 repoUrl := "https://github.com/gruntwork-io/terragrunt-infrastructure-live-example" 1093 _, err := git.PlainClone(tempDir, false, &git.CloneOptions{ 1094 URL: repoUrl, 1095 Progress: os.Stdout, 1096 }) 1097 assert.NoError(t, err) 1098 1099 defer createFile(path.Join(tempDir, "digger.yml"), diggerCfg)() 1100 1101 _, config, _, err := LoadDiggerConfig(tempDir, true) 1102 assert.NoError(t, err) 1103 assert.NotNil(t, config) 1104 1105 assert.Equal(t, "non-prod_us-east-1_qa_mysql", config.Projects[0].Name) 1106 assert.Equal(t, "non-prod_us-east-1_qa_webserver-cluster", config.Projects[1].Name) 1107 assert.Equal(t, "non-prod_us-east-1_stage_mysql", config.Projects[2].Name) 1108 assert.Equal(t, "non-prod_us-east-1_stage_webserver-cluster", config.Projects[3].Name) 1109 assert.Equal(t, "prod_us-east-1_prod_mysql", config.Projects[4].Name) 1110 assert.Equal(t, "prod_us-east-1_prod_webserver-cluster", config.Projects[5].Name) 1111 } 1112 1113 func TestDiggerGenerateProjectsMultipleBlocksDemo(t *testing.T) { 1114 tempDir, teardown := setUp() 1115 defer teardown() 1116 1117 repoUrl := "https://github.com/diggerhq/generate_projects_multiple_blocks_demo" 1118 _, err := git.PlainClone(tempDir, false, &git.CloneOptions{ 1119 URL: repoUrl, 1120 Progress: os.Stdout, 1121 }) 1122 assert.NoError(t, err) 1123 1124 _, config, _, err := LoadDiggerConfig(tempDir, true) 1125 assert.NoError(t, err) 1126 assert.NotNil(t, config) 1127 assert.Equal(t, "projects_dev_test1", config.Projects[0].Name) 1128 assert.Equal(t, "projects/dev/test1", config.Projects[0].Dir) 1129 assert.Equal(t, "projects_dev_test2", config.Projects[1].Name) 1130 assert.Equal(t, "projects/dev/test2", config.Projects[1].Dir) 1131 assert.Equal(t, "projects_dev_test3", config.Projects[2].Name) 1132 assert.Equal(t, "projects/dev/test3", config.Projects[2].Dir) 1133 assert.Equal(t, "projects_prod_test1", config.Projects[3].Name) 1134 assert.Equal(t, "projects/prod/test1", config.Projects[3].Dir) 1135 assert.Equal(t, "projects_prod_test2", config.Projects[4].Name) 1136 assert.Equal(t, "projects/prod/test2", config.Projects[4].Dir) 1137 assert.Equal(t, 5, len(config.Projects)) 1138 } 1139 1140 // todo test terragrunt digger_config with terragrunt_parsing block but without terragrunt: true 1141 1142 // TestDiggerTraverseToNestedProjects test if traverse_to_nested_projects is set to true, digger will traverse to nested projects 1143 func TestDiggerTraverseToNestedProjects(t *testing.T) { 1144 tempDir, teardown := setUp() 1145 defer teardown() 1146 1147 diggerCfg := ` 1148 allow_draft_prs: true 1149 traverse_to_nested_projects: true 1150 generate_projects: 1151 blocks: 1152 - include: dev/** 1153 aws_role_to_assume: 1154 state: "arn://abc:xyz:state" 1155 command: "arn://abc:xyz:cmd" 1156 ` 1157 deleteFile := createFile(path.Join(tempDir, "digger.yml"), diggerCfg) 1158 defer deleteFile() 1159 dirsToCreate := []string{"dev/test1", "dev/test2", "dev/project", "dev/project/test3", "testtt"} 1160 1161 for _, dir := range dirsToCreate { 1162 err := os.MkdirAll(path.Join(tempDir, dir), os.ModePerm) 1163 defer createFile(path.Join(tempDir, dir, "main.tf"), "")() 1164 assert.NoError(t, err, "expected error to be nil") 1165 } 1166 1167 dg, _, _, err := LoadDiggerConfig(tempDir, true) 1168 assert.NoError(t, err, "expected error to be nil") 1169 assert.NotNil(t, dg, "expected digger digger_config to be not nil") 1170 assert.Equal(t, true, dg.TraverseToNestedProjects) 1171 assert.Equal(t, 4, len(dg.Projects)) 1172 assert.Equal(t, "arn://abc:xyz:cmd", dg.Projects[0].AwsRoleToAssume.Command) 1173 assert.Equal(t, "arn://abc:xyz:state", dg.Projects[0].AwsRoleToAssume.State) 1174 assert.Equal(t, "dev_project", dg.Projects[0].Name) 1175 assert.Equal(t, "dev/project", dg.Projects[0].Dir) 1176 assert.Equal(t, "dev_project_test3", dg.Projects[1].Name) 1177 assert.Equal(t, "dev/project/test3", dg.Projects[1].Dir) 1178 assert.Equal(t, "dev_test1", dg.Projects[2].Name) 1179 assert.Equal(t, "dev/test1", dg.Projects[2].Dir) 1180 assert.Equal(t, "dev_test2", dg.Projects[3].Name) 1181 assert.Equal(t, "dev/test2", dg.Projects[3].Dir) 1182 assert.Equal(t, true, dg.AllowDraftPRs) 1183 } 1184 1185 // TestDiggerAllowDraftPRs tests if allow_draft_prs is set to true, digger will allow draft PRs. Defaults to false 1186 func TestDiggerAllowDraftPRs(t *testing.T) { 1187 tempDir, teardown := setUp() 1188 defer teardown() 1189 1190 diggerCfg := ` 1191 projects: 1192 - name: dev 1193 dir: . 1194 ` 1195 defer createFile(path.Join(tempDir, "digger.yml"), diggerCfg)() 1196 defer createFile(path.Join(tempDir, "main.tf"), "resource \"null_resource\" \"test4\" {}")() 1197 1198 dg, _, _, err := LoadDiggerConfig(tempDir, true) 1199 assert.NoError(t, err) 1200 assert.Equal(t, false, dg.AllowDraftPRs) 1201 } 1202 1203 func TestGetModifiedProjectsReturnsCorrectSourceMapping(t *testing.T) { 1204 changedFiles := []string{"modules/bucket/main.tf", "dev/main.tf"} 1205 projects := []Project{ 1206 Project{ 1207 Name: "dev", 1208 Dir: "dev", 1209 IncludePatterns: []string{"modules/**"}, 1210 }, 1211 Project{ 1212 Name: "prod", 1213 Dir: "prod", 1214 IncludePatterns: []string{"modules/**"}, 1215 }, 1216 } 1217 c := DiggerConfig{ 1218 Projects: projects, 1219 } 1220 expectedImpactingLocations := map[string]ProjectToSourceMapping{ 1221 "dev": {ImpactingLocations: []string{"modules/bucket", "dev"}}, 1222 "prod": {ImpactingLocations: []string{"modules/bucket"}}, 1223 } 1224 1225 impactedProjects, projectSourceMapping := c.GetModifiedProjects(changedFiles) 1226 assert.Equal(t, 2, len(impactedProjects)) 1227 assert.Equal(t, 2, len(projectSourceMapping)) 1228 assert.Equal(t, 2, len(projectSourceMapping["dev"].ImpactingLocations)) 1229 assert.Equal(t, 1, len(projectSourceMapping["prod"].ImpactingLocations)) 1230 assert.Equal(t, expectedImpactingLocations["dev"].ImpactingLocations, projectSourceMapping["dev"].ImpactingLocations) 1231 assert.Equal(t, expectedImpactingLocations["prod"].ImpactingLocations, projectSourceMapping["prod"].ImpactingLocations) 1232 1233 }