Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mydumper integration #1962

Merged
merged 34 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a7b3113
Add functions for testing if a file is a mydumper file
abdullah-kasim Jul 26, 2024
9b4a143
Utilize wp search-replace if we're importing a mydumper file
abdullah-kasim Jul 26, 2024
52cb749
Remove yauzl by reverting back to trunk's shrinkwrap.
abdullah-kasim Jul 29, 2024
12874f8
Remove .zip file support.
abdullah-kasim Jul 29, 2024
fd67819
Consistency - wonder if we can turn this into an eslint rule
abdullah-kasim Jul 29, 2024
20104e1
Merge branch 'trunk' into esp-72/mydumper-support
abdullah-kasim Jul 29, 2024
d683089
Throw an error if search-and-replace is used for mydumper
abdullah-kasim Jul 30, 2024
6cf1100
Close streams once we're done using them to prevent hanging handles
abdullah-kasim Jul 30, 2024
b3f6f25
Switch to myloader if mydumper sqldump is detected
abdullah-kasim Jul 30, 2024
4485a2f
Merge branch 'esp-72/mydumper-support' of github.com:Automattic/vip-c…
abdullah-kasim Jul 30, 2024
b4aab5b
Fix copy-paste fail
abdullah-kasim Jul 31, 2024
893fdd6
readLine.close() doesn't generate an event, so we need to remove that…
abdullah-kasim Jul 31, 2024
fbc3cae
Fix myloader description
abdullah-kasim Jul 31, 2024
39b9a85
Fix myloader errors by switching to root
abdullah-kasim Jul 31, 2024
bd1aaf1
Eliminate tight coupling by moving all exec calls to another file and…
abdullah-kasim Jul 31, 2024
7d01388
Fix SQL validation for mydumper files
abdullah-kasim Jul 31, 2024
062e29a
Fix searchReplace not working with a single search-replace
abdullah-kasim Aug 1, 2024
35ae6a2
Add fixMyDumperTransform() so that we can use it to transform our myd…
abdullah-kasim Aug 1, 2024
6d2b707
Oops, ignore line if there's no match.
abdullah-kasim Aug 1, 2024
7f80865
Use a more concise regex
abdullah-kasim Aug 1, 2024
c07d6b5
Remove unused flags to simplify things a bit
abdullah-kasim Aug 1, 2024
b9d2d1d
Add mysqldump fixtures to hopefully make our testing easier
abdullah-kasim Aug 1, 2024
6d7ae75
Add tests and remove as much mocking as possible so that we won't hav…
abdullah-kasim Aug 1, 2024
f9e717d
Merge branch 'trunk' into esp-72/mydumper-support
abdullah-kasim Aug 1, 2024
ec587ee
Rename fixture files to fit its purpose
abdullah-kasim Aug 1, 2024
6b7b15a
Gunzip stream doesn't have the `.on('close')` event.
abdullah-kasim Aug 1, 2024
4a01b34
Merge branch 'esp-72/mydumper-support' of github.com:Automattic/vip-c…
abdullah-kasim Aug 1, 2024
55377bf
Add tracking where possible on the type of sqldump used
abdullah-kasim Aug 2, 2024
68d2b25
Update src/lib/database.ts
sanmai Aug 2, 2024
a7416dc
Grab shrinkwrap from trunk to keep things consistent
abdullah-kasim Aug 2, 2024
d7792bc
Merge remote-tracking branch 'origin/esp-72/readability-refactor' int…
abdullah-kasim Aug 2, 2024
83b3e1c
Remove wpSearchReplace again as we're not using it
abdullah-kasim Aug 2, 2024
286feff
Merge branch 'trunk' into esp-72/mydumper-support
abdullah-kasim Aug 2, 2024
62918e7
Merge branch 'trunk' into esp-72/mydumper-support
abdullah-kasim Aug 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions __fixtures__/dev-env-e2e/mydumper-detection.expected.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

-- metadata.header -1
# Started dump at: 2024-07-26 03:00:36
[config]
quote_character = BACKTICK

[myloader_session_variables]
SQL_MODE='NO_AUTO_VALUE_ON_ZERO,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION' /*!40101


-- some_db-schema-create.sql -1
/*!40101 SET NAMES utf8mb4*/;
/*!40014 SET FOREIGN_KEY_CHECKS=0*/;
/*!40101 SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'*/;
/*!40103 SET TIME_ZONE='+00:00' */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `some_db` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
16 changes: 16 additions & 0 deletions __fixtures__/dev-env-e2e/mydumper-detection.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

-- metadata.header 198
# Started dump at: 2024-07-26 03:00:36
[config]
quote_character = BACKTICK

[myloader_session_variables]
SQL_MODE='NO_AUTO_VALUE_ON_ZERO,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION' /*!40101


-- some_db-schema-create.sql 370
/*!40101 SET NAMES utf8mb4*/;
/*!40014 SET FOREIGN_KEY_CHECKS=0*/;
/*!40101 SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'*/;
/*!40103 SET TIME_ZONE='+00:00' */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `some_db` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
Binary file added __fixtures__/dev-env-e2e/mydumper-detection.sql.gz
Binary file not shown.
16 changes: 16 additions & 0 deletions __fixtures__/dev-env-e2e/mysqldump-detection.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- MySQL dump 10.13 Distrib 8.0.28, for Linux (x86_64)
--
-- Host: localhost Database: some_db
-- ------------------------------------------------------
-- Server version 8.0.28

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
Binary file added __fixtures__/dev-env-e2e/mysqldump-detection.sql.gz
Binary file not shown.
122 changes: 59 additions & 63 deletions __tests__/commands/dev-env-sync-sql.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,24 @@
import { replace } from '@automattic/vip-search-replace';
import fs, { ReadStream } from 'fs';
import fs from 'fs';
import Lando from 'lando';
import { WriteStream } from 'node:fs';
import { Interface } from 'node:readline';
import { PassThrough } from 'stream';
import path from 'path';

import { DevEnvImportSQLCommand } from '../../src/commands/dev-env-import-sql';
import { DevEnvSyncSQLCommand } from '../../src/commands/dev-env-sync-sql';
import { ExportSQLCommand } from '../../src/commands/export-sql';
import { unzipFile } from '../../src/lib/client-file-uploader';
import { getReadInterface } from '../../src/lib/validations/line-by-line';

/**
*
* @param {Array<{name, data}>} eventArgs Event arguments
* @param {timeout} timeout
*
* @return {Stream} A passthrough stream
*/
function getMockStream( eventArgs: { name: string; data?: string }[], timeout = 10 ) {
Copy link
Contributor Author

@abdullah-kasim abdullah-kasim Aug 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to refactor the tests by quite a bit - it's mocking too much and adjusting the mock is starting to take too much time. Instead, I don't mock it but instead, provide a small fixture that is also speedy.

const mockStream = new PassThrough();

if ( ! eventArgs ) {
eventArgs = [ { name: 'finish' } ];
}

// Leave 10ms of room for the listeners to setup
setTimeout( () => {
eventArgs.forEach( ( { name, data } ) => {
mockStream.emit( name, data );
} );
}, timeout );

return mockStream;
}

const mockReadStream: ReadStream = getMockStream(
[ { name: 'finish' }, { name: 'data', data: 'data' } ],
10
) as unknown as ReadStream;
const mockWriteStream: WriteStream = getMockStream(
[ { name: 'finish' } ],
20
) as unknown as WriteStream;

jest.spyOn( fs, 'createReadStream' ).mockReturnValue( mockReadStream );
jest.spyOn( fs, 'createWriteStream' ).mockReturnValue( mockWriteStream );
jest.spyOn( fs, 'renameSync' ).mockImplementation( () => {} );
jest.mock( '@automattic/vip-search-replace', () => {
return {
replace: jest.fn(),
};
} );
jest.mock( '../../src/lib/client-file-uploader', () => {
return {
unzipFile: jest.fn(),
};
} );
import * as clientFileUploader from '../../src/lib/client-file-uploader';

jest.mock( '../../src/lib/validations/line-by-line', () => {
jest.mock( '@automattic/vip-search-replace', () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { PassThrough } = require( 'node:stream' ) as typeof import('node:stream');
return {
getReadInterface: jest.fn(),
replace: jest.fn( ( ...args ) => {
return Promise.resolve( new PassThrough().pipe( args[ 0 ] ) );
} ),
};
} );

jest.mocked( replace ).mockResolvedValue( mockReadStream );
jest.mocked( unzipFile ).mockResolvedValue();
jest
.mocked( getReadInterface )
.mockResolvedValue( getMockStream( [ { name: 'close' } ], 100 ) as unknown as Interface );
jest.spyOn( clientFileUploader, 'unzipFile' );

jest.spyOn( console, 'log' ).mockImplementation( () => {} );

Expand Down Expand Up @@ -126,17 +76,55 @@ describe( 'commands/DevEnvSyncSQLCommand', () => {
} );

describe( '.runSearchReplace', () => {
it( 'should run search-replace operation on the SQL file', async () => {
it( 'should run search-replace operation on the mysqldump file', async () => {
const cmd = new DevEnvSyncSQLCommand( app, env, 'test-slug', lando );
fs.copyFileSync(
path.join( __dirname, '../../__fixtures__/dev-env-e2e/mysqldump-detection.sql.gz' ),
cmd.gzFile
);
fs.copyFileSync(
path.join( __dirname, '../../__fixtures__/dev-env-e2e/mysqldump-detection.sql' ),
cmd.sqlFile
);
await cmd.initSqlDumpType();
cmd.searchReplaceMap = { 'test.go-vip.com': 'test-slug.vipdev.lndo.site' };
cmd.slug = 'test-slug';

await cmd.runSearchReplace();
expect( replace ).toHaveBeenCalledWith( mockReadStream, [
expect( replace ).toHaveBeenCalledWith( expect.any( Object ), [
'test.go-vip.com',
'test-slug.vipdev.lndo.site',
] );
} );

it( 'should run search-replace operation on the mydumper file', async () => {
const cmd = new DevEnvSyncSQLCommand( app, env, 'test-slug', lando );
fs.copyFileSync(
path.join( __dirname, '../../__fixtures__/dev-env-e2e/mydumper-detection.sql.gz' ),
cmd.gzFile
);
fs.copyFileSync(
path.join( __dirname, '../../__fixtures__/dev-env-e2e/mydumper-detection.sql' ),
cmd.sqlFile
);
await cmd.initSqlDumpType();
cmd.searchReplaceMap = { 'test.go-vip.com': 'test-slug.vipdev.lndo.site' };
cmd.slug = 'test-slug';

await cmd.runSearchReplace();
expect( replace ).toHaveBeenCalledWith( expect.any( Object ), [
'test.go-vip.com',
'test-slug.vipdev.lndo.site',
] );

const fileContentExpected = fs.readFileSync(
path.join( __dirname, '../../__fixtures__/dev-env-e2e/mydumper-detection.expected.sql' ),
'utf8'
);
const fileContent = fs.readFileSync( cmd.sqlFile, 'utf8' );

expect( fileContent ).toBe( fileContentExpected );
} );
} );

describe( '.runImport', () => {
Expand All @@ -159,6 +147,14 @@ describe( 'commands/DevEnvSyncSQLCommand', () => {
const importSpy = jest.spyOn( syncCommand, 'runImport' );

beforeAll( () => {
fs.copyFileSync(
path.join( __dirname, '../../__fixtures__/dev-env-e2e/mysqldump-detection.sql.gz' ),
syncCommand.gzFile
);
fs.copyFileSync(
path.join( __dirname, '../../__fixtures__/dev-env-e2e/mysqldump-detection.sql' ),
syncCommand.sqlFile
);
exportSpy.mockResolvedValue();
searchReplaceSpy.mockResolvedValue();
importSpy.mockResolvedValue();
Expand All @@ -174,7 +170,7 @@ describe( 'commands/DevEnvSyncSQLCommand', () => {
await syncCommand.run();

expect( exportSpy ).toHaveBeenCalled();
expect( unzipFile ).toHaveBeenCalled();
expect( clientFileUploader.unzipFile ).toHaveBeenCalled();
expect( generateSearchReplaceMapSpy ).toHaveBeenCalled();
expect( searchReplaceSpy ).toHaveBeenCalled();
expect( importSpy ).toHaveBeenCalled();
Expand Down
7 changes: 7 additions & 0 deletions assets/dev-env.lando.template.yml.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,13 @@ tooling:
cmd:
- wp

db-myloader:
service: php
description: "Run mydumper's myloader to import database dumps generated by mydumper"
user: root
cmd:
- myloader -h database -u wordpress -p wordpress --database wordpress

db:
service: php
description: "Connect to the DB using mysql client (e.g. allow to run imports)"
Expand Down
10 changes: 9 additions & 1 deletion src/bin/vip-dev-env-import-sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { DevEnvImportSQLCommand } from '../commands/dev-env-import-sql';
import command from '../lib/cli/command';
import { getSqlDumpDetails } from '../lib/database';
import {
getEnvTrackingInfo,
handleCLIException,
Expand Down Expand Up @@ -66,9 +67,16 @@ command( {
.argv( process.argv, async ( unmatchedArgs, opt ) => {
const [ fileName ] = unmatchedArgs;
const slug = await getEnvironmentName( opt );
if ( opt.searchReplace && ! Array.isArray( opt.searchReplace ) ) {
opt.searchReplace = [ opt.searchReplace ];
}
const cmd = new DevEnvImportSQLCommand( fileName, opt, slug );
const dumpDetails = await getSqlDumpDetails( fileName );
const trackingInfo = getEnvTrackingInfo( cmd.slug );
const trackerFn = makeCommandTracker( 'dev_env_import_sql', trackingInfo );
const trackerFn = makeCommandTracker( 'dev_env_import_sql', {
...trackingInfo,
sqldump_type: dumpDetails.type,
} );
await trackerFn( 'execute' );

try {
Expand Down
30 changes: 26 additions & 4 deletions src/commands/dev-env-import-sql.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import chalk from 'chalk';
import fs from 'fs';
import os from 'os';

import * as exit from '../lib/cli/exit';
import { getFileMeta, unzipFile } from '../lib/client-file-uploader';
import { getSqlDumpDetails, SqlDumpDetails, SqlDumpType } from '../lib/database';
import {
processBooleanOption,
validateDependencies,
Expand Down Expand Up @@ -43,6 +45,9 @@ export class DevEnvImportSQLCommand {

validateImportFileExtension( this.fileName );

const dumpDetails = await getSqlDumpDetails( this.fileName );
const isMyDumper = dumpDetails.type === SqlDumpType.MYDUMPER;

// Check if file is compressed and if so, extract the
const fileMeta = await getFileMeta( this.fileName );
if ( fileMeta.isCompressed ) {
Expand Down Expand Up @@ -83,13 +88,13 @@ export class DevEnvImportSQLCommand {
const expectedDomain = `${ this.slug }.${ lando.config.domain }`;
await validateSQL( resolvedPath, {
isImport: false,
skipChecks: [],
skipChecks: isMyDumper ? [ 'dropTable', 'dropDB' ] : [],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dropTable and dropDB validations doesn't make sense in the mydumper context because isMyDumper uses --overwrite-table to perform any table/db dropping - hence we're ignoring it here.

extraCheckParams: { siteHomeUrlLando: expectedDomain },
} );
}

const fd = await fs.promises.open( resolvedPath, 'r' );
const importArg = this.getImportArgs();
const importArg = this.getImportArgs( dumpDetails );
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted to a function to satisfy eslint complexity limit - which I totally agree with.


const origIsTTY = process.stdin.isTTY;

Expand Down Expand Up @@ -131,10 +136,27 @@ export class DevEnvImportSQLCommand {
await addAdminUser( lando, this.slug );
}

public getImportArgs() {
const importArg = [ 'db', '--disable-auto-rehash' ].concat(
public getImportArgs( dumpDetails: SqlDumpDetails ) {
let importArg = [ 'db', '--disable-auto-rehash' ].concat(
this.options.quiet ? '--silent' : []
);
const threadCount = Math.max( os.cpus().length - 2, 1 );
if ( dumpDetails.type === SqlDumpType.MYDUMPER ) {
importArg = [
'db-myloader',
'--overwrite-tables',
`--source-db=${ dumpDetails.sourceDb }`,
`--threads=${ threadCount }`,
'--max-threads-for-schema-creation=10',
'--max-threads-for-index-creation=10',
'--skip-triggers',
'--skip-post',
'--innodb-optimize-keys',
'--checksum=SKIP',
'--metadata-refresh-interval=2000000',
'--stream',
].concat( this.options.quiet ? [ '--verbose=0' ] : [ '--verbose=3' ] );
}

return importArg;
abdullah-kasim marked this conversation as resolved.
Show resolved Hide resolved
}
Expand Down
Loading