ftl/common/builder.py (98 lines of code) (raw):
# Copyright 2017 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import abc
import datetime
import tarfile
import logging
import httplib2
from containerregistry.client import docker_creds
from containerregistry.client import docker_name
from containerregistry.client.v2_2 import docker_image
from containerregistry.client.v2_2 import docker_session
from containerregistry.client.v2_2 import save
from containerregistry.transport import transport_pool
from ftl.common import cache
from ftl.common import constants
from ftl.common import ftl_util
# Do not Remove. Fix for strptime not being thread safe.
# Initialize datetime in the base class RuntimeBase. The Build calls
# datetime.datetime.strptime in threads.
# See http://bugs.python.org/issue7980
datetime.datetime.strptime('', '')
class Base(object):
"""Base is an abstract base class representing a container builder.
It provides methods for generating runtime layers and an application
layer.
"""
__metaclass__ = abc.ABCMeta # For enforcing that methods are overriden.
def __init__(self, ctx):
self._ctx = ctx
@abc.abstractmethod
def Build(self):
"""Build method encapsulates all layer building and image creation.
"""
class JustApp(Base):
"""JustApp is an implementation of a builder that has logic to build an
application layer.
"""
def __init__(self, ctx):
super(JustApp, self).__init__(ctx)
def Build(self):
"""Override."""
# this can't be abstract as it is instantiated in tests
return
class RuntimeBase(JustApp):
"""RuntimeBase is an abstract base class representing a container builder
for runtime applications with dependencies.
It provides methods for generating appending layers and caching images
"""
def __init__(self, ctx, cache_namespace, args, descriptor_files):
super(RuntimeBase, self).__init__(ctx)
self._cache_namespace = cache_namespace
if args.entrypoint:
args.entrypoint = args.entrypoint.split(" ")
if args.sh_c_prefix:
args.entrypoint = ['bash', '-c', " ".join(args.entrypoint)]
if args.exposed_ports:
args.exposed_ports = args.exposed_ports.split(",")
args.cache_key_version = "%s %s" % (args.cache_key_version,
args.cache_salt)
self._args = args
self._base_name = docker_name.Tag(self._args.base, strict=False)
self._base_creds = docker_creds.DefaultKeychain.Resolve(
self._base_name)
self._target_image = docker_name.Tag(self._args.name, strict=False)
self._target_creds = docker_creds.DefaultKeychain.Resolve(
self._target_image)
self._transport = transport_pool.Http(
httplib2.Http, size=constants.THREADS)
if args.tar_base_image_path:
self._base_image = docker_image.FromTarball(
args.tar_base_image_path)
else:
self._base_image = docker_image.FromRegistry(
self._base_name, self._base_creds, self._transport)
self._base_image.__enter__()
cache_repo = args.cache_repository
if not cache_repo:
cache_repo = self._target_image.as_repository()
if args.ttl:
ttl = args.ttl
else:
ttl = ftl_util.get_ttl(descriptor_files, ctx)
self._cache = cache.Registry(
repo=cache_repo,
namespace=self._cache_namespace,
creds=self._target_creds,
transport=self._transport,
ttl=ttl,
threads=constants.THREADS,
mount=[self._base_name],
use_global=args.global_cache,
export_stats=args.export_cache_stats,
export_location=args.builder_output_path,
should_cache=args.cache,
should_upload=args.upload)
self._descriptor_files = descriptor_files
def Build(self):
return
def StoreImage(self, result_image):
with ftl_util.Timing('Uploading final image'):
if self._args.output_path:
with ftl_util.Timing('Saving tarball image'):
with tarfile.open(
name=self._args.output_path, mode='w') as tar:
save.tarball(self._target_image, result_image, tar)
logging.info('{0} tarball located at {1}'.format(
str(self._target_image), self._args.output_path))
return
if self._args.upload:
with ftl_util.Timing('Pushing image to Docker registry'):
with docker_session.Push(
self._target_image,
self._target_creds,
self._transport,
threads=constants.THREADS,
mount=[self._base_name]) as session:
logging.info('Pushing final image...')
session.upload(result_image)
return