~xubuntu-dev/ubuntu-cdimage/xubuntu-base

« back to all changes in this revision

Viewing changes to lib/cdimage/config.py

  • Committer: Iain Lane
  • Date: 2015-06-18 16:00:58 UTC
  • Revision ID: iain.lane@canonical.com-20150618160058-huqv2kmpi3r2zfec
Fix desktop-next system image tests

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
__metaclass__ = type
23
23
 
24
 
from collections import defaultdict, namedtuple
 
24
from collections import Iterable, defaultdict
25
25
import fnmatch
26
26
import operator
27
27
import os
28
 
import re
29
 
import subprocess
30
28
import sys
31
29
 
 
30
from cdimage import osextras
 
31
 
32
32
 
33
33
class UnknownSeries(Exception):
34
34
    pass
35
35
 
36
36
 
37
 
BaseSeries = namedtuple("BaseSeries", ["name", "version", "displayname"])
38
37
all_series = []
39
38
 
40
39
 
41
 
class Series(BaseSeries):
42
 
    def __init__(self, *args, **kwargs):
 
40
class Series(Iterable):
 
41
    def __init__(self, name, version, displayname, distribution="ubuntu",
 
42
                 **kwargs):
 
43
        self.name = name
 
44
        self.version = version
 
45
        self.displayname = displayname
 
46
        self.distribution = distribution
43
47
        self._index = None
 
48
        for key, value in kwargs.items():
 
49
            setattr(self, key, value)
44
50
 
45
51
    @classmethod
46
52
    def find_by_name(self, name):
 
53
        if "/" in name:
 
54
            distribution, name = name.split("/", 1)
 
55
        else:
 
56
            distribution = "ubuntu"
47
57
        for series in all_series:
48
 
            if series.name == name:
 
58
            if series.distribution == distribution and series.name == name:
49
59
                return series
50
60
        else:
51
 
            raise ValueError("No series named %s" % name)
 
61
            raise ValueError("No series named %s/%s" % (distribution, name))
52
62
 
53
63
    @classmethod
54
64
    def find_by_version(self, version):
 
65
        if "/" in version:
 
66
            distribution, version = version.split("/", 1)
 
67
        else:
 
68
            distribution = "ubuntu"
55
69
        for series in all_series:
56
 
            if series.version == version:
 
70
            if (series.distribution == distribution and
 
71
                    series.version == version):
57
72
                return series
58
73
        else:
59
 
            raise ValueError("No series with version %s" % version)
 
74
            raise ValueError(
 
75
                "No series with version %s/%s" % (distribution, version))
60
76
 
61
77
    @classmethod
62
 
    def latest(self):
63
 
        return all_series[-1]
 
78
    def latest(self, distribution="ubuntu"):
 
79
        for series in reversed(all_series):
 
80
            if series.distribution == distribution:
 
81
                return series
 
82
        raise ValueError("No series with distribution %s" % distribution)
64
83
 
65
84
    def __str__(self):
66
85
        return self.name
67
86
 
68
87
    @property
 
88
    def full_name(self):
 
89
        if self.distribution == "ubuntu":
 
90
            return self.name
 
91
        else:
 
92
            return "%s/%s" % (self.distribution, self.name)
 
93
 
 
94
    def __iter__(self):
 
95
        yield self.name
 
96
        yield self.version
 
97
        yield self.displayname
 
98
 
 
99
    @property
69
100
    def index(self):
70
101
        if self._index is None:
71
102
            self._index = [
74
105
 
75
106
    @property
76
107
    def is_latest(self):
77
 
        return self == all_series[-1]
 
108
        for series in reversed(all_series):
 
109
            if self.distribution == series.distribution:
 
110
                return self == series
 
111
        return False
78
112
 
79
113
    def _compare(self, other, method):
80
114
        if not isinstance(other, Series):
99
133
    def __gt__(self, other):
100
134
        return self._compare(other, operator.gt)
101
135
 
 
136
    def displayversion(self, project):
 
137
        version = getattr(self, "pointversion", self.version)
 
138
        if (project in getattr(self, "lts_projects", []) or
 
139
                getattr(self, "all_lts_projects", False)):
 
140
            version += " LTS"
 
141
        return version
 
142
 
102
143
 
103
144
# TODO: This should probably come from a configuration file.
104
145
all_series.extend([
105
146
    Series("warty", "4.10", "Warty Warthog"),
106
147
    Series("hoary", "5.04", "Hoary Hedgehog"),
107
148
    Series("breezy", "5.10", "Breezy Badger"),
108
 
    Series("dapper", "6.06", "Dapper Drake"),
 
149
    Series(
 
150
        "dapper", "6.06", "Dapper Drake",
 
151
        pointversion="6.06.2",
 
152
        lts_projects=["ubuntu", "kubuntu", "edubuntu", "ubuntu-server"]),
109
153
    Series("edgy", "6.10", "Edgy Eft"),
110
154
    Series("feisty", "7.04", "Feisty Fawn"),
111
155
    Series("gutsy", "7.10", "Gutsy Gibbon"),
112
 
    Series("hardy", "8.04", "Hardy Heron"),
 
156
    Series(
 
157
        "hardy", "8.04", "Hardy Heron",
 
158
        pointversion="8.04.4", lts_projects=["ubuntu", "ubuntu-server"]),
113
159
    Series("intrepid", "8.10", "Intrepid Ibex"),
114
160
    Series("jaunty", "9.04", "Jaunty Jackalope"),
115
161
    Series("karmic", "9.10", "Karmic Koala"),
116
 
    Series("lucid", "10.04", "Lucid Lynx"),
 
162
    Series(
 
163
        "lucid", "10.04", "Lucid Lynx",
 
164
        pointversion="10.04.4",
 
165
        lts_projects=["ubuntu", "kubuntu", "ubuntu-server"]),
117
166
    Series("maverick", "10.10", "Maverick Meerkat"),
118
167
    Series("natty", "11.04", "Natty Narwhal"),
119
168
    Series("oneiric", "11.10", "Oneiric Ocelot"),
120
 
    Series("precise", "12.04", "Precise Pangolin"),
 
169
    Series(
 
170
        "precise", "12.04", "Precise Pangolin",
 
171
        pointversion="12.04.5",
 
172
        lts_projects=[
 
173
            "ubuntu", "kubuntu", "ubuntu-server", "edubuntu", "xubuntu",
 
174
            "mythbuntu", "ubuntustudio",
 
175
        ]),
121
176
    Series("quantal", "12.10", "Quantal Quetzal"),
122
177
    Series("raring", "13.04", "Raring Ringtail"),
123
 
])
124
 
 
 
178
    Series("saucy", "13.10", "Saucy Salamander"),
 
179
    Series(
 
180
        "trusty", "14.04", "Trusty Tahr",
 
181
        pointversion="14.04.2",
 
182
        all_lts_projects=True),
 
183
    Series("utopic", "14.10", "Utopic Unicorn"),
 
184
    Series("vivid", "15.04", "Vivid Vervet"),
 
185
    Series("wily", "15.10", "Wily Werewolf"),
 
186
 
 
187
    Series("14.09", "14.09", "RTM 14.09", distribution="ubuntu-rtm"),
 
188
    Series(
 
189
        "14.09-factory", "14.09.1", "RTM 14.09-factory",
 
190
        distribution="ubuntu-rtm"),
 
191
])
 
192
 
 
193
all_touch_targets = []
 
194
 
 
195
 
 
196
class Touch:
 
197
    def __init__(self, subarch, android_arch, ubuntu_arch):
 
198
        self.subarch = subarch
 
199
        self.android_arch = android_arch
 
200
        self.ubuntu_arch = ubuntu_arch
 
201
 
 
202
    @classmethod
 
203
    def list_android_arches(self):
 
204
        return list(set([touch.android_arch for touch in all_touch_targets]))
 
205
 
 
206
    @classmethod
 
207
    def list_ubuntu_arches(self):
 
208
        return list(set([touch.ubuntu_arch for touch in all_touch_targets]))
 
209
 
 
210
    @classmethod
 
211
    def list_targets_by_ubuntu_arch(self, arch):
 
212
        return [target for target in all_touch_targets
 
213
                if target.ubuntu_arch == arch]
 
214
 
 
215
 
 
216
# TODO: This should probably come from a configuration file.
 
217
all_touch_targets.extend([
 
218
    Touch("mako", "armel", "armhf"),
 
219
    Touch("manta", "armel", "armhf"),
 
220
    Touch("generic", "armel", "armhf"),
 
221
    Touch("generic_x86", "i386", "i386"),
 
222
    Touch("flo", "armel", "armhf"),
 
223
])
125
224
 
126
225
_whitelisted_keys = (
127
226
    "PROJECT",
128
227
    "CAPPROJECT",
129
 
    "ALL_DISTS",
130
228
    "DIST",
 
229
    "PROPOSED",
131
230
    "ALL_PROJECTS",
132
231
    "ARCHES",
133
232
    "CPUARCHES",
134
233
    "GNUPG_DIR",
135
234
    "SIGNING_KEYID",
136
 
    "BRITNEY",
137
235
    "LOCAL",
138
236
    "LOCALDEBS",
139
237
    "LOCAL_SEEDS",
140
238
    "TRIGGER_MIRRORS",
141
239
    "TRIGGER_MIRRORS_ASYNC",
142
 
    "DEBOOTSTRAPROOT",
143
240
    "DEBUG",
144
241
    "DATE",
145
242
    "DATE_SUFFIX",
148
245
    "LIVECD_BASE",
149
246
    "SUBPROJECT",
150
247
    "UBUNTU_DEFAULTS_LOCALE",
 
248
    "SSH_ORIGINAL_COMMAND",
 
249
    "EXTRA_PPAS",
151
250
)
152
251
 
153
252
 
155
254
    def __init__(self, read=True, **kwargs):
156
255
        super(Config, self).__init__(str)
157
256
        if "CDIMAGE_ROOT" not in os.environ:
158
 
            os.environ["CDIMAGE_ROOT"] = "/srv/cdimage.ubuntu.com"
 
257
            root = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
 
258
            root = os.path.realpath(root)
 
259
            os.environ["CDIMAGE_ROOT"] = root
159
260
        self.root = os.environ["CDIMAGE_ROOT"]
 
261
        self.fix_paths()
160
262
        for key, value in kwargs.items():
161
263
            self[key] = value
162
264
        config_path = os.path.join(self.root, "etc", "config")
165
267
                self.read(config_path)
166
268
            else:
167
269
                self.read()
168
 
            self.fix_paths()
169
 
        if "IMAGE_TYPE" in kwargs:
170
 
            if "ARCHES" not in os.environ:
171
 
                self.set_default_arches()
172
 
            if "CPUARCHES" not in os.environ:
173
 
                self.set_default_cpuarches()
174
 
 
175
 
    def _read_nullsep_output(self, command):
176
 
        raw = subprocess.Popen(
177
 
            command, stdout=subprocess.PIPE,
178
 
            universal_newlines=True).communicate()[0]
179
 
        out = {}
180
 
        for line in raw.split("\0"):
181
 
            try:
182
 
                key, value = line.split("=", 1)
183
 
                out[key] = value
184
 
            except ValueError:
185
 
                continue
186
 
        return out
187
 
 
188
 
    def _shell_escape(self, arg):
189
 
        if re.match(r"^[a-zA-Z0-9+,./:=@_-]+$", arg):
190
 
            return arg
191
 
        else:
192
 
            return "'%s'" % arg.replace("'", "'\\''")
193
270
 
194
271
    def read(self, config_path=None):
195
 
        commands = []
196
 
        if config_path is not None:
197
 
            commands.append(". %s" % self._shell_escape(config_path))
198
 
        commands.append("cat /proc/self/environ")
199
 
        for key in _whitelisted_keys:
200
 
            commands.append(
201
 
                "test -z \"${KEY+x}\" || printf '%s\\0' \"KEY=$KEY\"".replace(
202
 
                    "KEY", key))
203
 
        env = self._read_nullsep_output(["sh", "-c", "; ".join(commands)])
204
 
        for key, value in env.items():
 
272
        for key, value in osextras.read_shell_config(
 
273
                config_path, _whitelisted_keys):
205
274
            if key.startswith("CDIMAGE_") or key in _whitelisted_keys:
206
275
                super(Config, self).__setitem__(key, value)
207
276
 
209
278
        if "DIST" in self:
210
279
            super(Config, self).__setitem__(
211
280
                "DIST", Series.find_by_name(self["DIST"]))
 
281
        if "ARCHES" not in self:
 
282
            self.set_default_arches()
 
283
        if "CPUARCHES" not in self:
 
284
            self.set_default_cpuarches()
212
285
 
213
286
    def __setitem__(self, key, value):
214
287
        config_value = value
239
312
        self._add_package("germinate")
240
313
        self._add_package("ubuntu-archive-tools")
241
314
 
242
 
    def _default_arches_match_series(self, series):
 
315
    def match_series(self, series):
 
316
        if "/" in series:
 
317
            distribution, series = series.split("/", 1)
 
318
            if distribution != self.distribution:
 
319
                return False
 
320
        else:
 
321
            distribution = "ubuntu"
 
322
 
243
323
        if series == "*":
244
324
            return True
245
325
        elif "-" in series:
247
327
            in_range = False
248
328
            if not series_start:
249
329
                in_range = True
250
 
            for tryseries in self.all_series:
251
 
                if tryseries == series_start:
 
330
            for tryseries in all_series:
 
331
                if tryseries.distribution != distribution:
 
332
                    continue
 
333
                if tryseries.name == series_start:
252
334
                    in_range = True
253
 
                if tryseries == self.series:
 
335
                if tryseries.name == self.series:
254
336
                    return in_range
255
 
                if tryseries == series_end:
 
337
                if tryseries.name == series_end:
256
338
                    in_range = False
257
339
            else:
258
340
                return False
282
364
                    continue
283
365
                if not fnmatch.fnmatchcase(self.image_type, image_type):
284
366
                    continue
285
 
                if not self._default_arches_match_series(series):
 
367
                if not self.match_series(series):
286
368
                    continue
287
369
                self["ARCHES"] = arches
288
370
                return arches
292
374
        self["CPUARCHES"] = " ".join(
293
375
            sorted(set(arch.split("+")[0] for arch in self.arches)))
294
376
 
 
377
    def limit_arches(self, new_arches):
 
378
        self["ARCHES"] = " ".join(
 
379
            arch for arch in self.arches if arch in new_arches)
 
380
        new_cpuarches = " ".join(
 
381
            sorted(set(arch.split("+")[0] for arch in new_arches)))
 
382
        self["CPUARCHES"] = " ".join(
 
383
            cpuarch for cpuarch in self.cpuarches if cpuarch in new_cpuarches)
 
384
 
295
385
    @property
296
386
    def project(self):
297
387
        return self["PROJECT"]
305
395
        return self["SUBPROJECT"]
306
396
 
307
397
    @property
 
398
    def distribution(self):
 
399
        return self["DIST"].distribution
 
400
 
 
401
    @property
308
402
    def series(self):
309
403
        return str(self["DIST"])
310
404
 
311
405
    @property
 
406
    def full_series(self):
 
407
        return self["DIST"].full_name
 
408
 
 
409
    @property
312
410
    def arches(self):
313
411
        return self["ARCHES"].split()
314
412
 
324
422
    def all_projects(self):
325
423
        return self["ALL_PROJECTS"].split()
326
424
 
327
 
    @property
328
 
    def all_series(self):
329
 
        return self["ALL_DISTS"].split()
330
 
 
331
425
    def export(self):
332
426
        ret = dict(os.environ)
333
427
        for key, value in self.items():
336
430
            else:
337
431
                ret[key] = value
338
432
        return ret
339
 
 
340
 
 
341
 
config = Config()