Skip to content

Commit c740448

Browse files
filippolmtCopilot
andauthored
feat: enhance MySQL support and improve script logging (#20)
* feat: enhance MySQL support and improve script logging Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
1 parent e6504b3 commit c740448

File tree

11 files changed

+206
-47
lines changed

11 files changed

+206
-47
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres
77
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88

9+
## [Unreleased]
10+
11+
## [0.5.0] - 2025-11-11
12+
13+
### Added
14+
15+
- Introduced the `permissions_refresh_id` input plus the `null_resource.force_permissions_refresh`/`null_resource.grant_permissions` helpers so you can rerun the proxy/grant scripts without recreating users.
16+
17+
### Changed
18+
19+
- Reworked the resource lifecycles so proxy start/kill and permission scripts are re-executed whenever `permissions_refresh_id` changes.
20+
- Hardened the Cloud SQL proxy helper scripts: better logging, explicit dependency checks (mysql/nc/proxy), use of the v2 proxy CLI syntax, and safer shutdown handling.
21+
- Enhanced `execute_sql.sh` so MySQL 8.0/8.4 users have the `cloudsqlsuperuser` role removed, default roles cleared, and only database-scoped privileges granted.
22+
- Documentation updates: clarify Cloud SQL Auth Proxy v2 requirement, mention MySQL 8.4 support.
23+
924
## [0.4.1] - 2025-02-24
1025

1126
[Compare with previous version](https://github.com/sparkfabrik/terraform-google-gcp-mysql-db-and-user-creation-helper/compare/0.4.0...0.4.1)

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
# Terraform module for creating database and associated user on an existing Google CloudSQL instance
22

3-
This module creates database and users on an existing CloudSQL instance. The structure of the input variable is designed so that the database/user ratio is 1:1, so the module not only takes care of creating the database and its user, but also sets permissions on the user so that it has access to only the database for which it is responsible.
3+
This module creates databases and users on an existing CloudSQL instance. The structure of the input variable enforces a 1:1 database/user ratio. The module both creates each pair and applies the required permissions so that the user can access only its database.
44

5-
To enforce permissions, the module executes SQL commands with the mysql cli, which is therefore a prerequisite (it must be present in the filesystem where terraform apply is executed).
5+
To enforce permissions, the module executes SQL commands through the MySQL CLI, which therefore must be installed on the machine running `terraform apply`. The bundled proxy helper scripts rely on the [Cloud SQL Auth Proxy **v2** command syntax](https://cloud.google.com/sql/docs/mysql/connect-auth-proxy), so make sure you install proxy version 2.x (the newer `${CLOUDSQL_PROXY_BIN} \"${CONNECTION_NAME}\" --port ...` invocation) rather than the legacy 1.x binary that used `-instances=` flags.
66

7-
In addition, the script must be able to connect to the CloudSQL instance. In case this is not easily accessible from the terraform cli, the module is able to:
7+
For MySQL 8.x instances, the module automatically removes the default `cloudsqlsuperuser` role, clears any global privileges and assigns the target database as the only default role so that new users are scoped exclusively to their database.
88

9-
1. Start an instance of [CloudSQL Auth Proxy](https://cloud.google.com/sql/docs/mysql/sql-proxy), for this purpose two null resources will be created for each user added to the database, enabling this option requires the [presence of the proxy executable](https://cloud.google.com/sql/docs/mysql/sql-proxy) in the filesystem where `terraform apply` is executed.
10-
2. Connect from a [CloudSQL Auth Proxy](https://cloud.google.com/sql/docs/mysql/sql-proxy) instance not present in the filesystem.
9+
If you ever need to rerun all local scripts (start proxy → grant privileges → stop proxy) without recreating the module-managed users, set a different value for the `permissions_refresh_id` variable (use `YYYYMMDD` or `YYYYMMDDHHMM`, e.g. `20251110` or `202511101030`) and run `terraform apply`; changing the value forces Terraform to recreate the null resources that execute those scripts while keeping the `google_sql_user` resources in place (see `examples/main.tf` for a ready-to-use snippet).
10+
11+
In addition, the module must be able to connect to the CloudSQL instance. If the instance is not directly reachable from the machine running `terraform apply`, the module can:
12+
13+
1. Start a local instance of [CloudSQL Auth Proxy](https://cloud.google.com/sql/docs/mysql/sql-proxy). This creates two null resources per user and requires the [proxy executable](https://cloud.google.com/sql/docs/mysql/sql-proxy) to be present on the machine running `terraform apply`.
14+
2. Connect through an existing [CloudSQL Auth Proxy](https://cloud.google.com/sql/docs/mysql/sql-proxy) instance that is already running elsewhere (outside this module).
1115

1216
### IMPORTANT
1317

@@ -33,12 +37,13 @@ CloudSQL Auth Proxy needs the CloudSQL instance to expose a public IP address in
3337

3438
| Name | Description | Type | Default | Required |
3539
|------|-------------|------|---------|:--------:|
36-
| <a name="input_cloudsql_instance_name"></a> [cloudsql\_instance\_name](#input\_cloudsql\_instance\_name) | The name of the existing Google CloudSQL Instance name. Actually only a MySQL 5.7 or 8 instance is supported. | `string` | n/a | yes |
40+
| <a name="input_cloudsql_instance_name"></a> [cloudsql\_instance\_name](#input\_cloudsql\_instance\_name) | The name of the existing Google CloudSQL Instance name. MySQL 5.7, 8.0 and 8.4 are supported. | `string` | n/a | yes |
3741
| <a name="input_cloudsql_privileged_user_name"></a> [cloudsql\_privileged\_user\_name](#input\_cloudsql\_privileged\_user\_name) | The name of the privileged user of the Cloud SQL instance | `string` | n/a | yes |
3842
| <a name="input_cloudsql_privileged_user_password"></a> [cloudsql\_privileged\_user\_password](#input\_cloudsql\_privileged\_user\_password) | The password of the privileged user of the Cloud SQL instance | `string` | n/a | yes |
3943
| <a name="input_cloudsql_proxy_host"></a> [cloudsql\_proxy\_host](#input\_cloudsql\_proxy\_host) | The host of the Cloud SQL Auth Proxy; if a value other than localhost or 127.0.0.1 (default) is entered, it is assumed that there is a CloudSQL Auth Proxy instance defined and already configured outside this module, and therefore the proxy will not be launched. | `string` | `"127.0.0.1"` | no |
4044
| <a name="input_cloudsql_proxy_port"></a> [cloudsql\_proxy\_port](#input\_cloudsql\_proxy\_port) | Port of the Cloud SQL Auth Proxy | `string` | `"1234"` | no |
41-
| <a name="input_database_and_user_list"></a> [database\_and\_user\_list](#input\_database\_and\_user\_list) | The list with all the databases and the relative user. Please not that you can assign only a database to a single user, the same user cannot be assigned to multiple databases. `user_host` is optional, has a default value of '%' to allow the user to connect from any host, or you can specify it for the given user for a more restrictive access. | <pre>list(object({<br/> user = string<br/> user_host = optional(string, "%")<br/> database = string<br/> }))</pre> | n/a | yes |
45+
| <a name="input_database_and_user_list"></a> [database\_and\_user\_list](#input\_database\_and\_user\_list) | The list with all the databases and the relative user. Please note that you can assign only a database to a single user, the same user cannot be assigned to multiple databases. `user_host` is optional, has a default value of '%' to allow the user to connect from any host, or you can specify it for the given user for a more restrictive access. | <pre>list(object({<br/> user = string<br/> user_host = optional(string, "%")<br/> database = string<br/> }))</pre> | n/a | yes |
46+
| <a name="input_permissions_refresh_id"></a> [permissions\_refresh\_id](#input\_permissions\_refresh\_id) | Optional identifier (use format YYYYMMDD or YYYYMMDDHHMM, e.g. 20251110 or 202511101030) used only to force Terraform to rerun the proxy/grant scripts without recreating users. Change the value whenever you need to reapply permissions. | `string` | `""` | no |
4247
| <a name="input_project_id"></a> [project\_id](#input\_project\_id) | The ID of the project in which the resource belongs. | `string` | n/a | yes |
4348
| <a name="input_region"></a> [region](#input\_region) | The region in which the resource belongs. | `string` | n/a | yes |
4449
| <a name="input_terraform_start_cloud_sql_proxy"></a> [terraform\_start\_cloud\_sql\_proxy](#input\_terraform\_start\_cloud\_sql\_proxy) | If `true` terraform will automatically start the Cloud SQL Proxy instance present in the filesystem at the condition that cloudsql\_proxy\_host is set to a supported value. If `false` you have to start the Cloud SQL Proxy manually. This variable is used to prevent the creation of a Cloud SQL Proxy instance even if cloudsql\_proxy\_host has a supported value. | `bool` | `true` | no |
@@ -54,6 +59,8 @@ CloudSQL Auth Proxy needs the CloudSQL instance to expose a public IP address in
5459
| [google_sql_database.sql_database](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database) | resource |
5560
| [google_sql_user.sql_user](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_user) | resource |
5661
| [null_resource.execute_cloud_sql_proxy](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
62+
| [null_resource.force_permissions_refresh](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
63+
| [null_resource.grant_permissions](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
5764
| [null_resource.kill_cloud_sql_proxy](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
5865
| [random_password.sql_user_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource |
5966
| [google_sql_database_instance.cloudsql_instance](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/sql_database_instance) | data source |

examples/main.tf

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,13 @@ resource "google_sql_user" "admin_user_mysql" {
7070

7171
# Add additional user and database using this this module.
7272
module "mysql_additional_users_and_databases" {
73-
source = "sparkfabrik/gcp-mysql-db-and-user-creation-helper/sparkfabrik"
74-
version = "~> 0.1"
75-
project_id = var.project_id
76-
region = var.region
77-
database_and_user_list = var.database_and_user_list
73+
source = "sparkfabrik/gcp-mysql-db-and-user-creation-helper/sparkfabrik"
74+
version = "~> 0.1"
75+
project_id = var.project_id
76+
region = var.region
77+
database_and_user_list = var.database_and_user_list
78+
# Change this value (use YYYYMMDD or YYYYMMDDHHMM, e.g. 20251110 or 202511101030) whenever you need to rerun the proxy/grant scripts without recreating users.
79+
permissions_refresh_id = var.permissions_refresh_id
7880
cloudsql_instance_name = google_sql_database_instance.instance.name
7981
cloudsql_privileged_user_name = google_sql_user.admin_user_mysql.name
8082
cloudsql_privileged_user_password = google_sql_user.admin_user_mysql.password

examples/test.tfvars

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ database_and_user_list = [
1717
user = "user4"
1818
}
1919
]
20+
21+
# Bump this value (YYYYMMDD or YYYYMMDDHHMM, e.g. 20251110 or 202511101030) whenever you need to rerun the proxy/grant scripts without recreating users.
22+
permissions_refresh_id = "202511101030"

examples/variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,9 @@ variable "database_and_user_list" {
1919
database = string
2020
}))
2121
}
22+
23+
variable "permissions_refresh_id" {
24+
type = string
25+
default = ""
26+
description = "Change this date or date-time (YYYYMMDD or YYYYMMDDHHMM, e.g. 20251110 or 202511101030) to force rerunning the proxy/grant scripts."
27+
}

main.tf

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ resource "null_resource" "execute_cloud_sql_proxy" {
22
for_each = (((var.cloudsql_proxy_host == "localhost" || var.cloudsql_proxy_host == "127.0.0.1") && var.terraform_start_cloud_sql_proxy) ? {
33
for u in var.database_and_user_list : u.user => u
44
} : {})
5+
lifecycle {
6+
replace_triggered_by = [
7+
null_resource.force_permissions_refresh.id
8+
]
9+
}
510
provisioner "local-exec" {
611
command = "${path.module}/scripts/execute_cloud_sql_proxy.sh"
712
environment = {
@@ -55,6 +60,31 @@ resource "google_sql_user" "sql_user" {
5560
name = each.value.user
5661
password = random_password.sql_user_password[each.value.user].result
5762
host = each.value.user_host
63+
depends_on = [
64+
google_sql_database.sql_database
65+
]
66+
}
67+
68+
resource "null_resource" "force_permissions_refresh" {
69+
triggers = {
70+
refresh_id = var.permissions_refresh_id
71+
}
72+
}
73+
74+
resource "null_resource" "grant_permissions" {
75+
for_each = { for u in var.database_and_user_list : u.user => u }
76+
77+
triggers = {
78+
user = each.key
79+
user_host = each.value.user_host
80+
database = each.value.database
81+
}
82+
83+
lifecycle {
84+
replace_triggered_by = [
85+
null_resource.force_permissions_refresh.id
86+
]
87+
}
5888

5989
provisioner "local-exec" {
6090
command = "${path.module}/scripts/execute_sql.sh"
@@ -74,17 +104,24 @@ resource "google_sql_user" "sql_user" {
74104
interpreter = [
75105
"/bin/sh", "-c"
76106
]
77-
when = create
78107
}
108+
79109
depends_on = [
80-
google_sql_database.sql_database
110+
google_sql_database.sql_database,
111+
google_sql_user.sql_user,
112+
null_resource.execute_cloud_sql_proxy
81113
]
82114
}
83115

84116
resource "null_resource" "kill_cloud_sql_proxy" {
85117
for_each = (((var.cloudsql_proxy_host == "localhost" || var.cloudsql_proxy_host == "127.0.0.1") && var.terraform_start_cloud_sql_proxy) ? {
86118
for u in var.database_and_user_list : u.user => u
87119
} : {})
120+
lifecycle {
121+
replace_triggered_by = [
122+
null_resource.force_permissions_refresh.id
123+
]
124+
}
88125
provisioner "local-exec" {
89126
command = "${path.module}/scripts/kill_cloud_sql_proxy.sh"
90127
interpreter = [
@@ -94,6 +131,7 @@ resource "null_resource" "kill_cloud_sql_proxy" {
94131
}
95132
depends_on = [
96133
google_sql_database.sql_database,
97-
google_sql_user.sql_user
134+
google_sql_user.sql_user,
135+
null_resource.grant_permissions
98136
]
99137
}

scripts/execute_cloud_sql_proxy.sh

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,50 @@
11
#!/usr/bin/env sh
22

3-
if ! [ -x "$(command -v cloud_sql_proxy)" ]; then
4-
echo "Error: cannot find the cloud_sql_proxy executable, please install it or add to your path." >&2
3+
set -eu
4+
5+
# shellcheck disable=SC3040
6+
if (set -o pipefail 2>/dev/null); then
7+
set -o pipefail
8+
fi
9+
10+
log() {
11+
printf '[sql-proxy] %s\n' "${1}"
12+
}
13+
14+
CLOUDSQL_PROXY_BIN=""
15+
if command -v cloud_sql_proxy >/dev/null 2>&1; then
16+
CLOUDSQL_PROXY_BIN="cloud_sql_proxy"
17+
else
18+
log "Error: cannot find the Cloud SQL Auth Proxy executable cloud_sql_proxy. Please install it or add it to your PATH." >&2
519
exit 1
6-
elif ! [ -x "$(command -v nc)" ]; then
7-
echo "Error: Netcat is not installed." >&2
20+
fi
21+
22+
if ! command -v nc >/dev/null 2>&1; then
23+
log "Error: Netcat is not installed." >&2
824
exit 1
925
fi
1026

11-
SERVICE="cloud_sql_proxy"
27+
CONNECTION_NAME="${CLOUDSDK_CORE_PROJECT}:${GCLOUD_PROJECT_REGION}:${CLOUDSQL_INSTANCE_NAME}"
1228

13-
if ! pgrep -x "$SERVICE" >/dev/null
14-
then
15-
exec cloud_sql_proxy -instances="${CLOUDSDK_CORE_PROJECT}:${GCLOUD_PROJECT_REGION}:${CLOUDSQL_INSTANCE_NAME}"="tcp:0.0.0.0:${CLOUDSQL_PROXY_PORT}" /dev/null 2>&1 &
29+
if ! pgrep -x "$CLOUDSQL_PROXY_BIN" >/dev/null; then
30+
log "Starting Cloud SQL Auth Proxy (${CLOUDSQL_PROXY_BIN}) for ${CONNECTION_NAME} on localhost:${CLOUDSQL_PROXY_PORT}."
31+
"${CLOUDSQL_PROXY_BIN}" "${CONNECTION_NAME}" --port "${CLOUDSQL_PROXY_PORT}" >/dev/null 2>&1 &
32+
sleep 1s
33+
else
34+
log "Cloud SQL Auth Proxy already running; skipping start."
1635
fi
1736

1837
for j in $(seq 1 10); do
1938
READY=$(sh -c 'nc -v ${CLOUDSQL_PROXY_HOST} ${CLOUDSQL_PROXY_PORT} </dev/null; echo $?;' 2>/dev/null)
2039
if [ "$READY" -eq 0 ]; then
21-
echo "Connection with with CloudSQL Auth Proxy established at ${CLOUDSQL_PROXY_HOST}."
40+
log "Connection with Cloud SQL Auth Proxy established at ${CLOUDSQL_PROXY_HOST}:${CLOUDSQL_PROXY_PORT}."
2241
break
2342
fi
24-
echo "Waiting for Cloud SQL Proxy to start... $j"
43+
log "Waiting for Cloud SQL Proxy to start (attempt ${j}/10)..."
2544
sleep 1s
2645
done
2746

28-
if [ "$READY" -eq 1 ]; then
29-
echo "ERROR: cannot connect to the CloudSQL Auth Proxy at ${CLOUDSQL_PROXY_HOST}, please check your settings."
47+
if [ "$READY" -ne 0 ]; then
48+
log "ERROR: cannot connect to the Cloud SQL Auth Proxy at ${CLOUDSQL_PROXY_HOST}:${CLOUDSQL_PROXY_PORT}, please check your settings." >&2
3049
exit 1
3150
fi

0 commit comments

Comments
 (0)