Skip to content
This repository has been archived by the owner on May 5, 2023. It is now read-only.

Commit

Permalink
ultra-fast noop-builds by talking directly to the registry
Browse files Browse the repository at this point in the history
  • Loading branch information
graingert committed Jan 13, 2017
1 parent bcf3019 commit a24ed50
Show file tree
Hide file tree
Showing 10 changed files with 375 additions and 61 deletions.
5 changes: 3 additions & 2 deletions CHANGES.rst
@@ -1,8 +1,9 @@
0.6.6 (unreleased)
------------------

- Nothing changed yet.

- Exprimental --registry-login cache flag to skip creation of already built
images and speed up tagging. Feature not subject to semver.
(`Issue #89 <https://github.com/6si/shipwright/pull/89>`_).

0.6.5 (2017-01-08)
------------------
Expand Down
9 changes: 4 additions & 5 deletions setup.py
Expand Up @@ -20,7 +20,7 @@ def read(*names, **kwargs):

setup(
name='shipwright',
version='0.6.6.dev0',
version='0.6.6.dev3',
url='https://github.com/6si/shipwright/',
license='Apache Software License',
author='Scott Robertson',
Expand All @@ -29,6 +29,9 @@ def read(*names, **kwargs):
'docker-py>=1.8.1, <2.0.0',
'GitPython>=2.0.5, <3.0.0',
],
extras_require={
'registry': ['docker-registry-client>=0.5.1, <0.6.0'],
},
author_email='scott@6sense.com',
description=(
'The right way to build, tag and ship shared Docker images.'
Expand All @@ -37,7 +40,6 @@ def read(*names, **kwargs):
packages=find_packages(),
include_package_data=True,
platforms='any',

classifiers=[
'Programming Language :: Python',
'Programming Language :: Python :: 2',
Expand All @@ -53,9 +55,6 @@ def read(*names, **kwargs):
'Operating System :: OS Independent',
'Topic :: Software Development :: Libraries :: Python Modules',
],
extras_require={
'testing': ['nose'],
},
entry_points={
'console_scripts': [
'shipwright = shipwright.cli:main',
Expand Down
26 changes: 7 additions & 19 deletions shipwright/base.py
@@ -1,14 +1,14 @@
from __future__ import absolute_import

from . import build, dependencies, docker, push
from . import build, dependencies


class Shipwright(object):
def __init__(self, source_control, docker_client, tags, pull_cache=False):
def __init__(self, source_control, docker_client, tags, cache):
self.source_control = source_control
self.docker_client = docker_client
self.tags = tags
self._pull_cache = pull_cache
self._cache = cache

def targets(self):
return self.source_control.targets()
Expand All @@ -20,22 +20,16 @@ def build(self, build_targets):

def _build(self, this_ref_str, targets):
client = self.docker_client
pull_cache = self._pull_cache
ref = this_ref_str
for evt in build.do_build(client, ref, targets, pull_cache):
for evt in build.do_build(client, ref, targets, self._cache):
yield evt

# now that we're built and tagged all the images.
# (either during the process of building or forwarding the tag)
# tag all images with the human readable tags.
tags = self.source_control.default_tags() + self.tags + [this_ref_str]
for image in targets:
for tag in tags:
yield docker.tag_image(
self.docker_client,
image,
tag,
)
for evt in self._cache.tag(targets, tags):
yield evt

def images(self, build_targets):
for target in dependencies.eval(build_targets, self.targets()):
Expand All @@ -57,11 +51,5 @@ def push(self, build_targets, no_build=False):

this_ref_str = self.source_control.this_ref_str()
tags = self.source_control.default_tags() + self.tags + [this_ref_str]
names_and_tags = set()
for image in targets:
names_and_tags.add((image.name, image.ref))
for tag in tags:
names_and_tags.add((image.name, tag))

for evt in push.do_push(self.docker_client, sorted(names_and_tags)):
for evt in self._cache.push(targets, tags):
yield evt
37 changes: 13 additions & 24 deletions shipwright/build.py
Expand Up @@ -3,6 +3,7 @@
import os

from . import docker
from .cache import CacheMissException
from .compat import json_loads
from .tar import mkcontext

Expand All @@ -13,7 +14,7 @@ def _merge(d1, d2):
return d


def do_build(client, build_ref, targets, pull_cache):
def do_build(client, build_ref, targets, cache):
"""
Generic function for building multiple images while
notifying a callback function with output produced.
Expand All @@ -39,11 +40,11 @@ def do_build(client, build_ref, targets, pull_cache):
parent_ref = None
if target.parent:
parent_ref = build_index.get(target.parent)
for evt in build(client, parent_ref, target, pull_cache):
for evt in build(client, parent_ref, target, cache):
yield evt


def build(client, parent_ref, image, pull_cache):
def build(client, parent_ref, image, cache):
"""
builds the given image tagged with <build_ref> and ensures that
it depends on it's parent if it's part of this build group (shares
Expand All @@ -57,31 +58,19 @@ def build(client, parent_ref, image, pull_cache):
}

def process_event_(evt):
evt_parsed = json_loads(evt)
return _merge(merge_config, evt_parsed)
return _merge(merge_config, evt)

built_tags = docker.last_built_from_docker(client, image.name)
if image.ref in built_tags:
return

if pull_cache:
pull_evts = client.pull(
repository=image.name,
tag=image.ref,
stream=True,
)

failed = False
for evt in pull_evts:
event = process_event_(evt)
if 'error' in event:
event['warn'] = event['error']
del event['error']
failed = True
yield event

if not failed:
return
try:
for evt in cache.pull_cache(image):
yield process_event_(evt)
except CacheMissException:
pass
else:
return

build_evts = client.build(
fileobj=mkcontext(parent_ref, image.path),
Expand All @@ -93,4 +82,4 @@ def process_event_(evt):
)

for evt in build_evts:
yield process_event_(evt)
yield process_event_(json_loads(evt))
143 changes: 143 additions & 0 deletions shipwright/cache.py
@@ -0,0 +1,143 @@
from __future__ import absolute_import

from requests import exceptions as requests_exceptions

from . import compat, docker, push


class CacheMissException(Exception):
pass


class NoCache(object):
def __init__(self, docker_client):
self.docker_client = docker_client

def pull_cache(self, image):
raise CacheMissException()
yield

def tag(self, targets, tags):
for image in targets:
for tag in tags:
yield docker.tag_image(
self.docker_client,
image,
tag,
)

def push(self, targets, tags):
names_and_tags = set()
for image in targets:
names_and_tags.add((image.name, image.ref))
for tag in tags:
names_and_tags.add((image.name, tag))

for evt in push.do_push(self.docker_client, sorted(names_and_tags)):
yield evt

names_and_tags = set()
names_and_tags.add((image.name, image.ref))
for tag in tags:
names_and_tags.add((image.name, tag))

for evt in push.do_push(self.docker_client, sorted(names_and_tags)):
yield evt


class Cache(NoCache):
def pull_cache(self, image):
client = self.docker_client
pull_evts = client.pull(
repository=image.name,
tag=image.ref,
stream=True,
)

failed = False
for evt in pull_evts:
event = compat.json_loads(evt)
if 'error' in event:
event['warn'] = event['error']
del event['error']
failed = True
yield event

if failed:
raise CacheMissException()


class DirectRegistry(NoCache):
def __init__(self, docker_client, docker_registry):
super(DirectRegistry, self).__init__(docker_client)
self.drc = docker_registry
self._cache = {}

def _get_manifest(self, tag):
name, ref = tag
try:
return self._cache[tag]
except KeyError:
try:
m = self.drc.get_manifest(name, ref)
except requests_exceptions.HTTPError:
return None
else:
self._cache[tag] = m
return m

def _put_manifest(self, tag, manifest):
name, ref = tag
try:
self.drc.put_manifest(name, ref, manifest)
except requests_exceptions.HTTPError as e:
yield {'error': e}
else:
yield {}

def pull_cache(self, image):
tag = (image.name, image.ref)
if self._get_manifest(tag) is None:
raise CacheMissException()
return
yield

def tag(self, targets, tags):
"""
A noop operation because we can't tag locally, if we don't have the
built images
"""
return
yield

def push(self, targets, tags):
to_push = set()
to_alias = []
for image in targets:
tag = (image.name, image.ref)
manifest = self._get_manifest(tag)
if manifest is not None:
to_alias.append((tag, manifest))
else:
to_push.add(tag)

sorted_to_push = sorted(to_push)
for evt in push.do_push(self.docker_client, sorted_to_push):
yield evt

for tag in sorted_to_push:
manifest = self._get_manifest(tag)
to_alias.append((tag, manifest))

for (name, ref), manifest in to_alias:
for tag in tags:
dest = (name, tag)
extra = {
'event': 'alias',
'old_image': name + ':' + ref,
'repository': name,
'tag': tag,
}
for evt in self._put_manifest(dest, manifest):
evt.update(extra)
yield evt

0 comments on commit a24ed50

Please sign in to comment.