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') |