This file is indexed.

/usr/lib/python3/dist-packages/maasserver/bootsources.py is in python3-django-maas 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
308
309
310
311
312
313
314
315
316
# Copyright 2014-2017 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""Boot Sources."""

__all__ = [
    "cache_boot_sources",
    "ensure_boot_source_definition",
    "get_boot_sources",
    "get_os_info_from_boot_sources",
]

import datetime
import html
import os

from maasserver.components import (
    discard_persistent_error,
    register_persistent_error,
)
from maasserver.enum import COMPONENT
from maasserver.models import (
    BootSource,
    BootSourceCache,
    BootSourceSelection,
    Config,
    Notification,
)
from maasserver.models.timestampedmodel import now
from maasserver.utils import get_maas_user_agent
from maasserver.utils.orm import transactional
from maasserver.utils.threads import deferToDatabase
from provisioningserver.auth import get_maas_user_gpghome
from provisioningserver.config import (
    DEFAULT_IMAGES_URL,
    DEFAULT_KEYRINGS_PATH,
)
from provisioningserver.drivers.osystem.ubuntu import UbuntuOS
from provisioningserver.import_images.download_descriptions import (
    download_all_image_descriptions,
)
from provisioningserver.import_images.keyrings import write_all_keyrings
from provisioningserver.logger import get_maas_logger
from provisioningserver.utils.fs import tempdir
from provisioningserver.utils.twisted import (
    asynchronous,
    FOREVER,
)
from requests.exceptions import ConnectionError
from twisted.internet.defer import inlineCallbacks


maaslog = get_maas_logger("bootsources")


@transactional
def ensure_boot_source_definition():
    """Set default boot source if none is currently defined."""
    if not BootSource.objects.exists():
        source = BootSource.objects.create(
            url=DEFAULT_IMAGES_URL, keyring_filename=DEFAULT_KEYRINGS_PATH)
        # Default is to import newest Ubuntu LTS releases, for only amd64
        # release versions only.
        ubuntu = UbuntuOS()
        BootSourceSelection.objects.create(
            boot_source=source, os=ubuntu.name,
            release=ubuntu.get_default_commissioning_release(),
            arches=['amd64'], subarches=['*'], labels=['*'])
        return True
    else:
        return False


@transactional
def get_boot_sources():
    """Return list of boot sources for the region to import from."""
    return [
        source.to_dict()
        for source in BootSource.objects.all()
        ]


@transactional
def get_simplestreams_env():
    """Get environment that should be used with simplestreams."""
    env = {'GNUPGHOME': get_maas_user_gpghome()}
    if Config.objects.get_config('enable_http_proxy'):
        http_proxy = Config.objects.get_config('http_proxy')
        if http_proxy is not None:
            env['http_proxy'] = http_proxy
            env['https_proxy'] = http_proxy
            # When the proxy environment variables are set they effect the
            # entire process, including controller refresh. When the region
            # needs to refresh itself it sends itself results over HTTP to
            # 127.0.0.1.
            env['no_proxy'] = '127.0.0.1,localhost'
    return env


def set_simplestreams_env():
    """Set the environment that simplestreams needs."""
    # We simply set the env variables here as another simplestreams-based
    # import might be running concurrently (bootresources._import_resources)
    # and we don't want to use the environment_variables context manager that
    # would reset the environment variables (they are global to the entire
    # process) while the other import is still running.
    os.environ.update(get_simplestreams_env())


def get_os_info_from_boot_sources(os):
    """Return sources, list of releases, and list of architectures that exists
    for the given operating system from the `BootSource`'s.

    This pulls the information for BootSourceCache.
    """
    os_sources = []
    releases = set()
    arches = set()
    for source in BootSource.objects.all():
        for cache_item in BootSourceCache.objects.filter(
                boot_source=source, os=os):
            if source not in os_sources:
                os_sources.append(source)
            releases.add(cache_item.release)
            arches.add(cache_item.arch)
    return os_sources, releases, arches


def get_product_title(item):
    os_title = item.get('os_title')
    release_title = item.get('release_title')
    gadget_title = item.get('gadget_title')
    if None not in (os_title, release_title, gadget_title):
        return "%s %s %s" % (os_title, release_title, gadget_title)
    elif None not in (os_title, release_title):
        return "%s %s" % (os_title, release_title)
    else:
        return None


@transactional
def _update_cache(source, descriptions):
    try:
        bootsource = BootSource.objects.get(url=source["url"])
    except BootSource.DoesNotExist:
        # The record was deleted while we were fetching the description.
        maaslog.debug(
            "Image descriptions at %s are no longer needed; discarding.",
            source["url"])
    else:
        if bootsource.compare_dict_without_selections(source):
            if descriptions.is_empty():
                # No images for this source, so clear the cache.
                BootSourceCache.objects.filter(boot_source=bootsource).delete()
            else:
                def make_image_tuple(image):
                    return (
                        image.os, image.arch, image.subarch,
                        image.release, image.kflavor, image.label)

                # Get current images for the source.
                current_images = {
                    make_image_tuple(image): image
                    for image in BootSourceCache.objects.filter(
                        boot_source=bootsource)
                }
                bulk_create = []
                for spec, item in descriptions.mapping.items():
                    title = get_product_title(item)
                    if title is None:
                        extra = {}
                    else:
                        extra = {'title': title}
                    current = current_images.pop(make_image_tuple(spec), None)
                    if current is not None:
                        current.release_codename = item.get('release_codename')
                        current.release_title = item.get('release_title')
                        current.support_eol = item.get('support_eol')
                        current.bootloader_type = item.get('bootloader-type')
                        current.extra = extra

                        # Support EOL needs to be a datetime so it will only
                        # be marked updated if actually different.
                        support_eol = item.get('support_eol')
                        if support_eol:
                            current.support_eol = datetime.datetime.strptime(
                                support_eol, '%Y-%m-%d').date()
                        else:
                            current.support_eol = support_eol

                        # Will do nothing if nothing has changed.
                        current.save()
                    else:
                        created = now()
                        bulk_create.append(BootSourceCache(
                            boot_source=bootsource, os=spec.os,
                            arch=spec.arch, subarch=spec.subarch,
                            kflavor=spec.kflavor,
                            release=spec.release, label=spec.label,
                            release_codename=item.get('release_codename'),
                            release_title=item.get('release_title'),
                            support_eol=item.get('support_eol'),
                            bootloader_type=item.get('bootloader-type'),
                            extra=extra,
                            created=created,
                            updated=created))
                if bulk_create:
                    # Insert all cache items in 1 query.
                    BootSourceCache.objects.bulk_create(bulk_create)
                if current_images:
                    image_ids = {
                        image.id for _, image in current_images.items()}
                    BootSourceCache.objects.filter(id__in=image_ids).delete()
            maaslog.debug(
                "Image descriptions for %s have been updated.",
                source["url"])
        else:
            maaslog.debug(
                "Image descriptions for %s are outdated; discarding.",
                source["url"])


@asynchronous(timeout=FOREVER)
@inlineCallbacks
def cache_boot_sources():
    """Cache all image information in boot sources.

    Called from *outside* of a transaction this will:

    1. Retrieve information about all boot sources from the database. The
       transaction is committed before proceeding.

    2. The boot sources are consulted (i.e. there's network IO now) and image
       descriptions downloaded.

    3. Update the boot source cache with the fetched information. If the boot
       source has been modified or deleted during #2 then the results are
       discarded.

    This approach does not require an exclusive lock.

    """
    # Nomenclature herein: `bootsource` is an ORM record for BootSource;
    # `source` is one of those converted to a dict. The former ought not to be
    # used outside of a transactional context.

    @transactional
    def get_sources():
        return list(
            bootsource.to_dict_without_selections()
            for bootsource in BootSource.objects.all()
            # TODO: Only where there are no corresponding BootSourceCache
            # records or the BootSource's updated timestamp is later than any
            # of the BootSourceCache records' timestamps.
        )

    @transactional
    def check_commissioning_series_selected():
        commissioning_osystem = Config.objects.get_config(
            name='commissioning_osystem')
        commissioning_series = Config.objects.get_config(
            name='commissioning_distro_series')
        qs = BootSourceSelection.objects.filter(
            os=commissioning_osystem, release=commissioning_series)
        if not qs.exists():
            if not Notification.objects.filter(
                    ident='commissioning_series_unselected').exists():
                Notification.objects.create_error_for_users(
                    '%s %s is configured as the commissioning release but it '
                    'is not selected for download!' % (
                        commissioning_osystem, commissioning_series),
                    ident='commissioning_series_unselected')
        qs = BootSourceCache.objects.filter(
            os=commissioning_osystem, release=commissioning_series)
        if not qs.exists():
            if not Notification.objects.filter(
                    ident='commissioning_series_unavailable').exists():
                Notification.objects.create_error_for_users(
                    '%s %s is configured as the commissioning release but it '
                    'is unavailable in the configured streams!' % (
                        commissioning_osystem, commissioning_series),
                    ident='commissioning_series_unavailable')

    # FIXME: This modifies the environment of the entire process, which is Not
    # Cool. We should integrate with simplestreams in a more Pythonic manner.
    yield deferToDatabase(set_simplestreams_env)

    errors = []
    sources = yield deferToDatabase(get_sources)
    for source in sources:
        with tempdir("keyrings") as keyrings_path:
            [source] = write_all_keyrings(keyrings_path, [source])
            try:
                user_agent = yield deferToDatabase(get_maas_user_agent)
                descriptions = download_all_image_descriptions(
                    [source],
                    user_agent=user_agent)
            except (IOError, ConnectionError) as error:
                errors.append(
                    "Failed to import images from boot source "
                    "%s: %s" % (source["url"], error))
            else:
                yield deferToDatabase(_update_cache, source, descriptions)

    yield deferToDatabase(check_commissioning_series_selected)

    maaslog.info("Updated boot sources cache.")

    component = COMPONENT.REGION_IMAGE_IMPORT
    if len(errors) > 0:
        yield deferToDatabase(
            register_persistent_error, component,
            "<br>".join(map(html.escape, errors)))
    else:
        yield deferToDatabase(
            discard_persistent_error, component)