github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_codebuild_project.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "regexp" 8 "time" 9 10 "github.com/aws/aws-sdk-go/aws" 11 "github.com/aws/aws-sdk-go/service/codebuild" 12 "github.com/hashicorp/terraform/helper/hashcode" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 func resourceAwsCodeBuildProject() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceAwsCodeBuildProjectCreate, 20 Read: resourceAwsCodeBuildProjectRead, 21 Update: resourceAwsCodeBuildProjectUpdate, 22 Delete: resourceAwsCodeBuildProjectDelete, 23 24 Schema: map[string]*schema.Schema{ 25 "artifacts": &schema.Schema{ 26 Type: schema.TypeSet, 27 Required: true, 28 MaxItems: 1, 29 Elem: &schema.Resource{ 30 Schema: map[string]*schema.Schema{ 31 "name": { 32 Type: schema.TypeString, 33 Optional: true, 34 }, 35 "location": { 36 Type: schema.TypeString, 37 Optional: true, 38 }, 39 "namespace_type": { 40 Type: schema.TypeString, 41 Optional: true, 42 ValidateFunc: validateAwsCodeBuildArifactsNamespaceType, 43 }, 44 "packaging": { 45 Type: schema.TypeString, 46 Optional: true, 47 }, 48 "path": { 49 Type: schema.TypeString, 50 Optional: true, 51 }, 52 "type": { 53 Type: schema.TypeString, 54 Required: true, 55 ValidateFunc: validateAwsCodeBuildArifactsType, 56 }, 57 }, 58 }, 59 Set: resourceAwsCodeBuildProjectArtifactsHash, 60 }, 61 "description": { 62 Type: schema.TypeString, 63 Optional: true, 64 Computed: true, 65 ValidateFunc: validateAwsCodeBuildProjectDescription, 66 }, 67 "encryption_key": { 68 Type: schema.TypeString, 69 Optional: true, 70 Computed: true, 71 }, 72 "environment": &schema.Schema{ 73 Type: schema.TypeSet, 74 Required: true, 75 MaxItems: 1, 76 Elem: &schema.Resource{ 77 Schema: map[string]*schema.Schema{ 78 "compute_type": { 79 Type: schema.TypeString, 80 Required: true, 81 ValidateFunc: validateAwsCodeBuildEnvironmentComputeType, 82 }, 83 "environment_variable": &schema.Schema{ 84 Type: schema.TypeList, 85 Optional: true, 86 Computed: true, 87 Elem: &schema.Resource{ 88 Schema: map[string]*schema.Schema{ 89 "name": { 90 Type: schema.TypeString, 91 Required: true, 92 }, 93 "value": { 94 Type: schema.TypeString, 95 Required: true, 96 }, 97 }, 98 }, 99 }, 100 "image": { 101 Type: schema.TypeString, 102 Required: true, 103 }, 104 "type": { 105 Type: schema.TypeString, 106 Required: true, 107 ValidateFunc: validateAwsCodeBuildEnvironmentType, 108 }, 109 }, 110 }, 111 Set: resourceAwsCodeBuildProjectEnvironmentHash, 112 }, 113 "name": { 114 Type: schema.TypeString, 115 Required: true, 116 ForceNew: true, 117 ValidateFunc: validateAwsCodeBuildProjectName, 118 }, 119 "service_role": { 120 Type: schema.TypeString, 121 Optional: true, 122 Computed: true, 123 }, 124 "source": &schema.Schema{ 125 Type: schema.TypeSet, 126 Elem: &schema.Resource{ 127 Schema: map[string]*schema.Schema{ 128 "auth": &schema.Schema{ 129 Type: schema.TypeSet, 130 Elem: &schema.Resource{ 131 Schema: map[string]*schema.Schema{ 132 "resource": { 133 Type: schema.TypeString, 134 Optional: true, 135 }, 136 "type": { 137 Type: schema.TypeString, 138 Required: true, 139 ValidateFunc: validateAwsCodeBuildSourceAuthType, 140 }, 141 }, 142 }, 143 Optional: true, 144 }, 145 "buildspec": { 146 Type: schema.TypeString, 147 Optional: true, 148 }, 149 "location": { 150 Type: schema.TypeString, 151 Optional: true, 152 }, 153 "type": { 154 Type: schema.TypeString, 155 Required: true, 156 ValidateFunc: validateAwsCodeBuildSourceType, 157 }, 158 }, 159 }, 160 Required: true, 161 MaxItems: 1, 162 }, 163 "timeout": { 164 Type: schema.TypeInt, 165 Optional: true, 166 ValidateFunc: validateAwsCodeBuildTimeout, 167 Removed: "This field has been removed. Please use build_timeout instead", 168 }, 169 "build_timeout": { 170 Type: schema.TypeInt, 171 Optional: true, 172 Default: "60", 173 ValidateFunc: validateAwsCodeBuildTimeout, 174 }, 175 "tags": tagsSchema(), 176 }, 177 } 178 } 179 180 func resourceAwsCodeBuildProjectCreate(d *schema.ResourceData, meta interface{}) error { 181 conn := meta.(*AWSClient).codebuildconn 182 183 projectEnv := expandProjectEnvironment(d) 184 projectSource := expandProjectSource(d) 185 projectArtifacts := expandProjectArtifacts(d) 186 187 params := &codebuild.CreateProjectInput{ 188 Environment: projectEnv, 189 Name: aws.String(d.Get("name").(string)), 190 Source: &projectSource, 191 Artifacts: &projectArtifacts, 192 } 193 194 if v, ok := d.GetOk("description"); ok { 195 params.Description = aws.String(v.(string)) 196 } 197 198 if v, ok := d.GetOk("encryption_key"); ok { 199 params.EncryptionKey = aws.String(v.(string)) 200 } 201 202 if v, ok := d.GetOk("service_role"); ok { 203 params.ServiceRole = aws.String(v.(string)) 204 } 205 206 if v, ok := d.GetOk("build_timeout"); ok { 207 params.TimeoutInMinutes = aws.Int64(int64(v.(int))) 208 } 209 210 if v, ok := d.GetOk("tags"); ok { 211 params.Tags = tagsFromMapCodeBuild(v.(map[string]interface{})) 212 } 213 214 var resp *codebuild.CreateProjectOutput 215 err := resource.Retry(2*time.Minute, func() *resource.RetryError { 216 var err error 217 218 resp, err = conn.CreateProject(params) 219 220 if err != nil { 221 return resource.RetryableError(err) 222 } 223 224 return resource.NonRetryableError(err) 225 }) 226 227 if err != nil { 228 return fmt.Errorf("[ERROR] Error creating CodeBuild project: %s", err) 229 } 230 231 d.SetId(*resp.Project.Arn) 232 233 return resourceAwsCodeBuildProjectUpdate(d, meta) 234 } 235 236 func expandProjectArtifacts(d *schema.ResourceData) codebuild.ProjectArtifacts { 237 configs := d.Get("artifacts").(*schema.Set).List() 238 data := configs[0].(map[string]interface{}) 239 240 projectArtifacts := codebuild.ProjectArtifacts{ 241 Type: aws.String(data["type"].(string)), 242 } 243 244 if data["location"].(string) != "" { 245 projectArtifacts.Location = aws.String(data["location"].(string)) 246 } 247 248 if data["name"].(string) != "" { 249 projectArtifacts.Name = aws.String(data["name"].(string)) 250 } 251 252 if data["namespace_type"].(string) != "" { 253 projectArtifacts.NamespaceType = aws.String(data["namespace_type"].(string)) 254 } 255 256 if data["packaging"].(string) != "" { 257 projectArtifacts.Packaging = aws.String(data["packaging"].(string)) 258 } 259 260 if data["path"].(string) != "" { 261 projectArtifacts.Path = aws.String(data["path"].(string)) 262 } 263 264 return projectArtifacts 265 } 266 267 func expandProjectEnvironment(d *schema.ResourceData) *codebuild.ProjectEnvironment { 268 configs := d.Get("environment").(*schema.Set).List() 269 projectEnv := &codebuild.ProjectEnvironment{} 270 271 envConfig := configs[0].(map[string]interface{}) 272 273 if v := envConfig["compute_type"]; v != nil { 274 projectEnv.ComputeType = aws.String(v.(string)) 275 } 276 277 if v := envConfig["image"]; v != nil { 278 projectEnv.Image = aws.String(v.(string)) 279 } 280 281 if v := envConfig["type"]; v != nil { 282 projectEnv.Type = aws.String(v.(string)) 283 } 284 285 if v := envConfig["environment_variable"]; v != nil { 286 envVariables := v.([]interface{}) 287 if len(envVariables) > 0 { 288 projectEnvironmentVariables := make([]*codebuild.EnvironmentVariable, 0, len(envVariables)) 289 290 for _, envVariablesConfig := range envVariables { 291 config := envVariablesConfig.(map[string]interface{}) 292 293 projectEnvironmentVar := &codebuild.EnvironmentVariable{} 294 295 if v := config["name"].(string); v != "" { 296 projectEnvironmentVar.Name = &v 297 } 298 299 if v := config["value"].(string); v != "" { 300 projectEnvironmentVar.Value = &v 301 } 302 303 projectEnvironmentVariables = append(projectEnvironmentVariables, projectEnvironmentVar) 304 } 305 306 projectEnv.EnvironmentVariables = projectEnvironmentVariables 307 } 308 } 309 310 return projectEnv 311 } 312 313 func expandProjectSource(d *schema.ResourceData) codebuild.ProjectSource { 314 configs := d.Get("source").(*schema.Set).List() 315 projectSource := codebuild.ProjectSource{} 316 317 for _, configRaw := range configs { 318 data := configRaw.(map[string]interface{}) 319 320 sourceType := data["type"].(string) 321 location := data["location"].(string) 322 buildspec := data["buildspec"].(string) 323 324 projectSource = codebuild.ProjectSource{ 325 Type: &sourceType, 326 Location: &location, 327 Buildspec: &buildspec, 328 } 329 330 if v, ok := data["auth"]; ok { 331 if len(v.(*schema.Set).List()) > 0 { 332 auth := v.(*schema.Set).List()[0].(map[string]interface{}) 333 334 projectSource.Auth = &codebuild.SourceAuth{ 335 Type: aws.String(auth["type"].(string)), 336 Resource: aws.String(auth["resource"].(string)), 337 } 338 } 339 } 340 } 341 342 return projectSource 343 } 344 345 func resourceAwsCodeBuildProjectRead(d *schema.ResourceData, meta interface{}) error { 346 conn := meta.(*AWSClient).codebuildconn 347 348 resp, err := conn.BatchGetProjects(&codebuild.BatchGetProjectsInput{ 349 Names: []*string{ 350 aws.String(d.Id()), 351 }, 352 }) 353 354 if err != nil { 355 return fmt.Errorf("[ERROR] Error retreiving Projects: %q", err) 356 } 357 358 // if nothing was found, then return no state 359 if len(resp.Projects) == 0 { 360 log.Printf("[INFO]: No projects were found, removing from state") 361 d.SetId("") 362 return nil 363 } 364 365 project := resp.Projects[0] 366 367 if err := d.Set("artifacts", flattenAwsCodebuildProjectArtifacts(project.Artifacts)); err != nil { 368 return err 369 } 370 371 if err := d.Set("environment", schema.NewSet(resourceAwsCodeBuildProjectEnvironmentHash, flattenAwsCodebuildProjectEnvironment(project.Environment))); err != nil { 372 return err 373 } 374 375 if err := d.Set("source", flattenAwsCodebuildProjectSource(project.Source)); err != nil { 376 return err 377 } 378 379 d.Set("description", project.Description) 380 d.Set("encryption_key", project.EncryptionKey) 381 d.Set("name", project.Name) 382 d.Set("service_role", project.ServiceRole) 383 d.Set("build_timeout", project.TimeoutInMinutes) 384 385 if err := d.Set("tags", tagsToMapCodeBuild(project.Tags)); err != nil { 386 return err 387 } 388 389 return nil 390 } 391 392 func resourceAwsCodeBuildProjectUpdate(d *schema.ResourceData, meta interface{}) error { 393 conn := meta.(*AWSClient).codebuildconn 394 395 params := &codebuild.UpdateProjectInput{ 396 Name: aws.String(d.Get("name").(string)), 397 } 398 399 if d.HasChange("environment") { 400 projectEnv := expandProjectEnvironment(d) 401 params.Environment = projectEnv 402 } 403 404 if d.HasChange("source") { 405 projectSource := expandProjectSource(d) 406 params.Source = &projectSource 407 } 408 409 if d.HasChange("artifacts") { 410 projectArtifacts := expandProjectArtifacts(d) 411 params.Artifacts = &projectArtifacts 412 } 413 414 if d.HasChange("description") { 415 params.Description = aws.String(d.Get("description").(string)) 416 } 417 418 if d.HasChange("encryption_key") { 419 params.EncryptionKey = aws.String(d.Get("encryption_key").(string)) 420 } 421 422 if d.HasChange("service_role") { 423 params.ServiceRole = aws.String(d.Get("service_role").(string)) 424 } 425 426 if d.HasChange("build_timeout") { 427 params.TimeoutInMinutes = aws.Int64(int64(d.Get("build_timeout").(int))) 428 } 429 430 // The documentation clearly says "The replacement set of tags for this build project." 431 // But its a slice of pointers so if not set for every update, they get removed. 432 params.Tags = tagsFromMapCodeBuild(d.Get("tags").(map[string]interface{})) 433 434 _, err := conn.UpdateProject(params) 435 436 if err != nil { 437 return fmt.Errorf( 438 "[ERROR] Error updating CodeBuild project (%s): %s", 439 d.Id(), err) 440 } 441 442 return resourceAwsCodeBuildProjectRead(d, meta) 443 } 444 445 func resourceAwsCodeBuildProjectDelete(d *schema.ResourceData, meta interface{}) error { 446 conn := meta.(*AWSClient).codebuildconn 447 448 _, err := conn.DeleteProject(&codebuild.DeleteProjectInput{ 449 Name: aws.String(d.Id()), 450 }) 451 452 if err != nil { 453 return err 454 } 455 456 d.SetId("") 457 458 return nil 459 } 460 461 func flattenAwsCodebuildProjectArtifacts(artifacts *codebuild.ProjectArtifacts) *schema.Set { 462 463 artifactSet := schema.Set{ 464 F: resourceAwsCodeBuildProjectArtifactsHash, 465 } 466 467 values := map[string]interface{}{} 468 469 values["type"] = *artifacts.Type 470 471 if artifacts.Location != nil { 472 values["location"] = *artifacts.Location 473 } 474 475 if artifacts.Name != nil { 476 values["name"] = *artifacts.Name 477 } 478 479 if artifacts.NamespaceType != nil { 480 values["namespace_type"] = *artifacts.NamespaceType 481 } 482 483 if artifacts.Packaging != nil { 484 values["packaging"] = *artifacts.Packaging 485 } 486 487 if artifacts.Path != nil { 488 values["path"] = *artifacts.Path 489 } 490 491 artifactSet.Add(values) 492 493 return &artifactSet 494 } 495 496 func flattenAwsCodebuildProjectEnvironment(environment *codebuild.ProjectEnvironment) []interface{} { 497 envConfig := map[string]interface{}{} 498 499 envConfig["type"] = *environment.Type 500 envConfig["compute_type"] = *environment.ComputeType 501 envConfig["image"] = *environment.Image 502 503 if environment.EnvironmentVariables != nil { 504 envConfig["environment_variable"] = environmentVariablesToMap(environment.EnvironmentVariables) 505 } 506 507 return []interface{}{envConfig} 508 509 } 510 511 func flattenAwsCodebuildProjectSource(source *codebuild.ProjectSource) *schema.Set { 512 513 sourceSet := schema.Set{ 514 F: resourceAwsCodeBuildProjectSourceHash, 515 } 516 517 authSet := schema.Set{ 518 F: resourceAwsCodeBuildProjectSourceAuthHash, 519 } 520 521 sourceConfig := map[string]interface{}{} 522 523 sourceConfig["type"] = *source.Type 524 525 if source.Auth != nil { 526 authSet.Add(sourceAuthToMap(source.Auth)) 527 sourceConfig["auth"] = &authSet 528 } 529 530 if source.Buildspec != nil { 531 sourceConfig["buildspec"] = *source.Buildspec 532 } 533 534 if source.Location != nil { 535 sourceConfig["location"] = *source.Location 536 } 537 538 sourceSet.Add(sourceConfig) 539 540 return &sourceSet 541 542 } 543 544 func resourceAwsCodeBuildProjectArtifactsHash(v interface{}) int { 545 var buf bytes.Buffer 546 m := v.(map[string]interface{}) 547 548 artifactType := m["type"].(string) 549 550 buf.WriteString(fmt.Sprintf("%s-", artifactType)) 551 552 return hashcode.String(buf.String()) 553 } 554 555 func resourceAwsCodeBuildProjectEnvironmentHash(v interface{}) int { 556 var buf bytes.Buffer 557 m := v.(map[string]interface{}) 558 559 environmentType := m["type"].(string) 560 computeType := m["compute_type"].(string) 561 image := m["image"].(string) 562 environmentVariables := m["environment_variable"].([]interface{}) 563 buf.WriteString(fmt.Sprintf("%s-", environmentType)) 564 buf.WriteString(fmt.Sprintf("%s-", computeType)) 565 buf.WriteString(fmt.Sprintf("%s-", image)) 566 for _, e := range environmentVariables { 567 if e != nil { // Old statefiles might have nil values in them 568 ev := e.(map[string]interface{}) 569 buf.WriteString(fmt.Sprintf("%s:%s-", ev["name"].(string), ev["value"].(string))) 570 } 571 } 572 573 return hashcode.String(buf.String()) 574 } 575 576 func resourceAwsCodeBuildProjectSourceHash(v interface{}) int { 577 var buf bytes.Buffer 578 m := v.(map[string]interface{}) 579 580 sourceType := m["type"].(string) 581 buildspec := m["buildspec"].(string) 582 location := m["location"].(string) 583 584 buf.WriteString(fmt.Sprintf("%s-", sourceType)) 585 buf.WriteString(fmt.Sprintf("%s-", buildspec)) 586 buf.WriteString(fmt.Sprintf("%s-", location)) 587 588 return hashcode.String(buf.String()) 589 } 590 591 func resourceAwsCodeBuildProjectSourceAuthHash(v interface{}) int { 592 var buf bytes.Buffer 593 m := v.(map[string]interface{}) 594 595 buf.WriteString(fmt.Sprintf("%s-", m["type"].(string))) 596 597 if m["resource"] != nil { 598 buf.WriteString(fmt.Sprintf("%s-", m["resource"].(string))) 599 } 600 601 return hashcode.String(buf.String()) 602 } 603 604 func environmentVariablesToMap(environmentVariables []*codebuild.EnvironmentVariable) []interface{} { 605 606 envVariables := []interface{}{} 607 if len(environmentVariables) > 0 { 608 for _, env := range environmentVariables { 609 item := map[string]interface{}{} 610 item["name"] = *env.Name 611 item["value"] = *env.Value 612 envVariables = append(envVariables, item) 613 } 614 } 615 616 return envVariables 617 } 618 619 func sourceAuthToMap(sourceAuth *codebuild.SourceAuth) map[string]interface{} { 620 621 auth := map[string]interface{}{} 622 auth["type"] = *sourceAuth.Type 623 624 if sourceAuth.Resource != nil { 625 auth["resource"] = *sourceAuth.Resource 626 } 627 628 return auth 629 } 630 631 func validateAwsCodeBuildArifactsType(v interface{}, k string) (ws []string, errors []error) { 632 value := v.(string) 633 types := map[string]bool{ 634 "CODEPIPELINE": true, 635 "NO_ARTIFACTS": true, 636 "S3": true, 637 } 638 639 if !types[value] { 640 errors = append(errors, fmt.Errorf("CodeBuild: Arifacts Type can only be CODEPIPELINE / NO_ARTIFACTS / S3")) 641 } 642 return 643 } 644 645 func validateAwsCodeBuildArifactsNamespaceType(v interface{}, k string) (ws []string, errors []error) { 646 value := v.(string) 647 types := map[string]bool{ 648 "NONE": true, 649 "BUILD_ID": true, 650 } 651 652 if !types[value] { 653 errors = append(errors, fmt.Errorf("CodeBuild: Arifacts Namespace Type can only be NONE / BUILD_ID")) 654 } 655 return 656 } 657 658 func validateAwsCodeBuildProjectName(v interface{}, k string) (ws []string, errors []error) { 659 value := v.(string) 660 if !regexp.MustCompile(`^[A-Za-z0-9]`).MatchString(value) { 661 errors = append(errors, fmt.Errorf( 662 "first character of %q must be a letter or number", value)) 663 } 664 665 if !regexp.MustCompile(`^[A-Za-z0-9\-_]+$`).MatchString(value) { 666 errors = append(errors, fmt.Errorf( 667 "only alphanumeric characters, hyphens and underscores allowed in %q", value)) 668 } 669 670 if len(value) > 255 { 671 errors = append(errors, fmt.Errorf( 672 "%q cannot be greater than 255 characters", value)) 673 } 674 675 return 676 } 677 678 func validateAwsCodeBuildProjectDescription(v interface{}, k string) (ws []string, errors []error) { 679 value := v.(string) 680 if len(value) > 255 { 681 errors = append(errors, fmt.Errorf("%q cannot be greater than 255 characters", value)) 682 } 683 return 684 } 685 686 func validateAwsCodeBuildEnvironmentComputeType(v interface{}, k string) (ws []string, errors []error) { 687 value := v.(string) 688 types := map[string]bool{ 689 "BUILD_GENERAL1_SMALL": true, 690 "BUILD_GENERAL1_MEDIUM": true, 691 "BUILD_GENERAL1_LARGE": true, 692 } 693 694 if !types[value] { 695 errors = append(errors, fmt.Errorf("CodeBuild: Environment Compute Type can only be BUILD_GENERAL1_SMALL / BUILD_GENERAL1_MEDIUM / BUILD_GENERAL1_LARGE")) 696 } 697 return 698 } 699 700 func validateAwsCodeBuildEnvironmentType(v interface{}, k string) (ws []string, errors []error) { 701 value := v.(string) 702 types := map[string]bool{ 703 "LINUX_CONTAINER": true, 704 } 705 706 if !types[value] { 707 errors = append(errors, fmt.Errorf("CodeBuild: Environment Type can only be LINUX_CONTAINER")) 708 } 709 return 710 } 711 712 func validateAwsCodeBuildSourceType(v interface{}, k string) (ws []string, errors []error) { 713 value := v.(string) 714 types := map[string]bool{ 715 "CODECOMMIT": true, 716 "CODEPIPELINE": true, 717 "GITHUB": true, 718 "S3": true, 719 } 720 721 if !types[value] { 722 errors = append(errors, fmt.Errorf("CodeBuild: Source Type can only be CODECOMMIT / CODEPIPELINE / GITHUB / S3")) 723 } 724 return 725 } 726 727 func validateAwsCodeBuildSourceAuthType(v interface{}, k string) (ws []string, errors []error) { 728 value := v.(string) 729 types := map[string]bool{ 730 "OAUTH": true, 731 } 732 733 if !types[value] { 734 errors = append(errors, fmt.Errorf("CodeBuild: Source Auth Type can only be OAUTH")) 735 } 736 return 737 } 738 739 func validateAwsCodeBuildTimeout(v interface{}, k string) (ws []string, errors []error) { 740 value := v.(int) 741 742 if value < 5 || value > 480 { 743 errors = append(errors, fmt.Errorf("%q must be greater than 5 minutes and less than 480 minutes (8 hours)", value)) 744 } 745 return 746 }