Update upstream source from tag 'upstream/0.6.0'
Update to upstream version '0.6.0'
with Debian dir 47ee958a83844054b2a4b48083e01e71fb6d6650
Matthias Klumpp
1 year, 7 months ago
40 | 40 | |
41 | 41 | - name: Install dependencies |
42 | 42 | run: python -m pip install |
43 | toml types-toml | |
43 | tomlkit | |
44 | 44 | pkgconfig |
45 | 45 | flake8 |
46 | 46 | pytest |
0 | Version 0.6.0 | |
1 | ~~~~~~~~~~~~~ | |
2 | Released: 2022-10-02 | |
3 | ||
4 | Features: | |
5 | * Allow containers that run a custom command to be booted as well | |
6 | * Add configuration option to disable package caching | |
7 | * Add configuration option to set a different bootstrap tool | |
8 | * Make artifact storage in interactive mode an explicit action | |
9 | ||
10 | Bugfixes: | |
11 | * Fix pyproject.toml license/author fields | |
12 | * Don't use deprecated Lintian argument | |
13 | * Use tomlkit instead of deprecated toml | |
14 | ||
0 | 15 | Version 0.5.2 |
1 | ~~~~~~~~~~~~~~ | |
16 | ~~~~~~~~~~~~~ | |
2 | 17 | Released: 2022-02-22 |
3 | 18 | |
4 | 19 | Features: |
10 | 25 | * Do not include APT package caches in tarballs |
11 | 26 | |
12 | 27 | Version 0.5.1 |
13 | ~~~~~~~~~~~~~~ | |
28 | ~~~~~~~~~~~~~ | |
14 | 29 | Released: 2021-11-08 |
15 | 30 | |
16 | 31 | Notes: |
36 | 51 | * man: Clarify new image name / suite relations in ds-create manual page |
37 | 52 | |
38 | 53 | Version 0.5.0 |
39 | ~~~~~~~~~~~~~~ | |
54 | ~~~~~~~~~~~~~ | |
40 | 55 | Released: 2021-06-04 |
41 | 56 | |
42 | 57 | Features: |
53 | 68 | * Fix image creation if resolv.conf is a symlink |
54 | 69 | |
55 | 70 | Version 0.4.2 |
56 | ~~~~~~~~~~~~~~ | |
71 | ~~~~~~~~~~~~~ | |
57 | 72 | Released: 2021-05-24 |
58 | 73 | |
59 | 74 | Features: |
80 | 95 | Helmut Grohne, Matthias Klumpp |
81 | 96 | |
82 | 97 | Version 0.4.1 |
83 | ~~~~~~~~~~~~~~ | |
98 | ~~~~~~~~~~~~~ | |
84 | 99 | Released: 2020-12-22 |
85 | 100 | |
86 | 101 | Features: |
99 | 114 | * Disable syscall filter for some groups by default (Matthias Klumpp) |
100 | 115 | |
101 | 116 | Version 0.4.0 |
102 | ~~~~~~~~~~~~~~ | |
117 | ~~~~~~~~~~~~~ | |
103 | 118 | Released: 2020-01-20 |
104 | 119 | |
105 | 120 | Features: |
118 | 133 | * Add MANIFEST file |
119 | 134 | |
120 | 135 | Version 0.3.0 |
121 | ~~~~~~~~~~~~~~ | |
136 | ~~~~~~~~~~~~~ | |
122 | 137 | Released: 2020-01-06 |
123 | 138 | |
124 | 139 | Features: |
150 | 165 | * Ensure all data lands in its intended directories when installing |
151 | 166 | |
152 | 167 | Version 0.2.1 |
153 | ~~~~~~~~~~~~~~ | |
168 | ~~~~~~~~~~~~~ | |
154 | 169 | Released: 2019-01-10 |
155 | 170 | |
156 | 171 | Features: |
159 | 174 | * Allow creating an image with a suite and base-suite |
160 | 175 | |
161 | 176 | Version 0.2.0 |
162 | ~~~~~~~~~~~~~~ | |
177 | ~~~~~~~~~~~~~ | |
163 | 178 | Released: 2018-08-28 |
164 | 179 | |
165 | 180 | Features: |
179 | 194 | * Set environment shell |
180 | 195 | |
181 | 196 | Version 0.1.0 |
182 | ~~~~~~~~~~~~~~ | |
197 | ~~~~~~~~~~~~~ | |
183 | 198 | Released: 2018-08-20 |
184 | 199 | |
185 | 200 | Notes: |
1 | 1 | |
2 | 2 | 1. Write NEWS entries for Debspawn in the same format as usual. |
3 | 3 | |
4 | git shortlog v0.5.1.. | grep -i -v trivial | grep -v Merge > NEWS.new | |
4 | git shortlog v0.5.2.. | grep -i -v trivial | grep -v Merge > NEWS.new | |
5 | 5 | |
6 | 6 | -------------------------------------------------------------------------------- |
7 | Version 0.5.2 | |
8 | ~~~~~~~~~~~~~~ | |
9 | Released: 2021-xx-xx | |
7 | Version 0.5.3 | |
8 | ~~~~~~~~~~~~~ | |
9 | Released: 2022-xx-xx | |
10 | 10 | |
11 | 11 | Notes: |
12 | 12 | |
17 | 17 | |
18 | 18 | 2. Commit changes in Git: |
19 | 19 | |
20 | git commit -a -m "Release version 0.5.2" | |
21 | git tag -s -f -m "Release 0.5.2" v0.5.2 <gpg password> | |
20 | git commit -a -m "Release version 0.5.3" | |
21 | git tag -s -f -m "Release 0.5.3" v0.5.3 <gpg password> | |
22 | 22 | git push --tags |
23 | 23 | git push |
24 | 24 |
0 | # This file is part of debspawn. | |
0 | # This file is part of debspawn. | |
1 | 1 | # |
2 | 2 | # Debspawn is free software: you can redistribute it and/or modify |
3 | 3 | # it under the terms of the GNU Lesser General Public License as published by |
17 | 17 | # along with this software. If not, see <http://www.gnu.org/licenses/>. |
18 | 18 | |
19 | 19 | __appname__ = 'debspawn' |
20 | __version__ = '0.5.2' | |
20 | __version__ = '0.6.0' |
33 | 33 | get_random_free_uid_gid, |
34 | 34 | ) |
35 | 35 | from .utils.log import ( |
36 | input_bool, | |
36 | 37 | print_info, |
37 | 38 | print_warn, |
38 | 39 | print_error, |
40 | print_bullet, | |
39 | 41 | print_header, |
40 | 42 | print_section, |
41 | 43 | capture_console_output, |
62 | 64 | aptcache_tmp, |
63 | 65 | pkginjector, |
64 | 66 | prev_exitcode, |
65 | ): | |
67 | ) -> bool: | |
66 | 68 | '''Launch an interactive shell in the build environment''' |
67 | 69 | |
68 | 70 | # find the right directory to switch to |
95 | 97 | verbose=True, |
96 | 98 | ) |
97 | 99 | |
100 | print() | |
101 | copy_artifacts = input_bool( | |
102 | 'Should any generated build artifacts (binary/source packages, etc.) be saved?', default=False | |
103 | ) | |
104 | if copy_artifacts: | |
105 | print_bullet('Artifacts will be copied to the results directory.') | |
106 | else: | |
107 | print_bullet('Artifacts will not be kept.') | |
108 | ||
98 | 109 | if source_pkg_dir: |
99 | print() | |
100 | while True: | |
101 | try: | |
102 | copy_changes = input( | |
103 | ( | |
104 | 'Should changes to the debian/ directory be copied back to the host?\n' | |
105 | 'This will OVERRIDE all changes made on files on the host. [y/N]: ' | |
106 | ) | |
107 | ) | |
108 | except EOFError: | |
109 | copy_changes = 'n' | |
110 | if copy_changes == 'y' or copy_changes == 'Y': | |
111 | copy_changes = True | |
112 | break | |
113 | elif copy_changes == 'n' or copy_changes == 'N': | |
114 | copy_changes = False | |
115 | break | |
116 | elif not copy_changes: | |
117 | copy_changes = False | |
118 | break | |
110 | copy_changes = input_bool( | |
111 | ( | |
112 | 'Should changes to the debian/ directory be copied back to the host?\n' | |
113 | 'This will OVERRIDE all changes made on files on the host.' | |
114 | ), | |
115 | default=False, | |
116 | ) | |
119 | 117 | |
120 | 118 | if copy_changes: |
121 | 119 | print_info('Cleaning up...') |
175 | 173 | os.remove(fname) |
176 | 174 | print() |
177 | 175 | else: |
178 | print_info('Discarding build environment.') | |
176 | print_bullet('Discarding build environment.') | |
179 | 177 | else: |
180 | 178 | print_info('Can not copy back changes as original package directory is unknown.') |
179 | ||
180 | return copy_artifacts | |
181 | 181 | |
182 | 182 | |
183 | 183 | def internal_execute_build( |
292 | 292 | print() # extra blank line after Lintian output |
293 | 293 | |
294 | 294 | if interact: |
295 | interact_with_build_environment( | |
295 | ri = interact_with_build_environment( | |
296 | 296 | osbase, |
297 | 297 | instance_dir, |
298 | 298 | machine_name, |
302 | 302 | pkginjector=pkginjector, |
303 | 303 | prev_exitcode=r, |
304 | 304 | ) |
305 | # exit with status of previous exit code | |
306 | if r != 0: | |
305 | # if we exit with a non-True result, we stop here and don't proceed | |
306 | # with the next steps that save artifacts. | |
307 | if not ri: | |
307 | 308 | return False |
308 | 309 | |
309 | 310 | build_dir_size = get_tree_size(pkg_dir) |
277 | 277 | custom_command, |
278 | 278 | options.build_dir, |
279 | 279 | options.artifacts_dir, |
280 | boot=options.boot, | |
280 | 281 | init_command=options.init_command, |
281 | 282 | copy_command=options.external_commad, |
282 | 283 | header_msg=options.header, |
667 | 668 | 'List one or more additional permissions to grant the container. Takes a comma-separated ' |
668 | 669 | 'list of capability names.' |
669 | 670 | ), |
671 | ) | |
672 | sp.add_argument( | |
673 | '--boot', | |
674 | action='store_true', | |
675 | dest='boot', | |
676 | help='Boot container image (requires the image to contain an init system).', | |
670 | 677 | ) |
671 | 678 | sp.add_argument('command', action='store', nargs='*', default=None, help='The command to run.') |
672 | 679 |
19 | 19 | import os |
20 | 20 | import sys |
21 | 21 | |
22 | import toml | |
22 | import tomlkit | |
23 | 23 | |
24 | 24 | thisfile = __file__ |
25 | 25 | if not os.path.isabs(thisfile): |
45 | 45 | if os.path.isfile(fname): |
46 | 46 | with open(fname) as f: |
47 | 47 | try: |
48 | cdata = toml.load(f) | |
49 | except toml.TomlDecodeError as e: | |
48 | cdata = tomlkit.load(f) | |
49 | except tomlkit.exceptions.ParseError as e: | |
50 | 50 | print( |
51 | 51 | 'Unable to parse global configuration (global.toml): {}'.format(str(e)), |
52 | 52 | file=sys.stderr, |
70 | 70 | self._temp_dir = cdata.get('TempDir', '/var/tmp/debspawn/') |
71 | 71 | self._default_bootstrap_variant = cdata.get('DefaultBootstrapVariant', 'buildd') |
72 | 72 | self._allow_unsafe_perms = cdata.get('AllowUnsafePermissions', False) |
73 | self._cache_packages = bool(cdata.get('CachePackages', True)) | |
74 | self._bootstrap_tool = cdata.get('BootstrapTool', 'debootstrap') | |
73 | 75 | |
74 | 76 | self._syscall_filter = cdata.get('SyscallFilter', 'compat') |
75 | 77 | if self._syscall_filter == 'compat': |
125 | 127 | |
126 | 128 | @property |
127 | 129 | def syscall_filter(self) -> list: |
130 | """Customize which syscalls should be filtered.""" | |
128 | 131 | return self._syscall_filter |
129 | 132 | |
130 | 133 | @property |
131 | 134 | def allow_unsafe_perms(self) -> bool: |
135 | """Whether usage of unsafe permissions is allowed.""" | |
132 | 136 | return self._allow_unsafe_perms |
137 | ||
138 | @property | |
139 | def cache_packages(self) -> bool: | |
140 | """Whether APT packages should be cached by debspawn.""" | |
141 | return self._cache_packages | |
142 | ||
143 | @property | |
144 | def bootstrap_tool(self) -> str: | |
145 | """The chroot bootstrap tool that we should use.""" | |
146 | return self._bootstrap_tool | |
133 | 147 | |
134 | 148 | def __init__(self, fname=None): |
135 | 149 | if not GlobalConfig._instance: |
271 | 271 | cmd = ['lintian', |
272 | 272 | '-I', # infos by default |
273 | 273 | '--pedantic', # pedantic hints by default, |
274 | '--no-tag-display-limit' # display all tags found (even if that may be a lot occasionally) | |
274 | '--tag-display-limit', '0', # display all tags found (even if that may be a lot occasionally) | |
275 | 275 | ] |
276 | 276 | run_command(cmd) |
277 | 277 |
194 | 194 | |
195 | 195 | from . import __version__ |
196 | 196 | from .nspawn import systemd_version, systemd_detect_virt |
197 | from .osbase import debootstrap_version, print_container_base_image_info | |
197 | from .osbase import bootstrap_tool_version, print_container_base_image_info | |
198 | 198 | |
199 | 199 | print('Debspawn Status Report', end='') |
200 | 200 | sys.stdout.flush() |
212 | 212 | print('Platform:', platform.platform(aliased=True)) |
213 | 213 | print('Virtualization:', systemd_detect_virt()) |
214 | 214 | print('Systemd-nspawn version:', systemd_version()) |
215 | print('Debootstrap version:', debootstrap_version()) | |
215 | print('Bootstrap tool:', '{} {}'.format(gconf.bootstrap_tool, bootstrap_tool_version(gconf))) | |
216 | 216 | |
217 | 217 | print_section('Container image list') |
218 | 218 | print_container_base_image_info(gconf) |
17 | 17 | # along with this software. If not, see <http://www.gnu.org/licenses/>. |
18 | 18 | |
19 | 19 | import os |
20 | import typing as T | |
20 | 21 | import platform |
21 | from typing import Union | |
22 | import subprocess | |
22 | 23 | |
23 | 24 | from .utils import ( |
24 | 25 | safe_run, |
97 | 98 | syscall_filter: list[str] = None, |
98 | 99 | env_vars: dict[str, str] = None, |
99 | 100 | private_users: bool = False, |
100 | ): | |
101 | nowait: bool = False, | |
102 | ) -> T.Union[subprocess.CompletedProcess, subprocess.Popen]: | |
101 | 103 | ''' |
102 | 104 | Execute systemd-nspawn with the given parameters. |
103 | 105 | Mess around with cgroups if necessary. |
151 | 153 | # if we boot the container, we also register it with machinectl, otherwise |
152 | 154 | # we run an unregistered container with the command as PID2 |
153 | 155 | cmd.append('-b') |
156 | cmd.append('--notify-ready=yes') | |
154 | 157 | else: |
155 | 158 | cmd.append('--register=no') |
156 | 159 | cmd.append('-a') |
186 | 189 | |
187 | 190 | cmd.extend(parameters) |
188 | 191 | |
189 | proc = run_forwarded(cmd) | |
190 | return proc.returncode | |
192 | if nowait: | |
193 | return subprocess.Popen(cmd, shell=False, stdin=subprocess.DEVNULL) | |
194 | else: | |
195 | return run_forwarded(cmd) | |
191 | 196 | |
192 | 197 | |
193 | 198 | def nspawn_run_persist( |
195 | 200 | base_dir, |
196 | 201 | machine_name, |
197 | 202 | chdir, |
198 | command: Union[list[str], str] = None, | |
199 | flags: Union[list[str], str] = None, | |
203 | command: T.Union[list[str], str] = None, | |
204 | flags: T.Union[list[str], str] = None, | |
200 | 205 | *, |
201 | 206 | tmp_apt_cache_dir: str = None, |
202 | 207 | pkginjector: PackageInjector = None, |
222 | 227 | params = [ |
223 | 228 | '--chdir={}'.format(chdir), |
224 | 229 | '--link-journal=no', |
225 | '--bind={}:/var/cache/apt/archives/'.format(aptcache_tmp_dir), | |
226 | 230 | ] |
231 | if aptcache_tmp_dir: | |
232 | params.append('--bind={}:/var/cache/apt/archives/'.format(aptcache_tmp_dir)) | |
227 | 233 | if pkginjector and pkginjector.instance_repo_dir: |
228 | 234 | params.append('--bind={}:/srv/extra-packages/'.format(pkginjector.instance_repo_dir)) |
229 | 235 | |
231 | 237 | params.append('--personality={}'.format(personality)) |
232 | 238 | params.extend(flags) |
233 | 239 | params.extend(['-{}D'.format('' if verbose else 'q'), base_dir]) |
234 | params.extend(command) | |
240 | ||
241 | # nspawn can not run a command in a booted container on its own | |
242 | if not boot: | |
243 | params.extend(command) | |
244 | sdns_nowait = boot and command | |
235 | 245 | |
236 | 246 | # ensure the temporary apt cache is up-to-date |
237 | osbase.aptcache.create_instance_cache(aptcache_tmp_dir) | |
247 | if aptcache_tmp_dir: | |
248 | osbase.aptcache.create_instance_cache(aptcache_tmp_dir) | |
238 | 249 | |
239 | 250 | # run command in container |
240 | ret = _execute_sdnspawn( | |
251 | ns_proc = _execute_sdnspawn( | |
241 | 252 | osbase, |
242 | 253 | params, |
243 | 254 | machine_name, |
246 | 257 | env_vars=env_vars, |
247 | 258 | private_users=private_users, |
248 | 259 | boot=boot, |
260 | nowait=sdns_nowait, | |
249 | 261 | ) |
250 | 262 | |
251 | # archive APT cache, so future runs of this command are faster | |
252 | osbase.aptcache.merge_from_dir(aptcache_tmp_dir) | |
263 | if not sdns_nowait: | |
264 | ret = ns_proc.returncode | |
265 | else: | |
266 | try: | |
267 | import time | |
268 | ||
269 | # the container is (hopefully) running now, but let's check for that | |
270 | time_ac_start = time.time() | |
271 | container_booted = False | |
272 | while (time.time() - time_ac_start) < 60: | |
273 | scisr_out, _, _ = run_command( | |
274 | [ | |
275 | 'systemd-run', | |
276 | '-GP', | |
277 | '--wait', | |
278 | '-qM', | |
279 | machine_name, | |
280 | 'systemctl', | |
281 | 'is-system-running', | |
282 | ] | |
283 | ) | |
284 | ||
285 | # check if we are actually running, try again later if not | |
286 | if scisr_out.strip() in ('running', 'degraded'): | |
287 | print() | |
288 | container_booted = True | |
289 | break | |
290 | time.sleep(0.5) | |
291 | ||
292 | if container_booted: | |
293 | sdr_cmd = [ | |
294 | 'systemd-run', | |
295 | '-GP', | |
296 | '--wait', | |
297 | '-qM', | |
298 | machine_name, | |
299 | '--working-directory', | |
300 | chdir, | |
301 | ] + command | |
302 | proc = run_forwarded(sdr_cmd) | |
303 | ret = proc.returncode | |
304 | else: | |
305 | ret = 7 | |
306 | print_error('Timed out while waiting for the container to boot.') | |
307 | finally: | |
308 | run_forwarded(['machinectl', 'poweroff', machine_name]) | |
309 | try: | |
310 | ns_proc.wait(30) | |
311 | except subprocess.TimeoutExpired: | |
312 | ns_proc.terminate() | |
313 | ||
314 | # archive APT cache, so future runs of this command are faster (unless disabled in configuration) | |
315 | if aptcache_tmp_dir: | |
316 | osbase.aptcache.merge_from_dir(aptcache_tmp_dir) | |
253 | 317 | |
254 | 318 | return ret |
255 | 319 | |
256 | if tmp_apt_cache_dir: | |
320 | if not osbase.cache_packages: | |
321 | # APT package caching was explicitly disabled by the user | |
322 | ret = run_nspawn_with_aptcache(None) | |
323 | elif tmp_apt_cache_dir: | |
324 | # we will be reusing an externally provided temporary APT cache directory | |
257 | 325 | ret = run_nspawn_with_aptcache(tmp_apt_cache_dir) |
258 | 326 | else: |
327 | # we will create our own temporary APT cache dir | |
259 | 328 | with temp_dir('aptcache-' + machine_name) as aptcache_tmp: |
260 | 329 | ret = run_nspawn_with_aptcache(aptcache_tmp) |
261 | 330 | |
267 | 336 | base_dir, |
268 | 337 | machine_name, |
269 | 338 | chdir, |
270 | command: Union[list[str], str] = None, | |
271 | flags: Union[list[str], str] = None, | |
339 | command: T.Union[list[str], str] = None, | |
340 | flags: T.Union[list[str], str] = None, | |
272 | 341 | allowed: list[str] = None, |
273 | 342 | syscall_filter: list[str] = None, |
274 | 343 | env_vars: dict[str, str] = None, |
301 | 370 | syscall_filter=syscall_filter, |
302 | 371 | env_vars=env_vars, |
303 | 372 | private_users=private_users, |
304 | ) | |
373 | ).returncode | |
305 | 374 | |
306 | 375 | |
307 | 376 | def nspawn_make_helper_cmd(flags, build_uid: int): |
328 | 397 | chdir='/tmp', |
329 | 398 | *, |
330 | 399 | build_uid: int, |
331 | nspawn_flags: Union[list[str], str] = None, | |
400 | nspawn_flags: T.Union[list[str], str] = None, | |
332 | 401 | allowed: list[str] = None, |
333 | 402 | env_vars: dict[str, str] = None, |
334 | 403 | private_users: bool = False, |
34 | 34 | print_section, |
35 | 35 | format_filesize, |
36 | 36 | ) |
37 | from .config import GlobalConfig | |
37 | 38 | from .nspawn import nspawn_run_persist, nspawn_run_helper_persist |
38 | 39 | from .aptcache import APTCache |
39 | 40 | from .utils.env import ensure_root, get_owner_uid_gid, get_random_free_uid_gid |
42 | 43 | from .utils.zstd_tar import ensure_tar_zstd, compress_directory, decompress_tarball |
43 | 44 | |
44 | 45 | |
45 | def debootstrap_version(): | |
46 | def bootstrap_tool_version(gconf=None): | |
47 | if not gconf: | |
48 | gconf = GlobalConfig() | |
46 | 49 | ds_version = 'unknown' |
47 | 50 | try: |
48 | out, _, _ = safe_run(['debootstrap', '--version']) | |
51 | out, _, _ = safe_run([gconf.bootstrap_tool, '--version']) | |
49 | 52 | parts = out.strip().split(' ', 2) |
50 | 53 | ds_version = parts[0 if len(parts) < 2 else 1] |
51 | 54 | except Exception as e: |
52 | print_warn('Unable to determine debootstrap version: {}'.format(e)) | |
55 | print_warn('Unable to determine bootstrap tool version: {}'.format(e)) | |
53 | 56 | |
54 | 57 | return ds_version |
55 | 58 | |
59 | 62 | Describes an OS base registered with debspawn |
60 | 63 | ''' |
61 | 64 | |
62 | def __init__(self, gconf, suite, arch, variant=None, *, base_suite=None, custom_name=None, cachekey=None): | |
65 | def __init__( | |
66 | self, | |
67 | gconf: GlobalConfig, | |
68 | suite: str, | |
69 | arch: str, | |
70 | variant=None, | |
71 | *, | |
72 | base_suite=None, | |
73 | custom_name=None, | |
74 | cachekey=None, | |
75 | ): | |
63 | 76 | self._gconf = gconf |
64 | 77 | self._suite = suite |
65 | 78 | self._base_suite = base_suite |
80 | 93 | |
81 | 94 | self._parameters_checked = False |
82 | 95 | self._aptcache = APTCache(self) |
96 | ||
97 | # debootstrap-compatible tools that we know about | |
98 | self._known_bootstrap_tools = ('debootstrap', 'mmdebstrap', 'qemu-debootstrap') | |
83 | 99 | |
84 | 100 | # get a fresh UID to give to our build user within the container |
85 | 101 | self._builder_uid = get_random_free_uid_gid()[0] |
194 | 210 | return self._aptcache |
195 | 211 | |
196 | 212 | @property |
213 | def cache_packages(self) -> bool: | |
214 | return self._gconf.cache_packages | |
215 | ||
216 | @property | |
197 | 217 | def has_base_suite(self) -> bool: |
198 | 218 | return True if self.base_suite and self.base_suite != self.suite else False |
199 | 219 | |
304 | 324 | data['AllowRecommends'] = True |
305 | 325 | if with_init: |
306 | 326 | data['IncludesInit'] = True |
327 | data['BootstrapTool'] = self._gconf.bootstrap_tool | |
307 | 328 | |
308 | 329 | with open(self.get_config_location(), 'wt') as f: |
309 | 330 | f.write(json.dumps(data, sort_keys=True, indent=4)) |
384 | 405 | if not extra_suites: |
385 | 406 | extra_suites = [] |
386 | 407 | |
408 | bootstrap_tool_exe = self._gconf.bootstrap_tool | |
409 | if not shutil.which(bootstrap_tool_exe): | |
410 | print_error('Unable to find executable for bootstrap tool "{}".'.format(bootstrap_tool_exe)) | |
411 | return False | |
412 | ||
387 | 413 | # ensure image location exists |
388 | 414 | Path(self._gconf.osroots_dir).mkdir(parents=True, exist_ok=True) |
389 | 415 | |
392 | 418 | else: |
393 | 419 | print_section('Creating new base: {} [{}]'.format(self.suite, self.arch)) |
394 | 420 | |
421 | print('Bootstrap tool:', '{} {}'.format(bootstrap_tool_exe, bootstrap_tool_version(self._gconf))) | |
422 | if bootstrap_tool_exe not in self._known_bootstrap_tools: | |
423 | print_warn('Using unfamiliar bootstrap tool: {}'.format(bootstrap_tool_exe)) | |
395 | 424 | if self._custom_name: |
396 | 425 | print('Custom name: {}'.format(self.name)) |
397 | 426 | print('Using mirror: {}'.format(mirror if mirror else 'default')) |
403 | 432 | include_pkgs = ['python3-minimal', 'eatmydata'] |
404 | 433 | if with_init: |
405 | 434 | include_pkgs.append('systemd-sysv') |
406 | cmd = ['debootstrap', '--arch={}'.format(self.arch), '--include=' + ','.join(include_pkgs)] | |
435 | cmd = [bootstrap_tool_exe, '--arch={}'.format(self.arch), '--include=' + ','.join(include_pkgs)] | |
407 | 436 | if components: |
408 | 437 | cmd.append('--components={}'.format(','.join(components))) |
409 | 438 | if self.variant: |
851 | 880 | command, |
852 | 881 | build_dir, |
853 | 882 | artifacts_dir, |
883 | boot: bool = False, | |
854 | 884 | init_command=None, |
855 | 885 | copy_command=False, |
856 | 886 | header_msg=None, |
995 | 1025 | nspawn_flags.extend(['--bind-ro={}:/srv/build/'.format(build_dir)]) |
996 | 1026 | |
997 | 1027 | r = nspawn_run_persist( |
998 | self, instance_dir, machine_name, chdir, command, nspawn_flags, allowed=allowed | |
1028 | self, instance_dir, machine_name, chdir, command, nspawn_flags, allowed=allowed, boot=boot | |
999 | 1029 | ) |
1000 | 1030 | if r != 0: |
1001 | 1031 | return False |
21 | 21 | listify, |
22 | 22 | temp_dir, |
23 | 23 | rmtree_mntsafe, |
24 | systemd_escape, | |
24 | 25 | format_filesize, |
25 | 26 | hardlink_or_copy, |
26 | 27 | ) |
42 | 43 | 'hardlink_or_copy', |
43 | 44 | 'format_filesize', |
44 | 45 | 'rmtree_mntsafe', |
46 | 'systemd_escape', | |
45 | 47 | ] |
126 | 126 | else: |
127 | 127 | print(prefix) |
128 | 128 | sys.stdout.flush() |
129 | ||
130 | ||
131 | def input_bool(question_text, default=False) -> bool: | |
132 | """As user a Yes/No question.""" | |
133 | if default: | |
134 | default_info = '[Y/n]' | |
135 | else: | |
136 | default_info = '[y/N]' | |
137 | while True: | |
138 | try: | |
139 | in_str = input('{} {}:'.format(question_text, default_info)) | |
140 | except EOFError: | |
141 | return default | |
142 | if in_str == 'y' or in_str == 'Y': | |
143 | return True | |
144 | elif in_str == 'n' or in_str == 'N': | |
145 | return False | |
146 | elif not in_str: | |
147 | return default | |
129 | 148 | |
130 | 149 | |
131 | 150 | class TwoStreamLogger: |
21 | 21 | import stat |
22 | 22 | import fcntl |
23 | 23 | import shutil |
24 | import typing as T | |
24 | 25 | import subprocess |
25 | from typing import Any, Optional | |
26 | 26 | from pathlib import Path |
27 | 27 | from contextlib import contextmanager |
28 | 28 | |
29 | 29 | from ..config import GlobalConfig |
30 | 30 | |
31 | 31 | |
32 | def listify(item: Any): | |
32 | def listify(item: T.Any): | |
33 | 33 | ''' |
34 | 34 | Return a list of :item, unless :item already is a lit. |
35 | 35 | ''' |
47 | 47 | os.chdir(ncwd) |
48 | 48 | |
49 | 49 | |
50 | def random_string(prefix: Optional[str] = None, count: int = 8): | |
50 | def random_string(prefix: T.Optional[str] = None, count: int = 8): | |
51 | 51 | ''' |
52 | 52 | Create a string of random alphanumeric characters of a given length, |
53 | 53 | separated with a hyphen from an optional prefix. |
62 | 62 | if prefix: |
63 | 63 | return '{}-{}'.format(prefix, rdm_id) |
64 | 64 | return rdm_id |
65 | ||
66 | ||
67 | def systemd_escape(name: str) -> T.Optional[str]: | |
68 | '''Escape a string using systemd's escaping rules.''' | |
69 | from .command import run_command | |
70 | ||
71 | out, _, ret = run_command(['systemd-escape', name]) | |
72 | if ret != 0: | |
73 | return None | |
74 | return out.strip() | |
65 | 75 | |
66 | 76 | |
67 | 77 | @contextmanager |
259 | 269 | # This can only happen if someone replaces |
260 | 270 | # a directory with a symlink after the call to |
261 | 271 | # os.scandir or stat.S_ISDIR above. |
262 | raise OSError("Cannot call rmtree on a symbolic " "link") | |
272 | raise OSError('Cannot call rmtree on a symbolic link') | |
263 | 273 | except OSError: |
264 | 274 | onerror(os.path.islink, fullname, sys.exc_info()) |
265 | 275 | finally: |
249 | 249 | host's <filename>/dev</filename> and <filename>/proc</filename> filesystems |
250 | 250 | available from within the container. See the <parameter>--allow</parameter> option |
251 | 251 | of <command>&package; run</command> for more details. |
252 | (Default: <code>false</code>) | |
253 | </para> | |
254 | </listitem> | |
255 | </varlistentry> | |
256 | ||
257 | <varlistentry> | |
258 | <term><option>CachePackages</option></term> | |
259 | <listitem> | |
260 | <para> | |
261 | Boolean option. If set to <literal>false</literal>, <command>&package;</command> will not | |
262 | manage its own local cache of APT packages, but will instead always try to download | |
263 | them. It is only recommended to change this option if you are already running a separate APT | |
264 | package repository mirror or a caching proxy such as apt-cacher-ng(8). | |
265 | (Default: <code>true</code>) | |
266 | </para> | |
267 | </listitem> | |
268 | </varlistentry> | |
269 | ||
270 | <varlistentry> | |
271 | <term><option>BootstrapTool</option></term> | |
272 | <listitem> | |
273 | <para> | |
274 | Set the bootstrap tool that should be used for bootstrapping new images. | |
275 | The tool should have an interface compatible with debootstrap(8). This option | |
276 | allows to use alternative tools like mmdebstrap(1) with <command>&package;</command>. | |
277 | (Default: <code>debootstrap</code>) | |
252 | 278 | </para> |
253 | 279 | </listitem> |
254 | 280 | </varlistentry> |
1 | 1 | name = "debspawn" |
2 | 2 | description = "Debian package builder and build helper using systemd-nspawn" |
3 | 3 | authors = [ |
4 | "Matthias Klumpp <matthias@tenstral.net>" | |
4 | {name = "Matthias Klumpp", email = "matthias@tenstral.net"}, | |
5 | 5 | ] |
6 | license = "LGPL-3.0-or-later" | |
6 | license = {text="LGPL-3.0-or-later"} | |
7 | 7 | readme = "README.md" |
8 | python = "^3.9" | |
9 | homepage = "https://github.com/lkhq/debspawn" | |
10 | repository = "https://github.com/lkhq/debspawn" | |
8 | requires-python = ">=3.9" | |
9 | dynamic = ['version'] | |
10 | ||
11 | [project.urls] | |
12 | Documentation = "https://github.com/lkhq/debspawn" | |
13 | Source = "https://github.com/lkhq/debspawn" | |
11 | 14 | |
12 | 15 | [build-system] |
13 | 16 | requires = ["setuptools", "wheel", "pkgconfig"] |
98 | 98 | |
99 | 99 | scripts = ['debspawn.py'] |
100 | 100 | |
101 | install_requires = ['toml>=0.10'] | |
101 | install_requires = ['tomlkit>=0.8'] | |
102 | 102 | |
103 | 103 | setup( |
104 | 104 | name=__appname__, |
117 | 117 | include_package_data=True, |
118 | 118 | # |
119 | 119 | packages=packages, |
120 | cmdclass=cmdclass, # type: ignore | |
120 | cmdclass=cmdclass, | |
121 | 121 | package_data=package_data, |
122 | 122 | scripts=scripts, |
123 | 123 | install_requires=install_requires, |