App-Dest

 view release on metacpan or  search on metacpan

lib/App/Dest.pm  view on Meta::CPAN


sub watches {
    my $self = _new(shift);

    my @watches = $self->_watch_list;
    print join( "\n", @watches ), "\n" if @watches;

    return 0;
}

sub putwatch {
    my $self   = _new(shift);
    my ($file) = @_;
    die "File specified does not exist\n" unless ( -f $file );

    open( my $new_watches, '<', $file ) or die "Unable to read specified file\n";

    my @new = map { chomp; $_ } <$new_watches>;
    my @old = $self->_watch_list;

    for my $old (@old) {
        next if ( grep { $_ eq $old } @new );
        $self->rm($old);
    }
    for my $new (@new) {
        next if ( grep { $_ eq $new } @old );
        $self->add($new);
    }

    return 0;
}

sub writewatch {
    my $self = _new(shift);

    copy( $self->_rel2dir('.dest/watch'), $self->_rel2dir('dest.watch') ) or die "$!\n";

    return 0;
}

sub make {
    my ( $self, $path, $ext ) = @_;
    die "No name specified; usage: dest make [path]\n" unless ($path);

    $path = strftime( $path, localtime );

    $ext = '.' . $ext if ( defined $ext );
    $ext //= '';

    try {
        mkpath($path);
        for ( qw( deploy verify revert ) ) {
            open( my $file, '>', "$path/$_$ext" ) or die;
            print $file "\n";
        }
    }
    catch ($e) {
        die "Failed to fully make $path; check permissions or existing files\n";
    }

    $self->expand($path);

    return 0;
}

sub expand {
    my ( $self, $path ) = @_;

    print join( ' ', map { <"$path/$_*"> } qw( deploy verify revert ) ), "\n";

    return 0;
}

sub list {
    my $self     = _new(shift);
    my ($filter) = @_;

    my $tree = $self->_actions_tree($filter);
    for my $path ( sort keys %$tree ) {
        print $path, ( ( @{ $tree->{$path} } ) ? ' actions:' : ' has no actions' ), "\n";
        print '  ', $_, "\n" for ( @{ $tree->{$path} } );
    }

    return 0;
}

sub prereqs {
    my $self = _new(shift);
    my ($filter) = @_;

    for my $action ( @{ $self->_prereq_tree($filter)->{actions} } ) {
        print $action->{action}, ( ( @{ $action->{prereqs} } ) ? ' prereqs:' : ' has no prereqs' ), "\n";
        print '  ', $_, "\n" for ( @{ $action->{prereqs} } );
    }

    return 0;
}

sub status {
    my $self = _new(shift);

    if ( -f $self->_rel2dir('dest.watch') ) {
        my $diff = Text::Diff::diff( $self->_rel2dir('.dest/watch'), $self->_rel2dir('dest.watch') );
        warn "Diff between current watch list and dest.watch file:\n" . $diff . "\n" if ($diff);
    }

    for my $path ( @{ $self->_status_data->{paths} } ) {
        print "$path->{state} - $path->{path}\n";

        for my $action ( @{ $path->{actions} } ) {
            unless ( $action->{modified} ) {
                print '  ' . ( ( $action->{deployed} ) ? '-' : '+' ) . ' ' . $action->{action} . "\n";
            }
            else {
                print "  $action->{action}\n";
                print "    M $action->{file}\n";
            }
        }
    }

    return 0;
}

sub diff {
    my $self   = _new(shift);
    my ($path) = @_;

lib/App/Dest.pm  view on Meta::CPAN


    if ( $type eq 'verify' ) {
        $run->();
        chomp($out);
        die "$err\n" if ($err);

        if ($out) {
            print "ok - verify: $action\n";
        }
        else {
            die "not ok - verify: $action\n";
        }
    }
    else {
        print "begin - $type: $action\n";
        $run->();
        print "ok - $type: $action\n";
    }

    my $dest_copy = $self->_rel2dir( '.dest/' . $self->_rel2root($action) );
    rmtree($dest_copy) unless ( $type eq 'verify' );
    dircopy( $action, $dest_copy ) if ( $type eq 'deploy' );

    return 0;
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

App::Dest - Deployment State Manager

=head1 VERSION

version 1.36

=for markdown [![test](https://github.com/gryphonshafer/dest/workflows/test/badge.svg)](https://github.com/gryphonshafer/dest/actions?query=workflow%3Atest)
[![codecov](https://codecov.io/gh/gryphonshafer/dest/graph/badge.svg)](https://codecov.io/gh/gryphonshafer/dest)

=for test_synopsis BEGIN { die "SKIP: skip synopsis check because it's non-Perl\n"; }

=head1 SYNOPSIS

dest COMMAND [OPTIONS]

    dest init               # initialize dest for a project
    dest add DIR            # add a directory to dest tracking list
    dest rm DIR             # remove a directory from dest tracking list

    dest watches            # returns a list of watched directories
    dest putwatch FILE      # set watch list to be what's in a file
    dest writewatch         # creates watch file in project root directory

    dest make NAME [EXT]    # create a named template set (set of 3 files)
    dest expand NAME        # dump a list of the template set (set of 3 files)
    dest list [FILTER]      # list all actions in all watches
    dest prereqs [FILTER]   # like "list" but include report of prereqs

    dest status             # check status of tracked directories
    dest diff [NAME]        # display a diff of any modified actions
    dest clean [NAME]       # reset dest state to match current files/dirs
    dest preinstall [NAME]  # set dest state so an update will deploy everything
    dest nuke               # de-initialize dest; remove all dest stuff

    dest deploy NAME [-d]   # deployment of a specific action
    dest verify [NAME]      # verification of tracked actions or specific action
    dest revert NAME [-d]   # revertion of a specific action
    dest redeploy NAME [-d] # deployment of a specific action
    dest revdeploy NAME     # revert and deployment of a specific action
    dest update [INCS] [-d] # automaticall deploy or revert to cause currency

    dest version            # dest current version
    dest help               # display command synposis
    dest man                # display man page

=head1 DESCRIPTION

C<dest> is a simple "deployment state" change management tool. Inspired by
what Sqitch does for databases, it provides a simple mechanism for writing
deploy, verify, and revert parts of a change action. The typical use of
C<dest> is in a development context because it allows for simplified state
changes when switching between branches (as an example).

Let's say you're working with a group of other software engineers on a
particular software project using your favorite revision control system.
Let's also say that you have a database that undergoes schema changes as
features are developed, and you have various system activities like the
installation of libraries or other applications. Then let's also say the team
branches, works on stuff, shares those branches, reverts, merges, etc. And also
from time to time you want to go back in time a bit so you can reproduce a bug.
Maintaining the database state and the state of the system across all that
activity can be problematic. C<dest> tries to solve this in a very simple way,
letting you be able to deploy, revert, and verify to any point in time in
the development history.

See below for an example scenario that may help illustrate using C<dest> in a
pseudo real world situation.

Note that using C<dest> for production deployment, provisioning, or
configuration management is not advised. Use a full-featured configuration
management tool instead.

=head1 COMMANDS

Typing just C<dest> should bring up the usage instructions, which include a
command list. You should be able to execute C<dest> commands from any directory
at or below your project's root directory once the project has been initiated
in C<dest>.

=head2 init

To start using C<dest>, you need to initialize your project by calling C<init>
while in the root directory of your project. (If you are in a different
directory, C<dest> will assume that is your project's root directory.)

lib/App/Dest.pm  view on Meta::CPAN

with recognizable names, and into each subdirectory a set of 3 files: deploy,
revert, and verify.

For example, let's say you have a database. So you create C<db> in your
project's root directory. Then call C<dest add db> from your root directory.
Inside C<db>, you might create the directory C<db/schema>. And under that
directory, add the files: deploy, revert, and verify.

The deploy file contains the instructions to create the database schema. The
revert file contains the instructions to revert what the deploy file did. And
the verify file let's you verify the deploy file worked.

=head2 rm DIR

This removes a directory from the C<dest> tracking list.

=head2 watches

Returns a list of tracked or watched directories.

=head2 putwatch FILE

Sets the current list of tracked or watched directories to be what's in a file.
For example, you could do this:

    dest watches > dest.watch
    echo 'new_dir_to_watch' >> dest.watch
    dest putwatch dest.watch

=head2 writewatch

Creates (or overwrites) a watch file in the project root directory with the
contents of the currently watched directories.

=head2 make NAME [EXT]

This is a helper command. Given a directory you've already added, it will create
the subdirectory and deploy, revert, and verify files.

    # given db, creates db/schema and the 3 files
    dest make db/schema

As a nice helper bit, C<make> will list the relative paths of the 3 new files.
So if you want, you can do something like this:

    vi `dest make db/schema`

Optionally, you can specify an extension for the created files. For example:

    vi `dest make db/schema sql`
    # this will create and open in vi:
    #    db/schema/deploy.sql
    #    db/schema/revert.sql
    #    db/schema/verify.sql

And optionally, you can include any date/time format supported by L<POSIX>
C<strftime>.

    dest make db/%s_action sql

=head2 expand NAME

This command lists out the relative paths and names of the 3 files of the
action provided, so you can do stuff like:

    vi `dest expand db/schema`

=head2 list [FILTER]

This command will list all tracked directories and every action within each
directory. If provided a filter, it will limit what's displayed to actions
containing the filter.

=head2 prereqs [FILTER]

This command will list every action within any tracked directory, then for each
action, it will list any prereqs of that action. If provided a filter, it will
limit what's displayed to actions containing the filter.

=head2 status

This command will tell you your current state compared to what the current code
says your state should be. For example, you might see something like this:

    diff - db
      + db/new_function
      - db/lolcats
      M db/schema/deploy
    ok - etc

C<dest> will report for each tracked directory what are new changes that haven't
yet been deployed (marked with a "+"), features that have been deployed in your
current system state but are missing from the code (marked with a "-"), and
changes to previously existing files (marked with an "M").

=head2 diff [NAME]

This will display a diff delta of the differences of any modified action files.
You can specify an optional name parameter that refers to a tracking directory,
action name, or specific sub-action.

    dest diff
    dest diff db/schema
    dest diff db/schema/deploy

=head2 clean [NAME]

Let's say that for some reason you have a delta between what C<dest> thinks your
system is and what your code says it ought to be, and you really believe your
code is right. You can call C<clean> to tell C<dest> to just assume that what
the code says is right.

You can optionally provide a specific action or even a step of an action to
C<clean>. For example:

    dest clean db/schema
    dest clean db/schema/deploy

=head2 preinstall [NAME]

Let's say you're setting up a new system or installing the project/application,
so you start by creating yourself a working directory. At some point, you'll
want to deploy all the deploy actions. You'll need to C<init> and C<add> the
directories/paths you need. But C<dest> will have a cache that matches the
current working directory. At this point, you need to C<preinstall> to remove
that cache and be in a state where you can C<update>.



( run in 2.077 seconds using v1.01-cache-2.11-cpan-97f6503c9c8 )