File Coverage

blib/lib/Cinnamon/Context.pm
Criterion Covered Total %
statement 25 27 92.5
branch n/a
condition n/a
subroutine 9 9 100.0
pod n/a
total 34 36 94.4


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