diff --git a/docs/data-sources/clickhouse.md b/docs/data-sources/clickhouse.md index f8e7d39..c36a67c 100644 --- a/docs/data-sources/clickhouse.md +++ b/docs/data-sources/clickhouse.md @@ -41,12 +41,16 @@ Read-Only: - `host` (String) Host to connect to - `https_port` (Number) Port to connect to using the HTTPS protocol +- `https_port_ctls` (Number) Port to connect to using the HTTPS protocol with custom TLS certificate - `https_uri` (String) URI to connect to using the HTTPS protocol +- `https_uri_ctls` (String) URI to connect to using the HTTPS protocol with custom TLS certificate - `jdbc_uri` (String) URI to connect to using the JDBC protocol - `native_protocol` (String) Connection string for the ClickHouse native protocol +- `native_protocol_ctls` (String) Connection string for the ClickHouse native protocol with custom TLS certificate - `odbc_uri` (String) URI to connect to using the ODBC protocol - `password` (String, Sensitive) Password for the ClickHouse user - `tcp_port_secure` (Number) Port to connect to using the TCP/native protocol +- `tcp_port_secure_ctls` (Number) Port to connect to using the TCP/native protocol with custom TLS certificate - `user` (String) ClickHouse user @@ -67,10 +71,14 @@ Read-Only: - `host` (String) Host to connect to - `https_port` (Number) Port to connect to using the HTTPS protocol +- `https_port_ctls` (Number) Port to connect to using the HTTPS protocol with custom TLS certificate - `https_uri` (String) URI to connect to using the HTTPS protocol +- `https_uri_ctls` (String) URI to connect to using the HTTPS protocol with custom TLS certificate - `jdbc_uri` (String) URI to connect to using the JDBC protocol - `native_protocol` (String) Connection string for the ClickHouse native protocol +- `native_protocol_ctls` (String) Connection string for the ClickHouse native protocol with custom TLS certificate - `odbc_uri` (String) URI to connect to using the ODBC protocol - `password` (String, Sensitive) Password for the ClickHouse user - `tcp_port_secure` (Number) Port to connect to using the TCP/native protocol +- `tcp_port_secure_ctls` (Number) Port to connect to using the TCP/native protocol with custom TLS certificate - `user` (String) ClickHouse user diff --git a/docs/resources/clickhouse_cluster.md b/docs/resources/clickhouse_cluster.md index d30bb2f..b43f06a 100644 --- a/docs/resources/clickhouse_cluster.md +++ b/docs/resources/clickhouse_cluster.md @@ -233,12 +233,16 @@ Read-Only: - `host` (String) Host to connect to - `https_port` (Number) Port to connect to using the HTTPS protocol +- `https_port_ctls` (Number) Port to connect to using the HTTPS protocol with custom TLS certificate - `https_uri` (String) URI to connect to using the HTTPS protocol +- `https_uri_ctls` (String) URI to connect to using the HTTPS protocol with custom TLS certificate - `jdbc_uri` (String) URI to connect to using the JDBC protocol - `native_protocol` (String) Connection string for the ClickHouse native protocol +- `native_protocol_ctls` (String) Connection string for the ClickHouse native protocol with custom TLS certificate - `odbc_uri` (String) URI to connect to using the ODBC protocol - `password` (String, Sensitive) Password for the ClickHouse user - `tcp_port_secure` (Number) Port to connect to using the TCP/native protocol +- `tcp_port_secure_ctls` (Number) Port to connect to using the TCP/native protocol with custom TLS certificate - `user` (String) ClickHouse user @@ -249,10 +253,14 @@ Read-Only: - `host` (String) Host to connect to - `https_port` (Number) Port to connect to using the HTTPS protocol +- `https_port_ctls` (Number) Port to connect to using the HTTPS protocol with custom TLS certificate - `https_uri` (String) URI to connect to using the HTTPS protocol +- `https_uri_ctls` (String) URI to connect to using the HTTPS protocol with custom TLS certificate - `jdbc_uri` (String) URI to connect to using the JDBC protocol - `native_protocol` (String) Connection string for the ClickHouse native protocol +- `native_protocol_ctls` (String) Connection string for the ClickHouse native protocol with custom TLS certificate - `odbc_uri` (String) URI to connect to using the ODBC protocol - `password` (String, Sensitive) Password for the ClickHouse user - `tcp_port_secure` (Number) Port to connect to using the TCP/native protocol +- `tcp_port_secure_ctls` (Number) Port to connect to using the TCP/native protocol with custom TLS certificate - `user` (String) ClickHouse user diff --git a/internal/provider/clickhouse_cluster_resource.go b/internal/provider/clickhouse_cluster_resource.go index 10360b9..e7a02ad 100644 --- a/internal/provider/clickhouse_cluster_resource.go +++ b/internal/provider/clickhouse_cluster_resource.go @@ -51,7 +51,7 @@ type clickhouseClusterModel struct { // https://github.com/doublecloud/api/blob/main/doublecloud/v1/maintenance.proto // MaintenanceWindow *maintenanceWindow `tfsdk:"maintenance_window"` - CustomCertificate types.Object `tfsdk:"custom_certificate"` + CustomCertificate *clickhouseCustomCertificate `tfsdk:"custom_certificate"` } type clickhouseClusterResources struct { @@ -112,6 +112,40 @@ func (m *clickhouseClusterResources) convert() (*clickhouse.ClusterResources, di return &r, diags } +type clickhouseCustomCertificate struct { + Certificate types.String `tfsdk:"certificate"` + Key types.String `tfsdk:"key"` + RootCA types.String `tfsdk:"root_ca"` +} + +func (cc *clickhouseCustomCertificate) convert() (*clickhouse.CustomCertificate, diag.Diagnostics) { + res := clickhouse.CustomCertificate{ + Enabled: false, + } + + var diags diag.Diagnostics + + if cc != nil { + if !cc.Certificate.IsNull() && !cc.Key.IsNull() { + res.Enabled = true + res.Certificate = &wrappers.BytesValue{Value: []byte(cc.Certificate.ValueString())} + res.Key = &wrappers.BytesValue{Value: []byte(cc.Key.ValueString())} + if !cc.RootCA.IsNull() { + res.RootCa = &wrappers.BytesValue{Value: []byte(cc.RootCA.ValueString())} + } + } else { + if cc.Certificate.IsNull() { + diags.AddError("missed certificate", "for custom certificate must be both certificate and key") + } + if cc.Key.IsNull() { + diags.AddError("missed certificate", "for custom certificate must be both certificate and key") + } + } + } + + return &res, diags +} + type clickhouseClusterResourcesClickhouse struct { ResourcePresetId types.String `tfsdk:"resource_preset_id"` MinResourcePresetId types.String `tfsdk:"min_resource_preset_id"` @@ -350,6 +384,26 @@ func clickhouseConenctionInfoSchema() map[string]schema.Attribute { MarkdownDescription: "URI to connect to using the ODBC protocol", PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, }, + "https_port_ctls": schema.Int64Attribute{ + Computed: true, + MarkdownDescription: "Port to connect to using the HTTPS protocol with custom TLS certificate", + PlanModifiers: []planmodifier.Int64{int64planmodifier.UseStateForUnknown()}, + }, + "tcp_port_secure_ctls": schema.Int64Attribute{ + Computed: true, + MarkdownDescription: "Port to connect to using the TCP/native protocol with custom TLS certificate", + PlanModifiers: []planmodifier.Int64{int64planmodifier.UseStateForUnknown()}, + }, + "native_protocol_ctls": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Connection string for the ClickHouse native protocol with custom TLS certificate", + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + }, + "https_uri_ctls": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "URI to connect to using the HTTPS protocol with custom TLS certificate", + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + }, } } @@ -359,16 +413,26 @@ func clickhouseCustomCertificateSchema() map[string]schema.Attribute { Optional: true, MarkdownDescription: "Public certificate", PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("key")), + }, }, "key": schema.StringAttribute{ Optional: true, MarkdownDescription: "Private certificate key", PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("certificate")), + }, }, "root_ca": schema.StringAttribute{ Optional: true, MarkdownDescription: "Root certificate", PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("key")), + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("certificate")), + }, }, } } @@ -520,7 +584,6 @@ func (r *ClickhouseClusterResource) Schema(ctx context.Context, req resource.Sch Attributes: clickhouseCustomCertificateSchema(), PlanModifiers: []planmodifier.Object{objectplanmodifier.UseStateForUnknown()}, MarkdownDescription: "Custom TLS certificate", - Validators: []validator.Object{&clickhouseCustomCertificateValidator{}}, }, }, } @@ -575,6 +638,10 @@ func createClickhouseClusterRequest(m *clickhouseClusterModel) (*clickhouse.Crea } // TODO: mw + if m.CustomCertificate != nil { + diags.AddError("custom_certificate exists", "custom_certificate can't be applied during cluster creation") + } + return rq, diags } @@ -672,20 +739,9 @@ func updateClickhouseCluster(m *clickhouseClusterModel) (*clickhouse.UpdateClust rq.Access = access } - cc := m.CustomCertificate.Attributes() - rq.CustomCertificate = &clickhouse.CustomCertificate{ - Enabled: false, - } - certificate, certOk := cc["certificate"] - key, keyOk := cc["key"] - rq.CustomCertificate.Enabled = certOk && keyOk - if rq.CustomCertificate.Enabled { - rq.CustomCertificate.Certificate = &wrappers.BytesValue{Value: []byte(certificate.(types.String).ValueString())} - rq.CustomCertificate.Key = &wrappers.BytesValue{Value: []byte(key.(types.String).ValueString())} - if rootCa, ok := cc["root_ca"]; ok { - rq.CustomCertificate.RootCa = &wrappers.BytesValue{Value: []byte(rootCa.(types.String).ValueString())} - } - } + cc, d := m.CustomCertificate.convert() + rq.CustomCertificate = cc + diags.Append(d...) return rq, diags } @@ -779,10 +835,10 @@ func (m *clickhouseClusterModel) parse(rs *clickhouse.Cluster) diag.Diagnostics } oldKey := "" - if key, ok := m.CustomCertificate.Attributes()["key"]; ok { - oldKey = key.String() + if m.CustomCertificate != nil && !m.CustomCertificate.Key.IsNull() { + oldKey = m.CustomCertificate.Key.String() } - m.CustomCertificate = parseClickhouseCustomCertificate(rs.GetCustomCertificate(), oldKey, diags).convert(diags) + m.CustomCertificate = parseClickhouseCustomCertificate(rs.GetCustomCertificate(), oldKey, diags).convert() // parse MW return diags diff --git a/internal/provider/clickhouse_cluster_resource_test.go b/internal/provider/clickhouse_cluster_resource_test.go index 747a5fb..c4b81db 100644 --- a/internal/provider/clickhouse_cluster_resource_test.go +++ b/internal/provider/clickhouse_cluster_resource_test.go @@ -10,7 +10,6 @@ import ( "text/template" "github.com/doublecloud/go-genproto/doublecloud/clickhouse/v1" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) @@ -121,18 +120,24 @@ func TestAccClickhouseClusterResource(t *testing.T) { } m4 := m3 - cc, _ := types.ObjectValue(map[string]attr.Type{ - "certificate": types.StringType, - "key": types.StringType, - "root_ca": types.StringType, - }, - map[string]attr.Value{ - "certificate": types.StringValue(testAccClickhouseTLSCert), - "key": types.StringValue(testAccClickhouseTLSKey), - "root_ca": types.StringValue(testAccClickhouseTLSRootCA), + /* + cc, _ := types.ObjectValue(map[string]attr.Type{ + "certificate": types.StringType, + "key": types.StringType, + "root_ca": types.StringType, }, - ) - m4.CustomCertificate = cc + map[string]attr.Value{ + "certificate": types.StringValue(testAccClickhouseTLSCert), + "key": types.StringValue(testAccClickhouseTLSKey), + "root_ca": types.StringValue(testAccClickhouseTLSRootCA), + }, + ) + */ + m4.CustomCertificate = &clickhouseCustomCertificate{ + Certificate: types.StringValue(testAccClickhouseTLSCert), + Key: types.StringValue(testAccClickhouseTLSKey), + RootCA: types.StringValue(testAccClickhouseTLSRootCA), + } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -159,6 +164,10 @@ func TestAccClickhouseClusterResource(t *testing.T) { resource.TestCheckResourceAttr(testAccClickhouseId, "private_connection_info.user", "admin"), resource.TestCheckResourceAttr(testAccClickhouseId, "private_connection_info.https_port", "8443"), resource.TestCheckResourceAttr(testAccClickhouseId, "private_connection_info.tcp_port_secure", "9440"), + resource.TestCheckResourceAttr(testAccClickhouseId, "connection_info.https_port_ctls", "0"), + resource.TestCheckResourceAttr(testAccClickhouseId, "connection_info.tcp_port_secure_ctls", "0"), + resource.TestCheckResourceAttr(testAccClickhouseId, "private_connection_info.https_port_ctls", "0"), + resource.TestCheckResourceAttr(testAccClickhouseId, "private_connection_info.tcp_port_secure_ctls", "0"), ), }, // Update and Read testing @@ -197,6 +206,10 @@ func TestAccClickhouseClusterResource(t *testing.T) { resource.TestCheckResourceAttr(testAccClickhouseId, "custom_certificate.certificate", testAccClickhouseTLSCert), resource.TestCheckResourceAttr(testAccClickhouseId, "custom_certificate.key", testAccClickhouseTLSKey), resource.TestCheckResourceAttr(testAccClickhouseId, "custom_certificate.root_ca", testAccClickhouseTLSRootCA), + resource.TestCheckResourceAttr(testAccClickhouseId, "connection_info.https_port_ctls", "8444"), + resource.TestCheckResourceAttr(testAccClickhouseId, "connection_info.tcp_port_secure_ctls", "9444"), + resource.TestCheckResourceAttr(testAccClickhouseId, "private_connection_info.https_port_ctls", "8444"), + resource.TestCheckResourceAttr(testAccClickhouseId, "private_connection_info.tcp_port_secure_ctls", "9444"), ), }, // Delete testing automatically occurs in TestCase @@ -338,11 +351,11 @@ resource "doublecloud_clickhouse_cluster" "tf-acc-clickhouse" { ] {{- end}} } - {{- if not .CustomCertificate.IsNull }} + {{- if ne .CustomCertificate nil }} custom_certificate { - certificate = {{ .CustomCertificate.Attributes.certificate }} - key = {{ .CustomCertificate.Attributes.key }} - root_ca = {{ .CustomCertificate.Attributes.root_ca }} + certificate = {{ .CustomCertificate.Certificate }} + key = {{ .CustomCertificate.Key }} + root_ca = {{ .CustomCertificate.RootCA }} } {{- end}} }` diff --git a/internal/provider/clickhouse_data_source.go b/internal/provider/clickhouse_data_source.go index 7335baa..4969e42 100644 --- a/internal/provider/clickhouse_data_source.go +++ b/internal/provider/clickhouse_data_source.go @@ -44,39 +44,51 @@ type ClickhouseDataSourceModel struct { } type ClickhouseConnectionInfo struct { - Host types.String `tfsdk:"host"` - User types.String `tfsdk:"user"` - Password types.String `tfsdk:"password"` - HttpsPort types.Int64 `tfsdk:"https_port"` - TcpPortSecure types.Int64 `tfsdk:"tcp_port_secure"` - NativeProtocol types.String `tfsdk:"native_protocol"` - HttpsUri types.String `tfsdk:"https_uri"` - JdbcUri types.String `tfsdk:"jdbc_uri"` - OdbcUri types.String `tfsdk:"odbc_uri"` + Host types.String `tfsdk:"host"` + User types.String `tfsdk:"user"` + Password types.String `tfsdk:"password"` + HttpsPort types.Int64 `tfsdk:"https_port"` + TcpPortSecure types.Int64 `tfsdk:"tcp_port_secure"` + NativeProtocol types.String `tfsdk:"native_protocol"` + HttpsUri types.String `tfsdk:"https_uri"` + JdbcUri types.String `tfsdk:"jdbc_uri"` + OdbcUri types.String `tfsdk:"odbc_uri"` + HttpsPortCTLS types.Int64 `tfsdk:"https_port_ctls"` + TcpPortSecureCTLS types.Int64 `tfsdk:"tcp_port_secure_ctls"` + NativeProtocolCTLS types.String `tfsdk:"native_protocol_ctls"` + HttpsUriCTLS types.String `tfsdk:"https_uri_ctls"` } func (ci ClickhouseConnectionInfo) convert(diags diag.Diagnostics) types.Object { res, d := types.ObjectValue(map[string]attr.Type{ - "host": types.StringType, - "user": types.StringType, - "password": types.StringType, - "https_port": types.Int64Type, - "tcp_port_secure": types.Int64Type, - "native_protocol": types.StringType, - "https_uri": types.StringType, - "jdbc_uri": types.StringType, - "odbc_uri": types.StringType, + "host": types.StringType, + "user": types.StringType, + "password": types.StringType, + "https_port": types.Int64Type, + "tcp_port_secure": types.Int64Type, + "native_protocol": types.StringType, + "https_uri": types.StringType, + "jdbc_uri": types.StringType, + "odbc_uri": types.StringType, + "https_port_ctls": types.Int64Type, + "tcp_port_secure_ctls": types.Int64Type, + "native_protocol_ctls": types.StringType, + "https_uri_ctls": types.StringType, }, map[string]attr.Value{ - "host": ci.Host, - "user": ci.User, - "password": ci.Password, - "https_port": ci.HttpsPort, - "tcp_port_secure": ci.TcpPortSecure, - "native_protocol": ci.NativeProtocol, - "https_uri": ci.HttpsUri, - "jdbc_uri": ci.JdbcUri, - "odbc_uri": ci.OdbcUri, + "host": ci.Host, + "user": ci.User, + "password": ci.Password, + "https_port": ci.HttpsPort, + "tcp_port_secure": ci.TcpPortSecure, + "native_protocol": ci.NativeProtocol, + "https_uri": ci.HttpsUri, + "jdbc_uri": ci.JdbcUri, + "odbc_uri": ci.OdbcUri, + "https_port_ctls": ci.HttpsPortCTLS, + "tcp_port_secure_ctls": ci.TcpPortSecureCTLS, + "native_protocol_ctls": ci.NativeProtocolCTLS, + "https_uri_ctls": ci.HttpsUriCTLS, }, ) diags.Append(d...) @@ -89,24 +101,16 @@ type ClickhouseCustomCertificate struct { RootCA types.String `tfsdk:"root_ca"` } -func (cc *ClickhouseCustomCertificate) convert(diags diag.Diagnostics) types.Object { - attrTypeMap := map[string]attr.Type{ - "certificate": types.StringType, - "key": types.StringType, - "root_ca": types.StringType, - } +func (cc *ClickhouseCustomCertificate) convert() *clickhouseCustomCertificate { if cc == nil { - return types.ObjectNull(attrTypeMap) + return nil } - res, d := types.ObjectValue(attrTypeMap, - map[string]attr.Value{ - "certificate": cc.Certificate, - "key": cc.Key, - "root_ca": cc.RootCA, - }, - ) - diags.Append(d...) - return res + res := clickhouseCustomCertificate{ + Certificate: cc.Certificate, + Key: cc.Key, + RootCA: cc.RootCA, + } + return &res } func (d *ClickhouseDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { diff --git a/internal/provider/clickhouse_data_source_test.go b/internal/provider/clickhouse_data_source_test.go index d45d0cf..5cfa93b 100644 --- a/internal/provider/clickhouse_data_source_test.go +++ b/internal/provider/clickhouse_data_source_test.go @@ -24,6 +24,10 @@ func TestAccClickhouseDataSource(t *testing.T) { resource.TestCheckResourceAttr("data.doublecloud_clickhouse.test", "private_connection_info.user", "admin"), resource.TestCheckResourceAttr("data.doublecloud_clickhouse.test", "private_connection_info.https_port", "8443"), resource.TestCheckResourceAttr("data.doublecloud_clickhouse.test", "private_connection_info.tcp_port_secure", "9440"), + resource.TestCheckResourceAttr("data.doublecloud_clickhouse.test", "connection_info.https_port_ctls", "0"), + resource.TestCheckResourceAttr("data.doublecloud_clickhouse.test", "connection_info.tcp_port_secure_ctls", "0"), + resource.TestCheckResourceAttr("data.doublecloud_clickhouse.test", "private_connection_info.https_port_ctls", "0"), + resource.TestCheckResourceAttr("data.doublecloud_clickhouse.test", "private_connection_info.tcp_port_secure_ctls", "0"), ), }, }, diff --git a/internal/provider/helpers.go b/internal/provider/helpers.go index f4bd77d..96ddda1 100644 --- a/internal/provider/helpers.go +++ b/internal/provider/helpers.go @@ -138,42 +138,6 @@ func (*suppressAutoscaledDiskDiff) PlanModifyInt64(ctx context.Context, req plan } } -type clickhouseCustomCertificateValidator struct{} - -var _ validator.Object = &clickhouseCustomCertificateValidator{} - -func (*clickhouseCustomCertificateValidator) Description(context.Context) string { - return "validate custom TLS certificate" -} - -func (s *clickhouseCustomCertificateValidator) MarkdownDescription(ctx context.Context) string { - return s.Description(ctx) -} - -func (*clickhouseCustomCertificateValidator) ValidateObject(ctx context.Context, req validator.ObjectRequest, rsp *validator.ObjectResponse) { - if req.ConfigValue.IsNull() { - return - } - - certificatePresent := !req.ConfigValue.Attributes()["certificate"].IsNull() - keyPresent := !req.ConfigValue.Attributes()["key"].IsNull() - rootPresent := !req.ConfigValue.Attributes()["root_ca"].IsNull() - - if (certificatePresent && !keyPresent) || (!certificatePresent && keyPresent) { - rsp.Diagnostics.Append(validatordiag.InvalidAttributeCombinationDiagnostic( - req.Path, - `Must be both attributea "certificate" and "key"`, - )) - } - - if !certificatePresent && rootPresent { - rsp.Diagnostics.Append(validatordiag.InvalidAttributeCombinationDiagnostic( - req.Path, - `Attribute "root_ca" can be only with "certificate" and "key"`, - )) - } -} - type clusterResourcesValidator struct{} func (*clusterResourcesValidator) Description(context.Context) string {