Code and documentation fixes
This commit is contained in:
parent
6a42f2c887
commit
97a3d196f2
71
README.md
71
README.md
|
@ -1,63 +1,74 @@
|
|||
# MyVC (MySQL Version Control)
|
||||
|
||||
Utilities to ease the maintenance of MySQL database versioning using a Git
|
||||
repository.
|
||||
Utilities to ease the maintenance of MySQL or MariaDB database versioning using
|
||||
a Git repository.
|
||||
|
||||
This project is just to bring an idea to life and is still in an early stage of
|
||||
development, so it may not be fully functional.
|
||||
|
||||
Any help is welcomed! Feel free to contribute.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Required applications.
|
||||
|
||||
* Git
|
||||
* Node.js = 12.17.0 LTS
|
||||
* Git
|
||||
* Docker
|
||||
|
||||
## Installation
|
||||
|
||||
It's recommended to install the package globally.
|
||||
```
|
||||
```text
|
||||
# npm install -g myvc
|
||||
```
|
||||
|
||||
You can also install locally and use the *npx* command to execute it.
|
||||
```text
|
||||
$ npm install myvc
|
||||
$ npx myvc [action]
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
Export structure (uses production configuration).
|
||||
```
|
||||
$ myvc structure
|
||||
Execute *myvc* with the desired action.
|
||||
```text
|
||||
$ myvc [-w|--workdir] [-e|--env] [-h|--help] action
|
||||
```
|
||||
The default working directory is the current one and unless otherwise indicated,
|
||||
the default environment is *production*.
|
||||
|
||||
Export fixtures (uses production configuration).
|
||||
```
|
||||
$ myvc fixtures
|
||||
```
|
||||
Available actions are:
|
||||
* **structure**: Export the database structure.
|
||||
* **fixtures**: Export the database structure.
|
||||
* **routines**: Export database routines.
|
||||
* **apply**: Apply changes into database, uses *local* environment by default.
|
||||
* **run**: Builds and starts local database server container.
|
||||
* **start**: Starts local database server container.
|
||||
|
||||
Export routines.
|
||||
```
|
||||
$ myvc routines [environment]
|
||||
```
|
||||
|
||||
Apply changes into database.
|
||||
```
|
||||
$ myvc apply [-f] [-u] [environment]
|
||||
```
|
||||
Each action can have its own specific commandline options.
|
||||
|
||||
## Basic information
|
||||
|
||||
Create database connection configuration files for each environment at main
|
||||
project folder using the standard MySQL parameters. The predefined environment
|
||||
names are *production* and *testing*.
|
||||
```
|
||||
project folder using the standard MySQL *.ini* parameters. The predefined
|
||||
environment names are *production* and *testing*.
|
||||
```text
|
||||
db.[environment].ini
|
||||
```
|
||||
|
||||
Structure and fixture dumps are located inside *dump* folder.
|
||||
Structure and fixture dumps will be created inside *dump* folder.
|
||||
|
||||
* *structure.sql*
|
||||
* *fixtures.sql*
|
||||
* *fixtures.local.sql*
|
||||
|
||||
Routines are located inside *routines* folder. It includes procedures,
|
||||
functions, triggers, views and events with the following structure.
|
||||
```
|
||||
### Routines
|
||||
|
||||
Routines should be placed inside *routines* folder. All objects that have
|
||||
PL/SQL code are considered routines. It includes functions, triggers, views and
|
||||
events with the following structure.
|
||||
```text
|
||||
routines
|
||||
`- schema
|
||||
|- events
|
||||
|
@ -72,10 +83,10 @@ functions, triggers, views and events with the following structure.
|
|||
`- viewName.sql
|
||||
```
|
||||
|
||||
## Versions
|
||||
### Versions
|
||||
|
||||
Place your versions inside *changes* folder with the following structure.
|
||||
```
|
||||
Versions should be placed inside *changes* folder with the following structure.
|
||||
```text
|
||||
changes
|
||||
|- 00001-firstVersionCodeName
|
||||
| |- 00-firstExecutedScript.sql
|
||||
|
|
|
@ -4,11 +4,11 @@ FORCE=FALSE
|
|||
IS_USER=FALSE
|
||||
|
||||
usage() {
|
||||
echo "[ERROR] Usage: $0 [-f] [-u] [environment]"
|
||||
echo "[ERROR] Usage: $0 [-f] [-u] [-e environment]"
|
||||
exit 1
|
||||
}
|
||||
|
||||
while getopts ":fu" option
|
||||
while getopts ":fue:" option
|
||||
do
|
||||
case $option in
|
||||
f)
|
||||
|
@ -17,6 +17,9 @@ do
|
|||
u)
|
||||
IS_USER=TRUE
|
||||
;;
|
||||
e)
|
||||
ENV="$OPTARG"
|
||||
;;
|
||||
\?|:)
|
||||
usage
|
||||
;;
|
||||
|
@ -24,12 +27,11 @@ do
|
|||
done
|
||||
|
||||
shift $(($OPTIND - 1))
|
||||
ENV=$1
|
||||
|
||||
CONFIG_FILE="myvc.config.json"
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo "[ERROR] Config file not found in working directory."
|
||||
echo "[ERROR] Config file not found: $CONFIG_FILE"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
|
@ -69,12 +71,18 @@ else
|
|||
fi
|
||||
|
||||
if [ ! -f "$INI_FILE" ]; then
|
||||
echo "[ERROR] DB config file doesn't exists: $INI_FILE"
|
||||
echo "[ERROR] Database config file not found: $INI_FILE"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "[INFO] Using config file: $INI_FILE"
|
||||
|
||||
echo "SELECT 1;" | mysql --defaults-file="$INI_FILE" >> /dev/null
|
||||
|
||||
if [ "$?" -ne "0" ]; then
|
||||
exit 3
|
||||
fi
|
||||
|
||||
# Query functions
|
||||
|
||||
dbQuery() {
|
||||
|
@ -107,7 +115,7 @@ echo "[INFO] -> Commit: $DB_COMMIT"
|
|||
|
||||
if [[ ! "$DB_VERSION" =~ ^[0-9]*$ ]]; then
|
||||
echo "[ERROR] Wrong database version."
|
||||
exit 3
|
||||
exit 4
|
||||
fi
|
||||
if [[ -z "$DB_VERSION" ]]; then
|
||||
DB_VERSION=10000
|
||||
|
@ -232,7 +240,7 @@ applyRoutines() {
|
|||
ACTION="DROP"
|
||||
fi
|
||||
|
||||
echo "[INFO] -> $ROUTINE_TYPE $ROUTINE_NAME: $ACTION"
|
||||
echo "[INFO] -> $ACTION: $ROUTINE_TYPE $ROUTINE_NAME"
|
||||
|
||||
if [ "$ACTION" == "REPLACE" ]; then
|
||||
dbExecFromFile "$FILE_PATH" "$SCHEMA"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
const execFileSync = require('child_process').execFileSync;
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
module.exports = function(command) {
|
||||
module.exports = function(command, workdir, ...args) {
|
||||
const buildArgs = [
|
||||
'build',
|
||||
'-t', 'myvc/client',
|
||||
|
@ -11,15 +11,15 @@ module.exports = function(command) {
|
|||
];
|
||||
execFileSync('docker', buildArgs);
|
||||
|
||||
let args = [
|
||||
let runArgs = [
|
||||
'run',
|
||||
'-v', `${process.cwd()}:/workdir`,
|
||||
'-v', `${workdir}:/workdir`,
|
||||
'myvc/client',
|
||||
command
|
||||
];
|
||||
args = args.concat(process.argv.slice(2));
|
||||
runArgs = runArgs.concat(args);
|
||||
|
||||
const child = spawn('docker', args, {
|
||||
const child = spawn('docker', runArgs, {
|
||||
stdio: [
|
||||
process.stdin,
|
||||
process.stdout,
|
||||
|
|
10
docker.js
10
docker.js
|
@ -6,7 +6,7 @@ const path = require('path');
|
|||
const serverImage = require(`${cwd}/myvc.config.json`).serverImage;
|
||||
|
||||
module.exports = class Docker {
|
||||
constructor(name) {
|
||||
constructor(name, context) {
|
||||
Object.assign(this, {
|
||||
id: name,
|
||||
name,
|
||||
|
@ -16,7 +16,9 @@ module.exports = class Docker {
|
|||
port: '3306',
|
||||
username: 'root',
|
||||
password: 'root'
|
||||
}
|
||||
},
|
||||
imageTag: name || 'myvc/dump',
|
||||
context
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -35,7 +37,7 @@ module.exports = class Docker {
|
|||
let d = new Date();
|
||||
let pad = v => v < 10 ? '0' + v : v;
|
||||
let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
||||
await this.execP(`docker build --build-arg STAMP=${stamp} -f ${dockerfilePath}.dump -t ${serverImage} ${cwd}`);
|
||||
await this.execP(`docker build --build-arg STAMP=${stamp} -f ${dockerfilePath}.dump -t ${this.serverImage} ${this.context}`);
|
||||
|
||||
let dockerArgs;
|
||||
|
||||
|
@ -50,7 +52,7 @@ module.exports = class Docker {
|
|||
|
||||
let runChown = process.platform != 'linux';
|
||||
|
||||
const container = await this.execP(`docker run --env RUN_CHOWN=${runChown} -d ${dockerArgs} ${serverImage}`);
|
||||
const container = await this.execP(`docker run --env RUN_CHOWN=${runChown} -d ${dockerArgs} ${this.serverImage}`);
|
||||
this.id = container.stdout.trim();
|
||||
|
||||
try {
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
CONFIG_FILE="myvc.config.json"
|
||||
CONFIG_FILE=$1
|
||||
INI_FILE=$2
|
||||
DUMP_FILE="dump/fixtures.sql"
|
||||
INI_FILE="db.production.ini"
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo "Config file not found in working directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "SELECT 1;" | mysql --defaults-file="$INI_FILE" >> /dev/null
|
||||
echo "" > "$DUMP_FILE"
|
||||
|
|
|
@ -1,26 +1,18 @@
|
|||
#!/usr/bin/node
|
||||
const fs = require('fs-extra');
|
||||
const ini = require('ini');
|
||||
const mysql = require('mysql2/promise');
|
||||
const ejs = require('ejs');
|
||||
|
||||
let cwd = process.cwd();
|
||||
let env = process.argv[2];
|
||||
let iniFile = env ? `db.${env}.ini` : `${__dirname}/db.ini`;
|
||||
let dbConf = ini.parse(fs.readFileSync(iniFile, 'utf8')).client;
|
||||
let exportDir = `${cwd}/routines`;
|
||||
let config = require(`${cwd}/myvc.config.json`);
|
||||
|
||||
class Exporter {
|
||||
constructor(objectName, callback) {
|
||||
this.objectName = objectName;
|
||||
this.callback = callback;
|
||||
this.dstDir = `${objectName}s`;
|
||||
|
||||
let templateDir = `${__dirname}/templates/${objectName}`;
|
||||
const templateDir = `${__dirname}/templates/${objectName}`;
|
||||
this.query = fs.readFileSync(`${templateDir}.sql`, 'utf8');
|
||||
|
||||
let templateFile = fs.readFileSync(`${templateDir}.ejs`, 'utf8');
|
||||
const templateFile = fs.readFileSync(`${templateDir}.ejs`, 'utf8');
|
||||
this.template = ejs.compile(templateFile);
|
||||
|
||||
if (fs.existsSync(`${templateDir}.js`))
|
||||
|
@ -28,10 +20,10 @@ class Exporter {
|
|||
}
|
||||
|
||||
async export(conn, exportDir, schema) {
|
||||
let res = await conn.execute(this.query, [schema]);
|
||||
const res = await conn.execute(this.query, [schema]);
|
||||
if (!res[0].length) return;
|
||||
|
||||
let routineDir = `${exportDir}/${schema}/${this.dstDir}`;
|
||||
const routineDir = `${exportDir}/${schema}/${this.dstDir}`;
|
||||
if (!fs.existsSync(routineDir))
|
||||
fs.mkdirSync(routineDir);
|
||||
|
||||
|
@ -46,7 +38,7 @@ class Exporter {
|
|||
}
|
||||
}
|
||||
|
||||
let exporters = [
|
||||
const exporters = [
|
||||
new Exporter('function'),
|
||||
new Exporter('procedure'),
|
||||
new Exporter('view'),
|
||||
|
@ -56,27 +48,10 @@ let exporters = [
|
|||
|
||||
// Exports objects for all schemas
|
||||
|
||||
async function main() {
|
||||
let ssl;
|
||||
if (dbConf.ssl_ca) {
|
||||
ssl = {
|
||||
ca: fs.readFileSync(`${cwd}/${dbConf.ssl_ca}`),
|
||||
rejectUnauthorized: dbConf.ssl_verify_server_cert != undefined
|
||||
}
|
||||
}
|
||||
module.exports = async function main(opts, config, dbConf) {
|
||||
const exportDir = `${opts.workdir}/routines`;
|
||||
|
||||
let conn = await mysql.createConnection({
|
||||
host: !env ? 'localhost' : dbConf.host,
|
||||
port: dbConf.port,
|
||||
user: dbConf.user,
|
||||
password: dbConf.password,
|
||||
authPlugins: {
|
||||
mysql_clear_password() {
|
||||
return () => dbConf.password + '\0';
|
||||
}
|
||||
},
|
||||
ssl
|
||||
});
|
||||
const conn = await mysql.createConnection(dbConf);
|
||||
conn.queryFromFile = function(file, params) {
|
||||
return this.execute(
|
||||
fs.readFileSync(`${file}.sql`, 'utf8'),
|
||||
|
@ -104,5 +79,4 @@ async function main() {
|
|||
} finally {
|
||||
await conn.end();
|
||||
}
|
||||
}
|
||||
main();
|
||||
};
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
CONFIG_FILE="myvc.config.json"
|
||||
CONFIG_FILE=$1
|
||||
INI_FILE=$2
|
||||
DUMP_FILE="dump/structure.sql"
|
||||
INI_FILE="db.production.ini"
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo "Config file found in working directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCHEMAS=( $(jq -r ".structure[]" "$CONFIG_FILE") )
|
||||
|
||||
|
|
121
index.js
121
index.js
|
@ -4,65 +4,128 @@ const getopts = require('getopts');
|
|||
const package = require('./package.json');
|
||||
const dockerRun = require('./docker-run');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const ini = require('ini');
|
||||
|
||||
console.log('MyVC (MySQL Version Control)'.green, `v${package.version}`.blue);
|
||||
console.log('MyVC (MySQL Version Control)'.green, `v${package.version}`.magenta);
|
||||
|
||||
const options = getopts(process.argv.slice(2), {
|
||||
const argv = process.argv.slice(2);
|
||||
const opts = getopts(argv, {
|
||||
alias: {
|
||||
dir: 'd',
|
||||
env: 'e',
|
||||
workdir: 'w',
|
||||
help: 'h',
|
||||
version: 'v'
|
||||
},
|
||||
default: {}
|
||||
default: {
|
||||
workdir: process.cwd(),
|
||||
env: 'production'
|
||||
}
|
||||
})
|
||||
|
||||
if (opts.version)
|
||||
process.exit(0);
|
||||
|
||||
function usage() {
|
||||
console.log('Usage:'.gray, 'myvc [-d|--dir] [-e|--env] [-h|--help] action'.magenta);
|
||||
console.log('Usage:'.gray, 'myvc [-w|--workdir] [-e|--env] [-h|--help] action'.magenta);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (options.help) usage();
|
||||
if (options.version) process.exit(0);
|
||||
|
||||
let config;
|
||||
let container;
|
||||
|
||||
let action = options._[0];
|
||||
if (action) {
|
||||
console.log('Action:'.gray, action.magenta);
|
||||
|
||||
const configFile = 'myvc.config.json';
|
||||
if (!fs.existsSync(configFile)) {
|
||||
console.error('Error:'.gray, `Config file '${configFile}' not found in working directory`.red);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
config = require(`${process.cwd()}/${configFile}`);
|
||||
function error(message) {
|
||||
console.error('Error:'.gray, message.red);
|
||||
process.exit(1);
|
||||
}
|
||||
function parameter(parameter, value) {
|
||||
console.log(parameter.gray, value.blue);
|
||||
}
|
||||
|
||||
const action = opts._[0];
|
||||
if (!action) usage();
|
||||
|
||||
const actionArgs = {
|
||||
apply: {
|
||||
alias: {
|
||||
force: 'f',
|
||||
user: 'u'
|
||||
},
|
||||
default: {
|
||||
force: false,
|
||||
user: false,
|
||||
env: 'test'
|
||||
}
|
||||
}
|
||||
};
|
||||
const actionOpts = getopts(argv, actionArgs[action]);
|
||||
Object.assign(opts, actionOpts);
|
||||
|
||||
parameter('Environment:', opts.env);
|
||||
parameter('Workdir:', opts.workdir);
|
||||
parameter('Action:', action);
|
||||
|
||||
// Configuration file
|
||||
|
||||
const configFile = 'myvc.config.json';
|
||||
const configPath = path.join(opts.workdir, configFile);
|
||||
if (!fs.existsSync(configPath))
|
||||
error(`Config file not found: ${configFile}`);
|
||||
const config = require(configPath);
|
||||
|
||||
// Database configuration
|
||||
|
||||
let iniFile = 'db.ini';
|
||||
let iniDir = __dirname;
|
||||
if (opts.env) {
|
||||
iniFile = `db.${opts.env}.ini`;
|
||||
iniDir = opts.workdir;
|
||||
}
|
||||
const iniPath = path.join(iniDir, iniFile);
|
||||
|
||||
if (!fs.existsSync(iniPath))
|
||||
error(`Database config file not found: ${iniFile}`);
|
||||
|
||||
const iniConfig = ini.parse(fs.readFileSync(iniPath, 'utf8')).client;
|
||||
const dbConfig = {
|
||||
host: !opts.env ? 'localhost' : iniConfig.host,
|
||||
port: iniConfig.port,
|
||||
user: iniConfig.user,
|
||||
password: iniConfig.password,
|
||||
authPlugins: {
|
||||
mysql_clear_password() {
|
||||
return () => iniConfig.password + '\0';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (iniConfig.ssl_ca) {
|
||||
dbConfig.ssl = {
|
||||
ca: fs.readFileSync(`${opts.workdir}/${iniConfig.ssl_ca}`),
|
||||
rejectUnauthorized: iniConfig.ssl_verify_server_cert != undefined
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
|
||||
switch (action) {
|
||||
case 'structure':
|
||||
dockerRun('export-structure.sh');
|
||||
dockerRun('export-structure.sh', opts.workdir, configFile, iniFile);
|
||||
break;
|
||||
case 'fixtures':
|
||||
dockerRun('export-fixtures.sh');
|
||||
dockerRun('export-fixtures.sh', opts.workdir, configFile, iniFile);
|
||||
break;
|
||||
case 'routines':
|
||||
require('./export-routines');
|
||||
require('./export-routines')(opts, config, dbConfig);
|
||||
break;
|
||||
case 'apply':
|
||||
dockerRun('apply-changes.sh');
|
||||
dockerRun('apply-changes.sh', opts.workdir, ...argv);
|
||||
break;
|
||||
case 'run': {
|
||||
const Docker = require('./docker');
|
||||
container = new Docker();
|
||||
const container = new Docker(config.code, opts.workdir);
|
||||
container.run();
|
||||
break;
|
||||
}
|
||||
case 'start': {
|
||||
const Docker = require('./docker');
|
||||
container = new Docker();
|
||||
const container = new Docker(config.code, opts.workdir);
|
||||
container.start();
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "myvc",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"author": "Verdnatura Levante SL",
|
||||
"description": "MySQL Version Control",
|
||||
"license": "GPL-3.0",
|
||||
|
|
Loading…
Reference in New Issue