diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 2b421875..9accf7f3 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -82,7 +82,7 @@ jobs:
run: |
go test -count=1 -parallel=4 -timeout 10m -json -v ./... 2>&1 | tee TestResults-${{ matrix.os }}_${{ matrix.go-version }}.log
- name: Upload test log
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v4
if: always()
with:
name: TestResults-${{ matrix.os }}_${{ matrix.go-version }}.log
diff --git a/docs/resources/dw_aws_cluster.md b/docs/resources/dw_aws_cluster.md
index 37dec949..719740f5 100644
--- a/docs/resources/dw_aws_cluster.md
+++ b/docs/resources/dw_aws_cluster.md
@@ -79,6 +79,7 @@ output "name" {
- `database_backup_retention_days` (Number) The number of days to retain database backups.
- `instance_settings` (Attributes) (see [below for nested schema](#nestedatt--instance_settings))
- `node_role_cdw_managed_policy_arn` (String) The managed policy ARN to be attached to the created node instance role.
+- `polling_options` (Attributes) Polling related configuration options that could specify various values that will be used during CDP resource creation. (see [below for nested schema](#nestedatt--polling_options))
### Read-Only
@@ -86,6 +87,7 @@ output "name" {
- `id` (String) The ID of this resource.
- `last_updated` (String) Timestamp of the last Terraform update of the order.
- `name` (String) The name of the cluster matches the environment name.
+- `status` (String) The status of the cluster.
### Nested Schema for `network_settings`
@@ -124,3 +126,13 @@ Optional:
- `enable_spot_instances` (Boolean) Whether to use spot instances for worker nodes.
+
+### Nested Schema for `polling_options`
+
+Optional:
+
+- `async` (Boolean) Boolean value that specifies if Terraform should wait for resource creation/deletion.
+- `call_failure_threshold` (Number) Threshold value that specifies how many times should a single call failure happen before giving up the polling.
+- `polling_timeout` (Number) Timeout value in minutes that specifies for how long should the polling go for resource creation/deletion.
+
+
diff --git a/docs/resources/environments_user_sync.md b/docs/resources/environments_user_sync.md
index 367b5077..a6e7540e 100644
--- a/docs/resources/environments_user_sync.md
+++ b/docs/resources/environments_user_sync.md
@@ -33,6 +33,9 @@ This approach allows a fine-grain control of the sync operation.
resource "cdp_environments_user_sync" "example-user_sync" {
environment_names = ["example-cdp-environment-1", "example-cdp-environment-2"]
+ polling_options = {
+ async = true
+ }
}
```
@@ -42,7 +45,17 @@ resource "cdp_environments_user_sync" "example-user_sync" {
### Optional
- `environment_names` (Set of String) List of environments to be synced. If not present, all environments will be synced.
+- `polling_options` (Attributes) Polling related configuration options that could specify various values that will be used during CDP resource creation. (see [below for nested schema](#nestedatt--polling_options))
### Read-Only
-- `id` (String) The ID of this resource.
\ No newline at end of file
+- `id` (String) The ID of this resource.
+
+
+### Nested Schema for `polling_options`
+
+Optional:
+
+- `async` (Boolean) Boolean value that specifies if Terraform should wait for resource creation/deletion.
+- `call_failure_threshold` (Number) Threshold value that specifies how many times should a single call failure happen before giving up the polling.
+- `polling_timeout` (Number) Timeout value in minutes that specifies for how long should the polling go for resource creation/deletion.
\ No newline at end of file
diff --git a/examples/resources/cdp_environments_user_sync/resource.tf b/examples/resources/cdp_environments_user_sync/resource.tf
index 54b749b6..795f184f 100644
--- a/examples/resources/cdp_environments_user_sync/resource.tf
+++ b/examples/resources/cdp_environments_user_sync/resource.tf
@@ -10,4 +10,7 @@
resource "cdp_environments_user_sync" "example-user_sync" {
environment_names = ["example-cdp-environment-1", "example-cdp-environment-2"]
+ polling_options = {
+ async = true
+ }
}
diff --git a/resources/environments/resource_user_sync.go b/resources/environments/resource_user_sync.go
index 30866535..db5538cc 100644
--- a/resources/environments/resource_user_sync.go
+++ b/resources/environments/resource_user_sync.go
@@ -12,8 +12,11 @@ package environments
import (
"context"
+ "fmt"
+ "time"
"github.com/cloudera/terraform-provider-cdp/cdp-sdk-go/cdp"
+ "github.com/cloudera/terraform-provider-cdp/cdp-sdk-go/gen/environments/client"
"github.com/cloudera/terraform-provider-cdp/cdp-sdk-go/gen/environments/client/operations"
environmentsmodels "github.com/cloudera/terraform-provider-cdp/cdp-sdk-go/gen/environments/models"
"github.com/cloudera/terraform-provider-cdp/utils"
@@ -21,10 +24,14 @@ import (
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
)
var (
@@ -45,6 +52,33 @@ var userSyncSchema = schema.Schema{
ElementType: types.StringType,
Optional: true,
},
+ "polling_options": schema.SingleNestedAttribute{
+ MarkdownDescription: "Polling related configuration options that could specify various values that will be used during CDP resource creation.",
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "async": schema.BoolAttribute{
+ MarkdownDescription: "Boolean value that specifies if Terraform should wait for resource creation/deletion.",
+ Optional: true,
+ Computed: true,
+ Default: booldefault.StaticBool(false),
+ PlanModifiers: []planmodifier.Bool{
+ boolplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "polling_timeout": schema.Int64Attribute{
+ MarkdownDescription: "Timeout value in minutes that specifies for how long should the polling go for resource creation/deletion.",
+ Default: int64default.StaticInt64(90),
+ Computed: true,
+ Optional: true,
+ },
+ "call_failure_threshold": schema.Int64Attribute{
+ MarkdownDescription: "Threshold value that specifies how many times should a single call failure happen before giving up the polling.",
+ Default: int64default.StaticInt64(3),
+ Computed: true,
+ Optional: true,
+ },
+ },
+ },
},
}
@@ -61,9 +95,9 @@ func (r *userSyncResource) Metadata(ctx context.Context, req resource.MetadataRe
}
type userSyncResourceModel struct {
- ID types.String `tfsdk:"id"`
-
- EnvironmentNames types.Set `tfsdk:"environment_names"`
+ ID types.String `tfsdk:"id"`
+ EnvironmentNames types.Set `tfsdk:"environment_names"`
+ PollingOptions *utils.PollingOptions `tfsdk:"polling_options"`
}
func toSyncAllUsersRequest(ctx context.Context, model *userSyncResourceModel, diag *diag.Diagnostics) *environmentsmodels.SyncAllUsersRequest {
@@ -93,7 +127,7 @@ func (r *userSyncResource) Create(ctx context.Context, req resource.CreateReques
params := operations.NewSyncAllUsersParamsWithContext(ctx)
params.WithInput(toSyncAllUsersRequest(ctx, &state, &resp.Diagnostics))
- _, err := client.Operations.SyncAllUsers(params)
+ res, err := client.Operations.SyncAllUsers(params)
if err != nil {
if isSyncAllUsersNotFoundError(err) {
resp.Diagnostics.AddError(
@@ -113,6 +147,74 @@ func (r *userSyncResource) Create(ctx context.Context, req resource.CreateReques
if resp.Diagnostics.HasError() {
return
}
+
+ opID := res.Payload.OperationID
+ tflog.Debug(ctx, fmt.Sprintf("User sync operation ID: %s", *opID))
+ if !(state.PollingOptions != nil && state.PollingOptions.Async.ValueBool()) {
+ tflog.Debug(ctx, "User sync polling starts")
+ err = waitForUserSync(*opID, time.Hour*1, callFailureThreshold, r.client.Environments, ctx, state.PollingOptions)
+ if err != nil {
+ return
+ }
+ }
+}
+
+func waitForUserSync(opID string, fallbackTimeout time.Duration, callFailureThresholdDefault int, client *client.Environments, ctx context.Context, pollingOptions *utils.PollingOptions) error {
+ timeout, err := utils.CalculateTimeoutOrDefault(ctx, pollingOptions, fallbackTimeout)
+ if err != nil {
+ return err
+ }
+ callFailureThreshold, failureThresholdError := utils.CalculateCallFailureThresholdOrDefault(ctx, pollingOptions, callFailureThresholdDefault)
+ if failureThresholdError != nil {
+ return failureThresholdError
+ }
+ callFailedCount := 0
+ stateConf := &retry.StateChangeConf{
+ Pending: []string{"NEVER_RUN",
+ "REQUESTED",
+ "REJECTED",
+ "RUNNING",
+ "COMPLETED",
+ "FAILED",
+ "TIMEDOUT"},
+ Target: []string{"COMPLETED"},
+ Delay: 5 * time.Second,
+ Timeout: *timeout,
+ PollInterval: 10 * time.Second,
+ Refresh: func() (interface{}, string, error) {
+ tflog.Debug(ctx, fmt.Sprintf("About to get sync status for operationID %s", opID))
+ params := operations.NewSyncStatusParamsWithContext(ctx)
+ params.WithInput(&environmentsmodels.SyncStatusRequest{OperationID: &opID})
+ resp, err := client.Operations.SyncStatus(params)
+ if err != nil {
+ if isEnvNotFoundError(err) {
+ tflog.Debug(ctx, fmt.Sprintf("Recoverable error getting user sync status: %s", err))
+ callFailedCount = 0
+ return nil, "", nil
+ }
+ callFailedCount++
+ if callFailedCount <= callFailureThreshold {
+ tflog.Warn(ctx, fmt.Sprintf("Error getting user sync status with call failure due to [%s] but threshold limit is not reached yet (%d out of %d).", err.Error(), callFailedCount, callFailureThreshold))
+ return nil, "", nil
+ }
+ tflog.Error(ctx, fmt.Sprintf("Error getting user sync status (due to: %s) and call failure threshold limit exceeded.", err))
+ return nil, "", err
+ }
+ callFailedCount = 0
+ tflog.Info(ctx, fmt.Sprintf("User sync status: %s", resp.GetPayload().Status))
+ return checkUserSyncResponseStatusForError(resp)
+ },
+ }
+ _, err = stateConf.WaitForStateContext(ctx)
+
+ return err
+}
+
+func checkUserSyncResponseStatusForError(resp *operations.SyncStatusOK) (interface{}, string, error) {
+ if utils.ContainsAsSubstring([]string{"FAILED", "ERROR"}, string(resp.GetPayload().Status)) {
+ return nil, "", fmt.Errorf("unexpected user sync status status: %s. ", resp.GetPayload().Status)
+ }
+ return resp, string(resp.GetPayload().Status), nil
}
func isSyncAllUsersNotFoundError(err error) bool {