github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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 }, 168 "tags": tagsSchema(), 169 }, 170 } 171 } 172 173 func resourceAwsCodeBuildProjectCreate(d *schema.ResourceData, meta interface{}) error { 174 conn := meta.(*AWSClient).codebuildconn 175 176 projectEnv := expandProjectEnvironment(d) 177 projectSource := expandProjectSource(d) 178 projectArtifacts := expandProjectArtifacts(d) 179 180 params := &codebuild.CreateProjectInput{ 181 Environment: projectEnv, 182 Name: aws.String(d.Get("name").(string)), 183 Source: &projectSource, 184 Artifacts: &projectArtifacts, 185 } 186 187 if v, ok := d.GetOk("description"); ok { 188 params.Description = aws.String(v.(string)) 189 } 190 191 if v, ok := d.GetOk("encryption_key"); ok { 192 params.EncryptionKey = aws.String(v.(string)) 193 } 194 195 if v, ok := d.GetOk("service_role"); ok { 196 params.ServiceRole = aws.String(v.(string)) 197 } 198 199 if v, ok := d.GetOk("timeout"); ok { 200 params.TimeoutInMinutes = aws.Int64(int64(v.(int))) 201 } 202 203 if v, ok := d.GetOk("tags"); ok { 204 params.Tags = tagsFromMapCodeBuild(v.(map[string]interface{})) 205 } 206 207 var resp *codebuild.CreateProjectOutput 208 err := resource.Retry(2*time.Minute, func() *resource.RetryError { 209 var err error 210 211 resp, err = conn.CreateProject(params) 212 213 if err != nil { 214 return resource.RetryableError(err) 215 } 216 217 return resource.NonRetryableError(err) 218 }) 219 220 if err != nil { 221 return fmt.Errorf("[ERROR] Error creating CodeBuild project: %s", err) 222 } 223 224 d.SetId(*resp.Project.Arn) 225 226 return resourceAwsCodeBuildProjectUpdate(d, meta) 227 } 228 229 func expandProjectArtifacts(d *schema.ResourceData) codebuild.ProjectArtifacts { 230 configs := d.Get("artifacts").(*schema.Set).List() 231 data := configs[0].(map[string]interface{}) 232 233 projectArtifacts := codebuild.ProjectArtifacts{ 234 Type: aws.String(data["type"].(string)), 235 } 236 237 if data["location"].(string) != "" { 238 projectArtifacts.Location = aws.String(data["location"].(string)) 239 } 240 241 if data["name"].(string) != "" { 242 projectArtifacts.Name = aws.String(data["name"].(string)) 243 } 244 245 if data["namespace_type"].(string) != "" { 246 projectArtifacts.NamespaceType = aws.String(data["namespace_type"].(string)) 247 } 248 249 if data["packaging"].(string) != "" { 250 projectArtifacts.Packaging = aws.String(data["packaging"].(string)) 251 } 252 253 if data["path"].(string) != "" { 254 projectArtifacts.Path = aws.String(data["path"].(string)) 255 } 256 257 return projectArtifacts 258 } 259 260 func expandProjectEnvironment(d *schema.ResourceData) *codebuild.ProjectEnvironment { 261 configs := d.Get("environment").(*schema.Set).List() 262 projectEnv := &codebuild.ProjectEnvironment{} 263 264 envConfig := configs[0].(map[string]interface{}) 265 266 if v := envConfig["compute_type"]; v != nil { 267 projectEnv.ComputeType = aws.String(v.(string)) 268 } 269 270 if v := envConfig["image"]; v != nil { 271 projectEnv.Image = aws.String(v.(string)) 272 } 273 274 if v := envConfig["type"]; v != nil { 275 projectEnv.Type = aws.String(v.(string)) 276 } 277 278 if v := envConfig["environment_variable"]; v != nil { 279 envVariables := v.([]interface{}) 280 if len(envVariables) > 0 { 281 projectEnvironmentVariables := make([]*codebuild.EnvironmentVariable, 0, len(envVariables)) 282 283 for _, envVariablesConfig := range envVariables { 284 config := envVariablesConfig.(map[string]interface{}) 285 286 projectEnvironmentVar := &codebuild.EnvironmentVariable{} 287 288 if v := config["name"].(string); v != "" { 289 projectEnvironmentVar.Name = &v 290 } 291 292 if v := config["value"].(string); v != "" { 293 projectEnvironmentVar.Value = &v 294 } 295 296 projectEnvironmentVariables = append(projectEnvironmentVariables, projectEnvironmentVar) 297 } 298 299 projectEnv.EnvironmentVariables = projectEnvironmentVariables 300 } 301 } 302 303 return projectEnv 304 } 305 306 func expandProjectSource(d *schema.ResourceData) codebuild.ProjectSource { 307 configs := d.Get("source").(*schema.Set).List() 308 projectSource := codebuild.ProjectSource{} 309 310 for _, configRaw := range configs { 311 data := configRaw.(map[string]interface{}) 312 313 sourceType := data["type"].(string) 314 location := data["location"].(string) 315 buildspec := data["buildspec"].(string) 316 317 projectSource = codebuild.ProjectSource{ 318 Type: &sourceType, 319 Location: &location, 320 Buildspec: &buildspec, 321 } 322 323 if v, ok := data["auth"]; ok { 324 if len(v.(*schema.Set).List()) > 0 { 325 auth := v.(*schema.Set).List()[0].(map[string]interface{}) 326 327 projectSource.Auth = &codebuild.SourceAuth{ 328 Type: aws.String(auth["type"].(string)), 329 Resource: aws.String(auth["resource"].(string)), 330 } 331 } 332 } 333 } 334 335 return projectSource 336 } 337 338 func resourceAwsCodeBuildProjectRead(d *schema.ResourceData, meta interface{}) error { 339 conn := meta.(*AWSClient).codebuildconn 340 341 resp, err := conn.BatchGetProjects(&codebuild.BatchGetProjectsInput{ 342 Names: []*string{ 343 aws.String(d.Id()), 344 }, 345 }) 346 347 if err != nil { 348 return fmt.Errorf("[ERROR] Error retreiving Projects: %q", err) 349 } 350 351 // if nothing was found, then return no state 352 if len(resp.Projects) == 0 { 353 log.Printf("[INFO]: No projects were found, removing from state") 354 d.SetId("") 355 return nil 356 } 357 358 project := resp.Projects[0] 359 360 if err := d.Set("artifacts", flattenAwsCodebuildProjectArtifacts(project.Artifacts)); err != nil { 361 return err 362 } 363 364 if err := d.Set("environment", schema.NewSet(resourceAwsCodeBuildProjectEnvironmentHash, flattenAwsCodebuildProjectEnvironment(project.Environment))); err != nil { 365 return err 366 } 367 368 if err := d.Set("source", flattenAwsCodebuildProjectSource(project.Source)); err != nil { 369 return err 370 } 371 372 d.Set("description", project.Description) 373 d.Set("encryption_key", project.EncryptionKey) 374 d.Set("name", project.Name) 375 d.Set("service_role", project.ServiceRole) 376 d.Set("timeout", project.TimeoutInMinutes) 377 378 if err := d.Set("tags", tagsToMapCodeBuild(project.Tags)); err != nil { 379 return err 380 } 381 382 return nil 383 } 384 385 func resourceAwsCodeBuildProjectUpdate(d *schema.ResourceData, meta interface{}) error { 386 conn := meta.(*AWSClient).codebuildconn 387 388 params := &codebuild.UpdateProjectInput{ 389 Name: aws.String(d.Get("name").(string)), 390 } 391 392 if d.HasChange("environment") { 393 projectEnv := expandProjectEnvironment(d) 394 params.Environment = projectEnv 395 } 396 397 if d.HasChange("source") { 398 projectSource := expandProjectSource(d) 399 params.Source = &projectSource 400 } 401 402 if d.HasChange("artifacts") { 403 projectArtifacts := expandProjectArtifacts(d) 404 params.Artifacts = &projectArtifacts 405 } 406 407 if d.HasChange("description") { 408 params.Description = aws.String(d.Get("description").(string)) 409 } 410 411 if d.HasChange("encryption_key") { 412 params.EncryptionKey = aws.String(d.Get("encryption_key").(string)) 413 } 414 415 if d.HasChange("service_role") { 416 params.ServiceRole = aws.String(d.Get("service_role").(string)) 417 } 418 419 if d.HasChange("timeout") { 420 params.TimeoutInMinutes = aws.Int64(int64(d.Get("timeout").(int))) 421 } 422 423 // The documentation clearly says "The replacement set of tags for this build project." 424 // But its a slice of pointers so if not set for every update, they get removed. 425 params.Tags = tagsFromMapCodeBuild(d.Get("tags").(map[string]interface{})) 426 427 _, err := conn.UpdateProject(params) 428 429 if err != nil { 430 return fmt.Errorf( 431 "[ERROR] Error updating CodeBuild project (%s): %s", 432 d.Id(), err) 433 } 434 435 return resourceAwsCodeBuildProjectRead(d, meta) 436 } 437 438 func resourceAwsCodeBuildProjectDelete(d *schema.ResourceData, meta interface{}) error { 439 conn := meta.(*AWSClient).codebuildconn 440 441 _, err := conn.DeleteProject(&codebuild.DeleteProjectInput{ 442 Name: aws.String(d.Id()), 443 }) 444 445 if err != nil { 446 return err 447 } 448 449 d.SetId("") 450 451 return nil 452 } 453 454 func flattenAwsCodebuildProjectArtifacts(artifacts *codebuild.ProjectArtifacts) *schema.Set { 455 456 artifactSet := schema.Set{ 457 F: resourceAwsCodeBuildProjectArtifactsHash, 458 } 459 460 values := map[string]interface{}{} 461 462 values["type"] = *artifacts.Type 463 464 if artifacts.Location != nil { 465 values["location"] = *artifacts.Location 466 } 467 468 if artifacts.Name != nil { 469 values["name"] = *artifacts.Name 470 } 471 472 if artifacts.NamespaceType != nil { 473 values["namespace_type"] = *artifacts.NamespaceType 474 } 475 476 if artifacts.Packaging != nil { 477 values["packaging"] = *artifacts.Packaging 478 } 479 480 if artifacts.Path != nil { 481 values["path"] = *artifacts.Path 482 } 483 484 artifactSet.Add(values) 485 486 return &artifactSet 487 } 488 489 func flattenAwsCodebuildProjectEnvironment(environment *codebuild.ProjectEnvironment) []interface{} { 490 envConfig := map[string]interface{}{} 491 492 envConfig["type"] = *environment.Type 493 envConfig["compute_type"] = *environment.ComputeType 494 envConfig["image"] = *environment.Image 495 496 if environment.EnvironmentVariables != nil { 497 envConfig["environment_variable"] = environmentVariablesToMap(environment.EnvironmentVariables) 498 } 499 500 return []interface{}{envConfig} 501 502 } 503 504 func flattenAwsCodebuildProjectSource(source *codebuild.ProjectSource) *schema.Set { 505 506 sourceSet := schema.Set{ 507 F: resourceAwsCodeBuildProjectSourceHash, 508 } 509 510 authSet := schema.Set{ 511 F: resourceAwsCodeBuildProjectSourceAuthHash, 512 } 513 514 sourceConfig := map[string]interface{}{} 515 516 sourceConfig["type"] = *source.Type 517 518 if source.Auth != nil { 519 authSet.Add(sourceAuthToMap(source.Auth)) 520 sourceConfig["auth"] = &authSet 521 } 522 523 if source.Buildspec != nil { 524 sourceConfig["buildspec"] = *source.Buildspec 525 } 526 527 if source.Location != nil { 528 sourceConfig["location"] = *source.Location 529 } 530 531 sourceSet.Add(sourceConfig) 532 533 return &sourceSet 534 535 } 536 537 func resourceAwsCodeBuildProjectArtifactsHash(v interface{}) int { 538 var buf bytes.Buffer 539 m := v.(map[string]interface{}) 540 541 artifactType := m["type"].(string) 542 543 buf.WriteString(fmt.Sprintf("%s-", artifactType)) 544 545 return hashcode.String(buf.String()) 546 } 547 548 func resourceAwsCodeBuildProjectEnvironmentHash(v interface{}) int { 549 var buf bytes.Buffer 550 m := v.(map[string]interface{}) 551 552 environmentType := m["type"].(string) 553 computeType := m["compute_type"].(string) 554 image := m["image"].(string) 555 environmentVariables := m["environment_variable"].([]interface{}) 556 buf.WriteString(fmt.Sprintf("%s-", environmentType)) 557 buf.WriteString(fmt.Sprintf("%s-", computeType)) 558 buf.WriteString(fmt.Sprintf("%s-", image)) 559 for _, e := range environmentVariables { 560 if e != nil { // Old statefiles might have nil values in them 561 ev := e.(map[string]interface{}) 562 buf.WriteString(fmt.Sprintf("%s:%s-", ev["name"].(string), ev["value"].(string))) 563 } 564 } 565 566 return hashcode.String(buf.String()) 567 } 568 569 func resourceAwsCodeBuildProjectSourceHash(v interface{}) int { 570 var buf bytes.Buffer 571 m := v.(map[string]interface{}) 572 573 sourceType := m["type"].(string) 574 buildspec := m["buildspec"].(string) 575 location := m["location"].(string) 576 577 buf.WriteString(fmt.Sprintf("%s-", sourceType)) 578 buf.WriteString(fmt.Sprintf("%s-", buildspec)) 579 buf.WriteString(fmt.Sprintf("%s-", location)) 580 581 return hashcode.String(buf.String()) 582 } 583 584 func resourceAwsCodeBuildProjectSourceAuthHash(v interface{}) int { 585 var buf bytes.Buffer 586 m := v.(map[string]interface{}) 587 588 authType := m["type"].(string) 589 authResource := m["resource"].(string) 590 591 buf.WriteString(fmt.Sprintf("%s-", authType)) 592 buf.WriteString(fmt.Sprintf("%s-", authResource)) 593 594 return hashcode.String(buf.String()) 595 } 596 597 func environmentVariablesToMap(environmentVariables []*codebuild.EnvironmentVariable) []interface{} { 598 599 envVariables := []interface{}{} 600 if len(environmentVariables) > 0 { 601 for _, env := range environmentVariables { 602 item := map[string]interface{}{} 603 item["name"] = *env.Name 604 item["value"] = *env.Value 605 envVariables = append(envVariables, item) 606 } 607 } 608 609 return envVariables 610 } 611 612 func sourceAuthToMap(sourceAuth *codebuild.SourceAuth) map[string]interface{} { 613 614 auth := map[string]interface{}{} 615 auth["type"] = *sourceAuth.Type 616 617 if sourceAuth.Resource != nil { 618 auth["resource"] = *sourceAuth.Resource 619 } 620 621 return auth 622 } 623 624 func validateAwsCodeBuildArifactsType(v interface{}, k string) (ws []string, errors []error) { 625 value := v.(string) 626 types := map[string]bool{ 627 "CODEPIPELINE": true, 628 "NO_ARTIFACTS": true, 629 "S3": true, 630 } 631 632 if !types[value] { 633 errors = append(errors, fmt.Errorf("CodeBuild: Arifacts Type can only be CODEPIPELINE / NO_ARTIFACTS / S3")) 634 } 635 return 636 } 637 638 func validateAwsCodeBuildArifactsNamespaceType(v interface{}, k string) (ws []string, errors []error) { 639 value := v.(string) 640 types := map[string]bool{ 641 "NONE": true, 642 "BUILD_ID": true, 643 } 644 645 if !types[value] { 646 errors = append(errors, fmt.Errorf("CodeBuild: Arifacts Namespace Type can only be NONE / BUILD_ID")) 647 } 648 return 649 } 650 651 func validateAwsCodeBuildProjectName(v interface{}, k string) (ws []string, errors []error) { 652 value := v.(string) 653 if !regexp.MustCompile(`^[A-Za-z0-9]`).MatchString(value) { 654 errors = append(errors, fmt.Errorf( 655 "first character of %q must be a letter or number", value)) 656 } 657 658 if !regexp.MustCompile(`^[A-Za-z0-9\-_]+$`).MatchString(value) { 659 errors = append(errors, fmt.Errorf( 660 "only alphanumeric characters, hyphens and underscores allowed in %q", value)) 661 } 662 663 if len(value) > 255 { 664 errors = append(errors, fmt.Errorf( 665 "%q cannot be greater than 255 characters", value)) 666 } 667 668 return 669 } 670 671 func validateAwsCodeBuildProjectDescription(v interface{}, k string) (ws []string, errors []error) { 672 value := v.(string) 673 if len(value) > 255 { 674 errors = append(errors, fmt.Errorf("%q cannot be greater than 255 characters", value)) 675 } 676 return 677 } 678 679 func validateAwsCodeBuildEnvironmentComputeType(v interface{}, k string) (ws []string, errors []error) { 680 value := v.(string) 681 types := map[string]bool{ 682 "BUILD_GENERAL1_SMALL": true, 683 "BUILD_GENERAL1_MEDIUM": true, 684 "BUILD_GENERAL1_LARGE": true, 685 } 686 687 if !types[value] { 688 errors = append(errors, fmt.Errorf("CodeBuild: Environment Compute Type can only be BUILD_GENERAL1_SMALL / BUILD_GENERAL1_MEDIUM / BUILD_GENERAL1_LARGE")) 689 } 690 return 691 } 692 693 func validateAwsCodeBuildEnvironmentType(v interface{}, k string) (ws []string, errors []error) { 694 value := v.(string) 695 types := map[string]bool{ 696 "LINUX_CONTAINER": true, 697 } 698 699 if !types[value] { 700 errors = append(errors, fmt.Errorf("CodeBuild: Environment Type can only be LINUX_CONTAINER")) 701 } 702 return 703 } 704 705 func validateAwsCodeBuildSourceType(v interface{}, k string) (ws []string, errors []error) { 706 value := v.(string) 707 types := map[string]bool{ 708 "CODECOMMIT": true, 709 "CODEPIPELINE": true, 710 "GITHUB": true, 711 "S3": true, 712 } 713 714 if !types[value] { 715 errors = append(errors, fmt.Errorf("CodeBuild: Source Type can only be CODECOMMIT / CODEPIPELINE / GITHUB / S3")) 716 } 717 return 718 } 719 720 func validateAwsCodeBuildSourceAuthType(v interface{}, k string) (ws []string, errors []error) { 721 value := v.(string) 722 types := map[string]bool{ 723 "OAUTH": true, 724 } 725 726 if !types[value] { 727 errors = append(errors, fmt.Errorf("CodeBuild: Source Auth Type can only be OAUTH")) 728 } 729 return 730 } 731 732 func validateAwsCodeBuildTimeout(v interface{}, k string) (ws []string, errors []error) { 733 value := v.(int) 734 735 if value < 5 || value > 480 { 736 errors = append(errors, fmt.Errorf("%q must be greater than 5 minutes and less than 480 minutes (8 hours)", value)) 737 } 738 return 739 }