~laney/ubuntu-archive-tools/cm-show-fix-released

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
#! /usr/bin/python

# Copyright 2013 Canonical Ltd.
# Author: Colin Watson <cjwatson@ubuntu.com>

# 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; version 3 of the License.
#
# 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/>.

"""Manage build chroots."""

from __future__ import print_function

__metaclass__ = type

try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO
from contextlib import contextmanager
import fcntl
import hashlib
from optparse import OptionParser
import os
import struct
import subprocess
import sys
import termios

from launchpadlib.launchpad import Launchpad
from ubuntutools.question import YesNoQuestion

import lputils


if sys.version < "3.3":
    def get_terminal_size(fd=sys.stdout.fileno()):
        lines, columns = struct.unpack(
            "HH", fcntl.ioctl(fd, termios.TIOCGWINSZ, "xxxx"))
        # Switch order to match 3.3's os.get_terminal_size.
        return columns, lines
else:
    get_terminal_size = os.get_terminal_size


class ProgressStringIO(StringIO):
    """A strange object: a StringIO which manages a progress bar on read.

    This is here to fool httplib into thinking it has a file object, and
    thus sending data in chunks.
    """

    def __init__(self, s):
        StringIO.__init__(self, s)
        self._length = len(s)
        self._progress_width = get_terminal_size()[0] - 3
        self._progress_offset = 0
        self._progress_pos = -1

    def __len__(self):
        return self._length

    def read(self, size):
        pos = int(
            float(self._progress_offset) * self._progress_width / self._length)
        pos = min(pos, self._progress_width - 1)
        if pos != self._progress_pos:
            print(
                "\r[%s>%s]" % (
                    "=" * pos, " " * (self._progress_width - pos - 1)),
                end="")
            sys.stdout.flush()
            self._progress_pos = pos
        r = StringIO.read(self, size)
        self._progress_offset += len(r)
        return r


class ProgressBrowser:
    def __init__(self, child_browser):
        # Be careful; this class overrides __setattr__.
        self.__dict__["child_browser"] = child_browser

    def _request(self, url, data=None, *args, **kwargs):
        if data is not None:
            new_data = ProgressStringIO(data)
        else:
            new_data = data
        ret = self.child_browser._request(url, new_data, *args, **kwargs)
        if data is not None:
            print()
        return ret

    def __getattr__(self, name):
        return getattr(self.child_browser, name)

    def __setattr__(self, name, value):
        setattr(self.child_browser, name, value)

    def __delattr__(self, name):
        delattr(self.child_browser, name)


@contextmanager
def patch_browser(launchpad):
    # TODO: This pokes about in private attributes, and thus might break.
    # Unfortunately, I don't see a way to do this more neatly: we can't just
    # pass a file object to the resource method in the normal way, since
    # email.generator.Generator._handle_text falls over.  I think we need to
    # fix lazr.restfulclient/wadllib in order to improve this.
    original_browser = launchpad._browser
    try:
        launchpad._browser = ProgressBrowser(launchpad._browser)
        yield
    finally:
        launchpad._browser = original_browser


def get_chroot(options):
    das = options.architectures[0]
    url = das.chroot_url
    if url is None:
        print("No chroot for %s" % das)
        return 1
    if options.dry_run:
        print("Would fetch %s" % url)
    else:
        # We use wget here to save on having to implement a progress bar
        # with urlretrieve.
        command = ["wget"]
        if options.filepath is not None:
            command.extend(["-O", options.filepath])
        command.append(url)
        subprocess.check_call(command)
    return 0


def info_chroot(options):
    das = options.architectures[0]
    url = das.chroot_url
    if url is not None:
        print(das.chroot_url)
    return 0


def remove_chroot(options):
    das = options.architectures[0]
    if das.chroot_url is not None:
        print("Previous chroot: %s" % das.chroot_url)
    if options.dry_run:
        print("Would remove chroot from %s" % das)
    else:
        if not options.confirm_all:
            if YesNoQuestion().ask(
                    "Remove chroot from %s" % das, "no") == "no":
                return 0
        das.removeChroot()
    return 0


def set_chroot(options):
    das = options.architectures[0]
    if das.chroot_url is not None:
        print("Previous chroot: %s" % das.chroot_url)
    if options.dry_run:
        print("Would set chroot for %s to %s" % (das, options.filepath))
    else:
        if not options.confirm_all:
            if YesNoQuestion().ask(
                    "Set chroot for %s to %s" % (das, options.filepath),
                    "no") == "no":
                return 0
        with open(options.filepath, "rb") as f:
            data = f.read()
            sha1sum = hashlib.sha1(data).hexdigest()
        with patch_browser(options.launchpad):
            das.setChroot(data=data, sha1sum=sha1sum)
    return 0


commands = {
    "get": get_chroot,
    "info": info_chroot,
    "remove": remove_chroot,
    "set": set_chroot}


def main():
    parser = OptionParser(
        usage="usage: %prog -a ARCHITECTURE [options] <get|remove|set>")
    parser.add_option(
        "-l", "--launchpad", dest="launchpad_instance", default="production")
    parser.add_option(
        "-n", "--dry-run", default=False, action="store_true",
        help="only show removals that would be performed")
    parser.add_option(
        "-y", "--confirm-all", default=False, action="store_true",
        help="do not ask for confirmation")
    parser.add_option(
        "-d", "--distribution", default="ubuntu",
        metavar="DISTRIBUTION", help="manage chroots for DISTRIBUTION")
    parser.add_option(
        "-s", "--series", dest="suite", metavar="SERIES",
        help="manage chroots for SERIES")
    parser.add_option(
        "-a", "--architecture", metavar="ARCHITECTURE",
        help="manage chroots for ARCHITECTURE")
    parser.add_option(
        "-f", "--filepath", metavar="PATH", help="Chroot file path")
    options, args = parser.parse_args()

    if options.architecture is None:
        parser.error("You must specify an architecture.")
    if not args:
        parser.error(
            "You must specify a command (%s)." % ", ".join(sorted(commands)))
    command = args[0]
    if command not in commands:
        parser.error(
            "Unrecognised command: %s (valid: %s)" %
            (command, ", ".join(sorted(commands))))
    if command == "set" and options.filepath is None:
        parser.error("The set command requires a chroot file path (-f).")

    options.launchpad = Launchpad.login_with(
        "manage-chroot", options.launchpad_instance, version="devel")
    lputils.setup_location(options)

    return commands[command](options)


if __name__ == '__main__':
    sys.exit(main())