simplified ResolvConf probe output and make it conform to standard file data structure
[scpubgit/System-Introspector.git] / lib / System / Introspector / Probe / Packages / Apt.pm
1 package System::Introspector::Probe::Packages::Apt;
2 use Moo;
3 use File::Basename;
4
5 use System::Introspector::Util qw(
6     handle_from_command
7     transform_exceptions
8     output_from_file
9     files_from_dir
10 );
11
12 has apt_lists_dir => (is => 'ro', builder => 1);
13 has apt_update_after => (is => 'ro', default => sub { 86400 });
14 has apt_update => (is => 'ro');
15
16 has apt_sources => (is => 'ro', builder => 1);
17 has apt_sources_dir => (is => 'ro', builder => 1);
18
19 sub _build_apt_lists_dir   { '/var/lib/apt/lists' }
20 sub _build_apt_sources     { '/etc/apt/sources.list' }
21 sub _build_apt_sources_dir { '/etc/apt/sources.list.d' }
22
23 sub gather {
24     my ($self) = @_;
25     return {
26         update => {
27             last => $self->_last_apt_update,
28             run => transform_exceptions {
29                 return { result => $self->_check_apt_state };
30             },
31         },
32         installed => transform_exceptions {
33             return { packages => $self->_gather_installed };
34         },
35         upgradable => transform_exceptions {
36             return { actions => $self->_gather_upgradable };
37         },
38         sources => transform_exceptions {
39             return { config => $self->_gather_sources };
40         },
41     };
42 }
43
44 sub _last_apt_update {
45     my ($self) = @_;
46     return scalar( (stat($self->apt_lists_dir))[9] );
47 }
48
49 sub _check_apt_state {
50     my ($self) = @_;
51     return 'disabled' unless $self->apt_update;
52     my $threshold   = $self->apt_update_after;
53     my $last_change = $self->_last_apt_update;
54     return 'no'if ($last_change + $threshold) > time;
55     handle_from_command 'apt-get update';
56     return 'yes';
57 }
58
59 sub _open_dpkg_query_pipe {
60     my ($self) = @_;
61     return handle_from_command 'dpkg-query --show';
62 }
63
64 sub _open_apt_get_upgrade_simulation_pipe {
65     my ($self) = @_;
66     return handle_from_command 'apt-get -s upgrade';
67 }
68
69 sub _gather_sources {
70     my ($self) = @_;
71     my $sources_dir = $self->apt_sources_dir;
72     return {
73         'sources_list' => $self->_fetch_source_list($self->apt_sources),
74         'sources_list_dir' => (-e $sources_dir) ? transform_exceptions {
75             return +{ files => +{ map {
76                 ($_, $self->_fetch_source_list("$sources_dir/$_"));
77             } files_from_dir $sources_dir } };
78         } : {},
79     };
80 }
81
82 sub _fetch_source_list {
83     my ($self, $file) = @_;
84     return transform_exceptions {
85         return {
86             file => {
87                 file_name => $file,
88                 body => scalar(output_from_file $file),
89             },
90         };
91     };
92 }
93
94 sub _gather_upgradable {
95     my ($self) = @_;
96     my $pipe = $self->_open_apt_get_upgrade_simulation_pipe;
97     my %action;
98     while (defined( my $line = <$pipe> )) {
99         chomp $line;
100         if ($line =~ m{^(inst|remv)\s+(\S+)\s+(.+)$}i) {
101             $action{ lc($1) }{ $2 } = $3;
102         }
103     }
104     return \%action;
105 }
106
107 sub _gather_installed {
108     my ($self) = @_;
109     my $pipe = $self->_open_dpkg_query_pipe;
110     my %package;
111     while (defined( my $line = <$pipe> )) {
112         chomp $line;
113         my ($package, $version) = split m{\s+}, $line;
114         $package{ $package } = {
115             version => $version,
116         };
117     }
118     return \%package;
119 }
120
121 1;
122
123 __END__
124
125 =head1 NAME
126
127 System::Introspector::Packages::Apt - Gather APT package status
128
129 =head1 DESCRIPTION
130
131 Uses C<dpkg-query> to list all installed packages.
132
133 =head1 SEE ALSO
134
135 =over
136
137 =item L<System::Introspector>
138
139 =back
140
141 =cut