diff --git a/packages/env/CHANGELOG.md b/packages/env/CHANGELOG.md index dd77223ea720e3..88f0703552d033 100644 --- a/packages/env/CHANGELOG.md +++ b/packages/env/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### New Features + +- Add a `mariadbVersion` option and `WP_ENV_MARIADB_VERSION` environment variable to choose the MariaDB version used by the Docker runtime ([#83751](https://github.com/WordPress/gutenberg/pull/83751)). + ## 5.11.0 (2023-02-01) ### Bug fix diff --git a/packages/env/README.md b/packages/env/README.md index 30924172dbff60..0baae4bc4bdfbc 100644 --- a/packages/env/README.md +++ b/packages/env/README.md @@ -460,6 +460,7 @@ You can customize the WordPress installation, plugins and themes that the develo | -------------- | -------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `"core"` | `string\|null` | `null` | The WordPress installation to use. If `null` is specified, `wp-env` will use the latest production release of WordPress. | | `"phpVersion"` | `string\|null` | `null` | The PHP version to use. If `null` is specified, `wp-env` will use the default version used with production release of WordPress. | +| `"mariadbVersion"` | `string\|null` | `null` | The MariaDB version to use: `"lts"`, `"latest"`, or a version such as `"10.11"`. If `null` is specified, `latest` is used. | | `"plugins"` | `string[]` | `[]` | A list of plugins to install and activate in the environment. | | `"themes"` | `string[]` | `[]` | A list of themes to install in the environment. | | `"port"` | `integer` | `8888` (`8889` for the tests instance) | The primary port number to use for the installation. You'll access the instance through the port: 'http://localhost:8888'. | @@ -662,6 +663,23 @@ You can tell `wp-env` to use a specific PHP version for compatibility and testin } ``` +#### Specific MariaDB Version + +You can tell `wp-env` which MariaDB version the database should run, for compatibility and testing. Use `"lts"`, `"latest"`, or a version such as `"10.11"` or `"11.4.2"`. This can also be set via the environment variable `WP_ENV_MARIADB_VERSION`, which applies to every environment. + +```json +{ + "mariadbVersion": "10.11", + "plugins": ["."] +} +``` + +MariaDB cannot start on a database written by a newer version. When switching to a lower version, run `wp-env destroy` first. It removes the environment's Docker containers, volumes, networks, and local files, not only the databases, so everything is recreated on the next start. + +**Note:** With a `mariadbVersion` older than 11.4, `wp db` commands in the `cli` containers fail, and `wp-env clean` leaves the database as it is: the MariaDB client in the CLI image requires TLS when `phpVersion` is unset or 8.1 or newer, and MariaDB servers before 11.4 do not offer it. WordPress itself is not affected. To start over with an empty database, run `wp-env destroy`. + +**Note:** MariaDB 5.5 images are not published for ARM processors. Apple silicon devices can still run version 5.5 under emulation by setting `DOCKER_DEFAULT_PLATFORM=linux/amd64` before running `wp-env start`. Be aware that every container in the environment runs under emulation, not only the database, so the whole environment runs more slowly. + ## Contributing to this package This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects. diff --git a/packages/env/lib/build-docker-compose-config.js b/packages/env/lib/build-docker-compose-config.js index 7b27f85a7f6cfb..f32887bcb45e31 100644 --- a/packages/env/lib/build-docker-compose-config.js +++ b/packages/env/lib/build-docker-compose-config.js @@ -205,7 +205,9 @@ module.exports = function buildDockerComposeConfig( config ) { version: '3.7', services: { mysql: { - image: 'mariadb', + image: config.env.development.mariadbVersion + ? `mariadb:${ config.env.development.mariadbVersion }` + : 'mariadb', ports: [ '3306' ], environment: { MYSQL_ROOT_HOST: '%', @@ -216,7 +218,9 @@ module.exports = function buildDockerComposeConfig( config ) { volumes: [ 'mysql:/var/lib/mysql' ], }, 'tests-mysql': { - image: 'mariadb', + image: config.env.tests.mariadbVersion + ? `mariadb:${ config.env.tests.mariadbVersion }` + : 'mariadb', ports: [ '3306' ], environment: { MYSQL_ROOT_HOST: '%', diff --git a/packages/env/lib/config/config.js b/packages/env/lib/config/config.js index ca00aebacf4ba9..ef3870ff01b8d1 100644 --- a/packages/env/lib/config/config.js +++ b/packages/env/lib/config/config.js @@ -10,7 +10,11 @@ const os = require( 'os' ); * Internal dependencies */ const detectDirectoryType = require( './detect-directory-type' ); -const { validateConfig, ValidationError } = require( './validate-config' ); +const { + validateConfig, + checkMariaDBVersion, + ValidationError, +} = require( './validate-config' ); const readRawConfigFile = require( './read-raw-config-file' ); const parseConfig = require( './parse-config' ); const { includeTestsPath, parseSourceString } = parseConfig; @@ -33,13 +37,14 @@ const md5 = require( '../md5' ); * Base-level config for any particular environment. (development/tests/etc) * * @typedef WPServiceConfig - * @property {WPSource} coreSource The WordPress installation to load in the environment. - * @property {WPSource[]} pluginSources Plugins to load in the environment. - * @property {WPSource[]} themeSources Themes to load in the environment. - * @property {number} port The port to use. - * @property {Object} config Mapping of wp-config.php constants to their desired values. - * @property {Object.} mappings Mapping of WordPress directories to local directories which should be mounted. - * @property {string} phpVersion Version of PHP to use in the environments, of the format 0.0. + * @property {WPSource} coreSource The WordPress installation to load in the environment. + * @property {WPSource[]} pluginSources Plugins to load in the environment. + * @property {WPSource[]} themeSources Themes to load in the environment. + * @property {number} port The port to use. + * @property {Object} config Mapping of wp-config.php constants to their desired values. + * @property {Object.} mappings Mapping of WordPress directories to local directories which should be mounted. + * @property {string} phpVersion Version of PHP to use in the environments, of the format 0.0. + * @property {string} mariadbVersion Version of MariaDB to use in the environments: "lts", "latest", or of the format 0.0. Null uses the default. */ /** @@ -88,6 +93,7 @@ module.exports = async function readConfig( configPath ) { const defaultConfiguration = { core: null, // Indicates that the latest stable version should ultimately be used. phpVersion: null, + mariadbVersion: null, plugins: [], themes: [], port: 8888, @@ -272,6 +278,18 @@ function withOverrides( config ) { config.env.tests.phpVersion = process.env.WP_ENV_PHP_VERSION || config.env.tests.phpVersion; + // Override MariaDB version with environment variable. + if ( process.env.WP_ENV_MARIADB_VERSION ) { + checkMariaDBVersion( + 'environment variable', + 'WP_ENV_MARIADB_VERSION', + process.env.WP_ENV_MARIADB_VERSION + ); + config.env.development.mariadbVersion = + process.env.WP_ENV_MARIADB_VERSION; + config.env.tests.mariadbVersion = process.env.WP_ENV_MARIADB_VERSION; + } + const updateEnvUrl = ( configKey ) => { [ 'development', 'tests' ].forEach( ( envKey ) => { try { diff --git a/packages/env/lib/config/parse-config.js b/packages/env/lib/config/parse-config.js index 9528ac8b3528c1..e140ca967ff4b6 100644 --- a/packages/env/lib/config/parse-config.js +++ b/packages/env/lib/config/parse-config.js @@ -37,6 +37,7 @@ module.exports = async function parseConfig( config, options ) { return { port: config.port, phpVersion: config.phpVersion, + mariadbVersion: config.mariadbVersion, coreSource: includeTestsPath( await parseCoreSource( config.core, options ), options diff --git a/packages/env/lib/config/test/__snapshots__/config.js.snap b/packages/env/lib/config/test/__snapshots__/config.js.snap index ad0c18e4279de7..2d0af673956533 100644 --- a/packages/env/lib/config/test/__snapshots__/config.js.snap +++ b/packages/env/lib/config/test/__snapshots__/config.js.snap @@ -22,6 +22,7 @@ Object { "WP_TESTS_TITLE": "Test Blog", }, "mappings": Object {}, + "mariadbVersion": null, "phpVersion": null, "pluginSources": Array [], "port": 2000, @@ -44,6 +45,7 @@ Object { "WP_TESTS_TITLE": "Test Blog", }, "mappings": Object {}, + "mariadbVersion": null, "phpVersion": null, "pluginSources": Array [], "port": 1000, diff --git a/packages/env/lib/config/test/config.js b/packages/env/lib/config/test/config.js index 5552ecce9d70f4..fc3eeb1a18f1fc 100644 --- a/packages/env/lib/config/test/config.js +++ b/packages/env/lib/config/test/config.js @@ -788,6 +788,77 @@ describe( 'readConfig', () => { } ); } ); + describe( 'mariadbVersion parsing', () => { + afterEach( () => { + delete process.env.WP_ENV_MARIADB_VERSION; + } ); + + it( 'should default to null', async () => { + readFile.mockImplementation( () => + Promise.resolve( JSON.stringify( {} ) ) + ); + const config = await readConfig( '.wp-env.json' ); + expect( config.env.development.mariadbVersion ).toBeNull(); + expect( config.env.tests.mariadbVersion ).toBeNull(); + } ); + + it( 'should parse mariadbVersion at the root and per environment', async () => { + readFile.mockImplementation( () => + Promise.resolve( + JSON.stringify( { + mariadbVersion: '10.11', + env: { tests: { mariadbVersion: 'latest' } }, + } ) + ) + ); + const config = await readConfig( '.wp-env.json' ); + expect( config.env.development.mariadbVersion ).toBe( '10.11' ); + expect( config.env.tests.mariadbVersion ).toBe( 'latest' ); + } ); + + it( 'should throw a validation error for an invalid mariadbVersion', async () => { + readFile.mockImplementation( () => + Promise.resolve( + JSON.stringify( { + env: { tests: { mariadbVersion: 'LTS' } }, + } ) + ) + ); + await expect( readConfig( '.wp-env.json' ) ).rejects.toEqual( + new ValidationError( + 'Invalid .wp-env.json: "env.tests.mariadbVersion" must be "lts", "latest", or a version such as "10.11" or "11.4.2".' + ) + ); + } ); + + it( 'should override mariadbVersion in every environment with WP_ENV_MARIADB_VERSION', async () => { + readFile.mockImplementation( () => + Promise.resolve( + JSON.stringify( { + mariadbVersion: '10.11', + env: { tests: { mariadbVersion: 'latest' } }, + } ) + ) + ); + process.env.WP_ENV_MARIADB_VERSION = '10.3'; + const config = await readConfig( '.wp-env.json' ); + expect( config.env.development.mariadbVersion ).toBe( '10.3' ); + expect( config.env.tests.mariadbVersion ).toBe( '10.3' ); + } ); + + it( 'should throw a validation error for an invalid WP_ENV_MARIADB_VERSION', async () => { + readFile.mockImplementation( () => + Promise.resolve( JSON.stringify( {} ) ) + ); + process.env.WP_ENV_MARIADB_VERSION = 'LTS'; + await expect( readConfig( '.wp-env.json' ) ).rejects.toEqual( + new ValidationError( + 'Invalid environment variable: "WP_ENV_MARIADB_VERSION" must be "lts", "latest", or a version such as "10.11" or "11.4.2".' + ) + ); + } ); + } ); + describe( 'port number parsing', () => { it( 'should throw a validaton error if the ports are not numbers', async () => { expect.assertions( 10 ); diff --git a/packages/env/lib/config/validate-config.js b/packages/env/lib/config/validate-config.js index 1ecee11010f118..65b3e8cc8d324f 100644 --- a/packages/env/lib/config/validate-config.js +++ b/packages/env/lib/config/validate-config.js @@ -83,10 +83,48 @@ function validateConfig( config, envLocation ) { ); } + if ( + config.mariadbVersion !== undefined && + config.mariadbVersion !== null + ) { + checkMariaDBVersion( + '.wp-env.json', + `${ envPrefix }mariadbVersion`, + config.mariadbVersion + ); + } + return config; } +/** + * Validates a MariaDB version and throws if it isn't valid. Accepts the + * "lts" and "latest" image tags or a numeric version such as "10.11". + * + * @param {string} source Where the value came from, for the error message. + * @param {string} configKey The configuration key we're validating. + * @param {string} version The version that we're checking. + */ +function checkMariaDBVersion( source, configKey, version ) { + if ( typeof version !== 'string' ) { + throw new ValidationError( + `Invalid ${ source }: "${ configKey }" must be a string.` + ); + } + + if ( version === 'lts' || version === 'latest' ) { + return; + } + + if ( ! /^[0-9]+(?:\.[0-9]+)*$/.test( version ) ) { + throw new ValidationError( + `Invalid ${ source }: "${ configKey }" must be "lts", "latest", or a version such as "10.11" or "11.4.2".` + ); + } +} + module.exports = { validateConfig, + checkMariaDBVersion, ValidationError, }; diff --git a/packages/env/test/build-docker-compose-config.js b/packages/env/test/build-docker-compose-config.js index 5927e188c9df01..68b1c5fa81366f 100644 --- a/packages/env/test/build-docker-compose-config.js +++ b/packages/env/test/build-docker-compose-config.js @@ -162,4 +162,43 @@ describe( 'buildDockerComposeConfig', () => { expect( dockerConfig.volumes.wordpress ).toBe( undefined ); expect( dockerConfig.volumes[ 'tests-wordpress' ] ).toBe( undefined ); } ); + + describe( 'mariadbVersion', () => { + it( 'uses the floating mariadb image by default', () => { + const config = buildDockerComposeConfig( { + workDirectoryPath: '/path', + env: { development: CONFIG, tests: CONFIG }, + } ); + + expect( config.services.mysql.image ).toBe( 'mariadb' ); + expect( config.services[ 'tests-mysql' ].image ).toBe( 'mariadb' ); + } ); + + it( 'treats null the same as unset', () => { + const config = buildDockerComposeConfig( { + workDirectoryPath: '/path', + env: { + development: { ...CONFIG, mariadbVersion: null }, + tests: { ...CONFIG, mariadbVersion: null }, + }, + } ); + + expect( config.services.mysql.image ).toBe( 'mariadb' ); + } ); + + it( 'uses each environment’s own version', () => { + const config = buildDockerComposeConfig( { + workDirectoryPath: '/path', + env: { + development: { ...CONFIG, mariadbVersion: '10.6.4' }, + tests: { ...CONFIG, mariadbVersion: 'latest' }, + }, + } ); + + expect( config.services.mysql.image ).toBe( 'mariadb:10.6.4' ); + expect( config.services[ 'tests-mysql' ].image ).toBe( + 'mariadb:latest' + ); + } ); + } ); } );