Giter Site home page Giter Site logo

mbucc / shmig Goto Github PK

View Code? Open in Web Editor NEW
459.0 14.0 49.0 117 KB

Database migration tool written in BASH.

License: BSD 3-Clause "New" or "Revised" License

Makefile 2.68% Shell 92.83% PLpgSQL 2.39% Dockerfile 2.11%
database-migrations sql shell devops mysql postgresql sqlite3

shmig's Introduction

SHMIG Build Status

A database migration tool written in BASH consisting of just one file - shmig.

Automated Tests

Shell DB Result
/bin/bash sqlite3
/bin/bash mysql:5.7
/bin/bash postgres:9.6

See https://github.com/mbucc/shmig_test.

Quick Start

  $ cd shmig
  $ make install
  $ cd $HOME
  $ mkdir migrations
  $ shmig -t sqlite3 -d test.db create mytable
  generated ./migrations/1470490964-mytable.sql
  $ cat ./migrations/1470490964-mytable.sql
  -- Migration: mytable
  -- Created at: 2016-08-06 09:42:44
  -- ====  UP  ====

  BEGIN;
  	PRAGMA foreign_keys = ON;

  COMMIT;

  -- ==== DOWN ====

  BEGIN;

  COMMIT;
  $ # In normal usage, you would add SQL to this migration file.
  $ shmig -t sqlite3 -d test.db migrate
  shmig: creating migrations table: shmig_version
  shmig: applying  'mytable'    (1470490964)... done
  $ ls -l test.db
  -rw-r--r--  1 mark  staff  12288 Aug  6 09:41 test.db
  $ shmig -t sqlite3 -d test.db rollback
  shmig: reverting 'mytable'    (1470490964)... done
  $ shmig -h | wc -l
  73
  $

Edit the function sqlite3_up_text() and sqlite3_down_text() in shmig if you don't like the default SQL template.

Why?

Currently there are lots of database migration tools such as DBV, Liquibase, sqitch, Flyway and other framework-specific ones (for Ruby on Rails, Yii, Laravel, ...). But they all are pretty heavy, with lots of dependencies (or even unusable outside of their stack), some own DSLs...

I needed some simple, reliable solution with minimum dependencies and able to run in pretty much any POSIX-compatible environment against different databases (PostgreSQL, MySQL, SQLite3).

And here's the result.

Idea

RDMS'es are bundled along with their console clients. MySQL has mysql, PostgreSQL has psql and SQLite3 has sqlite3. And that's it! This is enough for interacting with database in batch mode w/o any drivers or connectors.

Using client options one can make its output suitable for batch processing with standard UNIX text-processing tools (sed, grep, awk, ...). This is enough for implementing simple migration system that will store current schema version information withing database (see SCHEMA_TABLE variable in shmig.conf.example).

Usage

SHMIG tries to read configuration from the configuration file shmig.conf in the current working directory. A sample configuration file is shmig.conf.example.

You can also provide an optional config override file by creating the file shmig.local.conf. This allows you to provide a default configuration which is version-controlled with your project, then specify a non-version-controlled local config file that you can use to provide instance-specific config. (An alternative is to use envrionment variables, though some people prefer concrete files to nebulous environment variables.) This works even with custom config files specified with the -c option.

You can also configure SHMIG from command line, or by using environmental variables. The command line settings have higher priority than configuration files or environment settings.

Required options are:

  1. TYPE or -t - database type
  2. DATABASE or -d - database to operate on
  3. MIGRATIONS or -m - directory with migrations

All other options (see shmig.conf.example and shmig -h) are not necessary.

To simplify usage, create shmig.conf in your project root directory with your configuration directives. When you shmig <action> ... in that directory, shmig will use the configuration in that file.

For detailed information see shmig.conf.example and shmig -h.

Migrations

Migrations are SQL files whose name starts with "<UNIX TIMESTAMP>-" and end with ".sql". The order that new migrations are applied is determined by the seconds-since-epoch time stamp in the filename, with the oldest migration going first.

Each migration contains two special markers: -- ==== UP ==== that marks start of section that will be executed when migration is applied and -- ==== DOWN ==== that marks start of section that will be executed when migration is reverted.

For example:

-- Migration: create users table
-- Created at: 2013-10-02 07:03:11
-- ====  UP  ====
CREATE TABLE `users`(
  id int not null primary key auto_increment,
  name varchar(32) not null,
  email varchar(255) not null
);

CREATE UNIQUE INDEX `users_email_uq` ON `users`(`email`);
-- ==== DOWN ====
DROP TABLE `users`;

Everything between -- ==== UP ==== till -- ==== DOWN ==== will be executed when migration is applied and everything between -- ==== DOWN ==== till the end of file will be executed when migration is reverted. If migration is missing marker or contents of marker is empty then appropriate action will fail (i.e. if you're trying to revert migration that has no or empty -- ==== DOWN ==== marker you'll get an error and script won't execute any migrations following script with error). Also note those semicolons terminating statements. They're required because you're basically typing that into your database CLI client.

SHMIG can generate skeleton migration for you, see create action.

Migrations with test data

One nice feature of Liquibase is contexts, which are used to implement different behavior based on environment; for example, in a development environment you can insert test data.

shmig can support this with symbolic links. For example, say your production migrations are in prod and test data in test:

.
└── migrations
    ├── prod
    │   └── 1485643154-create_table.sql
    └── test
        └── 1485648520-testdata.sql

To create a test environment context, link the prod SQL in test directory:

$ cd migrations/test/
$ ln -s ../prod/1485643154-create_table.sql
.
└── migrations
    ├── prod
    │   └── 1485643154-create_table.sql
    └── test
        ├── 1485643154-create_table.sql -> ../prod/1485643154-create_table.sql
        └── 1485648520-testdata.sql

When applying migrations to test, point shmig to the test directory either via the command line or using the local config override file.

Since migrations are applied in order of epoch seconds in the file name, this works.

Current state

Stable and maintained. Pull requests welcome.

Security considerations

Password is passed to mysql and psql via environment variable. This can be a security issue if your system allows other users to read environment of process that belongs to another user. In most Linux distributions with modern kernels this is forbidden. You can check this (on systems supporting /proc file system) like this: cat /proc/1/env - if you get permission denied error then you're secure.

Efficiency

Because SHMIG is just a shell script it's not a speed champion. Every time a statement is executed new client process is spawned. I didn't experience much issues with speed, but if you'll have then please file an issue and maybe I'll get to that in detail.

Usage with Docker

Shmig can be used and configured with env vars

docker run -e PASSWORD=root -e HOST=mariadb -v $(pwd)/migrations:/sql --link mariadb:mariadb mkbucc/shmig:latest -t mysql -d db-name up

This will attempt 3 retries of the command before exiting. This can be useful in a docker-compose set up where the SHMIG container depends_on another DB service within the docker-compose configuration.

OS Packaging

A Debian package is available for shmig at https://packages.kaelshipman.me.

NixOS supports shmig on Linux and Darwin at the moment, the package can be installed into the user's profile by running nix-env -iA nixos.shmig since 18.03.

*Contributions for other systems would be greatly welcomed, and can be submitted via PR to this repo.

Todo

  1. Speed. Some optimizations are definitely possible to speed things up.
  2. A way to spawn just one CLI client. Maybe something with FIFOs and SIGCHLD handler.
  3. Better documentation :\

shmig's People

Contributors

adlenafane avatar agonen avatar alk-jbrochet avatar aw avatar blockloop avatar dmunch avatar gsilk avatar kael-shipman avatar leyyinad avatar ma27 avatar mbucc avatar naquad avatar rgmccaw avatar shamrin avatar silenius avatar srghma avatar synchrone avatar ubergesundheit avatar vehsamrak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

shmig's Issues

Reconsider fixing shellcheck SC2155

Shellcheck SC2155 says "declare and assign separately to avoid masking return values". For example, it prefers:

local migration
migration=$(...)

versus original:

local migration=$(find_migrations ...)

I think it's not a good idea to follow SC2155 in shmig:

  1. We never need to check for return codes in these local declarations.
  2. It becomes easy to forget proper local declaration when it's on a separate line. We even have an example in the latest commit to master: 2d2ccc5#diff-b77c435350fe92985b2413c0bde725feR496. Unfortunately, shmig is not catching this error.

mktemp

Forks keep replacing the weird way I create temp files with mktemp. Originally, @naquad used pipes, which are generally a better solution than temp files. I switched to temp files to fix issue #24. Maybe https://github.com/cheusov/pipestatus could be used to go back to pipes. Or just use mktemp.

Doesn't work in ash (Alpine's default shell)

Bash doesn't come on Alpine by default, so:

bbox:~/src/shmig$ ./shmig -h
-ash: ./shmig: not found

Tried changing shebang to #!/bin/sh, but that failed with:

./shmig: line 169: syntax error: unexpected "(" (expecting "}")

which refers to

bbox:~/src/shmig$ grep -n ^ shmig | grep -C 3 169:
166:
167:  "${TYPE}_cli" "$show" | grep -F -qx "$SCHEMA_TABLE"
168:
169:  local status=("${PIPESTATUS[@]}")
170:
171:  [[ ${status[0]} -ne 0 ]] && return 1
172:

Thanks for your donation

Hi,

Thanks for your donation to acme.sh.

Yes, you can copy and use any code of acme.sh or acmetest.

Thanks.

Mariadb Out of range value for column

Hi.
I'll try use shmig in docker compose with mariadb 10.3

Migrate success but I get error when tool try write migrate table

ERROR 1264 (22003) at line 4: Out of range value for column 'version' at row 1

'till' argument in downward migrations should stop _before_ undoing that migration

@mbucc, again, very sorry to pepper you with all this stuff. I hope I'm not bothering you too much. (If I am, I can just maintain my own fork of shmig and not worry about trying to stay compatible, but of course, I'd rather there only be one shmig ;).)

This time I've got a somewhat more philosophical issue. When I run shmig up till=some-migration, I expect some-migration to have been applied. Similarly, when I run shmig down till=some-migration, I still expect some-migration to be applied.

Think of it this way: Assuming 123456789-some_migration.sql is the most recent migration, the following calls should produce no state changes at all.

shmig up till=123456789
shmig down till=123456789
shmig up till=123456789
shmig down till=123456789

This makes sense because the till argument should presumably indicate that I want to be on that migration.

Currently, however, the till argument produces the desired effect on upward migrations, but it runs the downward migration through the till argument, not to it.

Transfer ownership so I can use Dockerhub

@naquad Do you mind transferring ownership of this repo to me?

Looks like in order to use Dockerhub, I need to owner the repo so I can setup automated builds. I tried, and it only could see my mbucc/shmig repo. (Which I deleted, as I only commit here now.)

I have a current project where I will use shmig on. I want to try out docker containers for automated testing of shmig.

Support for ElasticSearch

Hi,

Seems like there is no db version manager for ElasticSearch.
I was wondering if it make sense to add ES support for shmig?

I guess client will be curl and the version will be kept in a document.
Can it work and is it worth it?

OSX support

Hi,

Both getopts and awk -v work differently on the BSD's (including OSX). I can think of two ways to handle this:

(1) detect OS in shell script and use correct format, or

(2) stick to posix (for example, see getopts here: http://shellhaters.heroku.com/posix)

My preference would be the latter. You lose some sugar (for example, the -s, -o, -l, and -n options for getopts), but it's simpler.

Add config item for recursive migration directories

Sorry to hammer you with change requests :P. I just thought that allowing recursion in migration directories would make a practical implementation of different migration environments easier. For example:

db/
├── backups/
├── init.sql
└── migrations
    ├── schema/
    │   ├── 1485643154-create_my_cool_table.sql
    │   ├── 1485648520-create_other_cool_table.sql
    │   └── 1485648600-alter_indices.sql
    ├── dev/
    │   ├── 1485643200-add_dev_fixtures_to_cool_table.sql
    │   ├── 1485648700-add_other_dev_fixtures_to_other_cool_table.sql
    │   └── schema > ../schema
    └── prod/
        ├── 1485648700-add_important_production_data.sql
        └── schema > ../schema

With this setup, you can see that as you create schema modifications in schema, they're automatically included in both of the environment folders, dev and prod, while these respective folders can add migrations of their own. However, this only works if the find_migrations command relaxes its max-depth parameter.

Create a release

This is an excellent tool. Having a zip file of the source to download directly from GitHub would be ideal for installing this on servers without git installed. You can do this from the releases tab here.

Allow `-a` argument to _add to_ previously-set `$ARGS`, rather than overwrite

$ARGS is special in that it contains command-line flags, which can usually be reversed (usually -- like --triggers and --no-triggers, for example). Thus, I believe the -a flag should add to any args that were set in config files, rather than overwrite them. My particular use-case is this:

I have several database-driven webservices on a server. Each webservice has its own database and its own user. To safely handle user credentials per webservice using shmig, I'm using a service-specific my.cnf file, which I specify to mysql by setting ARGS="--defaults-extra-file=$PWD/my.cnf" in shmig.local.conf. However, I also have a deployment automater that I use to run shmig when I push the right git branch up, and that adds the -f argument on downward migrations to force mysql to continue with the entire migration script in the event of errors (only for downward migrations). My issue is that I don't want to specify --defaults-extra-file=... in both my shmig.local.conf and my deployment automator's command-line call to shmig.

color auto doesn't really work

at least on zsh.

mark@marks-mbp:~/src/events/db% echo $0
-zsh
mark@marks-mbp:~/src/events/db% shmig -C auto
Usage: shmig [options] <action> [arguments...]
Where options include are:
  -h
      show a more detailed usage

  -C COLOR
      set color policy. possible options:
          auto - default. will use colors if possible
          never - do not use colors
          always - always use colors

and action is one of create, up, down, rollback, redo, and pending

shmig: \e[1m\e[93maction\e[0m \e[31mrequired\e[0m

Endless loop in latest

Docker container mkbucc/shmig:latest hangs in an endless loop, consuming 100% CPU (one core) after printing shmig: creating migrations table: shmig_version.

The only difference is 0ef3612.

The issue is not present in mkbucc/shmig:verbose, which is currently built on 2017-07-24T11:48:19.724Z, and does not have this shebang patch applied.

If DB up-to-date, exit status is 1

bbox:/service/elmarelli-api$ shmig -c db/shmig.conf up
shmig: creating migrations table: shmig_version
shmig: applying  'venue_events'	(1485620534)... done
shmig: applying  'fan'	(1488727517)... done
bbox:/service/elmarelli-api$ echo $?
0
bbox:/service/elmarelli-api$ shmig -c db/shmig.conf up
bbox:/service/elmarelli-api$ echo $?
1

Invalid command-line argument outputs "command not found" error

Marks-MBP-3:shmig mark$ ./shmig -c test
/bin/test: line 1: ????: command not found
Usage: shmig [options] <action> [arguments...]
Common options:
  -d <database name>
  -t [mysql|postgresql|sqlite3]
  -h
and action is one of create, up, down, rollback, status, redo, and pending.

Migration is 'done' but fails as expected when error occures

shmig: applying  'migration_with_sql_syntax_error'	(1541754135)... done
ERROR 1064 (42000) at line 3: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ...

I'll try to fix this behaviour, so when error occures, 'error' status must be set instead of 'done'.
Like this:

shmig: applying  'migration_with_sql_syntax_error'	(1541754135)... error

Don't require connection for generating scripts

shmig create add-people exits with

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)

Is there any reason that a connection is required to generate a script?

error when cwd is read only

With

docker-compose

  migrator:
    image: mkbucc/shmig:latest
    entrypoint: '' # i hate people that use custom entrypoints
    command: /bin/true # do nothing on `docker-compose up`
    working_dir: /migrations
    environment:
      TYPE:     postgresql
      LOGIN:    app_admin
      PASSWORD: app_admin_pass
      HOST:     postgres
      PORT:     5432
      DATABASE: gb_test
    volumes:
      - ../../migrations:/migrations:ro # READ-ONLY!!!, if i make it rw - error will dissappear 
      - ../../scripts/waitforit:/bin/waitforit:ro
      - ../../scripts/wait-for-postgres:/bin/wait-for-postgres:ro

makefile

feature_tests_dev_db_migrate:
	docker-compose \
		-p "$(PROJECT_NAME)_feature_tests_dev" \
		-f docker/feature_tests_dev.yml \
		run --rm migrator sh -c '\
		/bin/waitforit -host=$$HOST -port=$$PORT -timeout=10 && \
		DBNAME=postgres://$$LOGIN:$$PASSWORD@$$HOST:$$PORT/$$DATABASE /bin/wait-for-postgres && \
		shmig -m /migrations -C always migrate'

I'm gettirng error

/bin/shmig: line 169: 332874447.1.out: Read-only file system

Implement system for schema table migrations

This is not a common use-case, but we should account for times when we want to add or change columns in the shmig schema table.

In my case, when I run shmig status, I'd like to see the names of migrations together with their version; something like this:

Applied migrations:
20180702_142703	initial_seed_data	2018-07-13 22:12:23
20180702_134037	init			2018-07-13 22:12:11

It's easy to make this change, but of course it would potentially break implementations currently running.

Not sure what the best solution to this might be, but I thought I'd put it out there for us to think about....

Sqlite fails when custom sqliterc exists

Hi, I was having a difficult time getting the tool to work until I realized that my personal ~/.sqliterc file was causing grep to fail in the shmig script. So if you're having issues that look like the following:

➜ shmig status
shmig: creating migrations table: shmig_version
Error: near line 1: table `shmig_version` already exists

Try adding this to your shmig.conf to reset sqlite3 to its default client mode.

ARGS="-list -noheader"

or use -a "-list -noheader" on the command line.

For reference, my .sqliterc is as follows:

.headers on
.mode line

[Debian 8] shmig: () is not available

Hi,

I can successfully use shmig on Ubuntu but when I tried on Debian 8 but I get the following error:

./shmig -t mysql -d db_name up
shmig:  () is not available

It seems that the up (or create, or down) is not correctly retrieve. Any idea why?

Hints:

  • ACTION="$1" is correctly set at up
  • $MYSQL is empty

Thanks

Add check constraint to schema_version.version

shmig creates version numbers as the seconds since epoch.

It runs migrations that have a version number greater than what is in the schema_version.version field.

If someone inserts a big integer, schema migrations will stop. We probably shouldn't let this happen.

Multiple `shmig up` commands running at the same time

It seems to me (after reading shmig source code), it's not safe to run multiple shmig up commands at the same time. It could happen in real life if one tries to do shmig up as part of application instance startup. With multiple instances of those running.

This problem is discussed at stackexchange.com, with a potential solution using SELECT FROM ... FOR UPDATE:

  • BEGIN
  • SELECT FROM $SCHEMA_TABLE FOR UPDATE WHERE version=$VERSION
  • if the former statement returns a value, stop
  • apply the migration (ROLLBACK if it fails and stop)
  • INSERT INTO $SCHEMA_TABLE(version) VALUES ($VERSION)
  • COMMIT

@mbucc Would you be open to have this problem fixed in shmig? I could try to contribute the solution.

Add config item for date format

I prefer human-readable timestamps in files, so it would be great to have a config item that can be used as the source for timestamp generation. Something like TIMESTAMP_FORMAT="%Y%m%d-%H%M%S" (which, of course, the user could alter in any way s/he chooses).

I think the only hiccup this presents is in finding migration files, HOWEVER, I believe that it may be considered safe to assume that any *.sql files in the migrations directory can be considered to be migrations. You might argue that you could also store init and seed files in your migrations directory, but I would counter that that's probably not great system design and you should instead of have a hierarchy that includes migrations as a subdirectory of a db directory.

Anyway, I'm exploring implementing this now in my own human-timestamps branch, but am backlogged at work, so probably won't get to it as soon as I'd like. Please comment so I can implement something that you're happy enough with to merge ;).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.