diff --git a/docs/resources/service.md b/docs/resources/service.md index b1e90075..4fdfa265 100644 --- a/docs/resources/service.md +++ b/docs/resources/service.md @@ -66,8 +66,10 @@ resource "clickhouse_service" "service" { - `min_replica_memory_gb` (Number) Minimum memory of a single replica during auto-scaling in Gb. Must be a multiple of 4 greater than or equal to 8. `min_replica_memory_gb` x `num_replicas` (default 3) must be lower than 360 for non paid services or 720 for paid services. - `min_total_memory_gb` (Number, Deprecated) Minimum total memory of all workers during auto-scaling in Gb. Must be a multiple of 12 and greater than 24. - `num_replicas` (Number) Number of replicas for the service. Must be between 3 and 20. Contact support to enable this feature. -- `password` (String, Sensitive) Password for the default user. One of either `password` or `password_hash` must be specified. +- `password` (String, Sensitive) Password for the default user. One of either `password_wo`, `password` or `password_hash` must be specified. - `password_hash` (String, Sensitive) SHA256 hash of password for the default user. One of either `password` or `password_hash` must be specified. +- `password_wo` (String, Sensitive) Password write only (not stored in state) for the default user. One of either `password_wo`, `password` or `password_hash` must be specified. +- `password_wo_version` (Number) Password write only version for the default user. The version is stored in state so when it is updated password_wo gets updated too. Only `password_wo` must be specified. - `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). - `release_channel` (String) Release channel to use for this service. Either 'default' or 'fast'. Switching from 'fast' to 'default' release channel is not supported. diff --git a/pkg/resource/models/service_resource.go b/pkg/resource/models/service_resource.go index b8a10469..d35c635a 100644 --- a/pkg/resource/models/service_resource.go +++ b/pkg/resource/models/service_resource.go @@ -212,6 +212,8 @@ type ServiceResourceModel struct { Password types.String `tfsdk:"password"` PasswordHash types.String `tfsdk:"password_hash"` DoubleSha1PasswordHash types.String `tfsdk:"double_sha1_password_hash"` + PasswordWO types.String `tfsdk:"password_wo"` + PasswordWOVersion types.Int32 `tfsdk:"password_wo_version"` Endpoints types.Object `tfsdk:"endpoints"` CloudProvider types.String `tfsdk:"cloud_provider"` Region types.String `tfsdk:"region"` @@ -242,6 +244,8 @@ func (m *ServiceResourceModel) Equals(b ServiceResourceModel) bool { !m.IsPrimary.Equal(b.IsPrimary) || !m.Name.Equal(b.Name) || !m.Password.Equal(b.Password) || + !m.PasswordWO.Equal(b.PasswordWO) || + !m.PasswordWOVersion.Equal(b.PasswordWOVersion) || !m.PasswordHash.Equal(b.PasswordHash) || !m.DoubleSha1PasswordHash.Equal(b.DoubleSha1PasswordHash) || !m.Endpoints.Equal(b.Endpoints) || diff --git a/pkg/resource/service.go b/pkg/resource/service.go index 038797a0..182dc8d1 100644 --- a/pkg/resource/service.go +++ b/pkg/resource/service.go @@ -113,14 +113,42 @@ func (r *ServiceResource) Schema(_ context.Context, _ resource.SchemaRequest, re Required: true, }, "password": schema.StringAttribute{ - Description: "Password for the default user. One of either `password` or `password_hash` must be specified.", + Description: "Password for the default user. One of either `password_wo`, `password` or `password_hash` must be specified.", Optional: true, Sensitive: true, Validators: []validator.String{ - stringvalidator.ConflictsWith(path.Expressions{path.MatchRoot("double_sha1_password_hash")}...), + stringvalidator.ConflictsWith(path.Expressions{path.MatchRoot("double_sha1_password_hash"), path.MatchRoot("password_wo")}...), stringvalidator.AtLeastOneOf(path.Expressions{ path.MatchRoot("password_hash"), path.MatchRoot("warehouse_id"), + path.MatchRoot("password_wo"), + }...), + }, + }, + // WriteOnly indicates that Terraform will not store this attribute value in the plan or state artifacts. + // Acces to the value is only possible through config. + "password_wo": schema.StringAttribute{ + Description: "Password write only (not stored in state) for the default user. One of either `password_wo`, `password` or `password_hash` must be specified.", + Optional: true, + Sensitive: true, + WriteOnly: true, + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.Expressions{path.MatchRoot("double_sha1_password_hash"), path.MatchRoot("password")}...), + stringvalidator.AtLeastOneOf(path.Expressions{ + path.MatchRoot("password_hash"), + path.MatchRoot("password"), + path.MatchRoot("warehouse_id"), + }...), + }, + }, + "password_wo_version": schema.Int32Attribute{ + Description: "Password write only version for the default user. The version is stored in state so when it is updated password_wo gets updated too. Only `password_wo` must be specified.", + Optional: true, + Sensitive: false, + WriteOnly: false, + Validators: []validator.Int32{ + int32validator.AtLeastOneOf(path.Expressions{ + path.MatchRoot("password_wo"), }...), }, }, @@ -961,6 +989,12 @@ func (r *ServiceResource) Create(ctx context.Context, req resource.CreateRequest if plan.DataWarehouseID.IsUnknown() || plan.DataWarehouseID.IsNull() { // Update service password if provided explicitly planPassword := plan.Password.ValueString() + + // password_wo is writeOnly meaning that Terraform will not store this attribute value in the plan or state artifacts. + // Acces to the value is only possible through config. + var passwordWO types.String + req.Config.GetAttribute(ctx, path.Root("password_wo"), &passwordWO) + if len(planPassword) > 0 { _, err := r.client.UpdateServicePassword(ctx, s.Id, servicePasswordUpdateFromPlainPassword(planPassword)) if err != nil { @@ -970,6 +1004,15 @@ func (r *ServiceResource) Create(ctx context.Context, req resource.CreateRequest ) return } + } else if len(passwordWO.ValueString()) > 0 { + _, err := r.client.UpdateServicePassword(ctx, s.Id, servicePasswordUpdateFromPlainPassword(passwordWO.ValueString())) + if err != nil { + resp.Diagnostics.AddError( + "Error setting service password", + "Could not set service password after creation, unexpected error: "+err.Error(), + ) + return + } } // Update hashed service password if provided explicitly @@ -1312,6 +1355,12 @@ func (r *ServiceResource) Update(ctx context.Context, req resource.UpdateRequest } password := plan.Password.ValueString() + + // password_wo is writeOnly meaning that Terraform will not store this attribute value in the plan or state artifacts. + // Acces to the value is only possible through config. + var passwordWO types.String + req.Config.GetAttribute(ctx, path.Root("password_wo"), &passwordWO) + if len(password) > 0 && plan.Password != state.Password { password = plan.Password.ValueString() _, err := r.client.UpdateServicePassword(ctx, serviceId, servicePasswordUpdateFromPlainPassword(password)) @@ -1343,6 +1392,15 @@ func (r *ServiceResource) Update(ctx context.Context, req resource.UpdateRequest ) return } + } else if len(passwordWO.ValueString()) > 0 && plan.PasswordWOVersion.ValueInt32() > state.PasswordWOVersion.ValueInt32() { + _, err := r.client.UpdateServicePassword(ctx, serviceId, servicePasswordUpdateFromPlainPassword(passwordWO.ValueString())) + if err != nil { + resp.Diagnostics.AddError( + "Error Updating ClickHouse Service Password", + "Could not update service password, unexpected error: "+err.Error(), + ) + return + } } // Update Query API endpoints settings. @@ -1510,6 +1568,9 @@ func (r *ServiceResource) UpgradeState(ctx context.Context) map[int64]resource.S Optional: true, Sensitive: true, }, + "password_wo_version": schema.StringAttribute{ + Optional: true, + }, "password_hash": schema.StringAttribute{ Optional: true, Sensitive: true, @@ -1662,6 +1723,7 @@ func (r *ServiceResource) UpgradeState(ctx context.Context) map[int64]resource.S ReadOnly types.Bool `tfsdk:"readonly"` Name types.String `tfsdk:"name"` Password types.String `tfsdk:"password"` + PasswordWOVersion types.Int32 `tfsdk:"password_wo_version"` PasswordHash types.String `tfsdk:"password_hash"` DoubleSha1PasswordHash types.String `tfsdk:"double_sha1_password_hash"` EndpointsConfiguration types.Object `tfsdk:"endpoints_configuration"` @@ -1753,6 +1815,7 @@ func (r *ServiceResource) UpgradeState(ctx context.Context) map[int64]resource.S ReadOnly: priorStateData.ReadOnly, Name: priorStateData.Name, Password: priorStateData.Password, + PasswordWOVersion: priorStateData.PasswordWOVersion, PasswordHash: priorStateData.PasswordHash, DoubleSha1PasswordHash: priorStateData.DoubleSha1PasswordHash, Endpoints: endpoints.ObjectValue(),