Skip to content

Commit 8cdba92

Browse files
committed
Merge pull request #410 from tombh/376-move-slugbuilder-hook
Tested, works great. Thanks very much, Tom!
2 parents c7a8efb + 0896b9b commit 8cdba92

1 file changed

Lines changed: 168 additions & 0 deletions

File tree

bin/slugbuilder-hook.py

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

0 commit comments

Comments
 (0)