diff --git a/docs/data-sources/clickhouse.md b/docs/data-sources/clickhouse.md
index 72a5462..f8e7d39 100644
--- a/docs/data-sources/clickhouse.md
+++ b/docs/data-sources/clickhouse.md
@@ -28,6 +28,7 @@ Clickhouse data source
- `cloud_type` (String) Cloud provider (`aws`, `gcp`, or `azure`)
- `connection_info` (Attributes) Public connection info (see [below for nested schema](#nestedatt--connection_info))
+- `custom_certificate` (Attributes) Custom TLS certificate (see [below for nested schema](#nestedatt--custom_certificate))
- `description` (String) Cluster description
- `private_connection_info` (Attributes) Private connection info (see [below for nested schema](#nestedatt--private_connection_info))
- `region_id` (String) Region where the cluster is located
@@ -49,6 +50,16 @@ Read-Only:
- `user` (String) ClickHouse user
+
+### Nested Schema for `custom_certificate`
+
+Read-Only:
+
+- `certificate` (String) Public certificate
+- `key` (String) Private certificate key
+- `root_ca` (String) Root certificate
+
+
### Nested Schema for `private_connection_info`
diff --git a/docs/resources/clickhouse_cluster.md b/docs/resources/clickhouse_cluster.md
index 54a147b..d30bb2f 100644
--- a/docs/resources/clickhouse_cluster.md
+++ b/docs/resources/clickhouse_cluster.md
@@ -60,6 +60,7 @@ resource "doublecloud_clickhouse_cluster" "example-clickhouse" {
- `access` (Block, Optional) Access control configuration (see [below for nested schema](#nestedblock--access))
- `config` (Block, Optional) (see [below for nested schema](#nestedblock--config))
+- `custom_certificate` (Block, Optional) Custom TLS certificate (see [below for nested schema](#nestedblock--custom_certificate))
- `description` (String) Cluster description
- `id` (String) Cluster ID
- `resources` (Block, Optional) Cluster resources (see [below for nested schema](#nestedblock--resources))
@@ -179,6 +180,16 @@ Optional:
+
+### Nested Schema for `custom_certificate`
+
+Optional:
+
+- `certificate` (String) Public certificate
+- `key` (String) Private certificate key
+- `root_ca` (String) Root certificate
+
+
### Nested Schema for `resources`
diff --git a/go.mod b/go.mod
index 8980a16..9100567 100644
--- a/go.mod
+++ b/go.mod
@@ -35,6 +35,7 @@ require (
github.com/cloudflare/circl v1.3.7 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.16.0 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/hashicorp/cli v1.1.6 // indirect
diff --git a/go.sum b/go.sum
index ec6cd62..c2e808b 100644
--- a/go.sum
+++ b/go.sum
@@ -65,6 +65,8 @@ github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZt
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
@@ -146,6 +148,8 @@ github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgf
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
@@ -214,6 +218,8 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA=
github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
@@ -224,17 +230,23 @@ github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgr
go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw=
go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
@@ -242,12 +254,16 @@ golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -273,11 +289,15 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
diff --git a/internal/provider/clickhouse_cluster_resource.go b/internal/provider/clickhouse_cluster_resource.go
index 2be3f29..a580ca2 100644
--- a/internal/provider/clickhouse_cluster_resource.go
+++ b/internal/provider/clickhouse_cluster_resource.go
@@ -6,6 +6,7 @@ import (
"strings"
"time"
+ "github.com/golang/protobuf/ptypes/wrappers"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
@@ -49,6 +50,8 @@ type clickhouseClusterModel struct {
// TODO: support mw
// https://github.com/doublecloud/api/blob/main/doublecloud/v1/maintenance.proto
// MaintenanceWindow *maintenanceWindow `tfsdk:"maintenance_window"`
+
+ CustomCertificate types.Object `tfsdk:"custom_certificate"`
}
type clickhouseClusterResources struct {
@@ -350,6 +353,26 @@ func clickhouseConenctionInfoSchema() map[string]schema.Attribute {
}
}
+func clickhouseCustomCertificateSchema() map[string]schema.Attribute {
+ return map[string]schema.Attribute{
+ "certificate": schema.StringAttribute{
+ Optional: true,
+ MarkdownDescription: "Public certificate",
+ PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
+ },
+ "key": schema.StringAttribute{
+ Optional: true,
+ MarkdownDescription: "Private certificate key",
+ PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
+ },
+ "root_ca": schema.StringAttribute{
+ Optional: true,
+ MarkdownDescription: "Root certificate",
+ PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
+ },
+ }
+}
+
func (r *ClickhouseClusterResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
// This description is used by the documentation generator and the language server.
@@ -493,6 +516,12 @@ func (r *ClickhouseClusterResource) Schema(ctx context.Context, req resource.Sch
"access": AccessSchemaBlock(),
"config": clickhouseConfigSchemaBlock(),
// maintenance window
+ "custom_certificate": schema.SingleNestedBlock{
+ Attributes: clickhouseCustomCertificateSchema(),
+ PlanModifiers: []planmodifier.Object{objectplanmodifier.UseStateForUnknown()},
+ MarkdownDescription: "Custom TLS certificate",
+ Validators: []validator.Object{&clickhouseCustomCertificateValidator{}},
+ },
},
}
}
@@ -643,6 +672,21 @@ 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())}
+ }
+ }
+
return rq, diags
}
@@ -734,6 +778,12 @@ func (m *clickhouseClusterModel) parse(rs *clickhouse.Cluster) diag.Diagnostics
diags.Append(m.Access.parse(access)...)
}
+ oldKey := ""
+ if key, ok := m.CustomCertificate.Attributes()["key"]; ok {
+ oldKey = key.String()
+ }
+ m.CustomCertificate = parseClickhouseCustomCertificate(rs.GetCustomCertificate(), oldKey).convert(diags)
+
// parse MW
return diags
}
diff --git a/internal/provider/clickhouse_cluster_resource_test.go b/internal/provider/clickhouse_cluster_resource_test.go
index 5ef6237..1ea993a 100644
--- a/internal/provider/clickhouse_cluster_resource_test.go
+++ b/internal/provider/clickhouse_cluster_resource_test.go
@@ -10,6 +10,7 @@ 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"
)
@@ -17,6 +18,40 @@ import (
var (
testAccClickhouseName string = fmt.Sprintf("%v-clickhouse", testPrefix)
testAccClickhouseId string = fmt.Sprintf("doublecloud_clickhouse_cluster.%v", testAccClickhouseName)
+
+ testAccClickhouseTLSCert string = `
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcKT/wmDt+qLwEVOfU0UbJO5f77+0
+nuYermx15MOZh4jg4H/r98b/tD2dNxdLAW/VJ4VTF3vD0AGY2+xN7J8aTA==
+-----END PUBLIC KEY-----
+`
+
+ testAccClickhouseTLSKey string = `
+-----BEGIN CERTIFICATE-----
+MIICoTCCAkegAwIBAgIUWdVSBHIWp+w6Gtmt4Ps+RNgky00wCgYIKoZIzj0EAwIw
+gacxCzAJBgNVBAYTAkRFMRIwEAYDVQQIDAlGcmFua2Z1cnQxEjAQBgNVBAcMCUZy
+YW5rZnVydDEVMBMGA1UECgwMZG91YmxlLmNsb3VkMSAwHgYDVQQLDBdUZXJyYWZv
+cm0gcHJvdmlkZXIgdGVzdDEVMBMGA1UEAwwMZG91YmxlLmNsb3VkMSAwHgYJKoZI
+hvcNAQkBFhFpbmZvQGRvdWJsZS5jbG91ZDAeFw0yNDA5MTkxNjE5MDNaFw0yNTA5
+MTkxNjE5MDNaMIG0MQswCQYDVQQGEwJERTESMBAGA1UECAwJRnJhbmtmdXJ0MRIw
+EAYDVQQHDAlGcmFua2Z1cnQxFTATBgNVBAoMDGRvdWJsZS5jbG91ZDElMCMGA1UE
+CwwcVGVycmFmb3JtIHByb3ZpZGVyIHRlc3QgaW1wbDEdMBsGA1UEAwwUdGVzdC5h
+dC5kb3VibGUuY2xvdWQxIDAeBgkqhkiG9w0BCQEWEWluZm9AZG91YmxlLmNsb3Vk
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcKT/wmDt+qLwEVOfU0UbJO5f77+0
+nuYermx15MOZh4jg4H/r98b/tD2dNxdLAW/VJ4VTF3vD0AGY2+xN7J8aTKNCMEAw
+HQYDVR0OBBYEFElk8x4Sw1IYKahZDqAKrbPrMQvaMB8GA1UdIwQYMBaAFC/+xZgT
+4U3lxhcG2wdT5/NlGB7cMAoGCCqGSM49BAMCA0gAMEUCIBWS0StXMJCfOHU6UqKK
+PB+UYxG5mwIw4IP/T7sLa3XlAiEAyS8vLtbgrh8mLXwacAe/SFRS3L/DhOJQa+0e
+VQBbsVs=
+-----END CERTIFICATE-----
+`
+
+ testAccClickhouseTLSRootCA string = `
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2fZnlTyuGtgATXh0FmgvgsqTI/aB
+Wy2sRShP40UqdTQ4pxLkpkskb7RWssyrXZEiieGSIUY33setFOOMV6b4RA==
+-----END PUBLIC KEY-----
+`
)
func TestAccClickhouseClusterResource(t *testing.T) {
@@ -27,6 +62,7 @@ func TestAccClickhouseClusterResource(t *testing.T) {
RegionId: types.StringValue("eu-central-1"),
CloudType: types.StringValue("aws"),
NetworkId: types.StringValue(testNetworkId),
+ Version: types.StringValue("24.8"),
Resources: &clickhouseClusterResources{
Clickhouse: &clickhouseClusterResourcesClickhouse{
ResourcePresetId: types.StringValue("g2-c2-m8"),
@@ -84,6 +120,20 @@ 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),
+ },
+ )
+ m4.CustomCertificate = cc
+
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
@@ -140,6 +190,16 @@ func TestAccClickhouseClusterResource(t *testing.T) {
resource.TestCheckResourceAttr(testAccClickhouseId, "resources.clickhouse.max_disk_size", "68719476736"),
),
},
+ // Check custom TLS certificate
+ {
+ Config: convertClickHouseModelToHCL(&m4),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckNoResourceAttr(testAccClickhouseId, "resources.clickhouse.resource_preset_id"),
+ resource.TestCheckResourceAttr(testAccClickhouseId, "resources.clickhouse.custom_certificate.certificate", testAccClickhouseTLSCert),
+ resource.TestCheckResourceAttr(testAccClickhouseId, "resources.clickhouse.custom_certificate.key", testAccClickhouseTLSKey),
+ resource.TestCheckResourceAttr(testAccClickhouseId, "resources.clickhouse.custom_certificate.root_ca", testAccClickhouseTLSRootCA),
+ ),
+ },
// Delete testing automatically occurs in TestCase
},
})
@@ -213,6 +273,8 @@ resource "doublecloud_clickhouse_cluster" "tf-acc-clickhouse" {
region_id = "{{ .RegionId.ValueString }}"
cloud_type = "{{ .CloudType.ValueString }}"
network_id = "{{ .NetworkId.ValueString }}"
+ {{- if not .Version.IsNull }}
+ version = "{{ .Version.ValueString }}"{{end}}
resources {
clickhouse {
@@ -277,6 +339,13 @@ resource "doublecloud_clickhouse_cluster" "tf-acc-clickhouse" {
]
{{- end}}
}
+ {{- if not .CustomCertificate.IsNull }}
+ custom_certificate {
+ certificate = "{{ .CustomCertificate.Attributes.certificate }}"
+ key = "{{ .CustomCertificate.Attributes.key }}"
+ root_ca = "{{ .CustomCertificate.Attributes.root_ca }}"
+ }
+ {{- end}}
}`
var clickhouseHCLTemplate *template.Template
diff --git a/internal/provider/clickhouse_data_source.go b/internal/provider/clickhouse_data_source.go
index 756d670..b0645ea 100644
--- a/internal/provider/clickhouse_data_source.go
+++ b/internal/provider/clickhouse_data_source.go
@@ -3,8 +3,11 @@ package provider
import (
"context"
"fmt"
+ "strings"
+
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/doublecloud/go-genproto/doublecloud/clickhouse/v1"
dcsdk "github.com/doublecloud/go-sdk"
@@ -28,15 +31,16 @@ type ClickhouseDataSource struct {
}
type ClickhouseDataSourceModel struct {
- Id types.String `tfsdk:"id"`
- ProjectID types.String `tfsdk:"project_id"`
- Name types.String `tfsdk:"name"`
- Description types.String `tfsdk:"description"`
- RegionID types.String `tfsdk:"region_id"`
- CloudType types.String `tfsdk:"cloud_type"`
- Version types.String `tfsdk:"version"`
- ConnectionInfo *ClickhouseConnectionInfo `tfsdk:"connection_info"`
- PrivateConnectionInfo *ClickhouseConnectionInfo `tfsdk:"private_connection_info"`
+ Id types.String `tfsdk:"id"`
+ ProjectID types.String `tfsdk:"project_id"`
+ Name types.String `tfsdk:"name"`
+ Description types.String `tfsdk:"description"`
+ RegionID types.String `tfsdk:"region_id"`
+ CloudType types.String `tfsdk:"cloud_type"`
+ Version types.String `tfsdk:"version"`
+ ConnectionInfo *ClickhouseConnectionInfo `tfsdk:"connection_info"`
+ PrivateConnectionInfo *ClickhouseConnectionInfo `tfsdk:"private_connection_info"`
+ CustomCertificate *ClickhouseCustomCertificate `tfsdk:"custom_certificate"`
}
type ClickhouseConnectionInfo struct {
@@ -79,6 +83,32 @@ func (ci ClickhouseConnectionInfo) convert(diags diag.Diagnostics) types.Object
return res
}
+type ClickhouseCustomCertificate struct {
+ Certificate types.String `tfsdk:"certificate"`
+ Key types.String `tfsdk:"key"`
+ 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,
+ }
+ if cc == nil {
+ return types.ObjectNull(attrTypeMap)
+ }
+ res, d := types.ObjectValue(attrTypeMap,
+ map[string]attr.Value{
+ "certificate": cc.Certificate,
+ "key": cc.Key,
+ "root_ca": cc.RootCA,
+ },
+ )
+ diags.Append(d...)
+ return res
+}
+
func (d *ClickhouseDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_clickhouse"
}
@@ -86,6 +116,8 @@ func (d *ClickhouseDataSource) Metadata(ctx context.Context, req datasource.Meta
func (d *ClickhouseDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
connInfo := make(map[string]schema.Attribute)
resp.Diagnostics.Append(convertSchemaAttributes(clickhouseConenctionInfoSchema(), connInfo)...)
+ customCertificate := make(map[string]schema.Attribute)
+ resp.Diagnostics.Append(convertSchemaAttributes(clickhouseCustomCertificateSchema(), customCertificate)...)
resp.Schema = schema.Schema{
MarkdownDescription: "Clickhouse data source",
Attributes: map[string]schema.Attribute{
@@ -129,6 +161,11 @@ func (d *ClickhouseDataSource) Schema(ctx context.Context, req datasource.Schema
Attributes: connInfo,
MarkdownDescription: "Private connection info",
},
+ "custom_certificate": schema.SingleNestedAttribute{
+ Computed: true,
+ Attributes: customCertificate,
+ MarkdownDescription: "Custom TLS certificate",
+ },
},
}
}
@@ -187,6 +224,35 @@ func parseClickhousePrivateConnectionInfo(r *clickhouse.PrivateConnectionInfo) *
return c
}
+func parseClickhouseCustomCertificate(r *clickhouse.CustomCertificate, oldKey string) *ClickhouseCustomCertificate {
+ if r == nil {
+ return nil
+ }
+
+ if !r.GetEnabled() {
+ return nil
+ }
+
+ certRaw := string(r.Certificate.GetValue()[:])
+ if len(certRaw) == 0 {
+ return nil
+ }
+
+ certificate := types.StringValue(certRaw)
+ key := basetypes.NewStringValue(strings.Replace(strings.Replace(oldKey, "\\n", "\n", -1), "\"", "", -1))
+ rootCa := types.StringNull()
+ rootRaw := string(r.RootCa.GetValue()[:])
+ if len(rootRaw) > 0 {
+ rootCa = types.StringValue(rootRaw)
+ }
+ c := &ClickhouseCustomCertificate{
+ Certificate: certificate,
+ Key: key,
+ RootCA: rootCa,
+ }
+ return c
+}
+
func (d *ClickhouseDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data ClickhouseDataSourceModel
@@ -235,6 +301,11 @@ func (d *ClickhouseDataSource) Read(ctx context.Context, req datasource.ReadRequ
data.Version = types.StringValue(response.Version)
data.ConnectionInfo = parseClickhouseConnectionInfo(response.ConnectionInfo)
data.PrivateConnectionInfo = parseClickhousePrivateConnectionInfo(response.PrivateConnectionInfo)
+ oldKey := ""
+ if data.CustomCertificate != nil {
+ oldKey = data.CustomCertificate.Key.String()
+ }
+ data.CustomCertificate = parseClickhouseCustomCertificate(response.CustomCertificate, oldKey)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
diff --git a/internal/provider/helpers.go b/internal/provider/helpers.go
index 522e9c2..f4bd77d 100644
--- a/internal/provider/helpers.go
+++ b/internal/provider/helpers.go
@@ -30,6 +30,8 @@ func convertSchemaAttributes(resAttrs map[string]resourceschema.Attribute, dataA
dataAttrs[name] = convertInt64Attribute(attr)
case resourceschema.SingleNestedAttribute:
dataAttrs[name] = convertSingleNestedAttribute(attr, diags)
+ case resourceschema.BoolAttribute:
+ dataAttrs[name] = convertBoolAttribute(attr)
default:
diags.AddError("can not convert resource attribute to datasource attribute", fmt.Sprintf("unsupported type for attribute %q: %v", name, attr))
}
@@ -80,6 +82,16 @@ func convertSingleNestedAttribute(attr resourceschema.SingleNestedAttribute, dia
}
}
+func convertBoolAttribute(attr resourceschema.BoolAttribute) *dataschema.BoolAttribute {
+ return &dataschema.BoolAttribute{
+ Computed: true,
+ Sensitive: attr.Sensitive,
+ Description: attr.Description,
+ MarkdownDescription: attr.MarkdownDescription,
+ DeprecationMessage: attr.DeprecationMessage,
+ }
+}
+
type suppressAutoscaledDiskDiff struct{}
var _ planmodifier.Int64 = &suppressAutoscaledDiskDiff{}
@@ -126,6 +138,42 @@ 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 {