Merge ~jugmac00/lpci:add-optional-argument-for-add-apt-repository into lpci:main

Proposed by Jürgen Gmach
Status: Rejected
Rejected by: Jürgen Gmach
Proposed branch: ~jugmac00/lpci:add-optional-argument-for-add-apt-repository
Merge into: lpci:main
Diff against target: 373 lines (+275/-3)
6 files modified
NEWS.rst (+4/-1)
docs/cli-interface.rst (+42/-0)
docs/index.rst (+1/-0)
lpcraft/commands/run.py (+48/-2)
lpcraft/commands/tests/test_run.py (+170/-0)
lpcraft/main.py (+10/-0)
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+420656@code.launchpad.net

Commit message

Add optional argument to add apt repository

To post a comment you must log in.
Revision history for this message
Jürgen Gmach (jugmac00) wrote :

WIP - need to add tests - ran out of time.

Revision history for this message
Jürgen Gmach (jugmac00) wrote :

Unmerged commits

d270d8e... by Jürgen Gmach

Add optional CLI argument to replace `/etc/apt/sources.list`

Also add minimal CLI interface documentation, which at some point should
be autogenerated.

Failed
[SUCCEEDED] test:0 (build)
[FAILED] build:0 (build)
12 of 2 results

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/NEWS.rst b/NEWS.rst
2index e67e124..1bbb813 100644
3--- a/NEWS.rst
4+++ b/NEWS.rst
5@@ -5,7 +5,10 @@ Version history
6 0.0.11 (unreleased)
7 ===================
8
9-- Nothing yet.
10+- Add new optional and stackable argument ``--replace-sources-list`` which
11+ overwrites ``/etc/apt/sources.list``.
12+
13+- Add minimal CLI interface documentation.
14
15 0.0.10 (2022-04-27)
16 ====================
17diff --git a/docs/cli-interface.rst b/docs/cli-interface.rst
18new file mode 100644
19index 0000000..37f1136
20--- /dev/null
21+++ b/docs/cli-interface.rst
22@@ -0,0 +1,42 @@
23+=======================
24+lpcraft - CLI interface
25+=======================
26+
27+Please note that this is only a small selection of the available commands and
28+options.
29+
30+Please run ``lpcraft --help`` to see all commands.
31+
32+lpcraft run
33+-----------
34+
35+This command runs all jobs listed via pipelines from a configuration file.
36+
37+**Example:**
38+
39+``lpcraft run``
40+
41+lpcraft run optional arguments
42+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
43+
44+- ``--replace-sources-list SOURCE_LINE``, e.g.
45+ ``lpcraft run --replace-sources-list "deb http://archive.ubuntu.com/ubuntu/ focal main restricted"``.
46+ Please note that the option is stackable, so you can repeat it several times.
47+
48+lpcraft run-one
49+---------------
50+
51+This commands runs one specified job.
52+
53+**Example:**
54+
55+``lpcraft run-one test 0``
56+
57+where ``test`` is the job name and ``0`` is the index of the job/matrix.
58+
59+lpcraft run-one optional arguments
60+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
61+
62+- ``--replace-sources-list SOURCE_LINE``, e.g.
63+ ``lpcraft run-one --replace-sources list "deb http://archive.ubuntu.com/ubuntu/ focal main restricted" test 0``.
64+ Please note that the option is stackable, so you can repeat it several times.
65diff --git a/docs/index.rst b/docs/index.rst
66index b8cfbab..288b9bd 100644
67--- a/docs/index.rst
68+++ b/docs/index.rst
69@@ -46,6 +46,7 @@ Example configuration
70
71 self
72 configuration
73+ cli-interface
74 plugins
75 CONTRIBUTING
76 release-process
77diff --git a/lpcraft/commands/run.py b/lpcraft/commands/run.py
78index e5bc296..cc37319 100644
79--- a/lpcraft/commands/run.py
80+++ b/lpcraft/commands/run.py
81@@ -194,9 +194,15 @@ def _copy_output_properties(
82
83
84 def _run_job(
85- job_name: str, job: Job, provider: Provider, output: Optional[Path]
86+ job_name: str,
87+ job: Job,
88+ provider: Provider,
89+ output: Optional[Path],
90+ replacement_repositories: Optional[List[str]] = None,
91 ) -> None:
92 """Run a single job."""
93+ # XXX jugmac00 2022-04-27: we should create a configuration object to be
94+ # passed in and not so many arguments
95 host_architecture = get_host_architecture()
96 if host_architecture not in job.architectures:
97 return
98@@ -253,6 +259,37 @@ def _run_job(
99 )
100 packages = list(itertools.chain(*pm.hook.lpcraft_install_packages()))
101 if packages:
102+ if replacement_repositories:
103+ # replace sources.list
104+ lines = "\n".join(replacement_repositories)
105+ with emit.open_stream(
106+ "Replacing /etc/apt/sources.list"
107+ ) as stream:
108+ instance.push_file_io(
109+ destination=PurePath("/etc/apt/sources.list"),
110+ content=io.BytesIO(lines.encode()),
111+ file_mode="0644",
112+ group="root",
113+ user="root",
114+ )
115+ # update local repository information
116+ apt_update = ["apt", "update"]
117+ with emit.open_stream(f"Running {apt_update}") as stream:
118+ proc = instance.execute_run(
119+ apt_update,
120+ cwd=remote_cwd,
121+ env=environment,
122+ stdout=stream,
123+ stderr=stream,
124+ )
125+ if proc.returncode != 0:
126+ raise CommandError(
127+ f"Job {job_name!r} for "
128+ f"{job.series}/{host_architecture} failed with "
129+ f"exit status {proc.returncode} "
130+ f"while running `{shlex.join(apt_update)}`.",
131+ retcode=proc.returncode,
132+ )
133 packages_cmd = ["apt", "install", "-y"] + packages
134 emit.progress("Installing system packages")
135 with emit.open_stream(f"Running {packages_cmd}") as stream:
136@@ -344,6 +381,9 @@ def run(args: Namespace) -> int:
137 job,
138 provider,
139 getattr(args, "output_directory", None),
140+ replacement_repositories=getattr(
141+ args, "replace_sources_list", None
142+ ),
143 )
144 except CommandError as e:
145 if len(stage) == 1:
146@@ -391,7 +431,13 @@ def run_one(args: Namespace) -> int:
147
148 try:
149 _run_job(
150- args.job, job, provider, getattr(args, "output_directory", None)
151+ args.job,
152+ job,
153+ provider,
154+ getattr(args, "output_directory", None),
155+ replacement_repositories=getattr(
156+ args, "replace_sources_list", None
157+ ),
158 )
159 finally:
160 should_clean_environment = getattr(args, "clean", False)
161diff --git a/lpcraft/commands/tests/test_run.py b/lpcraft/commands/tests/test_run.py
162index b5ae1db..fca99f4 100644
163--- a/lpcraft/commands/tests/test_run.py
164+++ b/lpcraft/commands/tests/test_run.py
165@@ -419,6 +419,110 @@ class TestRun(RunBaseTestCase):
166
167 @patch("lpcraft.commands.run.get_provider")
168 @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
169+ def test_replace_sources_list(
170+ self, mock_get_host_architecture, mock_get_provider
171+ ):
172+ launcher = Mock(spec=launch)
173+ provider = makeLXDProvider(lxd_launcher=launcher)
174+ mock_get_provider.return_value = provider
175+ execute_run = launcher.return_value.execute_run
176+ execute_run.return_value = subprocess.CompletedProcess([], 0)
177+ launcher.return_value.pull_file
178+ config = dedent(
179+ """
180+ pipeline:
181+ - test
182+ jobs:
183+ test:
184+ series: focal
185+ architectures: amd64
186+ run: ls -la
187+ packages: [git]
188+ """
189+ )
190+ Path(".launchpad.yaml").write_text(config)
191+
192+ result = self.run_command("run", "--replace-sources-list", "repo info")
193+
194+ self.assertEqual(0, result.exit_code)
195+ self.assertEqual(
196+ [
197+ call(
198+ ["apt", "update"],
199+ cwd=Path("/root/lpcraft/project"),
200+ env={},
201+ stdout=ANY,
202+ stderr=ANY,
203+ ),
204+ call(
205+ ["apt", "install", "-y", "git"],
206+ cwd=Path("/root/lpcraft/project"),
207+ env={},
208+ stdout=ANY,
209+ stderr=ANY,
210+ ),
211+ call(
212+ ["bash", "--noprofile", "--norc", "-ec", "ls -la"],
213+ cwd=Path("/root/lpcraft/project"),
214+ env={},
215+ stdout=ANY,
216+ stderr=ANY,
217+ ),
218+ ],
219+ execute_run.call_args_list,
220+ )
221+
222+ mock_info = launcher.return_value.push_file_io.call_args_list[0][1]
223+ self.assertEqual(
224+ Path("/etc/apt/sources.list"), mock_info["destination"]
225+ )
226+ self.assertEqual("repo info", mock_info["content"].read().decode())
227+ self.assertEqual("0644", mock_info["file_mode"])
228+ self.assertEqual("root", mock_info["group"])
229+ self.assertEqual("root", mock_info["user"])
230+
231+ @patch("lpcraft.commands.run.get_provider")
232+ @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
233+ def test_updating_package_info_fails(
234+ self, mock_get_host_architecture, mock_get_provider
235+ ):
236+ launcher = Mock(spec=launch)
237+ provider = makeLXDProvider(lxd_launcher=launcher)
238+ mock_get_provider.return_value = provider
239+ execute_run = launcher.return_value.execute_run
240+ execute_run.return_value = subprocess.CompletedProcess([], 100)
241+ config = dedent(
242+ """
243+ pipeline:
244+ - test
245+ jobs:
246+ test:
247+ series: focal
248+ architectures: amd64
249+ run: ls -la
250+ packages: [git]
251+ """
252+ )
253+ Path(".launchpad.yaml").write_text(config)
254+
255+ result = self.run_command("run", "--replace-sources-list", "repo info")
256+
257+ self.assertEqual(100, result.exit_code)
258+ self.assertEqual(
259+ [
260+ call(
261+ ["apt", "update"],
262+ cwd=Path("/root/lpcraft/project"),
263+ env={},
264+ stdout=ANY,
265+ stderr=ANY,
266+ )
267+ ],
268+ execute_run.call_args_list,
269+ )
270+
271+ @patch("lpcraft.commands.run.get_provider")
272+ @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
273 def test_default_to_run_command(
274 self, mock_get_host_architecture, mock_get_provider
275 ):
276@@ -2149,3 +2253,69 @@ class TestRunOne(RunBaseTestCase):
277 project_path=self.tmp_project_path,
278 instances=instance_names,
279 )
280+
281+ @patch("lpcraft.commands.run.get_provider")
282+ @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
283+ def test_replace_sources_list(
284+ self, mock_get_host_architecture, mock_get_provider
285+ ):
286+ launcher = Mock(spec=launch)
287+ provider = makeLXDProvider(lxd_launcher=launcher)
288+ mock_get_provider.return_value = provider
289+ execute_run = launcher.return_value.execute_run
290+ execute_run.return_value = subprocess.CompletedProcess([], 0)
291+ launcher.return_value.pull_file
292+ config = dedent(
293+ """
294+ pipeline:
295+ - test
296+ jobs:
297+ test:
298+ series: focal
299+ architectures: amd64
300+ run: ls -la
301+ packages: [git]
302+ """
303+ )
304+ Path(".launchpad.yaml").write_text(config)
305+
306+ result = self.run_command(
307+ "run-one", "--replace-sources-list", "repo info", "test", "0"
308+ )
309+
310+ self.assertEqual(0, result.exit_code)
311+ self.assertEqual(
312+ [
313+ call(
314+ ["apt", "update"],
315+ cwd=Path("/root/lpcraft/project"),
316+ env={},
317+ stdout=ANY,
318+ stderr=ANY,
319+ ),
320+ call(
321+ ["apt", "install", "-y", "git"],
322+ cwd=Path("/root/lpcraft/project"),
323+ env={},
324+ stdout=ANY,
325+ stderr=ANY,
326+ ),
327+ call(
328+ ["bash", "--noprofile", "--norc", "-ec", "ls -la"],
329+ cwd=Path("/root/lpcraft/project"),
330+ env={},
331+ stdout=ANY,
332+ stderr=ANY,
333+ ),
334+ ],
335+ execute_run.call_args_list,
336+ )
337+
338+ mock_info = launcher.return_value.push_file_io.call_args_list[0][1]
339+ self.assertEqual(
340+ Path("/etc/apt/sources.list"), mock_info["destination"]
341+ )
342+ self.assertEqual("repo info", mock_info["content"].read().decode())
343+ self.assertEqual("0644", mock_info["file_mode"])
344+ self.assertEqual("root", mock_info["group"])
345+ self.assertEqual("root", mock_info["user"])
346diff --git a/lpcraft/main.py b/lpcraft/main.py
347index 42a68e5..4a90d01 100644
348--- a/lpcraft/main.py
349+++ b/lpcraft/main.py
350@@ -97,6 +97,11 @@ def main(argv: Optional[List[str]] = None) -> int:
351 "for the pipeline after the running it."
352 ),
353 )
354+ parser_run.add_argument(
355+ "--replace-sources-list",
356+ action="append",
357+ help="Overwrite /etc/apt/sources.list.",
358+ )
359 parser_run.set_defaults(func=run)
360
361 parser_run_one = subparsers.add_parser(
362@@ -129,6 +134,11 @@ def main(argv: Optional[List[str]] = None) -> int:
363 metavar="N",
364 help="Run only the Nth job with the given name (indexing from 0).",
365 )
366+ parser_run_one.add_argument(
367+ "--replace-sources-list",
368+ action="append",
369+ help="Overwrite /etc/apt/sources.list.",
370+ )
371 parser_run_one.set_defaults(func=run_one)
372
373 parser_version = subparsers.add_parser(

Subscribers

People subscribed via source and target branches