Initialisations and preinstallation
[p5sagit/Oyster.git] / lib / Oyster / Provision / Rackspace.pm
CommitLineData
c9ecd647 1package Oyster::Provision::Rackspace;
3ded6347 2use Moose::Role;
4bd125ea 3use Net::RackSpace::CloudServers;
4use Net::RackSpace::CloudServers::Server;
5use MIME::Base64;
3ded6347 6
d29be7cc 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
3ded6347 10requires 'config';
11
d6e5f5fe 12has 'api_username' => ( is => 'ro', isa => 'Str', required => 1, lazy => 1, default => sub {
de5283c8 13 my $self = shift;
758d921f 14 return $ENV{CLOUDSERVERS_USER} if exists $ENV{CLOUDSERVERS_USER};
a9e65cee 15 return $self->config->{api_username}
16 or die "Need api_username or CLOUDSERVERS_USER in environment";
d6e5f5fe 17});
8a0402ec 18
d6e5f5fe 19has 'api_password' => ( is => 'ro', isa => 'Str', required => 1, lazy => 1, default => sub {
de5283c8 20 my $self = shift;
758d921f 21 return $ENV{CLOUDSERVERS_KEY} if exists $ENV{CLOUDSERVERS_KEY};
a9e65cee 22 return $self->config->{api_password}
23 or die "Need api_password or CLOUDSERVERS_KEY in environment";
d6e5f5fe 24});
4bd125ea 25
d6e5f5fe 26has '_rs' => ( is => 'rw', isa => 'Net::RackSpace::CloudServers', lazy => 1, default => sub {
4bd125ea 27 my $self = shift;
d6e5f5fe 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 }
4bd125ea 39 $rs;
40});
41
3ded6347 42sub create {
43 my $self = shift;
44
b66f0754 45 die "Rackspace Provisioning backend requires a server name\n" if !defined $self->name;
46
4bd125ea 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
b66f0754 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
4bd125ea 71 # Check the ssh pub key exists and is <10K
370470b2 72 die "SSH pubkey needs to exist" if !-f $self->pub_ssh;
4bd125ea 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 };
370470b2 80 die "SSH pubkey needs to be < 10KiB" if length $pub_ssh > 10*1024;
4bd125ea 81
82 # Build the server
83 my $server = Net::RackSpace::CloudServers::Server->new(
6895749d 84 cloudservers => $self->_rs,
85 name => $self->name,
86 flavorid => $self->size,
87 imageid => $self->image,
88 personality => [
4bd125ea 89 {
83928564 90 path => '/root/.ssh/authorized_keys',
4bd125ea 91 contents => encode_base64($pub_ssh),
92 },
6895749d 93 ],
4bd125ea 94 );
355db0c8 95 my $newserver = $server->create_server;
96 warn "Server root password: ", $newserver->adminpass, "\n";
4bd125ea 97
9e1b1d26 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
a92449de 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);
4bd125ea 112
a92449de 113 # Adds the server's name to the user's ~/.ssh/config
114 # using oyster-servername
115 {
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" .
119 " User root\n" .
120 " Port 22\n" .
121 " Compression yes\n" .
122 " HostName %s\n" .
123 "\n";
124 print $fh sprintf($template, $servername, $public_ip);
125 close $fh or die "Error closing $ENV{HOME}/.ssh/config: $!";
126 }
127
128 # Connect to server and execute installation routines -- unlike EC2 each
129 # server needs instantiated from scratch every time
130 warn "Initializing the server...";
131 $self->initialise();
132
133 warn "Deploying the application...";
134 $self->deploy();
135}
136
137sub initialise {
138 my $self = shift;
139 my $servername = sprintf("oyster-%s", $self->name);
140
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'};
145
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'};
167
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/};
171}
172
173sub deploy {
174 my $self = shift;
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,)'"};
3ded6347 178}
179
3ded6347 180sub delete {
181 my $self = shift;
182
4bd125ea 183 # Die if the server named $self->name already exists
184 my ($server) = grep { $_->name eq $self->name } $self->_rs->get_server();
370470b2 185 die "No such server: ", $self->name if !$server;
4bd125ea 186
187 # Goodbye cruel user!
188 $server->delete_server();
3ded6347 189}
190
191sub resize {
192 my $self = shift;
193
194 $self->config();
195}
c9ecd647 196
1971;
dd69a60d 198
199__END__
200
201=head1 NAME
202
203Oyster::Provision::Rackspace -- Provision your Oyster on Rackspace
204
205=head1 SYNOPSIS
206
207Use the Rackspace backend on your Oyster configuration file
208
209=head1 REQUIRED PARAMETERS
210
211The following are required to instantiate a backend:
212
213=over
214
4bd125ea 215=item api_username
216
217The rackspace API username, or C<$ENV{RACKSPACE_USER}> will be used if that is
218not given
219
e058aeaa 220=item api_password
8a0402ec 221
222This is your rackspace API Key
4bd125ea 223
224The rackspace API key, or C<$ENV{RACKSPACE_KEY}> will be used if that is not
225given
226
dd69a60d 227=item name
228
229The name of your new/existing rackspace server.
230
231=item size
232
233The size ID of the rackspace server you want to create.
234Use the following incantation to see them:
235
236 perl -MNet::RackSpace::CloudServers -e'
237 $r=Net::RackSpace::CloudServers->new(
238 user=>$ENV{CLOUDSERVERS_USER},
239 key=>$ENV{CLOUDSERVERS_KEY},
240 );
241 print map
242 { "id $_->{id} ram $_->{ram} disk $_->{disk}\n" }
243 $r->get_flavor_detail
244 '
245 id 1 ram 256 disk 10
246 id 2 ram 512 disk 20
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
252
253=item image
254
255The image ID of the rackspace server you want to create.
256Use the following incantation to see them:
257
258 perl -MNet::RackSpace::CloudServers -e'
259 $r=Net::RackSpace::CloudServers->new(
260 user=>$ENV{CLOUDSERVERS_USER},
261 key=>$ENV{CLOUDSERVERS_KEY},
262 );
263 print map
264 { "id $_->{id} name $_->{name}\n" }
265 $r->get_image_detail
266 '
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
279 id 53 name Fedora 13
280 id 17 name Fedora 12
281 id 71 name Fedora 14
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
288
289Oyster only supports Linux images, specifically
290Ubuntu 10.10 (maverick).
291
292=item pub_ssh
293
294The public ssh key you would like copied to the
295new server's C</root/.ssh/authorized_keys> file
296to allow you to ssh in the box without providing
297a root password.
298
299=back
300
301=cut