diff --git a/GNUmakefile b/GNUmakefile index 7771cd69..42544ae5 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -4,3 +4,8 @@ default: testacc .PHONY: testacc testacc: TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m + +# example) +# $ TROCCO_TEST_URL=https://localhost:4000 \ +# TROCCO_API_KEY=**** \ +# make testacc TESTARGS="-run TestAccConnectionResource" diff --git a/docs/resources/connection.md b/docs/resources/connection.md index 45a216ed..d9cc6af7 100644 --- a/docs/resources/connection.md +++ b/docs/resources/connection.md @@ -198,7 +198,10 @@ resource "trocco_connection" "s3_with_assume_role" { - `aws_auth_type` (String) S3: The authentication type for the S3 connection. It must be one of `iam_user` or `assume_role`. - `aws_iam_user` (Attributes) S3: IAM User configuration. (see [below for nested schema](#nestedatt--aws_iam_user)) - `description` (String) The description of the connection. -- `driver` (String) PostgreSQL: The name of a PostgreSQL driver. +- `driver` (String) Snowflake, MySQL, PostgreSQL: The name of a Database driver. + - MySQL: null, mysql_connector_java_5_1_49 + - Snowflake: null, snowflake_jdbc_3_14_2, snowflake_jdbc_3_17_0, + - PostgreSQL: postgresql_42_5_1, postgresql_9_4_1205_jdbc41 - `gateway` (Attributes) MySQL, PostgreSQL: Whether to connect via SSH (see [below for nested schema](#nestedatt--gateway)) - `host` (String) Snowflake, PostgreSQL: The host of a (Snowflake, PostgreSQL) account. - `password` (String, Sensitive) Snowflake, PostgreSQL: The password for the (Snowflake, PostgreSQL) user. diff --git a/internal/client/connection.go b/internal/client/connection.go index 62defafb..85a78227 100644 --- a/internal/client/connection.go +++ b/internal/client/connection.go @@ -109,7 +109,7 @@ type CreateConnectionInput struct { SSLClientCa *string `json:"ssl_client_ca,omitempty"` SSLClientKey *string `json:"ssl_client_key,omitempty"` SSLMode *parameter.NullableString `json:"ssl_mode,omitempty"` - Driver *string `json:"driver,omitempty"` + Driver *parameter.NullableString `json:"driver,omitempty"` } type UpdateConnectionInput struct { @@ -163,7 +163,7 @@ type UpdateConnectionInput struct { SSLClientCa *string `json:"ssl_client_ca,omitempty"` SSLClientKey *string `json:"ssl_client_key,omitempty"` SSLMode *parameter.NullableString `json:"ssl_mode,omitempty"` - Driver *string `json:"driver,omitempty"` + Driver *parameter.NullableString `json:"driver,omitempty"` } func (c *TroccoClient) GetConnections(connectionType string, in *GetConnectionsInput) (*ConnectionList, error) { diff --git a/internal/provider/connection_resource.go b/internal/provider/connection_resource.go index 26028e0b..8cfc35ec 100644 --- a/internal/provider/connection_resource.go +++ b/internal/provider/connection_resource.go @@ -104,7 +104,7 @@ func (m *connectionResourceModel) ToCreateConnectionInput() *client.CreateConnec AWSAuthType: m.AWSAuthType.ValueStringPointer(), // PostgreSQL Fields - Driver: m.Driver.ValueStringPointer(), + Driver: model.NewNullableString(m.Driver), } // SSL Fields @@ -182,7 +182,7 @@ func (m *connectionResourceModel) ToUpdateConnectionInput() *client.UpdateConnec AWSAuthType: m.AWSAuthType.ValueStringPointer(), // PostgreSQL Fields - Driver: m.Driver.ValueStringPointer(), + Driver: model.NewNullableString(m.Driver), } // SSL Fields @@ -569,10 +569,23 @@ func (r *connectionResource) Schema( // PostgreSQL Fields "driver": schema.StringAttribute{ - MarkdownDescription: "PostgreSQL: The name of a PostgreSQL driver.", - Optional: true, + MarkdownDescription: `Snowflake, MySQL, PostgreSQL: The name of a Database driver. + - MySQL: null, mysql_connector_java_5_1_49 + - Snowflake: null, snowflake_jdbc_3_14_2, snowflake_jdbc_3_17_0, + - PostgreSQL: postgresql_42_5_1, postgresql_9_4_1205_jdbc41 +`, + Optional: true, Validators: []validator.String{ - stringvalidator.OneOf("postgresql_42_5_1", "postgresql_9_4_1205_jdbc41"), + stringvalidator.OneOf( + // MySQL + "mysql_connector_java_5_1_49", + // Snowflake + "snowflake_jdbc_3_14_2", + "snowflake_jdbc_3_17_0", + // PostgreSQL + "postgresql_42_5_1", + "postgresql_9_4_1205_jdbc41", + ), }, }, }, @@ -885,6 +898,7 @@ func (r *connectionResource) ValidateConfig( if plan.AuthMethod.ValueString() == "user_password" { validateRequiredString(plan.Password, "password", "Snowflake", resp) } + validateStringAgainstPatterns(plan.Driver, "driver", "Snowflake", resp, "snowflake_jdbc_3_14_2", "snowflake_jdbc_3_17_0") case "gcs": validateRequiredString(plan.ApplicationName, "application_name", "GCS", resp) validateRequiredString(plan.ServiceAccountEmail, "service_account_email", "GCS", resp) @@ -896,6 +910,7 @@ func (r *connectionResource) ValidateConfig( validateRequiredInt(plan.Port, "port", "MySQL", resp) validateRequiredString(plan.UserName, "user_name", "MySQL", resp) validateRequiredString(plan.Password, "password", "MySQL", resp) + validateStringAgainstPatterns(plan.Driver, "driver", "MySQL", resp, "mysql_connector_java_5_1_49") if plan.Gateway != nil { validateRequiredString(plan.Gateway.Host, "gateway.host", "MySQL", resp) validateRequiredInt(plan.Gateway.Port, "gateway.port", "MySQL", resp) @@ -948,6 +963,7 @@ func (r *connectionResource) ValidateConfig( validateRequiredInt(plan.Port, "port", "PostgreSQL", resp) validateRequiredString(plan.UserName, "user_name", "PostgreSQL", resp) validateRequiredString(plan.Driver, "driver", "PostgreSQL", resp) + validateStringAgainstPatterns(plan.Driver, "driver", "PostgreSQL", resp, "postgresql_42_5_1", "postgresql_9_4_1205_jdbc41") if plan.Gateway != nil { validateRequiredString(plan.Gateway.Host, "gateway.host", "PostgreSQL", resp) validateRequiredInt(plan.Gateway.Port, "gateway.port", "PostgreSQL", resp) @@ -956,6 +972,28 @@ func (r *connectionResource) ValidateConfig( } } +func validateStringAgainstPatterns(field types.String, fieldName, connectionType string, resp *resource.ValidateConfigResponse, patterns ...string) { + if field.IsNull() { + return + } + + for _, pattern := range patterns { + if field.ValueString() == pattern { + return + } + } + + resp.Diagnostics.AddError( + fieldName, + fmt.Sprintf("%s: `%s` is invalid for %s connection. Valid values are: %s", + fieldName, + field.ValueString(), + connectionType, + strings.Join(patterns, ", "), + ), + ) +} + func validateRequiredString(field types.String, fieldName, connectionType string, resp *resource.ValidateConfigResponse) { if field.IsNull() { resp.Diagnostics.AddError( diff --git a/internal/provider/connection_resource_test.go b/internal/provider/connection_resource_test.go index 49b0d205..32d539bc 100644 --- a/internal/provider/connection_resource_test.go +++ b/internal/provider/connection_resource_test.go @@ -2,6 +2,7 @@ package provider import ( "fmt" + "regexp" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -19,6 +20,7 @@ func TestAccConnectionResource(t *testing.T) { name = "test" description = "The quick brown fox jumps over the lazy dog." + project_id = "test" service_account_json_key = "{\"type\":\"service_account\",\"project_id\":\"\",\"private_key_id\":\"\",\"private_key\":\"\"}" } @@ -42,6 +44,127 @@ func TestAccConnectionResource(t *testing.T) { return fmt.Sprintf("bigquery,%s", connectionID), nil }, }, + // Snowflake + { + Config: providerConfig + ` + resource "trocco_connection" "snowflake_test" { + connection_type = "snowflake" + auth_method = "user_password" + + name = "snowflake test" + host = "example.snowflakecomputing.com" + user_name = "root" + password = "password" + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("trocco_connection.snowflake_test", "connection_type", "snowflake"), + resource.TestCheckResourceAttr("trocco_connection.snowflake_test", "name", "snowflake test"), + resource.TestCheckResourceAttrSet("trocco_connection.snowflake_test", "id"), + ), + }, + // MySQL + { + Config: providerConfig + ` + resource "trocco_connection" "mysql_test" { + connection_type = "mysql" + + name = "mysql test" + host = "localhost" + user_name = "root" + password = "password" + port = 3306 + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("trocco_connection.mysql_test", "connection_type", "mysql"), + resource.TestCheckResourceAttr("trocco_connection.mysql_test", "name", "mysql test"), + resource.TestCheckResourceAttrSet("trocco_connection.mysql_test", "id"), + ), + }, + // PostgreSQL + { + Config: providerConfig + ` + resource "trocco_connection" "postgresql_test" { + connection_type = "postgresql" + name = "postgresql test" + host = "localhost" + user_name = "root" + password = "password" + port = 5432 + driver = "postgresql_42_5_1" + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("trocco_connection.postgresql_test", "connection_type", "postgresql"), + resource.TestCheckResourceAttrSet("trocco_connection.postgresql_test", "id"), + ), + }, + }, + }) +} + +func TestInvalidDriver(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: providerConfig + ` + resource "trocco_connection" "invalid_driver_test" { + connection_type = "postgresql" + name = "invalid driver test" + host = "localhost" + user_name = "root" + password = "password" + port = 5432 + driver = "invalid_driver" + } + `, + ExpectError: regexp.MustCompile("driver: `invalid_driver` is invalid for PostgreSQL connection. "), + }, + { + Config: providerConfig + ` + resource "trocco_connection" "mismatch_driver_test_postgresql" { + connection_type = "postgresql" + name = "invalid driver test" + host = "localhost" + user_name = "root" + password = "password" + port = 5432 + driver = "mysql_connector_java_5_1_49" + } + `, + ExpectError: regexp.MustCompile("are: postgresql_42_5_1, postgresql_9_4_1205_jdbc41"), + }, + { + Config: providerConfig + ` + resource "trocco_connection" "mismatch_driver_test_mysql" { + connection_type = "mysql" + name = "invalid driver test" + host = "localhost" + user_name = "root" + password = "password" + port = 3306 + driver = "snowflake_jdbc_3_14_2" + } + `, + ExpectError: regexp.MustCompile("are: mysql_connector_java_5_1_49"), + }, + { + Config: providerConfig + ` + resource "trocco_connection" "mismatch_driver_test_snowflake" { + connection_type = "snowflake" + name = "invalid driver test" + + auth_method = "user_password" + host = "example.snowflakecomputing.com" + user_name = "root" + password = "password" + driver = "mysql_connector_java_5_1_49" + } + `, + ExpectError: regexp.MustCompile("are: snowflake_jdbc_3_14_2, snowflake_jdbc_3_17_0"), + }, }, }) }