Merge ~jugmac00/lpci:add-additional-apt-repositories into lpci:main
- Git
- lp:~jugmac00/lpci
- add-additional-apt-repositories
- Merge into main
Status: | Merged |
---|---|
Approved by: | Jürgen Gmach |
Approved revision: | 1a9d9b1f565ae73e61ed4b1bcac4e2461995f34c |
Merged at revision: | 1a9d9b1f565ae73e61ed4b1bcac4e2461995f34c |
Proposed branch: | ~jugmac00/lpci:add-additional-apt-repositories |
Merge into: | lpci:main |
Diff against target: |
564 lines (+392/-10) 7 files modified
NEWS.rst (+2/-0) docs/configuration.rst (+37/-0) lpcraft/commands/run.py (+30/-6) lpcraft/commands/tests/test_run.py (+167/-0) lpcraft/config.py (+61/-2) lpcraft/main.py (+15/-0) lpcraft/tests/test_config.py (+80/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email:
|
Commit message
Add new configuration option to provide additional package repositories
Description of the change
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jürgen Gmach (jugmac00) : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jürgen Gmach (jugmac00) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Colin Watson (cjwatson) wrote : | # |
Adding apt archives commonly also involves adding the corresponding GPG key that signs the archive, which is going to be a bit difficult to retrofit to this syntax, so I think we should consider some slightly different syntaxes before committing to this.
Have you considered the approach taken by https:/
I would definitely like to have something like this that can be a little more easily extended, even if we don't adopt literally the same syntax. But if our needs are compatible with snapcraft's, then there seems no reason not to adopt the same syntax, given that lpcraft is at least somewhat inspired by snapcraft in other areas.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jürgen Gmach (jugmac00) wrote : | # |
Thanks for the review - I really like your ideas.
I have updated my MP and it is ready for review again.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jürgen Gmach (jugmac00) : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Colin Watson (cjwatson) : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jürgen Gmach (jugmac00) wrote : | # |
Thanks for the review - I applied most suggestions :-)
Preview Diff
1 | diff --git a/NEWS.rst b/NEWS.rst |
2 | index 72603d6..1d6b28c 100644 |
3 | --- a/NEWS.rst |
4 | +++ b/NEWS.rst |
5 | @@ -9,6 +9,8 @@ Version history |
6 | |
7 | - Hide the internal ``run-one`` command from ``--help`` output. |
8 | |
9 | +- Add new configuration option to provide additional package repositories. |
10 | + |
11 | 0.0.17 (2022-06-17) |
12 | =================== |
13 | |
14 | diff --git a/docs/configuration.rst b/docs/configuration.rst |
15 | index ecaa727..b453270 100644 |
16 | --- a/docs/configuration.rst |
17 | +++ b/docs/configuration.rst |
18 | @@ -50,6 +50,11 @@ Job definitions |
19 | ``packages`` (optional) |
20 | Packages to install using ``apt`` as dependencies of this job. |
21 | |
22 | +``package-repositories`` (optional) |
23 | + Repositories which will be added to the already existing ones in |
24 | + `/etc/apt/sources.list`. |
25 | + Also see the :ref:`package-repositories` section below. |
26 | + |
27 | ``snaps`` (optional) |
28 | Snaps to install as dependencies of this job. |
29 | |
30 | @@ -132,3 +137,35 @@ Output properties |
31 | This value is parsed using `pydantic's standard timedelta parsing |
32 | <https://pydantic-docs.helpmanual.io/usage/types/#datetime-types>`_, |
33 | restricted to non-negative timedeltas. |
34 | + |
35 | + |
36 | +.. _package-repositories: |
37 | + |
38 | +Package-repositories properties |
39 | +------------------------------- |
40 | + |
41 | +The properties are inspired by the properties of `Snapcraft |
42 | +<https://snapcraft.io/docs/package-repositories>`_. |
43 | +Only a subset of them is currently implemented. |
44 | + |
45 | +More properties can be implemented on demand. |
46 | + |
47 | +``type`` (required) |
48 | + Specifies the type of package-repository. |
49 | + Currently only `apt` is supported. |
50 | + |
51 | +``formats`` (required) |
52 | + Specifies the format of the package-repository. |
53 | + Supported values: ``deb`` and ``deb-src``. |
54 | + |
55 | +``components`` (required) |
56 | + Specifies the component of the package-repository, |
57 | + One or several of ``main``, ``restricted``, ``universe``, ``multiverse``. |
58 | + |
59 | +``suites`` (required) |
60 | + Specifies the suite of the package-repository. |
61 | + One or several of ``bionic``, ``focal``, ``jammy``. |
62 | + |
63 | +``url`` (required) |
64 | + Specifies the URL of the package-repository. |
65 | + e.g. ``http://ppa.launchpad.net/snappy-dev/snapcraft-daily/ubuntu`` |
66 | diff --git a/lpcraft/commands/run.py b/lpcraft/commands/run.py |
67 | index dd39c1c..757dfe0 100644 |
68 | --- a/lpcraft/commands/run.py |
69 | +++ b/lpcraft/commands/run.py |
70 | @@ -9,6 +9,7 @@ import os |
71 | import shlex |
72 | from argparse import ArgumentParser, Namespace |
73 | from pathlib import Path, PurePath |
74 | +from tempfile import NamedTemporaryFile |
75 | from typing import Dict, List, Optional, Set |
76 | |
77 | from craft_cli import BaseCommand, EmitterMode, emit |
78 | @@ -18,7 +19,7 @@ from dotenv import dotenv_values |
79 | from pluggy import PluginManager |
80 | |
81 | from lpcraft import env |
82 | -from lpcraft.config import Config, Job, Output |
83 | +from lpcraft.config import Config, Job, Output, PackageRepository |
84 | from lpcraft.errors import CommandError |
85 | from lpcraft.plugin.manager import get_plugin_manager |
86 | from lpcraft.plugins import PLUGINS |
87 | @@ -226,19 +227,38 @@ def _install_apt_packages( |
88 | host_architecture: str, |
89 | remote_cwd: Path, |
90 | apt_replacement_repositories: Optional[List[str]], |
91 | + additional_apt_repositories: Optional[List[PackageRepository]], |
92 | environment: Optional[Dict[str, Optional[str]]], |
93 | ) -> None: |
94 | - if apt_replacement_repositories: |
95 | - # replace sources.list |
96 | - lines = "\n".join(apt_replacement_repositories) + "\n" |
97 | + if apt_replacement_repositories or additional_apt_repositories: |
98 | + sources_list_path = "/etc/apt/sources.list" |
99 | + |
100 | + with NamedTemporaryFile(mode="w+") as tmpfile: |
101 | + try: |
102 | + instance.pull_file( |
103 | + source=Path(sources_list_path), |
104 | + destination=Path(tmpfile.name), |
105 | + ) |
106 | + except Exception as e: |
107 | + raise CommandError(str(e), retcode=1) |
108 | + sources = tmpfile.read() |
109 | + |
110 | + if apt_replacement_repositories: |
111 | + sources = "\n".join(apt_replacement_repositories) + "\n" |
112 | + if additional_apt_repositories: |
113 | + for repository in additional_apt_repositories: |
114 | + sources += ( |
115 | + "\n" + "\n".join(repository.sources_list_lines()) + "\n" |
116 | + ) |
117 | with emit.open_stream("Replacing /etc/apt/sources.list") as stream: |
118 | instance.push_file_io( |
119 | - destination=PurePath("/etc/apt/sources.list"), |
120 | - content=io.BytesIO(lines.encode()), |
121 | + destination=PurePath(sources_list_path), |
122 | + content=io.BytesIO(sources.encode()), |
123 | file_mode="0644", |
124 | group="root", |
125 | user="root", |
126 | ) |
127 | + |
128 | # update local repository information |
129 | apt_update = ["apt", "update"] |
130 | with emit.open_stream(f"Running {apt_update}") as stream: |
131 | @@ -316,6 +336,7 @@ def _run_job( |
132 | provider: Provider, |
133 | output: Optional[Path], |
134 | apt_replacement_repositories: Optional[List[str]] = None, |
135 | + additional_apt_repositories: Optional[List[PackageRepository]] = None, |
136 | env_from_cli: Optional[List[str]] = None, |
137 | plugin_settings: Optional[List[str]] = None, |
138 | ) -> None: |
139 | @@ -406,6 +427,7 @@ def _run_job( |
140 | host_architecture=host_architecture, |
141 | remote_cwd=remote_cwd, |
142 | apt_replacement_repositories=apt_replacement_repositories, |
143 | + additional_apt_repositories=additional_apt_repositories, |
144 | environment=environment, |
145 | ) |
146 | for cmd in (pre_run_command, run_command, post_run_command): |
147 | @@ -518,6 +540,7 @@ class RunCommand(BaseCommand): |
148 | apt_replacement_repositories=( |
149 | args.apt_replace_repositories |
150 | ), |
151 | + additional_apt_repositories=job.package_repositories, # noqa: E501 |
152 | env_from_cli=args.set_env, |
153 | plugin_settings=args.plugin_setting, |
154 | ) |
155 | @@ -625,6 +648,7 @@ class RunOneCommand(BaseCommand): |
156 | provider, |
157 | args.output_directory, |
158 | apt_replacement_repositories=args.apt_replace_repositories, |
159 | + additional_apt_repositories=job.package_repositories, |
160 | env_from_cli=args.set_env, |
161 | plugin_settings=args.plugin_setting, |
162 | ) |
163 | diff --git a/lpcraft/commands/tests/test_run.py b/lpcraft/commands/tests/test_run.py |
164 | index 165d435..0da675a 100644 |
165 | --- a/lpcraft/commands/tests/test_run.py |
166 | +++ b/lpcraft/commands/tests/test_run.py |
167 | @@ -1995,6 +1995,67 @@ class TestRun(RunBaseTestCase): |
168 | execute_run.call_args_list, |
169 | ) |
170 | |
171 | + @patch("lpcraft.commands.run.get_provider") |
172 | + @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64") |
173 | + def test_run_with_additional_package_repositories( |
174 | + self, mock_get_host_architecture, mock_get_provider |
175 | + ): |
176 | + existing_repositories = [ |
177 | + "deb http://archive.ubuntu.com/ubuntu/ focal main restricted", |
178 | + "deb-src http://archive.ubuntu.com/ubuntu/ focal main restricted", |
179 | + ] |
180 | + |
181 | + def fake_pull_file(source: Path, destination: Path) -> None: |
182 | + destination.write_text("\n".join(existing_repositories)) |
183 | + |
184 | + launcher = Mock(spec=launch) |
185 | + provider = makeLXDProvider(lxd_launcher=launcher) |
186 | + mock_get_provider.return_value = provider |
187 | + execute_run = launcher.return_value.execute_run |
188 | + execute_run.return_value = subprocess.CompletedProcess([], 0) |
189 | + launcher.return_value.pull_file.side_effect = fake_pull_file |
190 | + |
191 | + config = dedent( |
192 | + """ |
193 | + pipeline: |
194 | + - test |
195 | + jobs: |
196 | + test: |
197 | + series: focal |
198 | + architectures: amd64 |
199 | + run: ls -la |
200 | + packages: [git] |
201 | + package-repositories: |
202 | + - type: apt |
203 | + formats: [deb] |
204 | + components: [main, universe] |
205 | + suites: [focal] |
206 | + url: https://canonical.example.org/artifactory/jammy-golang-backport |
207 | + """ # noqa: E501 |
208 | + ) |
209 | + Path(".launchpad.yaml").write_text(config) |
210 | + |
211 | + result = self.run_command("run") |
212 | + |
213 | + self.assertEqual(0, result.exit_code) |
214 | + |
215 | + mock_info = launcher.return_value.push_file_io.call_args_list |
216 | + self.assertEqual( |
217 | + Path("/etc/apt/sources.list"), mock_info[0][1]["destination"] |
218 | + ) |
219 | + |
220 | + file_contents = mock_info[0][1]["content"].read().decode() |
221 | + self.assertEqual( |
222 | + dedent( |
223 | + """\ |
224 | + deb http://archive.ubuntu.com/ubuntu/ focal main restricted |
225 | + deb-src http://archive.ubuntu.com/ubuntu/ focal main restricted |
226 | + deb https://canonical.example.org/artifactory/jammy-golang-backport focal main universe |
227 | + """ # noqa: E501 |
228 | + ), |
229 | + file_contents, |
230 | + ) |
231 | + |
232 | |
233 | class TestRunOne(RunBaseTestCase): |
234 | def test_config_file_not_under_project_directory(self): |
235 | @@ -2637,3 +2698,109 @@ class TestRunOne(RunBaseTestCase): |
236 | ], |
237 | execute_run.call_args_list, |
238 | ) |
239 | + |
240 | + @patch("lpcraft.commands.run.get_provider") |
241 | + @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64") |
242 | + def test_run_with_additional_package_repositories( |
243 | + self, mock_get_host_architecture, mock_get_provider |
244 | + ): |
245 | + existing_repositories = [ |
246 | + "deb http://archive.ubuntu.com/ubuntu/ focal main restricted", |
247 | + "deb-src http://archive.ubuntu.com/ubuntu/ focal main restricted", |
248 | + ] |
249 | + |
250 | + def fake_pull_file(source: Path, destination: Path) -> None: |
251 | + destination.write_text("\n".join(existing_repositories)) |
252 | + |
253 | + launcher = Mock(spec=launch) |
254 | + provider = makeLXDProvider(lxd_launcher=launcher) |
255 | + mock_get_provider.return_value = provider |
256 | + execute_run = launcher.return_value.execute_run |
257 | + execute_run.return_value = subprocess.CompletedProcess([], 0) |
258 | + launcher.return_value.pull_file.side_effect = fake_pull_file |
259 | + config = dedent( |
260 | + """ |
261 | + pipeline: |
262 | + - test |
263 | + jobs: |
264 | + test: |
265 | + series: focal |
266 | + architectures: amd64 |
267 | + run: ls -la |
268 | + packages: [git] |
269 | + package-repositories: |
270 | + - type: apt |
271 | + formats: [deb] |
272 | + components: [main, universe] |
273 | + suites: [focal] |
274 | + url: https://canonical.example.org/artifactory/jammy-golang-backport |
275 | + """ # noqa: E501 |
276 | + ) |
277 | + Path(".launchpad.yaml").write_text(config) |
278 | + |
279 | + result = self.run_command("run-one", "test", "0") |
280 | + |
281 | + self.assertEqual(0, result.exit_code) |
282 | + |
283 | + mock_info = launcher.return_value.push_file_io.call_args_list |
284 | + self.assertEqual( |
285 | + Path("/etc/apt/sources.list"), mock_info[0][1]["destination"] |
286 | + ) |
287 | + |
288 | + file_contents = mock_info[0][1]["content"].read().decode() |
289 | + self.assertEqual( |
290 | + dedent( |
291 | + """\ |
292 | + deb http://archive.ubuntu.com/ubuntu/ focal main restricted |
293 | + deb-src http://archive.ubuntu.com/ubuntu/ focal main restricted |
294 | + deb https://canonical.example.org/artifactory/jammy-golang-backport focal main universe |
295 | + """ # noqa: E501 |
296 | + ), |
297 | + file_contents, |
298 | + ) |
299 | + |
300 | + @patch("lpcraft.env.get_managed_environment_project_path") |
301 | + @patch("lpcraft.commands.run.get_provider") |
302 | + @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64") |
303 | + def test_fails_pulling_sources_list( |
304 | + self, |
305 | + mock_get_host_architecture, |
306 | + mock_get_provider, |
307 | + mock_get_project_path, |
308 | + ): |
309 | + launcher = Mock(spec=launch) |
310 | + provider = makeLXDProvider(lxd_launcher=launcher) |
311 | + mock_get_provider.return_value = provider |
312 | + execute_run = LocalExecuteRun(self.tmp_project_path) |
313 | + launcher.return_value.execute_run = execute_run |
314 | + launcher.return_value.pull_file.side_effect = FileNotFoundError( |
315 | + "File not found" |
316 | + ) |
317 | + config = dedent( |
318 | + """ |
319 | + pipeline: |
320 | + - test |
321 | + jobs: |
322 | + test: |
323 | + series: focal |
324 | + architectures: amd64 |
325 | + run: ls -la |
326 | + packages: [git] |
327 | + package-repositories: |
328 | + - type: apt |
329 | + formats: [deb] |
330 | + components: [main, universe] |
331 | + suites: [focal] |
332 | + url: https://canonical.example.org/repodir |
333 | + """ |
334 | + ) |
335 | + Path(".launchpad.yaml").write_text(config) |
336 | + |
337 | + result = self.run_command("run-one", "test", "0") |
338 | + |
339 | + self.assertThat( |
340 | + result, |
341 | + MatchesStructure.byEquality( |
342 | + exit_code=1, errors=[CommandError("File not found", retcode=1)] |
343 | + ), |
344 | + ) |
345 | diff --git a/lpcraft/config.py b/lpcraft/config.py |
346 | index 0a4120d..0e835e2 100644 |
347 | --- a/lpcraft/config.py |
348 | +++ b/lpcraft/config.py |
349 | @@ -5,10 +5,10 @@ import re |
350 | from datetime import timedelta |
351 | from enum import Enum |
352 | from pathlib import Path |
353 | -from typing import Any, Dict, List, Optional, Type, Union |
354 | +from typing import Any, Dict, Iterator, List, Optional, Type, Union |
355 | |
356 | import pydantic |
357 | -from pydantic import StrictStr |
358 | +from pydantic import AnyHttpUrl, StrictStr |
359 | |
360 | from lpcraft.errors import ConfigurationError |
361 | from lpcraft.plugins import PLUGINS |
362 | @@ -76,6 +76,64 @@ def _validate_plugin_config( |
363 | return values |
364 | |
365 | |
366 | +class PackageType(str, Enum): |
367 | + """Specifies the type of the package repository. |
368 | + |
369 | + Currently only supports apt. |
370 | + """ |
371 | + |
372 | + apt = "apt" |
373 | + |
374 | + |
375 | +class PackageFormat(str, Enum): |
376 | + """Specifies the format of the package repository.""" |
377 | + |
378 | + deb = "deb" |
379 | + deb_src = "deb-src" |
380 | + |
381 | + |
382 | +class PackageComponent(str, Enum): |
383 | + """Specifies the component of the package repository.""" |
384 | + |
385 | + main = "main" |
386 | + restricted = "restricted" |
387 | + universe = "universe" |
388 | + multiverse = "multiverse" |
389 | + |
390 | + |
391 | +class PackageSuite(str, Enum): |
392 | + """Specifies the suite of the package repository. |
393 | + |
394 | + e.g. xenial, focal, ... |
395 | + """ |
396 | + |
397 | + bionic = "bionic" # 18.04 |
398 | + focal = "focal" # 20.04 |
399 | + jammy = "jammy" # 22.04 |
400 | + |
401 | + |
402 | +class PackageRepository(ModelConfigDefaults): |
403 | + """A representation of a package repository. |
404 | + |
405 | + inspired by https://snapcraft.io/docs/package-repositories |
406 | + """ |
407 | + |
408 | + type: PackageType # e.g. `apt`` |
409 | + formats: List[PackageFormat] # e.g. `[deb, deb-src]` |
410 | + components: List[PackageComponent] # e.g. `[main, universe]` |
411 | + suites: List[PackageSuite] # e.g. `[bionic, focal]` |
412 | + url: AnyHttpUrl |
413 | + |
414 | + def sources_list_lines(self) -> Iterator[str]: |
415 | + """Yield repository lines as strings. |
416 | + |
417 | + e.g. 'deb https://canonical.example.org/artifactory/jammy-golang-backport focal main' |
418 | + """ # noqa: E501 |
419 | + for format in self.formats: |
420 | + for suite in self.suites: |
421 | + yield f"{format} {self.url!s} {suite} {' '.join(self.components)}" # noqa: E501 |
422 | + |
423 | + |
424 | class Job(ModelConfigDefaults): |
425 | """A job definition.""" |
426 | |
427 | @@ -92,6 +150,7 @@ class Job(ModelConfigDefaults): |
428 | output: Optional[Output] |
429 | snaps: Optional[List[StrictStr]] |
430 | packages: Optional[List[StrictStr]] |
431 | + package_repositories: Optional[List[PackageRepository]] |
432 | plugin: Optional[StrictStr] |
433 | plugin_config: Optional[BaseConfig] |
434 | |
435 | diff --git a/lpcraft/main.py b/lpcraft/main.py |
436 | index 83fe767..f6263cf 100644 |
437 | --- a/lpcraft/main.py |
438 | +++ b/lpcraft/main.py |
439 | @@ -63,6 +63,21 @@ def main(argv: Optional[List[str]] = None) -> int: |
440 | ) |
441 | ] |
442 | |
443 | + # dispatcher = Dispatcher( |
444 | + # "lpcraft", |
445 | + # command_groups, |
446 | + # summary=summary, |
447 | + # extra_global_args=extra_global_args, |
448 | + # default_command=RunCommand, |
449 | + # ) |
450 | + # global_args = dispatcher.pre_parse_args(argv) |
451 | + # if global_args["version"]: |
452 | + # emit.message(lpcraft_version) |
453 | + # emit.ended_ok() |
454 | + # return 0 |
455 | + # dispatcher.load_command(None) |
456 | + # ret = dispatcher.run() or 0 |
457 | + |
458 | try: |
459 | dispatcher = Dispatcher( |
460 | "lpcraft", |
461 | diff --git a/lpcraft/tests/test_config.py b/lpcraft/tests/test_config.py |
462 | index d34b718..3daf342 100644 |
463 | --- a/lpcraft/tests/test_config.py |
464 | +++ b/lpcraft/tests/test_config.py |
465 | @@ -7,7 +7,7 @@ from pathlib import Path |
466 | from textwrap import dedent |
467 | |
468 | from fixtures import TempDir |
469 | -from pydantic import ValidationError |
470 | +from pydantic import AnyHttpUrl, ValidationError |
471 | from testtools import TestCase |
472 | from testtools.matchers import ( |
473 | Equals, |
474 | @@ -16,7 +16,7 @@ from testtools.matchers import ( |
475 | MatchesStructure, |
476 | ) |
477 | |
478 | -from lpcraft.config import Config, OutputDistributeEnum |
479 | +from lpcraft.config import Config, OutputDistributeEnum, PackageRepository |
480 | from lpcraft.errors import CommandError |
481 | |
482 | |
483 | @@ -403,3 +403,81 @@ class TestConfig(TestCase): |
484 | config = Config.load(path) |
485 | |
486 | self.assertEqual("tox", config.jobs["test"][0].plugin) |
487 | + |
488 | + def test_package_repositories(self): |
489 | + path = self.create_config( |
490 | + dedent( |
491 | + """ |
492 | + pipeline: |
493 | + - test |
494 | + |
495 | + jobs: |
496 | + test: |
497 | + series: focal |
498 | + architectures: amd64 |
499 | + packages: [nginx, apache2] |
500 | + package-repositories: |
501 | + - type: apt |
502 | + formats: [deb] |
503 | + components: [main] |
504 | + suites: [focal] |
505 | + url: https://canonical.example.org/artifactory/jammy-golang-backport |
506 | + """ # noqa: E501 |
507 | + ) |
508 | + ) |
509 | + |
510 | + config = Config.load(path) |
511 | + |
512 | + self.assertEqual( |
513 | + [ |
514 | + PackageRepository( |
515 | + type="apt", |
516 | + formats=["deb"], |
517 | + components=["main"], |
518 | + suites=["focal"], |
519 | + url=AnyHttpUrl( |
520 | + "https://canonical.example.org/artifactory/jammy-golang-backport", # noqa: E501 |
521 | + scheme="https", |
522 | + host="canonical.example.org", |
523 | + tld="org", |
524 | + host_type="domain", |
525 | + path="/artifactory/jammy-golang-backport", |
526 | + ), |
527 | + ) |
528 | + ], |
529 | + config.jobs["test"][0].package_repositories, |
530 | + ) |
531 | + |
532 | + def test_package_repositories_as_string(self): |
533 | + path = self.create_config( |
534 | + dedent( |
535 | + """ |
536 | + pipeline: |
537 | + - test |
538 | + |
539 | + jobs: |
540 | + test: |
541 | + series: focal |
542 | + architectures: amd64 |
543 | + packages: [nginx, apache2] |
544 | + package-repositories: |
545 | + - type: apt |
546 | + formats: [deb, deb-src] |
547 | + components: [main] |
548 | + suites: [focal, bionic] |
549 | + url: https://canonical.example.org/artifactory/jammy-golang-backport |
550 | + """ # noqa: E501 |
551 | + ) |
552 | + ) |
553 | + config = Config.load(path) |
554 | + expected = [ |
555 | + "deb https://canonical.example.org/artifactory/jammy-golang-backport focal main", # noqa: E501 |
556 | + "deb https://canonical.example.org/artifactory/jammy-golang-backport bionic main", # noqa: E501 |
557 | + "deb-src https://canonical.example.org/artifactory/jammy-golang-backport focal main", # noqa: E501 |
558 | + "deb-src https://canonical.example.org/artifactory/jammy-golang-backport bionic main", # noqa: E501 |
559 | + ] |
560 | + repositories = config.jobs["test"][0].package_repositories |
561 | + assert repositories is not None # workaround necessary to please mypy |
562 | + self.assertEqual( |
563 | + expected, (list(repositories[0].sources_list_lines())) |
564 | + ) |
The auth placeholder feature will be added together with "Pass credentials from Launchpad via launchpad-buildd to lpcraft" /warthogs. atlassian. net/browse/ LP-812
https:/