File Coverage

blib/lib/DotCloud/Environment.pm
Criterion Covered Total %
statement 201 235 85.5
branch 91 136 66.9
condition 15 24 62.5
subroutine 32 38 84.2
pod 17 17 100.0
total 356 450 79.1


line stmt bran cond sub pod time code
1             package DotCloud::Environment;
2             {
3             $DotCloud::Environment::VERSION = '0.9.4';
4             }
5              
6             # ABSTRACT: easy handling of environment in dotcloud
7              
8 12     12   623707 use strict;
  12         27  
  12         446  
9 12     12   66 use warnings;
  12         25  
  12         324  
10 12     12   65 use Carp;
  12         24  
  12         843  
11 12     12   2732 use English qw( -no_match_vars );
  12         5295  
  12         121  
12 12     12   28587 use Storable qw< dclone >;
  12         48918  
  12         907  
13 12     12   12451 use Try::Tiny;
  12         26095  
  12         928  
14              
15 12         134 use Sub::Exporter -setup =>
16 12     12   10957 {exports => [qw< dotenv dotvars find_code_dir path_for >],};
  12         162042  
17              
18             our $main_file_path = '/home/dotcloud/environment.json';
19             our $main_dotcloud_code_dir = '/home/dotcloud/code';
20             my @application_keys = qw< environment project service_id service_name >;
21              
22             ### FUNCTIONAL INTERFACE ###
23              
24             {
25             my $default_instance;
26              
27             sub dotenv {
28 0   0 0 1 0 $default_instance ||= __PACKAGE__->new();
29 0         0 return $default_instance;
30             }
31             }
32              
33             sub dotvars {
34 0     0 1 0 my @params = @_;
35 0         0 my $wantarray = wantarray();
36 0         0 my @retval;
37             try {
38 0 0   0   0 if ($wantarray) {
39 0         0 @retval = dotenv()->service_vars(@params);
40             }
41             else {
42 0         0 $retval[0] = dotenv()->service_vars(@params);
43             }
44             }
45             catch {
46 0 0   0   0 if ($wantarray) {
47 0         0 @retval = dotenv()->subservice_vars(@params);
48             }
49             else {
50 0         0 $retval[0] = dotenv()->subservice_vars(@params);
51             }
52 0         0 };
53 0 0       0 return @retval if wantarray();
54 0         0 return $retval[0];
55             }
56              
57             sub find_code_dir {
58 6 50 33 6 1 12877 my %params = (@_ > 0 && ref $_[0]) ? %{$_[0]} : @_;
  0         0  
59 6         72 my $dir = _find_code_dir($params{n});
60 6 100 66     121 if (defined($dir) && $params{unix}) {
61 2         27 require File::Spec;
62 2         411 my $reldir = File::Spec->abs2rel($dir);
63 2         18 my @dirs = File::Spec->splitdir($reldir);
64 2         7 $dir = join '/', @dirs;
65             } ## end if (defined($dir) && $params...
66 6         80 return $dir;
67             } ## end sub find_code_dir
68              
69             sub path_for {
70 0     0 1 0 my @subs = @_;
71 0         0 (my $base = find_code_dir(unix => 1)) =~ s{/+\z}{}mxs;
72 0         0 return map {
73 0         0 $_ =~ s{\A /+}{}mxs;
74 0         0 $base . '/' . $_;
75             } @subs;
76             } ## end sub path_for
77              
78             ### OBJECT ORIENTED INTERFACE ###
79              
80             sub new {
81 43     43 1 80049 my $package = shift;
82 43 50 66     456 my %params = (@_ > 0 && ref $_[0]) ? %{$_[0]} : @_;
  0         0  
83 43         251 my $self = bless {
84             _params => \%params,
85             _envfor => {},
86             backtrack => 1, # backtrack by default
87             }, $package;
88 43 100       167 $self->{backtrack} = $params{backtrack} if exists $params{backtrack};
89 43 100       208 $self->load() unless $params{no_load};
90 40         252 return $self;
91             } ## end sub new
92              
93             sub _serialize_multiple {
94 2     2   5 my $self = shift;
95 2         4 my $serializer = shift;
96 2 50       16 my @applications = @_ > 0 ? @_ : $self->application_names();
97 2         8 my %retval =
98 2         5 map { $_ => $serializer->($self->_recompact($_)) } @applications;
99 2 50       14942 return %retval if wantarray();
100 2         64 return \%retval;
101             } ## end sub _serialize_multiple
102              
103             sub as_json {
104 1     1 1 6 my $self = shift;
105 1         6 require JSON;
106 1         5 return $self->_serialize_multiple(\&JSON::to_json, @_);
107             }
108              
109             sub as_yaml {
110 1     1 1 6 my $self = shift;
111 1         13 require YAML;
112 1         6 return $self->_serialize_multiple(\&YAML::Dump, @_);
113             }
114              
115             sub load {
116 42     42 1 126 my $self = shift;
117 42 100       153 defined(my $env = $self->_get_environment(@_))
118             or croak 'no suitable environment found';
119 38 100       2229 if ($env =~ /\A \s* {/mxs) {
120 27         141 $self->merge_json($env);
121             }
122             else {
123 11         46 $self->merge_yaml($env);
124             }
125 38         519 return $self;
126             } ## end sub load
127              
128             sub _recompact {
129 2     2   5 my ($self, $application) = @_;
130 2         9 my $hash = $self->application($application);
131 8         35 my %retval =
132 2         4 map { 'DOTCLOUD_' . uc($_) => $hash->{$_} } @application_keys;
133 2         6 while (my ($name, $service) = each %{$hash->{services}}) {
  6         25  
134 4         8 $name = uc($name);
135 4         16 while (my ($type, $vars) = each %$service) {
136 8         14 $type = uc($type);
137 8         29 while (my ($varname, $value) = each %$vars) {
138 32         67 my $key = join '_', 'DOTCLOUD', $name, $type, uc($varname);
139 32         164 $retval{$key} = $value;
140             }
141             }
142             } ## end while (my ($name, $service...
143 2         23 return \%retval;
144             } ## end sub _recompact
145              
146             sub _merge {
147 38     38   175246 my ($self, $hash) = @_;
148              
149 38         120 my %flag_for = map { $_ => 1 } @application_keys;
  152         546  
150              
151 38         150 my %data_for;
152 38         246 while (my ($name, $value) = each %$hash) {
153 760 50       3066 my ($key) = $name =~ m{\A DOTCLOUD_ (.*) }mxs
154             or next;
155 760         1194 $key = lc $key;
156 760 100       1645 if ($flag_for{$key}) {
157 152         641 $data_for{$key} = $value;
158             }
159             else {
160 608         908 my $rref = \$data_for{services};
161 608         3690 $rref = \$$rref->{$_} for split /_/, $key, 3;
162 608         2825 $$rref = $value;
163             } ## end else [ if ($flag_for{$key})
164             } ## end while (my ($name, $value)...
165              
166 38         174 $self->{_envfor}{$data_for{project}} = \%data_for;
167              
168 38         147 return $self;
169             } ## end sub _merge
170              
171             sub merge_json {
172 27     27 1 92 my ($self, $env) = @_;
173 27         210 require JSON;
174 27         153 return $self->_merge(JSON::from_json($env));
175             }
176              
177             sub merge_yaml {
178 11     11 1 23 my ($self, $env) = @_;
179 11         2196 require YAML;
180 11         22886 return $self->_merge(YAML::Load($env));
181             }
182              
183             sub _slurp {
184 19     19   37 my ($filename) = @_;
185 19 100   5   1368 open my $fh, '<:encoding(utf8)', $filename
  5         51  
  5         11  
  5         49  
186             or croak "open('$filename'): $OS_ERROR";
187 18         68284 local $/;
188 18         597 my $text = <$fh>;
189 18         1164 close $fh;
190 18         322 return $text;
191             } ## end sub _slurp
192              
193             sub _to_chars {
194 20     20   53 my ($string) = @_;
195 20 50       73 return $string if utf8::is_utf8($string);
196 20         6793 require Encode;
197 20         63140 return Encode::decode('utf8', $string);
198             } ## end sub _to_chars
199              
200             sub _get_environment {
201 42     42   67 my $self = shift;
202 42 50 66     226 my %params = (@_ > 0 && ref $_[0]) ? %{$_[0]} : @_;
  0         0  
203 42 100       121 return _to_chars($params{environment_string})
204             if exists $params{environment_string};
205 40 100       107 return _slurp($params{environment_file})
206             if exists $params{environment_file};
207 38 100       207 return _to_chars($self->{_params}{environment_string})
208             if exists $self->{_params}{environment_string};
209 24 100       93 return _slurp($self->{_params}{environment_file})
210             if exists $self->{_params}{environment_file};
211 22 100       98 return _slurp($ENV{DOTCLOUD_ENVIRONMENT_FILE})
212             if exists $ENV{DOTCLOUD_ENVIRONMENT_FILE};
213 18 100       202 return _slurp($main_file_path)
214             if -e $main_file_path;
215 16 100       52 return _to_chars($params{fallback_string})
216             if exists $params{fallback_string};
217 14 100       37 return _slurp($params{fallback_file})
218             if exists $params{fallback_file};
219 12 100       46 return _to_chars($self->{_params}{fallback_string})
220             if exists $self->{_params}{fallback_string};
221 10 100       35 return _slurp($self->{_params}{fallback_file})
222             if exists $self->{_params}{fallback_file};
223              
224 7 100 66     141 return unless $params{backtrack} || $self->{backtrack};
225              
226             # We will backtrack from three starting points:
227             # * the "root" directory for the application, i.e
228             # what in dotCloud is /home/dotcloud/code
229             # * the current working directory
230             # * the directory containing the file that called us
231 4         17 my $code_dir = find_code_dir(n => 1);
232              
233 4         62 require Cwd;
234 4         25 require File::Basename;
235 4         20 require File::Spec;
236 4         17403 for my $path ($code_dir, Cwd::cwd(),
237             File::Basename::dirname((caller())[1]))
238             {
239 4         1119 my ($volume, $directories) = File::Spec->splitpath($path, 'no-file');
240 4         68 my @directories = File::Spec->splitdir($directories);
241 4         24 while (@directories) {
242 4         65 my $directories = File::Spec->catdir(@directories);
243 4         14 for my $format (qw< json yaml >) {
244 4         69 my $path = File::Spec->catpath($volume, $directories,
245             "environment.$format");
246 4 50       194 return _slurp($path) if -e $path;
247             }
248 0         0 pop @directories;
249             } ## end while (@directories)
250             } ## end for my $path ($code_dir...
251              
252 0         0 return;
253             } ## end sub _get_environment
254              
255             sub _find_code_dir {
256 6 50   6   67 return $main_dotcloud_code_dir if -d $main_dotcloud_code_dir;
257              
258 6   100     75 my $n = shift || 0;
259 6         80 require Cwd;
260 6         35 require File::Basename;
261 6         35 require File::Spec;
262 6         29268 for my $path (Cwd::cwd(), File::Basename::dirname((caller($n))[1])) {
263 6 50       2096 my $abspath =
264             File::Spec->file_name_is_absolute($path)
265             ? $path
266             : File::Spec->rel2abs($path);
267 6         309 my ($volume, $directories) =
268             File::Spec->splitpath($abspath, 'no-file');
269 6         203 my @directories = File::Spec->splitdir($directories);
270 6         43 while (@directories) {
271 11         207 my $directories = File::Spec->catdir(@directories);
272 11         158 my $filepath =
273             File::Spec->catpath($volume, $directories, 'dotcloud.yml');
274 11 100       615 return File::Spec->catpath($volume, $directories, '')
275             if -e $filepath;
276 5         16 pop @directories;
277             } ## end while (@directories)
278             } ## end for my $path (Cwd::cwd(...
279             } ## end sub _find_code_dir
280              
281             sub _dclone {
282 83 100   83   5226 return dclone(ref $_[0] ? $_[0] : {@_});
283             }
284              
285             sub _dclone_return {
286 83     83   213 my $retval = _dclone(@_);
287 83 100       501 return $retval unless wantarray();
288 14         198 return %$retval;
289             }
290              
291             sub application_names {
292 37     37 1 18992 my $self = shift;
293 37         62 my @names = keys %{$self->{_envfor}};
  37         191  
294 37 100       234 return @names if wantarray();
295 27         225 return \@names;
296             } ## end sub application_names
297              
298             sub applications {
299 27     27 1 60 my $self = shift;
300 27         93 return _dclone_return($self->{_envfor});
301             }
302              
303             sub application {
304 39     39 1 64049 my $self = shift;
305 39         79 my $application = shift;
306 39 50       120 $self->{_envfor}{$application} = _dclone(@_) if @_;
307 39 100       158 croak "no application '$application'"
308             unless exists $self->{_envfor}{$application};
309 38         123 _dclone_return($self->{_envfor}{$application});
310             } ## end sub application
311              
312             sub _service {
313 0     0   0 my ($self, $application, $service);
314 0 0       0 return unless exists $self->{_envfor}{$application};
315 0         0 my $services = $self->{_envfor}{$application}{services};
316 0 0       0 return unless exists $services->{$service};
317 0         0 return $services->{$service};
318             } ## end sub _service
319              
320             sub service {
321 5     5 1 7 my $self = shift;
322 0         0 my %params = @_ > 1 ? @_
323             : @_ == 0 ? ()
324 5 0       17 : ref($_[0]) ? %{$_[0]}
    0          
    50          
325             : (service => $_[0]);
326              
327 5         6 my $service = $params{service};
328              
329 5         10 my @found_services;
330 5 50       34 my @applications =
    100          
331             $service =~ s{\A (.*) \.}{}mxs ? $1
332             : exists $params{application} ? $params{application}
333             : $self->application_names();
334 5         10 for my $candidate (@applications) {
335 5         14 my $services =
336             $self->application($candidate)->{services}; # this croaks
337 5 100       44 push @found_services, $services->{$service}
338             if exists $services->{$service};
339             } ## end for my $candidate (@applications)
340              
341 5 100       39 croak "cannot find requested service"
342             if @found_services == 0;
343 4 50       11 croak "ambiguous request for service '$service', there are many"
344             if @found_services > 1;
345              
346 4         9 _dclone_return(@found_services);
347             } ## end sub service
348              
349             sub subservice {
350 5     5 1 8 my $self = shift;
351 0         0 my %params = @_ > 1 ? @_
352             : @_ == 0 ? ()
353 5 0       17 : ref($_[0]) ? %{$_[0]}
    0          
    50          
354             : (subservice => $_[0]);
355              
356 5         15 my ($subservice, $service, $application) = reverse split /\./, $params{subservice};
357 5 50       26 my @applications = defined $application ? $application
    50          
358             : exists $params{application} ? $params{application}
359             : $self->application_names();
360              
361 5         7 my @founds;
362 5         11 for my $candidate (@applications) {
363 5         10 my $services =
364             $self->application($candidate)->{services}; # this croaks
365 5 100       26 my @services_to_test = defined $service ? $service : keys %$services;
366 5         9 for my $service (@services_to_test) {
367 8 50       17 next unless exists $services->{$service};
368 8 100       28 next unless exists $services->{$service}{$subservice};
369 4         34 push @founds, $services->{$service}{$subservice};
370             }
371             } ## end for my $candidate (@applications)
372              
373 5 100       49 croak "cannot find requested subservice '$subservice'"
374             if @founds == 0;
375 4 50       13 croak "ambiguous request for subservice '$subservice', there are many"
376             if @founds > 1;
377              
378 4         9 _dclone_return(@founds);
379             }
380              
381             sub _subservice_vars {
382 8     8   14 my ($subservice, $list) = @_;
383 8 100       23 return _dclone_return($subservice) unless defined $list;
384 2         5 my @values = @{$subservice}{@$list};
  2         9  
385 2 50       70 return @values if wantarray();
386 0         0 return \@values;
387             }
388              
389             sub subservice_vars {
390 5     5 1 25 my $self = shift;
391 0         0 my %params = @_ > 1 ? @_
392             : @_ == 0 ? ()
393 5 50       35 : ref($_[0]) ? %{$_[0]}
    50          
    100          
394             : (subservice => $_[0]);
395 5         18 my $subservice = $self->subservice(%params);
396 4   100     23 return _subservice_vars($subservice, $params{list} // undef);
397             } ## end sub service_vars
398              
399             sub service_vars {
400 5     5 1 24 my $self = shift;
401 0         0 my %params = @_ > 1 ? @_
402             : @_ == 0 ? ()
403 5 50       28 : ref($_[0]) ? %{$_[0]}
    50          
    100          
404             : (service => $_[0]);
405 5         20 my %service = _dclone_return($self->service(%params));
406 4         19 my @real_subs = grep { ref($service{$_}) } keys %service;
  8         19  
407 4 50       16 croak "no subservices" if @real_subs == 0;
408 4 50       12 @real_subs = grep { $_ ne 'ssh' } @real_subs
  8         21  
409             if @real_subs > 1;
410 4 50       12 croak "too many subservices" if @real_subs > 1;
411 4         5 my $subservice = $service{$real_subs[0]};
412 4   100     22 return _subservice_vars($subservice, $params{list} // undef);
413             }
414              
415             1;
416              
417              
418             =pod
419              
420             =head1 NAME
421              
422             DotCloud::Environment - easy handling of environment in dotcloud
423              
424             =head1 VERSION
425              
426             version 0.9.4
427              
428             =head1 SYNOPSIS
429              
430             # Most typical usage, suppose you have a shared 'lib' directory
431             # under the root of your dotCloud directory hierarchy
432             use DotCloud::Environment 'path_for';
433             use lib path_for('lib');
434             use My::Shared::Module; # in your project-root/lib directory
435              
436             # Most typical usage when you set a default environment.json file
437             # in the root of your project and you need to access the variables
438             # of the 'redis' service
439             use DotCloud::Environment 'dotvars';
440             my $redis_vars = dotvars('redis');
441              
442             # Not-very-typical usage examples from now on!
443              
444             # get an object, fallback to $path if not in dotCloud deploy
445             my $dcenv = DotCloud::Environment->new(fallback_file => $path);
446              
447             # you should now which services make part of your stack!
448             my $nosqldb_conf = $dcenv->service('nosqldb');
449             my $type = $nosqldb_conf->{type}; # e.g. mysql, redis, etc.
450             my $vars = $nosqldb_conf->{vars}; # e.g. login, password, host...
451              
452             # suppose your nosqldb service is redis...
453             require Redis;
454             my $redis = Redis->new(server => "$vars->{host}:$vars->{port}");
455             $redis->auth($vars->{password});
456              
457             # another service, similar approach
458             my $conf = $dcenv->service('database');
459             die 'not MySQL?!?' unless $conf->{type} eq 'mysql';
460              
461             my ($host, $port, $user, $pass)
462             = @{$conf->{vars}}{qw< host port login password >}
463             require DBI;
464             my $dbh = DBI->connect("dbi:mysql:host=$host;port=$port;database=db",
465             $user, $pass, {RaiseError => 1});
466              
467             =head1 DESCRIPTION
468              
469             L is useful when you design applications to be
470             deployed in the dotCloud platform. It is assumed that you know what
471             dotCloud is (anyway, see L).
472              
473             In general you will have multiple services in your application, and when
474             you are in one instance inside dotCloud you can access the configuration
475             of the relevant ones reading either F
476             or F. For example, this lets your
477             frontend or backend applications know where the data services are, e.g.
478             a Redis database or a MySQL one.
479              
480             This modules serves to two main goals:
481              
482             =over
483              
484             =item *
485              
486             it reads either file to load the configuration of each service, so that
487             you can access this configuration easily as a hash of hashes;
488              
489             =item *
490              
491             it lets you abstract from the assumption that you're actually in a
492             dotCloud instance, allowing you to use the same interface also in your
493             development environment.
494              
495             =back
496              
497             With respect to the second goal, it should be observed that
498             most of the times in your development environment you don't have the
499             same exact situation as in dotCloud, e.g. it's improbable that you have
500             a F directory around. With this module you can set a
501             fallback to be used in different ways, e.g.:
502              
503             =over
504              
505             =item *
506              
507             providing a fallback file path to be loaded
508             if C is not found;
509              
510             =item *
511              
512             setting up the C environment variable to point
513             to the file to be used.
514              
515             B: as of version 0.9.1 this variable substitutes C,
516             which DotCloud started using for its own purposes.
517              
518             =back
519              
520             =head2 A Note On Available Data
521              
522             Data about DotCloud services is organized according to the structure of the
523             variables set in the relevant files. There are four significant parts:
524              
525             =over
526              
527             =item B<< application >>
528              
529             there can be multiple applications you're loading variables from, and
530             DotCloud::Environment lets you distinguish them apart
531              
532             =item B<< service >>
533              
534             this is the name of a service in DotCloud sense. For example, if you have
535             application whatever like this:
536              
537             $ dotcloud list whatever
538             whatever (flavor: legacy):
539             - nosqldb (type: redis; instances: 1)
540             - sqldb (type: mysql; instances: 1)
541             - www (type: perl; instances: 1)
542             - backend (type: perl-worker; instances: 1)
543              
544             you have four services defined: C, C, C and C
545              
546             =item B<< subservice >>
547              
548             this represents a subgroup of variables in a service. You should always find
549             two subservices: one is named C, the other one has the same name as the
550             service type (e.g. C, C,...).
551              
552             It makes sense to consider C some kind of accessory information and the
553             other subservice as the "real" service.
554              
555             =item B<< variable name >>
556              
557             this is the name of the variable, which is associated to a subservice.
558              
559             =back
560              
561             Values are assigned to variable names.
562              
563             =head2 Suggested/Typical Usage
564              
565             In order to keep your code clean, you will probably be dividing it
566             depending on the functional block that will be deployed as a service
567             in dotCloud. Suppose that you have a frontend service, a backend service
568             and a database; you probably have the following directory layout:
569              
570             project
571             +- dotcloud.yml
572             +- backend
573             | | ...
574             | +- lib
575             | +- Backend.pm
576             +- frontend
577             | | ...
578             | +- lib
579             | +- FrontEnd.pm
580             +- lib
581             +- Shared.pm
582              
583             Each service is put into a separate directory and all the code
584             that they both use (e.g. functions to connect to databases) is put in
585             a common C directory.
586              
587             How should you use DotCloud::Environment?
588              
589             The main goal is to let it find the right C (or,
590             equivalently, C) depending on the environment you
591             are into. If you are in dotCloud there is actually no problem, because
592             by default the I C file is
593             selected; for your local development the best thing to do is to put
594             the configuration file in the project's root directory, which becomes
595             like this:
596              
597             project
598             +- dotcloud.yml
599             +- backend
600             | | ...
601             | +- lib
602             | +- Backend.pm
603             +- frontend
604             | | ...
605             | +- lib
606             | +- FrontEnd.pm
607             +- lib
608             | +- Shared.pm
609             |
610             +- environment.json
611              
612             Putting the file in that position lets DotCloud::Environment find
613             it by default when no C file (or
614             the equivalent YAML file) is found in the system. Which hopefully is
615             the case of your development environment.
616              
617             In this case, you would have this in each service:
618              
619             # -- in BackEnd.pm and FrontEnd.pm --
620             use DotCloud::Environment 'path_for';
621             use lib path_for('lib');
622             use Shared ...;
623              
624             The function L helps you to set up the right path in
625             C<@INC> so that the module can find the shared code.
626              
627             In the shared module you can do this:
628              
629             # -- in Shared.pm --
630             use DotCloud::Environment 'dotenv';
631              
632             # ... when you need it...
633             my $service = dotenv()->service('service-name');
634             # ... now you have a hash ref which should have at least two
635             # elements: ssh and the real subservice type, e.g. mysql, redis, ...
636             my $redis_host = $service->{redis}{host};
637              
638             Most of the time all you need is to access the variables related
639             to a specific service, so there's a shortcut for this:
640              
641             use DotCloud::Environment 'dotvars';
642             my %vars = dotvars('service-name');
643              
644             The C shortcut tries its best to DWIM, i.e. it lets you specify
645             either the name of a service or the name of a subservice.
646              
647             For example, suppose that you want to implement a function to
648             connect to a Redis service called C:
649              
650             sub get_redis {
651             my %vars = dotvars('redisdb');
652             # it could also be:
653             #
654             # my %vars = dotvars('redis'); # name of service type
655             #
656             # if there is only one service of type redis
657              
658             require Redis;
659             my $redis = Redis->new(server => "$vars{host}:$vars{port}");
660             $redis->auth($vars{password});
661             return $redis;
662             }
663              
664             Of course you can use C/C directly in C and
665             C, but you will probably benefit from refactoring your
666             common code to avoid duplications.
667              
668             =head1 METHODS
669              
670             =head2 new
671              
672             $dcenv = DotCloud::Environment->new(%params);
673             $dcenv = DotCloud::Environment->new({%params});
674              
675             Create a new object. Parameters are:
676              
677             =over
678              
679             =item B<< no_load >>
680              
681             don't attempt to load the configuration
682              
683             =item B<< environment_string >>
684              
685             unconditionally use the provided string, ignoring everything else;
686              
687             =item B<< environment_file >>
688              
689             unconditionally use the provided file, ignoring everything else;
690              
691             =item B<< fallback_string >>
692              
693             use the provided string if other methods fail;
694              
695             =item B<< fallback_file >>
696              
697             use the provided file if other methods fail.
698              
699             =item B<< backtrack >>
700              
701             if nothing works and no fallback is set, look for suitable files
702             in filesystem. This option is activated by default, so you can use
703             it to I it (e.g. with C<< backtrack => 0 >>).
704              
705             =back
706              
707             Unless C is passed and set to true, the object creation also
708             calls the L method.
709              
710             Returns the new object or Cs if errors occur.
711              
712             =head2 load
713              
714             $dcenv->load(%params);
715             $dcenv->load({%params});
716              
717             Load the configuration for an application. The accepted parameters are
718             C, C, C,
719             C and C with the same meaning as in the
720             constructor (see L).
721              
722             The sequence to get the configuration string is the following:
723              
724             =over
725              
726             =item B<< environment_string >>
727              
728             from parameter passed to the method
729              
730             =item B<< environment_file >>
731              
732             from parameter passed to the method
733              
734             =item B<< environment_string >>
735              
736             from parameter set in the constructor
737              
738             =item B<< environment_file >>
739              
740             from parameter set in the constructor
741              
742             =item B<< DOTCLOUD_ENVIRONMENT_FILE >>
743              
744             environment variable (i.e. C<$ENV{DOTCLOUD_ENVIRONMENT_FILE}>). Note
745             that this was formerly C<$ENV{DOTCLOUD_ENVIRONMENT}> but due to
746             DotCloud starting using this variable it is no longer available.
747              
748             =item B<< C<$DotCloud::Environment::main_file_path> >>
749              
750             which defaults to F (you SHOULD
751             NOT change this variable unless you really know what you're doing)
752              
753             =item B<< fallback_string >>
754              
755             from parameter passed to the method
756              
757             =item B<< fallback_file >>
758              
759             from parameter passed to the method
760              
761             =item B<< fallback_string >>
762              
763             from parameter set in the constructor
764              
765             =item B<< fallback_file >>
766              
767             from parameter set in the constructor
768              
769             =back
770              
771             If none of the above works there's still some hope in case there is
772             option C (or it was specified to the constructor). In this
773             case, either file is searched recursively starting from the
774             following directories:
775              
776             =over
777              
778             =item *
779              
780             the one returned by L (but as if it were called by
781             the caller of L, i.e. with a value of C equal to 1)
782              
783             =item *
784              
785             the current working directory
786              
787             =item *
788              
789             the directory of the file that called us.
790              
791             =back
792              
793             Actually, option C is enabled by default, so if you
794             B want the behaviour above you have to explicitly disable
795             it (e.g. passing C<< backtrack => 0 >> in the constructor).
796              
797             It is possible to load multiple configuration files from
798             multiple applications.
799              
800             Returns a reference to the object itself.
801              
802             =head2 as_json
803              
804             %json_for = $dcenv->as_json();
805             $json_for = $dcenv->as_json();
806              
807             Rebuild the JSON representations of all the
808             applications.
809              
810             Returns a hash (in list context) or an anonymous hash (in scalar
811             context) with each application name pointing to the relevant
812             JSON string.
813              
814             =head2 as_yaml
815              
816             %yaml_for = $dcenv->as_yaml();
817             $yaml_for = $dcenv->as_yaml();
818              
819             Rebuild the YAML representations of all the
820             applications.
821              
822             Returns a hash (in list context) or an anonymous hash (in scalar
823             context) with each application name pointing to the relevant
824             YAML string.
825              
826             =head2 merge_json
827              
828             $dcenv->merge_json($json_string);
829              
830             Add (or replace) the configuration of an application, provided as
831             JSON string. You should not need to do this explicitly, because
832             this does the same for you with autodetection of the format:
833              
834             $dcenv->load(environment_string => $json_or_yaml_string);
835              
836             Return a reference to the object itself.
837              
838             =head2 merge_yaml
839              
840             $dcenv->merge_yaml($yaml_string);
841              
842             Add (or replace) the configuration of an application, provided as
843             YAML string. You should not need to do this explicitly, because
844             this does the same for you with autodetection of the format:
845              
846             $dcenv->load(environment_string => $json_or_yaml_string);
847              
848             =head2 application_names
849              
850             my @names = $dcenv->application_names();
851              
852             Returns the names of the applications loaded. Generally only one
853             application will be available, i.e. the one of the stack you're
854             working with.
855              
856             =head2 applications
857              
858             my %conf_for = $dcenv->applications();
859             my $conf_for = $dcenv->applications();
860              
861             Get a hash (in list context) or anonymous hash (in scalar context)
862             with the relevant data of all the applications. Example:
863              
864             {
865             app1 => {
866             project => 'app1',
867             environment => 'default',
868             service_id => 0,
869             service_name => 'www',
870             services => {
871             nosqldb => {
872             redis => {
873             login => 'redis',
874             password => 'wafadsfsdfdsfdas',
875             host => 'data.app1.dotcloud.com',
876             port => '12345',
877             }
878             }
879             sqldb => {
880             mysql => {
881             login => 'mysql',
882             password => 'wafadsfsdfdsfdas',
883             host => 'data.app1.dotcloud.com',
884             port => '54321',
885             }
886             }
887             }
888             },
889             app2 => {
890             # ...
891             }
892             }
893              
894             =head2 application
895              
896             my %conf_for = $dcenv->application($appname);
897             my $conf_for = $dcenv->application($appname);
898              
899             Get a hash (in list context) or anonymous hash (in scalar context)
900             with the relevant data for the requested application. Example:
901              
902             {
903             project => 'app1',
904             environment => 'default',
905             service_id => 0,
906             service_name => 'www',
907             services => {
908             nosqldb => {
909             redis => {
910             login => 'redis',
911             password => 'wafadsfsdfdsfdas',
912             host => 'data.app1.dotcloud.com',
913             port => '12345',
914             }
915             }
916             sqldb => {
917             mysql => {
918             login => 'mysql',
919             password => 'wafadsfsdfdsfdas',
920             host => 'data.app1.dotcloud.com',
921             port => '54321',
922             }
923             }
924             }
925             }
926              
927             =head2 service
928              
929             my %conf_for = $dcenv->service(%params); # also with \%params
930             my $conf_for = $dcenv->service(%params); # also with \%params
931              
932             Get a hash (in list context) or anonymous hash (in scalar context)
933             with the relevant data for the requested service. Example:
934              
935             {
936             ssh => {
937             host => 'data.app1.dotcloud.com',
938             port => '12345',
939             url => 'ssh://data.app1.dotcloud.com:12345/',
940             },
941             redis => {
942             login => 'redis',
943             password => 'wafadsfsdfdsfdas',
944             host => 'data.app1.dotcloud.com',
945             port => '12345',
946             }
947             }
948              
949             The parameters are the following:
950              
951             =over
952              
953             =item B<< service >>
954              
955             (B) the name of the service.
956              
957             =item B<< application >>
958              
959             (B) the name of the application.
960              
961             =back
962              
963             The name of the application is optional because in most cases it can be
964             omitted, e.g. because there is only one application. The name can be also
965             provided in the service name, in line with what normally happens in dotCloud
966             where the complete name of a service is something like C.
967              
968             This is the algorithm:
969              
970             =over
971              
972             =item *
973              
974             if the name of the service is of the form C, the
975             name is split into the two components;
976              
977             =item *
978              
979             otherwise, if the application parameter is present it is used
980              
981             =item *
982              
983             otherwise the service is searched among all the services of all the
984             applications.
985              
986             =back
987              
988             If exactly one service is found it is returned, otherwise this method
989             Cs.
990              
991             =head2 subservice
992              
993             my %conf_for = $dcenv->subservice($subservice_name);
994             my %conf_for = $dcenv->subservice(%params); # also with \%params
995             my $conf_for = $dcenv->subservice(%params); # also with \%params
996              
997             Get a hash (in list context) or anonymous hash (in scalar context)
998             with the relevant data for the requested subservice. Example:
999              
1000             redis => {
1001             login => 'redis',
1002             password => 'wafadsfsdfdsfdas',
1003             host => 'data.app1.dotcloud.com',
1004             port => '12345',
1005             }
1006              
1007             It can be called with a single non-reference scalar that represents
1008             the subservice to look for. Otherwise it accepts the following parameters
1009             in a hash or a reference to a hash:
1010              
1011             =over
1012              
1013             =item B<< subservice >>
1014              
1015             the name of the subservice
1016              
1017             =item B<< service >>
1018              
1019             the name of the service, see L
1020              
1021             =item B<< application >>
1022              
1023             the name of the application, see L
1024              
1025             =back
1026              
1027             with obvious meanings.
1028              
1029             The application and the service name can also be specified in the
1030             subservice name with separating dots like in the following examples:
1031              
1032             application.service.subservice
1033             service.subservice
1034              
1035             These configurations in the subservice name override parameters of
1036             the same name (e.g. specifying C overrides the
1037             C<$params{service}> input parameters).
1038              
1039             Croaks if more than one subservice with the given name is found.
1040              
1041             =head2 subservice_vars
1042              
1043             my %vars = $dcenv->subservice_vars('subservice-name');
1044             my $vars = $dcenv->subservice_vars('subservice-name');
1045             my %vars = $dcenv->subservice_vars(%params); # also \%params
1046             my $vars = $dcenv->subservice_vars(%params); # also \%params
1047             my @values = $dcenv->subservice_vars(%params); # also \%params
1048             my $values = $dcenv->subservice_vars(%params); # also \%params
1049              
1050             Shorthand to get the configuration variables of a single
1051             subservice.
1052              
1053             The input parameter list can be a single string with the name of
1054             the subservice, or a hash/anonymous hash with parameters.
1055             Depending on the input, the return value might be structured like
1056             a hash or like an array:
1057              
1058             =over
1059              
1060             =item B<< subservice >>
1061              
1062             the name of the subservice, see L
1063              
1064             =item B<< service >>
1065              
1066             the name of the service, see L
1067              
1068             =item B<< application >>
1069              
1070             the name of the application, see L
1071              
1072             =item B<< list >>
1073              
1074             (B) if a list is provided, then the values corresponding to each
1075             item in order is returned. This allows writing things like this:
1076              
1077             my ($host, $port, $password) = $dcenv->service_list(
1078             service => 'nosqldb',
1079             list => [ qw< host port password > ],
1080             );
1081              
1082             and get directly the values to put into variables. In this case, the return
1083             value can be a list of values or an anonymous array with the values.
1084              
1085             If this parameter is not present, the whole name/value hash is returned, either
1086             as a list or as an anonymous hash depending on the context.
1087              
1088             =back
1089              
1090             =head2 service_vars
1091              
1092             my %vars = $dcenv->service_vars('service-name');
1093             my $vars = $dcenv->service_vars('service-name');
1094             my %vars = $dcenv->service_vars(%params); # also \%params
1095             my $vars = $dcenv->service_vars(%params); # also \%params
1096             my @values = $dcenv->service_vars(%params); # also \%params
1097             my $values = $dcenv->service_vars(%params); # also \%params
1098              
1099             Shorthand to get the configuration variables of a single
1100             service. This assumes that a I
subservice can be found
1101             in the requested service, according to the following algorithm:
1102              
1103             =over
1104              
1105             =item *
1106              
1107             first of all, a service is found with L
1108              
1109             =item *
1110              
1111             the I service C is ignored
1112              
1113             =item *
1114              
1115             if only one subservice remains in the service, it is assumed to be
1116             the I
subservice.
1117              
1118             =back
1119              
1120             After this, the method behaves as if L with the
1121             I
subservice were called.
1122              
1123             =head1 FUNCTIONS
1124              
1125             Nothing is exported by default, but you can import the following
1126             functions. If you need both, you can use the C<:all> tag, e.g.:
1127              
1128             use DotCloud::Environment ':all';
1129              
1130             This module uses L under the hood; this means that
1131             if you're not happy with the name of the imported subroutines you
1132             can provide your own names, e.g.:
1133              
1134             use DotCloud::Environment
1135             dotvars => { -as => 'dotcloud_variables_for' };
1136             my $vars = dotcloud_variables_for('my-service');
1137              
1138             =head2 B<< dotenv >>
1139              
1140             my $singleton = dotenv();
1141              
1142             This function returns a default instance of DotCloud::Environment that
1143             should suit the needs for the typical/suggested usage. Subsequent calls
1144             to the function always return the same object.
1145              
1146             It can be useful if you don't want a global variable in your code, e.g.:
1147              
1148             my @application_names = dotenv()->application_names();
1149             # ...
1150             my $vars = dotenv()->service_vars('my-sql-db');
1151              
1152             =head2 B<< dotvars >>
1153              
1154             my $vars = dotvars('service-name-or-subservice-name');
1155              
1156             This function gets the configuration variables for the provided
1157             service using the default singleton instance. Most of the time this
1158             is exactly what you want, and nothing more.
1159              
1160             This function actually calls L or L
1161             behind the scenes, you can pass all the parameters that the method accepts.
1162              
1163             =head2 B<< find_code_dir >>
1164              
1165             my $code_directory = find_code_dir(%params);
1166              
1167             This function tries to find the file F that
1168             describes the application backtracking from the current working directory and
1169             from the directory containing the file that called us (i.e. what happens to
1170             be C<(caller($n))[1]>).
1171              
1172             Parameters:
1173              
1174             =over
1175              
1176             =item B<< n >>
1177              
1178             an integer, defaulting to 0, that tells how to call
1179             C. You shouldn't need to set it, anyway.
1180              
1181             =item B<< unix >>
1182              
1183             when set, the name of the directory will be returned in Unix format, so that
1184             you can use it with C. By default the format is the same as the
1185             system.
1186              
1187             =back
1188              
1189             This should be useful if you want to put a default configuration file there or
1190             if you want to set up a shared library directory. If you are interested into
1191             this feature, anyway, look at L which is easier to use.
1192              
1193             =head2 B<< path_for >>
1194              
1195             use lib path_for('lib');
1196              
1197             This function produces a list of paths that are suitable for
1198             C. It uses L internally, see it for details.
1199              
1200             You should pass a list of subdirectories which will be rebased using
1201             the result of L as a parent directory. If you
1202             are actually in the dotCloud enviroment, the example above produces
1203             the path C.
1204              
1205             Returns a list of Unix paths, one element for each input directory.
1206              
1207             =head1 AUTHOR
1208              
1209             Flavio Poletti
1210              
1211             =head1 COPYRIGHT AND LICENSE
1212              
1213             Copyright (C) 2011 by Flavio Poletti polettix@cpan.org.
1214              
1215             This module is free software. You can redistribute it and/or
1216             modify it under the terms of the Artistic License 2.0.
1217              
1218             This program is distributed in the hope that it will be useful,
1219             but without any warranty; without even the implied warranty of
1220             merchantability or fitness for a particular purpose.
1221              
1222             =cut
1223              
1224              
1225             __END__