Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/44444.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-action
aws_codebuild_start_build
```
18 changes: 9 additions & 9 deletions .github/workflows/semgrep-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
name: Validate Code Quality Rules
runs-on: ubuntu-latest
container:
image: "returntocorp/semgrep:1.52.0"
image: "semgrep/semgrep:1.110.0"
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- run: |
Expand All @@ -43,7 +43,7 @@ jobs:
needs: [semgrep-validate]
runs-on: ubuntu-latest
container:
image: "returntocorp/semgrep:1.52.0"
image: "semgrep/semgrep:1.110.0"
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- run: |
Expand All @@ -54,7 +54,7 @@ jobs:
needs: [semgrep-test]
runs-on: ubuntu-latest
container:
image: "returntocorp/semgrep:1.52.0"
image: "semgrep/semgrep:1.110.0"
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- run: |
Expand All @@ -74,7 +74,7 @@ jobs:
name: Naming Scan Caps/AWS/EC2
runs-on: ubuntu-latest
container:
image: "returntocorp/semgrep:1.52.0"
image: "semgrep/semgrep:1.110.0"
if: (github.action != 'dependabot[bot]')
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
Expand All @@ -85,7 +85,7 @@ jobs:
name: Test Configs Scan
runs-on: ubuntu-latest
container:
image: "returntocorp/semgrep:1.52.0"
image: "semgrep/semgrep:1.110.0"
if: (github.action != 'dependabot[bot]')
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
Expand All @@ -96,7 +96,7 @@ jobs:
name: Service Name Scan A-C
runs-on: ubuntu-latest
container:
image: "returntocorp/semgrep:1.52.0"
image: "semgrep/semgrep:1.110.0"
if: (github.action != 'dependabot[bot]')
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
Expand All @@ -107,7 +107,7 @@ jobs:
name: Service Name Scan C-I
runs-on: ubuntu-latest
container:
image: "returntocorp/semgrep:1.52.0"
image: "semgrep/semgrep:1.110.0"
if: (github.action != 'dependabot[bot]')
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
Expand All @@ -118,7 +118,7 @@ jobs:
name: Service Name Scan I-Q
runs-on: ubuntu-latest
container:
image: "returntocorp/semgrep:1.52.0"
image: "semgrep/semgrep:1.110.0"
if: (github.action != 'dependabot[bot]')
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
Expand All @@ -129,7 +129,7 @@ jobs:
name: Service Name Scan Q-Z
runs-on: ubuntu-latest
container:
image: "returntocorp/semgrep:1.52.0"
image: "semgrep/semgrep:1.110.0"
if: (github.action != 'dependabot[bot]')
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
Expand Down
1 change: 1 addition & 0 deletions GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ provider-lint: ## [CI] ProviderLint Checks / providerlint

quick-fix-heading: ## Just a heading for quick-fix
@echo "make: Quick fixes..."
@echo "make: Multiple runs are needed if it finds errors (later targets not reached)"

quick-fix: quick-fix-heading fmt testacc-lint-fix fix-imports modern-fix semgrep-fix website-terrafmt-fix ## Some quick fixes

Expand Down
11 changes: 11 additions & 0 deletions internal/service/codebuild/service_package_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

195 changes: 195 additions & 0 deletions internal/service/codebuild/start_build_action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// 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/aws/aws-sdk-go-v2/service/codebuild"
awstypes "github.com/aws/aws-sdk-go-v2/service/codebuild/types"
"github.com/hashicorp/terraform-plugin-framework/action"
"github.com/hashicorp/terraform-plugin-framework/action/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-provider-aws/internal/framework"
fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex"
fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types"
"github.com/hashicorp/terraform-provider-aws/names"
)

// @Action(aws_codebuild_start_build, name="CodeBuild Start Build")
func newStartBuildAction(context.Context) (action.ActionWithConfigure, error) {
return &startBuildAction{}, nil
}

type startBuildAction struct {
framework.ActionWithModel[startBuildActionModel]
}

type startBuildActionModel struct {
framework.WithRegionModel
ProjectName types.String `tfsdk:"project_name"`
SourceVersion types.String `tfsdk:"source_version"`
Timeout types.Int64 `tfsdk:"timeout"`
EnvironmentVariablesOverride fwtypes.ListNestedObjectValueOf[environmentVariableModel] `tfsdk:"environment_variables_override"`
BuildID types.String `tfsdk:"build_id"`
}

type environmentVariableModel struct {
Name types.String `tfsdk:"name"`
Value types.String `tfsdk:"value"`
Type types.String `tfsdk:"type"`
}

func (a *startBuildAction) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Starts a CodeBuild project build",
Attributes: map[string]schema.Attribute{
"project_name": schema.StringAttribute{
Description: "Name of the CodeBuild project",
Required: true,
},
"source_version": schema.StringAttribute{
Description: "Version of the build input to be built",
Optional: true,
},
names.AttrTimeout: schema.Int64Attribute{
Description: "Timeout in seconds for the build operation",
Optional: true,
},
"build_id": schema.StringAttribute{
Description: "ID of the started build",
Optional: true,
},
},
Blocks: map[string]schema.Block{
"environment_variables_override": schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[environmentVariableModel](ctx),
Description: "Environment variables to override for this build",
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
names.AttrName: schema.StringAttribute{
Description: "Environment variable name",
Required: true,
},
names.AttrValue: schema.StringAttribute{
Description: "Environment variable value",
Required: true,
},
names.AttrType: schema.StringAttribute{
Description: "Environment variable type",
Optional: true,
},
},
},
},
},
}
}

func (a *startBuildAction) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {
var model startBuildActionModel
resp.Diagnostics.Append(req.Config.Get(ctx, &model)...)
if resp.Diagnostics.HasError() {
return
}

conn := a.Meta().CodeBuildClient(ctx)

timeout := 30 * time.Minute
if !model.Timeout.IsNull() {
timeout = time.Duration(model.Timeout.ValueInt64()) * time.Second
}

tflog.Info(ctx, "Starting CodeBuild project build", map[string]any{
"project_name": model.ProjectName.ValueString(),
})

resp.SendProgress(action.InvokeProgressEvent{
Message: "Starting CodeBuild project build...",
})

var input codebuild.StartBuildInput
resp.Diagnostics.Append(fwflex.Expand(ctx, model, &input)...)
if resp.Diagnostics.HasError() {
return
}

output, err := conn.StartBuild(ctx, &input)
if err != nil {
resp.Diagnostics.AddError("Starting CodeBuild project build", err.Error())
return
}

buildID := aws.ToString(output.Build.Id)
model.BuildID = types.StringValue(buildID)

resp.SendProgress(action.InvokeProgressEvent{
Message: "Build started, waiting for completion...",
})

// Poll for build completion
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for our future selves -- we should pull this poll/send progress logic into something generic like we have with retries.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed!

deadline := time.Now().Add(timeout)
pollInterval := 30 * time.Second
progressInterval := 2 * time.Minute
lastProgressUpdate := time.Now()

for {
select {
case <-ctx.Done():
resp.Diagnostics.AddError("Build monitoring cancelled", "Context was cancelled")
return
default:
}

if time.Now().After(deadline) {
resp.Diagnostics.AddError("Build timeout", "Build did not complete within the specified timeout")
return
}

input := codebuild.BatchGetBuildsInput{
Ids: []string{buildID},
}
batchGetBuildsOutput, err := conn.BatchGetBuilds(ctx, &input)
if err != nil {
resp.Diagnostics.AddError("Getting build status", err.Error())
return
}

if len(batchGetBuildsOutput.Builds) == 0 {
resp.Diagnostics.AddError("Build not found", "Build was not found in BatchGetBuilds response")
return
}

build := batchGetBuildsOutput.Builds[0]
status := build.BuildStatus

if time.Since(lastProgressUpdate) >= progressInterval {
resp.SendProgress(action.InvokeProgressEvent{
Message: "Build currently in state: " + string(status),
})
lastProgressUpdate = time.Now()
}

switch status {
case awstypes.StatusTypeSucceeded:
resp.SendProgress(action.InvokeProgressEvent{
Message: "Build completed successfully",
})
return
case awstypes.StatusTypeFailed, awstypes.StatusTypeFault, awstypes.StatusTypeStopped, awstypes.StatusTypeTimedOut:
resp.Diagnostics.AddError("Build failed", "Build completed with status: "+string(status))
return
case awstypes.StatusTypeInProgress:
// Continue polling
default:
resp.Diagnostics.AddError("Unexpected build status", "Received unexpected build status: "+string(status))
return
}

time.Sleep(pollInterval)
}
}
Loading
Loading