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
1diff --git a/lpbuildd/target/build_oci.py b/lpbuildd/target/build_oci.py
2index af56671..3e4f283 100644
3--- a/lpbuildd/target/build_oci.py
4+++ b/lpbuildd/target/build_oci.py
5@@ -6,6 +6,7 @@ from __future__ import print_function
6 __metaclass__ = type
7
8 from collections import OrderedDict
9+import json
10 import logging
11 import os.path
12 import sys
13@@ -53,6 +54,10 @@ class BuildOCI(SnapBuildProxyOperationMixin, VCSOperationMixin,
14 super(BuildOCI, self).__init__(args, parser)
15 self.bin = os.path.dirname(sys.argv[0])
16 self.buildd_path = os.path.join("/home/buildd", self.args.name)
17+ # Temp directory where we store files that will be included in the
18+ # final filesystem of the image.
19+ self.backend_tmp_fs_dir = "/tmp/image-root-dir/"
20+ self.security_manifest_target_path = "/.rocks/manifest.json"
21
22 def _add_docker_engine_proxy_settings(self):
23 """Add systemd file for docker proxy settings."""
24@@ -119,6 +124,65 @@ class BuildOCI(SnapBuildProxyOperationMixin, VCSOperationMixin,
25 env = self.build_proxy_environment(proxy_url=self.args.proxy_url)
26 self.vcs_fetch(self.args.name, cwd="/home/buildd", env=env)
27
28+ def createSecurityManifest(self):
29+ """Generates the security manifest file, returning the tmp file name
30+ where it is stored in the backend.
31+ """
32+ content = {
33+ "manifest-version": "0",
34+ "name": self.args.name
35+ }
36+ local_filename = tempfile.mktemp()
37+ destination_path = self.security_manifest_target_path.lstrip(
38+ os.path.sep)
39+ destination = os.path.join(self.backend_tmp_fs_dir, destination_path)
40+ with open(local_filename, 'w') as fd:
41+ json.dump(content, fd, indent=2)
42+ self.backend.copy_in(local_filename, destination)
43+ return destination
44+
45+ def initTempRootDir(self):
46+ """Initialize in the backend the directories that will be included in
47+ resulting image's filesystem.
48+ """
49+ security_manifest_dir = os.path.dirname(
50+ self.security_manifest_target_path)
51+ dir = os.path.join(
52+ self.backend_tmp_fs_dir,
53+ security_manifest_dir.lstrip(os.path.sep))
54+ self.backend.run(["mkdir", "-p", dir])
55+
56+ def createImageContainer(self):
57+ """Creates a container from the built image, so we can play with
58+ it's filesystem."""
59+ self.run_build_command([
60+ "docker", "create", "--name", self.args.name, self.args.name])
61+
62+ def removeImageContainer(self):
63+ self.run_build_command(["docker", "rm", self.args.name])
64+
65+ def commitImage(self):
66+ """Commits the tmp container, overriding the originally built image."""
67+ self.run_build_command([
68+ "docker", "commit", self.args.name, self.args.name])
69+
70+ def addFilesToImageContainer(self):
71+ """Flushes all files from temp root dir (in the backend) to the
72+ resulting image container."""
73+ # The extra '.' in the end is important. It indicates to docker that
74+ # the directory itself should be copied, instead of the list of
75+ # files in the directory. It makes docker keep the paths.
76+ src = os.path.join(self.backend_tmp_fs_dir, ".")
77+ self.run_build_command(["docker", "cp", src, "%s:/" % self.args.name])
78+
79+ def addSecurityManifest(self):
80+ self.createImageContainer()
81+ self.initTempRootDir()
82+ self.createSecurityManifest()
83+ self.addFilesToImageContainer()
84+ self.commitImage()
85+ self.removeImageContainer()
86+
87 def build(self):
88 logger.info("Running build phase...")
89 args = ["docker", "build", "--no-cache"]
90@@ -143,6 +207,7 @@ class BuildOCI(SnapBuildProxyOperationMixin, VCSOperationMixin,
91 self._check_path_escape(build_context_path)
92 args.append(build_context_path)
93 self.run_build_command(args)
94+ self.addSecurityManifest()
95
96 def run(self):
97 try:
98diff --git a/lpbuildd/target/tests/test_build_oci.py b/lpbuildd/target/tests/test_build_oci.py
99index e58344f..87b37b2 100644
100--- a/lpbuildd/target/tests/test_build_oci.py
101+++ b/lpbuildd/target/tests/test_build_oci.py
102@@ -290,6 +290,23 @@ class TestBuildOCI(TestCase):
103 cwd="/home/buildd/test-image", **env),
104 ]))
105
106+ def assertRanPostBuildCommands(self, build_oci):
107+ self.assertThat(build_oci.backend.run.calls[1:], MatchesListwise([
108+ RanBuildCommand(
109+ ['docker', 'create', '--name', 'test-image', 'test-image'],
110+ cwd="/home/buildd/test-image"),
111+ RanCommand(['mkdir', '-p', '/tmp/image-root-dir/.rocks']),
112+ RanBuildCommand(
113+ ['docker', 'cp', '/tmp/image-root-dir/.', 'test-image:/'],
114+ cwd="/home/buildd/test-image"),
115+ RanBuildCommand(
116+ ['docker', 'commit', 'test-image', 'test-image'],
117+ cwd="/home/buildd/test-image"),
118+ RanBuildCommand(
119+ ['docker', 'rm', 'test-image'],
120+ cwd="/home/buildd/test-image"),
121+ ]))
122+
123 def test_build(self):
124 args = [
125 "build-oci",
126@@ -299,12 +316,11 @@ class TestBuildOCI(TestCase):
127 build_oci = parse_args(args=args).operation
128 build_oci.backend.add_dir('/build/test-directory')
129 build_oci.build()
130- self.assertThat(build_oci.backend.run.calls, MatchesListwise([
131- RanBuildCommand(
132- ["docker", "build", "--no-cache", "--tag", "test-image",
133- "/home/buildd/test-image/."],
134- cwd="/home/buildd/test-image"),
135- ]))
136+ self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
137+ ["docker", "build", "--no-cache", "--tag", "test-image",
138+ "/home/buildd/test-image/."],
139+ cwd="/home/buildd/test-image"))
140+ self.assertRanPostBuildCommands(build_oci)
141
142 def test_build_with_file(self):
143 args = [
144@@ -316,13 +332,12 @@ class TestBuildOCI(TestCase):
145 build_oci = parse_args(args=args).operation
146 build_oci.backend.add_dir('/build/test-directory')
147 build_oci.build()
148- self.assertThat(build_oci.backend.run.calls, MatchesListwise([
149- RanBuildCommand(
150- ["docker", "build", "--no-cache", "--tag", "test-image",
151- "--file", "./build-aux/Dockerfile",
152- "/home/buildd/test-image/."],
153- cwd="/home/buildd/test-image"),
154- ]))
155+ self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
156+ ["docker", "build", "--no-cache", "--tag", "test-image",
157+ "--file", "./build-aux/Dockerfile",
158+ "/home/buildd/test-image/."],
159+ cwd="/home/buildd/test-image"))
160+ self.assertRanPostBuildCommands(build_oci)
161
162 def test_build_with_path(self):
163 args = [
164@@ -334,12 +349,11 @@ class TestBuildOCI(TestCase):
165 build_oci = parse_args(args=args).operation
166 build_oci.backend.add_dir('/build/test-directory')
167 build_oci.build()
168- self.assertThat(build_oci.backend.run.calls, MatchesListwise([
169- RanBuildCommand(
170- ["docker", "build", "--no-cache", "--tag", "test-image",
171- "/home/buildd/test-image/a-sub-directory/"],
172- cwd="/home/buildd/test-image"),
173- ]))
174+ self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
175+ ["docker", "build", "--no-cache", "--tag", "test-image",
176+ "/home/buildd/test-image/a-sub-directory/"],
177+ cwd="/home/buildd/test-image"))
178+ self.assertRanPostBuildCommands(build_oci)
179
180 def test_build_with_file_and_path(self):
181 args = [
182@@ -352,13 +366,12 @@ class TestBuildOCI(TestCase):
183 build_oci = parse_args(args=args).operation
184 build_oci.backend.add_dir('/build/test-directory')
185 build_oci.build()
186- self.assertThat(build_oci.backend.run.calls, MatchesListwise([
187- RanBuildCommand(
188- ["docker", "build", "--no-cache", "--tag", "test-image",
189- "--file", "test-build-path/build-aux/Dockerfile",
190- "/home/buildd/test-image/test-build-path"],
191- cwd="/home/buildd/test-image"),
192- ]))
193+ self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
194+ ["docker", "build", "--no-cache", "--tag", "test-image",
195+ "--file", "test-build-path/build-aux/Dockerfile",
196+ "/home/buildd/test-image/test-build-path"],
197+ cwd="/home/buildd/test-image"))
198+ self.assertRanPostBuildCommands(build_oci)
199
200 def test_build_with_args(self):
201 args = [
202@@ -372,14 +385,13 @@ class TestBuildOCI(TestCase):
203 build_oci = parse_args(args=args).operation
204 build_oci.backend.add_dir('/build/test-directory')
205 build_oci.build()
206- self.assertThat(build_oci.backend.run.calls, MatchesListwise([
207- RanBuildCommand(
208- ["docker", "build", "--no-cache", "--tag", "test-image",
209- "--file", "test-build-path/build-aux/Dockerfile",
210- "--build-arg=VAR1=xxx", "--build-arg=VAR2=yyy",
211- "/home/buildd/test-image/test-build-path"],
212- cwd="/home/buildd/test-image"),
213- ]))
214+ self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
215+ ["docker", "build", "--no-cache", "--tag", "test-image",
216+ "--file", "test-build-path/build-aux/Dockerfile",
217+ "--build-arg=VAR1=xxx", "--build-arg=VAR2=yyy",
218+ "/home/buildd/test-image/test-build-path"],
219+ cwd="/home/buildd/test-image"))
220+ self.assertRanPostBuildCommands(build_oci)
221
222 def test_build_proxy(self):
223 args = [
224@@ -391,14 +403,13 @@ class TestBuildOCI(TestCase):
225 build_oci = parse_args(args=args).operation
226 build_oci.backend.add_dir('/build/test-directory')
227 build_oci.build()
228- self.assertThat(build_oci.backend.run.calls, MatchesListwise([
229- RanBuildCommand(
230- ["docker", "build", "--no-cache",
231- "--build-arg", "http_proxy=http://proxy.example:3128/",
232- "--build-arg", "https_proxy=http://proxy.example:3128/",
233- "--tag", "test-image", "/home/buildd/test-image/."],
234- cwd="/home/buildd/test-image"),
235- ]))
236+ self.assertThat(build_oci.backend.run.calls[0], RanBuildCommand(
237+ ["docker", "build", "--no-cache",
238+ "--build-arg", "http_proxy=http://proxy.example:3128/",
239+ "--build-arg", "https_proxy=http://proxy.example:3128/",
240+ "--tag", "test-image", "/home/buildd/test-image/."],
241+ cwd="/home/buildd/test-image"))
242+ self.assertRanPostBuildCommands(build_oci)
243
244 def test_run_succeeds(self):
245 args = [

Subscribers

People subscribed via source and target branches