Files
tubestation/taskcluster/taskgraph/test/test_util_docker.py
Gregory Szorc 47830cf2f1 Bug 1288567 - Add special Dockerfile syntax to add arbitrary files to context; r=dustin
A limitation of traditional docker build context generation is it
only includes files from the same directory as the Dockerfile. When
repositories have multiple, related Dockerfiles, this limitation
results file duplication or putting all Dockerfiles in the same
directory (which isn't feasible for mozilla-central since they would
need to be in the root directory).

This commit enhances Dockerfiles to allow *any* file from the
repository checkout to be ADDed to the docker build context.

Using the syntax "# %include <path>" you are able to include paths
or directories (relative from the top source directory root) in the
generated context archive. Files add this way are available under the
"topsrcdir/" path and can be ADDed to Docker images.

Since context archive generation is deterministic and the hash of
the resulting archive is used to determine when images need to be
rebuilt, any extra included file that changes will change the hash
of the context archive and force image regeneration.

Basic tests for the new feature have been added.

MozReview-Commit-ID: 4hPZesJuGQV
2016-07-21 16:51:30 -07:00

189 lines
6.9 KiB
Python

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import absolute_import, print_function, unicode_literals
import os
import shutil
import stat
import tarfile
import tempfile
import unittest
from ..util import docker
from mozunit import MockedOpen
MODE_STANDARD = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
class TestDocker(unittest.TestCase):
def test_generate_context_hash(self):
tmpdir = tempfile.mkdtemp()
old_GECKO = docker.GECKO
docker.GECKO = tmpdir
try:
os.makedirs(os.path.join(tmpdir, 'docker', 'my-image'))
with open(os.path.join(tmpdir, 'docker', 'my-image', 'Dockerfile'), "w") as f:
f.write("FROM node\nADD a-file\n")
with open(os.path.join(tmpdir, 'docker', 'my-image', 'a-file'), "w") as f:
f.write("data\n")
self.assertEqual(
docker.generate_context_hash(docker.GECKO, 'docker/my-image', 'my-image'),
'872d76a656f47ea17c043023ecc9ae6a222ba6d2a8df67b75498bba382e4fb07'
)
finally:
docker.GECKO = old_GECKO
shutil.rmtree(tmpdir)
def test_docker_image_explicit_registry(self):
files = {}
files["{}/myimage/REGISTRY".format(docker.DOCKER_ROOT)] = "cool-images"
files["{}/myimage/VERSION".format(docker.DOCKER_ROOT)] = "1.2.3"
with MockedOpen(files):
self.assertEqual(docker.docker_image('myimage'), "cool-images/myimage:1.2.3")
def test_docker_image_default_registry(self):
files = {}
files["{}/REGISTRY".format(docker.DOCKER_ROOT)] = "mozilla"
files["{}/myimage/VERSION".format(docker.DOCKER_ROOT)] = "1.2.3"
with MockedOpen(files):
self.assertEqual(docker.docker_image('myimage'), "mozilla/myimage:1.2.3")
def test_create_context_tar_basic(self):
tmp = tempfile.mkdtemp()
try:
d = os.path.join(tmp, 'test_image')
os.mkdir(d)
with open(os.path.join(d, 'Dockerfile'), 'a'):
pass
os.chmod(os.path.join(d, 'Dockerfile'), MODE_STANDARD)
with open(os.path.join(d, 'extra'), 'a'):
pass
os.chmod(os.path.join(d, 'extra'), MODE_STANDARD)
tp = os.path.join(tmp, 'tar')
h = docker.create_context_tar(tmp, d, tp, 'my_image')
self.assertEqual(h, '2a6d7f1627eba60daf85402418e041d728827d309143c6bc1c6bb3035bde6717')
# File prefix should be "my_image"
with tarfile.open(tp, 'r:gz') as tf:
self.assertEqual(tf.getnames(), [
'my_image/Dockerfile',
'my_image/extra',
])
finally:
shutil.rmtree(tmp)
def test_create_context_topsrcdir_files(self):
tmp = tempfile.mkdtemp()
try:
d = os.path.join(tmp, 'test-image')
os.mkdir(d)
with open(os.path.join(d, 'Dockerfile'), 'wb') as fh:
fh.write(b'# %include extra/file0\n')
os.chmod(os.path.join(d, 'Dockerfile'), MODE_STANDARD)
extra = os.path.join(tmp, 'extra')
os.mkdir(extra)
with open(os.path.join(extra, 'file0'), 'a'):
pass
os.chmod(os.path.join(extra, 'file0'), MODE_STANDARD)
tp = os.path.join(tmp, 'tar')
h = docker.create_context_tar(tmp, d, tp, 'test_image')
self.assertEqual(h, '20faeb7c134f21187b142b5fadba94ae58865dc929c6c293d8cbc0a087269338')
with tarfile.open(tp, 'r:gz') as tf:
self.assertEqual(tf.getnames(), [
'test_image/Dockerfile',
'test_image/topsrcdir/extra/file0',
])
finally:
shutil.rmtree(tmp)
def test_create_context_absolute_path(self):
tmp = tempfile.mkdtemp()
try:
d = os.path.join(tmp, 'test-image')
os.mkdir(d)
# Absolute paths in %include syntax are not allowed.
with open(os.path.join(d, 'Dockerfile'), 'wb') as fh:
fh.write(b'# %include /etc/shadow\n')
with self.assertRaisesRegexp(Exception, 'cannot be absolute'):
docker.create_context_tar(tmp, d, os.path.join(tmp, 'tar'), 'test')
finally:
shutil.rmtree(tmp)
def test_create_context_outside_topsrcdir(self):
tmp = tempfile.mkdtemp()
try:
d = os.path.join(tmp, 'test-image')
os.mkdir(d)
with open(os.path.join(d, 'Dockerfile'), 'wb') as fh:
fh.write(b'# %include foo/../../../etc/shadow\n')
with self.assertRaisesRegexp(Exception, 'path outside topsrcdir'):
docker.create_context_tar(tmp, d, os.path.join(tmp, 'tar'), 'test')
finally:
shutil.rmtree(tmp)
def test_create_context_missing_extra(self):
tmp = tempfile.mkdtemp()
try:
d = os.path.join(tmp, 'test-image')
os.mkdir(d)
with open(os.path.join(d, 'Dockerfile'), 'wb') as fh:
fh.write(b'# %include does/not/exist\n')
with self.assertRaisesRegexp(Exception, 'path does not exist'):
docker.create_context_tar(tmp, d, os.path.join(tmp, 'tar'), 'test')
finally:
shutil.rmtree(tmp)
def test_create_context_extra_directory(self):
tmp = tempfile.mkdtemp()
try:
d = os.path.join(tmp, 'test-image')
os.mkdir(d)
with open(os.path.join(d, 'Dockerfile'), 'wb') as fh:
fh.write(b'# %include extra\n')
fh.write(b'# %include file0\n')
os.chmod(os.path.join(d, 'Dockerfile'), MODE_STANDARD)
extra = os.path.join(tmp, 'extra')
os.mkdir(extra)
for i in range(3):
p = os.path.join(extra, 'file%d' % i)
with open(p, 'wb') as fh:
fh.write(b'file%d' % i)
os.chmod(p, MODE_STANDARD)
with open(os.path.join(tmp, 'file0'), 'a'):
pass
os.chmod(os.path.join(tmp, 'file0'), MODE_STANDARD)
tp = os.path.join(tmp, 'tar')
h = docker.create_context_tar(tmp, d, tp, 'my_image')
self.assertEqual(h, 'e5440513ab46ae4c1d056269e1c6715d5da7d4bd673719d360411e35e5b87205')
with tarfile.open(tp, 'r:gz') as tf:
self.assertEqual(tf.getnames(), [
'my_image/Dockerfile',
'my_image/topsrcdir/extra/file0',
'my_image/topsrcdir/extra/file1',
'my_image/topsrcdir/extra/file2',
'my_image/topsrcdir/file0',
])
finally:
shutil.rmtree(tmp)