1 package Oyster::Provision::Rackspace;
3 use Net::RackSpace::CloudServers;
4 use Net::RackSpace::CloudServers::Server;
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
12 has 'api_username' => ( is => 'ro', isa => 'Str', required => 1, lazy => 1, default => sub {
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";
19 has 'api_password' => ( is => 'ro', isa => 'Str', required => 1, lazy => 1, default => sub {
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";
26 has '_rs' => ( is => 'rw', isa => 'Net::RackSpace::CloudServers', lazy => 1, default => sub {
29 Net::RackSpace::CloudServers->new(
30 user => $self->api_username,
31 key => $self->api_password,
36 "Could not instantiate a backend connection to RackSpace CloudServers.\n",
37 "Check the api_username and api_password on the configuration file\n";
45 die "Rackspace Provisioning backend requires a server name\n" if !defined $self->name;
47 # Do nothing if the server named $self->name already exists
48 return if scalar grep { $_->name eq $self->name } $self->_rs->get_server();
50 # Validate size and image
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),
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),
71 # Check the ssh pub key exists and is <10K
72 die "SSH pubkey needs to exist" if !-f $self->pub_ssh;
75 open my $fh, '<', $self->pub_ssh or die "Cannot open ", $self->pub_ssh, ": $!";
77 close $fh or die "Cannot close ", $self->pub_ssh, ": $!";
80 die "SSH pubkey needs to be < 10KiB" if length $pub_ssh > 10*1024;
83 my $server = Net::RackSpace::CloudServers::Server->new(
84 cloudservers => $self->_rs,
86 flavorid => $self->size,
87 imageid => $self->image,
90 path => '/root/.ssh/authorized_keys',
91 contents => encode_base64($pub_ssh),
95 my $newserver = $server->create_server;
96 warn "Server root password: ", $newserver->adminpass, "\n";
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' ) {
107 } while ( ( $server->status // '' ) ne 'ACTIVE' );
109 warn "Server public IPs are: @{$server->public_address}\n";
110 my $public_ip = (@{$server->public_address})[0];
111 my $servername = sprintf("oyster-%s", $self->name);
113 # Adds the server's name to the user's ~/.ssh/config
114 # using oyster-servername
116 open my $fh, '>>', "$ENV{HOME}/.ssh/config"
117 or die "Error opening $ENV{HOME}/.ssh/config for appending: $!";
118 my $template = "\nHost %s\n" .
121 " Compression yes\n" .
124 print $fh sprintf($template, $servername, $public_ip);
125 close $fh or die "Error closing $ENV{HOME}/.ssh/config: $!";
128 # Connect to server and execute installation routines -- unlike EC2 each
129 # server needs instantiated from scratch every time
130 warn "Initializing the server...";
133 warn "Deploying the application...";
139 my $servername = sprintf("oyster-%s", $self->name);
141 # Adds the server's key to the user's ~/.ssh/authorized_keys
142 # FIXME there must be a better way?!
143 warn "Adding SSH key for $servername to ~/.ssh/authorized_keys\n";
144 qx{/usr/bin/ssh -o StrictHostKeyChecking=no -l root $servername 'echo oyster'};
146 # FIXME should call the module which does the installation...
147 warn "Installing wget, lighttpd and git...\n";
148 print qx{/usr/bin/ssh -l root $servername 'LC_ALL=C /usr/bin/apt-get install --yes wget lighttpd git git-core'};
149 print qx{/usr/bin/ssh -l root $servername 'LC_ALL=C /usr/sbin/service lighttpd stop'};
150 warn "Adding user perloyster...\n";
151 print qx{/usr/bin/ssh -l root $servername 'LC_ALL=C /usr/sbin/adduser --disabled-password --gecos "Perl Oyster" perloyster'};
152 warn "Copying keys to ~perloyster...\n";
153 print qx{/usr/bin/ssh -l root $servername 'LC_ALL=C /bin/mkdir ~perloyster/.ssh/'};
154 print qx{/usr/bin/ssh -l root $servername 'LC_ALL=C /bin/cp ~/.ssh/authorized_keys ~perloyster/.ssh/'};
155 print qx{/usr/bin/ssh -l root $servername 'LC_ALL=C /bin/chown --recursive perloyster ~perloyster/.ssh/'};
156 warn "Making perloyster readable...\n";
157 print qx{/usr/bin/ssh -l perloyster $servername 'LC_ALL=C /bin/chmod a+r ~perloyster/'};
158 #warn "Installing cpanminus...\n";
159 #print qx{/usr/bin/ssh -l perloyster $servername 'LC_ALL=C /usr/bin/wget --no-check-certificate http://xrl.us/cpanm ; chmod +x cpanm'};
160 #warn "Installing prerequisites for Oyster::Deploy::Git...\n";
161 #print qx{/usr/bin/ssh -l perloyster $servername 'LC_ALL=C ./cpanm --local-lib=~/perl5 App::cpanminus Dist::Zilla'};
162 warn "Getting and unpacking base system...\n";
163 print qx{/usr/bin/ssh -l perloyster $servername 'LC_ALL=C /usr/bin/wget --no-check-certificate https://darkpan.com/files/oyster-prereqs-20101122-2217.tgz'};
164 print qx{/usr/bin/ssh -l perloyster $servername 'LC_ALL=C /bin/tar xvf oyster-prereqs-20101122-2217.tgz'};
165 print qx{/usr/bin/ssh -l perloyster $servername 'LC_ALL=C /bin/echo export PERL5LIB="/home/perloyster/perl5/lib/perl5:/home/perloyster/perl/lib/perl5/x86_64-linux-gnu-thread-multi" >> ~/.bashrc'};
166 print qx{/usr/bin/ssh -l perloyster $servername 'LC_ALL=C /bin/echo export PATH="/home/perloyster/perl5/bin:\$PATH" >> ~/.bashrc'};
168 warn "Pushing and unpacking Oyster::Deploy::Git...\n";
169 print qx{/usr/bin/ssh -l perloyster $servername 'LC_ALL=C /bin/mkdir -p perl5/lib/perl5/Oyster/Deploy'};
170 print qx{/usr/bin/scp lib/Oyster/Deploy/Git.pm perloyster\@$servername:perl5/lib/perl5/Oyster/Deploy/};
175 my $servername = sprintf("oyster-%s", $self->name);
176 warn "Deploying application to $servername...\n";
177 print qx{/usr/bin/ssh -l perloyster $servername "perl -MOyster::Deploy::Git -le'\$g=Oyster::Deploy::Git->new;\$g->deploy(q,/home/perloyster/oyster,)'"};
183 # Die if the server named $self->name already exists
184 my ($server) = grep { $_->name eq $self->name } $self->_rs->get_server();
185 die "No such server: ", $self->name if !$server;
187 # Goodbye cruel user!
188 $server->delete_server();
203 Oyster::Provision::Rackspace -- Provision your Oyster on Rackspace
207 Use the Rackspace backend on your Oyster configuration file
209 =head1 REQUIRED PARAMETERS
211 The following are required to instantiate a backend:
217 The rackspace API username, or C<$ENV{RACKSPACE_USER}> will be used if that is
222 This is your rackspace API Key
224 The rackspace API key, or C<$ENV{RACKSPACE_KEY}> will be used if that is not
229 The name of your new/existing rackspace server.
233 The size ID of the rackspace server you want to create.
234 Use the following incantation to see them:
236 perl -MNet::RackSpace::CloudServers -e'
237 $r=Net::RackSpace::CloudServers->new(
238 user=>$ENV{CLOUDSERVERS_USER},
239 key=>$ENV{CLOUDSERVERS_KEY},
242 { "id $_->{id} ram $_->{ram} disk $_->{disk}\n" }
243 $r->get_flavor_detail
247 id 3 ram 1024 disk 40
248 id 4 ram 2048 disk 80
249 id 5 ram 4096 disk 160
250 id 6 ram 8192 disk 320
251 id 7 ram 15872 disk 620
255 The image ID of the rackspace server you want to create.
256 Use the following incantation to see them:
258 perl -MNet::RackSpace::CloudServers -e'
259 $r=Net::RackSpace::CloudServers->new(
260 user=>$ENV{CLOUDSERVERS_USER},
261 key=>$ENV{CLOUDSERVERS_KEY},
264 { "id $_->{id} name $_->{name}\n" }
267 id 29 name Windows Server 2003 R2 SP2 x86
268 id 69 name Ubuntu 10.10 (maverick)
269 id 41 name Oracle EL JeOS Release 5 Update 3
270 id 40 name Oracle EL Server Release 5 Update 4
271 id 187811 name CentOS 5.4
272 id 4 name Debian 5.0 (lenny)
273 id 10 name Ubuntu 8.04.2 LTS (hardy)
274 id 23 name Windows Server 2003 R2 SP2 x64
275 id 24 name Windows Server 2008 SP2 x64
276 id 49 name Ubuntu 10.04 LTS (lucid)
277 id 14362 name Ubuntu 9.10 (karmic)
278 id 62 name Red Hat Enterprise Linux 5.5
282 id 31 name Windows Server 2008 SP2 x86
283 id 51 name CentOS 5.5
284 id 14 name Red Hat Enterprise Linux 5.4
285 id 19 name Gentoo 10.1
286 id 28 name Windows Server 2008 R2 x64
287 id 55 name Arch 2010.05
289 Oyster only supports Linux images, specifically
290 Ubuntu 10.10 (maverick).
294 The public ssh key you would like copied to the
295 new server's C</root/.ssh/authorized_keys> file
296 to allow you to ssh in the box without providing