Skip to content

Commit

Permalink
feat: Add a wait_for attribute to the mailjet_sender_validate res…
Browse files Browse the repository at this point in the history
…ource

The validation does not always succeed on the first try even when
all the resources exists. This attribute adds the possibility to let
the provider retry multiple times before failing.
  • Loading branch information
LeSuisse committed Apr 22, 2024
1 parent f9bc03c commit f62b83c
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 3 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ jobs:
- uses: cachix/install-nix-action@7ac1ec25491415c381d9b62f0657c7a028df52a7
- run: nix-shell --run 'go build'
- run: ./terraform-provider-mailjet --help
unit_tests:
runs-on: ubuntu-22.04
name: Run unit tests
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- uses: cachix/install-nix-action@7ac1ec25491415c381d9b62f0657c7a028df52a7
- run: nix-shell --run 'go test -v ./mailjet'
static_analysis:
runs-on: ubuntu-22.04
name: Run static analysis and linting
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/enalean/terraform-provider-mailjet
go 1.22

require (
github.com/google/go-cmp v0.6.0
github.com/hashicorp/terraform-plugin-framework v1.8.0
github.com/mailjet/mailjet-apiv3-go/v3 v3.2.0
github.com/mailjet/mailjet-apiv3-go/v4 v4.0.1
Expand Down
43 changes: 40 additions & 3 deletions mailjet/sender_validate_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package mailjet
import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/mailjet/mailjet-apiv3-go/v3/resources"
"github.com/mailjet/mailjet-apiv3-go/v4"
"time"
)

var (
Expand Down Expand Up @@ -58,12 +59,20 @@ func (r *senderValidateResource) Schema(_ context.Context, _ resource.SchemaRequ
int64planmodifier.RequiresReplace(),
},
},
"wait_for": schema.StringAttribute{
Optional: true,
Description: "When specified, the provider will make multiple attempts to validate the resource until the specified duration is reached. One attempt is made per second.",
Validators: []validator.String{
TimeDurationAtLeast1Sec(),
},
},
},
}
}

type senderValidateResourceModel struct {
ID types.Int64 `tfsdk:"id"`
ID types.Int64 `tfsdk:"id"`
WaitFor types.String `tfsdk:"wait_for"`
}

func (r *senderValidateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
Expand All @@ -78,8 +87,23 @@ func (r *senderValidateResource) Create(ctx context.Context, req resource.Create
Resource: "sender",
ID: state.ID.ValueInt64(),
}

durationString := state.WaitFor.ValueString()
if durationString == "" {
durationString = "0s"
}

waitForDuration, err := time.ParseDuration(state.WaitFor.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Failed to parse wait_for",
err.Error(),
)
return
}

var responseDataSearch []resources.Sender
err := r.client.Get(senderSearchRequest, &responseDataSearch)
err = r.client.Get(senderSearchRequest, &responseDataSearch)

if err == nil && len(responseDataSearch) == 1 && responseDataSearch[0].Status == "Active" {
diags := resp.State.Set(ctx, state)
Expand All @@ -99,6 +123,19 @@ func (r *senderValidateResource) Create(ctx context.Context, req resource.Create

var responseDataValidation []resources.SenderValidate

startAttempt := time.Now()
for {
err = r.client.Post(mailjetValidateFullRequest, responseDataValidation)

if err == nil && len(responseDataValidation) == 1 && responseDataValidation[0].GlobalError == "" {
return
}
if time.Since(startAttempt) > waitForDuration {
break
}
time.Sleep(time.Second)
}

err = r.client.Post(mailjetValidateFullRequest, responseDataValidation)
if err != nil {
resp.Diagnostics.AddError(
Expand Down
58 changes: 58 additions & 0 deletions mailjet/string_timeduration_validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package mailjet

import (
"context"
"fmt"
"time"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)

var _ validator.String = timeDurationValidator{}

type timeDurationValidator struct {
}

// Description describes the validation in plain text formatting.
func (validator timeDurationValidator) Description(_ context.Context) string {
return `must be a string representing a duration of at least 1 second`
}

// MarkdownDescription describes the validation in Markdown formatting.
func (validator timeDurationValidator) MarkdownDescription(ctx context.Context) string {
return validator.Description(ctx)
}

// ValidateString performs the validation.
func (validator timeDurationValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
s := req.ConfigValue

if s.IsUnknown() || s.IsNull() {
return
}

duration, err := time.ParseDuration(s.ValueString())

if err != nil {
resp.Diagnostics.Append(diag.NewAttributeErrorDiagnostic(
req.Path,
"failed to parse the time duration",
fmt.Sprintf("%q %s", s.ValueString(), validator.Description(ctx))),
)
return
}

if duration < time.Second {
resp.Diagnostics.Append(diag.NewAttributeErrorDiagnostic(
req.Path,
"the time duration must be at least 1 second",
fmt.Sprintf("%q %s", s.ValueString(), validator.Description(ctx))),
)
return
}
}

func TimeDurationAtLeast1Sec() validator.String {
return timeDurationValidator{}
}
73 changes: 73 additions & 0 deletions mailjet/string_timeduration_validator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package mailjet

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)

func TestTimeDuration(t *testing.T) {
t.Parallel()

type testCase struct {
val types.String
expectedDiagnostics diag.Diagnostics
}

tests := map[string]testCase{
"unknown": {
val: types.StringUnknown(),
},
"null": {
val: types.StringNull(),
},
"valid": {
val: types.StringValue("30s"),
},
"invalid": {
val: types.StringValue("30wrong"),
expectedDiagnostics: diag.Diagnostics{
diag.NewAttributeErrorDiagnostic(
path.Root("test"),
"failed to parse the time duration",
`"30wrong" must be a string representing a duration of at least 1 second`,
),
},
},
"invalid_too_small": {
val: types.StringValue("0s"),
expectedDiagnostics: diag.Diagnostics{
diag.NewAttributeErrorDiagnostic(
path.Root("test"),
"the time duration must be at least 1 second",
`"0s" must be a string representing a duration of at least 1 second`,
),
},
},
}

for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
t.Parallel()
request := validator.StringRequest{
Path: path.Root("test"),
PathExpression: path.MatchRoot("test"),
ConfigValue: test.val,
}

response := validator.StringResponse{}

TimeDurationAtLeast1Sec().ValidateString(context.Background(), request, &response)

if diff := cmp.Diff(response.Diagnostics, test.expectedDiagnostics); diff != "" {
t.Errorf("unexpected diagnostics difference: %s", diff)
}
})
}
}

0 comments on commit f62b83c

Please sign in to comment.