App-Sqitch

 view release on metacpan or  search on metacpan

lib/App/Sqitch/Engine.pm  view on Meta::CPAN

use App::Sqitch::X qw(hurl);
use List::Util qw(first max);
use URI::db 0.20;
use App::Sqitch::Types qw(Str Int Num Sqitch Plan Bool HashRef URI Maybe Target);
use namespace::autoclean;
use constant registry_release => '1.1';
use constant default_lock_timeout => 60;

our $VERSION = 'v1.6.1'; # VERSION

has sqitch => (
    is       => 'ro',
    isa      => Sqitch,
    required => 1,
    weak_ref => 1,
);

has target => (
    is       => 'ro',
    isa      => Target,
    required => 1,
    weak_ref => 1,
    handles => {
        uri         => 'uri',
        client      => 'client',
        registry    => 'registry',
        destination => 'name',
    }
);

has username => (
    is      => 'ro',
    isa     => Maybe[Str],
    lazy    => 1,
    default => sub {
        my $self = shift;
        $self->target->username || $self->_def_user
    },
);

has password => (
    is      => 'ro',
    isa     => Maybe[Str],
    lazy    => 1,
    default => sub {
        my $self = shift;
        $self->target->password || $self->_def_pass
    },
);

sub _def_user { }
sub _def_pass { }

sub registry_destination { shift->destination }

has start_at => (
    is  => 'rw',
    isa => Str
);

has no_prompt => (
    is      => 'rw',
    isa     => Bool,
    trigger => sub {
        # Deprecation notice added Feb 2023
        warnings::warnif(
            "deprecated",
            "Engine::no_prompt is deprecated and will be removed in a future release\n"
            . "Use direct arguments to revert() instead",
        );
    }
);

has prompt_accept => (
    is      => 'rw',
    isa     => Bool,
    trigger => sub {
        # Deprecation notice added Feb 2023
        warnings::warnif(
            "deprecated",
            "Engine::prompt_accept is deprecated and will be removed in a future release\n"
            . "Use direct arguments to revert() instead",
        );
    }
);

has log_only => (
    is      => 'rw',
    isa     => Bool,
    default => 0,
);

has with_verify => (
    is      => 'rw',
    isa     => Bool,
    default => 0,
);

has max_name_length => (
    is      => 'rw',
    isa     => Int,
    default => 0,
    lazy    => 1,
    default => sub {
        my $plan = shift->plan;
        max map {
            length $_->format_name_with_tags
        } $plan->changes;
    },
);

has plan => (
    is       => 'rw',
    isa      => Plan,
    lazy     => 1,
    default  => sub { shift->target->plan }
);

has _variables => (
    is      => 'rw',
    isa     => HashRef[Str],
    default => sub { {} },
);

# Usually expressed as an integer, but made a number for the purposes of
# shorter test run times.
has lock_timeout => (
    is      => 'rw',
    isa     => Num,
    default => default_lock_timeout,
);

has _locked => (
    is      => 'rw',
    isa     => Bool,
    default => 0,
);

has _no_registry => (
    is      => 'rw',
    isa     => Bool,

lib/App/Sqitch/Engine.pm  view on Meta::CPAN

            ));
            $self->initialize;
        }
        $self->register_project;

    } else {
        # Make sure that $to_index is greater than the current point.
        hurl deploy => __ 'Cannot deploy to an earlier change; use "revert" instead'
            if $to_index < $position;
        # Upgrade database if it needs it.
        $self->upgrade_registry;
    }

    $sqitch->info(
        defined $to ? __x(
            'Deploying changes through {change} to {destination}',
            change      => $plan->change_at($to_index)->format_name_with_tags,
            destination => $self->destination,
        ) : __x(
            'Deploying changes to {destination}',
            destination => $self->destination,
        )
    );

    $sqitch->debug(__ "Will deploy the following changes:");
    foreach my $will_deploy_position (($plan->position + 1) .. $to_index) {
        $sqitch->debug($plan->change_at($will_deploy_position)->format_name_with_tags);
    }

    # Check that all dependencies will be satisfied.
    $self->check_deploy_dependencies($plan, $to_index);

    # Do it!
    $mode ||= 'all';
    my $meth = $mode eq 'change' ? '_deploy_by_change'
             : $mode eq 'tag'  ? '_deploy_by_tag'
             : $mode eq 'all'  ? '_deploy_all'
             : hurl deploy => __x 'Unknown deployment mode: "{mode}"', mode => $mode;
    ;

    $self->max_name_length(
        max map {
            length $_->format_name_with_tags
        } ($plan->changes)[$position + 1..$to_index]
    );

    $self->$meth( $plan, $to_index );
}

# Do a thing similar to Sqitch::Plan::Change::format_name_with_tags,
# but for an output from $self->deployed_changes or
# $self->deployed_changes_since.
sub _format_deployed_change_name_with_tags($) {
    my ( $self, $change ) = @_;

    return join ' ', $change->{name}, map { '@' . $_ } @{$change->{tags}};
}

sub revert {
    # $to = revert up to (but not including) this change. May be undefined.
    # $prompt = If true, we ask for confirmation; if false, we don't.
    # $prompt_default = Default if the user just hits enter at the prompt.
    my ( $self, $to, $prompt, $prompt_default ) = @_;

    if (defined $prompt) {
        hurl revert => __('Missing required parameter $prompt_default')
            unless defined $prompt_default;
    } else {
        warnings::warnif(deprecated => join ("\n",
            "Engine::revert() requires the `prompt` and `prompt_default` arguments.",
            'Omitting them will become fatal in a future release.',
        ));

        $prompt = !($self->no_prompt // 0);
        $prompt_default = $self->prompt_accept // 1;
    }

    # Check the registry and, once we know it's there, lock the destination.
    $self->_check_registry;
    $self->lock_destination;

    my $sqitch = $self->sqitch;
    my $plan   = $self->plan;
    my @changes;

    if (defined $to) {
        my ($change) = $self->_load_changes(
            $self->change_for_key($to)
        ) or do {
            # Not deployed. Is it in the plan?
            if ( $plan->find($to) ) {
                # Known but not deployed.
                hurl revert => __x(
                    'Change not deployed: "{change}"',
                    change => $to
                );
            }
            # Never heard of it.
            hurl revert => __x(
                'Unknown change: "{change}"',
                change => $to,
            );
        };

        # NB this is an array of unblessed references, not of
        # Sqitch::Plan::Change references.
        @changes = $self->deployed_changes_since(
            $self->_load_changes($change)
        ) or do {
            $sqitch->info(__x(
                'No changes deployed since: "{change}"',
                change => $to,
            ));
            return $self;
        };
        my @change_descriptions =
            map { $self->_format_deployed_change_name_with_tags($_) } @changes;

        unless ($prompt) {
            $sqitch->info(__x(
                'Reverting changes to {change} from {destination}',
                change      => $change->format_name_with_tags,
                destination => $self->destination,
            ));
            $sqitch->debug(__ 'Will revert the following changes:');
            map { $sqitch->debug($_) } @change_descriptions;
        } else {
            $sqitch->debug(__ 'Would revert the following changes:');
            map { $sqitch->debug($_) } @change_descriptions;
            hurl {
                ident   => 'revert:confirm',
                message => __ 'Nothing reverted',
                exitval => 1,
            } unless $sqitch->ask_yes_no(__x(
                'Revert changes to {change} from {destination}?',
                change      => $change->format_name_with_tags,
                destination => $self->destination,
            ), $prompt_default );
        }
    } else {
        # NB this is an array of unblessed references, not of
        # Sqitch::Plan::Change references.
        @changes = $self->deployed_changes or do {
            $sqitch->info(__ 'Nothing to revert (nothing deployed)');
            return $self;
        };
        my @change_descriptions =
            map { $self->_format_deployed_change_name_with_tags($_) } @changes;

        unless ($prompt) {
            $sqitch->info(__x(
                'Reverting all changes from {destination}',
                destination => $self->destination,
            ));
            $sqitch->debug(__ 'Will revert the following changes:');
            map { $sqitch->debug($_) } @change_descriptions;
        } else {
            $sqitch->debug(__ 'Would revert the following changes:');
            map { $sqitch->debug($_) } @change_descriptions;
            hurl {
                ident   => 'revert',
                message => __ 'Nothing reverted',
                exitval => 1,
            } unless $sqitch->ask_yes_no(__x(
                'Revert all changes from {destination}?',
                destination => $self->destination,
            ), $prompt_default );
        }
    }

    # Make change objects and check that all dependencies will be satisfied.
    @changes = reverse $self->_load_changes( @changes );
    $self->check_revert_dependencies(@changes);

    # Do we want to support modes, where failures would re-deploy to previous
    # tag or all the way back to the starting point? This would be very much
    # like deploy() mode. I'm thinking not, as a failure on a revert is not
    # something you generally want to recover from by deploying back to where
    # you started. But maybe I'm wrong?
    $self->max_name_length(
        max map { length $_->format_name_with_tags } @changes
    );
    $self->revert_change($_) for @changes;

    return $self;
}

sub verify {
    my ( $self, $from, $to ) = @_;
    # $from = verify changes after and including this one, or if undefined starting from the first change.
    # $to = verify changes up to but not including this one, or if undefined up to all changes.

    $self->_check_registry;
    my $sqitch   = $self->sqitch;
    my $plan     = $self->plan;
    my @changes  = $self->_load_changes( $self->deployed_changes );

    $sqitch->info(__x(
        'Verifying {destination}',
        destination => $self->destination,
    ));

    if (!@changes) {
        my $msg = $plan->count
            ? __ 'No changes deployed'
            : __ 'Nothing to verify (no planned or deployed changes)';
        $sqitch->info($msg);
        return $self;
    }

    if ($plan->count == 0) {
        # Oy, there are deployed changes, but not planned!
        hurl verify => __ 'There are deployed changes, but none planned!';
    }

    # Figure out where to start and end relative to the plan.
    my $from_idx = $self->_from_idx('verify', $from, \@changes);
    my $to_idx = $self->_to_idx('verify', $to, \@changes);

    # Run the verify tests.
    if ( my $count = $self->_verify_changes($from_idx, $to_idx, !$to, @changes) ) {
        # Emit a quick report.
        # XXX Consider coloring red.
        my $num_changes = 1 + $to_idx - $from_idx;
        $num_changes = @changes if @changes > $num_changes;
        my $msg = __ 'Verify Summary Report';
        $sqitch->emit("\n", $msg);



( run in 0.745 second using v1.01-cache-2.11-cpan-0b5f733616e )