github.com/jiasir/deis@v1.12.2/controller/registry/dockerclient.py (about) 1 # -*- coding: utf-8 -*- 2 """Support the Deis workflow by manipulating and publishing Docker images.""" 3 4 from __future__ import unicode_literals 5 import io 6 import logging 7 8 from django.conf import settings 9 from rest_framework.exceptions import PermissionDenied 10 from simpleflock import SimpleFlock 11 import docker 12 13 logger = logging.getLogger(__name__) 14 15 16 class DockerClient(object): 17 """Use the Docker API to pull, tag, build, and push images to deis-registry.""" 18 19 FLOCKFILE = '/tmp/controller-pull' 20 21 def __init__(self): 22 self.client = docker.Client(version='auto') 23 self.registry = settings.REGISTRY_HOST + ':' + str(settings.REGISTRY_PORT) 24 25 def publish_release(self, source, config, target, deis_registry): 26 """Update a source Docker image with environment config and publish it to deis-registry.""" 27 # get the source repository name and tag 28 src_name, src_tag = docker.utils.parse_repository_tag(source) 29 # get the target repository name and tag 30 name, tag = docker.utils.parse_repository_tag(target) 31 # strip any "http://host.domain:port" prefix from the target repository name, 32 # since we always publish to the Deis registry 33 name = strip_prefix(name) 34 35 # pull the source image from the registry 36 # NOTE: this relies on an implementation detail of deis-builder, that 37 # the image has been uploaded already to deis-registry 38 if deis_registry: 39 repo = "{}/{}".format(self.registry, src_name) 40 else: 41 repo = src_name 42 self.pull(repo, src_tag) 43 44 # tag the image locally without the repository URL 45 image = "{}:{}".format(repo, src_tag) 46 self.tag(image, src_name, tag=src_tag) 47 48 # build a Docker image that adds a "last-mile" layer of environment 49 config.update({'DEIS_APP': name, 'DEIS_RELEASE': tag}) 50 self.build(source, config, name, tag) 51 52 # push the image to deis-registry 53 self.push("{}/{}".format(self.registry, name), tag) 54 55 def build(self, source, config, repo, tag): 56 """Add a "last-mile" layer of environment config to a Docker image for deis-registry.""" 57 check_blacklist(repo) 58 env = ' '.join("{}='{}'".format( 59 k, v.encode('unicode-escape').replace("'", "\\'")) for k, v in config.viewitems()) 60 dockerfile = "FROM {}\nENV {}".format(source, env) 61 f = io.BytesIO(dockerfile.encode('utf-8')) 62 target_repo = "{}/{}:{}".format(self.registry, repo, tag) 63 logger.info("Building Docker image {}".format(target_repo)) 64 with SimpleFlock(self.FLOCKFILE, timeout=1200): 65 stream = self.client.build(fileobj=f, tag=target_repo, stream=True, rm=True) 66 log_output(stream) 67 68 def pull(self, repo, tag): 69 """Pull a Docker image into the local storage graph.""" 70 check_blacklist(repo) 71 logger.info("Pulling Docker image {}:{}".format(repo, tag)) 72 with SimpleFlock(self.FLOCKFILE, timeout=1200): 73 stream = self.client.pull(repo, tag=tag, stream=True, insecure_registry=True) 74 log_output(stream) 75 76 def push(self, repo, tag): 77 """Push a local Docker image to a registry.""" 78 logger.info("Pushing Docker image {}:{}".format(repo, tag)) 79 stream = self.client.push(repo, tag=tag, stream=True, insecure_registry=True) 80 log_output(stream) 81 82 def tag(self, image, repo, tag): 83 """Tag a local Docker image with a new name and tag.""" 84 check_blacklist(repo) 85 logger.info("Tagging Docker image {} as {}:{}".format(image, repo, tag)) 86 if not self.client.tag(image, repo, tag=tag, force=True): 87 raise docker.errors.DockerException("tagging failed") 88 89 90 def check_blacklist(repo): 91 """Check a Docker repository name for collision with deis/* components.""" 92 blacklisted = [ # NOTE: keep this list up to date! 93 'builder', 'cache', 'controller', 'database', 'logger', 'logspout', 94 'publisher', 'registry', 'router', 'store-admin', 'store-daemon', 95 'store-gateway', 'store-metadata', 'store-monitor', 'swarm', 'mesos-master', 96 'mesos-marathon', 'mesos-slave', 'zookeeper', 97 ] 98 if any("deis/{}".format(c) in repo for c in blacklisted): 99 raise PermissionDenied("Repository name {} is not allowed".format(repo)) 100 101 102 def log_output(stream): 103 """Log a stream at DEBUG level, and raise DockerException if it contains "error".""" 104 for chunk in stream: 105 logger.debug(chunk) 106 # error handling requires looking at the response body 107 if '"error"' in chunk.lower(): 108 raise docker.errors.DockerException(chunk) 109 110 111 def strip_prefix(name): 112 """Strip the schema and host:port from a Docker repository name.""" 113 paths = name.split('/') 114 return '/'.join(p for p in paths if p and '.' not in p and ':' not in p) 115 116 117 def publish_release(source, config, target, deis_registry): 118 119 client = DockerClient() 120 return client.publish_release(source, config, target, deis_registry)