Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,7 @@ override.tf.json
terraform.rc

# Ignore docs overview
docs/assets/overview.webp
docs/assets/overview.webp

# Ignore compiled binary
terraform-provider-bytebase
5 changes: 5 additions & 0 deletions provider/data_source_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ func dataSourceInstance() *schema.Resource {
Description: "The connection user password used by Bytebase to perform DDL and DML operations.",
},
"external_secret": getExternalSecretSchema(),
"use_ssl": {
Type: schema.TypeBool,
Computed: true,
Description: "Enable SSL connection. Required to use SSL certificates.",
},
"ssl_ca": {
Type: schema.TypeString,
Computed: true,
Expand Down
5 changes: 5 additions & 0 deletions provider/data_source_instance_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ func dataSourceInstanceList() *schema.Resource {
Description: "The connection user password used by Bytebase to perform DDL and DML operations.",
},
"external_secret": getExternalSecretSchema(),
"use_ssl": {
Type: schema.TypeBool,
Computed: true,
Description: "Enable SSL connection. Required to use SSL certificates.",
},
"ssl_ca": {
Type: schema.TypeString,
Computed: true,
Expand Down
113 changes: 92 additions & 21 deletions provider/resource_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,12 @@ func resourceInstance() *schema.Resource {
Description: "The connection user name used by Bytebase to perform DDL and DML operations.",
},
"password": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
Default: "",
Description: "The connection user password used by Bytebase to perform DDL and DML operations.",
Type: schema.TypeString,
Optional: true,
Sensitive: true,
Computed: true,
DiffSuppressFunc: suppressSensitiveFieldDiff,
Description: "The connection user password used by Bytebase to perform DDL and DML operations.",
},
"external_secret": {
Type: schema.TypeList,
Expand Down Expand Up @@ -234,26 +235,35 @@ func resourceInstance() *schema.Resource {
},
},
},
"ssl_ca": {
Type: schema.TypeString,
"use_ssl": {
Type: schema.TypeBool,
Optional: true,
Default: "",
Sensitive: true,
Description: "The CA certificate. Optional, you can set this if the engine type is MYSQL, POSTGRES, TIDB or CLICKHOUSE.",
Default: false,
Description: "Enable SSL connection. Required to use SSL certificates.",
},
"ssl_ca": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
Computed: true,
DiffSuppressFunc: suppressSensitiveFieldDiff,
Description: "The CA certificate. Optional, you can set this if the engine type is MYSQL, POSTGRES, TIDB or CLICKHOUSE.",
},
"ssl_cert": {
Type: schema.TypeString,
Optional: true,
Default: "",
Sensitive: true,
Description: "The client certificate. Optional, you can set this if the engine type is MYSQL, POSTGRES, TIDB or CLICKHOUSE.",
Type: schema.TypeString,
Optional: true,
Sensitive: true,
Computed: true,
DiffSuppressFunc: suppressSensitiveFieldDiff,
Description: "The client certificate. Optional, you can set this if the engine type is MYSQL, POSTGRES, TIDB or CLICKHOUSE.",
},
"ssl_key": {
Type: schema.TypeString,
Optional: true,
Default: "",
Sensitive: true,
Description: "The client key. Optional, you can set this if the engine type is MYSQL, POSTGRES, TIDB or CLICKHOUSE.",
Type: schema.TypeString,
Optional: true,
Sensitive: true,
Computed: true,
DiffSuppressFunc: suppressSensitiveFieldDiff,
Description: "The client key. Optional, you can set this if the engine type is MYSQL, POSTGRES, TIDB or CLICKHOUSE.",
},
"host": {
Type: schema.TypeString,
Expand Down Expand Up @@ -285,9 +295,47 @@ func resourceInstance() *schema.Resource {
},
"databases": getDatabasesSchema(true),
},
CustomizeDiff: validateSSLFieldsCustomDiff,
}
}

// suppressSensitiveFieldDiff suppresses diffs for write-only sensitive fields.
func suppressSensitiveFieldDiff(_ string, oldValue, newValue string, _ *schema.ResourceData) bool {
// If the field was previously set (exists in state) and the new value is empty,
// suppress the diff because the API doesn't return these fields
if oldValue != "" && newValue == "" {
return true
}
// If both are equal, suppress the diff
return oldValue == newValue
}

// validateSSLFieldsCustomDiff ensures SSL cert and key are both provided or both empty.
func validateSSLFieldsCustomDiff(_ context.Context, d *schema.ResourceDiff, _ interface{}) error {
dataSources := d.Get("data_sources").(*schema.Set).List()

for i, ds := range dataSources {
dsMap := ds.(map[string]interface{})

sslCert := ""
sslKey := ""

if v, ok := dsMap["ssl_cert"].(string); ok {
sslCert = v
}
if v, ok := dsMap["ssl_key"].(string); ok {
sslKey = v
}

// Validate that both cert and key are provided or both are empty
if (sslCert != "" && sslKey == "") || (sslCert == "" && sslKey != "") {
return errors.Errorf("data_sources.%d: ssl_cert and ssl_key must both be provided or both be empty", i)
}
}

return nil
}

func resourceInstanceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
c := m.(api.Client)

Expand Down Expand Up @@ -656,6 +704,7 @@ func flattenDataSourceList(d *schema.ResourceData, dataSourceList []*v1pb.DataSo
raw["host"] = dataSource.Host
raw["port"] = dataSource.Port
raw["database"] = dataSource.Database
raw["use_ssl"] = dataSource.UseSsl

// These sensitive fields won't returned in the API. Propagate state value.
if ds, ok := oldDataSourceMap[dataSource.Id]; ok {
Expand Down Expand Up @@ -722,7 +771,26 @@ func flattenDataSourceList(d *schema.ResourceData, dataSourceList []*v1pb.DataSo

func dataSourceHash(rawDataSource interface{}) int {
dataSource := rawDataSource.(map[string]interface{})
return internal.ToHashcodeInt(dataSource["id"].(string))
// Include id and SSL-related field presence to detect configuration changes
hashStr := dataSource["id"].(string)

// Include use_ssl in hash to detect SSL enablement changes
if v, ok := dataSource["use_ssl"].(bool); ok {
hashStr = fmt.Sprintf("%s-ssl_%t", hashStr, v)
}

// Include whether SSL certificates are present (not the values themselves)
if v, ok := dataSource["ssl_ca"].(string); ok && v != "" {
hashStr = fmt.Sprintf("%s-ca_present", hashStr)
}
if v, ok := dataSource["ssl_cert"].(string); ok && v != "" {
hashStr = fmt.Sprintf("%s-cert_present", hashStr)
}
if v, ok := dataSource["ssl_key"].(string); ok && v != "" {
hashStr = fmt.Sprintf("%s-key_present", hashStr)
}

return internal.ToHashcodeInt(hashStr)
}

func convertDataSourceCreateList(d *schema.ResourceData, validate bool) ([]*v1pb.DataSource, error) {
Expand Down Expand Up @@ -797,6 +865,9 @@ func convertDataSourceCreateList(d *schema.ResourceData, validate bool) ([]*v1pb
return nil, errors.Errorf("cannot set both password and external_secret")
}

if v, ok := obj["use_ssl"].(bool); ok {
dataSource.UseSsl = v
}
if v, ok := obj["ssl_ca"].(string); ok {
dataSource.SslCa = v
}
Expand Down