Skip to content

Commit 6658dfb

Browse files
Gabriel MonroyMatthew Fisher
authored andcommitted
feat(controller): add deis build support
1 parent 97da47b commit 6658dfb

9 files changed

Lines changed: 179 additions & 24 deletions

File tree

controller/api/models.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ class Meta:
462462
def __str__(self):
463463
return "{0}-v{1}".format(self.app.id, self.version)
464464

465-
def new(self, user, config=None, build=None, summary=None, source_version=None):
465+
def new(self, user, config=None, build=None, summary=None):
466466
"""
467467
Create a new application release using the provided Build and Config
468468
on behalf of a user.
@@ -473,24 +473,19 @@ def new(self, user, config=None, build=None, summary=None, source_version=None):
473473
config = self.config
474474
if not build:
475475
build = self.build
476-
if not source_version:
477-
source_version = 'latest'
478-
else:
479-
source_version = 'v{}'.format(source_version)
480-
# prepare release tag
476+
# construct fully-qualified target image
481477
new_version = self.version + 1
482478
tag = 'v{}'.format(new_version)
483-
image = build.image + ':{tag}'.format(**locals())
479+
release_image = self.app.id + ':{tag}'.format(**locals())
480+
target_image = '{}:{}/{}'.format(
481+
settings.REGISTRY_HOST, settings.REGISTRY_PORT, release_image)
484482
# create new release and auto-increment version
485483
release = Release.objects.create(
486484
owner=user, app=self.app, config=config,
487-
build=build, version=new_version, image=image, summary=summary)
485+
build=build, version=new_version, image=target_image, summary=summary)
488486
# publish release to registry as new docker image
489487
repository_path = self.app.id
490-
publish_release(repository_path,
491-
config.values,
492-
tag,
493-
source_tag=source_version)
488+
publish_release(build.image, config.values, target_image)
494489
return release
495490

496491
def previous(self):
@@ -516,7 +511,9 @@ def save(self, *args, **kwargs):
516511
# compare this build to the previous build
517512
old_build = prev_release.build if prev_release else None
518513
# if the build changed, log it and who pushed it
519-
if self.build != old_build:
514+
if self.version == 1:
515+
self.summary += "{} created initial release".format(self.app.owner)
516+
elif self.build != old_build:
520517
if self.build.sha:
521518
self.summary += "{} deployed {}".format(self.build.owner, self.build.sha[:7])
522519
else:

controller/api/tasks.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@ def run_command(c, command):
7171
if rc != 0:
7272
raise EnvironmentError('Could not pull image: {pull_image}'.format(**locals()))
7373
# run the command
74-
docker_args = ' '.join(['-a', 'stdout', '-a', 'stderr', '--rm', image])
75-
env_args = ' '.join(["-e '{k}={v}'".format(**locals())
76-
for k, v in release.config.values.items()])
77-
command = "docker run {env_args} {docker_args} {command}".format(**locals())
74+
docker_args = ' '.join(['--entrypoint=/bin/bash',
75+
'-a', 'stdout', '-a', 'stderr', '--rm', image])
76+
escaped_command = command.replace("'", "'\\''")
77+
command = r"docker run {docker_args} -c \'{escaped_command}\'".format(**locals())
7878
return c.run(command)
7979
finally:
8080
c.delete()

controller/api/views.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -427,11 +427,10 @@ def rollback(self, request, *args, **kwargs):
427427
request.user,
428428
build=prev.build,
429429
config=prev.config,
430-
summary=summary,
431-
source_version=version)
430+
summary=summary)
432431
app.deploy(new_release)
433-
msg = "Rolled back to v{}".format(version)
434-
return Response(msg, status=status.HTTP_201_CREATED)
432+
response = {'version': new_release.version}
433+
return Response(response, status=status.HTTP_201_CREATED)
435434

436435

437436
class AppContainerViewSet(OwnerViewSet):

controller/bin/boot

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ while grep -q '<no value>' /templates/confd_settings.py; do
4747
sleep $(($ETCD_TTL/2)) # sleep for half the TTL
4848
done
4949

50+
# remove any pre-existing docker.sock
51+
test -e /var/run/docker.sock && rm -f /var/run/docker.sock
52+
53+
# spawn a docker daemon to run builds
54+
docker -d --storage-driver=btrfs --bip=172.20.42.1/16 &
55+
DOCKER_PID=$!
56+
57+
# wait for docker to start
58+
while [[ ! -e /var/run/docker.sock ]]; do
59+
sleep 1
60+
done
61+
5062
cd /app
5163

5264
# run an idempotent database migration

controller/bin/entry

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# DinD: a wrapper script which allows docker to be run inside a docker container.
5+
# Original version by Jerome Petazzoni <jerome@dotcloud.com>
6+
# See the blog post: http://blog.docker.io/2013/09/docker-can-now-run-within-docker/
7+
#
8+
# This script should be executed inside a docker container in privilieged mode
9+
# ('docker run --privileged', introduced in docker 0.6).
10+
11+
# Usage: dind CMD [ARG...]
12+
13+
# apparmor sucks and Docker needs to know that it's in a container (c) @tianon
14+
export container=docker
15+
16+
# First, make sure that cgroups are mounted correctly.
17+
CGROUP=/cgroup
18+
19+
mkdir -p "$CGROUP"
20+
21+
if ! mountpoint -q "$CGROUP"; then
22+
mount -n -t tmpfs -o uid=0,gid=0,mode=0755 cgroup $CGROUP || {
23+
echo >&2 'Could not make a tmpfs mount. Did you use --privileged?'
24+
exit 1
25+
}
26+
fi
27+
28+
if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security; then
29+
mount -t securityfs none /sys/kernel/security || {
30+
echo >&2 'Could not mount /sys/kernel/security.'
31+
echo >&2 'AppArmor detection and -privileged mode might break.'
32+
}
33+
fi
34+
35+
# Mount the cgroup hierarchies exactly as they are in the parent system.
36+
for SUBSYS in $(cut -d: -f2 /proc/1/cgroup); do
37+
mkdir -p "$CGROUP/$SUBSYS"
38+
if ! mountpoint -q $CGROUP/$SUBSYS; then
39+
mount -n -t cgroup -o "$SUBSYS" cgroup "$CGROUP/$SUBSYS"
40+
fi
41+
42+
# The two following sections address a bug which manifests itself
43+
# by a cryptic "lxc-start: no ns_cgroup option specified" when
44+
# trying to start containers withina container.
45+
# The bug seems to appear when the cgroup hierarchies are not
46+
# mounted on the exact same directories in the host, and in the
47+
# container.
48+
49+
# Named, control-less cgroups are mounted with "-o name=foo"
50+
# (and appear as such under /proc/<pid>/cgroup) but are usually
51+
# mounted on a directory named "foo" (without the "name=" prefix).
52+
# Systemd and OpenRC (and possibly others) both create such a
53+
# cgroup. To avoid the aforementioned bug, we symlink "foo" to
54+
# "name=foo". This shouldn't have any adverse effect.
55+
name="${SUBSYS#name=}"
56+
if [ "$name" != "$SUBSYS" ]; then
57+
ln -s "$SUBSYS" "$CGROUP/$name"
58+
fi
59+
60+
# Likewise, on at least one system, it has been reported that
61+
# systemd would mount the CPU and CPU accounting controllers
62+
# (respectively "cpu" and "cpuacct") with "-o cpuacct,cpu"
63+
# but on a directory called "cpu,cpuacct" (note the inversion
64+
# in the order of the groups). This tries to work around it.
65+
if [ "$SUBSYS" = 'cpuacct,cpu' ]; then
66+
ln -s "$SUBSYS" "$CGROUP/cpu,cpuacct"
67+
fi
68+
done
69+
70+
# Note: as I write those lines, the LXC userland tools cannot setup
71+
# a "sub-container" properly if the "devices" cgroup is not in its
72+
# own hierarchy. Let's detect this and issue a warning.
73+
if ! grep -q :devices: /proc/1/cgroup; then
74+
echo >&2 'WARNING: the "devices" cgroup should be in its own hierarchy.'
75+
fi
76+
if ! grep -qw devices /proc/1/cgroup; then
77+
echo >&2 'WARNING: it looks like the "devices" cgroup is not mounted.'
78+
fi
79+
80+
# Mount /tmp
81+
#mount -t tmpfs none /tmp
82+
83+
if [ $# -gt 0 ]; then
84+
exec "$@"
85+
fi
86+
87+
echo >&2 'ERROR: No command specified.'
88+
echo >&2 'You probably want to run hack/make.sh, or maybe a shell?'

controller/deis/settings.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,9 @@
278278

279279
# registry settings
280280
REGISTRY_MODULE = 'registry.mock'
281-
REGISTRY_URL = os.environ.get('DEIS_REGISTRY_URL', None)
281+
REGISTRY_URL = None
282+
REGISTRY_HOST = 'localhost'
283+
REGISTRY_PORT = 5000
282284

283285
# check if we can register users with `deis register`
284286
REGISTRATION_ENABLED = True

controller/registry/docker.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import os.path
2+
import shutil
3+
import subprocess
4+
import tempfile
5+
6+
7+
def publish_release(src_image, config, target_image):
8+
"""
9+
Publish a new release as a Docker image
10+
11+
Given a source image and dictionary of last-mile configuration,
12+
create a target Docker image on the registry.
13+
14+
For example publish_release('registry.local:5000/gabrtv/myapp:<sha>',
15+
{'ENVVAR': 'values'},
16+
'registry.local:5000/gabrtv/myapp:v23',
17+
results in a new Docker image at 'registry.local:5000/gabrtv/myapp:v23' which
18+
contains the new configuration as ENV entries.
19+
"""
20+
# write out dockerfile
21+
dockerfile = _build_dockerfile(src_image, config)
22+
tempdir = tempfile.mkdtemp()
23+
dockerfile_path = os.path.join(tempdir, 'Dockerfile')
24+
with open(dockerfile_path, 'w') as f:
25+
f.write(dockerfile)
26+
try:
27+
# pull the source image to ensure we have latest
28+
p = subprocess.Popen(['docker', 'pull', src_image])
29+
rc = p.wait()
30+
if rc != 0:
31+
raise RuntimeError('Failed to pull source image')
32+
# build the new image with last-mile configuration
33+
p = subprocess.Popen(['docker', 'build', '-t', target_image, tempdir])
34+
rc = p.wait()
35+
if rc != 0:
36+
raise RuntimeError('Failed to build release image')
37+
# push the target image
38+
p = subprocess.Popen(['docker', 'push', target_image])
39+
rc = p.wait()
40+
if rc != 0:
41+
raise RuntimeError('Failed to push release image')
42+
finally:
43+
shutil.rmtree(tempdir)
44+
# cleanup the temporary image
45+
p = subprocess.Popen(['docker', 'rmi', '-f', target_image])
46+
rc = p.wait()
47+
if rc != 0:
48+
print('warning: failed to delete temporary images')
49+
50+
51+
def _build_dockerfile(image, config):
52+
dockerfile = ["FROM "+image]
53+
for k, v in config.items():
54+
dockerfile.append("ENV {} {}".format(k.upper(), v))
55+
return '\n'.join(dockerfile)

controller/scheduler/fleetrun.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ SSH_OPTIONS="-i $FLEETW_KEY -o StrictHostKeyChecking=no -o UserKnownHostsFile=/d
77
[[ $DEBUG ]] && set -x
88

99
# run the fleetctl command remotely
10-
ssh $SSH_OPTIONS core@$FLEETW_HOST $@
10+
ssh $SSH_OPTIONS core@$FLEETW_HOST "$@"

controller/templates/confd_settings.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
BUILDER_KEY = '{{ .deis_controller_builderKey }}'
44

55
# use the private registry module
6-
REGISTRY_MODULE = 'registry.private'
6+
REGISTRY_MODULE = 'registry.docker'
77
REGISTRY_URL = '{{ .deis_registry_protocol }}://{{ .deis_registry_host }}:{{ .deis_registry_port }}' # noqa
8+
REGISTRY_HOST = '{{ .deis_registry_host }}'
9+
REGISTRY_PORT = '{{ .deis_registry_port }}'
810

911
# default to sqlite3, but allow postgresql config through envvars
1012
DATABASES = {

0 commit comments

Comments
 (0)