diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 813c0e4..7c214a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Java CI with Maven (multi-job) +name: CI on: workflow_dispatch: diff --git a/google-cloud-sql/docs/automate-table-creation-using-terraform.md b/google-cloud-sql/docs/automate-table-creation-using-terraform.md new file mode 100644 index 0000000..461bdae --- /dev/null +++ b/google-cloud-sql/docs/automate-table-creation-using-terraform.md @@ -0,0 +1,32 @@ +# Automate table creation using Terraform + +### Example + +```hcl +# run after instance+db+user exist +resource "null_resource" "init_table" { + depends_on = [google_sql_database.db, google_sql_user.user] + + provisioner "local-exec" { + interpreter = ["/bin/bash", "-c"] + command = <<-EOT + set -euo pipefail + # Start proxy + cloud-sql-proxy ${google_sql_database_instance.pg.connection_name} --port 5432 & + PID=$! + # Wait for port + for i in {1..30}; do nc -z 127.0.0.1 5432 && break; sleep 1; done + # Apply DDL + PGPASSWORD='${var.db_password}' psql -h 127.0.0.1 -p 5432 -U '${var.db_user}' -d '${var.db_name}' <<'SQL' + CREATE TABLE IF NOT EXISTS widgets ( + id UUID PRIMARY KEY, + name TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + meta JSONB + ); + SQL + kill $PID + EOT + } +} +``` diff --git a/google-cloud-sql/docs/cloud-sql-auth-proxy.md b/google-cloud-sql/docs/cloud-sql-auth-proxy.md new file mode 100644 index 0000000..900717b --- /dev/null +++ b/google-cloud-sql/docs/cloud-sql-auth-proxy.md @@ -0,0 +1,230 @@ +# Cloud SQL Auth Proxy + +### Set instance connection name + +```bash +export INSTANCE_CONNECTION_NAME="lofty-root-378503:us-central1:jm-pg-demo" +``` + +### Run the CloudSQL auth proxy + +Remove any old proxy container: + +```bash +docker rm -f csql-proxy 2>/dev/null || true +``` + +Then use one of the below options: + +#### Executable + +```bash +# CLI option +# Download from: https://cloud.google.com/sql/docs/postgres/connect-auth-proxy +./cloud-sql-proxy ${INSTANCE_CONNECTION_NAME} --port 5432 & +PROXY_PID=$! +``` + +#### Docker + +```bash + +export CREDENTIALS_PATH="/Users/admin/.config/gcloud/sa-private-key.json" +docker run -d --name csql-proxy -p 127.0.0.1:5400:5432 \ + -v "$GOOGLE_APPLICATION_CREDENTIALS:/creds/key.json:ro" \ + gcr.io/cloud-sql-connectors/cloud-sql-proxy:2 \ + --address 0.0.0.0 --port 5432 \ + --credentials-file=/creds/key.json "$INSTANCE_CONNECTION_NAME" +``` + +### Wait for proxy to listen + +```bash +for i in {1..30}; do nc -z 127.0.0.1 5433 && echo "proxy ready" && break; sleep 1; done +``` + +### Check if proxy is running + +```bash +docker ps -a | grep csql-proxy || true +``` + +### Verify proxy is running on a specific port + +```bash +nc -z 127.0.0.1 5400 && echo "proxy up" || echo "proxy not up" +``` + +### Check proxy logs + +```bash +docker logs csql-proxy --tail=200 || true +``` + +### Check if port is in use + +```bash +lsof -iTCP:5432 -sTCP:LISTEN || true +``` + +### List available ports + +#### Common Postgres ports + +```bash +for p in 5432 5433 5434 5435 5436; do (lsof -iTCP:$p -sTCP:LISTEN >/dev/null) && echo "❌ $p in use" || echo "✅ $p free"; done +``` + +#### Scan a wider port range + +```bash +for p in {5400..5450}; do (lsof -iTCP:$p -sTCP:LISTEN >/dev/null) || echo "✅ $p free"; done +``` + +#### Pick the first available port + +```bash +for p in {5400..5450}; do (lsof -iTCP:$p -sTCP:LISTEN >/dev/null) || { echo "First free port: $p"; break; }; done +``` + +### Test connection + +```bash +export TF_VAR_db_user=username +export TF_VAR_db_password=password +export TF_VAR_db_name=jm_demo_db +``` + +Have the proxy running and healthy to `127.0.0.1:5432`. +Make sure nothing else is using port `5432`. +If it is, pick another port (e.g., 5433) and update your `psql` command. + +Then run: + +```bash +PGPASSWORD="$TF_VAR_db_password" psql \ + -h 127.0.0.1 -p 5400 \ + -U "$TF_VAR_db_user" -d "$TF_VAR_db_name" \ + -c "select 1;" +``` + +### If the test is successful + +Assuming the file `/src/test/resources/init.sql` exists: + +```bash +PGPASSWORD="$TF_VAR_db_password" psql \ + -h 127.0.0.1 -p 5400 \ + -U "$TF_VAR_db_user" -d "$TF_VAR_db_name" \ + -f init.sql +``` + +You can also create the table directly from the command line: + +```bash +cat > init.sql <<'SQL' +PGPASSWORD="$TF_VAR_db_password" psql -h 127.0.0.1 -p 5400 -U "$TF_VAR_db_user" -d "$TF_VAR_db_name" -v ON_ERROR_STOP=1 <<'SQL' +CREATE TABLE IF NOT EXISTS widgets ( + id UUID PRIMARY KEY, + name TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + meta JSONB +); +SQL +``` + +### Single insert + +```bash +PGPASSWORD="$TF_VAR_db_password" psql \ + -h 127.0.0.1 -p 5400 \ + -U "$TF_VAR_db_user" -d "$TF_VAR_db_name" \ + -c "INSERT INTO widgets (id, name, meta) VALUES (gen_random_uuid(), 'Test Widget', '{\"color\":\"red\",\"size\":10}');" +``` + +### Multiple inserts + +```bash +PGPASSWORD="$TF_VAR_db_password" psql \ + -h 127.0.0.1 -p 5400 \ + -U "$TF_VAR_db_user" -d "$TF_VAR_db_name" \ + -c " +INSERT INTO widgets (id, name, meta) VALUES + (gen_random_uuid(), 'Widget A', '{\"type\":\"alpha\"}'), + (gen_random_uuid(), 'Widget B', '{\"type\":\"beta\"}'), + (gen_random_uuid(), 'Widget C', '{\"type\":\"gamma\"}'); +" +``` + +### Insert rows from a file + +```bash +cat > /tmp/insert_widgets.sql <<'SQL' +INSERT INTO widgets (id, name, meta) VALUES + (gen_random_uuid(), 'Widget X', '{"size":42}'), + (gen_random_uuid(), 'Widget Y', '{"color":"blue"}'); +SQL + +PGPASSWORD="$TF_VAR_db_password" psql \ + -h 127.0.0.1 -p 5400 \ + -U "$TF_VAR_db_user" -d "$TF_VAR_db_name" \ + -f insert_widgets.sql +``` + +### Read all rows in a table + +```bash +PGPASSWORD="$TF_VAR_db_password" psql \ + -h 127.0.0.1 -p 5400 \ + -U "$TF_VAR_db_user" -d "$TF_VAR_db_name" \ + -c "SELECT * FROM widgets;" +``` + +### Open an interactive psql session + +```bash +PGPASSWORD="$TF_VAR_db_password" psql -h 127.0.0.1 -p 5400 -U "$TF_VAR_db_user" -d "$TF_VAR_db_name" +``` + +```sql +INSERT INTO widgets (id, name, meta) VALUES (gen_random_uuid(), 'Widget ABC', '{"shape":"sphere"}'); +SELECT * FROM widgets; +``` + +### List tables + +```psql +\l +``` + +### Drop tables + +```bash +DROP TABLE widgets; +``` + +### Exit `psql` + +```psql +exit +``` + +--- + +### Grant create/use to a user + +```bash +PGPASSWORD="$TF_VAR_db_password" psql -h 127.0.0.1 -p 5432 -U "$TF_VAR_db_user" -d "$TF_VAR_db_name" -c \ +"GRANT USAGE ON SCHEMA public TO $TF_VAR_db_user; GRANT CREATE ON SCHEMA public TO $TF_VAR_db_user;" +``` + +Verify `roles/cloudsql.client` on the user's service account. + +--- + +### Stop the CloudSQL auth proxy + +```bash +kill $PROXY_PID 2>/dev/null || true +# or: docker rm -f csql-proxy +``` diff --git a/google-cloud-sql/pom.xml b/google-cloud-sql/pom.xml index 38e4486..33e8ded 100644 --- a/google-cloud-sql/pom.xml +++ b/google-cloud-sql/pom.xml @@ -13,6 +13,7 @@ org.squidmin.java.spring.maven.cloudsql google-cloud-sql 0.0.1-SNAPSHOT + google-cloud-sql google-cloud-sql diff --git a/google-cloud-sql/src/main/java/org/squidmin/java/spring/maven/cloudsql/controller/CloudSqlController.java b/google-cloud-sql/src/main/java/org/squidmin/java/spring/maven/cloudsql/controller/CloudSqlController.java index 33fe685..53facd6 100644 --- a/google-cloud-sql/src/main/java/org/squidmin/java/spring/maven/cloudsql/controller/CloudSqlController.java +++ b/google-cloud-sql/src/main/java/org/squidmin/java/spring/maven/cloudsql/controller/CloudSqlController.java @@ -13,7 +13,7 @@ public class CloudSqlController { public CloudSqlController() {} @GetMapping( - value = "/placeholder", + value = "/insert-rows", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE ) diff --git a/google-cloud-sql/src/test/resources/application-test.yml b/google-cloud-sql/src/test/resources/application-test.yml new file mode 100644 index 0000000..ac29fc0 --- /dev/null +++ b/google-cloud-sql/src/test/resources/application-test.yml @@ -0,0 +1,3 @@ +spring: + application: + name: google-cloud-sql diff --git a/google-cloud-sql/src/test/resources/init.sql b/google-cloud-sql/src/test/resources/init.sql new file mode 100644 index 0000000..664bd59 --- /dev/null +++ b/google-cloud-sql/src/test/resources/init.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS widgets ( + id UUID PRIMARY KEY, + name TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + meta JSONB +); diff --git a/google-cloud-sql/src/test/resources/insert_widgets.sql b/google-cloud-sql/src/test/resources/insert_widgets.sql new file mode 100644 index 0000000..c39a65f --- /dev/null +++ b/google-cloud-sql/src/test/resources/insert_widgets.sql @@ -0,0 +1,3 @@ +INSERT INTO widgets (id, name, meta) +VALUES (gen_random_uuid(), 'Widget X', '{"size":42}'), + (gen_random_uuid(), 'Widget Y', '{"color":"blue"}');