diff --git a/README.md b/README.md index 9d9202b9..0d3c8bcf 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ cd ~/.ssh cp /mnt/c/Users/USER-NAME/.ssh/id_rsa* . ``` ##### HTTPS on Windows -If https is not working in the browsers on Windows you will need to setup a shared root certificate. +If https is not working in the browsers on Windows you will need to setup a shared root certificate. You will need [mkcert installed](https://github.com/FiloSottile/mkcert) inside WSL2 machine. With `mkcert` installed run: ```bash @@ -247,7 +247,7 @@ In the ElasticPress settings, you should enter `http:///__elasticsearc ### WP Snapshots -See the section on [using WP Snapshots](#user-content-wpsnapshots) +See the section on [using Snapshots](#user-content-snapshots) ### Running WP CLI Commands @@ -436,30 +436,33 @@ Make sure your IDE is listening for PHP debug connections and set up a path mapp ] ``` -#### WPsnapshots +#### Snapshots + +As of version x.x.x of WP Local Docker, the snapshot system has switched from the old [WP Snapshots](https://github.com/10up/wpsnapshots) system, to the new [Snapshots](https://github.com/10up/snapshots) WP-CLI command. +Whilst the public API and configuration has stayed the same, the new system is much more robust and allows for features to be more easily added in the future. ##### Configuration -If you have not used [WP Snapshots](https://github.com/10up/wpsnapshots) with [WP Local Docker](https://github.com/10up/wp-local-docker-v2) yet, you'll first need to configure WP Snapshots with your AWS -credentials. To configure, run `10updocker wpsnapshots configure ` (e.g. `10updocker wpsnapshots configure 10up`). You will then be prompted to enter +If you have not used [Snapshots](https://github.com/10up/snapshots) with [WP Local Docker](https://github.com/10up/wp-local-docker-v2) yet, you'll first need to configure Snapshots with your AWS +credentials. To configure, run `10updocker wp snapshots configure ` (e.g. `10updocker wp snapshots configure 10up`). You will then be prompted to enter your AWS credentials and a few other configuration details. Once complete, the configuration will be available across all of your WP Local Docker environments. ##### Pulling an Environment -`10updocker wpsnapshots pull ` This command pulls an existing snapshot from the repository into your current +`10updocker wp snapshots pull ` This command pulls an existing snapshot from the repository into your current environment, replacing your database and wp-content. This command must be run from withing your environment directory (by default, this is somewhere within `~/wp-local-docker-sites//`). ##### Searching for an Environment -`10updocker wpsnapshots search ` with searches the repository for snapshots. `` will be +`10updocker wp snapshots search ` with searches the repository for snapshots. `` will be compared against project names and authors. Searching for "*" will return all snapshots. ##### Other Commands -`10updocker wpsnapshots ` is the general form for all WP Snapshots commands. `` is passed directly to -WP Snapshots, so any command that WP Snapshots accepts will work in this form. Any command that requires a WordPress +`10updocker wp snapshots ` is the general form for all Snapshots commands. `` is passed directly to +Snapshots, so any command that Snapshots accepts will work in this form. Any command that requires a WordPress environment (pull, create, etc) needs to be run from somewhere within an environment directory (by default, this is somewhere within `~/wp-local-docker-sites//`). @@ -570,6 +573,26 @@ Error: ER_ACCESS_DENIED_ERROR: Access denied for user 'root'@'localhost' (using For best results we recommend using the default port configuration whenever possible. +### I've upgraded WP Local Docker from a previous version and now the wpsnapshots command isn't working + +This is likely to do with the change from the [WP Snapshots](https://github.com/10up/wpsnapshots) system, to the new [Snapshots](https://github.com/10up/snapshots) WP-CLI command. +This update removed the need for a dedicated container for the snapshots system, and instead uses the `phpfpm` container. +Because of this, we need to ensure that the `phpfpm` container has access to the `~/.wpsnapshots` and `~/.aws` directories on your host machine. + +To provide that access, please edit the `docker-compose.yml` file in the environment you are updating, to add the following lines: + +```yaml + phpfpm: + image: '10up/wp-php-fpm-dev:7.4-ubuntu' + # ... + volumes: + # ... + - '~/.wpsnapshots:/home/www-data/.wpsnapshots:cached' + - '~/.aws:/home/www-data/.aws:cached:ro' +``` + +You'll then need to stop and start the environment for the changes to take effect. + --- ## Support Level diff --git a/src/commands/clone/pull-snapshot.js b/src/commands/clone/pull-snapshot.js deleted file mode 100644 index 19d9222e..00000000 --- a/src/commands/clone/pull-snapshot.js +++ /dev/null @@ -1,158 +0,0 @@ -const chalk = require( 'chalk' ); -const inquirer = require( 'inquirer' ); - -async function getSnapshotChoices( wpsnapshots, env, snapshot ) { - const snapshotChoices = []; - - const stdout = await wpsnapshots( env, [ - 'search', - ...snapshot, - '--format', - 'json', - ], 'pipe' ); - - try { - const data = JSON.parse( stdout ); - if ( Array.isArray( data ) ) { - const dateFormat = { - month: 'short', - day: '2-digit', - year: 'numeric', - }; - - data.forEach( ( { id, description, author, created } ) => { - const date = new Date( created * 1000 ); - snapshotChoices.push( { - name: `[${ date.toLocaleDateString( 'en-US', dateFormat ) }] ${ author }: ${ description }`, - value: id, - } ); - } ); - } - } catch ( e ) { - // do nothing - } - - return [ - ...snapshotChoices.sort( ( a, b ) => a.name.localeCompare( b.name ) ), - { - name: 'Don\'t use snapshot', - value: '', - }, - ]; -} - -module.exports = function makePullSnapshot( spinner, wpsnapshots ) { - return async ( env, mainDomain, config ) => { - const info = { - repository: undefined, - snapshot: [], - }; - - if ( typeof config === 'string' ) { - info.snapshot = [ config ]; - } else if ( Array.isArray( config ) ) { - info.snapshot = config; - } else if ( typeof config === 'object' ) { - if ( config.snapshot ) { - if ( typeof config.snapshot === 'string' ) { - info.snapshot = [ config.snapshot ]; - } else if ( Array.isArray( config.snapshot ) ) { - info.snapshot = config.snapshot; - } - } - - info.repository = config.repository || undefined; - } - - const { snapshot, repository } = info; - if ( ! Array.isArray( snapshot ) || ! snapshot.length ) { - return; - } - - const snapshotChoices = await getSnapshotChoices( wpsnapshots, env, snapshot ); - const questions = [ - { - name: 'snapshotId', - type: 'confirm', - message: 'Do you want to pull a snapshot?', - when() { - return ( Array.isArray( snapshot ) && snapshot.length === 1 ) || typeof snapshot === 'string'; - } - }, - { - name: 'snapshotId', - type: 'list', - message: 'What snapshot would you like to use?', - choices: snapshotChoices, - when() { - return snapshotChoices.length > 1; - }, - }, - { - name: 'includeFiles', - type: 'confirm', - message: 'Do you want to pull files from the snapshot?', - default: true, - when( { snapshotId } ) { - return snapshotId === true || ( typeof snapshotId === 'string' && snapshotId.trim().length > 0 ); - } - }, - { - name: 'includeDb', - type: 'confirm', - message: 'Do you want to pull the database from the snapshot?', - default: true, - when( { snapshotId } ) { - return snapshotId === true || ( typeof snapshotId === 'string' && snapshotId.trim().length > 0 ); - } - }, - ]; - - const { snapshotId, includeFiles, includeDb } = await inquirer.prompt( questions ); - - let selectedSnapshot; - if ( snapshotId === true ) { - selectedSnapshot = typeof snapshot === 'string' ? snapshot : snapshot[ 0 ]; - } else if ( snapshotId && typeof snapshotId === 'string' ) { - selectedSnapshot = snapshotId; - } - - if ( ! selectedSnapshot ) { - return; - } - - if ( spinner ) { - spinner.info( `Using ${ chalk.cyan( selectedSnapshot ) } snapshot...` ); - } else { - console.log( `Using ${ selectedSnapshot } snapshot...` ); - } - - const command = [ - 'pull', - selectedSnapshot, - `--main_domain=${ mainDomain }`, - '--confirm', - '--confirm_wp_version_change=no', - '--overwrite_local_copy', - '--suppress_instructions', - ]; - - if ( repository ) { - command.push( `--repository=${ repository }` ); - } - - if ( includeFiles ) { - command.push( '--include_files' ); - } else { - command.push( '--include_files=no' ); - } - - if ( includeDb ) { - command.push( '--include_db' ); - } else { - command.push( '--include_db=no' ); - } - - await wpsnapshots( env, command, 'inherit' ); - }; -}; diff --git a/src/commands/create/make-docker-compose.js b/src/commands/create/make-docker-compose.js index 0b686490..1f9c6ed1 100644 --- a/src/commands/create/make-docker-compose.js +++ b/src/commands/create/make-docker-compose.js @@ -4,6 +4,7 @@ const slugify = require( '@sindresorhus/slugify' ); const { cacheVolume } = require( '../../env-utils' ); const { images } = require( '../../docker-images' ); +const config = require( '../../configure' ); module.exports = function makeDockerCompose( spinner ) { return async ( hosts, settings ) => { @@ -24,6 +25,8 @@ module.exports = function makeDockerCompose( spinner ) { const { type: wordpressType } = wordpress || {}; const allHosts = [ ...hosts, ...hosts.map( ( host ) => `*.${ host }` ) ]; + const wpsnapshotsDir = await config.get( 'snapshotsPath' ); + const baseConfig = { // use version 2 so we can use limits version: '2.2', @@ -89,6 +92,8 @@ module.exports = function makeDockerCompose( spinner ) { if ( platform() == 'linux' ) { baseConfig.services.phpfpm.image = `wp-php-fpm-dev-${ phpVersion }-${ slugify( process.env.USER ) }`; baseConfig.services.phpfpm.volumes.push( `~/.ssh:/home/${ process.env.USER }/.ssh:cached` ); + baseConfig.services.phpfpm.volumes.push( `${ wpsnapshotsDir }:/home/${ process.env.USER }/.wpsnapshots:cached` ); + baseConfig.services.phpfpm.volumes.push( `~/.aws:/home/${ process.env.USER }/.aws:cached:ro` ); baseConfig.services.phpfpm.build = { dockerfile: 'php-fpm', context: '.containers', @@ -101,6 +106,8 @@ module.exports = function makeDockerCompose( spinner ) { } else { // the official containers for this project will have a www-data user. baseConfig.services.phpfpm.volumes.push( '~/.ssh:/home/www-data/.ssh:cached' ); + baseConfig.services.phpfpm.volumes.push( `${ wpsnapshotsDir }:/home/www-data/.wpsnapshots:cached` ); + baseConfig.services.phpfpm.volumes.push( '~/.aws:/home/www-data/.aws:cached:ro' ); } let nginxConfig = 'default.conf'; diff --git a/src/commands/wpsnapshots.js b/src/commands/wpsnapshots.js index e63f4fb4..2fda62eb 100644 --- a/src/commands/wpsnapshots.js +++ b/src/commands/wpsnapshots.js @@ -1,7 +1,12 @@ -const makeDocker = require( '../utils/make-docker' ); +const { execSync } = require( 'child_process' ); + const makeCommand = require( '../utils/make-command' ); const makeSpinner = require( '../utils/make-spinner' ); -const runSnapshots = require( '../utils/run-snapshots' ); +const shellEscape = require( 'shell-escape' ); +const envUtils = require( '../env-utils' ); +const gateway = require( '../gateway' ); +const environment = require( '../environment' ); +const compose = require( '../utils/docker-compose' ); exports.command = 'wpsnapshots '; exports.aliases = [ 'snapshots' ]; @@ -14,33 +19,54 @@ exports.builder = function( yargs ) { } ); }; -exports.handler = makeCommand( async function( { _, env, verbose } ) { +exports.handler = makeCommand( async function( { env, verbose } ) { + let envSlug = env; + if ( ! envSlug ) { + envSlug = await envUtils.parseOrPromptEnv(); + } + + if ( envSlug === false ) { + throw new Error( 'Error: Unable to determine which environment to use snapshots with. Please run this command from within your environment\'s directory.' ); + } + + const envPath = await envUtils.envPath( envSlug ); const spinner = ! verbose ? makeSpinner() : undefined; - // Get everything after the snapshots command, so we can pass to the docker container - let wpsnapshotsCommand = false; - const command = []; + // Check if the container is running, otherwise, start up the stacks + const isRunning = await compose.isRunning( envPath ); + if ( ! isRunning ) { + spinner && spinner.info( 'Environment is not running, starting it...' ); + await gateway.startGlobal( spinner ); + await environment.start( envSlug, spinner ); + } + + // Compose wp-cli command to run + let snapshotCommand = false; + const command = [ + 'wp', + 'snapshots', + ]; for ( let i = 0; i < process.argv.length; i++ ) { - if ( process.argv[i].toLowerCase() === _[0] ) { - wpsnapshotsCommand = true; - } else if ( wpsnapshotsCommand ) { + if ( process.argv[i].toLowerCase() === 'wpsnapshots' || process.argv[i].toLowerCase() === 'snapshots' ) { + snapshotCommand = true; + continue; + } + + if ( snapshotCommand ) { command.push( process.argv[i] ); } } try { - const docker = makeDocker(); - const wpsnapshots = runSnapshots( spinner, docker ); - await wpsnapshots( env, command, 'inherit' ); - } catch ( err ) { - if ( spinner ) { - if ( spinner.isSpinning ) { - spinner.stop(); - } - - spinner.fail( err ); - } else { - console.error( err ); - } + // Check for TTY + const ttyFlag = process.stdin.isTTY ? '' : ' -T'; + + // Run the command + execSync( `docker-compose exec${ ttyFlag } phpfpm ${ shellEscape( command ) }`, { + stdio: 'inherit', + cwd: envPath + } ); + } catch { + // do nothing } } ); diff --git a/src/docker-images.js b/src/docker-images.js index 72cb6fa5..9309b3a3 100644 --- a/src/docker-images.js +++ b/src/docker-images.js @@ -14,7 +14,6 @@ exports.images = { 'php7.1': '10up/wp-php-fpm-dev:7.1-ubuntu', 'php7.0': '10up/wp-php-fpm-dev:7.0-ubuntu', 'php5.6': '10up/wp-php-fpm-dev:5.6-ubuntu', - wpsnapshots: '10up/wpsnapshots:2', memcached: 'memcached:latest', nginx: 'nginx:latest', elasticsearch: 'docker.elastic.co/elasticsearch/elasticsearch:7.9.3', diff --git a/src/utils/run-snapshots.js b/src/utils/run-snapshots.js deleted file mode 100644 index 5d210629..00000000 --- a/src/utils/run-snapshots.js +++ /dev/null @@ -1,108 +0,0 @@ -const path = require( 'path' ); -const { execSync } = require( 'child_process' ); - -const fsExtra = require( 'fs-extra' ); -const which = require( 'which' ); -const shellEscape = require( 'shell-escape' ); - -const { images } = require( '../docker-images' ); -const { ensureNetworkExists, startGlobal } = require( '../gateway' ); -const envUtils = require( '../env-utils' ); - -async function ensureImageExists( spinner, docker ) { - const image = docker.getImage( images.wpsnapshots ); - const data = await image.inspect().catch( () => false ); - if ( ! data ) { - if ( spinner ) { - spinner.start( 'Pulling wpsnapshots image...' ); - } else { - console.log( 'Pulling wpsnapshots image' ); - } - - const stream = await docker.pull( images.wpsnapshots ); - - await new Promise( ( resolve ) => { - docker.modem.followProgress( stream, resolve, ( event ) => { - if ( ! spinner ) { - return; - } - - const { id, status, progressDetail } = event; - const { current, total } = progressDetail || {}; - const progress = total ? ` - ${ Math.ceil( ( current || 0 ) * 100 / total ) }%` : ''; - - spinner.text = `Pulling wpsnapshots image: [${ id }] ${ status }${ progress }...`; - } ); - } ); - - if ( spinner ) { - spinner.succeed( 'The wpsnapshots image has been pulled...' ); - } else { - console.log( ' - Done' ); - } - } -} - -module.exports = function runSnapshots( spinner, docker ) { - return async ( env, command, stdio ) => { - const wpsnapshotsDir = await envUtils.getSnapshotsPath(); - - // false catches the case when no subcommand is passed, and we just pass to snapshots to show usage - const subcommand = command[0]; - const bypassCommands = [ undefined, 'configure', 'help', 'list', 'create-repository' ]; - const noPathCommands = [ undefined, 'configure', 'help', 'list', 'create-repository', 'delete', 'search', 'download' ]; - - // Except for a few whitelisted commands, enforce a configuration before proceeding - if ( bypassCommands.indexOf( subcommand ) === -1 ) { - // Verify we have a configuration - const isConfigured = await fsExtra.pathExists( path.join( wpsnapshotsDir, 'config.json' ) ); - if ( ! isConfigured ) { - throw new Error( 'WP Snapshots does not have a configuration file. Please run "10updocker wpsnapshots configure" before continuing.' ); - } - } - - // These commands can be run without being in the context of a WP install - let envPath = ''; - if ( noPathCommands.indexOf( subcommand ) === -1 ) { - if ( env ) { - envPath = await envUtils.envPath( env ).catch( () => '' ); - } - - if ( ! envPath ) { - const envSlug = await envUtils.parseOrPromptEnv(); - if ( ! envSlug ) { - throw new Error( 'Unable to determine which environment to use wp snapshots with. Please run this command from within your environment.' ); - } else { - envPath = await envUtils.envPath( envSlug ).catch( () => '' ); - } - } - } - - await ensureImageExists( spinner, docker ); - await ensureNetworkExists( docker, spinner ); - - let network = ''; - let flags = '-it'; - - const dockerExec = await which( 'docker' ); - const volumes = [ `-v "${ wpsnapshotsDir }:/home/wpsnapshots/.wpsnapshots"` ]; - - if ( envPath ) { - await startGlobal( spinner ); - network = ' --network wplocaldocker'; - volumes.push( `-v "${ envPath }/wordpress:/var/www/html"` ); - command.push( '--db_user=root' ); - } - - if ( stdio === 'pipe' ) { - flags = '-i'; - } - - return execSync( `${ dockerExec } run ${ flags } --rm${ network } ${ volumes.join( ' ' ) } ${ images.wpsnapshots } ${ shellEscape( command ) }`, { - // @ts-ignore - stdio, - maxBuffer: 1 << 20, // 1mb - encoding: 'utf-8', - } ); - }; -};