diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 56d0f75..6b8bb68 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -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 diff --git a/docs/resources/sender_validate.md b/docs/resources/sender_validate.md index 4785448..7cb8396 100644 --- a/docs/resources/sender_validate.md +++ b/docs/resources/sender_validate.md @@ -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. diff --git a/go.mod b/go.mod index 69390e2..8d50c15 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/mailjet/sender_validate_resource.go b/mailjet/sender_validate_resource.go index ef3606d..d6178b8 100644 --- a/mailjet/sender_validate_resource.go +++ b/mailjet/sender_validate_resource.go @@ -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" @@ -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) { @@ -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) @@ -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( diff --git a/mailjet/string_timeduration_validator.go b/mailjet/string_timeduration_validator.go new file mode 100644 index 0000000..7693807 --- /dev/null +++ b/mailjet/string_timeduration_validator.go @@ -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{} +} diff --git a/mailjet/string_timeduration_validator_test.go b/mailjet/string_timeduration_validator_test.go new file mode 100644 index 0000000..da049e8 --- /dev/null +++ b/mailjet/string_timeduration_validator_test.go @@ -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) + } + }) + } +}