File Coverage

blib/lib/Doit/Deb.pm
Criterion Covered Total %
statement 14 93 15.0
branch 0 52 0.0
condition 0 3 0.0
subroutine 6 9 66.6
pod 3 5 60.0
total 23 162 14.2


line stmt bran cond sub pod time code
1             # -*- perl -*-
2              
3             #
4             # Author: Slaven Rezic
5             #
6             # Copyright (C) 2017,2018 Slaven Rezic. All rights reserved.
7             # This package is free software; you can redistribute it and/or
8             # modify it under the same terms as Perl itself.
9             #
10             # Mail: slaven@rezic.de
11             # WWW: http://www.rezic.de/eserte/
12             #
13              
14             package Doit::Deb; # Convention: all commands here should be prefixed with 'deb_'
15              
16 1     1   7 use strict;
  1         2  
  1         30  
17 1     1   23 use warnings;
  1         25  
  1         101  
18             our $VERSION = '0.022';
19              
20 1     1   7 use Doit::Log;
  1         2  
  1         61  
21 1     1   6 use Doit::Util 'get_sudo_cmd';
  1         2  
  1         1073  
22              
23 1     1 0 10 sub new { bless {}, shift }
24 1     1 0 4 sub functions { qw(deb_install_packages deb_missing_packages deb_install_key) }
25              
26             sub deb_install_packages {
27 0     0 1   my($self, @packages) = @_;
28 0           my @missing_packages = $self->deb_missing_packages(@packages); # XXX cmd vs. info???
29 0 0         if (@missing_packages) {
30 0           $self->system(get_sudo_cmd(), 'apt-get', '-y', 'install', @missing_packages);
31             }
32 0           @missing_packages;
33             }
34              
35              
36             sub deb_missing_packages {
37 0     0 1   my($self, @packages) = @_;
38              
39 0           my @missing_packages;
40              
41 0 0         if (@packages) {
42 0           require IPC::Open3;
43 0           require Symbol;
44              
45 0           my %seen_packages;
46             my %required_version;
47 0           for my $package (@packages) {
48 0 0         if (ref $package eq 'ARRAY') {
49 0           my($package_name, $package_version) = @$package;
50 0           $required_version{$package_name} = $package_version;
51 0           $package = $package_name;
52             }
53             }
54 0           my @cmd = ('dpkg-query', '-W', '-f=${Package}\t${Status}\t${Version}\n', @packages);
55 0           my $err = Symbol::gensym();
56 0           my $fh;
57 0 0         my $pid = IPC::Open3::open3(undef, $fh, $err, @cmd)
58             or error "Error running '@cmd': $!";
59 0           while(<$fh>) {
60 0           chomp;
61 0 0         if (m{^([^\t]+)\t([^\t]+)\t([^\t]*)$}) {
62 0 0         if ($2 ne 'install ok installed') {
63 0           push @missing_packages, $1;
64             }
65 0 0 0       if ($required_version{$1} && $required_version{$1} ne $3) {
66 0           push @missing_packages, $1;
67             }
68 0           $seen_packages{$1} = 1;
69             } else {
70 0           warning "cannot parse '$_', ignore line...";
71             }
72             }
73 0           waitpid $pid, 0;
74 0           for my $package (@packages) {
75 0 0         if (!$seen_packages{$package}) {
76 0           push @missing_packages, $package;
77             }
78             }
79             }
80 0           @missing_packages;
81             }
82              
83             sub deb_install_key {
84 0     0 1   my($self, %opts) = @_;
85 0           my $url = delete $opts{url};
86 0           my $keyserver = delete $opts{keyserver};
87 0           my $key = delete $opts{key};
88 0 0         error "Unhandled options: " . join(" ", %opts) if %opts;
89              
90 0 0         if (!$url) {
91 0 0         if (!$keyserver) {
92 0           error "keyserver is missing";
93             }
94 0 0         if (!$key) {
95 0           error "key is missing";
96             }
97             } else {
98 0 0         if ($keyserver) {
99 0           error "Don't define both url and keyserver";
100             }
101             }
102              
103 0           my $found_key;
104 0 0         if ($key) {
105 0           $key =~ s{\s}{}g; # convenience: strip spaces from key ('apt-key finger' returns them with spaces)
106 0           local $ENV{LC_ALL} = 'C';
107             # XXX If run with $sudo, then this will emit warnings in the form
108             # gpg: WARNING: unsafe ownership on configuration file `$HOME/.gnupg/gpg.conf'
109             # Annoying, but harmless. Could be workarounded by specifying
110             # '--homedir=/root/.gpg', but this would create gpg files under ~root. Similar
111             # if using something like
112             # local $ENV{HOME} = (getpwuid($<))[7];
113             # Probably better would be to work with privilege escalation and run
114             # this command as normal user (to be implemented).
115             #
116             # Older Debian (jessie and older?) have only /etc/apt/trusted.gpg,
117             # newer ones (stretch and newer?) have /etc/apt/trusted.gpg.d/*.gpg
118             SEARCH_FOR_KEY: {
119 0           require File::Glob;
  0            
120 0           for my $keyfile ('/etc/apt/trusted.gpg', File::Glob::bsd_glob('/etc/apt/trusted.gpg.d/*.gpg')) {
121 0 0         if (-r $keyfile) {
122 0           my @cmd = ('gpg', '--keyring', $keyfile, '--list-keys', '--fingerprint', '--with-colons');
123 0 0         open my $fh, '-|', @cmd
124             or error "Running '@cmd' failed: $!";
125 0           while(<$fh>) {
126 0 0         if (m{^fpr:::::::::\Q$key\E:$}) {
127 0           $found_key = 1;
128 0           last SEARCH_FOR_KEY;
129             }
130             }
131 0 0         close $fh
132             or error "Running '@cmd' failed: $!";
133             }
134             }
135             }
136             }
137              
138 0           my $changed = 0;
139 0 0         if (!$found_key) {
140 0 0         if ($keyserver) {
    0          
141 0           $self->system(get_sudo_cmd(), 'apt-key', 'adv', '--keyserver', $keyserver, '--recv-keys', $key);
142             } elsif ($url) {
143 0           my @fetch_cmd = ('curl', '-fsSL', $url);
144 0           my @add_cmd = (get_sudo_cmd(), 'apt-key', 'add', '-');
145 0 0         if ($self->is_dry_run) {
146 0           info "Fetch key using '@fetch_cmd' and add using '@add_cmd' (dry-run)";
147             } else {
148 0 0         open my $ifh, '-|', @fetch_cmd
149             or error "Failed to start '@fetch_cmd': $!";
150 0 0         open my $ofh, '|-', @add_cmd
151             or error "Failed to start '@add_cmd': $!";
152 0           local $/ = \1024;
153 0           while(<$ifh>) {
154 0           print $ofh $_;
155             }
156 0 0         close $ofh
157             or error "Running '@add_cmd' failed: $!";
158 0 0         close $ifh
159             or error "Running '@fetch_cmd' failed: $!";
160             }
161             } else {
162 0           error "Shouldn't happen (either url or keyserver has to be specified)";
163             }
164 0           $changed = 1;
165             }
166 0           $changed;
167             }
168              
169              
170             1;
171              
172             __END__