App-Yabsm

 view release on metacpan or  search on metacpan

lib/App/Yabsm/Command/Find.pm  view on Meta::CPAN

#  Author:  Nicholas Hubbard
#  WWW:     https://github.com/NicholasBHubbard/yabsm
#  License: MIT

#  Provides functionality for finding snapshots via a snapshot finding DSL.
#
#  See t/Yabsm/Snapshot.pm for this libraries tests.

use strict;
use warnings;
use v5.16.3;

package App::Yabsm::Command::Find;

use App::Yabsm::Tools qw( :ALL );
use App::Yabsm::Config::Query qw ( :ALL );
use App::Yabsm::Config::Parser qw(parse_config_or_die);
use App::Yabsm::Backup::SSH;
use App::Yabsm::Snapshot qw(nums_to_snapshot_name
                            snapshot_name_nums
                            current_time_snapshot_name
                            sort_snapshots
                            is_snapshot_name
                            snapshots_eq
                            snapshot_newer
                            snapshot_older
                            snapshot_newer_or_eq
                            snapshot_older_or_eq
                           );

use Feature::Compat::Try;
use Net::OpenSSH;
use Time::Piece;
use File::Basename qw(basename);
use Carp qw(confess);
use POSIX ();

use Parser::MGC;
use base qw(Parser::MGC);

sub usage {
    arg_count_or_die(0, 0, @_);
    return 'usage: yabsm <find|f> [--help] [<SNAP|SSH_BACKUP|LOCAL_BACKUP> <QUERY>]'."\n";
}

sub help {
    @_ == 0 or die usage();
    my $usage = usage();
    $usage =~ s/\s+$//;
    print <<"END_HELP";
$usage

see the section "Finding Snapshots" in 'man yabsm' for a detailed explanation on
how to find snapshots and backups.

examples:
    yabsm find home_snap back-10-hours
    yabsm f root_ssh_backup newest
    yabsm f home_local_backup oldest
    yabsm f home_snap 'between b-10-mins 15:45'
    yabsm f root_snap 'after back-2-days'
    yabsm f root_local_backup 'before b-14-d'
END_HELP
}

                 ####################################
                 #               MAIN               #
                 ####################################

sub main {

    if (@_ == 1) {
        shift =~ /^(-h|--help)$/ or die usage();
        help();
    }

    elsif (@_ == 2) {

        my $thing = shift;
        my $query = shift;

        my $config_ref = parse_config_or_die();

        unless (snap_exists($thing, $config_ref) || ssh_backup_exists($thing, $config_ref) || local_backup_exists($thing, $config_ref)) {
            die "yabsm: error: no such snap, ssh_backup, or local_backup named '$thing'\n";
        }

        my @snapshots = answer_query($thing, parse_query_or_die($query), $config_ref);

        say for @snapshots;
    }

    else {
        die usage()
    }
}

                 ####################################
                 #           QUERY ANSWERING        #
                 ####################################

sub answer_query {

    # Return a subset of all the snapshots/backups of $thing that satisfy
    # $query.

    arg_count_or_die(3, 3, @_);

    my $thing      = shift;
    my %query      = %{+shift};
    my $config_ref = shift;

    my @snapshots;

    if (snap_exists($thing, $config_ref)) {
        for my $tframe (snap_timeframes($thing, $config_ref)) {
            my $dir = snap_dest($thing, $tframe, $config_ref);
            unless (-r $dir) {
                die "yabsm: error: do not have read permission on '$dir'\n";
            }
            opendir my $dh, $dir or confess "yabsm: internal error: could not opendir '$dir'";
            push @snapshots, map { $_ = "$dir/$_" } grep { is_snapshot_name($_) } readdir($dh);
            closedir $dh;
        }
    }

    elsif (ssh_backup_exists($thing, $config_ref)) {

        die 'yabsm: error: permission denied'."\n" unless i_am_root();

        my $yabsm_uid = getpwnam('yabsm') or die q(yabsm: error: no user named 'yabsm')."\n";

        POSIX::setuid($yabsm_uid);

        my $ssh = App::Yabsm::Backup::SSH::new_ssh_conn($thing, $config_ref);

        my $ssh_dest = ssh_backup_ssh_dest($thing, $config_ref);

        if ($ssh->error) {
            die "yabsm: ssh error: $ssh_dest: ".$ssh->error."\n";
        }
        for my $tframe (ssh_backup_timeframes($thing, $config_ref)) {
            my $dir  = ssh_backup_dir($thing, $tframe, $config_ref);
            unless ($ssh->system("[ -r '$dir' ]")) {
                die "yabsm: ssh error: $ssh_dest: remote user does not have read permission on '$dir'\n";
            }
            push @snapshots, grep { chomp $_; is_snapshot_name($_) } App::Yabsm::Backup::SSH::ssh_system_or_die($ssh, "ls -1 '$dir'");
            map { $_ = "$dir/$_" } @snapshots;
        }
    }

    elsif (local_backup_exists($thing, $config_ref)) {
        for my $tframe (local_backup_timeframes($thing, $config_ref)) {
            my $dir = local_backup_dir($thing, $tframe, $config_ref);
            unless (-r $dir) {
                die "yabsm: error: do not have read permission on '$dir'\n";
            }
            opendir my $dh, $dir or confess "yabsm: internal error: could not opendir '$dir'";
            push @snapshots, map { $_ = "$dir/$_" } grep { is_snapshot_name($_) } readdir($dh);
            closedir $dh;
        }
    }

    else {
        die "yabsm: internal error: no such snap, ssh_backup, or local_backup named '$thing'";
    }

    @snapshots = sort_snapshots(\@snapshots);

    if ($query{type} eq 'all') {
        ;
    }

    elsif ($query{type} eq 'newest') {
        @snapshots = answer_newest_query(\@snapshots);
    }

    elsif ($query{type} eq 'oldest') {
        @snapshots = answer_oldest_query(\@snapshots);
    }

    elsif ($query{type} eq 'after') {
        @snapshots = answer_after_query($query{target}, \@snapshots);
    }

    elsif ($query{type} eq 'before') {
        @snapshots = answer_before_query($query{target}, \@snapshots);
    }

    elsif ($query{type} eq 'between') {
        @snapshots = answer_between_query($query{target1}, $query{target2}, \@snapshots);
    }

    elsif ($query{type} eq 'closest') {
        @snapshots = answer_closest_query($query{target}, \@snapshots);
    }

    else {
        confess("yabsm: internal error: no such query type $query{type}");
    }

    return wantarray ? @snapshots : \@snapshots;
}

sub answer_newest_query {

    # Return the newest snapshot in @snapshots. Because @snapshots is assumed to
    # be sorted from newest to oldest we know the newest snapshot is the first
    # snapshot in @snapshots.

    arg_count_or_die(1, 1, @_);

    my @newest;

    push @newest, shift->[0];

    return wantarray ? @newest : \@newest;
}

sub answer_oldest_query {

    # Return the oldest snapshot in @snapshots. Because @snapshots is assumed to
    # be sorted from newest to oldest we know the oldest snapshot is the last
    # snapshot in @snapshots.



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