From c874ebd4155c7c899925dd9b4dff9f2c57633e61 Mon Sep 17 00:00:00 2001 From: tpanetti Date: Mon, 1 Jun 2026 16:18:56 -0700 Subject: [PATCH 1/3] fix(clickpipes): change excluded columns type from list to set to avoid ordering issue --- docs/resources/clickpipe.md | 6 +- pkg/resource/clickpipe.go | 39 +++---- pkg/resource/clickpipe_bug_fixes_test.go | 123 +++++++++++++++++++++- pkg/resource/clickpipe_test.go | 2 +- pkg/resource/models/clickpipe_resource.go | 12 +-- 5 files changed, 151 insertions(+), 31 deletions(-) diff --git a/docs/resources/clickpipe.md b/docs/resources/clickpipe.md index 4db01e56..4f247b7b 100644 --- a/docs/resources/clickpipe.md +++ b/docs/resources/clickpipe.md @@ -209,7 +209,7 @@ Required: Optional: -- `excluded_columns` (List of String) Columns to exclude from replication. +- `excluded_columns` (Set of String) Columns to exclude from replication. - `sorting_keys` (List of String) Ordered list of columns to use as sorting key for the target table. Required when use_custom_sorting_key is true. - `table_engine` (String) Table engine to use for the target table. (`MergeTree`, `ReplacingMergeTree`, `Null`) - `use_custom_sorting_key` (Boolean) Whether to use a custom sorting key for the target table. @@ -447,7 +447,7 @@ Required: Optional: -- `excluded_columns` (List of String) Columns to exclude from replication. +- `excluded_columns` (Set of String) Columns to exclude from replication. - `partition_key` (String) Custom partitioning column used for parallel snapshotting. Must be an indexed column of integer, date, datetime, or timestamp type. - `sorting_keys` (List of String) Ordered list of columns to use as sorting key for the target table. Required when use_custom_sorting_key is true. - `table_engine` (String) Table engine to use for the target table. (`MergeTree`, `ReplacingMergeTree`, `Null`) @@ -554,7 +554,7 @@ Required: Optional: -- `excluded_columns` (List of String) Columns to exclude from replication. +- `excluded_columns` (Set of String) Columns to exclude from replication. - `partition_key` (String) Custom partitioning column used for parallel snapshotting. Only beneficial for PostgreSQL 13 (no benefit for PG14+, which supports indexed ctid scans). Must be an indexed column of type: `smallint`, `integer`, `bigint`, `timestamp without time zone`, or `timestamp with time zone`. Unrelated to ClickHouse partitioning. - `sorting_keys` (List of String) Ordered list of columns to use as sorting key for the target table. Required when use_custom_sorting_key is true. - `table_engine` (String) Table engine to use for the target table. (`MergeTree`, `ReplacingMergeTree`, `Null`) diff --git a/pkg/resource/clickpipe.go b/pkg/resource/clickpipe.go index 37969b1c..9f1438db 100644 --- a/pkg/resource/clickpipe.go +++ b/pkg/resource/clickpipe.go @@ -24,6 +24,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -1041,7 +1042,7 @@ func (c *ClickPipeResource) Schema(_ context.Context, _ resource.SchemaRequest, Description: "Target table name in ClickHouse.", Required: true, }, - "excluded_columns": schema.ListAttribute{ + "excluded_columns": schema.SetAttribute{ Description: "Columns to exclude from replication.", Optional: true, ElementType: types.StringType, @@ -1321,7 +1322,7 @@ func (c *ClickPipeResource) Schema(_ context.Context, _ resource.SchemaRequest, Description: "Target table name in ClickHouse.", Required: true, }, - "excluded_columns": schema.ListAttribute{ + "excluded_columns": schema.SetAttribute{ Description: "Columns to exclude from replication.", Optional: true, ElementType: types.StringType, @@ -1460,11 +1461,11 @@ func (c *ClickPipeResource) Schema(_ context.Context, _ resource.SchemaRequest, Description: "Target table name in ClickHouse.", Required: true, }, - "excluded_columns": schema.ListAttribute{ + "excluded_columns": schema.SetAttribute{ Description: "Columns to exclude from replication.", Optional: true, Computed: true, - Default: listdefault.StaticValue(types.ListNull(types.StringType)), + Default: setdefault.StaticValue(types.SetNull(types.StringType)), ElementType: types.StringType, }, "use_custom_sorting_key": schema.BoolAttribute{ @@ -4153,16 +4154,16 @@ func (c *ClickPipeResource) syncClickPipeState(ctx context.Context, state *model } if len(mapping.ExcludedColumns) > 0 { - excludedColsList := make([]attr.Value, len(mapping.ExcludedColumns)) + excludedCols := make([]attr.Value, len(mapping.ExcludedColumns)) for j, col := range mapping.ExcludedColumns { - excludedColsList[j] = types.StringValue(col) + excludedCols[j] = types.StringValue(col) } - tableMappingModel.ExcludedColumns, _ = types.ListValue(types.StringType, excludedColsList) + tableMappingModel.ExcludedColumns, _ = types.SetValue(types.StringType, excludedCols) } else if hasStateMapping && !stateMapping.ExcludedColumns.IsNull() { - // Preserve empty list from state (vs null) to avoid plan diff - tableMappingModel.ExcludedColumns, _ = types.ListValue(types.StringType, []attr.Value{}) + // Preserve empty set from state (vs null) to avoid plan diff + tableMappingModel.ExcludedColumns, _ = types.SetValue(types.StringType, []attr.Value{}) } else { - tableMappingModel.ExcludedColumns = types.ListNull(types.StringType) + tableMappingModel.ExcludedColumns = types.SetNull(types.StringType) } if mapping.UseCustomSortingKey != nil { @@ -4377,15 +4378,15 @@ func (c *ClickPipeResource) syncClickPipeState(ctx context.Context, state *model } if len(mapping.ExcludedColumns) > 0 { - excludedColsList := make([]attr.Value, len(mapping.ExcludedColumns)) + excludedCols := make([]attr.Value, len(mapping.ExcludedColumns)) for j, col := range mapping.ExcludedColumns { - excludedColsList[j] = types.StringValue(col) + excludedCols[j] = types.StringValue(col) } - tableMappingModel.ExcludedColumns, _ = types.ListValue(types.StringType, excludedColsList) + tableMappingModel.ExcludedColumns, _ = types.SetValue(types.StringType, excludedCols) } else if hasStateMapping && !stateMapping.ExcludedColumns.IsNull() { - tableMappingModel.ExcludedColumns, _ = types.ListValue(types.StringType, []attr.Value{}) + tableMappingModel.ExcludedColumns, _ = types.SetValue(types.StringType, []attr.Value{}) } else { - tableMappingModel.ExcludedColumns = types.ListNull(types.StringType) + tableMappingModel.ExcludedColumns = types.SetNull(types.StringType) } if mapping.UseCustomSortingKey != nil { @@ -4713,13 +4714,13 @@ func (c *ClickPipeResource) syncClickPipeState(ctx context.Context, state *model } if len(mapping.ExcludedColumns) > 0 { - excludedColsList := make([]attr.Value, len(mapping.ExcludedColumns)) + excludedCols := make([]attr.Value, len(mapping.ExcludedColumns)) for j, col := range mapping.ExcludedColumns { - excludedColsList[j] = types.StringValue(col) + excludedCols[j] = types.StringValue(col) } - tableMappingModel.ExcludedColumns, _ = types.ListValue(types.StringType, excludedColsList) + tableMappingModel.ExcludedColumns, _ = types.SetValue(types.StringType, excludedCols) } else { - tableMappingModel.ExcludedColumns = types.ListNull(types.StringType) + tableMappingModel.ExcludedColumns = types.SetNull(types.StringType) } if mapping.UseCustomSortingKey != nil { diff --git a/pkg/resource/clickpipe_bug_fixes_test.go b/pkg/resource/clickpipe_bug_fixes_test.go index 49c31be6..54004285 100644 --- a/pkg/resource/clickpipe_bug_fixes_test.go +++ b/pkg/resource/clickpipe_bug_fixes_test.go @@ -2,11 +2,14 @@ package resource import ( "context" + "strings" "testing" "github.com/gojuno/minimock/v3" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/stretchr/testify/assert" @@ -47,7 +50,7 @@ func buildPostgresPlanWithCredentials(credentials types.Object) models.ClickPipe "source_schema_name": types.StringValue("public"), "source_table": types.StringValue("users"), "target_table": types.StringValue("users"), - "excluded_columns": types.ListNull(types.StringType), + "excluded_columns": types.SetNull(types.StringType), "use_custom_sorting_key": types.BoolNull(), "sorting_keys": types.ListNull(types.StringType), "table_engine": types.StringNull(), @@ -170,7 +173,7 @@ func buildMySQLPlanWithCredentials(credentials types.Object) models.ClickPipeRes "source_schema_name": types.StringValue("public"), "source_table": types.StringValue("users"), "target_table": types.StringValue("users"), - "excluded_columns": types.ListNull(types.StringType), + "excluded_columns": types.SetNull(types.StringType), "use_custom_sorting_key": types.BoolNull(), "sorting_keys": types.ListNull(types.StringType), "table_engine": types.StringNull(), @@ -513,3 +516,119 @@ func TestGetStateCheckFunc_AcceptsPausedAndStopped(t *testing.T) { assert.False(t, checker(api.ClickPipeSnapShotState), "Snapshot is transient, not terminal") assert.False(t, checker(api.ClickPipeProvisioningState), "Provisioning is transient, not terminal") } + +// Issue #558 — reordering excluded_columns (now a Set, was an ordered List) must not trip the "table mappings are immutable" ModifyPlan guard. + +// postgresPlanWithExcludedColumns clones the complete Postgres fixture and +// overrides the single table mapping's excluded_columns (as a Set) and +// target_table. Everything else is identical between the state and plan we build +// from it, so ModifyPlan sees no change other than what the test varies. +func postgresPlanWithExcludedColumns(ctx context.Context, excludedColumns []string, targetTable string) models.ClickPipeResourceModel { + state := getPostgresInitialState() + + // getPostgresInitialState leaves these complex-typed fields as zero values, + // which carry no element/attribute types. tfsdk.State.Set rejects that, so + // fill them with correctly-typed nulls before encoding against the schema. + state.Scaling = types.ObjectNull(models.ClickPipeScalingModel{}.ObjectType().AttrTypes) + state.FieldMappings = types.ListNull(models.ClickPipeFieldMappingModel{}.ObjectType()) + state.Settings = types.DynamicNull() + state.Stopped = types.BoolNull() + state.TriggerResync = types.BoolNull() + + excludedVals := make([]attr.Value, len(excludedColumns)) + for i, c := range excludedColumns { + excludedVals[i] = types.StringValue(c) + } + mappingAttrs := map[string]attr.Value{ + "source_schema_name": types.StringValue("public"), + "source_table": types.StringValue("users"), + "target_table": types.StringValue(targetTable), + "excluded_columns": types.SetValueMust(types.StringType, excludedVals), + "use_custom_sorting_key": types.BoolNull(), + "sorting_keys": types.ListNull(types.StringType), // sorting_keys stays an ordered List + "table_engine": types.StringNull(), + "partition_key": types.StringNull(), + } + + var src models.ClickPipeSourceModel + state.Source.As(ctx, &src, basetypes.ObjectAsOptions{}) + var pg models.ClickPipePostgresSourceModel + src.Postgres.As(ctx, &pg, basetypes.ObjectAsOptions{}) + pg.TableMappings = types.SetValueMust( + models.ClickPipePostgresTableMappingModel{}.ObjectType(), + []attr.Value{ + types.ObjectValueMust(models.ClickPipePostgresTableMappingModel{}.ObjectType().AttrTypes, mappingAttrs), + }, + ) + src.Postgres = pg.ObjectValue() + state.Source = src.ObjectValue() + return state +} + +func TestClickPipeResource_ModifyPlan_ExcludedColumnsReorder_Issue558(t *testing.T) { + ctx := context.Background() + r := &ClickPipeResource{} + + schemaResp := &resource.SchemaResponse{} + r.Schema(ctx, resource.SchemaRequest{}, schemaResp) + if schemaResp.Diagnostics.HasError() { + t.Fatalf("building resource schema failed: %v", schemaResp.Diagnostics.Errors()) + } + sch := schemaResp.Schema + + // run drives ModifyPlan for an update (non-null state + plan) and returns the + // resulting diagnostics. + run := func(t *testing.T, stateModel, planModel models.ClickPipeResourceModel) diag.Diagnostics { + t.Helper() + stateVal := tfsdk.State{Schema: sch} + if d := stateVal.Set(ctx, &stateModel); d.HasError() { + t.Fatalf("encoding prior state failed: %v", d.Errors()) + } + planVal := tfsdk.Plan{Schema: sch} + if d := planVal.Set(ctx, &planModel); d.HasError() { + t.Fatalf("encoding plan failed: %v", d.Errors()) + } + req := resource.ModifyPlanRequest{ + State: stateVal, + Plan: planVal, + Config: tfsdk.Config{Schema: sch, Raw: planVal.Raw}, + } + resp := &resource.ModifyPlanResponse{Plan: tfsdk.Plan{Schema: sch, Raw: planVal.Raw}} + r.ModifyPlan(ctx, req, resp) + return resp.Diagnostics + } + + // detailContains reports whether any error diagnostic's detail contains substr. + // We assert on the specific immutability message rather than HasError() so the + // test is robust against unrelated ModifyPlan diagnostics. + detailContains := func(diags diag.Diagnostics, substr string) bool { + for _, d := range diags.Errors() { + if strings.Contains(d.Detail(), substr) { + return true + } + } + return false + } + + t.Run("reordered excluded_columns is not a forbidden modification", func(t *testing.T) { + // state = order a refresh wrote (API order); plan = config order. Same set. + state := postgresPlanWithExcludedColumns(ctx, []string{"status", "status_source", "created_at", "updated_at"}, "users") + plan := postgresPlanWithExcludedColumns(ctx, []string{"updated_at", "created_at", "status", "status_source"}, "users") + + diags := run(t, state, plan) + + assert.False(t, detailContains(diags, "Cannot modify excluded_columns"), + "#558: reordering excluded_columns must NOT be flagged as a mapping modification; got: %v", diags.Errors()) + }) + + t.Run("changing target_table is still rejected (guard intact)", func(t *testing.T) { + // Control: the immutability guard must still fire on a genuine change. + state := postgresPlanWithExcludedColumns(ctx, []string{"status"}, "users") + plan := postgresPlanWithExcludedColumns(ctx, []string{"status"}, "users_renamed") + + diags := run(t, state, plan) + + assert.True(t, detailContains(diags, "Cannot modify target_table"), + "a real target_table change on an existing mapping must still be rejected; got: %v", diags.Errors()) + }) +} diff --git a/pkg/resource/clickpipe_test.go b/pkg/resource/clickpipe_test.go index 1550ae6e..567ac826 100644 --- a/pkg/resource/clickpipe_test.go +++ b/pkg/resource/clickpipe_test.go @@ -367,7 +367,7 @@ func getPostgresInitialState() models.ClickPipeResourceModel { "source_schema_name": types.StringValue("public"), "source_table": types.StringValue("users"), "target_table": types.StringValue("users"), - "excluded_columns": types.ListNull(types.StringType), + "excluded_columns": types.SetNull(types.StringType), "use_custom_sorting_key": types.BoolNull(), "sorting_keys": types.ListNull(types.StringType), "table_engine": types.StringNull(), diff --git a/pkg/resource/models/clickpipe_resource.go b/pkg/resource/models/clickpipe_resource.go index 8dc3f1fa..b11a624c 100644 --- a/pkg/resource/models/clickpipe_resource.go +++ b/pkg/resource/models/clickpipe_resource.go @@ -363,7 +363,7 @@ type ClickPipePostgresTableMappingModel struct { SourceSchemaName types.String `tfsdk:"source_schema_name"` SourceTable types.String `tfsdk:"source_table"` TargetTable types.String `tfsdk:"target_table"` - ExcludedColumns types.List `tfsdk:"excluded_columns"` + ExcludedColumns types.Set `tfsdk:"excluded_columns"` UseCustomSortingKey types.Bool `tfsdk:"use_custom_sorting_key"` SortingKeys types.List `tfsdk:"sorting_keys"` TableEngine types.String `tfsdk:"table_engine"` @@ -376,7 +376,7 @@ func (m ClickPipePostgresTableMappingModel) ObjectType() types.ObjectType { "source_schema_name": types.StringType, "source_table": types.StringType, "target_table": types.StringType, - "excluded_columns": types.ListType{ElemType: types.StringType}, + "excluded_columns": types.SetType{ElemType: types.StringType}, "use_custom_sorting_key": types.BoolType, "sorting_keys": types.ListType{ElemType: types.StringType}, "table_engine": types.StringType, @@ -559,7 +559,7 @@ type ClickPipeBigQueryTableMappingModel struct { SourceDatasetName types.String `tfsdk:"source_dataset_name"` SourceTable types.String `tfsdk:"source_table"` TargetTable types.String `tfsdk:"target_table"` - ExcludedColumns types.List `tfsdk:"excluded_columns"` + ExcludedColumns types.Set `tfsdk:"excluded_columns"` UseCustomSortingKey types.Bool `tfsdk:"use_custom_sorting_key"` SortingKeys types.List `tfsdk:"sorting_keys"` TableEngine types.String `tfsdk:"table_engine"` @@ -571,7 +571,7 @@ func (m ClickPipeBigQueryTableMappingModel) ObjectType() types.ObjectType { "source_dataset_name": types.StringType, "source_table": types.StringType, "target_table": types.StringType, - "excluded_columns": types.ListType{ElemType: types.StringType}, + "excluded_columns": types.SetType{ElemType: types.StringType}, "use_custom_sorting_key": types.BoolType, "sorting_keys": types.ListType{ElemType: types.StringType}, "table_engine": types.StringType, @@ -667,7 +667,7 @@ type ClickPipeMySQLTableMappingModel struct { SourceSchemaName types.String `tfsdk:"source_schema_name"` SourceTable types.String `tfsdk:"source_table"` TargetTable types.String `tfsdk:"target_table"` - ExcludedColumns types.List `tfsdk:"excluded_columns"` + ExcludedColumns types.Set `tfsdk:"excluded_columns"` UseCustomSortingKey types.Bool `tfsdk:"use_custom_sorting_key"` SortingKeys types.List `tfsdk:"sorting_keys"` TableEngine types.String `tfsdk:"table_engine"` @@ -680,7 +680,7 @@ func (m ClickPipeMySQLTableMappingModel) ObjectType() types.ObjectType { "source_schema_name": types.StringType, "source_table": types.StringType, "target_table": types.StringType, - "excluded_columns": types.ListType{ElemType: types.StringType}, + "excluded_columns": types.SetType{ElemType: types.StringType}, "use_custom_sorting_key": types.BoolType, "sorting_keys": types.ListType{ElemType: types.StringType}, "table_engine": types.StringType, From 1fa3e7a27cb4335f1b384dc2f3eb273e4fa9b3dd Mon Sep 17 00:00:00 2001 From: tpanetti Date: Mon, 1 Jun 2026 16:44:35 -0700 Subject: [PATCH 2/3] Address PR comments --- pkg/resource/clickpipe.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/resource/clickpipe.go b/pkg/resource/clickpipe.go index 9f1438db..cc9c69e6 100644 --- a/pkg/resource/clickpipe.go +++ b/pkg/resource/clickpipe.go @@ -4719,6 +4719,9 @@ func (c *ClickPipeResource) syncClickPipeState(ctx context.Context, state *model excludedCols[j] = types.StringValue(col) } tableMappingModel.ExcludedColumns, _ = types.SetValue(types.StringType, excludedCols) + } else if stateMapping != nil && !stateMapping.ExcludedColumns.IsNull() { + // Preserve empty set from state (vs null) to avoid plan diff + tableMappingModel.ExcludedColumns, _ = types.SetValue(types.StringType, []attr.Value{}) } else { tableMappingModel.ExcludedColumns = types.SetNull(types.StringType) } From 9c5a159d9695777fc670b43f531c4326530dceae Mon Sep 17 00:00:00 2001 From: tpanetti Date: Mon, 1 Jun 2026 16:44:55 -0700 Subject: [PATCH 3/3] update docs --- docs/resources/clickpipe.md | 10 +++++----- docs/resources/service.md | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/resources/clickpipe.md b/docs/resources/clickpipe.md index a8cbafc2..4f247b7b 100644 --- a/docs/resources/clickpipe.md +++ b/docs/resources/clickpipe.md @@ -246,7 +246,7 @@ Optional: - `certificate` (String, Sensitive) PEM encoded client certificate for mTLS authentication. Use with `MUTUAL_TLS` authentication. - `connection_string` (String, Sensitive) The connection string for the Kafka source. Use with `azureeventhub` Kafka source type. Use with `PLAIN` authentication. - `password` (String, Sensitive) The password for the Kafka source. Use `password_wo` instead to keep the value out of state. -- `password_wo` (String, Sensitive) Write-only password for the Kafka source. Not persisted to state. Pair with `password_wo_version` to trigger updates. +- `password_wo` (String, Sensitive, [Write-only](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments)) Write-only password for the Kafka source. Not persisted to state. Pair with `password_wo_version` to trigger updates. - `password_wo_version` (Number) Version trigger for `password_wo`. Increment to push a new password to the API. - `private_key` (String, Sensitive) PEM encoded client private key for mTLS authentication. Use with `MUTUAL_TLS` authentication. - `secret_key` (String, Sensitive) The secret key for the Kafka source. Use with `IAM_USER` authentication. @@ -284,7 +284,7 @@ Required: Optional: - `password` (String, Sensitive) The password for the Schema Registry. Either `password` or `password_wo` must be provided. -- `password_wo` (String, Sensitive) Write-only password for the Schema Registry. Not persisted to state. Pair with `password_wo_version` to trigger updates. +- `password_wo` (String, Sensitive, [Write-only](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments)) Write-only password for the Schema Registry. Not persisted to state. Pair with `password_wo_version` to trigger updates. - `password_wo_version` (Number) Version trigger for `password_wo`. Increment to push a new password to the API. @@ -376,7 +376,7 @@ Required: Optional: - `password` (String, Sensitive) The password for the MongoDB instance. Use `password_wo` instead to keep the value out of state. -- `password_wo` (String, Sensitive) Write-only password for the MongoDB instance. Not persisted to state. Pair with `password_wo_version` to trigger updates. +- `password_wo` (String, Sensitive, [Write-only](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments)) Write-only password for the MongoDB instance. Not persisted to state. Pair with `password_wo_version` to trigger updates. - `password_wo_version` (Number) Version trigger for `password_wo`. Increment to push a new password to the API. @@ -412,7 +412,7 @@ Required: Optional: - `password` (String, Sensitive) The password for the MySQL instance. Use `password_wo` instead to keep the value out of state. -- `password_wo` (String, Sensitive) Write-only password for the MySQL instance. Not persisted to state. Pair with `password_wo_version` to trigger updates. +- `password_wo` (String, Sensitive, [Write-only](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments)) Write-only password for the MySQL instance. Not persisted to state. Pair with `password_wo_version` to trigger updates. - `password_wo_version` (Number) Version trigger for `password_wo`. Increment to push a new password to the API. @@ -518,7 +518,7 @@ Required: Optional: - `password` (String, Sensitive) The password for the Postgres instance. Use `password_wo` instead to keep the value out of state. -- `password_wo` (String, Sensitive) Write-only password for the Postgres instance. Not persisted to state. Pair with `password_wo_version` to trigger updates. +- `password_wo` (String, Sensitive, [Write-only](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments)) Write-only password for the Postgres instance. Not persisted to state. Pair with `password_wo_version` to trigger updates. - `password_wo_version` (Number) Version trigger for `password_wo`. Increment to push a new password to the API. diff --git a/docs/resources/service.md b/docs/resources/service.md index aebd4c9b..5dfed357 100644 --- a/docs/resources/service.md +++ b/docs/resources/service.md @@ -57,6 +57,8 @@ resource "clickhouse_service" "service" { ### Optional +> **NOTE**: [Write-only arguments](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments) are supported in Terraform 1.11 and later. + - `backup_configuration` (Attributes) Configuration of service backup settings. (see [below for nested schema](#nestedatt--backup_configuration)) - `backup_id` (String) ID of the backup to restore when creating new service. If specified, the service will be created as a restore operation - `byoc_id` (String) BYOC ID related to the cloud provider account you want to create this service into. @@ -75,7 +77,7 @@ resource "clickhouse_service" "service" { - `num_replicas` (Number) Number of replicas for the service. - `password` (String, Sensitive) Password for the default user. One of either `password`, `password_wo`, or `password_hash` must be specified. - `password_hash` (String, Sensitive) SHA256 hash of password for the default user. One of either `password`, `password_wo`, or `password_hash` must be specified. -- `password_wo` (String, Sensitive) Password for the default user (write-only, not persisted to state). Use this instead of `password` to avoid storing the password hash in Terraform state. +- `password_wo` (String, Sensitive, [Write-only](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments)) Password for the default user (write-only, not persisted to state). Use this instead of `password` to avoid storing the password hash in Terraform state. - `password_wo_version` (Number) Version number for password_wo. Increment this to trigger a password update when using password_wo. - `query_api_endpoints` (Attributes) Configuration of the query API endpoints feature. (see [below for nested schema](#nestedatt--query_api_endpoints)) - `readonly` (Boolean) Indicates if this service should be read only. Only allowed for secondary services, those which share data with another service (i.e. when `warehouse_id` field is set).