Merge lp:~corey.bryant/ubuntu-reports/upstream into lp:ubuntu-reports
- upstream
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 137 |
Proposed branch: | lp:~corey.bryant/ubuntu-reports/upstream |
Merge into: | lp:ubuntu-reports |
Diff against target: |
342 lines (+211/-10) 4 files modified
server/cloud-archive/version-tracker/README.txt (+9/-0) server/cloud-archive/version-tracker/ca-versions.py (+21/-7) server/cloud-archive/version-tracker/common.py (+4/-0) server/cloud-archive/version-tracker/gather-versions.py (+177/-3) |
To merge this branch: | bzr merge lp:~corey.bryant/ubuntu-reports/upstream |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Reports Dev Team | Pending | ||
Review via email: mp+302940@code.launchpad.net |
Commit message
Description of the change
This tool provides an easier way to track upstream releases vs watching mailing lists, etc. It's similar to the current cloud archive reports.
We may want to keep these reports behind the firewall. Currently I'm publishing reports here:
https:/
https:/
https:/
https:/
- 134. By Brian Murray
-
switch incoming release tags
- 135. By Brian Murray
-
be smarter about which duplicate bugs we check who the reporter is, handle an inaccessible bug
- 136. By Corey Bryant
-
Add cloud-archive upstream version-tracker
Add support for tracking the latest upstream versions against current
package versions. The versions are normalized to enable version
comparison and the package list is trimmed to include OpenStack
deliverables only.For more details see: server/
cloud-archive/ version- tracker/ README. txt - 137. By Corey Bryant
-
Add ocata for UCA reports
Preview Diff
1 | === modified file 'server/cloud-archive/version-tracker/README.txt' | |||
2 | --- server/cloud-archive/version-tracker/README.txt 2013-12-03 20:52:26 +0000 | |||
3 | +++ server/cloud-archive/version-tracker/README.txt 2016-10-19 14:41:11 +0000 | |||
4 | @@ -9,6 +9,15 @@ | |||
5 | 9 | ./gather-versions.py "--output_dir=$datadir" "$ca_item" | 9 | ./gather-versions.py "--output_dir=$datadir" "$ca_item" |
6 | 10 | ./ca-versions.py "--json_dir=$datadir" --os_release="$ca_item" | 10 | ./ca-versions.py "--json_dir=$datadir" --os_release="$ca_item" |
7 | 11 | 11 | ||
8 | 12 | Below is an example that includes upstream version details for Ocata, where the | ||
9 | 13 | versions are normalized and packages trimmed down to OpenStack deliverables only: | ||
10 | 14 | datadir="$PWD/data"; | ||
11 | 15 | ca_item="ocata" | ||
12 | 16 | |||
13 | 17 | mkdir "$datadir" | ||
14 | 18 | ./gather-versions.py "--output_dir=$datadir" "$ca_item" --upstream | ||
15 | 19 | ./ca-versions.py "--json_dir=$datadir" --os_release="$ca_item" --upstream | ||
16 | 20 | |||
17 | 12 | After doing so, output is in 'ca_versions_${ca_item}.html' | 21 | After doing so, output is in 'ca_versions_${ca_item}.html' |
18 | 13 | 22 | ||
19 | 14 | # output is in ca_versions_${ca_item}.html | 23 | # output is in ca_versions_${ca_item}.html |
20 | 15 | 24 | ||
21 | === modified file 'server/cloud-archive/version-tracker/ca-versions.py' | |||
22 | --- server/cloud-archive/version-tracker/ca-versions.py 2014-07-22 13:15:44 +0000 | |||
23 | +++ server/cloud-archive/version-tracker/ca-versions.py 2016-10-19 14:41:11 +0000 | |||
24 | @@ -59,10 +59,13 @@ | |||
25 | 59 | return rc == 0 | 59 | return rc == 0 |
26 | 60 | 60 | ||
27 | 61 | 61 | ||
29 | 62 | def load_sets(json_dir, release, sets=None): | 62 | def load_sets(json_dir, release, sets=None, include_upstream=False): |
30 | 63 | # this list determines which pockets to display, and in what order. | 63 | # this list determines which pockets to display, and in what order. |
31 | 64 | if sets is None: | 64 | if sets is None: |
33 | 65 | sets = ['ubuntu', 'staging', 'proposed', 'updates'] | 65 | if include_upstream: |
34 | 66 | sets = ['ubuntu', 'staging', 'proposed', 'updates', 'upstream'] | ||
35 | 67 | else: | ||
36 | 68 | sets = ['ubuntu', 'staging', 'proposed', 'updates'] | ||
37 | 66 | i = 1 | 69 | i = 1 |
38 | 67 | package_sets = {} | 70 | package_sets = {} |
39 | 68 | for s in sets: | 71 | for s in sets: |
40 | @@ -75,8 +78,12 @@ | |||
41 | 75 | i += 1 | 78 | i += 1 |
42 | 76 | return package_sets | 79 | return package_sets |
43 | 77 | 80 | ||
46 | 78 | def render_table(package_sets, release): | 81 | def render_table(package_sets, opts): |
47 | 79 | table = '<html><body><center>Ubuntu Cloud Archive Version Tracker: ' | 82 | release = opts.os_release |
48 | 83 | if opts.include_upstream: | ||
49 | 84 | table = '<html><body><center>Ubuntu Cloud Archive Upstream Version Tracker: ' | ||
50 | 85 | else: | ||
51 | 86 | table = '<html><body><center>Ubuntu Cloud Archive Version Tracker: ' | ||
52 | 80 | if release == 'cloud-tools': | 87 | if release == 'cloud-tools': |
53 | 81 | table += '<b>Cloud Tools</b><br>' | 88 | table += '<b>Cloud Tools</b><br>' |
54 | 82 | vers_suffix = '~ctools' | 89 | vers_suffix = '~ctools' |
55 | @@ -191,6 +198,9 @@ | |||
56 | 191 | action='store_true', default=False, | 198 | action='store_true', default=False, |
57 | 192 | help='Ensure Ubuntu pockets do not contain newer ' | 199 | help='Ensure Ubuntu pockets do not contain newer ' |
58 | 193 | 'packages than what is at least in CA staging.') | 200 | 'packages than what is at least in CA staging.') |
59 | 201 | parser.add_option('-u', '--upstream', dest='include_upstream', | ||
60 | 202 | action='store_true', default=False, | ||
61 | 203 | help='Include upstream version column in output.') | ||
62 | 194 | (opts, args) = parser.parse_args() | 204 | (opts, args) = parser.parse_args() |
63 | 195 | 205 | ||
64 | 196 | if not os.path.exists(opts.json_dir): | 206 | if not os.path.exists(opts.json_dir): |
65 | @@ -203,7 +213,8 @@ | |||
66 | 203 | if f['name'] == "staging": | 213 | if f['name'] == "staging": |
67 | 204 | f['header'] = "next" | 214 | f['header'] = "next" |
68 | 205 | else: | 215 | else: |
70 | 206 | sets = load_sets(opts.json_dir, opts.os_release) | 216 | sets = load_sets(opts.json_dir, opts.os_release, |
71 | 217 | include_upstream=opts.include_upstream) | ||
72 | 207 | 218 | ||
73 | 208 | if opts.check_ubuntu: | 219 | if opts.check_ubuntu: |
74 | 209 | # ensure there are no newer packages in the ubuntu release than in | 220 | # ensure there are no newer packages in the ubuntu release than in |
75 | @@ -241,11 +252,14 @@ | |||
76 | 241 | 252 | ||
77 | 242 | 253 | ||
78 | 243 | if not opts.out_file: | 254 | if not opts.out_file: |
80 | 244 | out_file = 'ca_versions_%s.html' % opts.os_release | 255 | if opts.include_upstream: |
81 | 256 | out_file = 'ca_upstream_versions_%s.html' % opts.os_release | ||
82 | 257 | else: | ||
83 | 258 | out_file = 'ca_versions_%s.html' % opts.os_release | ||
84 | 245 | else: | 259 | else: |
85 | 246 | out_file = opts.out_file | 260 | out_file = opts.out_file |
86 | 247 | 261 | ||
88 | 248 | html = render_table(sets, opts.os_release) | 262 | html = render_table(sets, opts) |
89 | 249 | out = open(out_file, 'w') | 263 | out = open(out_file, 'w') |
90 | 250 | out.write(html) | 264 | out.write(html) |
91 | 251 | out.close() | 265 | out.close() |
92 | 252 | 266 | ||
93 | === modified file 'server/cloud-archive/version-tracker/common.py' | |||
94 | --- server/cloud-archive/version-tracker/common.py 2016-05-31 08:45:05 +0000 | |||
95 | +++ server/cloud-archive/version-tracker/common.py 2016-10-19 14:41:11 +0000 | |||
96 | @@ -44,6 +44,10 @@ | |||
97 | 44 | 'ubuntu_stable': 'xenial', | 44 | 'ubuntu_stable': 'xenial', |
98 | 45 | 'ubuntu_dev': 'yakkety', | 45 | 'ubuntu_dev': 'yakkety', |
99 | 46 | }, | 46 | }, |
100 | 47 | 'ocata': { | ||
101 | 48 | 'ubuntu_stable': 'xenial', | ||
102 | 49 | 'ubuntu_dev': 'zesty', | ||
103 | 50 | }, | ||
104 | 47 | 'cloud-tools': { | 51 | 'cloud-tools': { |
105 | 48 | 'ubuntu_stable': CURRENT_UBUNTU_STABLE, | 52 | 'ubuntu_stable': CURRENT_UBUNTU_STABLE, |
106 | 49 | 'ubuntu_dev': 'trusty', | 53 | 'ubuntu_dev': 'trusty', |
107 | 50 | 54 | ||
108 | === modified file 'server/cloud-archive/version-tracker/gather-versions.py' | |||
109 | --- server/cloud-archive/version-tracker/gather-versions.py 2014-03-17 10:51:33 +0000 | |||
110 | +++ server/cloud-archive/version-tracker/gather-versions.py 2016-10-19 14:41:11 +0000 | |||
111 | @@ -14,19 +14,26 @@ | |||
112 | 14 | # The package set to be tracked is determined by what currently exists in the | 14 | # The package set to be tracked is determined by what currently exists in the |
113 | 15 | # staging PPA. | 15 | # staging PPA. |
114 | 16 | # | 16 | # |
115 | 17 | # Optionally include upstream version details, where versions are normalized | ||
116 | 18 | # and resulting packages trimmed down to OpenStack deliverables only. | ||
117 | 19 | # | ||
118 | 17 | # Version info is dumped to JSON files (one for each repo) to a specified | 20 | # Version info is dumped to JSON files (one for each repo) to a specified |
119 | 18 | # directory ($HOME/.ca-versions) by default. These can be used to generate | 21 | # directory ($HOME/.ca-versions) by default. These can be used to generate |
120 | 19 | # a HTML report using the ca-versions.py script. | 22 | # a HTML report using the ca-versions.py script. |
121 | 20 | 23 | ||
122 | 21 | import common | 24 | import common |
123 | 25 | import git | ||
124 | 22 | import json | 26 | import json |
125 | 23 | import os | 27 | import os |
126 | 28 | import re | ||
127 | 24 | import sys | 29 | import sys |
128 | 25 | import logging | 30 | import logging |
129 | 26 | import httplib2 | 31 | import httplib2 |
130 | 27 | import optparse | 32 | import optparse |
131 | 28 | import gzip | 33 | import gzip |
132 | 34 | import shutil | ||
133 | 29 | import StringIO | 35 | import StringIO |
134 | 36 | import yaml | ||
135 | 30 | 37 | ||
136 | 31 | from launchpadlib.launchpad import Launchpad | 38 | from launchpadlib.launchpad import Launchpad |
137 | 32 | 39 | ||
138 | @@ -37,6 +44,9 @@ | |||
139 | 37 | parser.add_option('-o', '--output_dir', action='store', dest='out_dir', | 44 | parser.add_option('-o', '--output_dir', action='store', dest='out_dir', |
140 | 38 | default=os.path.join(os.getenv('HOME'), '.ca-versions'), | 45 | default=os.path.join(os.getenv('HOME'), '.ca-versions'), |
141 | 39 | help='Directory to dump JSON (Default: $HOME/.ca-versions)') | 46 | help='Directory to dump JSON (Default: $HOME/.ca-versions)') |
142 | 47 | parser.add_option('-u', '--upstream', dest='include_upstream', | ||
143 | 48 | action='store_true', default=False, | ||
144 | 49 | help='Get upstream versions and normalize/trim results.') | ||
145 | 40 | (opts, args) = parser.parse_args() | 50 | (opts, args) = parser.parse_args() |
146 | 41 | 51 | ||
147 | 42 | if len(args) == 0: | 52 | if len(args) == 0: |
148 | @@ -192,6 +202,122 @@ | |||
149 | 192 | out[p] = vers | 202 | out[p] = vers |
150 | 193 | return out | 203 | return out |
151 | 194 | 204 | ||
152 | 205 | def normalize_version(version): | ||
153 | 206 | ''' | ||
154 | 207 | Convert a package version into its upstream version format, in order to | ||
155 | 208 | enable version comparison. | ||
156 | 209 | ''' | ||
157 | 210 | normalize_regular_expressions = { | ||
158 | 211 | "^\d+:": "", | ||
159 | 212 | "\-.*$": "", | ||
160 | 213 | "\+dfsg.*$": "", | ||
161 | 214 | "\~b1": ".0b1", | ||
162 | 215 | "\~b2": ".0b2", | ||
163 | 216 | "\~b3": ".0b3", | ||
164 | 217 | "\~rc1": ".0rc1", | ||
165 | 218 | "\~rc2": ".0rc2", | ||
166 | 219 | "\~rc3": ".0rc3", | ||
167 | 220 | "\~rc4": ".0rc4", | ||
168 | 221 | "\~cloud.*$": "", | ||
169 | 222 | "ubuntu.*$": "", | ||
170 | 223 | } | ||
171 | 224 | normalized_version = version | ||
172 | 225 | if version: | ||
173 | 226 | for pattern, replacement in normalize_regular_expressions.iteritems(): | ||
174 | 227 | normalized_version = re.sub(r"{}".format(pattern), | ||
175 | 228 | replacement, | ||
176 | 229 | normalized_version) | ||
177 | 230 | return normalized_version | ||
178 | 231 | |||
179 | 232 | # Map of ubuntu package names to their corresponding upstream project names. | ||
180 | 233 | # Most mappings are programatically determined in query_release_deliverables. | ||
181 | 234 | # This mapping is just for the tricky names. | ||
182 | 235 | PKG_TO_UPSTREAM_MAP = { | ||
183 | 236 | 'openstack-trove': 'trove', | ||
184 | 237 | 'oslo-sphinx': 'oslosphinx', | ||
185 | 238 | 'python-django-openstack-auth': 'django_openstack_auth', | ||
186 | 239 | 'python-keystoneauth1': 'keystoneauth', | ||
187 | 240 | } | ||
188 | 241 | |||
189 | 242 | def _clone_repo(url, out_dir, sub_dir, branch=None): | ||
190 | 243 | ''' | ||
191 | 244 | Clone a git repo. | ||
192 | 245 | ''' | ||
193 | 246 | dest = os.path.join(out_dir, sub_dir) | ||
194 | 247 | shutil.rmtree(dest, ignore_errors=True) | ||
195 | 248 | if branch: | ||
196 | 249 | git.Repo.clone_from(url, dest, branch=branch) | ||
197 | 250 | else: | ||
198 | 251 | git.Repo.clone_from(url, dest) | ||
199 | 252 | return dest | ||
200 | 253 | |||
201 | 254 | def query_release_deliverables(pkgs, release, out_dir): | ||
202 | 255 | ''' | ||
203 | 256 | Loop through pkgs and get corresponding upstream versions from release | ||
204 | 257 | repo's deliverables files. | ||
205 | 258 | ''' | ||
206 | 259 | dest = _clone_repo('git://github.com/openstack/releases', | ||
207 | 260 | out_dir, 'releases') | ||
208 | 261 | release_dir = os.path.join(dest, 'deliverables', release) | ||
209 | 262 | release_files = os.listdir(release_dir) | ||
210 | 263 | |||
211 | 264 | branch = 'stable/{}'.format(release) | ||
212 | 265 | try: | ||
213 | 266 | dest = _clone_repo('git://github.com/openstack/requirements', | ||
214 | 267 | out_dir, 'requirements', branch) | ||
215 | 268 | except git.exc.GitCommandError: | ||
216 | 269 | # if stable branch not found, clone master branch | ||
217 | 270 | branch = 'master' | ||
218 | 271 | dest = _clone_repo('git://github.com/openstack/requirements', | ||
219 | 272 | out_dir, 'requirements', branch) | ||
220 | 273 | uc_file = os.path.join(dest, 'upper-constraints.txt') | ||
221 | 274 | |||
222 | 275 | upstream_versions = {} | ||
223 | 276 | for pkg_name in pkgs: | ||
224 | 277 | # generate upstream project name based on package name | ||
225 | 278 | upstream_name = pkg_name | ||
226 | 279 | if 'client' not in pkg_name: | ||
227 | 280 | upstream_name = re.sub(r'python-', '', pkg_name) | ||
228 | 281 | if pkg_name in PKG_TO_UPSTREAM_MAP.keys(): | ||
229 | 282 | upstream_name = PKG_TO_UPSTREAM_MAP[pkg_name] | ||
230 | 283 | |||
231 | 284 | release_file = "{}.yaml".format(upstream_name) | ||
232 | 285 | if release_file not in release_files: | ||
233 | 286 | continue | ||
234 | 287 | |||
235 | 288 | # Get latest version from project's release deliverable file. | ||
236 | 289 | # Note: some projects exist in release deliverables but not in | ||
237 | 290 | # upper-constraints. | ||
238 | 291 | stream = file(os.path.join(release_dir, release_file), 'r') | ||
239 | 292 | project_yaml = yaml.load(stream) | ||
240 | 293 | upstream_vers = project_yaml['releases'][-1]['version'] | ||
241 | 294 | |||
242 | 295 | # If version exists in upper-constraints, use that instead. Release | ||
243 | 296 | # deliverables files can get updated before upper-constraints. | ||
244 | 297 | stream = file(uc_file, 'r') | ||
245 | 298 | for line in stream: | ||
246 | 299 | lline = line.lower().rstrip() | ||
247 | 300 | if upstream_name == re.sub(r'===.*$', '', lline): | ||
248 | 301 | upstream_vers = re.sub(r'^.*===', '', lline) | ||
249 | 302 | |||
250 | 303 | upstream_versions[pkg_name] = upstream_vers | ||
251 | 304 | |||
252 | 305 | return upstream_versions | ||
253 | 306 | |||
254 | 307 | def trim_packages_file(filename, packages): | ||
255 | 308 | ''' | ||
256 | 309 | Trim the file to only include packages that are in the packages dict. | ||
257 | 310 | ''' | ||
258 | 311 | f = open(filename, 'r') | ||
259 | 312 | |||
260 | 313 | pkgs = json.loads(f.read()) | ||
261 | 314 | for k in pkgs.keys(): | ||
262 | 315 | if k not in packages.keys(): | ||
263 | 316 | del pkgs[k] | ||
264 | 317 | |||
265 | 318 | f = open(filename, 'w') | ||
266 | 319 | f.write(json.dumps(pkgs)) | ||
267 | 320 | |||
268 | 195 | stable_rel = "precise" | 321 | stable_rel = "precise" |
269 | 196 | 322 | ||
270 | 197 | stable_rel = common.VERSION_MAP[os_release]['ubuntu_stable'] | 323 | stable_rel = common.VERSION_MAP[os_release]['ubuntu_stable'] |
271 | @@ -212,7 +338,13 @@ | |||
272 | 212 | staging_versions = query_ca_ppa(ppa=staging_ppa, | 338 | staging_versions = query_ca_ppa(ppa=staging_ppa, |
273 | 213 | release=stable_rel) | 339 | release=stable_rel) |
274 | 214 | f = open(os.path.join(opts.out_dir, '%s_staging_versions' % os_release), 'w+') | 340 | f = open(os.path.join(opts.out_dir, '%s_staging_versions' % os_release), 'w+') |
276 | 215 | f.write(json.dumps(staging_versions)) | 341 | if opts.include_upstream: |
277 | 342 | staging_versions_normalized = {} | ||
278 | 343 | for pkg_name, version in staging_versions.iteritems(): | ||
279 | 344 | staging_versions_normalized[pkg_name] = normalize_version(version[0]) | ||
280 | 345 | f.write(json.dumps(staging_versions_normalized)) | ||
281 | 346 | else: | ||
282 | 347 | f.write(json.dumps(staging_versions)) | ||
283 | 216 | f.close() | 348 | f.close() |
284 | 217 | 349 | ||
285 | 218 | # with the names of the packages published to the staging ppa, we can determine | 350 | # with the names of the packages published to the staging ppa, we can determine |
286 | @@ -220,7 +352,13 @@ | |||
287 | 220 | logging.info("Querying Ubuntu versions for all packages.") | 352 | logging.info("Querying Ubuntu versions for all packages.") |
288 | 221 | ubuntu_versions = query_ubuntu(staging_versions.keys(), dev_rel) | 353 | ubuntu_versions = query_ubuntu(staging_versions.keys(), dev_rel) |
289 | 222 | f = open(os.path.join(opts.out_dir, "%s_ubuntu_versions" % os_release), 'w+') | 354 | f = open(os.path.join(opts.out_dir, "%s_ubuntu_versions" % os_release), 'w+') |
291 | 223 | f.write(json.dumps(ubuntu_versions)) | 355 | if opts.include_upstream: |
292 | 356 | ubuntu_versions_normalized = {} | ||
293 | 357 | for pkg_name, version in ubuntu_versions.iteritems(): | ||
294 | 358 | ubuntu_versions_normalized[pkg_name] = normalize_version(version) | ||
295 | 359 | f.write(json.dumps(ubuntu_versions_normalized)) | ||
296 | 360 | else: | ||
297 | 361 | f.write(json.dumps(ubuntu_versions)) | ||
298 | 224 | f.close() | 362 | f.close() |
299 | 225 | 363 | ||
300 | 226 | for p in pockets: | 364 | for p in pockets: |
301 | @@ -228,4 +366,40 @@ | |||
302 | 228 | versions = versions_from_packages(os_release, p, staging_versions.keys()) | 366 | versions = versions_from_packages(os_release, p, staging_versions.keys()) |
303 | 229 | out = os.path.join(opts.out_dir, '%s_%s_versions' % (os_release, p)) | 367 | out = os.path.join(opts.out_dir, '%s_%s_versions' % (os_release, p)) |
304 | 230 | f = open(out, 'w+') | 368 | f = open(out, 'w+') |
306 | 231 | f.write(json.dumps(versions)) | 369 | versions_normalized = {} |
307 | 370 | if opts.include_upstream: | ||
308 | 371 | for pkg_name, version in versions.iteritems(): | ||
309 | 372 | normalized_version = version | ||
310 | 373 | if version: | ||
311 | 374 | normalized_version = normalize_version(version) | ||
312 | 375 | versions_normalized[pkg_name] = normalized_version | ||
313 | 376 | f.write(json.dumps(versions_normalized)) | ||
314 | 377 | else: | ||
315 | 378 | f.write(json.dumps(versions)) | ||
316 | 379 | |||
317 | 380 | if opts.include_upstream: | ||
318 | 381 | # with the names of the packages published to the staging ppa, we can determine | ||
319 | 382 | # the upstream versions we need to track. | ||
320 | 383 | logging.info("Querying upstream versions for packages from release deliverables repo.") | ||
321 | 384 | upstream_versions = query_release_deliverables(staging_versions, | ||
322 | 385 | os_release, opts.out_dir) | ||
323 | 386 | |||
324 | 387 | # trim the results to only include packages that we've detected upstream | ||
325 | 388 | # versions for | ||
326 | 389 | for k in upstream_versions.keys(): | ||
327 | 390 | if upstream_versions[k] == None: | ||
328 | 391 | del upstream_versions[k] | ||
329 | 392 | |||
330 | 393 | out = os.path.join(opts.out_dir, '%s_upstream_versions' % os_release) | ||
331 | 394 | f = open(out, 'w') | ||
332 | 395 | f.write(json.dumps(upstream_versions)) | ||
333 | 396 | |||
334 | 397 | out = os.path.join(opts.out_dir, '%s_staging_versions' % os_release) | ||
335 | 398 | trim_packages_file(out, upstream_versions) | ||
336 | 399 | |||
337 | 400 | out = os.path.join(opts.out_dir, '%s_ubuntu_versions' % os_release) | ||
338 | 401 | trim_packages_file(out, upstream_versions) | ||
339 | 402 | |||
340 | 403 | for p in pockets: | ||
341 | 404 | out = os.path.join(opts.out_dir, '%s_%s_versions' % (os_release, p)) | ||
342 | 405 | trim_packages_file(out, upstream_versions) |