File Coverage

blib/lib/DBIx/Admin/DSNManager.pm
Criterion Covered Total %
statement 75 101 74.2
branch 21 36 58.3
condition 9 23 39.1
subroutine 12 14 85.7
pod 6 7 85.7
total 123 181 67.9


line stmt bran cond sub pod time code
1             package DBIx::Admin::DSNManager;
2              
3 2     2   119579 use strict;
  2         5  
  2         72  
4 2     2   12 use warnings;
  2         4  
  2         56  
5              
6 2     2   1858 use Config::Tiny;
  2         2001  
  2         56  
7              
8 2     2   1801 use File::Slurp; # For write_file.
  2         27467  
  2         137  
9              
10 2     2   1685 use Moo;
  2         55693  
  2         15  
11              
12             has active =>
13             (
14             is => 'rw',
15             default => sub{return 0},
16             required => 0,
17             );
18              
19             has attributes =>
20             (
21             is => 'rw',
22             default => sub{return {AutoCommit => 1, PrintError => 0, RaiseError => 1} },
23             required => 0,
24             );
25              
26             has config =>
27             (
28             is => 'rw',
29             default => sub{return undef},
30             required => 0,
31             );
32              
33             has file_name =>
34             (
35             is => 'rw',
36             default => sub{return ''},
37             required => 0,
38             );
39              
40             has password =>
41             (
42             is => 'rw',
43             default => sub{return ''},
44             required => 0,
45             );
46              
47             has use_for_testing =>
48             (
49             is => 'rw',
50             default => sub{return 0},
51             required => 0,
52             );
53              
54             has username =>
55             (
56             is => 'rw',
57             default => sub{return ''},
58             required => 0,
59             );
60              
61             has verbose =>
62             (
63             is => 'rw',
64             default => sub{return ''},
65             required => 0,
66             );
67              
68             our $VERSION = '2.01';
69              
70             # -----------------------------------------------
71              
72             sub BUILD
73             {
74 3     3 0 29 my($self) = @_;
75              
76 3 100 66     71 if ($self -> file_name && ! $self -> config)
77             {
78             # read() initializes config(), and validate() defaults to it.
79             # So we don't need to pass config() to validate(), but the
80             # latter returns a {}, which we need to save in config().
81              
82 2         12 $self -> read($self -> file_name);
83 2         29 $self -> config($self -> validate);
84             }
85              
86             } # End of BUILD.
87              
88             # -----------------------------------------------
89              
90             sub hashref2string
91             {
92 0     0 1 0 my($self, $h) = @_;
93 0   0     0 $h ||= {};
94              
95 0         0 return '{' . join(', ', map{"$_ => $$h{$_}"} sort keys %$h) . '}';
  0         0  
96              
97             } # End of hashref2string.
98              
99             # -----------------------------------------------
100              
101             sub _keys
102             {
103 5     5   7 my($self) = @_;
104              
105 5         13 return (qw/active attributes dsn password use_for_testing username/);
106              
107             } # End of _keys.
108              
109             # -----------------------------------------------
110              
111             sub _log
112             {
113 3     3   9 my($self, $s) = @_;
114 3   50     10 $s ||= '';
115              
116             # The leading hash fits in with 'diag' during testing.
117              
118 3 50       16 if ($self -> verbose)
119             {
120 3         128 print STDERR "# $s\n";
121             }
122              
123             } # End of _log.
124              
125             # -----------------------------------------------
126              
127             sub read
128             {
129 2     2 1 6 my($self, $file_name) = @_;
130              
131 2         12 $self -> _log("Reading: $file_name");
132              
133 2   50     20 my($config) = Config::Tiny -> read($file_name) || die "$Config::Tiny::errstr\n";
134 2         677 $config = {%$config};
135              
136             # For each DSN, we have to convert the attributes from a string to a hashref.
137              
138 2         21 for my $section (keys %$config)
139             {
140 4         18 $$config{$section}{attributes} = $self -> string2hashref($$config{$section}{attributes});
141             }
142              
143 2         10 $self -> config($config);
144              
145             } # End of read.
146              
147             # -----------------------------------------------
148              
149             sub report
150             {
151 0     0 1 0 my($self, $config) = @_;
152              
153 0 0       0 if (! $config)
154             {
155 0         0 $self -> _log("File: " . $self -> file_name);
156             }
157              
158 0   0     0 $config ||= $self -> config;
159              
160 0         0 my($attr);
161              
162 0         0 for my $section (sort keys %$config)
163             {
164 0         0 $self -> _log("Section: $section");
165              
166 0         0 for my $key ($self -> _keys)
167             {
168 0 0       0 next if (! exists $$config{$section}{$key});
169              
170 0 0       0 if ($key eq 'attributes')
171             {
172 0         0 $attr = $$config{$section}{$key};
173              
174 0         0 $self -> _log("$key: " . join(', ', map{"$_ => $$attr{$_}"} sort keys %$attr) );
  0         0  
175             }
176             else
177             {
178 0         0 $self -> _log("$key: $$config{$section}{$key}");
179             }
180             }
181              
182 0         0 $self -> _log;
183             }
184              
185             } # End of report.
186              
187             # -----------------------------------------------
188              
189             sub string2hashref
190             {
191 4     4 1 7 my($self, $s) = @_;
192 4   100     18 $s ||= '';
193 4         6 my($result) = {};
194              
195 4 100       11 if ($s)
196             {
197 1 50       6 if ($s =~ m/^\{\s*([^}]*)\}$/)
198             {
199 1         10 my(@attr) = map{s/([\"\'])(.*)\1/$2/; $_} map{split(/\s*=>\s*/)} split(/\s*,\s*/, $1);
  6         6  
  6         10  
  3         9  
200              
201 1 50       6 die "Invalid syntax for hashref: $s\n" if ( ( (scalar @attr) % 2) != 0);
202              
203 1 50       3 if (@attr)
204             {
205 1         20 $result = {@attr};
206             }
207             }
208             else
209             {
210 0         0 die "Invalid syntax for hashref: $s\n";
211             }
212             }
213              
214 4         14 return $result;
215              
216             } # End of string2hashref.
217              
218             # -----------------------------------------------
219              
220             sub validate
221             {
222 2     2 1 5 my($self, $config) = @_;
223 2   33     22 $config ||= $self -> config;
224              
225 2 50 33     14 if (! $config || (ref($config) ne 'HASH') )
226             {
227 0         0 die "You must use new(config => {...}) or new(file_name => 'name') or \$object -> config({...})\n";
228             }
229              
230 2         4 my($count) = 0;
231              
232 2         8 for my $section (sort keys %$config)
233             {
234 4         3 $count++;
235              
236 4         12 for my $key ($self -> _keys)
237             {
238             # The dns key is mandatory.
239              
240 24 100       66 if ($key eq 'dsn')
241             {
242 4 50       9 if ($$config{$section}{$key})
243             {
244 4         5 next;
245             }
246             else
247             {
248 0         0 die "Section $section has no value for the 'dsn' key\n";
249             }
250             }
251              
252 20 100       40 next if (! exists $$config{$section}{$key});
253              
254             # If not set, use the default.
255              
256 19 100       55 if (! $$config{$section}{$key})
257             {
258 3         11 $$config{$section}{$key} = $self -> $key;
259             }
260             }
261             }
262              
263 2 50       6 if ($count == 0)
264             {
265 0         0 die "No sections found\n";
266             }
267              
268 2         54 return $config;
269              
270             } # End of validate.
271              
272             # -----------------------------------------------
273              
274             sub write
275             {
276 1     1 1 1293 my($self, $file_name, $config) = @_;
277              
278             # Allow calls of the form $object -> write({...}).
279              
280 1 50       5 if (ref($file_name) eq 'HASH')
281             {
282 0         0 $config = $file_name;
283 0         0 $file_name = $self -> file_name;
284             }
285             else
286             {
287             # Allow calls of the form $object -> write($file_name) and write($file_name, {...}).
288              
289 1   33     13 $config ||= $self -> config;
290             }
291              
292 1         6 $self -> _log("Writing: $file_name");
293              
294 1         3 my(@line);
295             my($s);
296              
297 1         7 for my $section (sort keys %$config)
298             {
299 1         4 push @line, "[$section]";
300              
301 1         5 for my $key ($self -> _keys)
302             {
303 6 100       20 next if (! exists $$config{$section}{$key});
304              
305 4         9 $s = $$config{$section}{$key};
306              
307             # For each DSN, we have to convert the attributes from a hashref to a string.
308              
309 4 50       10 if ($key eq 'attributes')
310             {
311 0         0 $s = $self -> hashref2string($s);
312             }
313              
314 4         13 push @line, "$key = $s";
315             }
316              
317 1         4 push @line, '';
318             }
319              
320 1         3 write_file($file_name, map{"$_\n"} @line);
  6         20  
321              
322             } # End of write.
323              
324             # -----------------------------------------------
325              
326             1;
327              
328             =head1 NAME
329              
330             DBIx::Admin::DSNManager - Manage a file of DSNs, for both testing and production
331              
332             =head1 Synopsis
333              
334             #!/usr/bin/env perl
335              
336             use strict;
337             use warnings;
338              
339             use DBIx::Admin::DSNManager;
340              
341             us Try::Tiny;
342              
343             # --------------------------
344              
345             try
346             {
347             my($man1) = DBIx::Admin::DSNManager -> new
348             (
349             config => {'Pg.1' => {dsn => 'dbi:Pg:dbname=test', username => 'me', active => 1} },
350             verbose => 1,
351             );
352              
353             my($file_name) = '/tmp/dsn.ini';
354              
355             $man1 -> write($file_name);
356              
357             my($man2) = DBIx::Admin::DSNManager -> new
358             (
359             file_name => $file_name,
360             verbose => 1,
361             );
362              
363             $man2 -> report;
364             }
365             catch
366             {
367             print "DBIx::Admin::DSNManager died. Error: $_";
368             };
369              
370             See scripts/synopsis.pl.
371              
372             =head1 Description
373              
374             L manages a file of DSNs, for both testing and production.
375              
376             The INI-style format was selected, rather than, say, using an SQLite database, so that casual users could edit
377             the file without needing to know SQL and without having to install the command line program sqlite3.
378              
379             Each DSN is normally for something requiring manual preparation, such as creating the database named in the DSN.
380              
381             In the case of SQLite, etc, where manual intervention is not required, you can still put the DSN in
382             dsn.ini.
383              
384             One major use of this module is to avoid environment variable overload, since it is common to test Perl modules
385             by setting the env vars $DBI_DSN, $DBI_USER and $DBI_PASS.
386              
387             But then the problem becomes: What do you do when you want to run tests against a set of databases servers?
388             Some modules define sets of env vars, one set per database server, with awkward and hard-to-guess names.
389             This is messy and obscure.
390              
391             L is a solution to this problem.
392              
393             =head1 Database Creation
394              
395             By design, L does not provide a create-database option.
396              
397             For database servers like Postgres, MySQL, etc, you must create users, and give them the createdb privilege.
398             Such actions are outside the scope of this module.
399              
400             For database servers like SQLite, any code can create a database anyway, but you can use options in dsn.ini
401             to indicate the DSN is inactive, or not to be used for testing. See L below.
402              
403             =head1 Testing 'v' Production
404              
405             Of course, you may have DSNs in dsn.ini which you don't want to be used for testing.
406              
407             Here's a policy for handling such situations:
408              
409             =over 4
410              
411             =item o An explicit use_for_testing flag
412              
413             Each DSN in the file can be marked with the option 'use_for_testing = 0', to stop usage for testing,
414             or 'use_for_testing = 1', to allow usage for testing.
415              
416             The default is 0 - do not use for testing.
417              
418             =item o An implicit DSN
419              
420             For cases like SQLite, testing code can either look in dsn.ini, or manufacture a temporary directory and file name
421             for testing.
422              
423             This leads to a new question: If the testing code finds a DSN in dsn.ini which is marked use_for_testing = 0,
424             should that code still generate another DSN for testing? My suggestions is: Yes, since the one in dsn.ini does
425             not indicate that all possible DSNs should be blocked from testing.
426              
427             =back
428              
429             =head1 The Format of dsn.ini
430              
431             On disk, dsn.ini is a typical INI-style file. In RAM it is a hashref of config options. E.g.:
432              
433             config => {'Pg.1' => {dsn => 'dbi:Pg:dbname=test', ...}, 'Pg.2' => {...} }
434              
435             where config is the name of the module getter/setter which provides access to the hashref.
436              
437             =over 4
438              
439             =item o Sections
440              
441             Section names are unique, case-sensitive, strings.
442              
443             So 2 Postgres sections might be:
444              
445             [Pg.1]
446             ...
447              
448             [Pg.2]
449             ...
450              
451             =item o Connexion info within each section
452              
453             Each section can have these keys:
454              
455             =over 4
456              
457             =item o A DSN string
458              
459             A typical Postgres dsn would be:
460              
461             dsn = dbi:Pg:dbname=test
462              
463             A dsn key is mandatory within each section.
464              
465             The DSN names the driver to use and the database.
466              
467             =item o A Username string
468              
469             E.g.: username = testuser
470              
471             A username is optional.
472              
473             If a username is not provided for a dsn, the empty string is used.
474              
475             =item o A Password string
476              
477             E.g.: password = testpass
478              
479             A password is optional.
480              
481             If a password is not provided for a dsn, the empty string is used.
482              
483             =item o DSN Attributes as a hashref
484              
485             E.g.:
486              
487             attributes = {AutoCommit => 1, PrintError => 0, RaiseError = 1}
488              
489             Attributes are optional.
490              
491             Their format is exactly the same as for L.
492              
493             If attributes are not provided, they default to the example above.
494              
495             =item o A Boolean active flag
496              
497             E.g.: active = 0
498              
499             or active = 1
500              
501             The active key is optional.
502              
503             If the active key is not provided for a dsn, it defaults to 0 - do not use.
504              
505             This key means you can easily disable a DSN without having to delete the section, or comment it all out.
506              
507             =item o A Boolean testing flag
508              
509             E.g.: use_for_testing = 0
510              
511             or use_for_testing = 1
512              
513             The use_for_testing key is optional.
514              
515             If the use_for_testing key is not provided for a dsn, it defaults to 0 - do not use for testing.
516              
517             =back
518              
519             =back
520              
521             So, a sample dsn.ini file looks like:
522              
523             [Pg.1]
524             dsn=dbi:Pg:dbname=test1
525             username=user1
526             password=pass1
527             attributes = {AutoCommit => 1, PrintError => 0, RaiseError => 1}
528             use_for_testing = 0
529              
530             [Pg.2]
531             dsn=dbi:Pg:dbname=test2
532             username=user2
533             password=pass2
534             active = 0
535             use_for_testing = 1
536              
537             [SQLite.1]
538             dsn=dbi:SQLite:dbname=/tmp/test.module.sqlite
539              
540             This file is read by L. Check its docs for details, but there is one thing to be aware of:
541             L does not recognize comments at the ends of lines. So:
542              
543             key = value # A comment.
544              
545             sets key to 'value # A comment.', which is probably not what you intended.
546              
547             =head1 Constructor and Initialization
548              
549             Calling C returns a object of type L, or dies.
550              
551             C takes a hash of key/value pairs, some of which might be mandatory. Further, some combinations
552             might be mandatory.
553              
554             The keys are listed here in alphabetical order.
555              
556             They are lower-case because they are (also) method names, meaning they can be called to set or get the value
557             at any time.
558              
559             But a warning: In some cases, setting them after this module has used the previous value, will have no effect.
560             All such cases are documented (or should be).
561              
562             =over 4
563              
564             =item o config => {...}
565              
566             Specifies a hashref to use as the initial value of the internal config hashref which holds the set of DSNs.
567              
568             This hashref is keyed by section name, with each key pointing to a hashref of dsn data. E.g.:
569              
570             config => {'Pg.1' => {dsn => 'dbi:Pg:dbname=test', ...}, 'Pg.2' => {...} }
571              
572             Default: undef.
573              
574             =item o file_name => $string
575              
576             Specifies the name of the file holding the DSNs.
577              
578             If specified, the code reads this file and populates the hashref returned by C.
579              
580             This key is optional.
581              
582             Default: ''.
583              
584             =item o verbose => 0 | 1
585              
586             Specify more or less output.
587              
588             Default: 0.
589              
590             =back
591              
592             =head1 Methods
593              
594             =head2 config([{...}])
595              
596             Here, the [] indicate an optional parameter.
597              
598             Get or set the internal config hashref holding all the DSN data.
599              
600             If called as config({...}), set the config hashref to the parameter.
601              
602             If called as config(), return the config hashref.
603              
604             =head2 hashref2string($hashref)
605              
606             Returns a string corresponding to the $hashref.
607              
608             {} is converted to '{}'.
609              
610             =head2 read($file_name)
611              
612             Read $file_name using L and set the config hashref.
613              
614             =head2 report([{...}])
615              
616             Here, the [] indicate an optional parameter.
617              
618             If called as $object -> report, print both $object -> file_name, and the contents of the config hashref, to STDERR.
619              
620             If called as $object -> report({...}), print just the contents of the hashref, to STDERR.
621              
622             =head2 string2hashref($s)
623              
624             Returns a hashref built from the string.
625              
626             The string is expected to be something like '{AutoCommit => 1, PrintError => 0}'.
627              
628             The empty string is returned as {}.
629              
630             =head2 validate([{...}])
631              
632             Here, the [] indicate an optional parameter.
633              
634             Validate the given or config hashref.
635              
636             Returns the validated hashref.
637              
638             If a hashref is not supplied, validate the config one.
639              
640             Currently, the checks are:
641              
642             =over 4
643              
644             =item o There must be at least 1 section
645              
646             =item o All sections must have a 'dsn' key
647              
648             =back
649              
650             =head2 write([$file_name,][{...}])
651              
652             Here, the [] indicate an optional parameter.
653              
654             Write the given or config hashref to $file_name.
655              
656             The [] mean a parameter is optional.
657              
658             If called as $object -> write('dsn.ini'), write the config hashref to $file_name.
659              
660             If called as $object -> write('dsn.ini', {...}), write the given hashref to $file_name.
661              
662             If called as $object -> write({...}), write the given hashref to $object -> file_name.
663              
664             L is used to write this file, since these hashes are not of type C.
665              
666             =head2 See Also
667              
668             L.
669              
670             L.
671              
672             =head1 Version Numbers
673              
674             Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions.
675              
676             =head1 Support
677              
678             Log a bug on RT: L.
679              
680             =head1 Author
681              
682             L was written by Ron Savage Iron@savage.net.auE> in 2010.
683              
684             Home page: L.
685              
686             =head1 Copyright
687              
688             Australian copyright (c) 2010, Ron Savage.
689              
690             All Programs of mine are 'OSI Certified Open Source Software';
691             you can redistribute them and/or modify them under the terms of
692             The Artistic License, a copy of which is available at:
693             http://www.opensource.org/licenses/index.html
694              
695             =cut