File Coverage

blib/lib/DBIx/Class/Schema/Config.pm
Criterion Covered Total %
statement 63 63 100.0
branch 30 30 100.0
condition n/a
subroutine 16 16 100.0
pod 3 5 60.0
total 112 114 98.2


line stmt bran cond sub pod time code
1             package DBIx::Class::Schema::Config;
2 10     10   791213 use 5.005;
  10         129  
3 10     10   53 use warnings;
  10         18  
  10         292  
4 10     10   62 use strict;
  10         15  
  10         304  
5 10     10   66 use base 'DBIx::Class::Schema';
  10         19  
  10         5889  
6 10     10   622223 use File::HomeDir;
  10         52251  
  10         655  
7 10     10   75 use Storable qw( dclone );
  10         20  
  10         428  
8 10     10   4258 use Hash::Merge qw( merge );
  10         47866  
  10         645  
9 10     10   82 use namespace::clean;
  10         23  
  10         91  
10              
11             our $VERSION = '0.001013'; # 0.1.13
12             $VERSION = eval $VERSION;
13              
14             sub connection {
15 15     15 1 612249 my ( $class, @info ) = @_;
16              
17 15 100       91 if ( ref($info[0]) eq 'CODE' ) {
18 1         5 return $class->next::method( @info );
19             }
20              
21 14         86 my $attrs = $class->_make_connect_attrs(@info);
22              
23             # We will not load credentials for someone who uses dbh_maker,
24             # however we will pass their request through.
25             return $class->next::method( $attrs )
26 14 100       67 if defined $attrs->{dbh_maker};
27              
28             # Take responsibility for passing through normal-looking
29             # credentials.
30             $attrs = $class->load_credentials($attrs)
31 13 100       110 unless $attrs->{dsn} =~ /dbi:/i;
32              
33 13         1501 return $class->next::method( $attrs );
34             }
35              
36             # Normalize arguments into a single hash. If we get a single hashref,
37             # return it.
38             # Check if $user and $pass are hashes to support things like
39             # ->connect( 'CONFIG_FILE', { hostname => 'db.foo.com' } );
40              
41             sub _make_connect_attrs {
42 34     34   24374 my ( $class, $dsn, $user, $pass, $dbi_attr, $extra_attr ) = @_;
43 34 100       185 return $dsn if ref $dsn eq 'HASH';
44              
45             return {
46             dsn => $dsn,
47 25 100       129 %{ref $user eq 'HASH' ? $user : { user => $user }},
48 25 100       98 %{ref $pass eq 'HASH' ? $pass : { password => $pass }},
49 25 100       133 %{$dbi_attr || {} },
50 25 100       58 %{ $extra_attr || {} }
  25         201  
51             };
52             }
53              
54             # Cache the loaded configuration.
55             sub config {
56 19     19 0 3346 my ( $class ) = @_;
57              
58 19 100       519 if ( ! $class->_config ) {
59 7         589 $class->_config( $class->_load_config );
60             }
61 19         109631 return dclone( $class->_config );
62             }
63              
64              
65             sub _load_config {
66 7     7   20 my ( $class ) = @_;
67 7         2249 require Config::Any; # Only loaded if we need to load credentials.
68              
69             # If we have ->config_files, we'll use those and load_files
70             # instead of the default load_stems.
71 7         39576 my %cf_opts = ( use_ext => 1 );
72 7 100       17 return @{$class->config_files}
  7         246  
73             ? Config::Any->load_files({ files => $class->config_files, %cf_opts })
74             : Config::Any->load_stems({ stems => $class->config_paths, %cf_opts });
75             }
76              
77              
78             sub load_credentials {
79 18     18 1 53 my ( $class, $connect_args ) = @_;
80              
81             # While ->connect is responsible for returning normal-looking
82             # credential information, we do it here as well so that it can be
83             # independently unit tested.
84 18 100       116 return $connect_args if $connect_args->{dsn} =~ /^dbi:/i;
85              
86 16         64 return $class->filter_loaded_credentials(
87             $class->_find_credentials( $connect_args, $class->config ),
88             $connect_args
89             );
90              
91             }
92              
93             # This will look through the data structure returned by Config::Any
94             # and return the first instance of the database credentials it can
95             # find.
96             sub _find_credentials {
97 16     16   1441 my ( $class, $connect_args, $ConfigAny ) = @_;
98              
99 16         77 for my $cfile ( @$ConfigAny ) {
100 16         64 for my $filename ( keys %$cfile ) {
101 16         32 for my $database ( keys %{$cfile->{$filename}} ) {
  16         59  
102 25 100       100 if ( $database eq $connect_args->{dsn} ) {
103 16         127 return $cfile->{$filename}->{$database};
104             }
105             }
106             }
107             }
108             }
109              
110             sub get_env_vars {
111 10 100   10 0 61 return $ENV{DBIX_CONFIG_DIR} . "/dbic" if exists $ENV{DBIX_CONFIG_DIR};
112 9         64 return ();
113             }
114              
115             # Intended to be sub-classed, the default behavior is to
116             # overwrite the loaded configuration with any specified
117             # configuration from the connect() call, with the exception
118             # of the DSN itself.
119              
120             sub filter_loaded_credentials {
121 14     14 1 36 my ( $class, $new, $old ) = @_;
122              
123 14 100       65 local $old->{password}, delete $old->{password} unless $old->{password};
124 14 100       55 local $old->{user}, delete $old->{user} unless $old->{user};
125 14         38 local $old->{dsn}, delete $old->{dsn};
126              
127 14         170 return merge( $old, $new );
128             };
129              
130             __PACKAGE__->mk_classaccessor('config_paths');
131             __PACKAGE__->mk_classaccessor('config_files');
132             __PACKAGE__->mk_classaccessor('_config');
133             __PACKAGE__->config_paths([( get_env_vars(), './dbic', File::HomeDir->my_home . '/.dbic', '/etc/dbic')]);
134             __PACKAGE__->config_files([ ] );
135              
136             1;
137              
138             =encoding UTF-8
139              
140             =head1 NAME
141              
142             DBIx::Class::Schema::Config - Credential Management for DBIx::Class
143              
144             =head1 DESCRIPTION
145              
146             DBIx::Class::Schema::Config is a subclass of DBIx::Class::Schema that allows
147             the loading of credentials & configuration from a file. The actual code itself
148             would only need to know about the name used in the configuration file. This
149             aims to make it simpler for operations teams to manage database credentials.
150              
151             A simple tutorial that compliments this documentation and explains converting
152             an existing DBIx::Class Schema to use this software to manage credentials can
153             be found at L
154              
155             =head1 SYNOPSIS
156              
157             /etc/dbic.yaml
158             MY_DATABASE:
159             dsn: "dbi:Pg:host=localhost;database=blog"
160             user: "TheDoctor"
161             password: "dnoPydoleM"
162             TraceLevel: 1
163              
164             package My::Schema
165             use warnings;
166             use strict;
167              
168             use base 'DBIx::Class::Schema::Config';
169             __PACKAGE__->load_namespaces;
170              
171             package My::Code;
172             use warnings;
173             use strict;
174             use My::Schema;
175              
176             my $schema = My::Schema->connect('MY_DATABASE');
177              
178             # arbitrary config access from anywhere in your $app
179             my $level = My::Schema->config->{TraceLevel};
180              
181             =head1 CONFIG FILES
182              
183             This module will load the files in the following order if they exist:
184              
185             =over 4
186              
187             =item * C<$ENV{DBIX_CONFIG_DIR}> . '/dbic',
188              
189             C<$ENV{DBIX_CONFIG_DIR}> can be configured at run-time, for instance:
190              
191             DBIX_CONFIG_DIR="/var/local/" ./my_program.pl
192              
193             =item * ./dbic.*
194              
195             =item * ~/.dbic.*
196              
197             =item * /etc/dbic.*
198              
199             =back
200              
201             The files should have an extension that L recognizes,
202             for example /etc/dbic.B.
203              
204             NOTE: The first available credential will be used. Therefore I
205             in ~/.dbic.yaml will only be looked at if it was not found in ./dbic.yaml.
206             If there are duplicates in one file (such that DATABASE is listed twice in
207             ~/.dbic.yaml,) the first configuration will be used.
208              
209             =head1 CHANGE CONFIG PATH
210              
211             Use C<__PACKAGE__-Econfig_paths([( '/file/stub', '/var/www/etc/dbic')]);>
212             to change the paths that are searched. For example:
213              
214             package My::Schema
215             use warnings;
216             use strict;
217              
218             use base 'DBIx::Class::Schema::Config';
219             __PACKAGE__->config_paths([( '/var/www/secret/dbic', '/opt/database' )]);
220              
221             The above code would have I and I
222             searched, in that order. As above, the first credentials found would be used.
223             This will replace the files originally searched for, not add to them.
224              
225             =head1 USE SPECIFIC CONFIG FILES
226              
227             If you would rather explicitly state the configuration files you
228             want loaded, you can use the class accessor C
229             instead.
230              
231             package My::Schema
232             use warnings;
233             use strict;
234              
235             use base 'DBIx::Class::Schema::Config';
236             __PACKAGE__->config_files([( '/var/www/secret/dbic.yaml', '/opt/database.yaml' )]);
237              
238             This will check the files, C,
239             and C in the same way as C,
240             however it will only check the specific files, instead of checking
241             for each extension that L supports. You MUST use the
242             extension that corresponds to the file type you are loading.
243             See L for information on supported file types and
244             extension mapping.
245              
246             =head1 ACCESSING THE CONFIG FILE
247              
248             The config file is stored via the C<__PACKAGE__-Econfig> accessor, which can be
249             called as both a class and instance method.
250              
251             =head1 OVERRIDING
252              
253             The API has been designed to be simple to override if you have additional
254             needs in loading DBIC configurations.
255              
256             =head2 Overriding Connection Configuration
257              
258             Simple cases where one wants to replace specific configuration tokens can be
259             given as extra parameters in the ->connect call.
260              
261             For example, suppose we have the database MY_DATABASE from above:
262              
263             MY_DATABASE:
264             dsn: "dbi:Pg:host=localhost;database=blog"
265             user: "TheDoctor"
266             password: "dnoPydoleM"
267             TraceLevel: 1
268              
269             If you’d like to replace the username with “Eccleston” and we’d like to turn
270             PrintError off.
271              
272             The following connect line would achieve this:
273              
274             $Schema->connect(“MY_DATABASE”, “Eccleston”, undef, { PrintError => 0 } );
275              
276             The name of the connection to load from the configuration file is still given
277             as the first argument, while other arguments may be given exactly as you would
278             for any other call to C.
279              
280             Historical Note: This class accepts numerous ways to connect to DBIC that would
281             otherwise not be valid. These connection methods are discouraged but tested for
282             and kept for compatibility with earlier versions. For valid ways of connecting to DBIC
283             please see L
284              
285             =head2 filter_loaded_credentials
286              
287             Override this function if you want to change the loaded credentials before
288             they are passed to DBIC. This is useful for use-cases that include decrypting
289             encrypted passwords or making programmatic changes to the configuration before
290             using it.
291              
292             sub filter_loaded_credentials {
293             my ( $class, $loaded_credentials, $connect_args ) = @_;
294             ...
295             return $loaded_credentials;
296             }
297              
298             C<$loaded_credentials> is the structure after it has been loaded from the
299             configuration file. In this case, C<$loaded_credentials-E{user}> eq
300             B and C<$loaded_credentials-E{dsn}> eq
301             B.
302              
303             C<$connect_args> is the structure originally passed on C<-Econnect()>
304             after it has been turned into a hash. For instance,
305             C<-Econnect('DATABASE', 'USERNAME')> will result in
306             C<$connect_args-E{dsn}> eq B and C<$connect_args-E{user}>
307             eq B.
308              
309             Additional parameters can be added by appending a hashref,
310             to the connection call, as an example, C<-Econnect( 'CONFIG',
311             { hostname =E "db.foo.com" } );> will give C<$connect_args> a
312             structure like C<{ dsn =E 'CONFIG', hostname =E "db.foo.com" }>.
313              
314             For instance, if you want to use hostnames when you make the
315             initial connection to DBIC and are using the configuration primarily
316             for usernames, passwords and other configuration data, you can create
317             a config like the following:
318              
319             DATABASE:
320             dsn: "DBI:mysql:database=students;host=%s;port=3306"
321             user: "WalterWhite"
322             password: "relykS"
323              
324             In your Schema class, you could include the following:
325              
326             package My::Schema
327             use warnings;
328             use strict;
329             use base 'DBIx::Class::Schema::Config';
330              
331             sub filter_loaded_credentials {
332             my ( $class, $loaded_credentials, $connect_args ) = @_;
333             if ( $loaded_credentials->{dsn} =~ /\%s/ ) {
334             $loaded_credentials->{dsn} = sprintf( $loaded_credentials->{dsn},
335             $connect_args->{hostname});
336             }
337             }
338              
339             __PACKAGE__->load_classes;
340             1;
341              
342             Then the connection could be done with
343             C<$Schema-Econnect('DATABASE', { hostname => 'my.hostname.com' });>
344              
345             See L for more complex changes that require changing
346             how the configuration itself is loaded.
347              
348             =head2 load_credentials
349              
350             Override this function to change the way that L
351             loads credentials. The function takes the class name, as well as a hashref.
352              
353             If you take the route of having C<-Econnect('DATABASE')> used as a key for
354             whatever configuration you are loading, I would be
355             C<$config-E{dsn}>
356              
357             Some::Schema->connect(
358             "SomeTarget",
359             "Yuri",
360             "Yawny",
361             {
362             TraceLevel => 1
363             }
364             );
365              
366             Would result in the following data structure as $config in
367             C:
368              
369             {
370             dsn => "SomeTarget",
371             user => "Yuri",
372             password => "Yawny",
373             TraceLevel => 1,
374             }
375              
376             Currently, load_credentials will NOT be called if the first argument to
377             C<-Econnect()> looks like a valid DSN. This is determined by match
378             the DSN with C.
379              
380             The function should return the same structure. For instance:
381              
382             package My::Schema
383             use warnings;
384             use strict;
385             use base 'DBIx::Class::Schema::Config';
386             use LWP::Simple;
387             use JSON
388              
389             # Load credentials from internal web server.
390             sub load_credentials {
391             my ( $class, $config ) = @_;
392              
393             return decode_json(
394             get( "http://someserver.com/v1.0/database?key=somesecret&db=" .
395             $config->{dsn} ));
396             }
397              
398             __PACKAGE__->load_classes;
399              
400             =head1 AUTHOR
401              
402             Kaitlyn Parkhurst (SymKat) Isymkat@symkat.comE> ( Blog: L )
403              
404             =head1 CONTRIBUTORS
405              
406             =over 4
407              
408             =item * Matt S. Trout (mst) Imst@shadowcat.co.ukE>
409              
410             =item * Peter Rabbitson (ribasushi) Iribasushi@cpan.orgE>
411              
412             =item * Christian Walde (Mihtaldu) Iwalde.christian@googlemail.comE>
413              
414             =item * Dagfinn Ilmari Mannsåker (ilmari) Iilmari@ilmari.orgE>
415              
416             =item * Matthew Phillips (mattp) Imattp@cpan.orgE>
417              
418             =back
419              
420             =head1 COPYRIGHT AND LICENSE
421              
422             This library is free software and may be distributed under the same terms
423             as perl itself.
424              
425             =head1 AVAILABILITY
426              
427             The latest version of this software is available at
428             L
429              
430             =cut