App-migrate
view release on metacpan or search on metacpan
need complex operations to upgrade/downgrade between these versions.
For example, to migrate source code you can use VCS like Git or
Mercurial, but they didn't support empty directories, file permissions
(except executable), non-plain file types (FIFO, UNIX socket, etc.),
xattr, ACL, configuration files which must differ on each site, and
databases. So, if you need to migrate anything isn't supported by VCS -
you can try this module/tool.
Sometimes it isn't possible to really downgrade because some data was
lost while upgrade - to handle these situations you should provide a
ways to create complete backup of your project and restore any
project's version from these backups while downgrade (of course,
restoring backups will result in losing new changes, so whenever
possible it's better to do some extra work to provide a way to
downgrade without losing any data).
Example
Here is example how to run migration from version '1.1.8' to '1.2.3' of
some project which uses even minor versions '1.0.x' and '1.2.x' for
stable releases and odd minor versions '1.1.x' for unstable releases.
The nearest common version between '1.1.8' and '1.2.3' is '1.0.42',
which was the parent for both '1.1.x' and '1.2.x' branches, so we need
to downgrade project from '1.1.8' to '1.0.42' first, and then upgrade
from '1.0.42' to '1.2.3'. You'll need two *.migrate files, one which
describe migrations from '1.0.42' (or earlier version) to '1.1.8', and
another with migrations from '1.0.42' (or earlier) to '1.2.3'. For
brevity let's not make any backups while migration.
my $migrate = App::migrate
->new
->load('1.1.8.migrate')
->load('1.2.3.migrate');
$migrate
->on(BACKUP => sub {})
->run( $migrate->find_paths('1.1.8' => '1.2.3') );
INTERFACE
Set handler for given event.
All handlers will be called only by "run"; they will get single
parameter - step HASHREF (BACKUP handler will get step in same format
as RESTORE), see "get_steps" for details of that HASHREF contents. Also
these handlers may use $ENV{MIGRATE_PREV_VERSION} and
$ENV{MIGRATE_NEXT_VERSION} - see "run" for more details.
'BACKUP' event
Handler will be executed when project backup should be created:
before starting any new migration, except next one after RESTORE.
If handler throws then 'error' handler will be executed.
Default handler will throw (because it doesn't know how to backup
your project).
NOTE: If you'll use handler which doesn't really create and keep
backups for all versions then it will be impossible to do RESTORE
operation.
'RESTORE' event
Handler will be executed when project should be restored from backup:
when downgrading between versions which contain RESTORE operation or
when migration fails.
If handler throws then 'error' handler will be executed.
Default handler will throw (because it doesn't know how to restore
your project).
'VERSION' event
Handler will be executed when one of commands executed while
migration fails or when BACKUP, RESTORE or VERSION handlers throw.
If handler throws then try to restore version-before-migration
(without calling error handler again if it throws too).
Default handler will run $SHELL (to let you manually fix errors) and
throw if you $SHELL exit status != 0 (to let you choose what to do
next - continue migration if you fixed error or interrupt migration
to restore version-before-migration from backup).
run
$migrate->run( \@versions );
Will use "get_steps" to get steps for path given in @versions and
execute them in order. Will also call handlers as described in "on".
Before executing each step will set $ENV{MIGRATE_PREV_VERSION} to
current version (which it will migrate from) and
Thus custom file format is needed.
* Make it easier to manually analyse is 'downgrade' operation looks
correct for corresponding 'upgrade' operation.
Thus related 'upgrade' and 'downgrade' operations must go one right
after another.
* Make it obvious some version can't be downgraded and have to be
restored from backup.
Thus RESTORE operation is named in upper case.
* Given all these requirements try to make it simple and obvious to
define migrate operations, without needs to write downgrade code for
typical cases.
Thus it's possible to define macro to turn combination of
upgrade/downgrade operations into one user-defined operation (no
worries here: these macro doesn't support recursion, it isn't
VERSION 0.0.0
# To upgrade from 0.0.0 to 0.1.0 we need to create new empty file and
# empty directory.
upgrade touch empty_file
downgrade rm empty_file
upgrade mkdir empty_dir
downgrade rmdir empty_dir
VERSION 0.1.0
# To upgrade from 0.1.0 to 0.2.0 we need to drop old database. This
# change can't be undone, so only way to downgrade from 0.2.0 is to
# restore 0.1.0 from backup.
upgrade rm useless.db
RESTORE
VERSION 0.2.0
# To upgrade from 0.2.0 to 1.0.0 we need to run several commands,
# and after downgrading we need to kill some background service.
before_upgrade
patch <0.2.0.patch >/dev/null
chmod +x some_daemon
downgrade
patch -R <0.2.0.patch >/dev/null
While executing any commands two environment variables will be set:
$MIGRATE_PREV_VERSION and $MIGRATE_NEXT_VERSION (first is always
version we're migrating from, and second is always version we're
migrating to - i.e. while downgrading $MIGRATE_NEXT_VERSION will be
lower/older version than $MIGRATE_PREV_VERSION)
All executed commands must complete without error, otherwise emergency
shell will be started and user should either fix the error and exit
from shell to continue migration, or exit 1 from shell to interrupt
migration and restore previous-before-this-migration version from
backup.
Supported operations
VERSION
Must have exactly one param (version number). Some symbols are not
allowed in version numbers: special (0x00-0x1F,0x7F), both slash, all
three quotes, ?, * and space.
Multiline param not supported.
RESTORE
Doesn't support any params, neither usual nor multiline.
Can be used only after 'before_upgrade' or 'upgrade' operations.
When one or more 'RESTORE' operations are used between some 'VERSION'
operations then all 'downgrade' and 'after_downgrade' operations
between same 'VERSION' operations will be ignored and on downgrading
previous version will be restored from backup.
DEFINE
This operation must have only one non-multiline param - name of defined
macro. This name must not be same as one of existing operation names,
both documented here or created by one of previous 'DEFINE' or
'DEFINE2' or 'DEFINE4' operations.
Next operation must be one of 'before_upgrade', 'upgrade', 'downgrade'
or 'after_downgrade' - it will be substituted in place of all next
lib/App/migrate.pm view on Meta::CPAN
$SIG{INT} = $SIG{INT} // sub { exit 130 }; ## no critic (RequireLocalizedPunctuationVars ProhibitMagicNumbers)
$SIG{QUIT}= $SIG{QUIT} // sub { exit 131 }; ## no critic (RequireLocalizedPunctuationVars ProhibitMagicNumbers)
$SIG{TERM}= $SIG{TERM} // sub { exit 143 }; ## no critic (RequireLocalizedPunctuationVars ProhibitMagicNumbers)
sub new {
my ($class) = @_;
my $self = bless {
paths => {}, # {prev_version}{next_version} = \@steps
on => {
BACKUP => \&_on_backup,
RESTORE => \&_on_restore,
VERSION => \&_on_version,
error => \&_on_error,
},
}, ref $class || $class;
return $self;
}
sub find_paths {
my ($self, $from, $to) = @_;
lib/App/migrate.pm view on Meta::CPAN
}
sub _find_paths {
my ($self, $to, @from) = @_;
my $p = $self->{paths}{ $from[-1] } || {};
return [@from, $to] if $p->{$to};
my %seen = map {$_=>1} @from;
return map {$self->_find_paths($to,@from,$_)} grep {!$seen{$_}} keys %{$p};
}
sub _on_backup {
croak 'You need to define how to make BACKUP';
}
sub _on_restore {
croak 'You need to define how to RESTORE from backup';
}
sub _on_version {
# do nothing
}
sub _on_error {
warn <<'ERROR';
YOU NEED TO MANUALLY FIX THIS ISSUE RIGHT NOW
When done, use:
exit to continue migration
exit 1 to interrupt migration and RESTORE from backup
ERROR
system($ENV{SHELL} // '/bin/sh') == 0 or die "Migration interrupted\n";
return;
}
sub _preprocess { ## no critic (ProhibitExcessComplexity)
my @tokens = @_;
my @op;
my %macro;
lib/App/migrate.pm view on Meta::CPAN
complex operations to upgrade/downgrade between these versions.
For example, to migrate source code you can use VCS like Git or Mercurial,
but they didn't support empty directories, file permissions (except
executable), non-plain file types (FIFO, UNIX socket, etc.), xattr, ACL,
configuration files which must differ on each site, and databases. So, if
you need to migrate anything isn't supported by VCS - you can try this
module/tool.
Sometimes it isn't possible to really downgrade because some data was lost
while upgrade - to handle these situations you should provide a ways to
create complete backup of your project and restore any project's version
from these backups while downgrade (of course, restoring backups will
result in losing new changes, so whenever possible it's better to do some
extra work to provide a way to downgrade without losing any data).
=head2 Example
Here is example how to run migration from version '1.1.8' to '1.2.3' of
some project which uses even minor versions '1.0.x' and '1.2.x' for stable
releases and odd minor versions '1.1.x' for unstable releases. The nearest
common version between '1.1.8' and '1.2.3' is '1.0.42', which was the
parent for both '1.1.x' and '1.2.x' branches, so we need to downgrade
project from '1.1.8' to '1.0.42' first, and then upgrade from '1.0.42' to
'1.2.3'. You'll need two C<*.migrate> files, one which describe migrations
from '1.0.42' (or earlier version) to '1.1.8', and another with migrations
from '1.0.42' (or earlier) to '1.2.3'. For brevity let's not make any
backups while migration.
my $migrate = App::migrate
->new
->load('1.1.8.migrate')
->load('1.2.3.migrate');
$migrate
->on(BACKUP => sub {})
->run( $migrate->find_paths('1.1.8' => '1.2.3') );
lib/App/migrate.pm view on Meta::CPAN
All handlers will be called only by L</"run">; they will get single
parameter - step HASHREF (BACKUP handler will get step in same format as
RESTORE), see L</"get_steps"> for details of that HASHREF contents.
Also these handlers may use C<$ENV{MIGRATE_PREV_VERSION}> and
C<$ENV{MIGRATE_NEXT_VERSION}> - see L</"run"> for more details.
=over
=item 'BACKUP' event
Handler will be executed when project backup should be created: before
starting any new migration, except next one after RESTORE.
If handler throws then 'error' handler will be executed.
Default handler will throw (because it doesn't know how to backup your
project).
NOTE: If you'll use handler which doesn't really create and keep backups
for all versions then it will be impossible to do RESTORE operation.
=item 'RESTORE' event
Handler will be executed when project should be restored from backup: when
downgrading between versions which contain RESTORE operation or when
migration fails.
If handler throws then 'error' handler will be executed.
Default handler will throw (because it doesn't know how to restore your
project).
=item 'VERSION' event
lib/App/migrate.pm view on Meta::CPAN
Handler will be executed when one of commands executed while migration
fails or when BACKUP, RESTORE or VERSION handlers throw.
If handler throws then try to restore version-before-migration (without
calling error handler again if it throws too).
Default handler will run $SHELL (to let you manually fix errors) and throw
if you $SHELL exit status != 0 (to let you choose what to do next -
continue migration if you fixed error or interrupt migration to restore
version-before-migration from backup).
=back
=head2 run
$migrate->run( \@versions );
Will use L</"get_steps"> to get steps for path given in C<@versions> and
execute them in order. Will also call handlers as described in L</"on">.
lib/App/migrate.pm view on Meta::CPAN
Make it easier to manually analyse is 'downgrade' operation looks correct
for corresponding 'upgrade' operation.
I<Thus related 'upgrade' and 'downgrade' operations must go one right
after another.>
=item *
Make it obvious some version can't be downgraded and have to be restored
from backup.
I<Thus RESTORE operation is named in upper case.>
=item *
Given all these requirements try to make it simple and obvious to define
migrate operations, without needs to write downgrade code for typical
cases.
I<Thus it's possible to define macro to turn combination of
lib/App/migrate.pm view on Meta::CPAN
VERSION 0.0.0
# To upgrade from 0.0.0 to 0.1.0 we need to create new empty file and
# empty directory.
upgrade touch empty_file
downgrade rm empty_file
upgrade mkdir empty_dir
downgrade rmdir empty_dir
VERSION 0.1.0
# To upgrade from 0.1.0 to 0.2.0 we need to drop old database. This
# change can't be undone, so only way to downgrade from 0.2.0 is to
# restore 0.1.0 from backup.
upgrade rm useless.db
RESTORE
VERSION 0.2.0
# To upgrade from 0.2.0 to 1.0.0 we need to run several commands,
# and after downgrading we need to kill some background service.
before_upgrade
patch <0.2.0.patch >/dev/null
chmod +x some_daemon
downgrade
patch -R <0.2.0.patch >/dev/null
lib/App/migrate.pm view on Meta::CPAN
While executing any commands two environment variables will be set:
C<$MIGRATE_PREV_VERSION> and C<$MIGRATE_NEXT_VERSION> (first is always
version we're migrating from, and second is always version we're migrating
to - i.e. while downgrading C<$MIGRATE_NEXT_VERSION> will be lower/older
version than C<$MIGRATE_PREV_VERSION>)
All executed commands must complete without error, otherwise emergency
shell will be started and user should either fix the error and C<exit>
from shell to continue migration, or C<exit 1> from shell to interrupt
migration and restore previous-before-this-migration version from backup.
=head2 Supported operations
=head3 VERSION
Must have exactly one param (version number). Some symbols are not allowed
in version numbers: special (0x00-0x1F,0x7F), both slash, all three
quotes, ?, * and space.
Multiline param not supported.
lib/App/migrate.pm view on Meta::CPAN
=head3 RESTORE
Doesn't support any params, neither usual nor multiline.
Can be used only after 'before_upgrade' or 'upgrade' operations.
When one or more 'RESTORE' operations are used between some 'VERSION'
operations then all 'downgrade' and 'after_downgrade' operations between
same 'VERSION' operations will be ignored and on downgrading previous
version will be restored from backup.
=head3 DEFINE
This operation must have only one non-multiline param - name of defined
macro. This name must not be same as one of existing operation names, both
documented here or created by one of previous 'DEFINE' or 'DEFINE2' or
'DEFINE4' operations.
Next operation must be one of 'before_upgrade', 'upgrade', 'downgrade' or
'after_downgrade' - it will be substituted in place of all next operations
script/migrate view on Meta::CPAN
print <<'EOUSAGE';
migrate [-B <cmd>] [-R <cmd>] [-V <cmd>] [[-f <file>] ...] <from_ver> <to_ver>
migrate [-B <cmd>] [-R <cmd>] [-V <cmd>] [[-f <file>] ...] -p <ver1> <ver2> ...
migrate -c [<file>]
migrate -h|--help
EOUSAGE
exit 1;
}
sub main {
my $b_cmd = 'echo No backup command defined, skip backup';
my $r_cmd = 'false';
my $v_cmd = 'echo Current version is $MIGRATE_NEXT_VERSION';
my @files = ();
my $is_path = 0;
my $is_check = 0;
GetOptionsFromArray(\@_,
'B=s' => \$b_cmd,
'R=s' => \$r_cmd,
'V=s' => \$v_cmd,
'f=s@' => \@files,
script/migrate view on Meta::CPAN
migrate -h|--help
=head1 DESCRIPTION
To use this tool to migrate your project between any versions you should
provide:
- one or more *.migrate files, which will define known versions of your
project and commands needed to migrate between these versions
- command which will create backup of your project (and keep these backups
to make it possible to restore any of previous project versions)
- command which will restore project from backup of any version (if such
backup exists)
- (optional) command which will record somewhere current version of
project after each successful migration
See L<App::migrate> for details.
=head1 OPTIONS
=over
t/example.t view on Meta::CPAN
my $migrate = App::migrate->new;
my $file = tempfile('migrate.XXXXXX');
my $proj = tempdir('migrate.project.XXXXXX');
my $guard = bless {};
sub DESTROY { chdir q{/} }
chdir $proj or die "chdir($proj): $!";
my (@backup, @restore);
$migrate->on(BACKUP => sub { push @backup, shift->{version} });
$migrate->on(RESTORE => sub { push @restore, shift->{version} });
$migrate->on(error => sub { diag 'on ERROR was called' });
ok $migrate, 'new';
# Make sure example from documentation actually works
$file->spew_utf8(<<'MIGRATE');
VERSION 0.0.0
# To upgrade from 0.0.0 to 0.1.0 we need to create new empty file and
# empty directory.
upgrade touch empty_file
downgrade rm empty_file
upgrade mkdir empty_dir
downgrade rmdir empty_dir
VERSION 0.1.0
# To upgrade from 0.1.0 to 0.2.0 we need to drop old database. This
# change can't be undone, so only way to downgrade from 0.2.0 is to
# restore 0.1.0 from backup.
upgrade rm useless.db
RESTORE
VERSION 0.2.0
# To upgrade from 0.2.0 to 1.0.0 we need to run several commands,
# and after downgrading we need to kill some background service.
before_upgrade
patch -E <0.2.0.patch >/dev/null
rm -f *.orig
chmod +x some_daemon
downgrade
( run in 1.654 second using v1.01-cache-2.11-cpan-49f99fa48dc )