~launchpad-pqm/launchpad/devel

8687.15.22 by Karl Fogel
Add the copyright header block to the remaining .py files.
1
# Copyright 2009 Canonical Ltd.  This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
8537.5.3 by Celso Providelo
Splitting the script class.
4
"""PPA report tool
5
6
Generate several reports about the PPA repositories.
7
8
 * Over-quota
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
9
 * User's emails
8537.5.5 by Celso Providelo
Typos and final touches.
10
 * Orphan repositories (requires access to the PPA host machine disk)
11
 * Missing repositories (requires access to the PPA host machine disk)
8537.5.3 by Celso Providelo
Splitting the script class.
12
"""
13
8537.5.4 by Celso Providelo
Script tweaks and unittest.
14
import operator
8537.5.3 by Celso Providelo
Splitting the script class.
15
import os
16
import sys
17
18
from storm.locals import Join
19
from storm.store import Store
20
from zope.component import getUtility
21
22
from canonical.config import config
23
from canonical.launchpad.webapp import canonical_url
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
24
from lp.registry.interfaces.distribution import IDistributionSet
12655.5.3 by Gary Poster
add some optimizations for getting recipient emails more efficiently.
25
from lp.registry.model.person import get_recipients
11382.6.34 by Gavin Panella
Reformat imports in all files touched so far.
26
from lp.services.propertycache import cachedproperty
8537.5.3 by Celso Providelo
Splitting the script class.
27
from lp.services.scripts.base import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
28
    LaunchpadScript,
29
    LaunchpadScriptFailure,
30
    )
11411.6.2 by Julian Edwards
Change code imports for ArchivePurpose and ArchiveStatus
31
from lp.soyuz.enums import ArchivePurpose
8537.5.3 by Celso Providelo
Splitting the script class.
32
33
34
class PPAReportScript(LaunchpadScript):
35
36
    description = "PPA report tool."
37
    output = None
38
39
    def add_my_options(self):
40
        self.parser.add_option(
41
            '-p', '--ppa', dest='archive_owner_name', action='store',
42
            help='Archive owner name in case of PPA operations')
43
44
        self.parser.add_option(
45
            '-o', '--output', metavar='FILENAME', action='store',
46
            type='string', dest='output', default=None,
47
            help='Optional file to store output.')
48
49
        self.parser.add_option(
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
50
            '-t', '--quota-threshold', dest='quota_threshold',
8537.5.4 by Celso Providelo
Script tweaks and unittest.
51
            action='store', type=float, default=80,
8537.5.7 by Celso Providelo
Using mkstemp for testing tempfile.
52
            help='Quota threshold percentage, defaults to %default%')
8537.5.4 by Celso Providelo
Script tweaks and unittest.
53
54
        self.parser.add_option(
8537.5.3 by Celso Providelo
Splitting the script class.
55
            '--gen-over-quota', action='store_true', default=False,
56
            help='Generate PPAs over-quota list.')
57
58
        self.parser.add_option(
59
            '--gen-user-emails', action='store_true', default=False,
60
            help='Generate active PPA user email list')
61
62
        self.parser.add_option(
63
            '--gen-orphan-repos', action='store_true', default=False,
64
            help='Generate PPAs orphan repositories list.')
65
66
        self.parser.add_option(
67
            '--gen-missing-repos', action='store_true', default=False,
68
            help='Generate PPAs missing repositories list.')
69
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
70
    @cachedproperty
71
    def ppas(self):
72
        """A cached tuple containing relevant PPAs objects for 'ubuntu'.
8537.5.3 by Celso Providelo
Splitting the script class.
73
8537.5.4 by Celso Providelo
Script tweaks and unittest.
74
        if `self.options.archive_owner_name` is defined only return PPAs
75
        with matching owner names.
8537.5.3 by Celso Providelo
Splitting the script class.
76
        """
77
        # Avoiding circular imports.
78
        from lp.soyuz.model.archive import Archive
79
        from lp.soyuz.model.publishing import SourcePackagePublishingHistory
80
        from lp.registry.model.person import Person
81
8537.5.4 by Celso Providelo
Script tweaks and unittest.
82
        distribution = getUtility(IDistributionSet).getByName('ubuntu')
8537.5.3 by Celso Providelo
Splitting the script class.
83
        store = Store.of(distribution)
84
        origin = [
85
            Archive,
86
            Join(SourcePackagePublishingHistory,
87
                 SourcePackagePublishingHistory.archive == Archive.id),
88
            ]
89
        clauses = [
90
            Archive.distribution == distribution,
91
            Archive.purpose == ArchivePurpose.PPA,
10138.1.5 by Muharem Hrnjadovic
The actual property name (_enabled) must be used in storm clauses.
92
            Archive._enabled == True,
8537.5.3 by Celso Providelo
Splitting the script class.
93
            ]
94
8537.5.4 by Celso Providelo
Script tweaks and unittest.
95
        owner_name = self.options.archive_owner_name
8537.5.3 by Celso Providelo
Splitting the script class.
96
        if owner_name is not None:
97
            origin.append(Join(Person, Archive.owner == Person.id))
98
            clauses.append(Person.name == owner_name)
99
100
        results = store.using(*origin).find(
101
            Archive, *clauses)
102
        results.order_by(Archive.date_created)
103
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
104
        return tuple(results.config(distinct=True))
8537.5.3 by Celso Providelo
Splitting the script class.
105
8537.5.4 by Celso Providelo
Script tweaks and unittest.
106
    def setOutput(self):
107
        """Set the output file descriptor.
108
109
        If the 'output' options was passed open a file named as its
110
        content, otherwise use `sys.stdout`.
111
        """
8537.5.3 by Celso Providelo
Splitting the script class.
112
        if self.options.output is not None:
113
            self.logger.info('Report file: %s' % self.options.output)
114
            self.output = open(self.options.output, 'w')
115
        else:
116
            self.output = sys.stdout
117
8537.5.4 by Celso Providelo
Script tweaks and unittest.
118
    def closeOutput(self):
119
        """Closes the `output` file descriptor """
120
        self.output.close()
121
122
    def checkOptions(self):
123
        """Verify if the given command-line options are sane."""
124
        if ((self.options.gen_orphan_repos or
125
             self.options.gen_missing_repos or
126
             self.options.gen_over_quota) and
127
            self.options.gen_user_emails):
128
            raise LaunchpadScriptFailure(
129
                'Users-list cannot be combined with other reports.')
130
131
        if ((self.options.gen_orphan_repos or
132
             self.options.gen_missing_repos) and
133
            self.options.archive_owner_name is not None):
134
            raise LaunchpadScriptFailure(
135
                'Cannot calculate repository paths for a single PPA.')
136
8537.5.5 by Celso Providelo
Typos and final touches.
137
        if ((self.options.gen_orphan_repos or
138
             self.options.gen_missing_repos) and
139
            not os.path.exists(config.personalpackagearchive.root)):
140
            raise LaunchpadScriptFailure(
141
                'Cannot access PPA root directory.')
142
8537.5.4 by Celso Providelo
Script tweaks and unittest.
143
    def main(self):
144
        self.checkOptions()
145
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
146
        self.logger.info('Considering %d active PPAs.' % len(self.ppas))
8537.5.3 by Celso Providelo
Splitting the script class.
147
8537.5.4 by Celso Providelo
Script tweaks and unittest.
148
        self.setOutput()
149
8537.5.3 by Celso Providelo
Splitting the script class.
150
        if self.options.gen_over_quota:
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
151
            self.reportOverQuota()
8537.5.3 by Celso Providelo
Splitting the script class.
152
153
        if self.options.gen_user_emails:
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
154
            self.reportUserEmails()
8537.5.3 by Celso Providelo
Splitting the script class.
155
156
        if self.options.gen_orphan_repos:
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
157
            self.reportOrphanRepos()
8537.5.3 by Celso Providelo
Splitting the script class.
158
159
        if self.options.gen_missing_repos:
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
160
            self.reportMissingRepos()
8537.5.3 by Celso Providelo
Splitting the script class.
161
8537.5.4 by Celso Providelo
Script tweaks and unittest.
162
        self.closeOutput()
8537.5.3 by Celso Providelo
Splitting the script class.
163
164
        self.logger.info('Done')
165
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
166
    def reportOverQuota(self):
8537.5.3 by Celso Providelo
Splitting the script class.
167
        self.output.write(
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
168
            '= PPAs over %.2f%% of their quota =\n' %
169
            self.options.quota_threshold)
170
        threshold = self.options.quota_threshold / 100.0
171
        for ppa in self.ppas:
8537.5.3 by Celso Providelo
Splitting the script class.
172
            limit = ppa.authorized_size
173
            size = ppa.estimated_size / (2 ** 20)
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
174
            if size <= (threshold * limit):
8537.5.3 by Celso Providelo
Splitting the script class.
175
                continue
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
176
            line = "%s | %d | %d\n" % (canonical_url(ppa), limit, size)
177
            self.output.write(line.encode('utf-8'))
178
        self.output.write('\n')
8537.5.3 by Celso Providelo
Splitting the script class.
179
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
180
    def reportUserEmails(self):
181
        self.output.write('= PPA user emails =\n')
8537.5.3 by Celso Providelo
Splitting the script class.
182
        people_to_email = set()
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
183
        for ppa in self.ppas:
12655.5.3 by Gary Poster
add some optimizations for getting recipient emails more efficiently.
184
            people_to_email.update(get_recipients(ppa.owner))
8537.5.4 by Celso Providelo
Script tweaks and unittest.
185
        sorted_people_to_email = sorted(
186
            people_to_email, key=operator.attrgetter('name'))
187
        for user in sorted_people_to_email:
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
188
            line = u"%s | %s | %s\n" % (
189
                user.name, user.displayname, user.preferredemail.email)
190
            self.output.write(line.encode('utf-8'))
191
        self.output.write('\n')
192
193
    @cachedproperty
194
    def expected_paths(self):
195
        """Frozenset containing the expected PPA repository paths."""
196
        return frozenset(ppa.owner.name for ppa in self.ppas)
197
198
    @cachedproperty
199
    def existing_paths(self):
200
        """Frozenset containing the existing PPA repository paths."""
201
        return frozenset(os.listdir(config.personalpackagearchive.root))
202
203
    def reportOrphanRepos(self):
204
        self.output.write('= Orphan PPA repositories =\n')
205
        orphan_paths = self.existing_paths - self.expected_paths
8537.5.4 by Celso Providelo
Script tweaks and unittest.
206
        for orphan in sorted(orphan_paths):
8537.5.3 by Celso Providelo
Splitting the script class.
207
            repo_path = os.path.join(
208
                config.personalpackagearchive.root, orphan)
209
            self.output.write('%s\n' % repo_path)
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
210
        self.output.write('\n')
8537.5.3 by Celso Providelo
Splitting the script class.
211
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
212
    def reportMissingRepos(self):
213
        self.output.write('= Missing PPA repositories =\n')
214
        missing_paths = self.expected_paths - self.existing_paths
8537.5.4 by Celso Providelo
Script tweaks and unittest.
215
        for missing in sorted(missing_paths):
8537.5.3 by Celso Providelo
Splitting the script class.
216
            repo_path = os.path.join(
217
                config.personalpackagearchive.root, missing)
218
            self.output.write('%s\n' % repo_path)
8537.5.6 by Celso Providelo
applying review comments, r=allenap.
219
        self.output.write('\n')