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 29, 2024
1 parent f9bc03c commit 4d1db91
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 2 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
4 changes: 4 additions & 0 deletions docs/resources/sender_validate.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ resource "mailjet_sender_validate" "sender_validate_example" {
### Required

- `id` (Number) Unique numeric ID for the sender you want to validate.

### Optional

- `wait_for` (String) When specified, the provider will make multiple attempts to validate the resource until the specified duration is reached. One attempt is made per second.
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
42 changes: 40 additions & 2 deletions mailjet/sender_validate_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package mailjet
import (
"context"
"fmt"
"time"

"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"
Expand Down Expand Up @@ -58,12 +60,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 +88,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(durationString)
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 +124,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
55 changes: 55 additions & 0 deletions mailjet/string_timeduration_validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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 {
}

func (validator timeDurationValidator) Description(_ context.Context) string {
return `must be a string representing a duration of at least 1 second`
}

func (validator timeDurationValidator) MarkdownDescription(ctx context.Context) string {
return validator.Description(ctx)
}

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 4d1db91

Please sign in to comment.