This file is indexed.

/usr/lib/python3/dist-packages/provisioningserver/import_images/boot_resources.py is in python3-maas-provisioningserver 2.4.0~beta2-6865-gec43e47e6-0ubuntu1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# Copyright 2014-2017 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

__all__ = [
    'import_images',
    'main',
    'main_with_services',
    'make_arg_parser',
    ]

from argparse import ArgumentParser
import errno
from io import StringIO
import os

from provisioningserver.boot import BootMethodRegistry
from provisioningserver.config import (
    BootSources,
    ClusterConfiguration,
)
from provisioningserver.events import (
    EVENT_TYPES,
    try_send_rack_event,
)
from provisioningserver.import_images.cleanup import (
    cleanup_snapshots_and_cache,
)
from provisioningserver.import_images.download_descriptions import (
    download_all_image_descriptions,
)
from provisioningserver.import_images.download_resources import (
    download_all_boot_resources,
)
from provisioningserver.import_images.helpers import maaslog
from provisioningserver.import_images.keyrings import write_all_keyrings
from provisioningserver.import_images.product_mapping import map_products
from provisioningserver.rpc import getRegionClient
from provisioningserver.utils.fs import (
    atomic_symlink,
    atomic_write,
    read_text_file,
    tempdir,
)
from twisted.internet.defer import inlineCallbacks
from twisted.python.filepath import FilePath


class NoConfigFile(Exception):
    """Raised when the config file for the script doesn't exist."""


def link_bootloaders(destination):
    """Link the all the required file from each bootloader method.
    :param destination: Directory where the loaders should be stored.
    """
    for _, boot_method in BootMethodRegistry:
        try:
            boot_method.link_bootloader(destination)
        except BaseException:
            msg = "Unable to link the %s bootloader.", boot_method.name
            try_send_rack_event(EVENT_TYPES.RACK_IMPORT_ERROR, msg)
            maaslog.error(msg)


def make_arg_parser(doc):
    """Create an `argparse.ArgumentParser` for this script."""

    parser = ArgumentParser(description=doc)
    parser.add_argument(
        '--sources-file', action="store", required=True,
        help=(
            "Path to YAML file defining import sources. "
            "See this script's man page for a description of "
            "that YAML file's format."
        )
    )
    return parser


def meta_contains(storage, content):
    """Does the `maas.meta` file match `content`?

    If the file's contents match the latest data, there is no need to update.

    The file's timestamp is also updated to now to reflect the last time
    that this import was run.
    """
    current_meta = os.path.join(storage, 'current', 'maas.meta')
    exists = os.path.isfile(current_meta)
    if exists:
        # Touch file to the current timestamp so that the last time this
        # import ran can be determined.
        os.utime(current_meta, None)
    return exists and content == read_text_file(current_meta)


def update_current_symlink(storage, latest_snapshot):
    """Symlink `latest_snapshot` as the "current" snapshot."""
    atomic_symlink(latest_snapshot, os.path.join(storage, 'current'))


def write_snapshot_metadata(snapshot, meta_file_content):
    """Write "maas.meta" file.

    :param meta_file_content: A Unicode string (`str`) containing JSON using
        only ASCII characters.
    """
    meta_file = os.path.join(snapshot, 'maas.meta')
    atomic_write(meta_file_content.encode("ascii"), meta_file, mode=0o644)


def read_sources(sources_yaml):
    """Read boot resources config file.

    :param sources_yaml: Path to a YAML file containing a list of boot
        resource definitions.
    :return: A dict representing the boot-resources configuration.
    :raise NoConfigFile: If the configuration file was not present.
    """
    # The config file is required.  We do not fall back to defaults if it's
    # not there.
    try:
        return BootSources.load(filename=sources_yaml)
    except IOError as ex:
        if ex.errno == errno.ENOENT:
            # No config file. We have helpful error output for this.
            raise NoConfigFile(ex)
        else:
            # Unexpected error.
            raise


def parse_sources(sources_yaml):
    """Given a YAML `config` string, return a `BootSources` for it."""
    return BootSources.parse(StringIO(sources_yaml))


def import_images(sources):
    """Import images.  Callable from the command line.

    :param config: An iterable of dicts representing the sources from
        which boot images will be downloaded.
    """
    if len(sources) == 0:
        msg = "Can't import: region did not provide a source."
        try_send_rack_event(EVENT_TYPES.RACK_IMPORT_WARNING, msg)
        maaslog.warning(msg)
        return False

    msg = "Starting rack boot image import"
    maaslog.info(msg)
    try_send_rack_event(EVENT_TYPES.RACK_IMPORT_INFO, msg)

    with ClusterConfiguration.open() as config:
        storage = FilePath(config.tftp_root).parent().path

    with tempdir('keyrings') as keyrings_path:
        # XXX: Band-aid to ensure that the keyring_data is bytes. Future task:
        # try to figure out why this sometimes happens.
        for source in sources:
            if ('keyring_data' in source and
                    not isinstance(source['keyring_data'], bytes)):
                source['keyring_data'] = source['keyring_data'].encode('utf-8')

        # We download the keyrings now  because we need them for both
        # download_all_image_descriptions() and
        # download_all_boot_resources() later.
        sources = write_all_keyrings(keyrings_path, sources)

        # The region produces a SimpleStream which is similar, but not
        # identical to the actual SimpleStream. These differences cause
        # validation to fail. So grab everything from the region and trust it
        # did proper filtering before the rack.
        image_descriptions = download_all_image_descriptions(
            sources, validate_products=False)
        if image_descriptions.is_empty():
            msg = (
                "Finished importing boot images, the region does not have "
                "any boot images available.")
            try_send_rack_event(EVENT_TYPES.RACK_IMPORT_WARNING, msg)
            maaslog.warning(msg)
            return False

        meta_file_content = image_descriptions.dump_json()
        if meta_contains(storage, meta_file_content):
            maaslog.info(
                "Finished importing boot images, the region does not "
                "have any new images.")
            try_send_rack_event(EVENT_TYPES.RACK_IMPORT_INFO, msg)
            maaslog.info(msg)
            return False

        product_mapping = map_products(image_descriptions)

        try:
            snapshot_path = download_all_boot_resources(
                sources, storage, product_mapping)
        except Exception as e:
            try_send_rack_event(
                EVENT_TYPES.RACK_IMPORT_ERROR,
                "Unable to import boot images: %s" % e)
            maaslog.error(
                "Unable to import boot images; cleaning up failed snapshot "
                "and cache.")
            # Cleanup snapshots and cache since download failed.
            cleanup_snapshots_and_cache(storage)
            raise

    maaslog.info("Writing boot image metadata.")
    write_snapshot_metadata(snapshot_path, meta_file_content)

    maaslog.info("Linking boot images snapshot %s" % snapshot_path)
    link_bootloaders(snapshot_path)

    # If we got here, all went well.  This is now truly the "current" snapshot.
    update_current_symlink(storage, snapshot_path)

    # Now cleanup the old snapshots and cache.
    maaslog.info('Cleaning up old snapshots and cache.')
    cleanup_snapshots_and_cache(storage)

    # Import is now finished.
    msg = "Finished importing boot images."
    maaslog.info(msg)
    try_send_rack_event(EVENT_TYPES.RACK_IMPORT_INFO, msg)
    return True


def main(args):
    """Entry point for the command-line import script.

    :param args: Command-line arguments as parsed by the `ArgumentParser`
        returned by `make_arg_parser`.
    :raise NoConfigFile: If a config file is specified but doesn't exist.
    """
    sources = read_sources(args.sources_file)
    import_images(sources=sources)


def main_with_services(args):
    """The *real* entry point for the command-line import script.

    This sets up the necessary RPC services before calling `main`, then clears
    up behind itself.

    :param args: Command-line arguments as parsed by the `ArgumentParser`
        returned by `make_arg_parser`.
    :raise NoConfigFile: If a config file is specified but doesn't exist.

    """
    from sys import stderr
    import traceback

    from provisioningserver import services
    from provisioningserver.rpc.clusterservice import ClusterClientService
    from provisioningserver.rpc.exceptions import NoConnectionsAvailable
    from provisioningserver.utils.twisted import retries, pause
    from twisted.internet import reactor
    from twisted.internet.threads import deferToThread

    @inlineCallbacks
    def start_services():
        rpc_service = ClusterClientService(reactor)
        rpc_service.setName("rpc")
        rpc_service.setServiceParent(services)

        yield services.startService()

        for elapsed, remaining, wait in retries(15, 1, reactor):
            try:
                yield getRegionClient()
            except NoConnectionsAvailable:
                yield pause(wait, reactor)
            else:
                break
        else:
            print("Can't connect to the region.", file=stderr)
            raise SystemExit(1)

    @inlineCallbacks
    def stop_services():
        yield services.stopService()

    exit_codes = {0}

    @inlineCallbacks
    def run_main():
        try:
            yield start_services()
            try:
                yield deferToThread(main, args)
            finally:
                yield stop_services()
        except SystemExit as se:
            exit_codes.add(se.code)
        except:
            exit_codes.add(2)
            print("Failed to import boot resources", file=stderr)
            traceback.print_exc()
        finally:
            reactor.callLater(0, reactor.stop)

    reactor.callWhenRunning(run_main)
    reactor.run()

    exit_code = max(exit_codes)
    raise SystemExit(exit_code)