Skip to content

Commit c6d986a

Browse files
committed
Moved slugbuilder from cookbook to deis. deis/deis#376
1 parent e20373a commit c6d986a

1 file changed

Lines changed: 171 additions & 0 deletions

File tree

bin/slugbuilder-hook.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#!/opt/deis/controller/venv/bin/python
2+
from yaml.error import YAMLError
3+
import argparse
4+
import getpass
5+
import json
6+
import os
7+
import subprocess
8+
import sys
9+
import uuid
10+
import yaml
11+
12+
SLUG_DIR = os.environ['SLUG_DIR']
13+
CONTROLLER_DIR = os.environ['CONTROLLER_DIR']
14+
15+
if SLUG_DIR is None || CONTROLLER_DIR is None:
16+
raise EnvironmentError("Environment variables not set.")
17+
18+
def parse_args():
19+
desc = """
20+
Process a git push by running it through the buildpack process
21+
22+
Note this script must be run as the `git` user.
23+
"""
24+
parser = argparse.ArgumentParser(description=desc,
25+
formatter_class=argparse.RawDescriptionHelpFormatter)
26+
parser.add_argument('src', action='store',
27+
help='path to source repository')
28+
parser.add_argument('user', action='store',
29+
help='name of user who started build',
30+
default='system')
31+
# check execution environment
32+
if getpass.getuser() != 'git':
33+
sys.stderr.write('This script must be run as the "git" user\n')
34+
parser.print_help()
35+
sys.exit(1)
36+
args = parser.parse_args()
37+
args.src = os.path.abspath(args.src)
38+
# store app ID
39+
args.app = '/'.join(args.src.split(os.path.sep)[-1:]).replace('.git', '')
40+
return args
41+
42+
43+
def puts_step(s):
44+
sys.stdout.write("-----> %s\n" % s)
45+
sys.stdout.flush()
46+
47+
48+
def puts_line():
49+
sys.stdout.write("\n")
50+
sys.stdout.flush()
51+
52+
53+
def puts(s):
54+
sys.stdout.write(" " + s)
55+
sys.stdout.flush()
56+
57+
58+
def exit_on_error(error_code, msg):
59+
sys.stderr.write(msg)
60+
sys.stderr.write('\n')
61+
sys.stderr.flush()
62+
sys.exit(error_code)
63+
64+
65+
if __name__ == '__main__':
66+
args = parse_args()
67+
# get sha of master
68+
try:
69+
with open(os.path.join(args.src, 'refs/heads/master')) as f:
70+
sha = f.read().strip('\n')
71+
except IOError:
72+
exit_on_error(2, 'Could not read the repository SHA--is the refspec correct?')
73+
# prepare for buildpack run
74+
slug_path = os.path.join(SLUG_DIR, '{0}-{1}.tar.gz'.format(args.app, sha))
75+
# create cache dir
76+
cache_dir = os.path.join(args.src, 'cache')
77+
if not os.path.exists(cache_dir):
78+
os.mkdir(cache_dir)
79+
# build/compile
80+
cmd = "git archive master | docker run -i -a stdin" \
81+
" -v {cache_dir}:/tmp/cache:rw " \
82+
" -v /opt/deis/build/packs:/tmp/buildpacks:rw " \
83+
" deis/slugbuilder"
84+
cmd = cmd.format(**locals())
85+
p = subprocess.Popen(cmd, cwd=args.src, shell=True, stdout=subprocess.PIPE)
86+
rc = p.wait()
87+
if rc != 0:
88+
exit_on_error(rc, 'Could not execute build, leaving current release in place')
89+
container = p.stdout.read().strip('\n')
90+
# attach to container and wait for build
91+
cmd = 'docker attach {container}'.format(**locals())
92+
p = subprocess.Popen(cmd, cwd=args.src, shell=True)
93+
rc = p.wait()
94+
if rc != 0:
95+
exit_on_error(rc, 'Build failed, leaving current release in place')
96+
# extract slug
97+
cmd = 'docker cp {container}:/tmp/slug.tgz .'.format(**locals())
98+
p = subprocess.Popen(cmd, cwd=args.src, shell=True)
99+
rc = p.wait()
100+
if rc != 0:
101+
exit_on_error(rc, 'Could not extract slug from container')
102+
os.rename(os.path.join(args.src, 'slug.tgz'), slug_path)
103+
# extract procfile
104+
cmd = 'tar xfO {slug_path} ./Procfile'.format(**locals())
105+
p = subprocess.Popen(cmd, cwd=args.src, shell=True, stdout=subprocess.PIPE)
106+
rc = p.wait()
107+
if rc != 0:
108+
exit_on_error(rc, 'Could not extract Procfile from container')
109+
try:
110+
procfile = yaml.safe_load(p.stdout.read())
111+
except YAMLError as e:
112+
exit_on_error(1, 'Invalid Procfile format: {0}'.format(e))
113+
# extract release
114+
cmd = 'tar xfO {slug_path} ./.release'.format(**locals())
115+
p = subprocess.Popen(cmd, cwd=args.src, shell=True, stdout=subprocess.PIPE)
116+
rc = p.wait()
117+
if rc != 0:
118+
exit_on_error(rc, 'Could not extract Release from container')
119+
try:
120+
release = yaml.safe_load(p.stdout.read())
121+
except YAMLError as e:
122+
exit_on_error(1, 'Invalid Release format: {0}'.format(e))
123+
# remove the container
124+
cmd = 'docker rm {container}'.format(**locals())
125+
p = subprocess.Popen(cmd, cwd=args.src, shell=True, stdout=subprocess.PIPE)
126+
rc = p.wait()
127+
if rc != 0:
128+
exit_on_error(rc, 'Could not remove build container')
129+
# calculate checksum
130+
p = subprocess.Popen(['sha256sum', slug_path], stdout=subprocess.PIPE)
131+
rc = p.wait()
132+
if rc != 0:
133+
exit_on_error(rc, 'Could not calculate SHA of slug')
134+
checksum = p.stdout.read().split(' ')[0]
135+
# prepare the push-hook
136+
push = {'username': args.user, 'app': args.app, 'sha': sha, 'checksum': checksum,
137+
'config': release.get('config_vars', {})}
138+
# TODO: why can't we run this with `sudo -u deis`?
139+
output = subprocess.check_output(
140+
['sudo', '-u', 'deis', "{}/bin/pre-push-hook".format(CONTROLLER_DIR)])
141+
data = json.loads(output)
142+
ip = data['domain']
143+
push['url'] = "http://{ip}/slugs/{args.app}-{sha}.tar.gz".format(**locals())
144+
push['procfile'] = procfile
145+
# calculate slug size
146+
push['size'] = os.stat(slug_path).st_size
147+
puts_line()
148+
# run stage
149+
sys.stdout.write(" " + "Launching... ")
150+
sys.stdout.flush()
151+
p = subprocess.Popen(['sudo', '-u', 'deis', "{}/bin/push-hook".format(CONTROLLER_DIR)],
152+
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
153+
stdout, stderr = p.communicate(json.dumps(push))
154+
rc = p.wait()
155+
if rc != 0:
156+
raise RuntimeError('Build error {0}'.format(stderr))
157+
databag = json.loads(stdout)
158+
sys.stdout.write("done, v{}\n".format(databag['release']['version']))
159+
sys.stdout.flush()
160+
puts_line()
161+
puts_step("{args.app} deployed to Deis".format(**locals()))
162+
domains = databag.get('domains', [])
163+
if domains:
164+
for domain in domains:
165+
puts("http://{domain}\n".format(**locals()))
166+
else:
167+
puts('No proxy nodes found for this formation.\n')
168+
puts_line()
169+
puts('To learn more, use `deis help` or visit http://deis.io')
170+
puts_line()
171+
puts_line()

0 commit comments

Comments
 (0)