Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add buildx support and remote layer caching #14

Draft
wants to merge 3 commits into
base: chameleoncloud/2023.1
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions kolla/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,12 @@
help='Prefix prepended to image names'),
cfg.StrOpt('repos-yaml', default='',
help='Path to alternative repos.yaml file'),
cfg.StrOpt('engine', default='docker', choices=['docker'],
help='Container engine to build images on.')
cfg.StrOpt('engine', default='docker', choices=['docker','whales'],
help='Container engine to build images on.'),
cfg.StrOpt('cache_from_repo', default=None, help='repository to fetch image cache from'),
cfg.MultiOpt('cache_from_tags', types.String(),
help='additional tags to fetch image cache from, can be specified multiple times.'),
cfg.BoolOpt('push_inline_cache', default=False, help='whether to push layer cahe to repo inline'),
]

_BASE_OPTS = [
Expand Down
13 changes: 13 additions & 0 deletions kolla/engine_adapter/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@
except (ImportError):
LOG.debug("Docker python library was not found")

try:
import python_on_whales
except ImportError:
LOG.debug("python_on_whales library was not found")


class Engine(Enum):

DOCKER = "docker"
WHALES = "whales"


class UnsupportedEngineError(ValueError):
Expand All @@ -38,6 +44,8 @@ def __str__(self):
def getEngineException(conf):
if conf.engine == Engine.DOCKER.value:
return (docker.errors.DockerException)
elif conf.engine == Engine.WHALES.value:
return python_on_whales.exceptions.DockerException
else:
raise UnsupportedEngineError(conf.engine)

Expand All @@ -46,12 +54,17 @@ def getEngineClient(conf):
if conf.engine == Engine.DOCKER.value:
kwargs_env = docker.utils.kwargs_from_env()
return docker.APIClient(version='auto', **kwargs_env)
elif conf.engine == Engine.WHALES.value:
kwargs_env = docker.utils.kwargs_from_env()
return python_on_whales.DockerClient(**kwargs_env)
else:
raise UnsupportedEngineError(conf.engine)


def getEngineVersion(conf):
if conf.engine == Engine.DOCKER.value:
return StrictVersion(docker.__version__)
elif conf.engine == Engine.WHALES.value:
return StrictVersion(python_on_whales.__version__)
else:
raise UnsupportedEngineError(conf.engine)
2 changes: 1 addition & 1 deletion kolla/image/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def run_build():
if conf.debug:
LOG.setLevel(logging.DEBUG)

if conf.engine not in (engine.Engine.DOCKER.value,):
if conf.engine not in (engine.Engine.DOCKER.value, engine.Engine.WHALES.value):
LOG.error(f'Unsupported engine name "{conf.engine}", exiting.')
sys.exit(1)
LOG.info(f'Using engine: {conf.engine}')
Expand Down
20 changes: 17 additions & 3 deletions kolla/image/kolla_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
class Image(object):
def __init__(self, name, canonical_name, path, parent_name='',
status=Status.UNPROCESSED, parent=None,
source=None, logger=None, engine_client=None):
source=None, logger=None, engine_client=None,
cache_from=None):
self.name = name
self.canonical_name = canonical_name
self.path = path
Expand All @@ -57,11 +58,13 @@ def __init__(self, name, canonical_name, path, parent_name='',
self.plugins = []
self.additions = []
self.engine_client = engine_client
self.cache_from = cache_from

def copy(self):
c = Image(self.name, self.canonical_name, self.path,
logger=self.logger, parent_name=self.parent_name,
status=self.status, parent=self.parent)
status=self.status, parent=self.parent,
cache_from=self.cache_from)
if self.source:
c.source = self.source.copy()
if self.children:
Expand Down Expand Up @@ -636,10 +639,21 @@ def process_source_installation(image, section):
else:
parent_name = ''
del match

image_kwargs = {}

if self.conf.cache_from_repo:
cache_from_repo = self.conf.cache_from_repo
cache_from_tags = [self.tag]
cache_from_tags.extend(self.conf.cache_from_tags)
cache_from = [f"{cache_from_repo}/{image_name}:{tag}" for tag in cache_from_tags]
image_kwargs["cache_from"]=cache_from

image = Image(image_name, canonical_name, path,
parent_name=parent_name,
logger=utils.make_a_logger(self.conf, image_name),
engine_client=self.engine_client)
engine_client=self.engine_client,
**image_kwargs)

# NOTE(jeffrey4l): register the opts if the section didn't
# register in the kolla/common/config.py file
Expand Down
88 changes: 60 additions & 28 deletions kolla/image/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,20 @@ def run(self):
self.success = False

def push_image(self, image):
kwargs = dict(stream=True, decode=True)

for response in self.engine_client.push(
image.canonical_name, **kwargs):
if 'stream' in response:
self.logger.info(response['stream'])
elif 'errorDetail' in response:
raise PushError(response['errorDetail']['message'])
if self.conf.engine == engine.Engine.DOCKER.value:
kwargs = dict(stream=True, decode=True)
for response in self.engine_client.push(image.canonical_name, **kwargs):
if "stream" in response:
self.logger.info(response["stream"])
elif "errorDetail" in response:
raise PushError(response["errorDetail"]["message"])

if self.conf.engine == engine.Engine.WHALES.value:
kwargs = dict(quiet=True)
try:
self.engine_client.push(image.canonical_name, **kwargs)
except Exception as ex:
raise PushError(ex)

# Reset any previous errors.
image.status = Status.BUILT
Expand Down Expand Up @@ -367,28 +373,54 @@ def reset_userinfo(tarinfo):
pull = self.conf.pull if image.parent is None else False

buildargs = self.update_buildargs()

kwargs = {}
if self.conf.engine == engine.Engine.DOCKER.value:
kwargs["path"] = image.path
kwargs["tag"] = image.canonical_name
kwargs["nocache"] = not self.conf.cache
kwargs["rm"] = True
kwargs["decode"] = True
kwargs["network_mode"] = self.conf.network_mode
kwargs["pull"] = pull
kwargs["forcerm"] = self.forcerm
kwargs["buildargs"] = buildargs

if self.conf.engine == engine.Engine.WHALES.value:
kwargs["context_path"] = image.path
kwargs["tags"] = [image.canonical_name]
kwargs["pull"] = pull
if buildargs:
kwargs["build_args"] = buildargs
kwargs["stream_logs"] = True
kwargs["cache"] = self.conf.cache

# pull layer cahce from remote registries
if image.cache_from:
kwargs["cache_from"] = [{"type": "registry", "ref": ref} for ref in image.cache_from]

# push inline layer cache
if self.conf.push_inline_cache:
kwargs["cache_to"] = {"type": "inline"}

try:
for stream in \
self.engine_client.build(path=image.path,
tag=image.canonical_name,
nocache=not self.conf.cache,
rm=True,
decode=True,
network_mode=self.conf.network_mode,
pull=pull,
forcerm=self.forcerm,
buildargs=buildargs):
if 'stream' in stream:
for line in stream['stream'].split('\n'):
if line:
self.logger.info('%s', line)
if 'errorDetail' in stream:
image.status = Status.ERROR
self.logger.error('Error\'d with the following message')
for line in stream['errorDetail']['message'].split('\n'):
for stream in self.engine_client.build(**kwargs):
if isinstance(stream, dict):
if "stream" in stream:
for line in stream["stream"].split("\n"):
if line:
self.logger.info("%s", line)
if "errorDetail" in stream:
image.status = Status.ERROR
self.logger.error("Error'd with the following message")
for line in stream["errorDetail"]["message"].split("\n"):
if line:
self.logger.error("%s", line)
return
else:
for line in stream.split("\n"):
if line:
self.logger.error('%s', line)
return
self.logger.info("%s", line)

if image.status != Status.ERROR and self.conf.squash and \
self.conf.engine == engine.Engine.DOCKER.value:
Expand Down