Commit | Line | Data |
c9ecd647 |
1 | package Oyster::Provision::Rackspace; |
3ded6347 |
2 | use Moose::Role; |
4bd125ea |
3 | use Net::RackSpace::CloudServers; |
4 | use Net::RackSpace::CloudServers::Server; |
5 | use 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 |
10 | requires 'config'; |
11 | |
d6e5f5fe |
12 | has '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 |
19 | has '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 |
26 | has '_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 |
42 | sub 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 | |
137 | sub 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 | |
173 | sub 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 |
180 | sub 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 | |
191 | sub resize { |
192 | my $self = shift; |
193 | |
194 | $self->config(); |
195 | } |
c9ecd647 |
196 | |
197 | 1; |
dd69a60d |
198 | |
199 | __END__ |
200 | |
201 | =head1 NAME |
202 | |
203 | Oyster::Provision::Rackspace -- Provision your Oyster on Rackspace |
204 | |
205 | =head1 SYNOPSIS |
206 | |
207 | Use the Rackspace backend on your Oyster configuration file |
208 | |
209 | =head1 REQUIRED PARAMETERS |
210 | |
211 | The following are required to instantiate a backend: |
212 | |
213 | =over |
214 | |
4bd125ea |
215 | =item api_username |
216 | |
217 | The rackspace API username, or C<$ENV{RACKSPACE_USER}> will be used if that is |
218 | not given |
219 | |
e058aeaa |
220 | =item api_password |
8a0402ec |
221 | |
222 | This is your rackspace API Key |
4bd125ea |
223 | |
224 | The rackspace API key, or C<$ENV{RACKSPACE_KEY}> will be used if that is not |
225 | given |
226 | |
dd69a60d |
227 | =item name |
228 | |
229 | The name of your new/existing rackspace server. |
230 | |
231 | =item size |
232 | |
233 | The size ID of the rackspace server you want to create. |
234 | Use 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 | |
255 | The image ID of the rackspace server you want to create. |
256 | Use 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 | |
289 | Oyster only supports Linux images, specifically |
290 | Ubuntu 10.10 (maverick). |
291 | |
292 | =item pub_ssh |
293 | |
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 |
297 | a root password. |
298 | |
299 | =back |
300 | |
301 | =cut |