Skip to content

Commit

Permalink
Add retries to pipeline-schedule (#380)
Browse files Browse the repository at this point in the history
* Add retries to pipeline-schedule
  • Loading branch information
lizrabuya authored Aug 30, 2023
1 parent 5eea6ed commit ef09b49
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 34 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,6 @@ tf-proj/terraform.d/*
tf-proj/*.tfstate
tf-proj/*.tfstate.backup
tf-proj/.terraform.lock.hcl

#locally generated test files
junit*.xml
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
- SUP-805: SUP-805: Team resource retries [[PR #360](https://github.com/buildkite/terraform-provider-buildkite/pull/360)] @james2791
- SUP-1392: Random test suite names in tests/t.Run() conversion [[PR #376](https://github.com/buildkite/terraform-provider-buildkite/pull/376)] @james2791
- SUP-1374 Add timeout to provider and cluster datasource [[PR #363](https://github.com/buildkite/terraform-provider-buildkite/pull/363)] @jradtilbrook
- SUP-804: Retry pipeline_schedule api requests[[PR #380](https://github.com/buildkite/terraform-provider-buildkite/pull/380)] @lizrabuya
- SUP-1374 Remove timeout context [[PR #378](https://github.com/buildkite/terraform-provider-buildkite/pull/378)] @jradtilbrook
- SUP-1322: Team member resource retries [[PR #381](https://github.com/buildkite/terraform-provider-buildkite/pull/381)] @james2791
- SUP-1402: Agent token resource retries [[PR #382](https://github.com/buildkite/terraform-provider-buildkite/pull/382)] @james2791
Expand Down
147 changes: 115 additions & 32 deletions buildkite/resource_pipeline_schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bytes"
"context"
"fmt"
"log"
"strings"

"github.com/hashicorp/terraform-plugin-framework/path"
Expand All @@ -15,6 +14,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)

type pipelineSchedule struct {
Expand All @@ -38,7 +38,7 @@ func NewPipelineScheduleResource() resource.Resource {
return &pipelineSchedule{}
}

func (ps *pipelineSchedule) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
func (ps *pipelineSchedule) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_pipeline_schedule"
}

Expand All @@ -49,7 +49,7 @@ func (ps *pipelineSchedule) Configure(ctx context.Context, req resource.Configur
ps.client = req.ProviderData.(*Client)
}

func (ps *pipelineSchedule) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
func (ps *pipelineSchedule) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = resource_schema.Schema{
MarkdownDescription: "A Pipeline Schedule is a schedule that triggers a pipeline to run at a specific time.",
Attributes: map[string]resource_schema.Attribute{
Expand Down Expand Up @@ -115,19 +115,38 @@ func (ps *pipelineSchedule) Create(ctx context.Context, req resource.CreateReque
return
}

log.Printf("Creating Pipeline schedule %s ...", plan.Label.ValueString())
timeouts, diags := ps.client.timeouts.Create(ctx, DefaultTimeout)

resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

envVars := envVarsMapFromTfToString(ctx, plan.Env)
apiResponse, err := createPipelineSchedule(ctx,
ps.client.genqlient,
plan.PipelineId.ValueString(),
plan.Label.ValueStringPointer(),
plan.Cronline.ValueStringPointer(),
plan.Message.ValueStringPointer(),
plan.Commit.ValueStringPointer(),
plan.Branch.ValueStringPointer(),
&envVars,
plan.Enabled.ValueBool())
var apiResponse *createPipelineScheduleResponse

err := retry.RetryContext(ctx, timeouts, func() *retry.RetryError {
var err error

apiResponse, err = createPipelineSchedule(ctx,
ps.client.genqlient,
plan.PipelineId.ValueString(),
plan.Label.ValueStringPointer(),
plan.Cronline.ValueStringPointer(),
plan.Message.ValueStringPointer(),
plan.Commit.ValueStringPointer(),
plan.Branch.ValueStringPointer(),
&envVars,
plan.Enabled.ValueBool())

if err != nil {
if isRetryableError(err) {
return retry.RetryableError(err)
}
return retry.NonRetryableError(err)
}
return nil
})

if err != nil {
resp.Diagnostics.AddError(
Expand Down Expand Up @@ -155,17 +174,36 @@ func (ps *pipelineSchedule) Create(ctx context.Context, req resource.CreateReque
func (ps *pipelineSchedule) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state pipelineScheduleResourceModel

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
diagsState := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diagsState...)

if resp.Diagnostics.HasError() {
return
}

timeouts, diags := ps.client.timeouts.Read(ctx, DefaultTimeout)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

log.Printf("Reading Pipeline schedule %s ...", state.Label.ValueString())
apiResponse, err := getPipelineSchedule(ctx,
ps.client.genqlient,
state.Id.ValueString(),
)
var apiResponse *getPipelineScheduleResponse
err := retry.RetryContext(ctx, timeouts, func() *retry.RetryError {
var err error

apiResponse, err = getPipelineSchedule(ctx,
ps.client.genqlient,
state.Id.ValueString(),
)

if err != nil {
if isRetryableError(err) {
return retry.RetryableError(err)
}
return retry.NonRetryableError(err)
}
return nil
})

if err != nil {
resp.Diagnostics.AddError(
Expand All @@ -184,20 +222,36 @@ func (ps *pipelineSchedule) Read(ctx context.Context, req resource.ReadRequest,
return
}
updatePipelineScheduleNode(ctx, &state, *pipelineScheduleNode)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
} else {
resp.Diagnostics.AddWarning(
"Pipeline schedule not found",
"Removing Pipeline schedule from state...",
)
resp.State.RemoveResource(ctx)
}
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)

}

func (ps *pipelineSchedule) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var state, plan pipelineScheduleResourceModel

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
diagsState := req.State.Get(ctx, &state)
diagsPlan := req.Plan.Get(ctx, &plan)

resp.Diagnostics.Append(diagsState...)
resp.Diagnostics.Append(diagsPlan...)

if resp.Diagnostics.HasError() {
return
}

timeouts, diags := ps.client.timeouts.Update(ctx, DefaultTimeout)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

envVars := envVarsMapFromTfToString(ctx, plan.Env)
input := PipelineScheduleUpdateInput{
Id: state.Id.ValueString(),
Expand All @@ -210,18 +264,28 @@ func (ps *pipelineSchedule) Update(ctx context.Context, req resource.UpdateReque
Enabled: plan.Enabled.ValueBool(),
}

log.Printf("Updating Pipeline schedule %s ...", plan.Label.ValueString())
_, err := updatePipelineSchedule(ctx,
ps.client.genqlient,
input,
)
err := retry.RetryContext(ctx, timeouts, func() *retry.RetryError {
var err error
_, err = updatePipelineSchedule(ctx,
ps.client.genqlient,
input,
)

if err != nil {
if isRetryableError(err) {
return retry.RetryableError(err)
}
return retry.NonRetryableError(err)
}

return nil
})

if err != nil {
resp.Diagnostics.AddError(
"Unable to update Pipeline schedule",
fmt.Sprintf("Unable to update Pipeline schedule: %s", err.Error()),
)
return
}

plan.Id = state.Id
Expand All @@ -230,14 +294,33 @@ func (ps *pipelineSchedule) Update(ctx context.Context, req resource.UpdateReque

func (ps *pipelineSchedule) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var plan pipelineScheduleResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &plan)...)

diagsPlan := req.State.Get(ctx, &plan)
resp.Diagnostics.Append(diagsPlan...)

if resp.Diagnostics.HasError() {
return
}

log.Println("Deleting Pipeline schedule ...")
_, err := deletePipelineSchedule(ctx, ps.client.genqlient, plan.Id.ValueString())
timeout, diags := ps.client.timeouts.Delete(ctx, DefaultTimeout)
resp.Diagnostics.Append(diags...)

if resp.Diagnostics.HasError() {
return
}

err := retry.RetryContext(ctx, timeout, func() *retry.RetryError {
_, err := deletePipelineSchedule(ctx, ps.client.genqlient, plan.Id.ValueString())

if err != nil {
if isRetryableError(err) {
return retry.RetryableError(err)
}
return retry.NonRetryableError(err)
}
return nil
})

if err != nil {
resp.Diagnostics.AddError(
"Unable to delete Pipeline schedule",
Expand Down
38 changes: 38 additions & 0 deletions buildkite/resource_pipeline_schedule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,22 @@ import (

"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/plancheck"
"github.com/hashicorp/terraform-plugin-testing/terraform"
)

func TestAccBuildkitePipelineSchedule(t *testing.T) {
config := func(name, cronline, label string) string {
return fmt.Sprintf(`
provider "buildkite" {
timeouts {
create = "10s"
read = "10s"
update = "10s"
delete = "10s"
}
}
resource "buildkite_pipeline" "pipeline" {
name = "%s"
repository = "https://github.com/buildkite/terraform-provider-buildkite.git"
Expand Down Expand Up @@ -168,4 +178,32 @@ func TestAccBuildkitePipelineSchedule(t *testing.T) {
},
})
})

t.Run("pipeline schedule is recreated if removed", func(t *testing.T) {
pipelineName := acctest.RandString(12)
label := acctest.RandString(12)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: protoV6ProviderFactories(),
Steps: []resource.TestStep{
{
Config: config(pipelineName, "0 * * * *", label),
Check: func(s *terraform.State) error {
ps := s.RootModule().Resources["buildkite_pipeline_schedule.pipeline"]
_, err := deletePipelineSchedule(context.Background(),
genqlientGraphql,
ps.Primary.ID)
return err
},
ExpectNonEmptyPlan: true,
ConfigPlanChecks: resource.ConfigPlanChecks{
PostApplyPostRefresh: []plancheck.PlanCheck{
plancheck.ExpectResourceAction("buildkite_pipeline_schedule.pipeline", plancheck.ResourceActionCreate),
},
},
},
},
})
})
}
2 changes: 1 addition & 1 deletion docs/resources/pipeline_schedule.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ resource "buildkite_pipeline_schedule" "repo2_nightly" {
pipeline_id = buildkite_pipeline.repo2.id
label = "Nightly build"
cronline = "@midnight"
branch = buildkite_pipeline.repo2.default_branch
branch = buildkite_pipeline.repo2.default_branch
}
```

Expand Down
2 changes: 1 addition & 1 deletion examples/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ resource "buildkite_pipeline_schedule" "weekly" {
label = "Weekly build from default branch"
cronline = "@midnight"
branch = "default"
message = "Weekly scheduled build"
message = "Weekly scheduled build"
}

resource "buildkite_pipeline_team" "developers" {
Expand Down

0 comments on commit ef09b49

Please sign in to comment.