Merge lp:~james-w/pkgme-service/fab-deploy-tasks into lp:pkgme-service

Proposed by James Westby
Status: Merged
Approved by: Jonathan Lange
Approved revision: 32
Merged at revision: 26
Proposed branch: lp:~james-w/pkgme-service/fab-deploy-tasks
Merge into: lp:pkgme-service
Prerequisite: lp:~james-w/pkgme-service/puppet
Diff against target: 296 lines (+285/-0)
2 files modified
fabtasks/__init__.py (+1/-0)
fabtasks/deploy.py (+284/-0)
To merge this branch: bzr merge lp:~james-w/pkgme-service/fab-deploy-tasks
Reviewer Review Type Date Requested Status
Jonathan Lange (community) Approve
Review via email: mp+89165@code.launchpad.net

This proposal supersedes a proposal from 2012-01-18.

Description of the change

Hi,

This adds some fab tasks for helping with spinning up dev instances.

There's no tests unfortunately.

There's a command to deploy to an existing instance, and one to spin
up an ec2 instance and deploy to it (like Launchpad's "ec2 demo".)

There's also a couple of commands to help with managing the ec2 instances.

The pre-requisites are rather convoluted, but that seems inevitable with ec2
unfortuanately, and they are documented as best as I know how at this time.

Thanks,

James

To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote :

This all looks OK to me.

It's a bit disappointing that there's not a Python library that provides some of the stuff that you hand-roll in the first few functions.

Any ideas on how we can prevent this from sprawling into a project the size of Launchpad's ec2test?

Also, when can we target this at a local virtual machine / chroot / LXC thing?

Revision history for this message
Jonathan Lange (jml) :
review: Approve
Revision history for this message
Jonathan Lange (jml) wrote :

I consistently get this error:

$ fab deploy_to_ec2
Waiting for instance i-3c7d7f5e to start...
Instance started as ec2-23-20-25-159.compute-1.amazonaws.com
Waiting for ssh to come up
[<email address hidden>:22] run: sudo add-apt-repository ppa:canonical-sysadmins/puppet

Fatal error: Timed out trying to connect to ec2-23-20-25-159.compute-1.amazonaws.com

Aborting.

Revision history for this message
Jonathan Lange (jml) :
review: Needs Fixing
Revision history for this message
James Westby (james-w) wrote :

On Thu, 19 Jan 2012 10:13:31 -0000, Jonathan Lange <email address hidden> wrote:
> It's a bit disappointing that there's not a Python library that provides some of the stuff that you hand-roll in the first few functions.

Indeed, I don't think that boto has a location it looks, or knows about
the ubuntu-specific script I use. I don't think it's worth turning it in
to a library at this point though.

> Any ideas on how we can prevent this from sprawling into a project the size of Launchpad's ec2test?

I wouldn't mind something the size of Launchpad's ec2test if it didn't
live in this project, or at least the parts that aren't specific to
pkgme-service.

Probably we'll abandon it all in favour of juju in a few months though.

> Also, when can we target this at a local virtual machine / chroot / LXC thing?

If you have one up the deploy_to_existing should work ok (it could do
with dealing with not having a fresh install a little better if you want
to re-use.)

We could add lxc without too much work I expect, but I haven't played
with lxc directly yet to know what to do.

Thanks,

James

29. By James Westby

Merge puppet improvements.

30. By James Westby

Merge puppet fixes.

31. By James Westby

Set the security group of the created instance so it is accessible.

Revision history for this message
James Westby (james-w) wrote :

On Thu, 19 Jan 2012 10:59:59 -0000, Jonathan Lange <email address hidden> wrote:
> I consistently get this error:
>
> $ fab deploy_to_ec2
> Waiting for instance i-3c7d7f5e to start...
> Instance started as ec2-23-20-25-159.compute-1.amazonaws.com
> Waiting for ssh to come up
> [<email address hidden>:22] run: sudo add-apt-repository ppa:canonical-sysadmins/puppet
>
> Fatal error: Timed out trying to connect to ec2-23-20-25-159.compute-1.amazonaws.com
>
> Aborting.

Hi,

This should now work with the latest changes pushed, as it now creates a
security group for the instance that gives the world access to 22,80,443
(I didn't think that locking it down to specific IP addresses gained us
much at all.)

Thanks,

James

32. By James Westby

Delete the security group before the instance.

Revision history for this message
Jonathan Lange (jml) wrote :

On Thu, Jan 19, 2012 at 3:06 PM, James Westby <email address hidden> wrote:
> On Thu, 19 Jan 2012 10:59:59 -0000, Jonathan Lange <email address hidden> wrote:
>> I consistently get this error:
>>
>> $ fab deploy_to_ec2
>> Waiting for instance i-3c7d7f5e to start...
>> Instance started as ec2-23-20-25-159.compute-1.amazonaws.com
>> Waiting for ssh to come up
>> [<email address hidden>:22] run: sudo add-apt-repository ppa:canonical-sysadmins/puppet
>>
>> Fatal error: Timed out trying to connect to ec2-23-20-25-159.compute-1.amazonaws.com
>>
>> Aborting.
>
> Hi,
>
> This should now work with the latest changes pushed, as it now creates a
> security group for the instance that gives the world access to 22,80,443
> (I didn't think that locking it down to specific IP addresses gained us
> much at all.)
>

Works now. Thanks.

jml

Revision history for this message
Jonathan Lange (jml) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'fabtasks/__init__.py'
--- fabtasks/__init__.py 2011-08-29 23:50:07 +0000
+++ fabtasks/__init__.py 2012-01-19 18:09:25 +0000
@@ -1,2 +1,3 @@
1from .bootstrap import *1from .bootstrap import *
2from .deploy import *
2from .django import *3from .django import *
34
=== added file 'fabtasks/deploy.py'
--- fabtasks/deploy.py 1970-01-01 00:00:00 +0000
+++ fabtasks/deploy.py 2012-01-19 18:09:25 +0000
@@ -0,0 +1,284 @@
1from datetime import datetime
2import os
3import subprocess
4import time
5
6from fabric.api import env, run
7from fabric.utils import abort, puts
8from fabric.operations import open_shell as _open_shell
9
10from boto import ec2
11
12
13RELEASE = 'lucid'
14ARCH = 'amd64'
15FS_TYPE = 'ebs'
16INSTANCE_TYPE = 't1.micro'
17USERNAME = 'ubuntu'
18REGION_NAME = 'us-east-1'
19
20NAME_KEY = "name"
21NAME_PREFIX = "fab/pkgme-service"
22
23ALL_HOSTS = "0.0.0.0/0"
24
25
26def open_shell():
27 """Open a shell on the remote host."""
28 _open_shell()
29
30
31def _get_aws_credentials():
32 """Get the AWS credentials for the user from ~/.ec2/aws_id.
33
34 :returns: a tuple of access key id, secret access key
35 """
36 key_id = None
37 secret_access_key = None
38 with open(os.path.expanduser("~/.ec2/aws_id")) as f:
39 for i, line in enumerate(f.readlines()):
40 if i == 0:
41 key_id = line.strip()
42 if i == 1:
43 secret_access_key = line.strip()
44 if key_id is None:
45 raise AssertionError("Missing key id in ~/.ec2/aws_id")
46 if secret_access_key is None:
47 raise AssertionError("Missing secret access key in ~/.ec2/aws_id")
48 return key_id, secret_access_key
49
50
51def _get_ami_id(region_name):
52 """Get the AMI to use for a particular region.
53
54 This consults the ubuntu-cloudimg-query tool to find out the best
55 AMI to use for a particular region.
56
57 :returns: the ami id (as a string)
58 """
59 proc = subprocess.Popen(
60 ['ubuntu-cloudimg-query', RELEASE, ARCH, FS_TYPE, region_name],
61 stdout=subprocess.PIPE)
62 stdout, _ = proc.communicate()
63 if proc.returncode != 0:
64 raise AssertionError("calling ubuntu-cloudimg-query failed")
65 return stdout.strip()
66
67
68def _get_security_group(conn, name):
69 security_group = conn.create_security_group(name,
70 "Access to the fab pkgme-service ec2 deployment %s" % name)
71 security_group.authorize('tcp', 22, 22, ALL_HOSTS)
72 security_group.authorize('tcp', 80, 80, ALL_HOSTS)
73 security_group.authorize('tcp', 443, 443, ALL_HOSTS)
74 return security_group
75
76
77def _new_ec2_instance(keypair):
78 """Starts a new ec2 instance, giving the specified keypair access.
79
80 This will use the AWS credentials from ~/.ec2/aws_id, and the
81 AMI recommended by ubuntu-cloudimg-query.
82
83 :returns: the boto Instance object of the launched instance. It
84 will be in a state where it can be accessed over ssh.
85 """
86 key_id, secret_access_key = _get_aws_credentials()
87 ami_id = _get_ami_id(REGION_NAME)
88 conn = ec2.connect_to_region(REGION_NAME, aws_access_key_id=key_id,
89 aws_secret_access_key=secret_access_key)
90 image = conn.get_image(ami_id)
91 now = datetime.utcnow()
92 name = "%s/%s" % (NAME_PREFIX, now.isoformat())
93 security_group = _get_security_group(conn, name)
94 reservation = image.run(instance_type=INSTANCE_TYPE, key_name=keypair,
95 security_groups=[security_group.name])
96 instance = reservation.instances[0]
97 puts("Waiting for instance %s to start..." % instance.id)
98 while True:
99 time.sleep(10)
100 instance.update()
101 if instance.state != 'pending':
102 break
103 if instance.state != 'running':
104 raise AssertionError("Instance failed to start")
105 puts("Instance started as %s" % instance.dns_name)
106 puts("Waiting for ssh to come up")
107 # FIXME: use something better than a sleep to determine this.
108 time.sleep(30)
109 instance.add_tag(NAME_KEY, name)
110 return instance
111
112
113def deploy_to_ec2(branch="lp:pkgme-service", use_staging_deps=True, pkgme_branch="lp:pkgme", pkgme_binary_branch="lp:pkgme-binary", keypair='ec2-keypair'):
114 """Deploy to a new ec2 instance.
115
116 This command will spin up an ec2 instance, and deploy to it.
117
118 To run this command you must first set up an ec2 account.
119
120 http://aws.amazon.com/ec2/
121
122 Once you have that create access keys for this to use. Go to
123
124 https://aws-portal.amazon.com/gp/aws/developer/account?ie=UTF8&action=access-key
125
126 and create an access key. Then create a file at ~/.ec2/aws_id and
127 put the "Access Key ID" on the first line, and the "Secret Access Key" on
128 the second line (with nothing else on either line.)
129
130 Next you will need an ec2 keypair. Go to the EC2 console at
131
132 https://console.aws.amazon.com/ec2/home?region=us-east-1
133
134 click on "Key Pairs" and then "Create Key Pair", name the keypair "ec2-keypair".
135 Save the resulting file as
136
137 ~/.ec2/ec2-keypair.pem
138
139 Now you are ready to deploy, so run
140
141 fab deploy_to_ec2:keypair=ec2-keypair -i ~/.ec2/ec2-keypair.pem
142
143 and wait.
144
145 Note that you will be responsible for terminating the instance after
146 use (see the destroy_ec2_instances command.)
147
148 You can also specify the following arguments:
149
150 * branch: the branch to deploy, defaults to lp:pkgme-service. To
151 test some in-progress work push to lp:~you/pkgme-service/something
152 and then specify that as the branch.
153
154 * pkgme_branch: the branch of pkgme to deploy, defaults to
155 lp:pkgme.
156
157 * pkgme_binary_branch: the branch of pkgme-binary to deploy,
158 defaults to lp:pkgme-binary.
159
160 * use_staging_deps: whether to use the staging PPA for
161 dependencies, as well as the production one, defaults to True.
162
163 Arguments are all specified by attaching them to the command name, e.g.
164
165 fab deploy_to_ec2:keypair=ec2-keypair,branch=lp:~me/pkgme-service/something
166 """
167 instance = _new_ec2_instance(keypair)
168 _set_instance_as_host(instance)
169 deploy_to_existing(branch=branch, use_staging_deps=use_staging_deps, pkgme_branch=pkgme_branch, pkgme_binary_branch=pkgme_binary_branch)
170
171
172def _set_instance_as_host(instance):
173 """Set the host to be acted on by fab to this instance."""
174 env.host_string = "%s@%s:22" % (USERNAME, instance.dns_name)
175
176
177def deploy_to_existing(branch="lp:pkgme-service", use_staging_deps=True, pkgme_branch="lp:pkgme", pkgme_binary_branch="lp:pkgme-binary"):
178 """Deploy to an existing instance.
179
180 This command will deploy to an existing instance. Don't use it on an
181 instance you care about as it may overwrite anything.
182
183 To specify the host to deploy to use the -H option of fab:
184
185 fab -H ubuntu@<host> deploy_to_existing
186
187 You can also specify the following arguments:
188
189 * branch: the branch to deploy, defaults to lp:pkgme-service. To
190 test some in-progress work push to lp:~you/pkgme-service/something
191 and then specify that as the branch.
192
193 * pkgme_branch: the branch of pkgme to deploy, defaults to
194 lp:pkgme.
195
196 * pkgme_binary_branch: the branch of pkgme-binary to deploy,
197 defaults to lp:pkgme-binary.
198
199 * use_staging_deps: whether to use the staging PPA for
200 dependencies, as well as the production one, defaults to True.
201
202 Arguments are all specified by attaching them to the command name, e.g.
203
204 fab deploy_to_ec2:keypair=ec2-keypair,branch=lp:~me/pkgme-service/something
205 """
206 # Get the puppet used by IS
207 run('sudo add-apt-repository ppa:canonical-sysadmins/puppet')
208 # Add our dependency PPA
209 run('sudo add-apt-repository ppa:canonical-ca-hackers/production')
210 if use_staging_deps:
211 # Add our dependency staging PPA
212 run('sudo add-apt-repository ppa:canonical-ca-hackers/staging')
213 run('sudo apt-get update -q')
214 # Upgrade the base system in case we are shipping any updates in
215 # our PPAs
216 run('sudo apt-get dist-upgrade -q -y --force-yes')
217 # Avoid a debconf note when installing rabbitmq-server on lucid
218 run('echo "rabbitmq-server rabbitmq-server/upgrade_previous note" | sudo debconf-set-selections')
219 # Install the dependencies needed to get puppet going
220 # TODO: move the rest of the dependencies to puppet
221 run('sudo apt-get install -q -y --force-yes pkgme-service-dependencies bzr apache2 libapache2-mod-wsgi rabbitmq-server postgresql-8.4 puppet')
222 # Grab the branches
223 # TODO: investigate re-using IS' config-manager config
224 run('bzr branch -q %s pkgme-service' % branch)
225 run('bzr branch -q %s pkgme-service/sourcecode/pkgme' % pkgme_branch)
226 run('bzr branch -q %s pkgme-service/sourcecode/pkgme-binary' % pkgme_binary_branch)
227 run('cd pkgme-service/sourcecode/pkgme && python setup.py build')
228 run('cd pkgme-service/sourcecode/pkgme-binary && python setup.py build')
229 # Grab canonical-memento and use it?
230 # Run puppet to set everything else up.
231 run('./pkgme-service/dev_config/apply')
232
233
234def _connect_to_ec2():
235 """Get a connection to ec2, using the credentials on disk."""
236 key_id, secret_access_key = _get_aws_credentials()
237 conn = ec2.connect_to_region(REGION_NAME, aws_access_key_id=key_id,
238 aws_secret_access_key=secret_access_key)
239 return conn
240
241
242def _get_started_ec2_instances(conn):
243 """Get the ec2 instances started from here."""
244 for reservation in conn.get_all_instances():
245 for instance in reservation.instances:
246 if instance.state == 'running':
247 tags = getattr(instance, "tags", {})
248 name = tags.get(NAME_KEY, None)
249 if name and name.startswith(NAME_PREFIX):
250 yield instance
251
252
253def destroy_ec2_instances():
254 """Destroy any ec2 instances created by this deployment."""
255 conn = _connect_to_ec2()
256 for instance in _get_started_ec2_instances(conn):
257 puts("Stopping %s" % instance.id)
258 conn.delete_security_group(name=instance.tags[NAME_KEY])
259 instance.terminate()
260
261
262def last_ec2_launched():
263 """Cause other commands to act on the last ec2 instance launched from here.
264
265 Use this in a list of commands to have the rest of the commands act on the
266 last ec2 instance launched with deploy_to_ec2, e.g.
267
268 fab -i ~/.ec2/ec2-keypair.pem last_ec2_launched -- ls
269
270 This will then run ls on that instance.
271 """
272 def get_date(instance):
273 return instance.tags[NAME_KEY][len(NAME_PREFIX):]
274 last_instance = None
275 conn = _connect_to_ec2()
276 for instance in _get_started_ec2_instances(conn):
277 if last_instance is None:
278 last_instance = instance
279 else:
280 if get_date(instance) > get_date(last_instance):
281 last_instance = instance
282 if last_instance is None:
283 abort("No instances found.")
284 _set_instance_as_host(last_instance)

Subscribers

People subscribed via source and target branches