All in with PG
This commit is contained in:
14
test/Gopkg.lock
generated
14
test/Gopkg.lock
generated
@@ -24,6 +24,7 @@
|
||||
"logging",
|
||||
"proxy/certs",
|
||||
"proxy/dialers/mysql",
|
||||
"proxy/dialers/postgres",
|
||||
"proxy/proxy",
|
||||
"proxy/util",
|
||||
]
|
||||
@@ -182,6 +183,17 @@
|
||||
pruneopts = ""
|
||||
revision = "c2b33e84"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:29145d7af4adafd72a79df5e41456ac9e232d5a28c1cd4dacf3ff008a217fc10"
|
||||
name = "github.com/lib/pq"
|
||||
packages = [
|
||||
".",
|
||||
"oid",
|
||||
]
|
||||
pruneopts = ""
|
||||
revision = "4ded0e9383f75c197b3a2aaa6d590ac52df6fd79"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411"
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
@@ -413,11 +425,13 @@
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/dialers/mysql",
|
||||
"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/dialers/postgres",
|
||||
"github.com/go-sql-driver/mysql",
|
||||
"github.com/gruntwork-io/terratest/modules/gcp",
|
||||
"github.com/gruntwork-io/terratest/modules/logger",
|
||||
"github.com/gruntwork-io/terratest/modules/terraform",
|
||||
"github.com/gruntwork-io/terratest/modules/test-structure",
|
||||
"github.com/lib/pq",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"github.com/stretchr/testify/require",
|
||||
]
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCloudSQLPostgres(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.Equal(t, "5432", "5432")
|
||||
}
|
||||
@@ -42,7 +42,7 @@ func TestMySqlPrivateIP(t *testing.T) {
|
||||
test_structure.RunTestStage(t, "deploy", func() {
|
||||
region := test_structure.LoadString(t, exampleDir, KEY_REGION)
|
||||
projectId := test_structure.LoadString(t, exampleDir, KEY_PROJECT)
|
||||
terraformOptions := createTerratestOptionsForMySql(projectId, region, exampleDir, NAME_PREFIX_PRIVATE, "", "", 0, "")
|
||||
terraformOptions := createTerratestOptionsForCloudSql(projectId, region, exampleDir, NAME_PREFIX_PRIVATE, "", "", 0, "")
|
||||
test_structure.SaveTerraformOptions(t, exampleDir, terraformOptions)
|
||||
|
||||
terraform.InitAndApply(t, terraformOptions)
|
||||
|
||||
@@ -65,7 +65,7 @@ func TestMySqlPublicIP(t *testing.T) {
|
||||
test_structure.RunTestStage(t, "deploy", func() {
|
||||
region := test_structure.LoadString(t, exampleDir, KEY_REGION)
|
||||
projectId := test_structure.LoadString(t, exampleDir, KEY_PROJECT)
|
||||
terraformOptions := createTerratestOptionsForMySql(projectId, region, exampleDir, NAME_PREFIX_PUBLIC, "", "", 0, "")
|
||||
terraformOptions := createTerratestOptionsForCloudSql(projectId, region, exampleDir, NAME_PREFIX_PUBLIC, "", "", 0, "")
|
||||
test_structure.SaveTerraformOptions(t, exampleDir, terraformOptions)
|
||||
|
||||
terraform.InitAndApply(t, terraformOptions)
|
||||
@@ -119,8 +119,8 @@ func TestMySqlPublicIP(t *testing.T) {
|
||||
}
|
||||
|
||||
// Clean up
|
||||
logger.Logf(t, "Empty table: %s", MYSQL_EMPTY_TEST_TABLE_STATEMENT)
|
||||
if _, err = db.Exec(MYSQL_EMPTY_TEST_TABLE_STATEMENT); err != nil {
|
||||
logger.Logf(t, "Empty table: %s", SQL_EMPTY_TEST_TABLE_STATEMENT)
|
||||
if _, err = db.Exec(SQL_EMPTY_TEST_TABLE_STATEMENT); err != nil {
|
||||
t.Fatalf("Failed to clean up table: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ func TestMySqlReplicas(t *testing.T) {
|
||||
masterZone := test_structure.LoadString(t, exampleDir, KEY_MASTER_ZONE)
|
||||
failoverReplicaZone := test_structure.LoadString(t, exampleDir, KEY_FAILOVER_REPLICA_ZONE)
|
||||
readReplicaZone := test_structure.LoadString(t, exampleDir, KEY_READ_REPLICA_ZONE)
|
||||
terraformOptions := createTerratestOptionsForMySql(projectId, region, exampleDir, NAME_PREFIX_REPLICAS, masterZone, failoverReplicaZone, 1, readReplicaZone)
|
||||
terraformOptions := createTerratestOptionsForCloudSql(projectId, region, exampleDir, NAME_PREFIX_REPLICAS, masterZone, failoverReplicaZone, 1, readReplicaZone)
|
||||
test_structure.SaveTerraformOptions(t, exampleDir, terraformOptions)
|
||||
|
||||
terraform.InitAndApply(t, terraformOptions)
|
||||
@@ -132,8 +132,8 @@ func TestMySqlReplicas(t *testing.T) {
|
||||
}
|
||||
|
||||
// Clean up
|
||||
logger.Logf(t, "Empty table: %s", MYSQL_EMPTY_TEST_TABLE_STATEMENT)
|
||||
if _, err = db.Exec(MYSQL_EMPTY_TEST_TABLE_STATEMENT); err != nil {
|
||||
logger.Logf(t, "Empty table: %s", SQL_EMPTY_TEST_TABLE_STATEMENT)
|
||||
if _, err = db.Exec(SQL_EMPTY_TEST_TABLE_STATEMENT); err != nil {
|
||||
t.Fatalf("Failed to clean up table: %v", err)
|
||||
}
|
||||
|
||||
@@ -190,11 +190,11 @@ func TestMySqlReplicas(t *testing.T) {
|
||||
logger.Logf(t, "Failed to insert data to read replica as expected: %v", err)
|
||||
|
||||
// Prepare statement for reading data
|
||||
stmtOut, err := db.Prepare(MYSQL_QUERY_ROW_COUNT)
|
||||
stmtOut, err := db.Prepare(SQL_QUERY_ROW_COUNT)
|
||||
require.NoError(t, err, "Failed to prepare readonly count statement")
|
||||
|
||||
// Query data, results don't matter...
|
||||
logger.Logf(t, "Query r/o data: %s", MYSQL_QUERY_ROW_COUNT)
|
||||
logger.Logf(t, "Query r/o data: %s", SQL_QUERY_ROW_COUNT)
|
||||
|
||||
var numResults int
|
||||
|
||||
|
||||
73
test/example_postgres_private_ip_test.go
Normal file
73
test/example_postgres_private_ip_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gruntwork-io/terratest/modules/gcp"
|
||||
"github.com/gruntwork-io/terratest/modules/terraform"
|
||||
"github.com/gruntwork-io/terratest/modules/test-structure"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const NAME_PREFIX_POSTGRES_PRIVATE = "postgres-private"
|
||||
const EXAMPLE_NAME_POSTGRES_PRIVATE = "postgres-private-ip"
|
||||
|
||||
func TestPostgresPrivateIP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
//os.Setenv("SKIP_bootstrap", "true")
|
||||
//os.Setenv("SKIP_deploy", "true")
|
||||
//os.Setenv("SKIP_validate_outputs", "true")
|
||||
//os.Setenv("SKIP_teardown", "true")
|
||||
|
||||
_examplesDir := test_structure.CopyTerraformFolderToTemp(t, "../", "examples")
|
||||
exampleDir := filepath.Join(_examplesDir, EXAMPLE_NAME_POSTGRES_PRIVATE)
|
||||
|
||||
test_structure.RunTestStage(t, "bootstrap", func() {
|
||||
projectId := gcp.GetGoogleProjectIDFromEnvVar(t)
|
||||
region := getRandomRegion(t, projectId)
|
||||
|
||||
test_structure.SaveString(t, exampleDir, KEY_REGION, region)
|
||||
test_structure.SaveString(t, exampleDir, KEY_PROJECT, projectId)
|
||||
})
|
||||
|
||||
// At the end of the test, run `terraform destroy` to clean up any resources that were created
|
||||
defer test_structure.RunTestStage(t, "teardown", func() {
|
||||
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
|
||||
terraform.Destroy(t, terraformOptions)
|
||||
})
|
||||
|
||||
test_structure.RunTestStage(t, "deploy", func() {
|
||||
region := test_structure.LoadString(t, exampleDir, KEY_REGION)
|
||||
projectId := test_structure.LoadString(t, exampleDir, KEY_PROJECT)
|
||||
terraformOptions := createTerratestOptionsForCloudSql(projectId, region, exampleDir, NAME_PREFIX_POSTGRES_PRIVATE, "", "", 0, "")
|
||||
test_structure.SaveTerraformOptions(t, exampleDir, terraformOptions)
|
||||
|
||||
terraform.InitAndApply(t, terraformOptions)
|
||||
})
|
||||
|
||||
test_structure.RunTestStage(t, "validate_outputs", func() {
|
||||
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
|
||||
|
||||
region := test_structure.LoadString(t, exampleDir, KEY_REGION)
|
||||
projectId := test_structure.LoadString(t, exampleDir, KEY_PROJECT)
|
||||
|
||||
instanceNameFromOutput := terraform.Output(t, terraformOptions, OUTPUT_MASTER_INSTANCE_NAME)
|
||||
ipAddressesFromOutput := terraform.Output(t, terraformOptions, OUTPUT_MASTER_IP_ADDRESSES)
|
||||
privateIPFromOutput := terraform.Output(t, terraformOptions, OUTPUT_MASTER_PRIVATE_IP)
|
||||
|
||||
assert.Contains(t, ipAddressesFromOutput, "PRIVATE", "IP Addresses output has to contain 'PRIVATE'")
|
||||
assert.Contains(t, ipAddressesFromOutput, privateIPFromOutput, "IP Addresses output has to contain 'private_ip' from output")
|
||||
|
||||
dbNameFromOutput := terraform.Output(t, terraformOptions, OUTPUT_DB_NAME)
|
||||
proxyConnectionFromOutput := terraform.Output(t, terraformOptions, OUTPUT_MASTER_PROXY_CONNECTION)
|
||||
|
||||
expectedDBConn := fmt.Sprintf("%s:%s:%s", projectId, region, instanceNameFromOutput)
|
||||
|
||||
assert.True(t, strings.HasPrefix(instanceNameFromOutput, NAME_PREFIX_POSTGRES_PRIVATE))
|
||||
assert.Equal(t, DB_NAME, dbNameFromOutput)
|
||||
assert.Equal(t, expectedDBConn, proxyConnectionFromOutput)
|
||||
})
|
||||
}
|
||||
249
test/example_postgres_public_ip_test.go
Normal file
249
test/example_postgres_public_ip_test.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/dialers/postgres"
|
||||
"github.com/gruntwork-io/terratest/modules/gcp"
|
||||
"github.com/gruntwork-io/terratest/modules/logger"
|
||||
"github.com/gruntwork-io/terratest/modules/terraform"
|
||||
"github.com/gruntwork-io/terratest/modules/test-structure"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const NAME_PREFIX_POSTGRES_PUBLIC = "postgres-public"
|
||||
const EXAMPLE_NAME_POSTGRES_PUBLIC = "postgres-public-ip"
|
||||
|
||||
func TestPostgresPublicIP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
//os.Setenv("SKIP_bootstrap", "true")
|
||||
//os.Setenv("SKIP_deploy", "true")
|
||||
//os.Setenv("SKIP_validate_outputs", "true")
|
||||
//os.Setenv("SKIP_sql_tests", "true")
|
||||
//os.Setenv("SKIP_proxy_tests", "true")
|
||||
//os.Setenv("SKIP_deploy_cert", "true")
|
||||
//os.Setenv("SKIP_redeploy", "true")
|
||||
//os.Setenv("SKIP_ssl_sql_tests", "true")
|
||||
//os.Setenv("SKIP_teardown_cert", "true")
|
||||
//os.Setenv("SKIP_teardown", "true")
|
||||
|
||||
_examplesDir := test_structure.CopyTerraformFolderToTemp(t, "../", "examples")
|
||||
exampleDir := filepath.Join(_examplesDir, EXAMPLE_NAME_POSTGRES_PUBLIC)
|
||||
certExampleDir := filepath.Join(_examplesDir, EXAMPLE_NAME_CERT)
|
||||
|
||||
// BOOTSTRAP VARIABLES FOR THE TESTS
|
||||
test_structure.RunTestStage(t, "bootstrap", func() {
|
||||
projectId := gcp.GetGoogleProjectIDFromEnvVar(t)
|
||||
region := getRandomRegion(t, projectId)
|
||||
|
||||
test_structure.SaveString(t, exampleDir, KEY_REGION, region)
|
||||
test_structure.SaveString(t, exampleDir, KEY_PROJECT, projectId)
|
||||
})
|
||||
|
||||
// AT THE END OF THE TESTS, RUN `terraform destroy`
|
||||
// TO CLEAN UP ANY RESOURCES THAT WERE CREATED
|
||||
defer test_structure.RunTestStage(t, "teardown", func() {
|
||||
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
|
||||
terraform.Destroy(t, terraformOptions)
|
||||
})
|
||||
|
||||
defer test_structure.RunTestStage(t, "teardown_cert", func() {
|
||||
terraformOptions := test_structure.LoadTerraformOptions(t, certExampleDir)
|
||||
terraform.Destroy(t, terraformOptions)
|
||||
})
|
||||
|
||||
test_structure.RunTestStage(t, "deploy", func() {
|
||||
region := test_structure.LoadString(t, exampleDir, KEY_REGION)
|
||||
projectId := test_structure.LoadString(t, exampleDir, KEY_PROJECT)
|
||||
terraformOptions := createTerratestOptionsForCloudSql(projectId, region, exampleDir, NAME_PREFIX_POSTGRES_PUBLIC, "", "", 0, "")
|
||||
test_structure.SaveTerraformOptions(t, exampleDir, terraformOptions)
|
||||
|
||||
terraform.InitAndApply(t, terraformOptions)
|
||||
})
|
||||
|
||||
// VALIDATE MODULE OUTPUTS
|
||||
test_structure.RunTestStage(t, "validate_outputs", func() {
|
||||
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
|
||||
|
||||
region := test_structure.LoadString(t, exampleDir, KEY_REGION)
|
||||
projectId := test_structure.LoadString(t, exampleDir, KEY_PROJECT)
|
||||
|
||||
instanceNameFromOutput := terraform.Output(t, terraformOptions, OUTPUT_MASTER_INSTANCE_NAME)
|
||||
dbNameFromOutput := terraform.Output(t, terraformOptions, OUTPUT_DB_NAME)
|
||||
proxyConnectionFromOutput := terraform.Output(t, terraformOptions, OUTPUT_MASTER_PROXY_CONNECTION)
|
||||
|
||||
expectedDBConn := fmt.Sprintf("%s:%s:%s", projectId, region, instanceNameFromOutput)
|
||||
|
||||
assert.True(t, strings.HasPrefix(instanceNameFromOutput, NAME_PREFIX_POSTGRES_PUBLIC))
|
||||
assert.Equal(t, DB_NAME, dbNameFromOutput)
|
||||
assert.Equal(t, expectedDBConn, proxyConnectionFromOutput)
|
||||
})
|
||||
|
||||
// TEST REGULAR SQL CLIENT
|
||||
test_structure.RunTestStage(t, "sql_tests", func() {
|
||||
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
|
||||
|
||||
publicIp := terraform.Output(t, terraformOptions, OUTPUT_MASTER_PUBLIC_IP)
|
||||
|
||||
connectionString := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable", DB_USER, DB_PASS, publicIp, DB_NAME)
|
||||
|
||||
// Does not actually open up the connection - just returns a DB ref
|
||||
logger.Logf(t, "Connecting to: %s", publicIp)
|
||||
db, err := sql.Open("postgres", connectionString)
|
||||
require.NoError(t, err, "Failed to open DB connection")
|
||||
|
||||
// Make sure we clean up properly
|
||||
defer db.Close()
|
||||
|
||||
// Run ping to actually test the connection
|
||||
logger.Log(t, "Ping the DB")
|
||||
if err = db.Ping(); err != nil {
|
||||
t.Fatalf("Failed to ping DB: %v", err)
|
||||
}
|
||||
|
||||
// Create table if not exists
|
||||
logger.Logf(t, "Create table: %s", POSTGRES_CREATE_TEST_TABLE_WITH_SERIAL)
|
||||
if _, err = db.Exec(POSTGRES_CREATE_TEST_TABLE_WITH_SERIAL); err != nil {
|
||||
t.Fatalf("Failed to create table: %v", err)
|
||||
}
|
||||
|
||||
// Clean up
|
||||
logger.Logf(t, "Empty table: %s", SQL_EMPTY_TEST_TABLE_STATEMENT)
|
||||
if _, err = db.Exec(SQL_EMPTY_TEST_TABLE_STATEMENT); err != nil {
|
||||
t.Fatalf("Failed to clean up table: %v", err)
|
||||
}
|
||||
|
||||
logger.Logf(t, "Insert data: %s", POSTGRES_INSERT_TEST_ROW)
|
||||
var testid int
|
||||
err = db.QueryRow(POSTGRES_INSERT_TEST_ROW).Scan(&testid)
|
||||
require.NoError(t, err, "Failed to insert data")
|
||||
|
||||
assert.True(t, testid > 0, "Data was inserted")
|
||||
})
|
||||
|
||||
// TEST CLOUD SQL PROXY
|
||||
test_structure.RunTestStage(t, "proxy_tests", func() {
|
||||
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
|
||||
|
||||
proxyConn := terraform.Output(t, terraformOptions, OUTPUT_MASTER_PROXY_CONNECTION)
|
||||
|
||||
logger.Logf(t, "Connecting to: %s via Cloud SQL Proxy", proxyConn)
|
||||
|
||||
// Use the Cloud SQL Proxy for queries
|
||||
// See https://cloud.google.com/sql/docs/mysql/sql-proxy
|
||||
|
||||
// Note that sslmode=disable is required it does not mean that the connection
|
||||
// is unencrypted. All connections via the proxy are completely encrypted.
|
||||
datasourceName := fmt.Sprintf("host=%s user=%s dbname=%s password=%s sslmode=disable", proxyConn, DB_USER, DB_NAME, DB_PASS)
|
||||
db, err := sql.Open("cloudsqlpostgres", datasourceName)
|
||||
|
||||
require.NoError(t, err, "Failed to open Proxy DB connection")
|
||||
|
||||
// Make sure we clean up properly
|
||||
defer db.Close()
|
||||
|
||||
// Run ping to actually test the connection
|
||||
logger.Log(t, "Ping the DB via Proxy")
|
||||
if err = db.Ping(); err != nil {
|
||||
t.Fatalf("Failed to ping DB via Proxy: %v", err)
|
||||
}
|
||||
|
||||
logger.Logf(t, "Insert data via Proxy: %s", POSTGRES_INSERT_TEST_ROW)
|
||||
var testid int
|
||||
err = db.QueryRow(POSTGRES_INSERT_TEST_ROW).Scan(&testid)
|
||||
require.NoError(t, err, "Failed to insert data via Proxy")
|
||||
|
||||
assert.True(t, testid > 0, "Assert data was inserted")
|
||||
})
|
||||
|
||||
// CREATE CLIENT CERT
|
||||
test_structure.RunTestStage(t, "deploy_cert", func() {
|
||||
region := test_structure.LoadString(t, exampleDir, KEY_REGION)
|
||||
projectId := test_structure.LoadString(t, exampleDir, KEY_PROJECT)
|
||||
|
||||
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
|
||||
instanceNameFromOutput := terraform.Output(t, terraformOptions, OUTPUT_MASTER_INSTANCE_NAME)
|
||||
commonName := fmt.Sprintf("%s-client", instanceNameFromOutput)
|
||||
|
||||
terraformOptionsForCert := createTerratestOptionsForClientCert(projectId, region, certExampleDir, commonName, instanceNameFromOutput)
|
||||
test_structure.SaveTerraformOptions(t, certExampleDir, terraformOptionsForCert)
|
||||
|
||||
terraform.InitAndApply(t, terraformOptionsForCert)
|
||||
})
|
||||
|
||||
// REDEPLOY WITH FORCED SSL SETTINGS
|
||||
test_structure.RunTestStage(t, "redeploy", func() {
|
||||
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
|
||||
|
||||
// Force secure connections
|
||||
terraformOptions.Vars["require_ssl"] = true
|
||||
terraform.InitAndApply(t, terraformOptions)
|
||||
})
|
||||
|
||||
// RUN TESTS WITH SECURED CONNECTION
|
||||
test_structure.RunTestStage(t, "ssl_sql_tests", func() {
|
||||
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
|
||||
terraformOptionsForCert := test_structure.LoadTerraformOptions(t, certExampleDir)
|
||||
|
||||
//********************************************************
|
||||
// First test that we're not allowed to connect over insecure connection
|
||||
//********************************************************
|
||||
|
||||
publicIp := terraform.Output(t, terraformOptions, OUTPUT_MASTER_PUBLIC_IP)
|
||||
|
||||
connectionString := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable", DB_USER, DB_PASS, publicIp, DB_NAME)
|
||||
|
||||
// Does not actually open up the connection - just returns a DB ref
|
||||
logger.Logf(t, "Connecting to: %s", publicIp)
|
||||
db, err := sql.Open("postgres",
|
||||
connectionString)
|
||||
require.NoError(t, err, "Failed to open DB connection")
|
||||
|
||||
// Make sure we clean up properly
|
||||
defer db.Close()
|
||||
|
||||
// Run ping to actually test the connection
|
||||
logger.Log(t, "Ping the DB with forced SSL")
|
||||
if err = db.Ping(); err != nil {
|
||||
logger.Logf(t, "Not allowed to ping %s as expected.", publicIp)
|
||||
} else {
|
||||
t.Fatalf("Ping %v succeeded against the odds.", publicIp)
|
||||
}
|
||||
|
||||
//********************************************************
|
||||
// Test connection over secure connection
|
||||
//********************************************************
|
||||
|
||||
// Prepare certificates
|
||||
serverCertB := []byte(terraform.Output(t, terraformOptions, OUTPUT_MASTER_CA_CERT))
|
||||
clientCertB := []byte(terraform.Output(t, terraformOptionsForCert, OUTPUT_CLIENT_CA_CERT))
|
||||
clientPKB := []byte(terraform.Output(t, terraformOptionsForCert, OUTPUT_CLIENT_PRIVATE_KEY))
|
||||
|
||||
serverCertFile := createTempFile(t, serverCertB)
|
||||
defer os.Remove(serverCertFile.Name())
|
||||
|
||||
clientCertFile := createTempFile(t, clientCertB)
|
||||
defer os.Remove(clientCertFile.Name())
|
||||
|
||||
clientPKFile := createTempFile(t, clientPKB)
|
||||
defer os.Remove(clientPKFile.Name())
|
||||
|
||||
// Prepare the secure connection string and ping the DB
|
||||
sslConnectionString := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=require&sslrootcert=%s&sslcert=%s&sslkey=%s", DB_USER, DB_PASS, publicIp, DB_NAME, serverCertFile.Name(), clientCertFile.Name(), clientPKFile.Name())
|
||||
|
||||
db, err = sql.Open("postgres", sslConnectionString)
|
||||
|
||||
// Run ping to actually test the connection with the SSL config
|
||||
logger.Log(t, "Ping the DB with forced SSL")
|
||||
if err = db.Ping(); err != nil {
|
||||
t.Fatalf("Failed to ping DB with forced SSL: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
177
test/example_postgres_replicas_test.go
Normal file
177
test/example_postgres_replicas_test.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gruntwork-io/terratest/modules/gcp"
|
||||
"github.com/gruntwork-io/terratest/modules/logger"
|
||||
"github.com/gruntwork-io/terratest/modules/terraform"
|
||||
"github.com/gruntwork-io/terratest/modules/test-structure"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const NAME_PREFIX_POSTGRES_REPLICAS = "postgres-replicas"
|
||||
const EXAMPLE_NAME_POSTGRES_REPLICAS = "postgres-replicas"
|
||||
|
||||
func TestPostgresReplicas(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
//os.Setenv("SKIP_bootstrap", "true")
|
||||
//os.Setenv("SKIP_deploy", "true")
|
||||
//os.Setenv("SKIP_validate_outputs", "true")
|
||||
//os.Setenv("SKIP_sql_tests", "true")
|
||||
//os.Setenv("SKIP_read_replica_tests", "true")
|
||||
//os.Setenv("SKIP_teardown", "true")
|
||||
|
||||
_examplesDir := test_structure.CopyTerraformFolderToTemp(t, "../", "examples")
|
||||
exampleDir := filepath.Join(_examplesDir, EXAMPLE_NAME_POSTGRES_REPLICAS)
|
||||
|
||||
// BOOTSTRAP VARIABLES FOR THE TESTS
|
||||
test_structure.RunTestStage(t, "bootstrap", func() {
|
||||
projectId := gcp.GetGoogleProjectIDFromEnvVar(t)
|
||||
region := getRandomRegion(t, projectId)
|
||||
|
||||
masterZone, readReplicaZone := getTwoDistinctRandomZonesForRegion(t, projectId, region)
|
||||
|
||||
test_structure.SaveString(t, exampleDir, KEY_REGION, region)
|
||||
test_structure.SaveString(t, exampleDir, KEY_MASTER_ZONE, masterZone)
|
||||
test_structure.SaveString(t, exampleDir, KEY_READ_REPLICA_ZONE, readReplicaZone)
|
||||
test_structure.SaveString(t, exampleDir, KEY_PROJECT, projectId)
|
||||
})
|
||||
|
||||
// AT THE END OF THE TESTS, RUN `terraform destroy`
|
||||
// TO CLEAN UP ANY RESOURCES THAT WERE CREATED
|
||||
defer test_structure.RunTestStage(t, "teardown", func() {
|
||||
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
|
||||
terraform.Destroy(t, terraformOptions)
|
||||
})
|
||||
|
||||
test_structure.RunTestStage(t, "deploy", func() {
|
||||
region := test_structure.LoadString(t, exampleDir, KEY_REGION)
|
||||
projectId := test_structure.LoadString(t, exampleDir, KEY_PROJECT)
|
||||
masterZone := test_structure.LoadString(t, exampleDir, KEY_MASTER_ZONE)
|
||||
readReplicaZone := test_structure.LoadString(t, exampleDir, KEY_READ_REPLICA_ZONE)
|
||||
terraformOptions := createTerratestOptionsForCloudSql(projectId, region, exampleDir, NAME_PREFIX_POSTGRES_REPLICAS, masterZone, "", 1, readReplicaZone)
|
||||
test_structure.SaveTerraformOptions(t, exampleDir, terraformOptions)
|
||||
|
||||
terraform.InitAndApply(t, terraformOptions)
|
||||
})
|
||||
|
||||
// VALIDATE MODULE OUTPUTS
|
||||
test_structure.RunTestStage(t, "validate_outputs", func() {
|
||||
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
|
||||
|
||||
region := test_structure.LoadString(t, exampleDir, KEY_REGION)
|
||||
projectId := test_structure.LoadString(t, exampleDir, KEY_PROJECT)
|
||||
|
||||
instanceNameFromOutput := terraform.Output(t, terraformOptions, OUTPUT_MASTER_INSTANCE_NAME)
|
||||
dbNameFromOutput := terraform.Output(t, terraformOptions, OUTPUT_DB_NAME)
|
||||
proxyConnectionFromOutput := terraform.Output(t, terraformOptions, OUTPUT_MASTER_PROXY_CONNECTION)
|
||||
|
||||
expectedDBConn := fmt.Sprintf("%s:%s:%s", projectId, region, instanceNameFromOutput)
|
||||
|
||||
assert.True(t, strings.HasPrefix(instanceNameFromOutput, NAME_PREFIX_POSTGRES_REPLICAS))
|
||||
assert.Equal(t, DB_NAME, dbNameFromOutput)
|
||||
assert.Equal(t, expectedDBConn, proxyConnectionFromOutput)
|
||||
|
||||
// Read replica outputs
|
||||
readReplicaInstanceNameFromOutputList := terraform.OutputList(t, terraformOptions, OUTPUT_READ_REPLICA_INSTANCE_NAMES)
|
||||
readReplicaProxyConnectionFromOutputList := terraform.OutputList(t, terraformOptions, OUTPUT_READ_REPLICA_PROXY_CONNECTIONS)
|
||||
|
||||
readReplicaInstanceNameFromOutput := readReplicaInstanceNameFromOutputList[0]
|
||||
readReplicaProxyConnectionFromOutput := readReplicaProxyConnectionFromOutputList[0]
|
||||
|
||||
expectedReadReplicaDBConn := fmt.Sprintf("%s:%s:%s", projectId, region, readReplicaInstanceNameFromOutput)
|
||||
|
||||
assert.True(t, strings.HasPrefix(readReplicaInstanceNameFromOutput, NAME_PREFIX_POSTGRES_REPLICAS))
|
||||
assert.Equal(t, expectedReadReplicaDBConn, readReplicaProxyConnectionFromOutput)
|
||||
})
|
||||
|
||||
// TEST REGULAR SQL CLIENT
|
||||
test_structure.RunTestStage(t, "sql_tests", func() {
|
||||
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
|
||||
|
||||
publicIp := terraform.Output(t, terraformOptions, OUTPUT_MASTER_PUBLIC_IP)
|
||||
|
||||
connectionString := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable", DB_USER, DB_PASS, publicIp, DB_NAME)
|
||||
|
||||
// Does not actually open up the connection - just returns a DB ref
|
||||
logger.Logf(t, "Connecting to: %s", publicIp)
|
||||
db, err := sql.Open("postgres", connectionString)
|
||||
require.NoError(t, err, "Failed to open DB connection")
|
||||
|
||||
// Make sure we clean up properly
|
||||
defer db.Close()
|
||||
|
||||
// Run ping to actually test the connection
|
||||
logger.Log(t, "Ping the DB")
|
||||
if err = db.Ping(); err != nil {
|
||||
t.Fatalf("Failed to ping DB: %v", err)
|
||||
}
|
||||
|
||||
// Create table if not exists
|
||||
logger.Logf(t, "Create table: %s", POSTGRES_CREATE_TEST_TABLE_WITH_SERIAL)
|
||||
if _, err = db.Exec(POSTGRES_CREATE_TEST_TABLE_WITH_SERIAL); err != nil {
|
||||
t.Fatalf("Failed to create table: %v", err)
|
||||
}
|
||||
|
||||
// Clean up
|
||||
logger.Logf(t, "Empty table: %s", SQL_EMPTY_TEST_TABLE_STATEMENT)
|
||||
if _, err = db.Exec(SQL_EMPTY_TEST_TABLE_STATEMENT); err != nil {
|
||||
t.Fatalf("Failed to clean up table: %v", err)
|
||||
}
|
||||
|
||||
logger.Logf(t, "Insert data: %s", POSTGRES_INSERT_TEST_ROW)
|
||||
var testid int
|
||||
err = db.QueryRow(POSTGRES_INSERT_TEST_ROW).Scan(&testid)
|
||||
require.NoError(t, err, "Failed to insert data")
|
||||
|
||||
assert.True(t, testid > 0, "Data was inserted")
|
||||
})
|
||||
|
||||
// TEST READ REPLICA WITH REGULAR SQL CLIENT
|
||||
test_structure.RunTestStage(t, "read_replica_tests", func() {
|
||||
terraformOptions := test_structure.LoadTerraformOptions(t, exampleDir)
|
||||
|
||||
readReplicaPublicIpList := terraform.OutputList(t, terraformOptions, OUTPUT_READ_REPLICA_PUBLIC_IPS)
|
||||
readReplicaPublicIp := readReplicaPublicIpList[0]
|
||||
|
||||
connectionString := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable", DB_USER, DB_PASS, readReplicaPublicIp, DB_NAME)
|
||||
|
||||
// Does not actually open up the connection - just returns a DB ref
|
||||
logger.Logf(t, "Connecting to: %s", readReplicaPublicIp)
|
||||
db, err := sql.Open("postgres", connectionString)
|
||||
require.NoError(t, err, "Failed to open DB connection")
|
||||
|
||||
// Make sure we clean up properly
|
||||
defer db.Close()
|
||||
|
||||
// Run ping to actually test the connection
|
||||
logger.Log(t, "Ping the DB")
|
||||
if err = db.Ping(); err != nil {
|
||||
t.Fatalf("Failed to ping DB: %v", err)
|
||||
}
|
||||
|
||||
// Try to insert data to verify we cannot write
|
||||
logger.Logf(t, "Insert data: %s", POSTGRES_INSERT_TEST_ROW)
|
||||
var testid int
|
||||
err = db.QueryRow(POSTGRES_INSERT_TEST_ROW).Scan(&testid)
|
||||
|
||||
// This time we actually expect an error:
|
||||
// 'cannot execute INSERT in a read-only transaction'
|
||||
require.Error(t, err, "Should not be able to write to read replica")
|
||||
logger.Logf(t, "Failed to insert data to read replica as expected: %v", err)
|
||||
|
||||
// Query data, results don't matter...
|
||||
logger.Logf(t, "Query r/o data: %s", SQL_QUERY_ROW_COUNT)
|
||||
rows, err := db.Query(SQL_QUERY_ROW_COUNT)
|
||||
require.NoError(t, err, "Failed to execute query statement on read replica")
|
||||
|
||||
assert.True(t, rows.Next(), "We have a result")
|
||||
})
|
||||
}
|
||||
@@ -3,6 +3,9 @@ package test
|
||||
import (
|
||||
"github.com/gruntwork-io/terratest/modules/gcp"
|
||||
"github.com/gruntwork-io/terratest/modules/terraform"
|
||||
"github.com/stretchr/testify/require"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -16,8 +19,6 @@ const KEY_MASTER_ZONE = "masterZone"
|
||||
const KEY_FAILOVER_REPLICA_ZONE = "failoverReplicaZone"
|
||||
const KEY_READ_REPLICA_ZONE = "readReplicaZone"
|
||||
|
||||
const MYSQL_VERSION = "MYSQL_5_7"
|
||||
|
||||
const OUTPUT_MASTER_IP_ADDRESSES = "master_ip_addresses"
|
||||
const OUTPUT_MASTER_INSTANCE_NAME = "master_instance_name"
|
||||
const OUTPUT_FAILOVER_INSTANCE_NAME = "failover_instance_name"
|
||||
@@ -35,9 +36,13 @@ const OUTPUT_CLIENT_PRIVATE_KEY = "client_private_key"
|
||||
const OUTPUT_DB_NAME = "db_name"
|
||||
|
||||
const MYSQL_CREATE_TEST_TABLE_WITH_AUTO_INCREMENT_STATEMENT = "CREATE TABLE IF NOT EXISTS test (id int NOT NULL AUTO_INCREMENT, name varchar(10) NOT NULL, PRIMARY KEY (ID))"
|
||||
const MYSQL_EMPTY_TEST_TABLE_STATEMENT = "DELETE FROM test"
|
||||
const MYSQL_INSERT_TEST_ROW = "INSERT INTO test(name) VALUES(?)"
|
||||
const MYSQL_QUERY_ROW_COUNT = "SELECT count(*) FROM test"
|
||||
|
||||
const SQL_EMPTY_TEST_TABLE_STATEMENT = "DELETE FROM test"
|
||||
const SQL_QUERY_ROW_COUNT = "SELECT count(*) FROM test"
|
||||
|
||||
const POSTGRES_CREATE_TEST_TABLE_WITH_SERIAL = "CREATE TABLE IF NOT EXISTS test (id SERIAL, name varchar(10) NOT NULL, PRIMARY KEY (ID))"
|
||||
const POSTGRES_INSERT_TEST_ROW = "INSERT INTO test(name) VALUES('Grunty') RETURNING id"
|
||||
|
||||
func getRandomRegion(t *testing.T, projectID string) string {
|
||||
approvedRegions := []string{"europe-north1", "europe-west1", "europe-west2", "europe-west3", "us-central1", "us-east1", "us-west1"}
|
||||
@@ -58,7 +63,7 @@ func getTwoDistinctRandomZonesForRegion(t *testing.T, projectID string, region s
|
||||
return firstZone, secondZone
|
||||
}
|
||||
|
||||
func createTerratestOptionsForMySql(projectId string, region string, exampleDir string, namePrefix string, masterZone string, failoverReplicaZone string, numReadReplicas int, readReplicaZone string) *terraform.Options {
|
||||
func createTerratestOptionsForCloudSql(projectId string, region string, exampleDir string, namePrefix string, masterZone string, failoverReplicaZone string, numReadReplicas int, readReplicaZone string) *terraform.Options {
|
||||
|
||||
terratestOptions := &terraform.Options{
|
||||
// The path to where your Terraform code is located
|
||||
@@ -71,7 +76,6 @@ func createTerratestOptionsForMySql(projectId string, region string, exampleDir
|
||||
"failover_replica_zone": failoverReplicaZone,
|
||||
"project": projectId,
|
||||
"name_prefix": namePrefix,
|
||||
"mysql_version": MYSQL_VERSION,
|
||||
"db_name": DB_NAME,
|
||||
"master_user_name": DB_USER,
|
||||
"master_user_password": DB_PASS,
|
||||
@@ -96,3 +100,13 @@ func createTerratestOptionsForClientCert(projectId string, region string, exampl
|
||||
|
||||
return terratestOptions
|
||||
}
|
||||
|
||||
func createTempFile(t *testing.T, content []byte) *os.File {
|
||||
tmpFile, err := ioutil.TempFile(os.TempDir(), "temp-")
|
||||
require.NoError(t, err, "Failed to create temp file")
|
||||
_, err = tmpFile.Write(content)
|
||||
require.NoError(t, err, "Failed to write to temp file")
|
||||
err = tmpFile.Close()
|
||||
require.NoError(t, err, "Failed to close temp file")
|
||||
return tmpFile
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user