App-migrate
view release on metacpan or search on metacpan
lib/App/migrate.pm view on Meta::CPAN
type => 'BACKUP', # internal step type
version => $prev,
prev_version => $prev,
next_version => $next,
});
}
$just_restored = 0;
$from = $prev;
for my $step ($self->get_steps([$prev, $next])) {
$self->_do($step);
if ($step->{type} eq 'RESTORE') {
$just_restored = 1;
}
}
}
1;
}
or do {
my $err = $@;
if ($from) {
eval {
$self->_do({
type => 'RESTORE', # internal step type
version => $from,
prev_version => $from,
next_version => $path[-1],
}, 1);
warn "Successfully undone interrupted migration by RESTORE $from\n";
1;
} or warn "Failed to RESTORE $from: $@";
}
die $err;
};
return;
}
sub _data2arg {
my ($data) = @_;
return if $data eq q{};
my ($fh, $file) = tempfile('migrate.XXXXXX', TMPDIR=>1, UNLINK=>1);
print {$fh} $data;
close $fh or croak "close($file): $!";
return $file;
}
sub _do {
my ($self, $step, $is_fatal) = @_;
local $ENV{MIGRATE_PREV_VERSION} = $step->{prev_version};
local $ENV{MIGRATE_NEXT_VERSION} = $step->{next_version};
eval {
if ($step->{type} eq 'BACKUP' or $step->{type} eq 'RESTORE' or $step->{type} eq 'VERSION') {
$self->{on}{ $step->{type} }->($step);
}
else {
my $cmd = $step->{cmd};
if ($cmd =~ /\A#!/ms) {
$cmd = _data2arg($cmd);
chmod 0700, $cmd or croak "chmod($cmd): $!"; ## no critic (ProhibitMagicNumbers)
}
system($cmd, @{ $step->{args} }) == 0 or die "'$step->{type}' failed: $cmd @{ $step->{args} }\n";
print "\n";
}
1;
}
or do {
die $@ if $is_fatal;
warn $@;
$self->{on}{error}->($step);
};
return;
}
sub _e {
my ($t, $msg, $near) = @_;
return "parse error: $msg at $t->{loc}{file}:$t->{loc}{line}"
. (length $near ? " near '$near'\n" : "\n");
}
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;
while (@tokens) {
my $t = shift @tokens;
if ($t->{op} =~ /\ADEFINE[24]?\z/ms) {
lib/App/migrate.pm view on Meta::CPAN
=over
=item *
Be able to automatically make sure each 'upgrade' operation has
corresponding 'downgrade' operation (so it won't be forget - but, of
course, it's impossible to automatically check is 'downgrade' operation
will correctly undo effect of 'upgrade' operation).
I<Thus custom file format is needed.>
=item *
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
upgrade/downgrade operations into one user-defined operation (no worries
here: these macro doesn't support recursion, it isn't possible to redefine
them, and they have lexical scope - from definition to the end of this
file - so they won't really add complexity).>
=back
=head2 Example
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
upgrade
./some_daemon &
after_downgrade
killall -9 some_daemon
VERSION 1.0.0
# Let's define some lazy helpers:
DEFINE2 only_upgrade
upgrade
downgrade true
DEFINE2 mkdir
upgrade
mkdir "$@"
downgrade
rm -rf "$@"
# ... and use it:
only_upgrade
echo "Just upgraded to $MIGRATE_NEXT_VERSION"
VERSION 1.0.1
# another lazy macro (must be defined above in same file)
mkdir dir1 dir2
VERSION 1.1.0
=head2 Specification
Recommended name for file with upgrade/downgrade operations is either
C<migrate> or C<< <version>.migrate >>.
Each line in migrate file must be one of these:
=over
=item * line start with symbol "#"
For comments. Line is ignored.
=item * line start with any non-space symbol, except "#"
Contain one or more elements separated by one or more space symbols:
operation name (case-sensitive), zero or more params (any param may be
quoted, params which contain one of 5 symbols "\\\"\t\r\n" must be
quoted).
Quoted params must be surrounded by double-quote symbol, and any of
mentioned above 5 symbols must be escaped like shown above.
=item * line start with two spaces
Zero or more such lines after line with operation name form one more,
multiline, extra param for that operation (first two spaces will be
removed from start of each line before providing this param to operation).
Not all operations may have such multiline param.
( run in 1.361 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )