Merge ~pelpsi/lpci:jobs-cannot-run-as-root into lpci:main
- Git
- lp:~pelpsi/lpci
- jobs-cannot-run-as-root
- Merge into main
Proposed by
Simone Pelosi
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Simone Pelosi | ||||
Approved revision: | dbc25c6b9140f0113264f2d18fe5bd4c0695695f | ||||
Merge reported by: | Simone Pelosi | ||||
Merged at revision: | dbc25c6b9140f0113264f2d18fe5bd4c0695695f | ||||
Proposed branch: | ~pelpsi/lpci:jobs-cannot-run-as-root | ||||
Merge into: | lpci:main | ||||
Diff against target: |
481 lines (+308/-3) 11 files modified
NEWS.rst (+2/-0) docs/configuration.rst (+6/-0) lpci/commands/run.py (+11/-0) lpci/commands/tests/test_run.py (+49/-0) lpci/config.py (+11/-0) lpci/env.py (+4/-0) lpci/providers/_base.py (+1/-0) lpci/providers/_lxd.py (+34/-0) lpci/providers/tests/test_lxd.py (+144/-3) lpci/tests/test_config.py (+43/-0) lpci/tests/test_env.py (+3/-0) |
||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email:
|
Commit message
Add a root flag
New root flag to choose whether to run commands as root or as default user (_lpci).
Lpci runs jobs as root inside the container.
However, there are some reasonable jobs that call code that refuses to run as root.
LP: #1982954
Description of the change
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Simone Pelosi (pelpsi) wrote : | # |
> Thanks! I have a collection of nitpicks, but they're mostly just that.
Thank you Colin! I make the necessary fixes and then I merge it.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/NEWS.rst b/NEWS.rst | |||
2 | index ba2012b..45f0dde 100644 | |||
3 | --- a/NEWS.rst | |||
4 | +++ b/NEWS.rst | |||
5 | @@ -9,6 +9,8 @@ Version history | |||
6 | 9 | - Fix ``policy-rc.d`` issue preventing services | 9 | - Fix ``policy-rc.d`` issue preventing services |
7 | 10 | from running into the container. | 10 | from running into the container. |
8 | 11 | 11 | ||
9 | 12 | - Add a ``root`` flag. | ||
10 | 13 | |||
11 | 12 | 0.1.0 (2023-04-18) | 14 | 0.1.0 (2023-04-18) |
12 | 13 | ================== | 15 | ================== |
13 | 14 | 16 | ||
14 | diff --git a/docs/configuration.rst b/docs/configuration.rst | |||
15 | index 65b0b5d..3a71bcd 100644 | |||
16 | --- a/docs/configuration.rst | |||
17 | +++ b/docs/configuration.rst | |||
18 | @@ -72,6 +72,12 @@ Job definitions | |||
19 | 72 | ``plugin`` (optional) | 72 | ``plugin`` (optional) |
20 | 73 | A plugin which will be used for this job. See :doc:`../plugins` | 73 | A plugin which will be used for this job. See :doc:`../plugins` |
21 | 74 | 74 | ||
22 | 75 | ``root`` (optional) | ||
23 | 76 | If ``true``, run shell commands declared by ``run-before``, | ||
24 | 77 | ``run``, and ``run-after`` as root; | ||
25 | 78 | if ``false``, run them as a non-root user (``_lpci``). | ||
26 | 79 | Default value: ``false``. | ||
27 | 80 | |||
28 | 75 | ``run-before`` (optional) | 81 | ``run-before`` (optional) |
29 | 76 | A string (possibly multi-line) containing shell commands to run for this | 82 | A string (possibly multi-line) containing shell commands to run for this |
30 | 77 | job prior to the main ``run`` section. | 83 | job prior to the main ``run`` section. |
31 | diff --git a/lpci/commands/run.py b/lpci/commands/run.py | |||
32 | index d6c65b2..c543ed1 100644 | |||
33 | --- a/lpci/commands/run.py | |||
34 | +++ b/lpci/commands/run.py | |||
35 | @@ -433,8 +433,12 @@ def _run_instance_command( | |||
36 | 433 | host_architecture: str, | 433 | host_architecture: str, |
37 | 434 | remote_cwd: Path, | 434 | remote_cwd: Path, |
38 | 435 | environment: Optional[Dict[str, Optional[str]]], | 435 | environment: Optional[Dict[str, Optional[str]]], |
39 | 436 | root: bool = True, | ||
40 | 436 | ) -> None: | 437 | ) -> None: |
41 | 437 | full_run_cmd = ["bash", "--noprofile", "--norc", "-ec", command] | 438 | full_run_cmd = ["bash", "--noprofile", "--norc", "-ec", command] |
42 | 439 | if not root: | ||
43 | 440 | full_run_cmd[:0] = ["runuser", "-u", env.get_non_root_user(), "--"] | ||
44 | 441 | |||
45 | 438 | emit.progress("Running command for the job...") | 442 | emit.progress("Running command for the job...") |
46 | 439 | original_mode = emit.get_mode() | 443 | original_mode = emit.get_mode() |
47 | 440 | if original_mode == EmitterMode.BRIEF: | 444 | if original_mode == EmitterMode.BRIEF: |
48 | @@ -475,6 +479,11 @@ def _run_job( | |||
49 | 475 | # XXX jugmac00 2022-04-27: we should create a configuration object to be | 479 | # XXX jugmac00 2022-04-27: we should create a configuration object to be |
50 | 476 | # passed in and not so many arguments | 480 | # passed in and not so many arguments |
51 | 477 | job = config.jobs[job_name][job_index] | 481 | job = config.jobs[job_name][job_index] |
52 | 482 | root = job.root | ||
53 | 483 | |||
54 | 484 | # workaround necessary to please coverage | ||
55 | 485 | assert isinstance(root, bool) | ||
56 | 486 | |||
57 | 478 | host_architecture = get_host_architecture() | 487 | host_architecture = get_host_architecture() |
58 | 479 | if host_architecture not in job.architectures: | 488 | if host_architecture not in job.architectures: |
59 | 480 | return | 489 | return |
60 | @@ -540,6 +549,7 @@ def _run_job( | |||
61 | 540 | series=job.series, | 549 | series=job.series, |
62 | 541 | architecture=host_architecture, | 550 | architecture=host_architecture, |
63 | 542 | gpu_nvidia=gpu_nvidia, | 551 | gpu_nvidia=gpu_nvidia, |
64 | 552 | root=root, | ||
65 | 543 | ) as instance: | 553 | ) as instance: |
66 | 544 | snaps = list(itertools.chain(*pm.hook.lpci_install_snaps())) | 554 | snaps = list(itertools.chain(*pm.hook.lpci_install_snaps())) |
67 | 545 | for snap in snaps: | 555 | for snap in snaps: |
68 | @@ -583,6 +593,7 @@ def _run_job( | |||
69 | 583 | host_architecture=host_architecture, | 593 | host_architecture=host_architecture, |
70 | 584 | remote_cwd=remote_cwd, | 594 | remote_cwd=remote_cwd, |
71 | 585 | environment=environment, | 595 | environment=environment, |
72 | 596 | root=root, | ||
73 | 586 | ) | 597 | ) |
74 | 587 | if config.license: | 598 | if config.license: |
75 | 588 | if not job.output: | 599 | if not job.output: |
76 | diff --git a/lpci/commands/tests/test_run.py b/lpci/commands/tests/test_run.py | |||
77 | index 07ae61b..c47f1e7 100644 | |||
78 | --- a/lpci/commands/tests/test_run.py | |||
79 | +++ b/lpci/commands/tests/test_run.py | |||
80 | @@ -3537,6 +3537,55 @@ class TestRun(RunBaseTestCase): | |||
81 | 3537 | remote="test-remote", | 3537 | remote="test-remote", |
82 | 3538 | ) | 3538 | ) |
83 | 3539 | 3539 | ||
84 | 3540 | @patch("lpci.commands.run.get_provider") | ||
85 | 3541 | @patch("lpci.commands.run.get_host_architecture", return_value="amd64") | ||
86 | 3542 | def test_root_field(self, mock_get_host_architecture, mock_get_provider): | ||
87 | 3543 | launcher = Mock(spec=launch) | ||
88 | 3544 | provider = makeLXDProvider(lxd_launcher=launcher) | ||
89 | 3545 | mock_get_provider.return_value = provider | ||
90 | 3546 | execute_run = launcher.return_value.execute_run | ||
91 | 3547 | execute_run.return_value = subprocess.CompletedProcess("_lpci", 0) | ||
92 | 3548 | config = dedent( | ||
93 | 3549 | """ | ||
94 | 3550 | pipeline: | ||
95 | 3551 | - build | ||
96 | 3552 | |||
97 | 3553 | jobs: | ||
98 | 3554 | build: | ||
99 | 3555 | root: False | ||
100 | 3556 | series: focal | ||
101 | 3557 | architectures: amd64 | ||
102 | 3558 | run: whoami | ||
103 | 3559 | """ | ||
104 | 3560 | ) | ||
105 | 3561 | Path(".launchpad.yaml").write_text(config) | ||
106 | 3562 | |||
107 | 3563 | result = self.run_command("run") | ||
108 | 3564 | |||
109 | 3565 | self.assertEqual(0, result.exit_code) | ||
110 | 3566 | self.assertEqual( | ||
111 | 3567 | [ | ||
112 | 3568 | call( | ||
113 | 3569 | [ | ||
114 | 3570 | "runuser", | ||
115 | 3571 | "-u", | ||
116 | 3572 | "_lpci", | ||
117 | 3573 | "--", | ||
118 | 3574 | "bash", | ||
119 | 3575 | "--noprofile", | ||
120 | 3576 | "--norc", | ||
121 | 3577 | "-ec", | ||
122 | 3578 | "whoami", | ||
123 | 3579 | ], | ||
124 | 3580 | cwd=Path("/build/lpci/project"), | ||
125 | 3581 | env={}, | ||
126 | 3582 | stdout=ANY, | ||
127 | 3583 | stderr=ANY, | ||
128 | 3584 | ) | ||
129 | 3585 | ], | ||
130 | 3586 | execute_run.call_args_list, | ||
131 | 3587 | ) | ||
132 | 3588 | |||
133 | 3540 | 3589 | ||
134 | 3541 | class TestRunOne(RunBaseTestCase): | 3590 | class TestRunOne(RunBaseTestCase): |
135 | 3542 | def test_config_file_not_under_project_directory(self): | 3591 | def test_config_file_not_under_project_directory(self): |
136 | diff --git a/lpci/config.py b/lpci/config.py | |||
137 | index 8e3f77a..4af17e5 100644 | |||
138 | --- a/lpci/config.py | |||
139 | +++ b/lpci/config.py | |||
140 | @@ -290,6 +290,7 @@ class Job(ModelConfigDefaults): | |||
141 | 290 | package_repositories: List[PackageRepository] = [] | 290 | package_repositories: List[PackageRepository] = [] |
142 | 291 | plugin: Optional[StrictStr] | 291 | plugin: Optional[StrictStr] |
143 | 292 | plugin_config: Optional[BaseConfig] | 292 | plugin_config: Optional[BaseConfig] |
144 | 293 | root: Optional[bool] = True | ||
145 | 293 | 294 | ||
146 | 294 | @pydantic.validator("architectures", pre=True) | 295 | @pydantic.validator("architectures", pre=True) |
147 | 295 | def validate_architectures( | 296 | def validate_architectures( |
148 | @@ -299,6 +300,16 @@ class Job(ModelConfigDefaults): | |||
149 | 299 | v = [v] | 300 | v = [v] |
150 | 300 | return v | 301 | return v |
151 | 301 | 302 | ||
152 | 303 | @pydantic.validator("root", pre=True) | ||
153 | 304 | def validate_root(cls, v: Any) -> Any: | ||
154 | 305 | if type(v) is not bool or v is None: | ||
155 | 306 | raise ValueError( | ||
156 | 307 | "You configured `root` parameter, " | ||
157 | 308 | + "but you did not specify a valid value. " | ||
158 | 309 | + "Valid values would either be `true` or `false`." | ||
159 | 310 | ) | ||
160 | 311 | return v | ||
161 | 312 | |||
162 | 302 | @pydantic.validator("snaps", pre=True) | 313 | @pydantic.validator("snaps", pre=True) |
163 | 303 | def validate_snaps(cls, v: List[Any]) -> Any: | 314 | def validate_snaps(cls, v: List[Any]) -> Any: |
164 | 304 | clean_values = [] | 315 | clean_values = [] |
165 | diff --git a/lpci/env.py b/lpci/env.py | |||
166 | index cfc7331..0aa9b96 100644 | |||
167 | --- a/lpci/env.py | |||
168 | +++ b/lpci/env.py | |||
169 | @@ -6,6 +6,10 @@ | |||
170 | 6 | from pathlib import Path | 6 | from pathlib import Path |
171 | 7 | 7 | ||
172 | 8 | 8 | ||
173 | 9 | def get_non_root_user() -> str: | ||
174 | 10 | return "_lpci" | ||
175 | 11 | |||
176 | 12 | |||
177 | 9 | def get_managed_environment_home_path() -> Path: | 13 | def get_managed_environment_home_path() -> Path: |
178 | 10 | """Path for home when running in managed environment.""" | 14 | """Path for home when running in managed environment.""" |
179 | 11 | return Path("/root") | 15 | return Path("/root") |
180 | diff --git a/lpci/providers/_base.py b/lpci/providers/_base.py | |||
181 | index 15d14f7..388891a 100644 | |||
182 | --- a/lpci/providers/_base.py | |||
183 | +++ b/lpci/providers/_base.py | |||
184 | @@ -109,6 +109,7 @@ class Provider(ABC): | |||
185 | 109 | series: str, | 109 | series: str, |
186 | 110 | architecture: str, | 110 | architecture: str, |
187 | 111 | gpu_nvidia: bool = False, | 111 | gpu_nvidia: bool = False, |
188 | 112 | root: bool = False, | ||
189 | 112 | ) -> Generator[lxd.LXDInstance, None, None]: | 113 | ) -> Generator[lxd.LXDInstance, None, None]: |
190 | 113 | """Launch environment for specified series and architecture. | 114 | """Launch environment for specified series and architecture. |
191 | 114 | 115 | ||
192 | diff --git a/lpci/providers/_lxd.py b/lpci/providers/_lxd.py | |||
193 | index 1a20c92..c673502 100644 | |||
194 | --- a/lpci/providers/_lxd.py | |||
195 | +++ b/lpci/providers/_lxd.py | |||
196 | @@ -20,6 +20,7 @@ from pydantic import StrictStr | |||
197 | 20 | from lpci.env import ( | 20 | from lpci.env import ( |
198 | 21 | get_managed_environment_home_path, | 21 | get_managed_environment_home_path, |
199 | 22 | get_managed_environment_project_path, | 22 | get_managed_environment_project_path, |
200 | 23 | get_non_root_user, | ||
201 | 23 | ) | 24 | ) |
202 | 24 | from lpci.errors import CommandError | 25 | from lpci.errors import CommandError |
203 | 25 | from lpci.providers._base import Provider, sanitize_lxd_instance_name | 26 | from lpci.providers._base import Provider, sanitize_lxd_instance_name |
204 | @@ -238,6 +239,34 @@ class LXDProvider(Provider): | |||
205 | 238 | **kwargs, | 239 | **kwargs, |
206 | 239 | ) | 240 | ) |
207 | 240 | 241 | ||
208 | 242 | def _set_up_non_root_user( | ||
209 | 243 | self, | ||
210 | 244 | instance: lxd.LXDInstance, | ||
211 | 245 | instance_name: str, | ||
212 | 246 | ) -> None: | ||
213 | 247 | |||
214 | 248 | default_user = get_non_root_user() | ||
215 | 249 | create_cmd = "getent passwd " + default_user + " >/dev/null" | ||
216 | 250 | create_cmd += " || useradd -m -U " + default_user | ||
217 | 251 | |||
218 | 252 | # Create default user if doesn't exist. | ||
219 | 253 | self._internal_execute_run( | ||
220 | 254 | instance, instance_name, ["sh", "-c", create_cmd], check=True | ||
221 | 255 | ) | ||
222 | 256 | |||
223 | 257 | # Give ownership of the project directory to _lpci | ||
224 | 258 | self._internal_execute_run( | ||
225 | 259 | instance, | ||
226 | 260 | instance_name, | ||
227 | 261 | [ | ||
228 | 262 | "chown", | ||
229 | 263 | "-R", | ||
230 | 264 | f"{default_user}:{default_user}", | ||
231 | 265 | str(get_managed_environment_project_path()), | ||
232 | 266 | ], | ||
233 | 267 | check=True, | ||
234 | 268 | ) | ||
235 | 269 | |||
236 | 241 | @contextmanager | 270 | @contextmanager |
237 | 242 | def launched_environment( | 271 | def launched_environment( |
238 | 243 | self, | 272 | self, |
239 | @@ -247,6 +276,7 @@ class LXDProvider(Provider): | |||
240 | 247 | series: str, | 276 | series: str, |
241 | 248 | architecture: str, | 277 | architecture: str, |
242 | 249 | gpu_nvidia: bool = False, | 278 | gpu_nvidia: bool = False, |
243 | 279 | root: bool = True, | ||
244 | 250 | ) -> Generator[lxd.LXDInstance, None, None]: | 280 | ) -> Generator[lxd.LXDInstance, None, None]: |
245 | 251 | """Launch environment for specified series and architecture. | 281 | """Launch environment for specified series and architecture. |
246 | 252 | 282 | ||
247 | @@ -354,6 +384,10 @@ class LXDProvider(Provider): | |||
248 | 354 | ["rm", "-f", "/usr/local/sbin/policy-rc.d"], | 384 | ["rm", "-f", "/usr/local/sbin/policy-rc.d"], |
249 | 355 | check=True, | 385 | check=True, |
250 | 356 | ) | 386 | ) |
251 | 387 | if not root: | ||
252 | 388 | self._set_up_non_root_user( | ||
253 | 389 | instance=instance, instance_name=instance_name | ||
254 | 390 | ) | ||
255 | 357 | except subprocess.CalledProcessError as error: | 391 | except subprocess.CalledProcessError as error: |
256 | 358 | raise CommandError(str(error)) from error | 392 | raise CommandError(str(error)) from error |
257 | 359 | finally: | 393 | finally: |
258 | diff --git a/lpci/providers/tests/test_lxd.py b/lpci/providers/tests/test_lxd.py | |||
259 | index 0a4f803..0e9b439 100644 | |||
260 | --- a/lpci/providers/tests/test_lxd.py | |||
261 | +++ b/lpci/providers/tests/test_lxd.py | |||
262 | @@ -500,10 +500,151 @@ class TestLXDProvider(TestCase): | |||
263 | 500 | ), | 500 | ), |
264 | 501 | call().lxc.exec( | 501 | call().lxc.exec( |
265 | 502 | instance_name=expected_instance_name, | 502 | instance_name=expected_instance_name, |
266 | 503 | command=["rm", "-f", "/usr/local/sbin/policy-rc.d"], | ||
267 | 504 | project="test-project", | ||
268 | 505 | remote="test-remote", | ||
269 | 506 | runner=subprocess.run, | ||
270 | 507 | check=True, | ||
271 | 508 | ), | ||
272 | 509 | call().unmount(target=Path("/root/tmp-project")), | ||
273 | 510 | ], | ||
274 | 511 | mock_launcher.mock_calls, | ||
275 | 512 | ) | ||
276 | 513 | mock_launcher.reset_mock() | ||
277 | 514 | |||
278 | 515 | self.assertEqual( | ||
279 | 516 | [ | ||
280 | 517 | call().lxc.exec( | ||
281 | 518 | instance_name=expected_instance_name, | ||
282 | 519 | command=["rm", "-rf", "/build/lpci/project"], | ||
283 | 520 | project="test-project", | ||
284 | 521 | remote="test-remote", | ||
285 | 522 | runner=subprocess.run, | ||
286 | 523 | check=True, | ||
287 | 524 | ), | ||
288 | 525 | call().unmount_all(), | ||
289 | 526 | call().stop(), | ||
290 | 527 | ], | ||
291 | 528 | mock_launcher.mock_calls, | ||
292 | 529 | ) | ||
293 | 530 | |||
294 | 531 | @patch("os.environ", {"PATH": "not-using-host-path"}) | ||
295 | 532 | def test_launched_environment_default_user(self): | ||
296 | 533 | expected_instance_name = "lpci-my-project-12345-focal-amd64" | ||
297 | 534 | mock_lxc = Mock(spec=LXC) | ||
298 | 535 | mock_lxc.profile_show.return_value = { | ||
299 | 536 | "config": {"sentinel": "true"}, | ||
300 | 537 | "devices": {"eth0": {}}, | ||
301 | 538 | } | ||
302 | 539 | mock_lxc.project_list.return_value = [] | ||
303 | 540 | mock_lxc.remote_list.return_value = {} | ||
304 | 541 | mock_launcher = Mock(spec=launch) | ||
305 | 542 | provider = makeLXDProvider(lxc=mock_lxc, lxd_launcher=mock_launcher) | ||
306 | 543 | |||
307 | 544 | with provider.launched_environment( | ||
308 | 545 | project_name="my-project", | ||
309 | 546 | project_path=self.mock_path, | ||
310 | 547 | series="focal", | ||
311 | 548 | architecture="amd64", | ||
312 | 549 | root=False, | ||
313 | 550 | ) as instance: | ||
314 | 551 | self.assertIsNotNone(instance) | ||
315 | 552 | mock_lxc.project_list.assert_called_once_with("test-remote") | ||
316 | 553 | mock_lxc.project_create.assert_called_once_with( | ||
317 | 554 | project="test-project", remote="test-remote" | ||
318 | 555 | ) | ||
319 | 556 | mock_lxc.profile_show.assert_called_once_with( | ||
320 | 557 | profile="default", project="default", remote="test-remote" | ||
321 | 558 | ) | ||
322 | 559 | mock_lxc.profile_edit.assert_called_once_with( | ||
323 | 560 | profile="default", | ||
324 | 561 | config={ | ||
325 | 562 | "config": {"sentinel": "true"}, | ||
326 | 563 | "devices": {"eth0": {}}, | ||
327 | 564 | }, | ||
328 | 565 | project="test-project", | ||
329 | 566 | remote="test-remote", | ||
330 | 567 | ) | ||
331 | 568 | self.assertEqual( | ||
332 | 569 | [ | ||
333 | 570 | call( | ||
334 | 571 | name=expected_instance_name, | ||
335 | 572 | base_configuration=LPCIBuilddBaseConfiguration( | ||
336 | 573 | alias=BuilddBaseAlias.FOCAL, | ||
337 | 574 | environment={"PATH": _base_path}, | ||
338 | 575 | hostname=expected_instance_name, | ||
339 | 576 | ), | ||
340 | 577 | image_name="focal", | ||
341 | 578 | image_remote="craft-com.ubuntu.cloud-buildd", | ||
342 | 579 | auto_clean=True, | ||
343 | 580 | auto_create_project=True, | ||
344 | 581 | map_user_uid=True, | ||
345 | 582 | use_base_instance=True, | ||
346 | 583 | project="test-project", | ||
347 | 584 | remote="test-remote", | ||
348 | 585 | lxc=mock_lxc, | ||
349 | 586 | ), | ||
350 | 587 | call().mount( | ||
351 | 588 | host_source=self.mock_path, | ||
352 | 589 | target=Path("/root/tmp-project"), | ||
353 | 590 | ), | ||
354 | 591 | call().lxc.exec( | ||
355 | 592 | instance_name=expected_instance_name, | ||
356 | 593 | command=["rm", "-rf", "/build/lpci/project"], | ||
357 | 594 | project="test-project", | ||
358 | 595 | remote="test-remote", | ||
359 | 596 | runner=subprocess.run, | ||
360 | 597 | check=True, | ||
361 | 598 | ), | ||
362 | 599 | call().lxc.exec( | ||
363 | 600 | instance_name=expected_instance_name, | ||
364 | 601 | command=["mkdir", "-p", "/build/lpci"], | ||
365 | 602 | project="test-project", | ||
366 | 603 | remote="test-remote", | ||
367 | 604 | runner=subprocess.run, | ||
368 | 605 | check=True, | ||
369 | 606 | ), | ||
370 | 607 | call().lxc.exec( | ||
371 | 608 | instance_name=expected_instance_name, | ||
372 | 503 | command=[ | 609 | command=[ |
376 | 504 | "rm", | 610 | "cp", |
377 | 505 | "-f", | 611 | "-a", |
378 | 506 | "/usr/local/sbin/policy-rc.d", | 612 | "/root/tmp-project", |
379 | 613 | "/build/lpci/project", | ||
380 | 614 | ], | ||
381 | 615 | project="test-project", | ||
382 | 616 | remote="test-remote", | ||
383 | 617 | runner=subprocess.run, | ||
384 | 618 | check=True, | ||
385 | 619 | ), | ||
386 | 620 | call().lxc.exec( | ||
387 | 621 | instance_name=expected_instance_name, | ||
388 | 622 | command=["rm", "-f", "/usr/local/sbin/policy-rc.d"], | ||
389 | 623 | project="test-project", | ||
390 | 624 | remote="test-remote", | ||
391 | 625 | runner=subprocess.run, | ||
392 | 626 | check=True, | ||
393 | 627 | ), | ||
394 | 628 | call().lxc.exec( | ||
395 | 629 | instance_name=expected_instance_name, | ||
396 | 630 | command=[ | ||
397 | 631 | "sh", | ||
398 | 632 | "-c", | ||
399 | 633 | "getent passwd _lpci >/dev/null" | ||
400 | 634 | + " || useradd -m -U _lpci", | ||
401 | 635 | ], | ||
402 | 636 | project="test-project", | ||
403 | 637 | remote="test-remote", | ||
404 | 638 | runner=subprocess.run, | ||
405 | 639 | check=True, | ||
406 | 640 | ), | ||
407 | 641 | call().lxc.exec( | ||
408 | 642 | instance_name=expected_instance_name, | ||
409 | 643 | command=[ | ||
410 | 644 | "chown", | ||
411 | 645 | "-R", | ||
412 | 646 | "_lpci:_lpci", | ||
413 | 647 | "/build/lpci/project", | ||
414 | 507 | ], | 648 | ], |
415 | 508 | project="test-project", | 649 | project="test-project", |
416 | 509 | remote="test-remote", | 650 | remote="test-remote", |
417 | diff --git a/lpci/tests/test_config.py b/lpci/tests/test_config.py | |||
418 | index 7bf45a5..17cc308 100644 | |||
419 | --- a/lpci/tests/test_config.py | |||
420 | +++ b/lpci/tests/test_config.py | |||
421 | @@ -848,3 +848,46 @@ class TestConfig(TestCase): | |||
422 | 848 | Config.load, | 848 | Config.load, |
423 | 849 | path, | 849 | path, |
424 | 850 | ) | 850 | ) |
425 | 851 | |||
426 | 852 | def test_root_value(self): | ||
427 | 853 | path = self.create_config( | ||
428 | 854 | dedent( | ||
429 | 855 | """ | ||
430 | 856 | pipeline: | ||
431 | 857 | - test | ||
432 | 858 | |||
433 | 859 | jobs: | ||
434 | 860 | test: | ||
435 | 861 | root: True | ||
436 | 862 | series: focal | ||
437 | 863 | architectures: amd64 | ||
438 | 864 | """ | ||
439 | 865 | ) | ||
440 | 866 | ) | ||
441 | 867 | config = Config.load(path) | ||
442 | 868 | root_value = config.jobs["test"][0].root | ||
443 | 869 | self.assertEqual(True, root_value) | ||
444 | 870 | |||
445 | 871 | def test_bad_root_value(self): | ||
446 | 872 | path = self.create_config( | ||
447 | 873 | dedent( | ||
448 | 874 | """ | ||
449 | 875 | pipeline: | ||
450 | 876 | - test | ||
451 | 877 | |||
452 | 878 | jobs: | ||
453 | 879 | test: | ||
454 | 880 | root: bad_value | ||
455 | 881 | series: focal | ||
456 | 882 | architectures: amd64 | ||
457 | 883 | """ | ||
458 | 884 | ) | ||
459 | 885 | ) | ||
460 | 886 | self.assertRaisesRegex( | ||
461 | 887 | ValidationError, | ||
462 | 888 | "You configured `root` parameter, " | ||
463 | 889 | + "but you did not specify a valid value. " | ||
464 | 890 | + "Valid values would either be `true` or `false`.", | ||
465 | 891 | Config.load, | ||
466 | 892 | path, | ||
467 | 893 | ) | ||
468 | diff --git a/lpci/tests/test_env.py b/lpci/tests/test_env.py | |||
469 | index 58b69f1..7079481 100644 | |||
470 | --- a/lpci/tests/test_env.py | |||
471 | +++ b/lpci/tests/test_env.py | |||
472 | @@ -9,6 +9,9 @@ from lpci import env | |||
473 | 9 | 9 | ||
474 | 10 | 10 | ||
475 | 11 | class TestEnvironment(TestCase): | 11 | class TestEnvironment(TestCase): |
476 | 12 | def test_get_non_root_user(self): | ||
477 | 13 | self.assertEqual("_lpci", env.get_non_root_user()) | ||
478 | 14 | |||
479 | 12 | def test_get_managed_environment_home_path(self): | 15 | def test_get_managed_environment_home_path(self): |
480 | 13 | self.assertEqual( | 16 | self.assertEqual( |
481 | 14 | Path("/root"), env.get_managed_environment_home_path() | 17 | Path("/root"), env.get_managed_environment_home_path() |
Thanks! I have a collection of nitpicks, but they're mostly just that.