File: aptitude-robot

package info (click to toggle)
aptitude-robot 1.5.1-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 520 kB
  • sloc: sh: 704; perl: 536; makefile: 32
file content (273 lines) | stat: -rwxr-xr-x 7,132 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#!/usr/bin/perl
# vim: set filetype=perl :
use strict;
use warnings;
use 5.010;
use English qw( -no_match_vars );
use Carp;
use autodie;
use Run::Parts;

main() unless caller(0);

sub main {
    use Pod::Usage;
    use Getopt::Long qw( :config pass_through no_auto_abbrev );

    my $help;
    my $pass_through_options;
    my $show_cmdline;
    my $force_install;
    my $config_dir = '/etc/aptitude-robot';
    GetOptions(
        'help'          => \$help,
        'show-cmdline'  => \$show_cmdline,
        'force-install' => \$force_install,
        'config-dir=s'  => \$config_dir,
    );
    pod2usage('-verbose' => 2, '-exit_status' => 0) if $help;

    my $aptitude_robot = Aptitude::Robot::Command->new(
        config_dir           => $config_dir,
        force_install        => $force_install,
        pass_through_options => \@ARGV,
    );
    die $aptitude_robot->error_msg() . "\n" if $aptitude_robot->error_msg();

    _run_triggers("$config_dir/triggers.pre", $show_cmdline);

    my @command = $aptitude_robot->command();
    if ($show_cmdline) {
        say "'" . join("' '", @command) . "'";
    }
    else {
        system(@command);
        if ($CHILD_ERROR == -1) {
            die "aptitude failed to execute: $OS_ERROR\n";
        }
        elsif ($CHILD_ERROR) {
            my $exit_code = ($CHILD_ERROR >> 8);
            printf STDERR "aptitude exited with value %d\n", $exit_code;
            exit $exit_code;
        }
    }

    _run_triggers("$config_dir/triggers.post", $show_cmdline);
}

sub _run_triggers {
    return unless @_;
    my ($triggers, $show_cmdline) = @_;

    if ( -d $triggers ) {
        my $rp = Run::Parts->new($triggers);
        if ($show_cmdline) {
            say $rp->test;
        } else {
            $rp->run;
        }
    }
    return;
}

BEGIN{
package Aptitude::Robot::Command;
use Moo;

has( 'config_dir' => (
        is       => 'ro',
        required => 1,
    )
);
has( 'pass_through_options' => (
        is       => 'ro',
        required => 0,
    )
);
has( 'force_install' => (
        is       => 'rw',
        default  => sub { return 0; },
    )
);
has( 'error_msg'  => (
        is       => 'ro',
        init_arg => undef,
        default  => sub {
            my $self = shift;
            if ( -d $self->config_dir() . '/pkglist.d/.' ) {
                return '';
            }
            else {
                return 'Error: ' . $self->config_dir . ' is not a aptitude-robot config directory';
            }
        },
    )
);

sub command {
    my $self = shift;
    return () if $self->error_msg();

    my %pkg_action = ();
    for my $line ($self->run_parts_lines('pkglist.d')) {
        my ($action, $pkg) = split(/\s+/, $line, 2);

        # ignore extra ':' and '=' entries on force-install
        next if
            $self->force_install()        and
            defined($pkg_action{$pkg})    and
            ($action eq ':' or $action eq '=');

        $pkg_action{$pkg} = $action;
    }
    my @cmd = ( 'aptitude' );
    push(@cmd, @{$self->pass_through_options()}) if $self->pass_through_options();
    push(@cmd, $self->options(), 'full-upgrade', '~U !~ahold' );
    for my $pkg (sort keys %pkg_action) {
        push(@cmd, $pkg . $pkg_action{$pkg});
    }
    return @cmd;
}

sub options {
    my $self = shift;
    return () if $self->error_msg();

    return
        _split_parameters($self->run_parts_lines('options.d'));
}

sub run_parts_lines {
    my ($self, $parts_dir) = @_;
    return () if $self->error_msg();

    my $full_path = $self->config_dir() . "/$parts_dir";
    return () unless -d $full_path;

    my $rp = Run::Parts->new($full_path);
    return _clean_comments_and_whitespace( $rp->concat );
}

sub _clean_comments_and_whitespace {
    my (@lines) = @_;

    for my $line (@lines) {
        $line =~ s{\# .* \Z}{}xms; # remove comments
        $line =~ s{\A \s+}{}xms; # remove leading space
        $line =~ s{\s+ \Z}{}xms; # remove trailing space
    }
    return grep { $_ ne '' } @lines; # non-empty lines only
}

sub _split_parameters {
    my (@lines) = @_;

    my @parameters = ();
    for my $line (@lines) {
        push(@parameters, split(qr(=|\s+), $line, 2));
    }
    return @parameters;
}

no Moo;

1;
}

__END__

=head1 NAME

aptitude-robot - automate package choice management

=head1 SYNOPSIS

aptitude-robot [options]

aptitude-robot --help

=head1 DESCRIPTION

aptitude-robot uses configuration files to install and remove Debian software
packages automatically.  This allows hands-off setup and maintenance of
workstations and servers.  Create package lists in a development environment
and copy the package lists over to the production machines.  aptitude-robot
will then make sure that the packages mentioned in the lists are installed or
removed as indicated.

=head1 OPTIONS

=over 4

=item B<--force-install>

give priority to install(+), remove(-), and purge(_) actions over keep(:) and hold(=)

=item B<--config-dir F</path/to/config/dir>>

specify an alternate configuration directory.  Defaults to F</etc/aptitude-robot>

=item B<--show-cmdline>

only show the command that would be executed

=item B<--help>

Prints this page.

=item I<other options>

any option not recognized by aptitude-robot itself is passed through to
aptitude.  The options B<-q> and B<-s> are likely to be used occasionally.  See
L<aptitude(8)> for details.

=back

=head1 CONFIGURATION

The configuration directory given by the C<--config-dir> (or
F</etc/aptitude-robot> by default) must contain a directory named F<pkglist.d>.
This directory may have several package list files that are chosen according to
the criteria of L<run-parts(8)>.  They are concatenated in the order given by
L<run-parts(8)>.

Each line shall contain an action and a package name separated by white space.
The actions are those used by the C<aptitude> command line interface (see
aptitude documentation).  Typically they are:

I<+  package  # install package>

I<+M package  # install package and mark it as automatically installed>

I<-  package  # remove package>

I<_  package  # purge package>

I<:  package  # keep package at current version>

I<=  package  # mark a package as "hold", i.e., prevent automatic updates>

If a package is mentioned several times the last entry will determine the
action.  If the C<--force-install> option is given the keep(:) and hold(=)
actions are given lower priority, i.e., the appear only when no other action
for the package is specified.

A C<#> character starts a comment extending to the end of line.  Empty lines or
extra white space at the beginning or end of line is ignored.

Optionally the configuration directory may contain a file name F<options>
containing extra options given to aptitude in the form of one option per line.

Optionally the configuration directory may contain two directories
F<triggers.pre> and F<triggers.post> with scripts that are executed by
L<run-parts(8)> before and after aptitude respectively.

=head1 SEE ALSO

L<aptitude(8)>, L<aptitude-robot-session(8)>

=head1 AUTHORS

Elmar S. Heeb <elmar@heebs.ch> and Axel Beckert <abe@debian.org>

=cut