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