-
Notifications
You must be signed in to change notification settings - Fork 2
/
fabfile.py
269 lines (204 loc) · 7.99 KB
/
fabfile.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
import os
from os.path import join
from datetime import datetime
import subprocess
import boto
from fabric.api import cd, env, run, task
from fabric.contrib.files import sed
from fabtools import service
from fabtools.python import virtualenv
import hammock
CLOUD_APP = "giraffe"
APP_DIR = "/opt/app"
VIRTUAL_ENV = join(APP_DIR, "env")
SERVICE_NAME = "giraffe"
GIT_REPO = "[email protected]:steder/giraffe.git"
DEV_PHASES = ["beta", "staging", "production"]
DEV_PHASE = "staging"
NEWRELIC_LICENSE = os.environ.get("NEWRELIC_LICENSE_KEY")
AWS_ACCOUNT_NUMBER = os.environ.get("AWS_ACCOUNT_NUMBER")
LOAD_BALANCER_NAME = "{}-staging".format(CLOUD_APP)
SNS_ARN = "arn:aws:sns:us-east-1:{}:{}".format(AWS_ACCOUNT_NUMBER, CLOUD_APP)
SERVER_USER = "ubuntu"
KEY_NAME = os.environ.get("KEY_NAME")
if KEY_NAME:
SSH_KEY_FILE = KEY_NAME if os.path.exists(KEY_NAME) else os.path.expanduser("~/.ssh/{}".format(KEY_NAME))
env.key_filename = SSH_KEY_FILE
ASGARD_HOST = os.environ.get("ASGARD_HOST")
ASGARD_CRED_FILE = "{}/.asgard/credentials".format(os.getenv("HOME"))
ASGARD_CREDENTIALS = (line[:-1] for line in open(ASGARD_CRED_FILE, "r").readlines())
env.forward_agent = True
env.user = SERVER_USER
env.connection_attempts = 5
def aws_hosts(lb=LOAD_BALANCER_NAME):
print "aws_hosts(lb):", lb
# This assumes your bash_profile has
# AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY set.
# Get a list of instance IDs for the ELB.
instances = []
conn = boto.connect_elb()
for elb in conn.get_all_load_balancers(lb):
instances.extend(elb.instances)
# Get the instance IDs and public dns for the reservations.
conn = boto.connect_ec2()
reservations = conn.get_all_instances([i.id for i in instances])
instance_ids = []
for reservation in reservations:
for i in reservation.instances:
instance_ids.append((i.id, i.private_ip_address, i.tags.get('aws:autoscaling:groupName')))
instance_ids.sort() # Put the tuples in some sort of order
# Get the public CNAMES for those instances.
hosts = [node[1] for node in instance_ids if node[1] is not None]
return hosts, instance_ids[0][0], instance_ids[0][2]
def autoscale_group_hosts(group_name):
import boto.ec2
from boto.ec2.autoscale import AutoScaleConnection
ec2 = boto.connect_ec2()
conn = AutoScaleConnection()
groups = conn.get_all_groups(names=[])
groups = [ group for group in groups if group.name.startswith(group_name) ]
instance_ids = []
instances = []
for group in groups:
print "group name:", group.name
instance_ids.extend([i.instance_id for i in group.instances])
instances.extend(ec2.get_only_instances(instance_ids))
return set([i.private_ip_address for i in instances]), instances[0].id, instances[0].tags.get("aws:autoscaling:groupName")
def is_git_tag(ref):
lines = subprocess.check_output(['git', 'show-ref', ref])
return 'refs/tags' in lines
def set_newrelic_license_key():
with cd(APP_DIR):
sed("etc/newrelic.ini", 'not-a-license-key', NEWRELIC_LICENSE,
limit='', use_sudo=False,
backup='.bak', flags='', shell=False)
def update_code(tag):
with cd(APP_DIR):
run('find . -name "*.pyc" -print -delete')
run("git fetch", pty=False)
run("git reset --hard")
run("git checkout " + str(tag))
if not is_git_tag(tag):
run("git pull")
set_newrelic_license_key()
def build_app():
with cd(APP_DIR):
with virtualenv(VIRTUAL_ENV):
run("pip install -U -r requirements.txt")
def restart_app(service_name=SERVICE_NAME):
if service.is_running(SERVICE_NAME):
service.restart(SERVICE_NAME)
else:
service.start(SERVICE_NAME)
def publish_to_sns(message, subject):
print(subject)
print(message)
boto.connect_sns().publish(SNS_ARN, message, subject)
def make_ami(tag):
date = datetime.utcnow().isoformat().replace(":", "").split(".")[0]
image_name = "{}-{}_{}".format(CLOUD_APP, tag, date)
print "Snapshotting instance {} [{}] to image {}".format(INSTANCE_ID, INSTANCE_CLUSTER, image_name)
conn = boto.connect_ec2()
ami = conn.create_image(INSTANCE_ID, image_name, description=date)
message = "AMI:\n {}\nAMI Name:\n {}".format(ami, image_name)
subject = "AMI Created for {}".format(CLOUD_APP)
publish_to_sns(message, subject)
return ami, image_name
def deploy_next_asg(ami):
asgard = hammock.Hammock(ASGARD_HOST, auth=tuple(ASGARD_CREDENTIALS))
cluster = "{}-d0staging".format(CLOUD_APP)
current_asg = asgard("us-east-1").cluster.show(cluster + ".json").GET().json()[0]["autoScalingGroupName"]
param_dict = {"name": cluster, "imageId": ami, "trafficAllowed": "true"}
next_asg = asgard("us-east-1").cluster.createNextGroup.POST(params=param_dict)
if next_asg.status_code == 200:
task = next_asg.url.split("/")[-1]
status = ""
while status not in ["completed", "failed"]:
next_asg_result = asgard("us-east-1").task.show("{}.json".format(task)).GET().json()
status = next_asg_result["status"]
# only delete the asg if we were able to create the new one successfully
delete_asg_log = ""
delete_asg_result = ""
if status == "completed":
delete_asg = asgard("us-east-1").cluster.delete.POST(params={"name": current_asg})
if delete_asg.status_code == 200:
task = delete_asg.url.split("/")[-1]
status = ""
while status not in ["completed", "failed"]:
delete_asg_result = asgard("us-east-1").task.show("{}.json".format(task)).GET().json()
status = delete_asg_result["status"]
if delete_asg_result["log"]:
delete_asg_log = "\n".join(i for i in delete_asg_result["log"])
if next_asg_result["log"]:
next_asg_log = "\n".join(i for i in next_asg_result["log"])
message = next_asg_log + "\n\n" + delete_asg_log
subject = "Asgard results for {}-{}".format(CLOUD_APP, DEV_PHASE)
publish_to_sns(message, subject)
return True
# Set hosts to the defaults (giraffe-staging)
# env.hosts, INSTANCE_ID, INSTANCE_CLUSTER = autoscale_group_hosts(DEV_PHASE)
# print "hosts:", env.hosts
# print "instance_id:", INSTANCE_ID
# print "cluster:", INSTANCE_CLUSTER
def set_environment(env_name="staging"):
print "set_environment:", env_name
global DEV_PHASE, INSTANCE_ID, INSTANCE_CLUSTER
DEV_PHASE = env_name
group = "{}-d0{}".format(CLOUD_APP, env_name)
print 'autoscale group name', group
env.hosts, INSTANCE_ID, INSTANCE_CLUSTER = autoscale_group_hosts(group)
print "hosts:", env.hosts, INSTANCE_ID, INSTANCE_CLUSTER
# Tweak which boxes you run commands on:
#
# fab production hostnames
@task
def beta():
"""
By default we point every fab command at staging so this is
only necessary if you're feeling pedantic.
"""
set_environment(env_name="beta")
@task
def staging():
"""
By default we point every fab command at staging so this is
only necessary if you're feeling pedantic.
"""
set_environment(env_name="staging")
@task
def production():
"""
By default we're pointed at the staging box but if you
really want to run a command on production you can do:
$ fab production <command>
As an example we include 2 fab "targets":
$ fab production hostname
And
$ fab production restart
"""
print "running production task"
set_environment(env_name="production")
@task
def deploy(tag="master"):
update_code(tag)
build_app()
restart_app()
if tag != "master":
ami, image_name = make_ami(tag)
deploy_next_asg(ami)
@task
def inplace_deploy(tag="master"):
update_code(tag)
build_app()
restart_app()
@task
def hostname():
print "running hostname task"
run('hostname')
@task
def restart():
restart_app()
@task
def restart_newrelic():
run("sudo service newrelic-sysmond restart")
set_newrelic_license_key()