File Coverage

blib/lib/Arepa/Repository.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package Arepa::Repository;
2              
3 1     1   165371 use strict;
  1         3  
  1         37  
4 1     1   5 use warnings;
  1         2  
  1         31  
5              
6 1     1   5 use Carp qw(croak);
  1         1  
  1         43  
7 1     1   5 use IO::Zlib;
  1         2  
  1         5  
8              
9 1     1   1121 use Parse::Debian::PackageDesc;
  1         26943  
  1         45  
10 1     1   718 use Arepa::Config;
  1         3  
  1         36  
11 1     1   807 use Arepa::PackageDb;
  0            
  0            
12              
13             sub new {
14             my ($class, $config_path) = @_;
15              
16             my $config = Arepa::Config->new($config_path);
17             my $self = bless {
18             config_path => $config_path,
19             config => $config,
20             package_db => Arepa::PackageDb->new($config->get_key('package_db')),
21             }, $class;
22              
23             return $self;
24             }
25              
26             sub config_key_exists {
27             my ($self, $key) = @_;
28             return $self->{config}->key_exists($key);
29             }
30              
31             sub get_config_key {
32             my ($self, $key) = @_;
33             return $self->{config}->get_key($key);
34             }
35              
36             sub get_distributions {
37             my ($self) = @_;
38              
39             my $repository_config_file = $self->get_config_key('repository:path');
40             my $distributions_config_file = "$repository_config_file/conf/distributions";
41             open F, $distributions_config_file or croak "Can't open configuration file ";
42             my ($line, $repo_attrs, @repos);
43             while ($line = ) {
44             if ($line =~ /^\s*$/) {
45             push @repos, $repo_attrs if (ref($repo_attrs) && %$repo_attrs);
46             $repo_attrs = {};
47             }
48             elsif ($line =~ /^([^:]+):\s+(.+)/i) {
49             $repo_attrs->{lc($1)} = $2;
50             }
51             }
52             push @repos, $repo_attrs if (ref($repo_attrs) && %$repo_attrs);
53             close F;
54             return @repos;
55             }
56              
57             sub get_architectures {
58             my ($self) = @_;
59              
60             my @archs;
61             foreach my $repo ($self->get_distributions) {
62             foreach my $arch (split(/\s+/, $repo->{architectures})) {
63             push @archs, $arch unless grep { $arch eq $_ }
64             @archs;
65             }
66             }
67             return @archs;
68             }
69              
70             sub insert_source_package {
71             my ($self, $dsc_file, $distro, %user_opts) = @_;
72              
73             use Parse::Debian::PackageDesc;
74             my $parsed_dsc = Parse::Debian::PackageDesc->new($dsc_file);
75             my @archs = $parsed_dsc->architecture;
76             my $arch = (scalar @archs > 1) ? 'any' : $archs[0];
77             my %args = (name => $parsed_dsc->name,
78             full_version => $parsed_dsc->version,
79             architecture => $arch,
80             distribution => $distro);
81              
82             if (exists $user_opts{comments}) {
83             $args{comments} = $user_opts{comments};
84             delete $user_opts{comments};
85             }
86              
87             my $canonical_distro = $distro;
88             if ($user_opts{canonical_distro}) {
89             $canonical_distro = $user_opts{canonical_distro};
90             delete $user_opts{canonical_distro};
91             }
92              
93             my $r = $self->_execute_reprepro('includedsc',
94             $canonical_distro,
95             $dsc_file,
96             %user_opts);
97             if ($r) {
98             return $self->{package_db}->insert_source_package(%args);
99             }
100             else {
101             return 0;
102             }
103             }
104              
105             sub insert_binary_package {
106             my ($self, $deb_file, $distro) = @_;
107              
108             return $self->_execute_reprepro('includedeb',
109             $distro,
110             $deb_file);
111             }
112              
113             sub _shell_escape {
114             my ($self, $arg) = @_;
115             if (defined $arg) {
116             $arg =~ s/'/\\'/go;
117             return "'$arg'";
118             }
119              
120             return "";
121             }
122              
123             sub last_cmd_output {
124             my ($self) = @_;
125             $self->{last_cmd_output};
126             }
127              
128             sub _execute_reprepro {
129             my ($self, $mode, $distro, $file_path, %extra_args) = @_;
130              
131             my $repo_path = $self->get_config_key("repository:path");
132             $mode = $self->_shell_escape($mode);
133             $distro = $self->_shell_escape($distro);
134             $file_path = $self->_shell_escape($file_path);
135             # Extra arguments
136             my $extra = "";
137             foreach my $arg (keys %extra_args) {
138             if ($arg eq 'section') {
139             $extra .= " --section " . $self->_shell_escape($extra_args{$arg});
140             }
141             elsif ($arg eq 'priority') {
142             $extra .= " --priority " . $self->_shell_escape($extra_args{$arg})
143             }
144             else {
145             croak "Don't know anything about argument '$arg'";
146             }
147             }
148             # GNUPG home directory
149             if ($self->config_key_exists('web_ui:gpg_homedir')) {
150             my $gpg_homedir = $self->get_config_key('web_ui:gpg_homedir');
151             if (defined $gpg_homedir && $gpg_homedir) {
152             $extra .= " --gnupghome '$gpg_homedir'";
153             }
154             }
155              
156             my $cmd = "reprepro -b$repo_path $extra $mode $distro $file_path 2>&1";
157             my $umask = umask;
158             umask($umask & 0707); # Always allow group permissions
159             $self->{last_cmd_output} = `$cmd`;
160             my $status = $?;
161             umask $umask;
162             if ($status == 0) {
163             return 1;
164             }
165             else {
166             print STDERR "Reprepro command failed: '$cmd'\n";
167             return 0;
168             }
169             }
170              
171             sub get_package_list {
172             my ($self) = @_;
173              
174             my %pkg_list;
175             my $repo_path = $self->get_config_key("repository:path");
176             foreach my $codename (map { $_->{codename} } $self->get_distributions) {
177             my $cmd = "reprepro -b$repo_path list $codename";
178             open PIPE, "$cmd |";
179             while () {
180             my ($distro, $comp, $arch, $pkg_name, $pkg_version) =
181             /(.+)\|(.+)\|(.+): ([^ ]+) (.+)/;
182             $pkg_list{$pkg_name}->{"$distro/$comp"}->{$pkg_version} ||= [];
183             push @{$pkg_list{$pkg_name}->{"$distro/$comp"}->{$pkg_version}},
184             $arch;
185             }
186             close PIPE;
187             }
188             return %pkg_list;
189             }
190              
191             sub get_source_package_information {
192             my ($self, $package_name, $distro) = @_;
193              
194             my $repo_path = $self->get_config_key('repository:path');
195             my $sources_file_path = File::Spec->catfile($repo_path,
196             'dists',
197             $distro,
198             'main',
199             'source',
200             'Sources.gz');
201              
202             my $fh = new IO::Zlib;
203             my $current_pkg = "";
204             my %props;
205             if ($fh->open($sources_file_path, "rb")) {
206             while (<$fh>) {
207             if (/^Package: (.+)/) {
208             $current_pkg = $1;
209             }
210             elsif ($current_pkg eq $package_name) {
211             if (/^([^:]+): (.+)/) {
212             $props{lc($1)} = $2;
213             }
214             }
215             }
216             $fh->close;
217             }
218              
219             return %props;
220             }
221              
222             sub get_binary_package_information {
223             my ($self, $package_name, $distro, $arch) = @_;
224              
225             my $repo_path = $self->get_config_key('repository:path');
226             my $packages_file_path = File::Spec->catfile($repo_path,
227             'dists',
228             $distro,
229             'main',
230             'binary-' . $arch,
231             'Packages');
232              
233             my $current_pkg = "";
234             my %props;
235             open F, $packages_file_path;
236             while () {
237             if (/^Package: (.+)/) {
238             $current_pkg = $1;
239             }
240             elsif ($current_pkg eq $package_name) {
241             if (/^([^:]+): (.+)/) {
242             $props{lc($1)} = $2;
243             }
244             }
245             }
246             close F;
247              
248             return %props;
249             }
250              
251             sub _all_names_for_distro {
252             my ($self, %properties) = @_;
253              
254             my @aliases = ($properties{codename});
255             if (defined $properties{suite}) {
256             push @aliases, $properties{suite};
257             }
258             return @aliases;
259             }
260              
261             sub add_distribution {
262             my ($self, %properties) = @_;
263              
264             my $repository_path = $self->get_config_key('repository:path');
265             my $distributions_config_file = "$repository_path/conf/distributions";
266              
267              
268             if (! defined $properties{codename}) {
269             return 0;
270             }
271             # Duplicate names of any kind
272             my @new_distro_names = $self->_all_names_for_distro(%properties);
273             my @existing_distro_names = map { $self->_all_names_for_distro(%$_) }
274             $self->get_distributions;
275             foreach my $distro_name (@new_distro_names) {
276             if (grep { $_ eq $distro_name } @existing_distro_names) {
277             return 0;
278             }
279             }
280              
281             # Everything seems alright, serialise the distribution properties
282             my $serialised_distro = join("\n",
283             map { ucfirst($_) . ": $properties{$_}" }
284             keys %properties);
285              
286             open F, ">>$distributions_config_file" or do {
287             print STDERR "Can't open $distributions_config_file for writing\n";
288             return 0;
289             };
290             print F <
291              
292             $serialised_distro
293             EOD
294             close F;
295              
296             # Now, update the repository with the new distro
297             $self->_execute_reprepro('export', $properties{codename});
298             }
299              
300             sub sign_distribution {
301             my ($self, $distro_name) = @_;
302              
303             my $repo_path = $self->get_config_key('repository:path');
304             my $release_file_path = File::Spec->catfile($repo_path,
305             "dists",
306             $distro_name,
307             "Release");
308             unlink "$release_file_path.gpg";
309              
310             my $extra_options = "";
311             if ($self->config_key_exists('repository:signature:id')) {
312             my $key_id = $self->get_config_key('repository:signature:id');
313             $extra_options = " -u $key_id";
314             }
315             my $gpg_cmd = "gpg --batch -abs $extra_options -o $release_file_path.gpg $release_file_path >/dev/null";
316              
317             return (system($gpg_cmd) == 0);
318             }
319              
320             sub sync_remote {
321             my ($self) = @_;
322              
323             my $repo_path = $self->get_config_key('repository:path');
324             if ($self->config_key_exists('repository:remote_path')) {
325             my $remote_repo_path = $self->get_config_key('repository:remote_path');
326             my $rsync_cmd = "rsync -avz --delete $repo_path $remote_repo_path";
327             if (system($rsync_cmd) == 0) {
328             return 1;
329             }
330             else {
331             print STDERR "Command was '$rsync_cmd'\n";
332             return 0;
333             }
334             }
335             return 0;
336             }
337              
338             sub is_synced {
339             my ($self) = @_;
340              
341             my $repo_path = $self->get_config_key('repository:path');
342             if ($self->config_key_exists('repository:remote_path')) {
343             my $remote_repo_path = $self->get_config_key('repository:remote_path');
344             my $rsync_cmd = "rsync -avz --delete --dry-run --out-format='AREPA_CHANGE %i' $repo_path $remote_repo_path";
345             my $changes = 0;
346              
347             open RSYNCOUTPUT, "$rsync_cmd |";
348             while () {
349             next unless /^AREPA_CHANGE/;
350             if (/^AREPA_CHANGE [^.]/) {
351             $changes = 1;
352             }
353             }
354             close RSYNCOUTPUT;
355              
356             return (! $changes);
357             }
358             return 0;
359             }
360              
361             1;
362              
363             __END__