File Coverage

lib/App/MtAws/ConfigDefinition.pm
Criterion Covered Total %
statement 223 231 96.5
branch 61 74 82.4
condition 26 39 66.6
subroutine 27 29 93.1
pod 0 15 0.0
total 337 388 86.8


line stmt bran cond sub pod time code
1             # mt-aws-glacier - Amazon Glacier sync client
2             # Copyright (C) 2012-2014 Victor Efimov
3             # http://mt-aws.com (also http://vs-dev.com) vs@vs-dev.com
4             # License: GPLv3
5             #
6             # This file is part of "mt-aws-glacier"
7             #
8             # mt-aws-glacier is free software: you can redistribute it and/or modify
9             # it under the terms of the GNU General Public License as published by
10             # the Free Software Foundation, either version 3 of the License, or
11             # (at your option) any later version.
12             #
13             # mt-aws-glacier is distributed in the hope that it will be useful,
14             # but WITHOUT ANY WARRANTY; without even the implied warranty of
15             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16             # GNU General Public License for more details.
17             #
18             # You should have received a copy of the GNU General Public License
19             # along with this program. If not, see <http://www.gnu.org/licenses/>.
20              
21             package App::MtAws::ConfigDefinition;
22              
23             our $VERSION = "1.114_2";
24              
25 113     113   10386095 use strict;
  113         229  
  113         3252  
26 113     113   536 use warnings;
  113         157  
  113         2881  
27 113     113   43287 use utf8;
  113         640  
  113         671  
28 113     113   3105 use File::Spec;
  113         165  
  113         2990  
29 113     113   74739 use Encode;
  113         1076478  
  113         10636  
30 113     113   898 use Carp;
  113         209  
  113         8275  
31 113     113   636 use List::Util qw/first/;
  113         179  
  113         11211  
32 113     113   77050 use App::MtAws::Utils;
  113         335  
  113         17551  
33 113     113   135379 use App::MtAws::ConfigEngine;
  113         408  
  113         17964  
34 113     113   75579 use App::MtAws::Filter;
  113         282  
  113         5393  
35 113     113   705 use LWP::UserAgent;
  113         165  
  113         409858  
36              
37             sub filter_options
38             {
39 431     431 0 1328 my $filter_error = message 'filter_error', "Error in parsing filter %s a%";
40 431         708 scope 'filters', do {
41 431         1130 my @l = optional(qw/include exclude filter/);
42 431 100   1223   2580 if (first { present } @l) {
  1223         2279  
43 85         371 my $F = App::MtAws::Filter->new();
44 85         266 for (lists @l) {
45 145 100       436 if ($_->{name} eq 'filter') {
    100          
    50          
46 80         287 $F->parse_filters($_->{value});
47 80 100       280 return error $filter_error, a => $F->{error} if defined $F->{error};
48             } elsif ($_->{name} eq 'include') {
49 30         122 $F->parse_include($_->{value});
50             } elsif ($_->{name} eq 'exclude') {
51 35         123 $F->parse_exclude($_->{value});
52             } else {
53 0         0 confess;
54             }
55             }
56 70         261 @l, custom('parsed', $F);
57             } else {
58 346         1480 @l;
59             }
60             }
61             }
62              
63             sub check_base_dir
64             {
65 123 50 33 123 0 302 if ( present('base-dir') && !present('dir') ) {
    100          
66 0         0 error('base-dir can be used only with dir');
67             } elsif ( present('dir') ) {
68 58         134 optional('base-dir');
69             } else {
70 65         167 return;
71             }
72             }
73              
74             #sub abs_dir()
75             #{
76             # custom 'abs-dir', File::Spec->rel2abs(value('dir'));
77             #}
78              
79             sub mandatory_maxsize
80             {
81 55 100   55 0 136 unless (present(optional('check-max-file-size'))) {
82 1         4 error('mandatory_with', a => 'check-max-file-size', b => seen('stdin'));
83             }
84             'check-max-file-size'
85 55         213 }
86              
87             sub check_dir_or_relname
88             {
89              
90 123     123 0 351 message 'mutual', "%option a% and %option b% are mutual exclusive";
91 123         346 message 'mandatory_with', "Need to use %option b% together with %option a%";
92 123 100       269 if (present('filename')) {
    100          
93 66         185 custom('data-type', 'filename'), mandatory('filename'), do {
94 66 100       150 if (present('set-rel-filename')) {
    100          
95 9 100       28 if (present('dir')) {
96 1         4 error('mutual', a => seen('set-rel-filename'), b => seen('dir'));
97             } else {
98 8         21 custom('name-type', 'rel-filename'), mandatory('set-rel-filename'), custom('relfilename', value('set-rel-filename'));
99             }
100             } elsif (present('dir')) {
101 56         77 custom('relfilename', do {
102 56         151 validate 'dir', 'filename';
103 56 100 100     163 if (valid('dir') && valid('filename')) {
104              
105 49         115 my $b_dir = binary_abs_path(binaryfilename value('dir'));
106 49         162 my $b_file = binary_abs_path(binaryfilename value('filename'));
107              
108 49 50       195 if (!defined $b_dir) {
    50          
109 0         0 error(message('cannot_resolve_dir',
110             'Directory specified with "%option a%" cannot be resolved to full path'),
111             a => 'dir'), undef
112             } elsif (!defined $b_file) {
113 0         0 error(message('cannot_resolve_file',
114             'File specified with "%option a%" cannot be resolved to full path'),
115             a => 'filename'), undef;
116             } else {
117 49         171 my $relfilename = characterfilename abs2rel($b_file, $b_dir, allow_rel_base => 0, use_filename_encoding => 0);
118              
119 49         1531 my $dir = value('dir');
120 49         146 $dir =~ s!/$!!; # just in case
121              
122 49 50       113 confess "something wrong with relative-absolute paths"
123             unless file_inodev(value('filename')) eq file_inodev($dir."/".$relfilename);
124              
125 49 100       646 if (!is_relative_filename($relfilename)) {
126 4         13 error(message('filename_inside_dir',
127             'File specified with "option a" should be inside directory specified in %option b%'),
128             a => 'filename', b => 'dir'),
129             undef;
130             } else {
131 45         177 $relfilename
132             }
133             }
134             } else {
135 7         19 undef;
136             }
137             }), custom('name-type', 'dir'), mandatory('dir');
138             } else {
139 1         5 error(message('either', 'Please specify %option a% or %option b%'), a => 'set-rel-filename', b => 'dir');
140             }
141             }
142             } elsif (present('stdin')) {
143 56 100       134 if (present('set-rel-filename')) {
144 55 100       130 if (present('dir')) {
145 1         4 seen('stdin'), mandatory_maxsize, error('mutual', a => seen('set-rel-filename'), b => seen('dir'));
146             } else {
147 54         176 custom('name-type', 'rel-filename'), custom('data-type', 'stdin'), mandatory('set-rel-filename'), mandatory('stdin'),
148             custom('relfilename', value('set-rel-filename')), mandatory_maxsize;
149             }
150             } else {
151 1         4 error('mandatory_with', a => 'set-rel-filename', b => seen('stdin'))
152             }
153             } else {
154 1         4 error(message 'Please specify filename or stdin')
155             }
156             }
157              
158             sub http_download_options
159             {
160 59     59 0 169 scope('file_downloads', optional('segment-size'))
161             }
162              
163             sub download_options
164             {
165 0     0 0 0 mandatory('dir'), check_base_dir, http_download_options();
166             }
167              
168             sub check_wait
169             {
170 0 0   0 0 0 if (present('wait')) {
171 0         0 mandatory('wait'), download_options
172             } else {
173 0         0 return;
174             }
175             }
176              
177             sub existing_journal
178             {
179 205     205 0 316 my ($journal) = @_;
180 205 100 66     826 if (defined($journal) && present($journal) && !exists $App::MtAws::ConfigEngine::context->{override_validations}->{journal}) { # TODO: this is hack!
      100        
181 1 50       3 error('Journal file not found') unless -f binaryfilename value($journal);
182             }
183 205         642 $journal;
184             }
185              
186             sub writable_journal
187             {
188 463     463 0 629 my ($journal) = @_;
189 463 100 66     1771 if (defined($journal) && present($journal)) {
190 455         1004 my $value = binaryfilename value($journal);
191 455 0 0     22111 error('Journal file not writable') if (-e $value && (! -w $value or -d $value));
      33        
192             }
193 463         1853 $journal;
194             }
195              
196             sub empty_journal
197             {
198 9     9 0 11 my ($journal) = @_;
199 9 50 33     33 if (defined($journal) && present($journal)) {
200 9 50       20 error('Journal file not empty - please provide empty file no write new journal') unless ! -s binaryfilename value($journal);
201             }
202 9         413 $journal;
203             }
204              
205             sub check_https
206             {
207 603 100 66 603 0 1835 if (present('protocol') and value('protocol') eq 'https') {
208 20 100       153 if (LWP::UserAgent->is_protocol_supported("https")) {
209             # to get LWP version, use LWP->VERSION instead of LWP::UserAgent->VERSION
210 15 100       836 error('LWP::UserAgent 6.x required to use HTTPS') unless LWP->VERSION() ge '6';
211 15         182 require LWP::Protocol::https;
212 15 100 66     291 error('LWP::Protocol::https 6.x required to use HTTPS') unless LWP::Protocol::https->VERSION && LWP::Protocol::https->VERSION ge '6';
213             } else {
214 5         35 error('IO::Socket::SSL or LWP::Protocol::https is not installed');
215             }
216             }
217 603         2269 return;
218             }
219              
220             sub check_max_size
221             {
222 123 100   123 0 232 if (present('check-max-file-size')) {
223 54 100       115 if (value('check-max-file-size') > 10_000 * value('partsize')) {
224 24         60 seen('check-max-file-size'), error(message('partsize_vs_maxsize',
225             "With current partsize %d partsizevalue%MiB and maximum allowed file size %d maxsizevalue%MiB, upload might exceed 10 000 parts. ".
226             "Increase %option partsize% or decrease %option maxsize%"),
227             partsize => 'partsize', maxsize => 'check-max-file-size', partsizevalue => value('partsize'), maxsizevalue => value('check-max-file-size'));
228             } else {
229 30         77 seen('check-max-file-size')
230             }
231             } else {
232 69         210 return;
233             }
234             }
235              
236             sub detect_opts
237             {
238              
239 226     226 0 628 seen('detect'), do { # TODO: movify configengine to somehow simplify this
240 226 100 100     1479 explicit('detect') && (!present('replace-modified')) ?
241             error("option_for_command_can_be_used_only_with", a => 'detect', b => 'replace-modified', c => 'sync') :
242             ();
243             };
244             }
245              
246             sub sync_opts
247             {
248 226     226 0 636 my @sync_opts = qw/new replace-modified delete-removed/;
249 226         1527 optional(@sync_opts);
250 226 100 100     710 if (present('new') || present('replace-modified') || present('delete-removed')) {
      100        
251             @sync_opts
252 85         370 } else {
253 141         453 impose('new', 1); # TODO: can cause problems in the future
254             }
255             }
256              
257             sub get_config
258             {
259 623     623 0 1122472 my (%args) = @_;
260              
261 623         4045 my $c = App::MtAws::ConfigEngine->new(ConfigOption => 'config', CmdEncoding => 'terminal-encoding', ConfigEncoding => 'config-encoding', %args);
262              
263             $c->{preinitialize} = sub {
264 603     603   3007 set_filename_encoding $c->{options}{'filenames-encoding'}{value};
265 623         2763 };
266              
267             $c->define(sub {
268              
269 623     623   1647 message 'no_command', 'Please specify command', allow_redefine=>1;
270 623         1686 message 'already_specified_in_alias', '%option b% specified, while %option a% already defined', allow_redefine => 1;
271 623         1525 message 'unexpected_argument', "Extra argument in command line: %a%", allow_redefine => 1;
272 623         1339 message 'mandatory', "Please specify %option a%", allow_redefine => 1;
273 623         1275 message 'cannot_read_config', 'Cannot read config file "%config%"';
274 623         1360 message 'deprecated_option', '%option% deprecated, use %main% instead';
275 623         1304 message 'option_for_command_can_be_used_only_with', "Option %option a% for %command c% command can be used only together with %option b%";
276              
277              
278 623         2568 for (option 'dir', deprecated => ['to-dir', 'from-dir']) {
279 623         1525 validation $_, message('%option a% should be less than 512 characters'), stop => 1, sub { length($_) < 512 }; # TODO: check that dir is dir
  87         439  
280 623         2303 validation $_, message('%option a% not a directory'), stop => 1, sub { -d binaryfilename };
  86         222  
281             }
282              
283 623         1490 option 'base-dir';
284 623         1531 validation option('leaf-optimization', default => 1), message('%option a% should be either "1" or "0"'), sub { /^[01]$/ };
  226         1229  
285 623         1572 option 'follow', type=>'';
286              
287 623         1451 for (option 'filename') {
288 623         1535 validation $_, message('%option a% not a file'), stop => 1, sub { -f binaryfilename };
  59         163  
289 623         1828 validation $_, message('%option a% file not readable'), stop => 1, sub { -r binaryfilename };
  54         127  
290 623         1735 validation $_, message('%option a% file size is zero'), stop => 1, sub { -s binaryfilename };
  54         113  
291             }
292              
293              
294 623         1621 for (option 'set-rel-filename') {
295             validation $_, message('require_relative_filename', '%option a% should be canonical relative filename'),
296             stop => 1,
297 623         1430 sub { is_relative_filename($_) };
  62         204  
298             }
299 623         1633 option 'stdin', type=>'';
300              
301 623         1474 option 'vault', deprecated => 'to-vault';
302 623         1427 option 'config', binary => 1;
303 623         1946 options 'journal', 'job-id', 'max-number-of-files', 'new-journal';
304              
305             my @encodings =
306 623         1156 map { option($_, binary =>1, default => 'UTF-8') }
  2492         4255  
307             qw/terminal-encoding config-encoding filenames-encoding journal-encoding/;
308              
309 623         1326 for (@encodings) {
310 2492         7495 validation $_, 'unknown_encoding', sub { find_encoding($_) };
  2412         3837  
311             }
312              
313              
314 623         1184 my @filters = map { option($_, type => 's', list => 1) } qw/include exclude filter/;
  1869         3243  
315              
316 623         1661 option 'dry-run', type=>'';
317              
318 623         1383 my $invalid_format = message('invalid_format', 'Invalid format of "%a%"');
319 623         1351 my $must_be_an_integer = message('must_be_an_integer', '%option a% must be positive integer number');
320              
321 623         1388 option('new', type=>'');
322 623         1346 option('replace-modified', type=>'');
323 623         1346 option('delete-removed', type=>'');
324              
325              
326             # treehash, mtime, mtime-and-treehash, mtime-or-treehash
327             # mtime-and-treehash := treat_as_modified if differs(mtime) && differs(treehash)
328             # mtime-or-treehash := treat_as_modified if differs(mtime) or differs(treehash)
329             validation
330             option('detect', default => 'mtime-and-treehash'),
331             $invalid_format,
332 623         1452 sub { my $v = $_; first { $_ eq $v } qw/treehash mtime mtime-and-treehash mtime-or-treehash always-positive size-only/ };
  226         375  
  226         1305  
  732         1671  
333              
334             my @config_opts = (
335 7         36 validation(option('key'), $invalid_format, sub { /^[A-Za-z0-9]{20}$/ }),
336 7         30 validation(option('secret'), $invalid_format, sub { /^[\x21-\x7e]{40}$/ }),
337 596         4497 validation(option('region'), $invalid_format, sub { /^[A-Za-z0-9\-]{3,20}$/ }),
338 27         313 optional(validation(option('token'), $invalid_format, sub { /^[\x21-\x7e]{20,1024}$/ })),
339 603         3639 validation(option('timeout', default => 180), $invalid_format, sub { /^[0-9]{1,5}$/ }),
340 623         1556 validation(option('protocol', default => 'http'), message('protocol must be "https" or "http"'), sub { /^https?$/ }),
  603         2863  
341             );
342              
343 623         3522 for (option('concurrency', type => 'i', default => 4)) {
344 623         2544 validation $_, $must_be_an_integer, stop => 1, sub { $_ =~ /^\d+$/ };
  522         3189  
345 623 50       1526 validation $_, message('Max concurrency is 30, Min is 1'), sub { $_ >= 1 && $_ <= 30 };
  522         3158  
346             }
347              
348 623         1688 for (option('check-max-file-size', type => 'i')) {
349 623         2735 validation $_, $must_be_an_integer, stop => 1, sub { $_ =~ /^\d+$/ };
  54         332  
350 623         1589 validation $_, message('check-max-file-size should be greater than 0'), stop => 1, sub { $_ > 0 }; # TODO: %option .. %
  54         187  
351             validation $_, message('maxsize_too_big', '%option a% should be less than or equal to 40960000 (and you have %value%)'),
352 623         1659 stop => 1, sub { $_ <= 10_000 * 4096 };
  54         158  
353             }
354              
355 623         1750 for (option('partsize', type => 'i', default => 16)) {
356 623         2668 validation $_, $must_be_an_integer, stop => 1, sub { $_ =~ /^\d+$/ };
  349         1925  
357 623 100       1571 validation $_, message('Part size must be power of two'), sub { ($_ != 0) && (($_ & ($_ - 1)) == 0) };
  349         2471  
358 623         1520 validation $_, message('%option a% must be less or equal to 4096'), sub { $_ <= 4096 };
  349         1068  
359             }
360 623         1687 for (option('segment-size', type => 'i')) {
361 623         2679 validation $_, $must_be_an_integer, stop => 1, sub { $_ =~ /^\d+$/ };
  22         135  
362 623         1501 validation $_, message('%option a% must be zero or power of two'), sub { (($_ & ($_ - 1)) == 0) }; # TODO: proper format
  22         135  
363             }
364              
365             validation(option('request-inventory-format', default => 'json'),
366 623         1685 message('request-inventory-format must be "json" or "csv"'), sub { /^(json|csv)$/ });
  16         102  
367              
368             validation positional('vault-name'), message('Vault name should be 255 characters or less and consisting of a-z, A-Z, 0-9, ".", "-", and "_"'), sub {
369 23         179 /^[A-Za-z0-9\.\-_]{1,255}$/
370 623         1886 };
371              
372 623         3012 command 'create-vault' => sub { validate(optional('config'), mandatory(@encodings), mandatory('vault-name'), mandatory(@config_opts), check_https)};
  15         55  
373 623         2593 command 'delete-vault' => sub { validate(optional('config'), mandatory(@encodings), mandatory('vault-name'), mandatory(@config_opts), check_https)};
  9         35  
374              
375             command 'sync' => sub {
376 226         1803 validate(mandatory(
377             optional('config'), mandatory(@encodings), @config_opts, sync_opts, detect_opts, check_https,
378             qw/dir vault concurrency partsize/, writable_journal('journal'),
379             optional(qw/max-number-of-files leaf-optimization follow/),
380             filter_options, optional('dry-run')
381             ))
382 623         2496 };
383              
384             command 'upload-file' => sub {
385 123         354 validate(mandatory( optional('config'), mandatory(@encodings), @config_opts, check_https, qw/vault concurrency/, writable_journal('journal'),
386             check_dir_or_relname, check_base_dir, mandatory('partsize'), check_max_size ))
387 623         2648 };
388              
389              
390             command 'purge-vault' => sub {
391 50         155 validate(mandatory(
392             optional('config'), mandatory(@encodings), @config_opts, check_https, qw/vault concurrency/,
393             writable_journal(existing_journal('journal')),
394             deprecated('dir'), filter_options, optional('dry-run')
395             ))
396 623         2578 };
397              
398             command 'restore' => sub {
399 64         226 validate(mandatory(
400             optional('config'), mandatory(@encodings), @config_opts, check_https, qw/dir vault max-number-of-files concurrency/,
401             writable_journal(existing_journal('journal')),
402             filter_options, optional('dry-run')
403             ))
404 623         2435 };
405              
406             command 'restore-completed' => sub {
407 59         253 validate(mandatory(
408             optional('config'), mandatory(@encodings), @config_opts, check_https, qw/dir vault concurrency/, existing_journal('journal'),
409             filter_options, optional('dry-run'), http_download_options
410             ))
411 623         2396 };
412              
413             command 'check-local-hash' => sub {
414 32         132 validate(mandatory(
415             optional('config'), mandatory(@encodings), @config_opts, check_https, qw/dir/, existing_journal('journal'), deprecated('vault'),
416             filter_options, optional('dry-run')
417             ))
418 623         2477 };
419              
420             command 'retrieve-inventory' => sub {
421 16         44 validate(mandatory(optional('config'), mandatory(@encodings), 'request-inventory-format', @config_opts, check_https, qw/vault/))
422 623         2549 };
423              
424             command 'download-inventory' => sub {
425 9         30 validate(mandatory(optional('config'), mandatory(@encodings), @config_opts, check_https, 'vault', empty_journal('new-journal')))
426 623         2653 };
427 623         5485 });
428 623         1885 return $c;
429             }
430              
431             1;
432             __END__