ecb95eeec1c61fd2426de547c265056fa487eed0
[p5sagit/Oyster.git] / lib / Oyster / Provision / Rackspace.pm
1 package Oyster::Provision::Rackspace;
2 use Moose::Role;
3 use Net::RackSpace::CloudServers;
4 use Net::RackSpace::CloudServers::Server;
5 use MIME::Base64;
6
7 # TODO http://failverse.com/manually-creating-a-cloud-server-from-a-cloud-files-image/
8 # in order to use an already created image to build the server, a la EC2 way
9
10 requires 'config';
11
12 has 'api_username' => ( is => 'ro', isa => 'Str', required => 1, lazy => 1, default => sub {
13     my $self = shift;
14     return $ENV{CLOUDSERVERS_USER} if exists $ENV{CLOUDSERVERS_USER};
15     return $self->config->{api_username}
16         or die "Need api_username or CLOUDSERVERS_USER in environment";
17 });
18
19 has 'api_password' => ( is => 'ro', isa => 'Str', required => 1, lazy => 1, default => sub {
20     my $self = shift;
21     return $ENV{CLOUDSERVERS_KEY} if exists $ENV{CLOUDSERVERS_KEY};
22     return $self->config->{api_password}
23         or die "Need api_password or CLOUDSERVERS_KEY in environment";
24 });
25
26 has '_rs' => ( is => 'rw', isa => 'Net::RackSpace::CloudServers', lazy => 1, default => sub {
27     my $self = shift;
28     my $rs = eval {
29         Net::RackSpace::CloudServers->new(
30             user => $self->api_username,
31             key  => $self->api_password,
32         );
33     };
34     if ( $@ ) {
35         die
36             "Could not instantiate a backend connection to RackSpace CloudServers.\n",
37             "Check the api_username and api_password on the configuration file\n";
38     }
39     $rs;
40 });
41
42 sub create {
43    my $self = shift;
44
45    die "Rackspace Provisioning backend requires a server name\n" if !defined $self->name;
46
47    # Do nothing if the server named $self->name already exists
48    return if scalar grep { $_->name eq $self->name } $self->_rs->get_server();
49
50    # Validate size and image
51    {
52        die "Rackspace Provisioning backend requires a server image\n"  if !defined $self->image;
53        my @allowed_images = $self->_rs->get_image();
54        my $image_id = $self->image;
55        if ( !scalar grep { $_->{id} eq $image_id } @allowed_images ) {
56            die "Rackspace Provisioning backend requires a valid image id\nValid images:\n",
57                (map { sprintf("id %-10s -- %s\n", $_->{id}, $_->{name}) } @allowed_images),
58                "\n";
59        }
60
61        die "Rackspace Provisioning backend requires a server size\n"  if !defined $self->size;
62        my @allowed_flavors = $self->_rs->get_flavor();
63        my $flavor_id = $self->size;
64        if ( !scalar grep { $_->{id} eq $flavor_id } @allowed_flavors ) {
65            die "Rackspace Provisioning backend requires a valid size id\nValid flavors:\n",
66                (map { sprintf("id %-10s -- %s\n", $_->{id}, $_->{name}) } @allowed_flavors),
67                "\n";
68        }
69    }
70
71    # Check the ssh pub key exists and is <10K
72    die "SSH pubkey needs to exist" if !-f $self->pub_ssh;
73    my $pub_ssh = do {
74        local $/=undef;
75        open my $fh, '<', $self->pub_ssh or die "Cannot open ", $self->pub_ssh, ": $!";
76        my $_data = <$fh>;
77        close $fh or die "Cannot close ", $self->pub_ssh, ": $!";
78        $_data;
79    };
80    die "SSH pubkey needs to be < 10KiB" if length $pub_ssh > 10*1024;
81
82    # Build the server
83    my $server = Net::RackSpace::CloudServers::Server->new(
84       cloudservers => $self->_rs,
85       name         => $self->name,
86       flavorid     => $self->size,
87       imageid      => $self->image,
88       personality => [
89            {
90                path     => '/root/.ssh/authorized_keys',
91                contents => encode_base64($pub_ssh),
92            },
93       ],
94    );
95    my $newserver = $server->create_server;
96    warn "Server root password: ", $newserver->adminpass, "\n";
97
98    do {
99       $|=1;
100       my @tmpservers = $self->_rs->get_server_detail();
101       $server = ( grep { $_->name eq $self->name } @tmpservers )[0];
102       print "\rServer status: ", ($server->status || '?'), " progress: ", ($server->progress || '?');
103       if ( ( $server->status // '' ) ne 'ACTIVE' ) {
104         print " sleeping..";
105         sleep 2;
106       }
107    } while ( ( $server->status // '' ) ne 'ACTIVE' );
108
109    warn "Server public IP is: @{$server->public_address}\n";
110
111    # Connect to server and execute installation routines?
112    # Use Net::SSH?
113 }
114
115 sub delete {
116    my $self = shift;
117
118    # Die if the server named $self->name already exists
119    my ($server) = grep { $_->name eq $self->name } $self->_rs->get_server();
120    die "No such server: ", $self->name if !$server;
121
122    # Goodbye cruel user!
123    $server->delete_server();
124 }
125
126 sub resize {
127    my $self = shift;
128
129    $self->config();
130 }
131
132 1;
133
134 __END__
135
136 =head1 NAME
137
138 Oyster::Provision::Rackspace -- Provision your Oyster on Rackspace
139
140 =head1 SYNOPSIS
141
142 Use the Rackspace backend on your Oyster configuration file
143
144 =head1 REQUIRED PARAMETERS
145
146 The following are required to instantiate a backend:
147
148 =over
149
150 =item api_username
151
152 The rackspace API username, or C<$ENV{RACKSPACE_USER}> will be used if that is
153 not given
154
155 =item password
156
157 This is your rackspace API Key
158
159 The rackspace API key, or C<$ENV{RACKSPACE_KEY}> will be used if that is not
160 given
161
162 =item name
163
164 The name of your new/existing rackspace server.
165
166 =item size
167
168 The size ID of the rackspace server you want to create.
169 Use the following incantation to see them:
170
171     perl -MNet::RackSpace::CloudServers -e'
172         $r=Net::RackSpace::CloudServers->new(
173             user=>$ENV{CLOUDSERVERS_USER},
174             key=>$ENV{CLOUDSERVERS_KEY},
175         );
176         print map
177             { "id $_->{id} ram $_->{ram} disk $_->{disk}\n" }
178             $r->get_flavor_detail
179     '
180     id 1 ram 256 disk 10
181     id 2 ram 512 disk 20
182     id 3 ram 1024 disk 40
183     id 4 ram 2048 disk 80
184     id 5 ram 4096 disk 160
185     id 6 ram 8192 disk 320
186     id 7 ram 15872 disk 620
187
188 =item image
189
190 The image ID of the rackspace server you want to create.
191 Use the following incantation to see them:
192
193     perl -MNet::RackSpace::CloudServers -e'
194         $r=Net::RackSpace::CloudServers->new(
195             user=>$ENV{CLOUDSERVERS_USER},
196             key=>$ENV{CLOUDSERVERS_KEY},
197         );
198         print map
199             { "id $_->{id} name $_->{name}\n" }
200             $r->get_image_detail
201     '
202     id 29 name Windows Server 2003 R2 SP2 x86
203     id 69 name Ubuntu 10.10 (maverick)
204     id 41 name Oracle EL JeOS Release 5 Update 3
205     id 40 name Oracle EL Server Release 5 Update 4
206     id 187811 name CentOS 5.4
207     id 4 name Debian 5.0 (lenny)
208     id 10 name Ubuntu 8.04.2 LTS (hardy)
209     id 23 name Windows Server 2003 R2 SP2 x64
210     id 24 name Windows Server 2008 SP2 x64
211     id 49 name Ubuntu 10.04 LTS (lucid)
212     id 14362 name Ubuntu 9.10 (karmic)
213     id 62 name Red Hat Enterprise Linux 5.5
214     id 53 name Fedora 13
215     id 17 name Fedora 12
216     id 71 name Fedora 14
217     id 31 name Windows Server 2008 SP2 x86
218     id 51 name CentOS 5.5
219     id 14 name Red Hat Enterprise Linux 5.4
220     id 19 name Gentoo 10.1
221     id 28 name Windows Server 2008 R2 x64
222     id 55 name Arch 2010.05
223
224 Oyster only supports Linux images, specifically
225 Ubuntu 10.10 (maverick).
226
227 =item pub_ssh
228
229 The public ssh key you would like copied to the
230 new server's C</root/.ssh/authorized_keys> file
231 to allow you to ssh in the box without providing
232 a root password.
233
234 =back
235
236 =cut