Merge ~pappacena/launchpad-buildd:security-manifest into launchpad-buildd:master

Proposed by Thiago F. Pappacena
Status: Merged
Approved by: Thiago F. Pappacena
Approved revision: 8aefc4834005b63dbf9481978cd73e8121613877
Merged at revision: be12be72f1425cf731437f8f7063676fb6fac427
Proposed branch: ~pappacena/launchpad-buildd:security-manifest
Merge into: launchpad-buildd:master
Diff against target: 245 lines (+118/-42)
2 files modified
lpbuildd/target/build_oci.py (+65/-0)
lpbuildd/target/tests/test_build_oci.py (+53/-42)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Tom Wardill (community) Approve
Review via email: mp+392400@code.launchpad.net

Commit message

Including manifest file in resulting OCI image.

To post a comment you must log in.
Revision history for this message
Tom Wardill (twom) wrote :

This looks good to me as is, though I think we should try and work out what we do in the case of not being able to add/generate the manifest, and whether that's enough to fail a build or not.

review: Approve
Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

twom, I agree we should discuss this. The current approach (implemented in follow up MPs) is enclosing the manifest file content generation in try/except blocks, and always generating at least a minimum version of the file.

The steps to copy the file into the image are not protected, but there is a bit less uncertainty in those steps. For that reason, I thought it would be better to fail the build if something goes wrong in the file injection phase. But of course, if we agree I can add the guard there too.

Revision history for this message
Colin Watson (cjwatson) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lpbuildd/target/build_oci.py b/lpbuildd/target/build_oci.py
index af56671..3e4f283 100644
--- a/lpbuildd/target/build_oci.py
+++ b/lpbuildd/target/build_oci.py
@@ -6,6 +6,7 @@ from __future__ import print_function
6__metaclass__ = type6__metaclass__ = type
77
8from collections import OrderedDict8from collections import OrderedDict
9import json
9import logging10import logging
10import os.path11import os.path
11import sys12import sys
@@ -53,6 +54,10 @@ class BuildOCI(SnapBuildProxyOperationMixin, VCSOperationMixin,
53 super(BuildOCI, self).__init__(args, parser)54 super(BuildOCI, self).__init__(args, parser)
54 self.bin = os.path.dirname(sys.argv[0])55 self.bin = os.path.dirname(sys.argv[0])
55 self.buildd_path = os.path.join("/home/buildd", self.args.name)56 self.buildd_path = os.path.join("/home/buildd", self.args.name)
57 # Temp directory where we store files that will be included in the
58 # final filesystem of the image.
59 self.backend_tmp_fs_dir = "/tmp/image-root-dir/"
60 self.security_manifest_target_path = "/.rocks/manifest.json"
5661
57 def _add_docker_engine_proxy_settings(self):62 def _add_docker_engine_proxy_settings(self):
58 """Add systemd file for docker proxy settings."""63 """Add systemd file for docker proxy settings."""
@@ -119,6 +124,65 @@ class BuildOCI(SnapBuildProxyOperationMixin, VCSOperationMixin,
119 env = self.build_proxy_environment(proxy_url=self.args.proxy_url)124 env = self.build_proxy_environment(proxy_url=self.args.proxy_url)
120 self.vcs_fetch(self.args.name, cwd="/home/buildd", env=env)125 self.vcs_fetch(self.args.name, cwd="/home/buildd", env=env)
121126
127 def createSecurityManifest(self):
128 """Generates the security manifest file, returning the tmp file name
129 where it is stored in the backend.
130 """
131 content = {
132 "manifest-version": "0",
133 "name": self.args.name
134 }
135 local_filename = tempfile.mktemp()
136 destination_path = self.security_manifest_target_path.lstrip(
137 os.path.sep)
138 destination = os.path.join(self.backend_tmp_fs_dir, destination_path)
139 with open(local_filename, 'w') as fd:
140 json.dump(content, fd, indent=2)
141 self.backend.copy_in(local_filename, destination)
142 return destination
143
144 def initTempRootDir(self):
145 """Initialize in the backend the directories that will be included in
146 resulting image's filesystem.
147 """
148 security_manifest_dir = os.path.dirname(
149 self.security_manifest_target_path)
150 dir = os.path.join(
151 self.backend_tmp_fs_dir,
152 security_manifest_dir.lstrip(os.path.sep))
153 self.backend.run(["mkdir", "-p", dir])
154
155 def createImageContainer(self):
156 """Creates a container from the built image, so we can play with
157 it's filesystem."""
158 self.run_build_command([
159 "docker", "create", "--name", self.args.name, self.args.name])
160
161 def removeImageContainer(self):
162 self.run_build_command(["docker", "rm", self.args.name])
163
164 def commitImage(self):
165 """Commits the tmp container, overriding the originally built image."""
166 self.run_build_command([
167 "docker", "commit", self.args.name, self.args.name])
168
169 def addFilesToImageContainer(self):
170 """Flushes all files from temp root dir (in the backend) to the
171 resulting image container."""
172 # The extra '.' in the end is important. It indicates to docker that
173 # the directory itself should be copied, instead of the list of
174 # files in the directory. It makes docker keep the paths.
175 src = os.path.join(self.backend_tmp_fs_dir, ".")
176 self.run_build_command(["docker", "cp", src, "%s:/" % self.args.name])
177
178 def addSecurityManifest(self):
179 self.createImageContainer()
180 self.initTempRootDir()
181 self.createSecurityManifest()
182 self.addFilesToImageContainer()
183 self.commitImage()
184 self.removeImageContainer()
185
122 def build(self):186 def build(self):
123 logger.info("Running build phase...")187 logger.info("Running build phase...")
124 args = ["docker", "build", "--no-cache"]188 args = ["docker", "build", "--no-cache"]
@@ -143,6 +207,7 @@ class BuildOCI(SnapBuildProxyOperationMixin, VCSOperationMixin,
143 self._check_path_escape(build_context_path)207 self._check_path_escape(build_context_path)
144 args.append(build_context_path)208 args.append(build_context_path)
145 self.run_build_command(args)209 self.run_build_command(args)
210 self.addSecurityManifest()
146211
147 def run(self):212 def run(self):
148 try:213 try:
diff --git a/lpbuildd/target/tests/test_build_oci.py b/lpbuildd/target/tests/test_build_oci.py
index e58344f..87b37b2 100644
--- a/lpbuildd/target/tests/test_build_oci.py
+++ b/lpbuildd/target/tests/test_build_oci.py
@@ -290,6 +290,23 @@ class TestBuildOCI(TestCase):
290 cwd="/home/buildd/test-image", **env),290 cwd="/home/buildd/test-image", **env),
291 ]))291 ]))
292292
293 def assertRanPostBuildCommands(self, build_oci):
294 self.assertThat(build_oci.backend.run.calls[1:], MatchesListwise([
295 RanBuildCommand(
296 ['docker', 'create', '--name', 'test-image', 'test-image'],
297 cwd="/home/buildd/test-image"),
298 RanCommand(['mkdir', '-p', '/tmp/image-root-dir/.rocks']),
299 RanBuildCommand(
300 ['docker', 'cp', '/tmp/image-root-dir/.', 'test-image:/'],
301 cwd="/home/buildd/test-image"),
302 RanBuildCommand(
303 ['docker', 'commit', 'test-image', 'test-image'],
304 cwd="/home/buildd/test-image"),
305 RanBuildCommand(
306 ['docker', 'rm', 'test-image'],
307 cwd="/home/buildd/test-image"),
308 ]))
309
293 def test_build(self):310 def test_build(self):
294 args = [311 args = [
295 "build-oci",312 "build-oci",
@@ -299,12 +316,11 @@ class TestBuildOCI(TestCase):
299 build_oci = parse_args(args=args).operation316 build_oci = parse_args(args=args).operation
300 build_oci.backend.add_dir('/build/test-directory')317 build_oci.backend.add_dir('/build/test-directory')
301 build_oci.build()318 build_oci.build()
302 self.assertThat(build_oci.backend.run.calls, MatchesListwise([319 self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
303 RanBuildCommand(320 ["docker", "build", "--no-cache", "--tag", "test-image",
304 ["docker", "build", "--no-cache", "--tag", "test-image",321 "/home/buildd/test-image/."],
305 "/home/buildd/test-image/."],322 cwd="/home/buildd/test-image"))
306 cwd="/home/buildd/test-image"),323 self.assertRanPostBuildCommands(build_oci)
307 ]))
308324
309 def test_build_with_file(self):325 def test_build_with_file(self):
310 args = [326 args = [
@@ -316,13 +332,12 @@ class TestBuildOCI(TestCase):
316 build_oci = parse_args(args=args).operation332 build_oci = parse_args(args=args).operation
317 build_oci.backend.add_dir('/build/test-directory')333 build_oci.backend.add_dir('/build/test-directory')
318 build_oci.build()334 build_oci.build()
319 self.assertThat(build_oci.backend.run.calls, MatchesListwise([335 self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
320 RanBuildCommand(336 ["docker", "build", "--no-cache", "--tag", "test-image",
321 ["docker", "build", "--no-cache", "--tag", "test-image",337 "--file", "./build-aux/Dockerfile",
322 "--file", "./build-aux/Dockerfile",338 "/home/buildd/test-image/."],
323 "/home/buildd/test-image/."],339 cwd="/home/buildd/test-image"))
324 cwd="/home/buildd/test-image"),340 self.assertRanPostBuildCommands(build_oci)
325 ]))
326341
327 def test_build_with_path(self):342 def test_build_with_path(self):
328 args = [343 args = [
@@ -334,12 +349,11 @@ class TestBuildOCI(TestCase):
334 build_oci = parse_args(args=args).operation349 build_oci = parse_args(args=args).operation
335 build_oci.backend.add_dir('/build/test-directory')350 build_oci.backend.add_dir('/build/test-directory')
336 build_oci.build()351 build_oci.build()
337 self.assertThat(build_oci.backend.run.calls, MatchesListwise([352 self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
338 RanBuildCommand(353 ["docker", "build", "--no-cache", "--tag", "test-image",
339 ["docker", "build", "--no-cache", "--tag", "test-image",354 "/home/buildd/test-image/a-sub-directory/"],
340 "/home/buildd/test-image/a-sub-directory/"],355 cwd="/home/buildd/test-image"))
341 cwd="/home/buildd/test-image"),356 self.assertRanPostBuildCommands(build_oci)
342 ]))
343357
344 def test_build_with_file_and_path(self):358 def test_build_with_file_and_path(self):
345 args = [359 args = [
@@ -352,13 +366,12 @@ class TestBuildOCI(TestCase):
352 build_oci = parse_args(args=args).operation366 build_oci = parse_args(args=args).operation
353 build_oci.backend.add_dir('/build/test-directory')367 build_oci.backend.add_dir('/build/test-directory')
354 build_oci.build()368 build_oci.build()
355 self.assertThat(build_oci.backend.run.calls, MatchesListwise([369 self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
356 RanBuildCommand(370 ["docker", "build", "--no-cache", "--tag", "test-image",
357 ["docker", "build", "--no-cache", "--tag", "test-image",371 "--file", "test-build-path/build-aux/Dockerfile",
358 "--file", "test-build-path/build-aux/Dockerfile",372 "/home/buildd/test-image/test-build-path"],
359 "/home/buildd/test-image/test-build-path"],373 cwd="/home/buildd/test-image"))
360 cwd="/home/buildd/test-image"),374 self.assertRanPostBuildCommands(build_oci)
361 ]))
362375
363 def test_build_with_args(self):376 def test_build_with_args(self):
364 args = [377 args = [
@@ -372,14 +385,13 @@ class TestBuildOCI(TestCase):
372 build_oci = parse_args(args=args).operation385 build_oci = parse_args(args=args).operation
373 build_oci.backend.add_dir('/build/test-directory')386 build_oci.backend.add_dir('/build/test-directory')
374 build_oci.build()387 build_oci.build()
375 self.assertThat(build_oci.backend.run.calls, MatchesListwise([388 self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
376 RanBuildCommand(389 ["docker", "build", "--no-cache", "--tag", "test-image",
377 ["docker", "build", "--no-cache", "--tag", "test-image",390 "--file", "test-build-path/build-aux/Dockerfile",
378 "--file", "test-build-path/build-aux/Dockerfile",391 "--build-arg=VAR1=xxx", "--build-arg=VAR2=yyy",
379 "--build-arg=VAR1=xxx", "--build-arg=VAR2=yyy",392 "/home/buildd/test-image/test-build-path"],
380 "/home/buildd/test-image/test-build-path"],393 cwd="/home/buildd/test-image"))
381 cwd="/home/buildd/test-image"),394 self.assertRanPostBuildCommands(build_oci)
382 ]))
383395
384 def test_build_proxy(self):396 def test_build_proxy(self):
385 args = [397 args = [
@@ -391,14 +403,13 @@ class TestBuildOCI(TestCase):
391 build_oci = parse_args(args=args).operation403 build_oci = parse_args(args=args).operation
392 build_oci.backend.add_dir('/build/test-directory')404 build_oci.backend.add_dir('/build/test-directory')
393 build_oci.build()405 build_oci.build()
394 self.assertThat(build_oci.backend.run.calls, MatchesListwise([406 self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
395 RanBuildCommand(407 ["docker", "build", "--no-cache",
396 ["docker", "build", "--no-cache",408 "--build-arg", "http_proxy=http://proxy.example:3128/",
397 "--build-arg", "http_proxy=http://proxy.example:3128/",409 "--build-arg", "https_proxy=http://proxy.example:3128/",
398 "--build-arg", "https_proxy=http://proxy.example:3128/",410 "--tag", "test-image", "/home/buildd/test-image/."],
399 "--tag", "test-image", "/home/buildd/test-image/."],411 cwd="/home/buildd/test-image"))
400 cwd="/home/buildd/test-image"),412 self.assertRanPostBuildCommands(build_oci)
401 ]))
402413
403 def test_run_succeeds(self):414 def test_run_succeeds(self):
404 args = [415 args = [

Subscribers

People subscribed via source and target branches