1#
2# (c) Jan Gehring <jan.gehring@gmail.com>
3#
4# vim: set ts=2 sw=2 tw=0:
5# vim: set expandtab:
6
7=head1 NAME
8
9Rex::Commands::Cloud - Cloud Management Commands
10
11=head1 DESCRIPTION
12
13With this Module you can manage different Cloud services. Currently it supports Amazon EC2, Jiffybox and OpenStack.
14
15Version <= 1.0: All these functions will not be reported.
16
17=head1 SYNOPSIS
18
19 use Rex::Commands::Cloud;
20
21 cloud_service "Amazon";
22 cloud_auth "your-access-key", "your-private-access-key";
23 cloud_region "ec2.eu-west-1.amazonaws.com";
24
25 task "list", sub {
26   print Dumper cloud_instance_list;
27   print Dumper cloud_volume_list;
28 };
29
30 task "create", sub {
31   my $vol_id = cloud_volume create => { size => 1, zone => "eu-west-1a", };
32
33   cloud_instance create => {
34       image_id => "ami-xxxxxxx",
35       name    => "test01",
36       key    => "my-key",
37       volume  => $vol_id,
38       zone    => "eu-west-1a",
39     };
40 };
41
42 task "destroy", sub {
43   cloud_volume detach => "vol-xxxxxxx";
44   cloud_volume delete => "vol-xxxxxxx";
45
46   cloud_instance terminate => "i-xxxxxxx";
47 };
48
49=head1 EXPORTED FUNCTIONS
50
51=cut
52
53package Rex::Commands::Cloud;
54
55use 5.010001;
56use strict;
57use warnings;
58
59our $VERSION = '1.13.4'; # VERSION
60
61require Rex::Exporter;
62use base qw(Rex::Exporter);
63use vars qw(@EXPORT $cloud_service $cloud_region @cloud_auth);
64
65use Rex::Logger;
66use Rex::Config;
67use Rex::Cloud;
68use Rex::Group::Entry::Server;
69
70@EXPORT = qw(cloud_instance cloud_volume cloud_network
71  cloud_instance_list cloud_volume_list cloud_network_list
72  cloud_service cloud_auth cloud_region
73  get_cloud_instances_as_group get_cloud_regions get_cloud_availability_zones
74  get_cloud_plans
75  get_cloud_operating_systems
76  cloud_image_list
77  cloud_object
78  get_cloud_floating_ip
79  cloud_upload_key);
80
81Rex::Config->register_set_handler(
82  "cloud" => sub {
83    my ( $name, @options ) = @_;
84    my $sub_name = "cloud_$name";
85
86    if ( $name eq "service" ) {
87      cloud_service(@options);
88    }
89
90    if ( $name eq "auth" ) {
91      cloud_auth(@options);
92    }
93
94    if ( $name eq "region" ) {
95      cloud_region(@options);
96    }
97  }
98);
99
100=head2 cloud_service($cloud_service)
101
102Define which cloud service to use.
103
104=over 4
105
106=item Services
107
108=over 4
109
110=item Amazon
111
112=item Jiffybox
113
114=item OpenStack
115
116=back
117
118=back
119
120
121=cut
122
123sub cloud_service {
124  ($cloud_service) = @_;
125
126  # set retry counter to a higher value
127  if ( Rex::Config->get_max_connect_fails() < 5 ) {
128    Rex::Config->set_max_connect_fails(15);
129  }
130}
131
132=head2 cloud_auth($param1, $param2, ...)
133
134Set the authentication for the cloudservice.
135
136For example for Amazon it is:
137
138 cloud_auth($access_key, $secret_access_key);
139
140For JiffyBox:
141
142 cloud_auth($auth_key);
143
144For OpenStack:
145
146 cloud_auth(
147  tenant_name => 'tenant',
148  username    => 'user',
149  password    => 'password',
150 );
151
152=cut
153
154sub cloud_auth {
155  @cloud_auth = @_;
156}
157
158=head2 cloud_region($region)
159
160Set the cloud region.
161
162=cut
163
164sub cloud_region {
165  ($cloud_region) = @_;
166}
167
168=head2 cloud_instance_list
169
170Get all instances of a cloud service.
171
172 task "list", sub {
173   for my $instance (cloud_instance_list()) {
174     say "Arch  : " . $instance->{"architecture"};
175     say "IP   : " . $instance->{"ip"};
176     say "ID   : " . $instance->{"id"};
177     say "State : " . $instance->{"state"};
178   }
179 };
180
181There are some parameters for this function that can change the gathering of ip addresses for some cloud providers (like OpenStack).
182
183 task "list", sub {
184   my @instances = cloud_instance_list
185                      private_network => 'private',
186                      public_network  => 'public',
187                      public_ip_type  => 'floating',
188                      private_ip_type => 'fixed';
189 };
190
191=cut
192
193sub cloud_instance_list {
194  return cloud_object()->list_instances(@_);
195}
196
197=head2 cloud_volume_list
198
199Get all volumes of a cloud service.
200
201 task "list-volumes", sub {
202   for my $volume (cloud_volume_list()) {
203     say "ID     : " . $volume->{"id"};
204     say "Zone    : " . $volume->{"zone"};
205     say "State   : " . $volume->{"state"};
206     say "Attached : " . $volume->{"attached_to"};
207   }
208 };
209
210=cut
211
212sub cloud_volume_list {
213  return cloud_object()->list_volumes();
214}
215
216=head2 cloud_network_list
217
218Get all networks of a cloud service.
219
220 task "network-list", sub {
221   for my $network (cloud_network_list()) {
222     say "network  : " . $network->{network};
223     say "name    : " . $network->{name};
224     say "id     : " . $network->{id};
225   }
226 };
227
228=cut
229
230sub cloud_network_list {
231  return cloud_object()->list_networks();
232}
233
234=head2 cloud_image_list
235
236Get a list of all available cloud images.
237
238=cut
239
240sub cloud_image_list {
241  return cloud_object()->list_images();
242}
243
244=head2 cloud_upload_key
245
246Upload public SSH key to cloud provider
247
248 private_key '~/.ssh/mykey
249 public_key  '~/.ssh/mykey.pub';
250
251 task "cloudprovider", sub {
252   cloud_upload_key;
253
254   cloud_instance create => {
255     ...
256   };
257 };
258
259=cut
260
261sub cloud_upload_key {
262  return cloud_object()->upload_key();
263}
264
265=head2 get_cloud_instances_as_group
266
267Get a list of all running instances of a cloud service. This can be used for a I<group> definition.
268
269 group fe  => "fe01", "fe02", "fe03";
270 group ec2 => get_cloud_instances_as_group();
271
272=cut
273
274sub get_cloud_instances_as_group {
275
276  my @list = cloud_object()->list_running_instances();
277
278  my @ret;
279
280  for my $instance (@list) {
281    push( @ret, Rex::Group::Entry::Server->new( name => $instance->{"ip"} ) );
282  }
283
284  return @ret;
285}
286
287=head2 cloud_instance($action, $data)
288
289This function controls all aspects of a cloud instance.
290
291=cut
292
293sub cloud_instance {
294
295  my ( $action, $data ) = @_;
296  my $cloud = cloud_object();
297
298  if ( $action eq "list" ) {
299    return $cloud->list_running_instances();
300  }
301
302=head2 create
303
304Create a new instance.
305
306 cloud_instance create => {
307     image_id => "ami-xxxxxx",
308     key    => "ssh-key",
309     name    => "fe-ec2-01",  # name is not necessary
310     volume  => "vol-yyyyy",  # volume is not necessary
311     zone    => "eu-west-1a",  # zone is not necessary
312     floating_ip  => "89.39.38.160" # floating_ip is not necessary
313   };
314
315=cut
316
317  elsif ( $action eq "create" ) {
318    my %data_hash = (
319
320      # image_id => $data->{"image_id"},
321      # name    => $data->{"name"} || undef,
322      # key    => $data->{"key"} || undef,
323      # zone    => $data->{"zone"} || undef,
324      # volume  => $data->{"volume"} || undef,
325      # password => $data->{"password"} || undef,
326      # plan_id  => $data->{"plan_id"} || undef,
327      # type    => $data->{"type"} || undef,
328      # security_group => $data->{"security_group"} || undef,
329      %{$data},
330    );
331
332    $cloud->run_instance(%data_hash);
333  }
334
335=head2 start
336
337Start an existing instance
338
339 cloud_instance start => "instance-id";
340
341=cut
342
343  elsif ( $action eq "start" ) {
344    $cloud->start_instance( instance_id => $data );
345  }
346
347=head2 stop
348
349Stop an existing instance
350
351 cloud_instance stop => "instance-id";
352
353=cut
354
355  elsif ( $action eq "stop" ) {
356    $cloud->stop_instance( instance_id => $data );
357  }
358
359=head2 terminate
360
361Terminate an instance. This will destroy all data and remove the instance.
362
363 cloud_instance terminate => "i-zzzzzzz";
364
365=cut
366
367  elsif ( $action eq "terminate" ) {
368    $cloud->terminate_instance( instance_id => $data );
369  }
370
371}
372
373=head2 get_cloud_regions
374
375Returns all regions as an array.
376
377=cut
378
379sub get_cloud_regions {
380  return cloud_object()->get_regions;
381}
382
383=head2 cloud_volume($action , $data)
384
385This function controlls all aspects of a cloud volume.
386
387=cut
388
389sub cloud_volume {
390
391  my ( $action, @_data ) = @_;
392  my $data;
393  if ( @_data == 1 ) {
394    if ( ref $_data[0] ) {
395      $data = $_data[0];
396    }
397    else {
398      $data = { id => $_data[0] };
399    }
400  }
401  else {
402    $data = { "id", @_data };
403  }
404
405  my $cloud = cloud_object();
406
407=head2 create
408
409Create a new volume. Size is in Gigabytes.
410
411 task "create-vol", sub {
412   my $vol_id = cloud_volume create => { size => 1, zone => "eu-west-1a", };
413 };
414
415=cut
416
417  if ( $action eq "create" ) {
418    $cloud->create_volume(
419      size => $data->{"size"} || 1,
420      %{$data},
421    );
422  }
423
424=head2 attach
425
426Attach a volume to an instance.
427
428 task "attach-vol", sub {
429   cloud_volume attach => "vol-xxxxxx", to => "server-id";
430 };
431
432=cut
433
434  elsif ( $action eq "attach" ) {
435    my $vol_id = $data->{id};
436    my $srv_id = $data->{to};
437
438    $cloud->attach_volume(
439      volume_id   => $vol_id,
440      server_id   => $srv_id,
441      device_name => $data->{device}
442    );
443  }
444
445=head2 detach
446
447Detach a volume from an instance.
448
449 task "detach-vol", sub {
450   cloud_volume detach => "vol-xxxxxx", from => "server-id";
451 };
452
453=cut
454
455  elsif ( $action eq "detach" ) {
456    my $vol_id = $data->{id};
457    my $srv_id = $data->{from};
458
459    $cloud->detach_volume(
460      volume_id => $vol_id,
461      server_id => $srv_id,
462      attach_id => $data->{attach_id}
463    );
464  }
465
466=head2 delete
467
468Delete a volume. This will destroy all data.
469
470 task "delete-vol", sub {
471   cloud_volume delete => "vol-xxxxxx";
472 };
473
474=cut
475
476  elsif ( $action eq "delete" ) {
477    $cloud->delete_volume( volume_id => $data->{id} );
478  }
479
480  elsif ( $action eq "list" ) {
481    return $cloud->list_volumes();
482  }
483
484}
485
486=head2 get_cloud_floating_ip
487
488Returns first available floating IP
489
490 task "get_floating_ip", sub {
491
492   my $ip = get_cloud_floating_ip;
493
494   my $instance = cloud_instance create => {
495      image_id => 'edffd57d-82bf-4ffe-b9e8-af22563741bf',
496      name => 'instance1',
497      plan_id => 17,
498      floating_ip => $ip
499    };
500 };
501
502=cut
503
504sub get_cloud_floating_ip {
505  return cloud_object()->get_floating_ip;
506}
507
508=head2 cloud_network
509
510=cut
511
512sub cloud_network {
513
514  my ( $action, $data ) = @_;
515  my $cloud = cloud_object();
516
517=head2 create
518
519Create a new network.
520
521 task "create-net", sub {
522   my $net_id = cloud_network create => { cidr => '192.168.0.0/24', name => "mynetwork", };
523 };
524
525=cut
526
527  if ( $action eq "create" ) {
528    $cloud->create_network( %{$data} );
529  }
530
531=head2 delete
532
533Delete a network.
534
535 task "delete-net", sub {
536   cloud_network delete => '18a4ccf8-f14a-a10d-1af4-4ac7fee08a81';
537 };
538
539=cut
540
541  elsif ( $action eq "delete" ) {
542    $cloud->delete_network($data);
543  }
544}
545
546=head2 get_cloud_availability_zones
547
548Returns all availability zones of a cloud services. If available.
549
550 task "get-zones", sub {
551   print Dumper get_cloud_availability_zones;
552 };
553
554=cut
555
556sub get_cloud_availability_zones {
557  return cloud_object()->get_availability_zones();
558}
559
560=head2 get_cloud_plans
561
562Retrieve information of the available cloud plans. If supported.
563
564=cut
565
566sub get_cloud_plans {
567  return cloud_object()->list_plans;
568}
569
570=head2 get_cloud_operating_systems
571
572Retrieve information of the available cloud plans. If supported.
573
574=cut
575
576sub get_cloud_operating_systems {
577  return cloud_object()->list_operating_systems;
578}
579
580=head2 cloud_object
581
582Returns the cloud object itself.
583
584=cut
585
586sub cloud_object {
587  my $cloud = get_cloud_service($cloud_service);
588
589  $cloud->set_auth(@cloud_auth);
590  $cloud->set_endpoint($cloud_region);
591
592  return $cloud;
593}
594
5951;
596