-
Notifications
You must be signed in to change notification settings - Fork 112
Expand file tree
/
Copy pathdockerclient.py
More file actions
120 lines (95 loc) · 4.92 KB
/
dockerclient.py
File metadata and controls
120 lines (95 loc) · 4.92 KB
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
# -*- coding: utf-8 -*-
"""Support the Deis workflow by manipulating and publishing Docker images."""
from __future__ import unicode_literals
import io
import logging
from django.conf import settings
from rest_framework.exceptions import PermissionDenied
from simpleflock import SimpleFlock
import docker
logger = logging.getLogger(__name__)
class DockerClient(object):
"""Use the Docker API to pull, tag, build, and push images to deis-registry."""
FLOCKFILE = '/tmp/controller-pull'
def __init__(self):
self.client = docker.Client(version='auto')
self.registry = settings.REGISTRY_HOST + ':' + str(settings.REGISTRY_PORT)
def publish_release(self, source, config, target, deis_registry):
"""Update a source Docker image with environment config and publish it to deis-registry."""
# get the source repository name and tag
src_name, src_tag = docker.utils.parse_repository_tag(source)
# get the target repository name and tag
name, tag = docker.utils.parse_repository_tag(target)
# strip any "http://host.domain:port" prefix from the target repository name,
# since we always publish to the Deis registry
name = strip_prefix(name)
# pull the source image from the registry
# NOTE: this relies on an implementation detail of deis-builder, that
# the image has been uploaded already to deis-registry
if deis_registry:
repo = "{}/{}".format(self.registry, src_name)
else:
repo = src_name
self.pull(repo, src_tag)
# tag the image locally without the repository URL
image = "{}:{}".format(repo, src_tag)
self.tag(image, src_name, tag=src_tag)
# build a Docker image that adds a "last-mile" layer of environment
config.update({'DEIS_APP': name, 'DEIS_RELEASE': tag})
self.build(source, config, name, tag)
# push the image to deis-registry
self.push("{}/{}".format(self.registry, name), tag)
def build(self, source, config, repo, tag):
"""Add a "last-mile" layer of environment config to a Docker image for deis-registry."""
check_blacklist(repo)
env = ' '.join("{}='{}'".format(
k, v.encode('unicode-escape').replace("'", "\\'")) for k, v in config.viewitems())
dockerfile = "FROM {}\nENV {}".format(source, env)
f = io.BytesIO(dockerfile.encode('utf-8'))
target_repo = "{}/{}:{}".format(self.registry, repo, tag)
logger.info("Building Docker image {}".format(target_repo))
with SimpleFlock(self.FLOCKFILE, timeout=1200):
stream = self.client.build(fileobj=f, tag=target_repo, stream=True, rm=True)
log_output(stream)
def pull(self, repo, tag):
"""Pull a Docker image into the local storage graph."""
check_blacklist(repo)
logger.info("Pulling Docker image {}:{}".format(repo, tag))
with SimpleFlock(self.FLOCKFILE, timeout=1200):
stream = self.client.pull(repo, tag=tag, stream=True, insecure_registry=True)
log_output(stream)
def push(self, repo, tag):
"""Push a local Docker image to a registry."""
logger.info("Pushing Docker image {}:{}".format(repo, tag))
stream = self.client.push(repo, tag=tag, stream=True, insecure_registry=True)
log_output(stream)
def tag(self, image, repo, tag):
"""Tag a local Docker image with a new name and tag."""
check_blacklist(repo)
logger.info("Tagging Docker image {} as {}:{}".format(image, repo, tag))
if not self.client.tag(image, repo, tag=tag, force=True):
raise docker.errors.DockerException("tagging failed")
def check_blacklist(repo):
"""Check a Docker repository name for collision with deis/* components."""
blacklisted = [ # NOTE: keep this list up to date!
'builder', 'cache', 'controller', 'database', 'logger', 'logspout',
'publisher', 'registry', 'router', 'store-admin', 'store-daemon',
'store-gateway', 'store-metadata', 'store-monitor', 'swarm', 'mesos-master',
'mesos-marathon', 'mesos-slave', 'zookeeper',
]
if any("deis/{}".format(c) in repo for c in blacklisted):
raise PermissionDenied("Repository name {} is not allowed".format(repo))
def log_output(stream):
"""Log a stream at DEBUG level, and raise DockerException if it contains "error"."""
for chunk in stream:
logger.debug(chunk)
# error handling requires looking at the response body
if '"error"' in chunk.lower():
raise docker.errors.DockerException(chunk)
def strip_prefix(name):
"""Strip the schema and host:port from a Docker repository name."""
paths = name.split('/')
return '/'.join(p for p in paths if p and '.' not in p and ':' not in p)
def publish_release(source, config, target, deis_registry):
client = DockerClient()
return client.publish_release(source, config, target, deis_registry)