diff --git a/mysql/bin/entrypoint.sh b/mysql/bin/entrypoint.sh index 569c094..fd852dd 100755 --- a/mysql/bin/entrypoint.sh +++ b/mysql/bin/entrypoint.sh @@ -9,7 +9,10 @@ else # shellcheck disable=SC2046 export $(parse_database_url.py | xargs) # Setup .my.cnf so `mysql` just does the right thing - /bin/echo -e "[client]\nhost=$HOST\nport=$PORT\nuser=$USER" > ~/.my.cnf + # `database` only goes in [mysql] (read by the interactive mysql client); + # putting it in [client] breaks mysqladmin/mysqldump, which also read + # [client] but reject `database` as an unknown option. + /bin/echo -e "[client]\nhost=$HOST\nport=$PORT\nuser=$USER\n\n[mysql]\ndatabase=$NAME" > ~/.my.cnf fi exec "$@" diff --git a/mysql/bin/load-from-s3.sh b/mysql/bin/load-from-s3.sh index 4b51ced..96b90d6 100755 --- a/mysql/bin/load-from-s3.sh +++ b/mysql/bin/load-from-s3.sh @@ -12,8 +12,14 @@ echo "Downloading $S3_PATH ..." aws s3 cp --no-progress "$S3_PATH" /tmp/db.sql.gz echo "Drop/create $NAME..." -mysql --execute "DROP DATABASE IF EXISTS "'`'"$NAME"'`' -mysql --execute "CREATE DATABASE "'`'"$NAME"'`' +# ~/.my.cnf now defaults to $NAME as the database (so a bare `mysql` works +# for interactive use), but $NAME doesn't exist for the moment between the +# DROP and CREATE below, and the app DB user typically has no privileges +# on any other schema to fall back to. Bypass the defaults file for these +# two statements so no default database is selected; MYSQL_PWD still +# supplies the password since it's read directly from the environment. +mysql --no-defaults --host="$HOST" --port="$PORT" --user="$USER" --execute "DROP DATABASE IF EXISTS "'`'"$NAME"'`' +mysql --no-defaults --host="$HOST" --port="$PORT" --user="$USER" --execute "CREATE DATABASE "'`'"$NAME"'`' echo "Loading $S3_PATH into $NAME..." set -x diff --git a/mysql/bin/parse_database_url.py b/mysql/bin/parse_database_url.py index 4a5f78e..fc50525 100755 --- a/mysql/bin/parse_database_url.py +++ b/mysql/bin/parse_database_url.py @@ -9,7 +9,7 @@ 'USER': parsed.username, 'MYSQL_PWD': parsed.password, 'HOST': parsed.hostname, - 'PORT': parsed.port, + 'PORT': parsed.port or 3306, 'NAME': parsed.path.strip('/') } for k, v in env.items(): diff --git a/mysql/docker-compose.test.yml b/mysql/docker-compose.test.yml index 1e49d3a..d59a7b1 100644 --- a/mysql/docker-compose.test.yml +++ b/mysql/docker-compose.test.yml @@ -7,7 +7,12 @@ services: volumes: - db_data:/var/lib/mysql s3: - image: localstack/localstack + # Pinned: localstack/localstack:latest moved to a unified image in March + # 2026 that requires a paid LOCALSTACK_AUTH_TOKEN to boot at all, even + # for plain S3. 4.14.0 is the last community-edition tag that starts + # without a license. Revisit this pin once the suite can run against a + # supported, token-free community image again. + image: localstack/localstack:4.14.0 platform: linux/amd64 environment: SERVICES: s3 diff --git a/mysql/tests/tests.sh b/mysql/tests/tests.sh index 78bb45a..3a6e72a 100755 --- a/mysql/tests/tests.sh +++ b/mysql/tests/tests.sh @@ -15,16 +15,34 @@ done echo "###### Setup test state" aws s3api create-bucket --bucket "$BUCKET" aws s3 rm --recursive "s3://$BUCKET/" -mysql -u root --execute 'DROP DATABASE IF EXISTS `test`' -mysql -u root --execute 'DROP DATABASE IF EXISTS `test-clone`' -mysql -u root --execute "DROP USER IF EXISTS 'test'" -mysql -u root --execute 'CREATE DATABASE `test`' -mysql -u root --execute "CREATE USER 'test'@'%' IDENTIFIED BY 'password'" -mysql -u root --execute 'GRANT ALL PRIVILEGES ON `test`.* TO `test`@`%`' +# `test` doesn't exist yet, and ~/.my.cnf now defaults to it (see [mysql] +# section written by entrypoint.sh), so these bootstrap statements target +# the always-present `mysql` system schema explicitly instead of relying +# on that default. +mysql -u root mysql --execute 'DROP DATABASE IF EXISTS `test`' +mysql -u root mysql --execute 'DROP DATABASE IF EXISTS `test-clone`' +mysql -u root mysql --execute "DROP USER IF EXISTS 'test'" +mysql -u root mysql --execute 'CREATE DATABASE `test`' +mysql -u root mysql --execute "CREATE USER 'test'@'%' IDENTIFIED BY 'password'" +mysql -u root mysql --execute 'GRANT ALL PRIVILEGES ON `test`.* TO `test`@`%`' mysql test --execute "CREATE TABLE tbl (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL)" mysql test --execute "INSERT INTO tbl (name) VALUES ('name1')" mysql test --execute "INSERT INTO tbl (name) VALUES ('name2')" +printf "\n###### Testing bare mysql (no --database) uses database from DATABASE_URL...\n" +mysql --execute "SELECT COUNT(*) FROM tbl" | grep "2" > /dev/null +echo "✅ bare mysql connected to the database named in DATABASE_URL via ~/.my.cnf" + +printf "\n###### Testing portless DATABASE_URL defaults PORT to 3306...\n" +PARSED_PORTLESS=$(DATABASE_URL="mysql://test:password@db/test" parse_database_url.py) +echo "$PARSED_PORTLESS" | grep "PORT=3306" > /dev/null +echo "✅ PORT defaulted to 3306 for a portless DATABASE_URL" + +printf "\n###### Testing connection succeeds with a portless DATABASE_URL...\n" +eval "$PARSED_PORTLESS" +mysql --host="$HOST" --port="$PORT" --user="$USER" --password="$MYSQL_PWD" "$NAME" --execute "SELECT 1" > /dev/null +echo "✅ mysql connected successfully using the parsed portless DATABASE_URL" + printf "\n###### Starting tests...\n" dump-to-s3.sh "s3://$BUCKET/dump.sql.gz" test printf "\n###### Verify dump file exists...\n" diff --git a/postgres/bin/entrypoint.sh b/postgres/bin/entrypoint.sh index 9d6d214..ffe5732 100755 --- a/postgres/bin/entrypoint.sh +++ b/postgres/bin/entrypoint.sh @@ -7,15 +7,20 @@ export PGSSLMODE=require wait_for_db() { local retries=30 local sleep_time=2 + local last_error="" echo "Waiting for PostgreSQL server to be ready..." - until psql "$DATABASE_URL" -c '\q' 2>/dev/null || [ "$retries" -eq 0 ]; do + until last_error=$(psql "$DATABASE_URL" -c '\q' 2>&1 >/dev/null) || [ "$retries" -eq 0 ]; do echo "PostgreSQL is unavailable - ($((retries--)) retries left)..." sleep "$sleep_time" done if [ "$retries" -eq 0 ]; then echo "ERROR: PostgreSQL server did not respond." + if [ -n "$last_error" ]; then + echo "Last error from psql:" + echo "$last_error" + fi exit 1 fi } diff --git a/postgres/bin/parse_database_url.py b/postgres/bin/parse_database_url.py index 221425b..418be9e 100755 --- a/postgres/bin/parse_database_url.py +++ b/postgres/bin/parse_database_url.py @@ -8,7 +8,7 @@ 'USER': parsed.username, 'PGPASSWORD': parsed.password, 'HOST': parsed.hostname, - 'PORT': parsed.port, + 'PORT': parsed.port or 5432, 'NAME': parsed.path.strip('/') } for k, v in env.items(): diff --git a/postgres/docker-compose.test.yml b/postgres/docker-compose.test.yml index 82dd2db..3f17e16 100644 --- a/postgres/docker-compose.test.yml +++ b/postgres/docker-compose.test.yml @@ -7,7 +7,12 @@ services: environment: POSTGRES_PASSWORD: password s3: - image: localstack/localstack + # Pinned: localstack/localstack:latest moved to a unified image in March + # 2026 that requires a paid LOCALSTACK_AUTH_TOKEN to boot at all, even + # for plain S3. 4.14.0 is the last community-edition tag that starts + # without a license. Revisit this pin once the suite can run against a + # supported, token-free community image again. + image: localstack/localstack:4.14.0 platform: linux/amd64 environment: SERVICES: s3 diff --git a/postgres/tests/tests.sh b/postgres/tests/tests.sh index 516e4b0..578845c 100755 --- a/postgres/tests/tests.sh +++ b/postgres/tests/tests.sh @@ -25,6 +25,14 @@ psql test -c "CREATE TABLE tbl (id SERIAL PRIMARY KEY, name CHAR(255) NOT NULL)" psql test -c "INSERT INTO tbl (name) VALUES ('name1')" psql test -c "INSERT INTO tbl (name) VALUES ('name2')" +printf "\n###### Testing portless DATABASE_URL defaults PORT to 5432...\n" +DATABASE_URL="postgres://test:password@db/test" parse_database_url.py | grep "PORT=5432" > /dev/null +echo "✅ PORT defaulted to 5432 for a portless DATABASE_URL" + +printf "\n###### Testing connection succeeds with a portless DATABASE_URL...\n" +psql "postgres://test:password@db/test" -c '\q' +echo "✅ psql connected successfully using a portless DATABASE_URL" + printf "\n###### Testing SERVER_VERSION override...\n" export SERVER_VERSION=17 DUMP_LOG=$(dump-to-s3.sh "s3://$BUCKET/explicit.dump" test 2>&1)