summaryrefslogtreecommitdiffstats
path: root/bin/sbuild-qemu-create
blob: ca59404cbca8ea23e4190405b49a94cb5e29ac7f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#!/usr/bin/python3
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright © 2020-2024 Christian Kastner <ckk@debian.org>
#             2024      Johannes Schauer Marin Rodrigues <josch@debian.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see
# <http://www.gnu.org/licenses/>.
#
#######################################################################


import argparse
import os
import subprocess
import sys
import textwrap


SUPPORTED_ARCHS = [
    'amd64',
    'arm64',
    'armhf',
    'i386',
    'ppc64el',
]

DEFAULT_ARCH = subprocess.check_output(
    ['dpkg', '--print-architecture'],
    text=True,
).strip()


def gen_sourceslist(mirror, dist, components, with_bpo=False):
    """Generate a sources.list file for the VM.

    If distribution ends with '-backports', then its base distribution will
    automatically be added.

    If distribution is 'experimental', then the 'unstable' distribution will
    automatically be added.
    """
    sl = textwrap.dedent(
        f"""\
        deb     {mirror} {dist} {' '.join(components)}
        deb-src {mirror} {dist} {' '.join(components)}
        """)
    if dist == 'experimental':
        sl += textwrap.dedent(
            f"""
            deb     {mirror} unstable {' '.join(components)}
            deb-src {mirror} unstable {' '.join(components)}
            """)
    elif dist.endswith('-backports'):
        sl += textwrap.dedent(
            f"""
            deb     {mirror} {dist[:-10]} {' '.join(components)}
            deb-src {mirror} {dist[:-10]} {' '.join(components)}
            """)
    return sl


def main():
    parser = argparse.ArgumentParser(
        description="Builds images for use with qemu-sbuild and autopkgtest.",
        epilog="Note that qemu-sbuild-create is just a simple wrapper around "
               "autopkgtest-build-qemu(1) that automates a few additional "
               "steps commonly performed with package-building images.",
    )
    parser.add_argument(
        '--arch',
        action='store',
        default=DEFAULT_ARCH,
        help="Architecture to use. Default is the host architecture. "
             "Currently supported architectures are: "
            f"{', '.join(SUPPORTED_ARCHS)}.",
    )
    parser.add_argument(
        '--install-packages',
        action='store',
        help="Comma-separated list of additional packages to install in the "
             "image using 'apt-get install' from within the image.",
    )
    parser.add_argument(
        '--extra-deb',
        action='append',
        help="Package file (.deb) from the local filesystem to install. Can "
             "be specified more than once.",
    )
    parser.add_argument(
        '--components',
        action='store',
        default='main',
        help="Comma-separated list of components to use with sources.list "
             "entries. Default: main.",
    )
    # Not yet merged into autopkgtest, see #973457
    # parser.add_argument('--variant', action='store')
    parser.add_argument(
        '--skel',
        type=str,
        action='store',
        help="Skeleton directory to use for /root.",
    )
    parser.add_argument(
        '--authorized-keys',
        metavar='FILE',
        action='store',
        help="Install this file as /root/.ssh/authorized_keys within the "
             "guest. This will automatically install the 'openssh-server' "
             "package. This supersedes any copying of this file by the "
             "--skel option.",
    )
    parser.add_argument(
        '--size',
        type=str,
        action='store',
        default='10G',
        help="Image size to use. Note that the images are in qcow2 format, so "
             "they won't consume that space right away. Default: 10G.",
    )
    parser.add_argument(
        '-o', '--out-file',
        action='store',
        help="Output filename. If not supplied, then "
             "DIST-autopkgtest-ARCH.img will be used.",
    )
    parser.add_argument(
        '--noexec',
        action='store_true',
        help="Don't actually do anything. Just print the autopkgtest-build-"
             "qemu(1) command string that would be executed, and then exit.",
    )
    parser.add_argument(
        '--boot',
        choices=['auto', 'bios', 'efi', 'ieee1275', 'none'],
        default='auto',
        help="How the image should boot. Default is BIOS on amd64 and i386, "
             "EFI on arm64 and armhf, and IEEE1275 on ppc64el.",
        )
    parser.add_argument(
        'distribution',
        action='store',
        help="The distribution to debootstrap.",
    )
    parser.add_argument(
        'mirror',
        action='store',
        help="The mirror to use for the installation. Note that the mirror "
             "will also be used for the sources.list file in the VM.",
    )
    parsed = parser.parse_args()

    # Internal args
    if parsed.arch not in SUPPORTED_ARCHS:
        print(
            f"Unsupported architecture: {parsed.arch}",
            file=sys.stderr,
        )
        sys.exit(1)
    if parsed.out_file:
        out_file = parsed.out_file
    else:
        out_file = f"{parsed.distribution}-autopkgtest-{parsed.arch}.img"
    components = parsed.components.split(',')

    # We can only pass arguments to the other tools via the environment
    #
    # Args consumed by the modscript
    if parsed.skel:
        os.environ['SQC_SKEL'] = parsed.skel
        print('export SQC_SKEL=' + os.environ['SQC_SKEL'])
    if parsed.authorized_keys:
        os.environ['SQC_AUTH_KEYS'] = parsed.authorized_keys
        print('export SQC_AUTH_KEYS=' + os.environ['SQC_AUTH_KEYS'])
    if parsed.extra_deb:
        extra_debs = ' '.join(parsed.extra_deb)
        os.environ['SQC_EXTRA_DEBS'] = extra_debs
        print('export SQC_EXTRA_DEBS=' + extra_debs)
    if parsed.install_packages:
        install_packages = parsed.install_packages.replace(',', ' ')
        os.environ['SQC_INSTALL_PACKAGES'] = install_packages
        print('export SQC_INSTALL_PACKAGES=' + install_packages)
    # Args consumed by autopkgtest-build-qemu
    os.environ['AUTOPKGTEST_APT_SOURCES'] = gen_sourceslist(
        parsed.mirror,
        parsed.distribution,
        components,
    )
    print('sources.list (via export AUTOPKGTEST_APT_SOURCES)\n------------')
    print(os.environ['AUTOPKGTEST_APT_SOURCES'])

    #if parsed.variant:
    #    args += ['--variant', parsed.variant]
    dist = parsed.distribution
    if dist.endswith('-backports'):
        dist = dist[:-len('-backports')]
    elif dist == 'experimental':
        dist = 'unstable'

    args = [
        'autopkgtest-build-qemu',
        '--architecture',   parsed.arch,
        '--mirror',         parsed.mirror,
        '--size',           parsed.size,
        '--script',         '/usr/share/sbuild/sbuild-qemu-create-modscript',
        '--boot',           parsed.boot,
        dist,
        out_file,
    ]

    if os.getuid() != 0:
        print('Must be root to use this.', file=sys.stderr)
        sys.exit(1)
    os.umask(22)

    print(' '.join(str(a) for a in args))
    if not parsed.noexec:
        os.execvp(args[0], args)


if __name__ == '__main__':
    main()