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