diff --git a/.changelog/39237.txt b/.changelog/39237.txt new file mode 100644 index 00000000000..a4b40563c2d --- /dev/null +++ b/.changelog/39237.txt @@ -0,0 +1,11 @@ +```release-note:new-resource +aws_codebuild_fleet +``` + +```release-note:new-data-source +aws_codebuild_fleet +``` + +```release-note:enhancement +resource/aws_codebuild_project: Add `fleet` attribute in `environment` configuration block +``` diff --git a/internal/service/codebuild/exports_test.go b/internal/service/codebuild/exports_test.go index 2b585b4c3c9..aeadfb7c054 100644 --- a/internal/service/codebuild/exports_test.go +++ b/internal/service/codebuild/exports_test.go @@ -5,12 +5,14 @@ package codebuild // Exports for use in tests only. var ( + ResourceFleet = resourceFleet ResourceProject = resourceProject ResourceReportGroup = resourceReportGroup ResourceResourcePolicy = resourceResourcePolicy ResourceSourceCredential = resourceSourceCredential ResourceWebhook = resourceWebhook + FindFleetByARN = findFleetByARN FindProjectByNameOrARN = findProjectByNameOrARN FindReportGroupByARN = findReportGroupByARN FindResourcePolicyByARN = findResourcePolicyByARN diff --git a/internal/service/codebuild/fleet.go b/internal/service/codebuild/fleet.go new file mode 100644 index 00000000000..6e9fe8e9207 --- /dev/null +++ b/internal/service/codebuild/fleet.go @@ -0,0 +1,665 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package codebuild + +import ( + "context" + "errors" + "log" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/codebuild" + "github.com/aws/aws-sdk-go-v2/service/codebuild/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKResource("aws_codebuild_fleet", name="Fleet") +// @Tags(identifierAttribute="arn") +func resourceFleet() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceFleetCreate, + ReadWithoutTimeout: resourceFleetRead, + UpdateWithoutTimeout: resourceFleetUpdate, + DeleteWithoutTimeout: resourceFleetDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + names.AttrARN: { + Type: schema.TypeString, + Computed: true, + }, + "base_capacity": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "compute_type": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.ComputeType](), + }, + "created": { + Type: schema.TypeString, + Computed: true, + }, + "environment_type": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.EnvironmentType](), + }, + "fleet_service_role": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: verify.ValidARN, + }, + names.AttrID: { + Type: schema.TypeString, + Computed: true, + }, + "image_id": { + Type: schema.TypeString, + Optional: true, + }, + "last_modified": { + Type: schema.TypeString, + Computed: true, + }, + names.AttrName: { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(2, 128), + }, + "overflow_behavior": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: enum.Validate[types.FleetOverflowBehavior](), + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if new == "" { + return true + } + return old == new + }, + }, + "scaling_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "desired_capacity": { + Type: schema.TypeInt, + Computed: true, + }, + names.AttrMaxCapacity: { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "scaling_type": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.FleetScalingType](), + }, + "target_tracking_scaling_configs": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "metric_type": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.FleetScalingMetricType](), + }, + "target_value": { + Type: schema.TypeFloat, + Optional: true, + ValidateFunc: validation.FloatAtLeast(0), + }, + }, + }, + }, + }, + }, + }, + names.AttrStatus: { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "context": { + Type: schema.TypeString, + Computed: true, + }, + names.AttrMessage: { + Type: schema.TypeString, + Computed: true, + }, + names.AttrStatusCode: { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), + names.AttrVPCConfig: { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + RequiredWith: []string{"fleet_service_role"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + names.AttrSecurityGroupIDs: { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + MinItems: 1, + MaxItems: 5, + }, + names.AttrSubnets: { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + MinItems: 1, + MaxItems: 16, + }, + names.AttrVPCID: { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + CustomizeDiff: verify.SetTagsDiff, + } +} + +const ( + resNameFleet = "Fleet" +) + +func resourceFleetCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + conn := meta.(*conns.AWSClient).CodeBuildClient(ctx) + + input := &codebuild.CreateFleetInput{ + BaseCapacity: aws.Int32(int32(d.Get("base_capacity").(int))), + ComputeType: types.ComputeType(d.Get("compute_type").(string)), + EnvironmentType: types.EnvironmentType(d.Get("environment_type").(string)), + Name: aws.String(d.Get(names.AttrName).(string)), + Tags: getTagsIn(ctx), + } + + if v, ok := d.GetOk("fleet_service_role"); ok { + input.FleetServiceRole = aws.String(v.(string)) + } + + if v, ok := d.GetOk("image_id"); ok { + input.ImageId = aws.String(v.(string)) + } + + if v, ok := d.GetOk("overflow_behavior"); ok { + input.OverflowBehavior = types.FleetOverflowBehavior(v.(string)) + } + + if v, ok := d.GetOk("scaling_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.ScalingConfiguration = expandScalingConfiguration(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk(names.AttrVPCConfig); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.VpcConfig = expandVPCConfig(v.([]interface{})[0].(map[string]interface{})) + } + + // InvalidInputException: CodeBuild is not authorized to perform + outputRaw, err := tfresource.RetryWhenIsAErrorMessageContains[*types.InvalidInputException](ctx, propagationTimeout, func() (interface{}, error) { + return conn.CreateFleet(ctx, input) + }, "ot authorized to perform") + + if err != nil { + return create.AppendDiagError(diags, names.CodeBuild, create.ErrActionCreating, resNameFleet, d.Get(names.AttrName).(string), err) + } + + d.SetId(aws.ToString(outputRaw.(*codebuild.CreateFleetOutput).Fleet.Arn)) + + const ( + timeout = 20 * time.Minute + ) + if _, err := waitFleetCreated(ctx, conn, d.Id(), timeout); err != nil { + return create.AppendDiagError(diags, names.CodeBuild, create.ErrActionWaitingForCreation, resNameFleet, d.Id(), err) + } + + return append(diags, resourceFleetRead(ctx, d, meta)...) +} + +func resourceFleetRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + conn := meta.(*conns.AWSClient).CodeBuildClient(ctx) + + fleet, err := findFleetByARN(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] CodeBuild Fleet (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return create.AppendDiagError(diags, names.CodeBuild, create.ErrActionReading, resNameFleet, d.Id(), err) + } + + d.Set(names.AttrARN, fleet.Arn) + d.Set("base_capacity", fleet.BaseCapacity) + d.Set("compute_type", fleet.ComputeType) + d.Set("created", aws.ToTime(fleet.Created).Format(time.RFC3339)) + d.Set("environment_type", fleet.EnvironmentType) + d.Set("fleet_service_role", fleet.FleetServiceRole) + d.Set(names.AttrID, fleet.Id) + d.Set("image_id", fleet.ImageId) + d.Set("last_modified", aws.ToTime(fleet.LastModified).Format(time.RFC3339)) + d.Set(names.AttrName, fleet.Name) + d.Set("overflow_behavior", fleet.OverflowBehavior) + if fleet.ScalingConfiguration != nil { + if err := d.Set("scaling_configuration", []interface{}{flattenScalingConfiguration(fleet.ScalingConfiguration)}); err != nil { + return create.AppendDiagError(diags, names.CodeBuild, create.ErrActionSetting, resNameFleet, d.Id(), err) + } + } else { + d.Set("scaling_configuration", nil) + } + if fleet.Status != nil { + if err := d.Set(names.AttrStatus, []interface{}{flattenStatus(fleet.Status)}); err != nil { + return create.AppendDiagError(diags, names.CodeBuild, create.ErrActionSetting, resNameFleet, d.Id(), err) + } + } else { + d.Set(names.AttrStatus, nil) + } + if err := d.Set(names.AttrVPCConfig, flattenVPCConfig(fleet.VpcConfig)); err != nil { + return create.AppendDiagError(diags, names.CodeBuild, create.ErrActionSetting, resNameFleet, d.Id(), err) + } + + setTagsOut(ctx, fleet.Tags) + + return diags +} + +func resourceFleetUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).CodeBuildClient(ctx) + + input := &codebuild.UpdateFleetInput{ + Arn: aws.String(d.Id()), + } + + if d.HasChange("base_capacity") { + input.BaseCapacity = aws.Int32(int32(d.Get("base_capacity").(int))) + } + + if d.HasChange("compute_type") { + input.ComputeType = types.ComputeType(d.Get("compute_type").(string)) + } + + if d.HasChange("environment_type") { + input.EnvironmentType = types.EnvironmentType(d.Get("environment_type").(string)) + } + + if d.HasChange("fleet_service_role") { + input.FleetServiceRole = aws.String(d.Get("fleet_service_role").(string)) + } + + if d.HasChange("image_id") { + input.ImageId = aws.String(d.Get("image_id").(string)) + } + + // Make sure that overflow_behavior is set (if defined) on update - API omits it on updates. + if v, ok := d.GetOk("overflow_behavior"); ok { + input.OverflowBehavior = types.FleetOverflowBehavior(v.(string)) + } + + if d.HasChange("scaling_configuration") { + if v, ok := d.GetOk("scaling_configuration"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.ScalingConfiguration = expandScalingConfiguration(v.([]interface{})[0].(map[string]interface{})) + } + } + + if d.HasChange(names.AttrVPCConfig) { + if v, ok := d.GetOk(names.AttrVPCConfig); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.VpcConfig = expandVPCConfig(v.([]interface{})[0].(map[string]interface{})) + } + } + + input.Tags = getTagsIn(ctx) + + _, err := tfresource.RetryWhenIsAErrorMessageContains[*types.InvalidInputException](ctx, propagationTimeout, func() (interface{}, error) { + return conn.UpdateFleet(ctx, input) + }, "ot authorized to perform") + + if err != nil { + return create.AppendDiagError(diags, names.CodeBuild, create.ErrActionUpdating, resNameFleet, d.Id(), err) + } + + const ( + timeout = 20 * time.Minute + ) + if _, err := waitFleetUpdated(ctx, conn, d.Id(), timeout); err != nil { + return create.AppendDiagError(diags, names.CodeBuild, create.ErrActionWaitingForUpdate, resNameFleet, d.Id(), err) + } + + return append(diags, resourceFleetRead(ctx, d, meta)...) +} + +func resourceFleetDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).CodeBuildClient(ctx) + + log.Printf("[INFO] Deleting CodeBuild Fleet: %s", d.Id()) + _, err := conn.DeleteFleet(ctx, &codebuild.DeleteFleetInput{ + Arn: aws.String(d.Id()), + }) + + if errs.IsA[*types.ResourceNotFoundException](err) { + return diags + } + + if err != nil { + return create.AppendDiagError(diags, names.CodeBuild, create.ErrActionDeleting, resNameFleet, d.Id(), err) + } + + const ( + timeout = 20 * time.Minute + ) + if _, err := waitFleetDeleted(ctx, conn, d.Id(), timeout); err != nil { + return create.AppendDiagError(diags, names.CodeBuild, create.ErrActionWaitingForDeletion, resNameFleet, d.Id(), err) + } + + return diags +} + +func findFleetByARN(ctx context.Context, conn *codebuild.Client, arn string) (*types.Fleet, error) { + input := &codebuild.BatchGetFleetsInput{ + Names: []string{arn}, + } + + output, err := findFleet(ctx, conn, input) + + if err != nil { + return nil, err + } + + if statusCode := output.Status.StatusCode; statusCode == types.FleetStatusCodePendingDeletion { + return nil, &retry.NotFoundError{ + Message: string(statusCode), + LastRequest: input, + } + } + + return output, nil +} + +func findFleet(ctx context.Context, conn *codebuild.Client, input *codebuild.BatchGetFleetsInput) (*types.Fleet, error) { + output, err := findFleets(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output, func(v *types.Fleet) bool { + return v.Status != nil + }) +} + +func findFleets(ctx context.Context, conn *codebuild.Client, input *codebuild.BatchGetFleetsInput) ([]types.Fleet, error) { + output, err := conn.BatchGetFleets(ctx, input) + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.Fleets, nil +} + +func statusFleet(ctx context.Context, conn *codebuild.Client, arn string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findFleetByARN(ctx, conn, arn) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, string(output.Status.StatusCode), nil + } +} + +func waitFleetCreated(ctx context.Context, conn *codebuild.Client, arn string, timeout time.Duration) (*types.Fleet, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.FleetStatusCodeCreating, types.FleetStatusCodeRotating), + Target: enum.Slice(types.FleetStatusCodeActive), + Refresh: statusFleet(ctx, conn, arn), + Timeout: timeout, + MinTimeout: 15 * time.Second, + Delay: 15 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*types.Fleet); ok { + tfresource.SetLastError(err, errors.New(aws.ToString(output.Status.Message))) + + return output, err + } + + return nil, err +} + +func waitFleetUpdated(ctx context.Context, conn *codebuild.Client, arn string, timeout time.Duration) (*types.Fleet, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.FleetStatusCodeUpdating, types.FleetStatusCodeRotating), + Target: enum.Slice(types.FleetStatusCodeActive), + Refresh: statusFleet(ctx, conn, arn), + Timeout: timeout, + MinTimeout: 15 * time.Second, + Delay: 15 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*types.Fleet); ok { + tfresource.SetLastError(err, errors.New(aws.ToString(output.Status.Message))) + + return output, err + } + + return nil, err +} + +func waitFleetDeleted(ctx context.Context, conn *codebuild.Client, arn string, timeout time.Duration) (*types.Fleet, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.FleetStatusCodeDeleting), + Target: []string{}, + Refresh: statusFleet(ctx, conn, arn), + Timeout: timeout, + MinTimeout: 15 * time.Second, + Delay: 15 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*types.Fleet); ok { + tfresource.SetLastError(err, errors.New(aws.ToString(output.Status.Message))) + + return output, err + } + + return nil, err +} + +func expandScalingConfiguration(tfMap map[string]interface{}) *types.ScalingConfigurationInput { + if tfMap == nil { + return nil + } + + apiObject := &types.ScalingConfigurationInput{} + + if v, ok := tfMap[names.AttrMaxCapacity].(int); ok { + apiObject.MaxCapacity = aws.Int32(int32(v)) + } + + if v, ok := tfMap["scaling_type"].(string); ok && v != "" { + apiObject.ScalingType = types.FleetScalingType(v) + } + + if v, ok := tfMap["target_tracking_scaling_configs"].([]interface{}); ok && len(v) > 0 { + apiObject.TargetTrackingScalingConfigs = expandTargetTrackingScalingConfigs(v) + } + + return apiObject +} + +func expandTargetTrackingScalingConfigs(tfList []interface{}) []types.TargetTrackingScalingConfiguration { + if len(tfList) == 0 { + return nil + } + + var apiObjects []types.TargetTrackingScalingConfiguration + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + if !ok { + continue + } + + apiObject := expandTargetTrackingScalingConfig(tfMap) + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, *apiObject) + } + return apiObjects +} + +func expandTargetTrackingScalingConfig(tfMap map[string]interface{}) *types.TargetTrackingScalingConfiguration { + if tfMap == nil { + return nil + } + + apiObject := &types.TargetTrackingScalingConfiguration{} + + if v, ok := tfMap["metric_type"].(string); ok { + apiObject.MetricType = types.FleetScalingMetricType(v) + } + + if v, ok := tfMap["target_value"].(float64); ok { + apiObject.TargetValue = aws.Float64(v) + } + + return apiObject +} + +func flattenScalingConfiguration(apiObject *types.ScalingConfigurationOutput) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.DesiredCapacity; v != nil { + tfMap["desired_capacity"] = aws.ToInt32(v) + } + + if v := apiObject.MaxCapacity; v != nil { + tfMap[names.AttrMaxCapacity] = aws.ToInt32(v) + } + + if v := apiObject.ScalingType; v != "" { + tfMap["scaling_type"] = v + } + + if v := apiObject.TargetTrackingScalingConfigs; v != nil { + tfMap["target_tracking_scaling_configs"] = flattenTargetTrackingScalingConfigs(v) + } + + return tfMap +} + +func flattenTargetTrackingScalingConfigs(apiObjects []types.TargetTrackingScalingConfiguration) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + tfList = append(tfList, flattenTargetTrackingScalingConfig(&apiObject)) + } + + return tfList +} + +func flattenTargetTrackingScalingConfig(apiObject *types.TargetTrackingScalingConfiguration) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.MetricType; v != "" { + tfMap["metric_type"] = v + } + + if v := apiObject.TargetValue; v != nil { + tfMap["target_value"] = aws.ToFloat64(v) + } + + return tfMap +} + +func flattenStatus(apiObject *types.FleetStatus) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Context; v != "" { + tfMap["context"] = v + } + + if v := apiObject.Message; v != nil { + tfMap[names.AttrMessage] = aws.ToString(v) + } + + if v := apiObject.StatusCode; v != "" { + tfMap[names.AttrStatusCode] = v + } + + return tfMap +} diff --git a/internal/service/codebuild/fleet_data_source.go b/internal/service/codebuild/fleet_data_source.go new file mode 100644 index 00000000000..1506426c727 --- /dev/null +++ b/internal/service/codebuild/fleet_data_source.go @@ -0,0 +1,202 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package codebuild + +import ( + "context" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKDataSource("aws_codebuild_fleet", name="Fleet") +func dataSourceFleet() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceFleetRead, + + Schema: map[string]*schema.Schema{ + names.AttrARN: { + Type: schema.TypeString, + Computed: true, + }, + "base_capacity": { + Type: schema.TypeInt, + Computed: true, + }, + "compute_type": { + Type: schema.TypeString, + Computed: true, + }, + "created": { + Type: schema.TypeString, + Computed: true, + }, + "environment_type": { + Type: schema.TypeString, + Computed: true, + }, + "fleet_service_role": { + Type: schema.TypeString, + Computed: true, + }, + names.AttrID: { + Type: schema.TypeString, + Computed: true, + }, + "image_id": { + Type: schema.TypeString, + Computed: true, + }, + "last_modified": { + Type: schema.TypeString, + Computed: true, + }, + names.AttrName: { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(2, 128), + }, + "overflow_behavior": { + Type: schema.TypeString, + Computed: true, + }, + "scaling_configuration": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "desired_capacity": { + Type: schema.TypeInt, + Computed: true, + }, + names.AttrMaxCapacity: { + Type: schema.TypeInt, + Computed: true, + }, + "scaling_type": { + Type: schema.TypeString, + Computed: true, + }, + "target_tracking_scaling_configs": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "metric_type": { + Type: schema.TypeString, + Computed: true, + }, + "target_value": { + Type: schema.TypeFloat, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + names.AttrStatus: { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "context": { + Type: schema.TypeString, + Computed: true, + }, + names.AttrMessage: { + Type: schema.TypeString, + Computed: true, + }, + names.AttrStatusCode: { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + names.AttrTags: tftags.TagsSchemaComputed(), + names.AttrVPCConfig: { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + names.AttrSecurityGroupIDs: { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + names.AttrSubnets: { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + names.AttrVPCID: { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +const ( + dsNameFleet = "Fleet Data Source" +) + +func dataSourceFleetRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + conn := meta.(*conns.AWSClient).CodeBuildClient(ctx) + name := d.Get(names.AttrName).(string) + + fleet, err := findFleetByARN(ctx, conn, name) + + if err != nil { + return create.AppendDiagError(diags, names.CodeBuild, create.ErrActionReading, dsNameFleet, name, err) + } + + d.SetId(aws.ToString(fleet.Arn)) + d.Set(names.AttrARN, fleet.Arn) + d.Set("base_capacity", fleet.BaseCapacity) + d.Set("compute_type", fleet.ComputeType) + d.Set("created", aws.ToTime(fleet.Created).Format(time.RFC3339)) + d.Set("environment_type", fleet.EnvironmentType) + d.Set("fleet_service_role", fleet.FleetServiceRole) + d.Set("image_id", fleet.ImageId) + d.Set("last_modified", aws.ToTime(fleet.LastModified).Format(time.RFC3339)) + d.Set(names.AttrName, fleet.Name) + d.Set("overflow_behavior", fleet.OverflowBehavior) + if fleet.ScalingConfiguration != nil { + if err := d.Set("scaling_configuration", []interface{}{flattenScalingConfiguration(fleet.ScalingConfiguration)}); err != nil { + return create.AppendDiagError(diags, names.CodeBuild, create.ErrActionSetting, dsNameFleet, d.Id(), err) + } + } + if fleet.Status != nil { + if err := d.Set(names.AttrStatus, []interface{}{flattenStatus(fleet.Status)}); err != nil { + return create.AppendDiagError(diags, names.CodeBuild, create.ErrActionSetting, dsNameFleet, d.Id(), err) + } + } + if err := d.Set(names.AttrVPCConfig, flattenVPCConfig(fleet.VpcConfig)); err != nil { + return create.AppendDiagError(diags, names.CodeBuild, create.ErrActionSetting, dsNameFleet, d.Id(), err) + } + + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + //lintignore:AWSR002 + if err := d.Set(names.AttrTags, KeyValueTags(ctx, fleet.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return create.AppendDiagError(diags, names.CodeBuild, create.ErrActionSetting, dsNameFleet, d.Id(), err) + } + + return diags +} diff --git a/internal/service/codebuild/fleet_data_source_test.go b/internal/service/codebuild/fleet_data_source_test.go new file mode 100644 index 00000000000..7d0f3697a9b --- /dev/null +++ b/internal/service/codebuild/fleet_data_source_test.go @@ -0,0 +1,71 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package codebuild_test + +import ( + "fmt" + "testing" + + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccCodeBuildFleetDataSource_basic(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_codebuild_fleet.test" + datasourceName := "data.aws_codebuild_fleet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CodeBuildServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccFleetDataSourceConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(datasourceName, names.AttrARN, resourceName, names.AttrARN), + resource.TestCheckResourceAttrPair(datasourceName, "base_capacity", resourceName, "base_capacity"), + resource.TestCheckResourceAttrPair(datasourceName, "compute_type", resourceName, "compute_type"), + resource.TestCheckResourceAttrPair(datasourceName, "created", resourceName, "created"), + resource.TestCheckResourceAttrPair(datasourceName, "environment_type", resourceName, "environment_type"), + resource.TestCheckResourceAttrPair(datasourceName, names.AttrID, resourceName, names.AttrID), + resource.TestCheckResourceAttrPair(datasourceName, "last_modified", resourceName, "last_modified"), + resource.TestCheckResourceAttrPair(datasourceName, names.AttrName, resourceName, names.AttrName), + resource.TestCheckResourceAttrPair(datasourceName, "overflow_behavior", resourceName, "overflow_behavior"), + resource.TestCheckResourceAttrPair(datasourceName, "scaling_configuration.0.max_capacity", resourceName, "scaling_configuration.0.max_capacity"), + resource.TestCheckResourceAttrPair(datasourceName, "scaling_configuration.0.scaling_type", resourceName, "scaling_configuration.0.scaling_type"), + resource.TestCheckResourceAttrPair(datasourceName, "scaling_configuration.0.target_tracking_scaling_configs.0.metric_type", resourceName, "scaling_configuration.0.target_tracking_scaling_configs.0.metric_type"), + resource.TestCheckResourceAttrPair(datasourceName, "scaling_configuration.0.target_tracking_scaling_configs.0.target_value", resourceName, "scaling_configuration.0.target_tracking_scaling_configs.0.target_value"), + ), + }, + }, + }) +} + +func testAccFleetDataSourceConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_codebuild_fleet" "test" { + base_capacity = 1 + compute_type = "BUILD_GENERAL1_SMALL" + environment_type = "ARM_CONTAINER" + name = %q + overflow_behavior = "QUEUE" + scaling_configuration { + max_capacity = 2 + scaling_type = "TARGET_TRACKING_SCALING" + target_tracking_scaling_configs { + metric_type = "FLEET_UTILIZATION_RATE" + target_value = 97.5 + } + } +} + +data "aws_codebuild_fleet" "test" { + name = aws_codebuild_fleet.test.name +} +`, rName) +} diff --git a/internal/service/codebuild/fleet_test.go b/internal/service/codebuild/fleet_test.go new file mode 100644 index 00000000000..f70627f808e --- /dev/null +++ b/internal/service/codebuild/fleet_test.go @@ -0,0 +1,593 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package codebuild_test + +import ( + "context" + "fmt" + "testing" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/service/codebuild/types" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfcodebuild "github.com/hashicorp/terraform-provider-aws/internal/service/codebuild" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccCodeBuildFleet_basic(t *testing.T) { + ctx := context.Background() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_codebuild_fleet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CodeBuildServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "base_capacity", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "compute_type", "BUILD_GENERAL1_SMALL"), + resource.TestCheckResourceAttr(resourceName, "environment_type", "LINUX_CONTAINER"), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttr(resourceName, "overflow_behavior", "ON_DEMAND"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccCodeBuildFleet_disappears(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_codebuild_fleet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CodeBuildServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfcodebuild.ResourceFleet(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccCodeBuildFleet_tags(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_codebuild_fleet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CodeBuildServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), + ), + }, + { + Config: testAccFleetConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct2), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1Updated), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + { + Config: testAccFleetConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + }, + }) +} + +func TestAccCodeBuildFleet_baseCapacity(t *testing.T) { + ctx := context.Background() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_codebuild_fleet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CodeBuildServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_baseCapacity(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "base_capacity", acctest.Ct1), + ), + }, + { + Config: testAccFleetConfig_baseCapacity(rName, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "base_capacity", acctest.Ct2), + ), + }, + }, + }) +} + +func TestAccCodeBuildFleet_computeType(t *testing.T) { + ctx := context.Background() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_codebuild_fleet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CodeBuildServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_computeType(rName, types.ComputeTypeBuildGeneral1Small), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "compute_type", "BUILD_GENERAL1_SMALL"), + ), + }, + { + Config: testAccFleetConfig_computeType(rName, types.ComputeTypeBuildGeneral1Medium), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "compute_type", "BUILD_GENERAL1_MEDIUM"), + ), + }, + }, + }) +} + +func TestAccCodeBuildFleet_environmentType(t *testing.T) { + ctx := context.Background() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_codebuild_fleet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CodeBuildServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_environmentType(rName, types.EnvironmentTypeLinuxContainer), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "environment_type", "LINUX_CONTAINER"), + ), + }, + { + Config: testAccFleetConfig_environmentType(rName, types.EnvironmentTypeArmContainer), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "environment_type", "ARM_CONTAINER"), + ), + }, + }, + }) +} + +func TestAccCodeBuildFleet_imageId(t *testing.T) { + ctx := context.Background() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_codebuild_fleet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CodeBuildServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_imageId(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "image_id", "aws/codebuild/macos-arm-base:14"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccCodeBuildFleet_scalingConfiguration(t *testing.T) { + ctx := context.Background() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_codebuild_fleet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CodeBuildServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_scalingConfiguration1(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "scaling_configuration.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "scaling_configuration.0.max_capacity", acctest.Ct2), + resource.TestCheckResourceAttr(resourceName, "scaling_configuration.0.scaling_type", "TARGET_TRACKING_SCALING"), + resource.TestCheckResourceAttr(resourceName, "scaling_configuration.0.target_tracking_scaling_configs.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "scaling_configuration.0.target_tracking_scaling_configs.0.metric_type", "FLEET_UTILIZATION_RATE"), + resource.TestCheckResourceAttr(resourceName, "scaling_configuration.0.target_tracking_scaling_configs.0.target_value", "97.5"), + ), + }, + { + Config: testAccFleetConfig_scalingConfiguration2(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "scaling_configuration.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "scaling_configuration.0.max_capacity", acctest.Ct3), + resource.TestCheckResourceAttr(resourceName, "scaling_configuration.0.scaling_type", "TARGET_TRACKING_SCALING"), + resource.TestCheckResourceAttr(resourceName, "scaling_configuration.0.target_tracking_scaling_configs.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "scaling_configuration.0.target_tracking_scaling_configs.0.metric_type", "FLEET_UTILIZATION_RATE"), + resource.TestCheckResourceAttr(resourceName, "scaling_configuration.0.target_tracking_scaling_configs.0.target_value", "90.5"), + ), + }, + }, + }) +} + +func TestAccCodeBuildFleet_vpcConfig(t *testing.T) { + ctx := context.Background() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_codebuild_fleet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CodeBuildServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckFleetDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccFleetConfig_vpcConfig2(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "vpc_config.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "vpc_config.0.security_group_ids.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "vpc_config.0.subnets.#", acctest.Ct1), + resource.TestCheckResourceAttrPair(resourceName, "vpc_config.0.subnets.0", "aws_subnet.test.1", names.AttrID), + resource.TestMatchResourceAttr(resourceName, "vpc_config.0.vpc_id", regexache.MustCompile(`^vpc-`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccFleetConfig_vpcConfig1(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckFleetExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "vpc_config.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "vpc_config.0.security_group_ids.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "vpc_config.0.subnets.#", acctest.Ct1), + resource.TestCheckResourceAttrPair(resourceName, "vpc_config.0.subnets.0", "aws_subnet.test.0", names.AttrID), + resource.TestMatchResourceAttr(resourceName, "vpc_config.0.vpc_id", regexache.MustCompile(`^vpc-`)), + ), + }, + }, + }) +} + +func testAccCheckFleetDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).CodeBuildClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_codebuild_fleet" { + continue + } + + _, err := tfcodebuild.FindFleetByARN(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("CodeBuild Fleet %s still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckFleetExists(ctx context.Context, n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).CodeBuildClient(ctx) + + _, err := tfcodebuild.FindFleetByARN(ctx, conn, rs.Primary.ID) + + return err + } +} + +func testAccFleetConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_codebuild_fleet" "test" { + base_capacity = 1 + compute_type = "BUILD_GENERAL1_SMALL" + environment_type = "LINUX_CONTAINER" + name = %[1]q + overflow_behavior = "ON_DEMAND" +} +`, rName) +} + +func testAccFleetConfig_baseCapacity(rName string, baseCapacity int) string { + return fmt.Sprintf(` +resource "aws_codebuild_fleet" "test" { + base_capacity = %[2]d + compute_type = "BUILD_GENERAL1_SMALL" + environment_type = "LINUX_CONTAINER" + name = %[1]q + overflow_behavior = "ON_DEMAND" +} +`, rName, baseCapacity) +} + +func testAccFleetConfig_computeType(rName string, computeType types.ComputeType) string { + return fmt.Sprintf(` +resource "aws_codebuild_fleet" "test" { + base_capacity = 1 + compute_type = %[2]q + environment_type = "LINUX_CONTAINER" + name = %[1]q + overflow_behavior = "ON_DEMAND" +} +`, rName, string(computeType)) +} + +func testAccFleetConfig_environmentType(rName string, environmentType types.EnvironmentType) string { + return fmt.Sprintf(` +resource "aws_codebuild_fleet" "test" { + base_capacity = 1 + compute_type = "BUILD_GENERAL1_LARGE" + environment_type = %[2]q + name = %[1]q + overflow_behavior = "ON_DEMAND" +} +`, rName, string(environmentType)) +} + +func testAccFleetConfig_imageId(rName string) string { + return fmt.Sprintf(` +resource "aws_codebuild_fleet" "test" { + base_capacity = 1 + compute_type = "BUILD_GENERAL1_MEDIUM" + environment_type = "MAC_ARM" + name = %[1]q + overflow_behavior = "QUEUE" + image_id = "aws/codebuild/macos-arm-base:14" +} +`, rName) +} + +func testAccFleetConfig_scalingConfiguration1(rName string) string { + return fmt.Sprintf(` +resource "aws_codebuild_fleet" "test" { + base_capacity = 1 + compute_type = "BUILD_GENERAL1_SMALL" + environment_type = "ARM_CONTAINER" + name = %[1]q + overflow_behavior = "QUEUE" + + scaling_configuration { + max_capacity = 2 + scaling_type = "TARGET_TRACKING_SCALING" + + target_tracking_scaling_configs { + metric_type = "FLEET_UTILIZATION_RATE" + target_value = 97.5 + } + } +} +`, rName) +} + +func testAccFleetConfig_scalingConfiguration2(rName string) string { + return fmt.Sprintf(` +resource "aws_codebuild_fleet" "test" { + base_capacity = 1 + compute_type = "BUILD_GENERAL1_SMALL" + environment_type = "ARM_CONTAINER" + name = %[1]q + overflow_behavior = "QUEUE" + + scaling_configuration { + max_capacity = 3 + scaling_type = "TARGET_TRACKING_SCALING" + + target_tracking_scaling_configs { + metric_type = "FLEET_UTILIZATION_RATE" + target_value = 90.5 + } + } +} +`, rName) +} + +func testAccFleetConfig_baseFleetServiceRole(rName string) string { + return fmt.Sprintf(` +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = < 0 && v[0] != nil { + tfMap := v[0].(map[string]interface{}) + + projectFleet := &types.ProjectFleet{} + + if v, ok := tfMap["fleet_arn"]; ok && v.(string) != "" { + projectFleet.FleetArn = aws.String(v.(string)) + } + + apiObject.Fleet = projectFleet + } + if v, ok := tfMap["image"].(string); ok && v != "" { apiObject.Image = aws.String(v) } @@ -1805,6 +1831,7 @@ func flattenProjectEnvironment(apiObject *types.ProjectEnvironment) []interface{ names.AttrType: apiObject.Type, } + tfMap["fleet"] = flattenFleet(apiObject.Fleet) tfMap["image"] = aws.ToString(apiObject.Image) tfMap[names.AttrCertificate] = aws.ToString(apiObject.Certificate) tfMap["privileged_mode"] = aws.ToBool(apiObject.PrivilegedMode) @@ -1817,6 +1844,18 @@ func flattenProjectEnvironment(apiObject *types.ProjectEnvironment) []interface{ return []interface{}{tfMap} } +func flattenFleet(apiObject *types.ProjectFleet) []interface{} { + if apiObject == nil { + return []interface{}{} + } + + tfMap := map[string]interface{}{ + "fleet_arn": aws.ToString(apiObject.FleetArn), + } + + return []interface{}{tfMap} +} + func flattenRegistryCredential(apiObject *types.RegistryCredential) []interface{} { if apiObject == nil { return []interface{}{} diff --git a/internal/service/codebuild/project_test.go b/internal/service/codebuild/project_test.go index 4013cd418b1..ca76c1dc418 100644 --- a/internal/service/codebuild/project_test.go +++ b/internal/service/codebuild/project_test.go @@ -2860,6 +2860,40 @@ func TestAccCodeBuildProject_concurrentBuildLimit(t *testing.T) { }) } +func TestAccCodeBuildProject_fleet(t *testing.T) { + ctx := acctest.Context(t) + var project types.Project + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resourceName := "aws_codebuild_project.test" + roleResourceName := "aws_iam_role.test" + fleetResourceName := "aws_codebuild_fleet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + testAccPreCheckSourceCredentialsForServerType(ctx, t, types.ServerTypeGithub) + }, + ErrorCheck: acctest.ErrorCheck(t, names.CodeBuildServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProjectDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProjectConfig_fleet(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProjectExists(ctx, resourceName, &project), + acctest.CheckResourceAttrRegionalARN(resourceName, names.AttrARN, "codebuild", fmt.Sprintf("project/%s", rName)), + resource.TestCheckResourceAttr(resourceName, "environment.0.compute_type", string(types.ComputeTypeBuildGeneral1Small)), + resource.TestCheckResourceAttr(resourceName, "environment.0.fleet.#", acctest.Ct1), + resource.TestCheckResourceAttrPair(resourceName, "environment.0.fleet.0.fleet_arn", fleetResourceName, names.AttrARN), + resource.TestCheckResourceAttrPair(resourceName, names.AttrServiceRole, roleResourceName, names.AttrARN), + ), + }, + }, + }) +} + func testAccCheckProjectExists(ctx context.Context, n string, v *types.Project) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -5558,3 +5592,38 @@ resource "aws_codebuild_project" "test" { } `, concurrentBuildLimit, rName)) } + +func testAccProjectConfig_fleet(rName string) string { + return acctest.ConfigCompose(testAccProjectConfig_baseServiceRole(rName), fmt.Sprintf(` +resource "aws_codebuild_fleet" "test" { + base_capacity = 1 + compute_type = "BUILD_GENERAL1_SMALL" + environment_type = "LINUX_CONTAINER" + name = %[1]q + overflow_behavior = "ON_DEMAND" +} + +resource "aws_codebuild_project" "test" { + name = %[1]q + service_role = aws_iam_role.test.arn + + artifacts { + type = "NO_ARTIFACTS" + } + + environment { + compute_type = "BUILD_GENERAL1_SMALL" + image = "2" + type = "LINUX_CONTAINER" + fleet { + fleet_arn = aws_codebuild_fleet.test.arn + } + } + + source { + location = %[2]q + type = "GITHUB" + } +} +`, rName, testAccGitHubSourceLocationFromEnv())) +} diff --git a/internal/service/codebuild/service_package_gen.go b/internal/service/codebuild/service_package_gen.go index dfce895ba83..ee483570cdb 100644 --- a/internal/service/codebuild/service_package_gen.go +++ b/internal/service/codebuild/service_package_gen.go @@ -23,11 +23,25 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic } func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { - return []*types.ServicePackageSDKDataSource{} + return []*types.ServicePackageSDKDataSource{ + { + Factory: dataSourceFleet, + TypeName: "aws_codebuild_fleet", + Name: "Fleet", + }, + } } func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { return []*types.ServicePackageSDKResource{ + { + Factory: resourceFleet, + TypeName: "aws_codebuild_fleet", + Name: "Fleet", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: names.AttrARN, + }, + }, { Factory: resourceProject, TypeName: "aws_codebuild_project", diff --git a/website/docs/d/codebuild_fleet.html.markdown b/website/docs/d/codebuild_fleet.html.markdown new file mode 100644 index 00000000000..73e720a7060 --- /dev/null +++ b/website/docs/d/codebuild_fleet.html.markdown @@ -0,0 +1,82 @@ +--- +subcategory: "CodeBuild" +layout: "aws" +page_title: "AWS: aws_codebuild_fleet" +description: |- + Retrieve information about an CodeBuild Fleet +--- + +# Data Source: aws_codebuild_fleet + +Retrieve information about an CodeBuild Fleet. + +## Example Usage + +```terraform +data "aws_codebuild_fleet" "test" { + name = aws_codebuild_fleet.test.name +} + +resource "aws_codebuild_fleet" "test" { + base_capacity = 2 + compute_type = "BUILD_GENERAL1_SMALL" + environment_type = "LINUX_CONTAINER" + name = "full-example-codebuild-fleet" + overflow_behavior = "QUEUE" + + scaling_configuration { + max_capacity = 5 + scaling_type = "TARGET_TRACKING_SCALING" + + target_tracking_scaling_configs { + metric_type = "FLEET_UTILIZATION_RATE" + target_value = 97.5 + } + } +} +``` + +### Basic Usage + +```terraform +data "aws_codebuild_fleet" "example" { + name = "my-codebuild-fleet-name" +} +``` + +## Argument Reference + +The following arguments are required: + +* `name` - (Required) Fleet name. + +## Attribute Reference + +This data source exports the following attributes in addition to the arguments above: + +* `arn` - ARN of the Fleet. +* `base_capacity` - Number of machines allocated to the fleet. +* `compute_type` - Compute resources the compute fleet uses. +* `created` - Creation time of the fleet. +* `environment_type` - Environment type of the compute fleet. +* `fleet_service_role` - The service role associated with the compute fleet. +* `id` - ARN of the Fleet. +* `image_id` - The Amazon Machine Image (AMI) of the compute fleet. +* `last_modified` - Last modification time of the fleet. +* `overflow_behavior` - Overflow behavior for compute fleet. +* `scaling_configuration` - Nested attribute containing information about the scaling configuration. + * `desired_capacity` - The desired number of instances in the fleet when auto-scaling. + * `max_capacity` - The maximum number of instances in the fleet when auto-scaling. + * `scaling_type` - The scaling type for a compute fleet. + * `target_tracking_scaling_configs` - Nested attribute containing information about thresholds when new instance is auto-scaled into the compute fleet. + * `metric_type` - The metric type to determine auto-scaling. + * `target_value` - The value of metric_type when to start scaling. +* `status` - Nested attribute containing information about the current status of the fleet. + * `context` - Additional information about a compute fleet. + * `message` - Message associated with the status of a compute fleet. + * `status_code` - Status code of the compute fleet. +* `tags` - Mapping of Key-Value tags for the resource. +* `vpc_config` - Nested attribute containing information about the VPC configuration. + * `security_group_ids` - A list of one or more security groups IDs in your Amazon VPC. + * `subnets` - A list of one or more subnet IDs in your Amazon VPC. + * `vpc_id` - The ID of the Amazon VPC. diff --git a/website/docs/r/codebuild_fleet.html.markdown b/website/docs/r/codebuild_fleet.html.markdown new file mode 100644 index 00000000000..a4bc345181b --- /dev/null +++ b/website/docs/r/codebuild_fleet.html.markdown @@ -0,0 +1,106 @@ +--- +subcategory: "CodeBuild" +layout: "aws" +page_title: "AWS: aws_codebuild_fleet" +description: |- + Provides a CodeBuild Fleet Resource. +--- + +# Resource: aws_codebuild_fleet + +Provides a CodeBuild Fleet Resource. + +## Example Usage + +```terraform +resource "aws_codebuild_fleet" "test" { + base_capacity = 2 + compute_type = "BUILD_GENERAL1_SMALL" + environment_type = "LINUX_CONTAINER" + name = "full-example-codebuild-fleet" + overflow_behavior = "QUEUE" + + scaling_configuration { + max_capacity = 5 + scaling_type = "TARGET_TRACKING_SCALING" + + target_tracking_scaling_configs { + metric_type = "FLEET_UTILIZATION_RATE" + target_value = 97.5 + } + } +} +``` + +### Basic Usage + +```terraform +resource "aws_codebuild_fleet" "example" { + name = "example-codebuild-fleet" +} +``` + +## Argument Reference + +The following arguments are required: + +* `name` - (Required) Fleet name. +* `base_capacity` - (Required) Number of machines allocated to the fleet. +* `compute_type` - (Required) Compute resources the compute fleet uses. See [compute types](https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-compute-types.html#environment.types) for more information and valid values. +* `environment_type` - (Required) Environment type of the compute fleet. See [environment types](https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-compute-types.html#environment.types) for more information and valid values. + +The following arguments are optional: + +* `fleet_service_role` - (Optional) The service role associated with the compute fleet. +* `image_id` - (Optional) The Amazon Machine Image (AMI) of the compute fleet. +* `overflow_behavior` - (Optional) Overflow behavior for compute fleet. Valid values: `ON_DEMAND`, `QUEUE`. +* `scaling_configuration` - (Optional) Configuration block. Detailed below. This option is only valid when your overflow behavior is `QUEUE`. +* `tags` - (Optional) Map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. +* `vpc_config` - (Optional) Configuration block. Detailed below. + +### scaling_configuration + +* `max_capacity` - (Optional) Maximum number of instances in the fleet when auto-scaling. +* `scaling_type` - (Optional) Scaling type for a compute fleet. Valid value: `TARGET_TRACKING_SCALING`. +* `target_tracking_scaling_configs` - (Optional) Configuration block. Detailed below. + +#### scaling_configuration: target_tracking_scaling_configs + +* `metric_type` - (Optional) Metric type to determine auto-scaling. Valid value: `FLEET_UTILIZATION_RATE`. +* `target_value` - (Optional) Value of metricType when to start scaling. + +### vpc_config + +* `security_group_ids` - (Required) A list of one or more security groups IDs in your Amazon VPC. +* `subnets` - (Required) A list of one or more subnet IDs in your Amazon VPC. +* `vpc_id` - (Required) The ID of the Amazon VPC. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - ARN of the Fleet. +* `created` - Creation time of the fleet. +* `id` - ARN of the Fleet. +* `last_modified` - Last modification time of the fleet. +* `status` - Nested attribute containing information about the current status of the fleet. + * `context` - Additional information about a compute fleet. + * `message` - Message associated with the status of a compute fleet. + * `status_code` - Status code of the compute fleet. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import CodeBuild Fleet using the `name` or the `arn`. For example: + +```terraform +import { + to = aws_codebuild_fleet.name + id = "fleet-name" +} +``` + +Using `terraform import`, import CodeBuild Fleet using the `name`. For example: + +```console +% terraform import aws_codebuild_fleet.name fleet-name +``` diff --git a/website/docs/r/codebuild_project.html.markdown b/website/docs/r/codebuild_project.html.markdown index d6d9b314e8b..afc307b2df7 100755 --- a/website/docs/r/codebuild_project.html.markdown +++ b/website/docs/r/codebuild_project.html.markdown @@ -289,6 +289,7 @@ The following arguments are optional: * `certificate` - (Optional) ARN of the S3 bucket, path prefix and object key that contains the PEM-encoded certificate. * `compute_type` - (Required) Information about the compute resources the build project will use. Valid values: `BUILD_GENERAL1_SMALL`, `BUILD_GENERAL1_MEDIUM`, `BUILD_GENERAL1_LARGE`, `BUILD_GENERAL1_2XLARGE`, `BUILD_LAMBDA_1GB`, `BUILD_LAMBDA_2GB`, `BUILD_LAMBDA_4GB`, `BUILD_LAMBDA_8GB`, `BUILD_LAMBDA_10GB`. `BUILD_GENERAL1_SMALL` is only valid if `type` is set to `LINUX_CONTAINER`. When `type` is set to `LINUX_GPU_CONTAINER`, `compute_type` must be `BUILD_GENERAL1_LARGE`. When `type` is set to `LINUX_LAMBDA_CONTAINER` or `ARM_LAMBDA_CONTAINER`, `compute_type` must be `BUILD_LAMBDA_XGB`.` +* `fleet` - (Optional) Configuration block. Detailed below. * `environment_variable` - (Optional) Configuration block. Detailed below. * `image_pull_credentials_type` - (Optional) Type of credentials AWS CodeBuild uses to pull images in your build. Valid values: `CODEBUILD`, `SERVICE_ROLE`. When you use a cross-account or private registry image, you must use SERVICE_ROLE credentials. When you use an AWS CodeBuild curated image, you must use CodeBuild credentials. Defaults to `CODEBUILD`. * `image` - (Required) Docker image to use for this build project. Valid values include [Docker images provided by CodeBuild](https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html) (e.g `aws/codebuild/amazonlinux2-x86_64-standard:4.0`), [Docker Hub images](https://hub.docker.com/) (e.g., `hashicorp/terraform:latest`), and full Docker repository URIs such as those for ECR (e.g., `137112412989.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:latest`). @@ -296,6 +297,10 @@ The following arguments are optional: * `registry_credential` - (Optional) Configuration block. Detailed below. * `type` - (Required) Type of build environment to use for related builds. Valid values: `LINUX_CONTAINER`, `LINUX_GPU_CONTAINER`, `WINDOWS_CONTAINER` (deprecated), `WINDOWS_SERVER_2019_CONTAINER`, `ARM_CONTAINER`, `LINUX_LAMBDA_CONTAINER`, `ARM_LAMBDA_CONTAINER`. For additional information, see the [CodeBuild User Guide](https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-compute-types.html). +#### environment: fleet + +* `fleet_arn` - (Optional) Compute fleet ARN for the build project. + #### environment: environment_variable * `name` - (Required) Environment variable's name or key.