File Coverage

blib/lib/Cinnamon/Context.pm
Criterion Covered Total %
statement 127 137 92.7
branch 21 26 80.7
condition 13 14 92.8
subroutine 27 27 100.0
pod 0 13 0.0
total 188 217 86.6


line stmt bran cond sub pod time code
1             package Cinnamon::Context;
2 3     3   150955 use strict;
  3         7  
  3         97  
3 3     3   15 use warnings;
  3         6  
  3         79  
4              
5 3     3   3576 use Moo;
  3         42303  
  3         20  
6              
7 3     3   7827 use YAML ();
  3         24048  
  3         130  
8 3     3   2355 use Class::Load ();
  3         65251  
  3         82  
9 3     3   2569 use Hash::MultiValue;
  3         7528  
  3         115  
10 3     3   2995 use Term::ReadKey;
  3         14100  
  3         368  
11              
12 3     3   1847 use Cinnamon;
  3         9  
  3         150  
13 3     3   1495 use Cinnamon::Runner;
  3         17  
  3         130  
14 3     3   28 use Cinnamon::Logger;
  3         6  
  3         151  
15 3     3   1632 use Cinnamon::Role;
  3         13  
  3         108  
16 3     3   1860 use Cinnamon::Task;
  3         9  
  3         92  
17 3     3   1652 use Cinnamon::Config::Loader;
  3         8  
  3         4396  
18              
19             our $CTX;
20              
21             has roles => (
22             is => 'ro',
23             default => sub { Hash::MultiValue->new() }
24             );
25              
26             has tasks => (
27             is => 'ro',
28             default => sub { Hash::MultiValue->new() }
29             );
30              
31             has params => (
32             is => 'ro',
33             default => sub { Hash::MultiValue->new() }
34             );
35              
36             sub run {
37 14     14 0 57 my ($self, $role_name, $task_name, %opts) = @_;
38              
39 14         101 Cinnamon::Config::Loader->load(config => $opts{config});
40              
41 14 100       48 if ($opts{info}) {
42 1         4 $self->dump_info;
43 1         48 return;
44             }
45              
46             # set role name and task name
47 13         41 CTX->set_param(role => $role_name);
48 13         285 CTX->set_param(task => $task_name);
49              
50             # override setting
51 13         271 for my $key (keys %{ $opts{override_settings} }) {
  13         55  
52 2         26 CTX->set_param($key => $opts{override_settings}->{$key});
53             }
54              
55 13         66 my $hosts = $self->get_role_hosts($role_name);
56 13         37 my $task = $self->get_task($task_name);
57 13   50     86 my $runner = $self->get_param('runner_class') || 'Cinnamon::Runner';
58              
59 13 50       34 unless (defined $hosts) {
60 0         0 log 'error', "undefined role : '$role_name'";
61 0         0 return;
62             }
63 13 100       33 unless (defined $task) {
64 2         11 log 'error', "undefined task : '$task_name'";
65 2         10 return;
66             }
67              
68 11         46 Class::Load::load_class $runner;
69              
70 11         1075 my $result = $runner->start($hosts, $task);
71 11         21 my (@success, @error);
72              
73 11 50       18 for my $key (keys %{$result || {}}) {
  11         55  
74 11 100       36 if ($result->{$key}->{error}) {
75 3         18 push @error, $key;
76             }
77             else {
78 8         23 push @success, $key;
79             }
80             }
81              
82 11   100     99 log success => sprintf(
83             "\n========================\n[success]: %s",
84             (join(', ', @success) || ''),
85             );
86              
87 11   100     85 log error => sprintf(
88             "[error]: %s",
89             (join(', ', @error) || ''),
90             );
91              
92 11         90 return (\@success, \@error);
93             }
94              
95             sub add_role {
96 17     17 0 148 my ($self, $name, $hosts, $params) = @_;
97 17   100     87 $params ||= {};
98 17         100 my $role = Cinnamon::Role->new(
99             name => $name,
100             hosts => $hosts,
101             params => Hash::MultiValue->new(%$params),
102             );
103 17         5508 $self->roles->set($name => $role);
104             }
105              
106             sub get_role {
107 20     20 0 1090 my ($self, $name) = @_;
108 20         86 return $self->roles->get($name);
109             }
110              
111             sub get_role_hosts {
112 16     16 0 88 my ($self, $name) = @_;
113 16 50       50 my $role = $self->get_role($name) or return undef;
114 16         153 my $hosts = $role->get_hosts;
115              
116             # set role params
117             # TODO: move from here
118 16         47 my $params = $role->params;
119 16         52 for my $key (keys %$params) {
120 1         4 $self->set_param($key => $params->{$key});
121             }
122              
123 16         65 return $hosts;
124             }
125              
126             sub add_task {
127 34     34 0 357 my ($self, $name, $code) = @_;
128 34 100       110 unless (ref $code eq 'HASH') {
129 31         742 my $task = Cinnamon::Task->new(
130             name => $name,
131             code => $code,
132             );
133 31         3943 return $self->tasks->set($name => $task);
134             }
135              
136             # a nest task is named as joined by colon
137 3         9 for my $child (keys %$code) {
138 4         27 my $child_name = join ":", $name, $child;
139 4         16 $self->add_task($child_name => $code->{$child});
140             }
141             }
142              
143             sub get_task {
144 21     21 0 4926 my ($self, $name) = @_;
145 21         168 return $self->tasks->get($name);
146             }
147              
148             sub set_param {
149 55     55 0 234 my ($self, $key, $value) = @_;
150 55         226 $self->params->set($key => $value);
151             }
152              
153             sub get_param {
154 51     51 0 150 my ($self, $key, @args) = @_;
155              
156 51         192 my $value = $self->params->get($key);
157 51 100       335 $value = $value->(@args) if ref $value eq 'CODE';
158              
159 51         710 return $value;
160             }
161              
162             # Thread-specific stash
163             sub stash {
164 13   100 13 0 62 my $stash = $Coro::current->{Cinnamon} ||= {};
165             }
166              
167             sub call_task {
168 3     3 0 1552 my ($self, $task_name, $host) = @_;
169 3 100       10 my $task = $self->get_task($task_name) or die "undefined task : '$task_name'";
170              
171 2         24 $task->execute($host);
172             }
173              
174             sub run_cmd {
175 6     6 0 9 my ($self, $commands, $opts) = @_;
176 6   100     22 $opts ||= {};
177              
178 6   100     15 my $current_host = $self->stash->{current_host} || 'localhost';
179 6         39 log info => sprintf "[%s :: executing] %s", $current_host, join(' ', @$commands);
180              
181 6 100       20 if ($opts->{sudo}) {
182 3         9 $opts->{password} = $self->_get_sudo_password();
183             }
184              
185 6         14 $opts->{tty} = !! $self->get_param('tty');
186              
187 6         13 my $executor = $self->build_command_executor;
188 6         76 my $result = $executor->execute($commands, $opts);
189              
190 6 50       4105 if ($result->{has_error}) {
191 0         0 die sprintf "error status: %d", $result->{error};
192             }
193              
194 6         46 return ($result->{stdout}, $result->{stderr});
195             }
196              
197             sub build_command_executor {
198 6     6 0 18 my ($self) = @_;
199              
200 6 100       11 if (my $remote = $self->stash->{current_remote}) {
201 2         5 return $remote;
202             }
203             else {
204 4         26 return Cinnamon::Local->new;
205             }
206             }
207              
208             sub dump_info {
209 1     1 0 90 my ($self) = @_;
210 1         3 my $info = {};
211              
212 1         4 my $roles = $self->roles;
213 1         12 my $role_info = +{
214 1         7 map { $_->name => $_->info } $roles->values,
215             };
216              
217 1         15 my $tasks = $self->tasks;
218 1         5 my $task_info = +{
219 1         4 map { %{ $_->info } } $tasks->values,
  1         5  
220             };
221              
222 1         9 log 'info', YAML::Dump({
223             roles => $role_info,
224             tasks => $task_info,
225             });
226             }
227              
228             sub _get_sudo_password {
229 3     3   47 my ($self) = @_;
230 3         10 my $password = $self->get_param('password');
231 3 50       14 return $password if defined $password;
232              
233 0           print "Enter sudo password: ";
234 0           ReadMode "noecho";
235 0           chomp($password = ReadLine 0);
236 0           ReadMode 0;
237 0           print "\n";
238              
239 0           $self->set_param(password => $password);
240 0           return $password;
241             }
242              
243             !!1;