8687.15.17
by Karl Fogel
Add the copyright header block to the rest of the files under lib/lp/. |
1 |
# Copyright 2009 Canonical Ltd. This software is licensed under the
|
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
3 |
|
4 |
__metaclass__ = type |
|
5 |
||
6 |
__all__ = [ |
|
7 |
'ProductReleaseFinder'
|
|
8 |
]
|
|
9 |
||
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
10 |
from datetime import datetime |
11 |
import mimetypes |
|
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
12 |
import os |
8848.3.4
by Curtis Hovey
Added extract_version function to guarantee that the file's version number is sane and can be used to make a milestone. |
13 |
import re |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
14 |
import urllib |
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
15 |
import urlparse |
16 |
||
4187.4.12
by Michael Hudson
fix imports broken by moving code around in cscvs |
17 |
from cscvs.dircompare import path |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
18 |
import pytz |
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
19 |
from zope.component import getUtility |
20 |
||
12442.2.2
by j.c.sackett
Moved validators to app, which makes more sense. |
21 |
from lp.app.validators.name import invalid_name_pattern |
22 |
from lp.app.validators.version import sane_version |
|
7675.110.3
by Curtis Hovey
Ran the migration script to move registry code to lp.registry. |
23 |
from lp.registry.interfaces.product import IProductSet |
24 |
from lp.registry.interfaces.productrelease import UpstreamFileType |
|
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
25 |
from lp.registry.interfaces.series import SeriesStatus |
26 |
from lp.registry.scripts.productreleasefinder.filter import FilterPattern |
|
7675.110.3
by Curtis Hovey
Ran the migration script to move registry code to lp.registry. |
27 |
from lp.registry.scripts.productreleasefinder.hose import Hose |
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
28 |
|
29 |
||
8848.3.4
by Curtis Hovey
Added extract_version function to guarantee that the file's version number is sane and can be used to make a milestone. |
30 |
processors = '|'.join([ |
31 |
'all', |
|
32 |
'amd64', |
|
33 |
'arm', |
|
34 |
'armel', |
|
35 |
'i386', |
|
36 |
'intel', |
|
37 |
'hppa', |
|
38 |
'hurd-i386', |
|
39 |
'ia64', |
|
40 |
'm68k', |
|
41 |
'mips', |
|
42 |
'mipsel', |
|
43 |
'powerpc', |
|
44 |
's390', |
|
45 |
'sparc', |
|
46 |
])
|
|
47 |
flavor_pattern = re.compile(r""" |
|
9772.1.1
by Curtis Hovey
Updated PRF flavor rules to identify the '~' character as the start of packaging |
48 |
(~ # Packaging target
|
49 |
|_[a-z][a-z]_[A-Z][A-Z] # or language version
|
|
8848.3.4
by Curtis Hovey
Added extract_version function to guarantee that the file's version number is sane and can be used to make a milestone. |
50 |
|_(%s) # or processor version |
51 |
|[\.-](win32|OSX) # or OS version
|
|
52 |
|\.(deb|noarch|rpm|dmg|exe) # or packaging version
|
|
53 |
).* # to the end of the string
|
|
54 |
""" % processors, re.VERBOSE) |
|
55 |
||
56 |
||
57 |
def extract_version(filename): |
|
58 |
"""Return the release version of the file, or None.
|
|
59 |
||
60 |
Ensure the version is compatible with Launchpad. None is returned
|
|
61 |
if a version could not be extracted.
|
|
62 |
"""
|
|
63 |
version = path.split_version(path.name(filename))[1] |
|
64 |
if version is None: |
|
65 |
return None |
|
66 |
# Tarballs pulled from a Debian-style archive often have
|
|
67 |
# ".orig" appended to the version number. We don't want this.
|
|
68 |
if version.endswith('.orig'): |
|
69 |
version = version[:-len('.orig')] |
|
70 |
# Remove processor and language flavors from the version:
|
|
71 |
# eg. _de_DE, _all, _i386.
|
|
8848.3.6
by Curtis Hovey
Quiet lint. |
72 |
version = flavor_pattern.sub('', version) |
11065.3.1
by Stuart Bishop
Product release finder should ignore unknown file extensions rather than incorporate them into the version number |
73 |
# Bug #599250. If there is no file extension after extracting
|
74 |
# the version number, we have added an unknown file extension to the
|
|
75 |
# version. Ignore this dud match.
|
|
76 |
if filename.endswith(version): |
|
77 |
return None |
|
8848.3.4
by Curtis Hovey
Added extract_version function to guarantee that the file's version number is sane and can be used to make a milestone. |
78 |
# Launchpad requires all versions to be lowercase. They may contain
|
79 |
# letters, numbers, dots, underscores, and hyphens (a-z0-9._-).
|
|
80 |
version = version.lower() |
|
81 |
version = invalid_name_pattern.sub('-', version) |
|
82 |
version = version.replace('+', '-') |
|
83 |
return version |
|
84 |
||
85 |
||
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
86 |
class ProductReleaseFinder: |
87 |
||
88 |
def __init__(self, ztm, log): |
|
89 |
self.ztm = ztm |
|
90 |
self.log = log |
|
91 |
||
92 |
def findReleases(self): |
|
93 |
"""Scan for new releases in all products."""
|
|
94 |
for product_name, filters in self.getFilters(): |
|
95 |
self.handleProduct(product_name, filters) |
|
96 |
||
97 |
def getFilters(self): |
|
98 |
"""Build the list of products and filters.
|
|
99 |
||
100 |
Returns a list of (product_name, filters) for each product in
|
|
101 |
the database, where the filter keys are series names.
|
|
102 |
"""
|
|
103 |
todo = [] |
|
104 |
||
105 |
self.ztm.begin() |
|
106 |
products = getUtility(IProductSet) |
|
12810.1.1
by Robert Collins
Stop using eager loading in product release finder. |
107 |
for product in products.get_all_active(eager_load=False): |
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
108 |
filters = [] |
109 |
||
9760.8.1
by Brad Crittenden
Change the non-English 'serieses' to 'series' throughout our codebase. |
110 |
for series in product.series: |
10977.6.2
by Curtis Hovey
PRF ignores obsolete series. |
111 |
if (series.status == SeriesStatus.OBSOLETE |
112 |
or not series.releasefileglob): |
|
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
113 |
continue
|
114 |
||
115 |
filters.append(FilterPattern(series.name, |
|
3691.169.22
by James Henstridge
adjust product-release-finder code to work with single releasefileglob field |
116 |
series.releasefileglob)) |
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
117 |
|
118 |
if not len(filters): |
|
119 |
continue
|
|
120 |
||
121 |
self.log.info("%s has %d series with information", product.name, |
|
122 |
len(filters)) |
|
123 |
||
124 |
todo.append((product.name, filters)) |
|
125 |
self.ztm.abort() |
|
126 |
||
127 |
return todo |
|
128 |
||
129 |
def handleProduct(self, product_name, filters): |
|
130 |
"""Scan for tarballs and create ProductReleases for the given product.
|
|
131 |
"""
|
|
3691.119.1
by James Henstridge
remove canonical.launchpad.scripts.productreleasrfinder.filter.Cache and all references to it |
132 |
hose = Hose(filters, log_parent=self.log) |
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
133 |
for series_name, url in hose: |
134 |
if series_name is not None: |
|
135 |
try: |
|
136 |
self.handleRelease(product_name, series_name, url) |
|
3691.131.4
by James Henstridge
add support for limiting which subdirs get walked in Hose |
137 |
except (KeyboardInterrupt, SystemExit): |
138 |
raise
|
|
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
139 |
except: |
140 |
self.log.exception("Could not successfully process " |
|
141 |
"URL %s for %s/%s", |
|
142 |
url, product_name, series_name) |
|
143 |
else: |
|
144 |
self.log.debug("File in %s found that matched no glob: %s", |
|
145 |
product_name, url) |
|
146 |
||
9772.1.3
by Curtis Hovey
Renamed PRF's hasReleaseTarball => hasReleaseFile and added the file_name argument. |
147 |
def hasReleaseFile(self, product_name, series_name, |
9772.1.4
by Curtis Hovey
Updated PRF hasReleaseFile to check for filename, not filetype. |
148 |
release_name, filename): |
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
149 |
"""Return True if we have a tarball for the given product release."""
|
9772.1.4
by Curtis Hovey
Updated PRF hasReleaseFile to check for filename, not filetype. |
150 |
has_file = False |
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
151 |
self.ztm.begin() |
152 |
try: |
|
153 |
product = getUtility(IProductSet).getByName(product_name) |
|
154 |
if product is not None: |
|
155 |
series = product.getSeries(series_name) |
|
156 |
if series is not None: |
|
157 |
release = series.getRelease(release_name) |
|
3691.105.2
by James Henstridge
some tests for the ProductReleaseFinder class |
158 |
if release is not None: |
159 |
for fileinfo in release.files: |
|
9772.1.4
by Curtis Hovey
Updated PRF hasReleaseFile to check for filename, not filetype. |
160 |
if filename == fileinfo.libraryfile.filename: |
161 |
has_file = True |
|
3691.105.2
by James Henstridge
some tests for the ProductReleaseFinder class |
162 |
break
|
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
163 |
finally: |
164 |
self.ztm.abort() |
|
9772.1.4
by Curtis Hovey
Updated PRF hasReleaseFile to check for filename, not filetype. |
165 |
return has_file |
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
166 |
|
167 |
def addReleaseTarball(self, product_name, series_name, release_name, |
|
168 |
filename, size, file, content_type): |
|
169 |
"""Create a ProductRelease (if needed), and attach tarball"""
|
|
3691.131.6
by James Henstridge
fixes from BjornT's review |
170 |
# Get the series.
|
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
171 |
self.ztm.begin() |
172 |
try: |
|
173 |
product = getUtility(IProductSet).getByName(product_name) |
|
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
174 |
# XXX: This might match a milestone on a product series that was
|
175 |
# not intended, since product release used to have unique
|
|
176 |
# names per product series, but are now dependent on the milestone
|
|
177 |
# name which is unique per product. The series_name method
|
|
178 |
# parameter can be removed.
|
|
179 |
milestone = product.getMilestone(release_name) |
|
180 |
if milestone is None: |
|
181 |
series = product.getSeries(series_name) |
|
182 |
milestone = series.newMilestone(release_name) |
|
183 |
# Normally, a milestone is deactived when that version is
|
|
184 |
# released. This is only safe to do in an automated script
|
|
185 |
# if we are not using a pre-existing milestone.
|
|
186 |
milestone.active = False |
|
187 |
release = milestone.product_release |
|
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
188 |
if release is None: |
7675.85.2
by Jonathan Lange
Undo revision generated by step 2 of process. |
189 |
release = milestone.createProductRelease( |
190 |
owner=product.owner, datereleased=datetime.now(pytz.UTC)) |
|
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
191 |
self.log.info("Created new release %s for %s/%s", |
192 |
release_name, product_name, series_name) |
|
6935.4.13
by Edwin Grubbs
Fixed tests. |
193 |
release.addReleaseFile( |
6935.7.7
by Brad Crittenden
Add IProductReleaseFile.delete |
194 |
filename, file, content_type, uploader=product.owner) |
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
195 |
self.ztm.commit() |
196 |
except: |
|
197 |
self.ztm.abort() |
|
198 |
raise
|
|
199 |
||
8848.3.4
by Curtis Hovey
Added extract_version function to guarantee that the file's version number is sane and can be used to make a milestone. |
200 |
|
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
201 |
def handleRelease(self, product_name, series_name, url): |
202 |
"""If the given URL looks like a release tarball, download it
|
|
203 |
and create a corresponding ProductRelease."""
|
|
204 |
filename = urlparse.urlsplit(url)[2] |
|
205 |
slash = filename.rfind("/") |
|
206 |
if slash != -1: |
|
207 |
filename = filename[slash+1:] |
|
208 |
self.log.debug("Filename portion is %s", filename) |
|
209 |
||
8848.3.4
by Curtis Hovey
Added extract_version function to guarantee that the file's version number is sane and can be used to make a milestone. |
210 |
version = extract_version(filename) |
3691.314.3
by Diogo Matsubara
Fixes 79563 (Product release finder script crashes if it can't parse the product version in the given URL.) |
211 |
if version is None: |
8848.3.7
by Curtis Hovey
Reduce product-release-finder warings to info because there is nothing that we can do to fix them. |
212 |
self.log.info("Unable to parse version from %s", url) |
3691.314.3
by Diogo Matsubara
Fixes 79563 (Product release finder script crashes if it can't parse the product version in the given URL.) |
213 |
return
|
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
214 |
self.log.debug("Version is %s", version) |
215 |
if not sane_version(version): |
|
216 |
self.log.error("Version number '%s' for '%s' is not sane", |
|
217 |
version, url) |
|
4195.1.1
by Brad Crittenden
Implement upload and management of files associated with a product release. |
218 |
return
|
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
219 |
|
9772.1.3
by Curtis Hovey
Renamed PRF's hasReleaseTarball => hasReleaseFile and added the file_name argument. |
220 |
if self.hasReleaseFile(product_name, series_name, version, filename): |
3691.105.1
by James Henstridge
move body of product-release-finder to a module, and make it check for existing releases and attached tarballs |
221 |
self.log.debug("Already have a tarball for release %s", version) |
222 |
return
|
|
223 |
||
224 |
mimetype, encoding = mimetypes.guess_type(url) |
|
225 |
self.log.debug("Mime type is %s", mimetype) |
|
226 |
if mimetype is None: |
|
227 |
mimetype = 'application/octet-stream' |
|
228 |
||
229 |
self.log.info("Downloading %s", url) |
|
230 |
try: |
|
231 |
local, headers = urllib.urlretrieve(url) |
|
232 |
stat = os.stat(local) |
|
233 |
except IOError: |
|
234 |
self.log.error("Download of %s failed", url) |
|
235 |
raise
|
|
236 |
except OSError: |
|
237 |
self.log.error("Unable to stat downloaded file") |
|
238 |
raise
|
|
239 |
||
240 |
fp = open(local, 'r') |
|
241 |
os.unlink(local) |
|
242 |
self.addReleaseTarball(product_name, series_name, version, |
|
243 |
filename, stat.st_size, fp, mimetype) |