File Coverage

blib/lib/DBIx/Class/Schema/Config.pm
Criterion Covered Total %
statement 81 81 100.0
branch 38 40 95.0
condition 4 5 80.0
subroutine 18 18 100.0
pod 3 6 50.0
total 144 150 96.0


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