Merge ~ddstreet/ubuntu-dev-tools/+git/ubuntu-dev-tools:pull-pkg into ubuntu-dev-tools:master

Proposed by Dan Streetman on 2017-04-20
Status: Needs review
Proposed branch: ~ddstreet/ubuntu-dev-tools/+git/ubuntu-dev-tools:pull-pkg
Merge into: ubuntu-dev-tools:master
Diff against target: 3133 lines (+1608/-760)
36 files modified
debian/control (+7/-1)
dev/null (+0/-79)
doc/pull-debian-ddebs.1 (+1/-0)
doc/pull-debian-debs.1 (+1/-0)
doc/pull-debian-source.1 (+1/-0)
doc/pull-debian-udebs.1 (+1/-0)
doc/pull-lp-ddebs.1 (+1/-0)
doc/pull-lp-debs.1 (+1/-0)
doc/pull-lp-source.1 (+1/-0)
doc/pull-lp-udebs.1 (+1/-0)
doc/pull-pkg.1 (+134/-0)
doc/pull-uca-ddebs.1 (+1/-0)
doc/pull-uca-debs.1 (+1/-0)
doc/pull-uca-source.1 (+1/-0)
doc/pull-uca-udebs.1 (+1/-0)
pull-debian-ddebs (+16/-0)
pull-debian-debs (+16/-0)
pull-debian-source (+6/-133)
pull-debian-udebs (+16/-0)
pull-lp-ddebs (+16/-0)
pull-lp-debs (+16/-0)
pull-lp-source (+6/-139)
pull-lp-udebs (+16/-0)
pull-pkg (+33/-0)
pull-uca-ddebs (+16/-0)
pull-uca-debs (+16/-0)
pull-uca-source (+6/-147)
pull-uca-udebs (+16/-0)
setup.py (+10/-0)
ubuntutools/archive.py (+690/-190)
ubuntutools/config.py (+2/-0)
ubuntutools/lp/libsupport.py (+24/-0)
ubuntutools/lp/lpapicache.py (+240/-44)
ubuntutools/pullpkg.py (+281/-0)
ubuntutools/requestsync/mail.py (+13/-24)
ubuntutools/test/test_archive.py (+0/-3)
Reviewer Review Type Date Requested Status
Ubuntu Development Team 2017-04-20 Pending
Review via email: mp+322863@code.launchpad.net

Description of the Change

This is a large change to the pull-DISTRO-source scripts, and their associated libs under ubuntutools/. It consolidates the scripts into a single script, and extends its functionality to be able to pull not only source packages/files, but also binary debs - normal ".deb", dbgsym ".ddeb" and -di ".udeb" files. It also allows listing all packages assoicated with a given source package, and it automatically performs binary package name to source package name lookups. It works with ubuntu (launchpad), as well as debian (using madison/snapshot) and cloud-archive.

To post a comment you must log in.
a313e50... by Dan Streetman on 2017-10-27

pull-pkg: catch any exception when trying to lookup bpph->build->spph

When looking up a package by its binary pkg name, the easiest way to get
to its SourcePackagePublishingHistory object is through its Build object.
However, some BPPH objects in Launchpad seem to have their Build's
SPPH link removed or redacted, which results in an exception while trying
to access it. So try that first, but catch any exception and then
fallback to recursively calling lp_spph using the looked-up source
package name and version number, which will work.

24bf863... by Dan Streetman on 2017-11-21

ubuntutools/lp/lpapicache.py method additions

DistroArchSeries.getSeries()
SPPH.getSeriesName()
SPPH.getArchive()

02b0d8b... by Dan Streetman on 2017-11-21

ubuntutools/lp/lpapicache.py: get pkg using version/status params also

Update Archive methods getSourcePackage and getBinaryPackage to allow
searching for a package using its version and/or status fields also

4578c36... by Dan Streetman on 2017-11-21

ubuntutools/lp/lpapicache.py: find old pkg version binaries

Launchpad unfortunately "hides" the BPPH records (and corresponding
binary files/urls) when a newer package version is released. This
means binary debs and ddebs for older package versions are effectively
"gone", which makes debugging problems with older package versions
(e.g. with gdb) very hard.

Luckily (and confusingly) Launchpad doesn't actually remove the old
binary files, or the BPPH records. It only *hides* them, because
the SPPH.getPublishedBinaries() LP API call only returns Published
or Pending BPPH records.

This change detects that (if the state is not Published or Pending)
and instead of using SPPH.getPublishedBinaries() goes the MUCH
longer route (since it seems to be the ONLY way left to find the
binaries) of using SPPH.binaryFileUrls() and then parsing the
filenames and looking up each BPPH using Archive.getPublishedBinaries().

This change allows pull-lp-debs and pull-lp-ddebs to now get debs
and/or ddebs for ALL package versions created in Launchpad, even if
they are old versions.

2c6023c... by Dan Streetman on 2017-11-21

ubuntutools/lp/lpapicache.py: get system arch for archtag 'all'

Packages applicable to all archs are labeled with archtag 'all',
so any reverse lookup of a DistroArchSeries by arch 'all'
should return the system's arch series (since the specific arch
doesn't matter for archtag 'all' binaries).

Unmerged commits

4578c36... by Dan Streetman on 2017-11-21

ubuntutools/lp/lpapicache.py: find old pkg version binaries

Launchpad unfortunately "hides" the BPPH records (and corresponding
binary files/urls) when a newer package version is released. This
means binary debs and ddebs for older package versions are effectively
"gone", which makes debugging problems with older package versions
(e.g. with gdb) very hard.

Luckily (and confusingly) Launchpad doesn't actually remove the old
binary files, or the BPPH records. It only *hides* them, because
the SPPH.getPublishedBinaries() LP API call only returns Published
or Pending BPPH records.

This change detects that (if the state is not Published or Pending)
and instead of using SPPH.getPublishedBinaries() goes the MUCH
longer route (since it seems to be the ONLY way left to find the
binaries) of using SPPH.binaryFileUrls() and then parsing the
filenames and looking up each BPPH using Archive.getPublishedBinaries().

This change allows pull-lp-debs and pull-lp-ddebs to now get debs
and/or ddebs for ALL package versions created in Launchpad, even if
they are old versions.

02b0d8b... by Dan Streetman on 2017-11-21

ubuntutools/lp/lpapicache.py: get pkg using version/status params also

Update Archive methods getSourcePackage and getBinaryPackage to allow
searching for a package using its version and/or status fields also

24bf863... by Dan Streetman on 2017-11-21

ubuntutools/lp/lpapicache.py method additions

DistroArchSeries.getSeries()
SPPH.getSeriesName()
SPPH.getArchive()

2c6023c... by Dan Streetman on 2017-11-21

ubuntutools/lp/lpapicache.py: get system arch for archtag 'all'

Packages applicable to all archs are labeled with archtag 'all',
so any reverse lookup of a DistroArchSeries by arch 'all'
should return the system's arch series (since the specific arch
doesn't matter for archtag 'all' binaries).

a313e50... by Dan Streetman on 2017-10-27

pull-pkg: catch any exception when trying to lookup bpph->build->spph

When looking up a package by its binary pkg name, the easiest way to get
to its SourcePackagePublishingHistory object is through its Build object.
However, some BPPH objects in Launchpad seem to have their Build's
SPPH link removed or redacted, which results in an exception while trying
to access it. So try that first, but catch any exception and then
fallback to recursively calling lp_spph using the looked-up source
package name and version number, which will work.

7057eb8... by Dan Streetman on 2017-09-15

pull-pkg: update man pages to all point to single pull-pkg.1 man page

remove the pull-lp-source.1 and pull-debian-source.1 man pages,
consolidating them both into a pull-pkg.1 man page. Also create
symlinks for all associated scripts pointing to pull-pkg.1 man page.

92f6bce... by Dan Streetman on 2017-09-14

pull-pkg: create pull-[lp|debian|uca]-* scripts

for backwards compatibility (i.e. to keep pull-lp-source,
pull-debian-source, and pull-uca-source) and for ease of use,
create scripts that default the pull-pkg -p and -D params

re-create pull-pkg script also, to call ubuntutools/pullpkg.py
without any default pull or distro parameters

0d9ca97... by Dan Streetman on 2017-09-15

pull-pkg: change pull-pkg script code into modular code

bbb62b2... by Dan Streetman on 2017-09-15

pull-pkg: rename pull-pkg to ubuntutools/pullpkg.py module

rename only with no content changes, so next commit shows code
changes converting script pull-pkg to module pullpkg.py

0b5736b... by Dan Streetman on 2017-09-14

pull-pkg: update to use previous SourcePackage improvements

New pull-pkg allows pulling source, debs, ddebs, or udebs, or just
listing all package files. Also, package lookup by binary name is done
automatically.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/debian/control b/debian/control
2index 19f2ba5..baf48a9 100644
3--- a/debian/control
4+++ b/debian/control
5@@ -106,7 +106,13 @@ Description: useful tools for Ubuntu developers
6 a Debian package and its immediate parent to generate a debdiff.
7 - pull-debian-source - downloads the latest source package available in
8 Debian of a package.
9- - pull-lp-source - downloads latest source package from Launchpad.
10+ - pull-lp-source - downloads source package from Launchpad.
11+ - pull-lp-debs - downloads debs package(s) from Launchpad.
12+ - pull-lp-ddebs - downloads dbgsym/ddebs package(s) from Launchpad.
13+ - pull-lp-udebs - downloads udebs package(s) from Launchpad.
14+ - pull-debian-* - same as pull-lp-* but for Debian packages.
15+ - pull-uca-* - same as pull-lp-* but for Ubuntu Cloud Archive packages.
16+ - pull-pkg - common script that provides above pull-* functionality.
17 - pull-revu-source - downloads the latest source package from REVU
18 - requestbackport - file a backporting request.
19 - requestsync - files a sync request with Debian changelog and rationale.
20diff --git a/doc/pull-debian-ddebs.1 b/doc/pull-debian-ddebs.1
21new file mode 120000
22index 0000000..a67bbc7
23--- /dev/null
24+++ b/doc/pull-debian-ddebs.1
25@@ -0,0 +1 @@
26+pull-pkg.1
27\ No newline at end of file
28diff --git a/doc/pull-debian-debs.1 b/doc/pull-debian-debs.1
29new file mode 120000
30index 0000000..a67bbc7
31--- /dev/null
32+++ b/doc/pull-debian-debs.1
33@@ -0,0 +1 @@
34+pull-pkg.1
35\ No newline at end of file
36diff --git a/doc/pull-debian-source.1 b/doc/pull-debian-source.1
37deleted file mode 100644
38index 468724a..0000000
39--- a/doc/pull-debian-source.1
40+++ /dev/null
41@@ -1,89 +0,0 @@
42-.\" Copyright (C) 2010-2011, Stefano Rivera <stefanor@ubuntu.com>
43-.\"
44-.\" Permission to use, copy, modify, and/or distribute this software for any
45-.\" purpose with or without fee is hereby granted, provided that the above
46-.\" copyright notice and this permission notice appear in all copies.
47-.\"
48-.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
49-.\" REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
50-.\" AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
51-.\" INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
52-.\" LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
53-.\" OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
54-.\" PERFORMANCE OF THIS SOFTWARE.
55-.TH PULL\-DEBIAN\-SOURCE "1" "22 January 2011" "ubuntu\-dev\-tools"
56-
57-.SH NAME
58-pull\-debian\-source \- download and extract a source package from Debian
59-
60-.SH SYNOPSIS
61-.B pull\-debian\-source \fR[\fIoptions\fR] <\fIsource package\fR>
62-[\fIrelease\fR|\fIversion\fR]
63-
64-.SH DESCRIPTION
65-\fBpull\-debian\-source\fR downloads and extracts the specified
66-\fIversion\fR of \fIsource package\fR, or the latest version in the
67-specified Debian \fIrelease\fR.
68-.P
69-\fBpull\-debian\-source\fR will try the preferred mirror, default
70-mirror, security mirror, and fall back to \fBLaunchpad\fR or
71-\fBsnapshot.debian.org\fR, in search of the requested version.
72-
73-.SH OPTIONS
74-.TP
75-.I source package
76-The source package to download from Debian.
77-.TP
78-.I release
79-The release to download the source package from. Defaults to
80-\fBunstable\fR.
81-.TP
82-.I version
83-The specific version of the package to download.
84-.TP
85-.BR \-d ", " \-\-download\-only
86-Do not extract the source package.
87-.TP
88-.B \-m \fIDEBIAN_MIRROR\fR, \fB\-\-mirror\fR=\fIDEBIAN_MIRROR\fR
89-Use the specified mirror.
90-Should be in the form \fBhttp://ftp.debian.org/debian\fR.
91-If the package isn't found on this mirror, \fBpull\-debian\-source\fR
92-will fall back to the default mirror.
93-.TP
94-.B \-s \fIDEBSEC_MIRROR\fR, \fB\-\-security\-mirror\fR=\fIDEBSEC_MIRROR\fR
95-Use the specified mirror.
96-Should be in the form \fBhttp://security.debian.org\fR.
97-If the package isn't found on this mirror, \fBpull\-debian\-source\fR
98-will fall back to the default mirror.
99-.TP
100-.B \-\-no\-conf
101-Do not read any configuration files, or configuration from environment
102-variables.
103-.TP
104-.BR \-h ", " \-\-help
105-Display the usage instructions and exit.
106-
107-.SH ENVIRONMENT
108-All of the \fBCONFIGURATION VARIABLES\fR below are also supported as
109-environment variables.
110-Variables in the environment take precedence to those in configuration
111-files.
112-
113-.SH CONFIGURATION VARIABLES
114-The following variables can be set in the environment or in
115-.BR ubuntu\-dev\-tools (5)
116-configuration files.
117-In each case, the script\-specific variable takes precedence over the
118-package\-wide variable.
119-.TP
120-.BR PULL_DEBIAN_SOURCE_DEBIAN_MIRROR ", " UBUNTUTOOLS_DEBIAN_MIRROR
121-The default value for \fB\-\-mirror\fR.
122-.TP
123-.BR PULL_DEBIAN_SOURCE_DEBSEC_MIRROR ", " UBUNTUTOOLS_DEBSEC_MIRROR
124-The default value for \fB\-\-security\-mirror\fR.
125-
126-.SH SEE ALSO
127-.BR dget (1),
128-.BR pull\-debian\-debdiff (1),
129-.BR pull\-lp\-source (1),
130-.BR ubuntu\-dev\-tools (5)
131diff --git a/doc/pull-debian-source.1 b/doc/pull-debian-source.1
132new file mode 120000
133index 0000000..a67bbc7
134--- /dev/null
135+++ b/doc/pull-debian-source.1
136@@ -0,0 +1 @@
137+pull-pkg.1
138\ No newline at end of file
139diff --git a/doc/pull-debian-udebs.1 b/doc/pull-debian-udebs.1
140new file mode 120000
141index 0000000..a67bbc7
142--- /dev/null
143+++ b/doc/pull-debian-udebs.1
144@@ -0,0 +1 @@
145+pull-pkg.1
146\ No newline at end of file
147diff --git a/doc/pull-lp-ddebs.1 b/doc/pull-lp-ddebs.1
148new file mode 120000
149index 0000000..a67bbc7
150--- /dev/null
151+++ b/doc/pull-lp-ddebs.1
152@@ -0,0 +1 @@
153+pull-pkg.1
154\ No newline at end of file
155diff --git a/doc/pull-lp-debs.1 b/doc/pull-lp-debs.1
156new file mode 120000
157index 0000000..a67bbc7
158--- /dev/null
159+++ b/doc/pull-lp-debs.1
160@@ -0,0 +1 @@
161+pull-pkg.1
162\ No newline at end of file
163diff --git a/doc/pull-lp-source.1 b/doc/pull-lp-source.1
164deleted file mode 100644
165index 757857a..0000000
166--- a/doc/pull-lp-source.1
167+++ /dev/null
168@@ -1,79 +0,0 @@
169-.TH PULL\-LP\-SOURCE "1" "4 August 2008" "ubuntu-dev-tools"
170-
171-.SH NAME
172-pull\-lp\-source \- download a source package from Launchpad
173-
174-.SH SYNOPSIS
175-.B pull\-lp\-source \fR[\fIoptions\fR]\fB \fBsource package\fR
176-[\fIrelease\fR|\fIversion\fR]
177-
178-.SH DESCRIPTION
179-\fBpull\-lp\-source\fR downloads and extracts the specified
180-\fIversion\fR of <\fBsource package\fR> from Launchpad, or the latest
181-version of the specified \fIrelease\fR.
182-To request a version from a particular pocket say
183-\fIrelease\fB\-\fIpocket\fR (with a magic \fB\-release\fR for only the
184-release pocket).
185-If no \fIversion\fR or \fIrelease\fR is specified, the latest version in
186-the development release will be downloaded.
187-
188-.SH OPTIONS
189-Listed below are the command line options for pull\-lp\-source:
190-.TP
191-.B source package
192-This is the source package that you would like to be downloaded from Launchpad.
193-.TP
194-.B version
195-This is the version of the source package to be downloaded.
196-.TP
197-.B release
198-This is the release that you would like the source package to be downloaded from.
199-This value defaults to the current development release.
200-.TP
201-.BR \-h ", " \-\-help
202-Display a help message and exit.
203-.TP
204-.BR \-d ", " \-\-download\-only
205-Do not extract the source package.
206-.TP
207-.B \-m \fIUBUNTU_MIRROR\fR, \fB\-\-mirror\fR=\fIUBUNTU_MIRROR\fR
208-Use the specified Ubuntu mirror.
209-Should be in the form \fBhttp://archive.ubuntu.com/ubuntu\fR.
210-If the package isn't found on this mirror, \fBpull\-lp\-source\fR will
211-fall back to Launchpad, as its name implies.
212-.TP
213-.B \-\-no\-conf
214-Do not read any configuration files, or configuration from environment
215-variables.
216-
217-.SH ENVIRONMENT
218-All of the \fBCONFIGURATION VARIABLES\fR below are also supported as
219-environment variables.
220-Variables in the environment take precedence to those in configuration
221-files.
222-.TP
223-.B
224-DIST
225-Specifies the default target.
226-
227-.SH CONFIGURATION VARIABLES
228-The following variables can be set in the environment or in
229-.BR ubuntu\-dev\-tools (5)
230-configuration files.
231-In each case, the script\-specific variable takes precedence over the
232-package\-wide variable.
233-.TP
234-.BR PULL_LP_SOURCE_UBUNTU_MIRROR ", " UBUNTUTOOLS_UBUNTU_MIRROR
235-The default value for \fB\-\-mirror\fR.
236-
237-.SH SEE ALSO
238-.BR dget (1),
239-.BR pull\-debian\-source (1),
240-.BR pull\-debian\-debdiff (1),
241-.BR ubuntu\-dev\-tools (5)
242-
243-.SH AUTHOR
244-.PP
245-\fBpull\-lp\-source\fR and this manual page were written by Iain Lane
246-<iain@orangesquash.org.uk>.
247-Both are released under the GNU General Public License, version 3 or later.
248diff --git a/doc/pull-lp-source.1 b/doc/pull-lp-source.1
249new file mode 120000
250index 0000000..a67bbc7
251--- /dev/null
252+++ b/doc/pull-lp-source.1
253@@ -0,0 +1 @@
254+pull-pkg.1
255\ No newline at end of file
256diff --git a/doc/pull-lp-udebs.1 b/doc/pull-lp-udebs.1
257new file mode 120000
258index 0000000..a67bbc7
259--- /dev/null
260+++ b/doc/pull-lp-udebs.1
261@@ -0,0 +1 @@
262+pull-pkg.1
263\ No newline at end of file
264diff --git a/doc/pull-pkg.1 b/doc/pull-pkg.1
265new file mode 100644
266index 0000000..f62aa11
267--- /dev/null
268+++ b/doc/pull-pkg.1
269@@ -0,0 +1,134 @@
270+.TH PULL\-PKG "1" "28 August 2017" "ubuntu-dev-tools"
271+
272+.SH NAME
273+pull\-pkg \- download a package for Debian, Ubuntu, or UCA
274+
275+.SH SYNOPSIS
276+.B pull\-pkg \fR[\fIoptions\fR]\fR <\fIpackage name\fR>
277+[\fIrelease\fR|\fIversion\fR]
278+
279+.SH DESCRIPTION
280+\fBpull\-pkg\fR downloads the specified \fIversion\fR of
281+<\fIpackage name\fR>, or the latest version from the
282+specified \fIrelease\fR. To request a version from
283+a particular pocket say \fIrelease\fB\-\fIpocket\fR (with a magic
284+\fB\-release\fR for only the release pocket).
285+If no \fIversion\fR or \fIrelease\fR is specified, the latest version in
286+the development release will be downloaded.
287+
288+There are convenience scripts that set pull type and distribution
289+appropriately: these are
290+\fBpull\-lp\-source\fR, \fBpull\-lp\-debs\fR, \fBpull\-lp\-ddebs\fR,
291+and \fBpull\-lp\-udebs\fR, which all pull Ubuntu packages;
292+\fBpull\-debian\-source\fR, \fBpull\-debian\-debs\fR, \fBpull\-debian\-ddebs\fR,
293+and \fBpull\-debian\-udebs\fR, which all pull Debian packages;
294+and \fBpull\-uca\-source\fR, \fBpull\-uca\-debs\fR, \fBpull\-uca\-ddebs\fR,
295+and \fBpull\-uca\-udebs\fR, which all pull Ubuntu Cloud Archive packages.
296+Each script pulls the file type in its name, i.e.
297+\fIsource\fR, \fIdebs\fR, \fIddebs\fR, or \fIudebs\fR.
298+
299+.SH OPTIONS
300+Listed below are the command line options for pull\-pkg:
301+.TP
302+.I package name
303+This is name of the package to downloaded.
304+You can use either the source package name, or binary package name.
305+.TP
306+.I version
307+This is the version of the package to downloaded.
308+.TP
309+.I release
310+This is the release to downloaded from.
311+For debian, you can use either the release name like \fBjessie\fR
312+or \fBsid\fR, or you can use the special release names \fBunstable\fR,
313+\fBstable\fR, or \fBtesting\fR.
314+For ubuntu, you can use either the release name like \fBxenial\fR
315+or the release-pocket like \fBxenial-proposed\fR.
316+For ubuntu cloud archive (uca) you can use either the uca release
317+name like \fBmitaka\fR or the ubuntu and uca release names like
318+\fBtrusty-mitaka\fR.
319+Defaults to the current development release.
320+.TP
321+.BR \-h ", " \-\-help
322+Display a help message and exit.
323+.TP
324+.BR \-v ", " \-\-verbose
325+Be verbose about what is being done.
326+.TP
327+.BR \-d ", " \-\-download\-only
328+Do not extract the source package (applies only to source packages).
329+.TP
330+.B \-m \fIMIRROR\fR, \fB\-\-mirror\fR=\fIMIRROR\fR
331+Use the specified mirror server.
332+Should be in the form \fBhttp://archive.ubuntu.com/ubuntu\fR or
333+\fBhttp://deb.debian.org/debian\fR. If not specified or if the
334+package is not found on the specified mirror, this will fall
335+back to the default mirror(s) and/or mirror(s) from environment
336+variables, and then will fall back to Launchpad or Debian Snapshot.
337+This can be specified multiple times to try multiple mirrors.
338+.TP
339+.B \-\-no\-conf
340+Do not use mirrors from the default configuration, or from
341+any environment variables.
342+.TP
343+.B \-a \fIARCH\fR, \fB\-\-arch\fR=\fIARCH\fR
344+Get binary packages from the \fIARCH\fR architecture.
345+Defaults to the local architecture, if it can be deteected.
346+.TP
347+.B \-p \fIPULL\fR, \fB\-\-pull\fR=\fIPULL\fR
348+What to pull: \fBsource\fR, \fBdebs\fR, \fBddebs\fR, \fBudebs\fR,
349+or \fBlist\fR. The \fBlist\fR action only lists all a package's
350+source and binary files, but does not actually download any.
351+Defaults to \fBsource\fR.
352+.TP
353+.B \-D \fIDISTRO\fR, \fB\-\-distro\fR=\fIDISTRO\fR
354+Pull from: \fBdebian\fR, \fBuca\fR, or \fBubuntu\fR.
355+\fBlp\fR can be used instead of \fBubuntu\fR.
356+Any string containing \fBcloud\fR can be used instead of \fBuca\fR.
357+Deafults to \fBubuntu\fR.
358+
359+.SH ENVIRONMENT
360+All of the \fBCONFIGURATION VARIABLES\fR below are also supported as
361+environment variables.
362+Variables in the environment take precedence to those in configuration
363+files.
364+
365+.SH CONFIGURATION VARIABLES
366+The following variables can be set in the environment or in
367+.BR ubuntu\-dev\-tools (5)
368+configuration files.
369+In each case, the script\-specific variable takes precedence over the
370+package\-wide variable.
371+.TP
372+.BR UBUNTUTOOLS_UBUNTU_MIRROR
373+The default mirror.
374+.TP
375+.BR PULL_PKG_UBUNTU_MIRROR
376+The default mirror when using the \fBpull\-pkg\fR script.
377+.TP
378+.BR PULL_[LP|DEBIAN|UCA]_[SOURCE|DEBS|DDEBS|UDEBS]_MIRROR
379+The default mirror when using the associated script.
380+
381+.SH SEE ALSO
382+.BR dget (1),
383+.BR pull\-lp\-source (1),
384+.BR pull\-lp\-debs (1),
385+.BR pull\-lp\-ddebs (1),
386+.BR pull\-lp\-udebs (1),
387+.BR pull\-debian\-source (1),
388+.BR pull\-debian\-debs (1),
389+.BR pull\-debian\-ddebs (1),
390+.BR pull\-debian\-udebs (1),
391+.BR pull\-uca\-source (1),
392+.BR pull\-uca\-debs (1),
393+.BR pull\-uca\-ddebs (1),
394+.BR pull\-uca\-udebs (1),
395+.BR pull\-debian\-debdiff (1),
396+.BR ubuntu\-dev\-tools (5)
397+
398+.SH AUTHOR
399+.PP
400+\fBpull\-pkg\fR was written by Dan Streetman <dan.streetman@canonical.com>,
401+based on the original \fBpull\-lp\-source\fR; it and this manual page
402+were written by Iain Lane <iain@orangesquash.org.uk>.
403+All are released under the GNU General Public License, version 3 or later.
404diff --git a/doc/pull-uca-ddebs.1 b/doc/pull-uca-ddebs.1
405new file mode 120000
406index 0000000..a67bbc7
407--- /dev/null
408+++ b/doc/pull-uca-ddebs.1
409@@ -0,0 +1 @@
410+pull-pkg.1
411\ No newline at end of file
412diff --git a/doc/pull-uca-debs.1 b/doc/pull-uca-debs.1
413new file mode 120000
414index 0000000..a67bbc7
415--- /dev/null
416+++ b/doc/pull-uca-debs.1
417@@ -0,0 +1 @@
418+pull-pkg.1
419\ No newline at end of file
420diff --git a/doc/pull-uca-source.1 b/doc/pull-uca-source.1
421new file mode 120000
422index 0000000..a67bbc7
423--- /dev/null
424+++ b/doc/pull-uca-source.1
425@@ -0,0 +1 @@
426+pull-pkg.1
427\ No newline at end of file
428diff --git a/doc/pull-uca-udebs.1 b/doc/pull-uca-udebs.1
429new file mode 120000
430index 0000000..a67bbc7
431--- /dev/null
432+++ b/doc/pull-uca-udebs.1
433@@ -0,0 +1 @@
434+pull-pkg.1
435\ No newline at end of file
436diff --git a/pull-debian-ddebs b/pull-debian-ddebs
437new file mode 100755
438index 0000000..05fd628
439--- /dev/null
440+++ b/pull-debian-ddebs
441@@ -0,0 +1,16 @@
442+#!/usr/bin/python
443+#
444+# pull-debian-ddebs -- pull ddeb package files for debian
445+# Basic usage: pull-debian-ddebs <package name> [version|release]
446+#
447+# See pull-pkg
448+
449+from ubuntutools import pullpkg
450+from ubuntutools.logger import Logger
451+
452+if __name__ == '__main__':
453+ try:
454+ parser = pullpkg.create_argparser(default_distro='debian', default_pull='ddebs')
455+ pullpkg.pull(parser.parse_args())
456+ except KeyboardInterrupt:
457+ Logger.normal('User abort.')
458diff --git a/pull-debian-debs b/pull-debian-debs
459new file mode 100755
460index 0000000..21672a0
461--- /dev/null
462+++ b/pull-debian-debs
463@@ -0,0 +1,16 @@
464+#!/usr/bin/python
465+#
466+# pull-debian-debs -- pull deb package files for debian
467+# Basic usage: pull-debian-debs <package name> [version|release]
468+#
469+# See pull-pkg
470+
471+from ubuntutools import pullpkg
472+from ubuntutools.logger import Logger
473+
474+if __name__ == '__main__':
475+ try:
476+ parser = pullpkg.create_argparser(default_distro='debian', default_pull='debs')
477+ pullpkg.pull(parser.parse_args())
478+ except KeyboardInterrupt:
479+ Logger.normal('User abort.')
480diff --git a/pull-debian-source b/pull-debian-source
481index d4e0519..9c82e6f 100755
482--- a/pull-debian-source
483+++ b/pull-debian-source
484@@ -1,143 +1,16 @@
485 #!/usr/bin/python
486 #
487-# pull-debian-source -- pull a source package from Launchpad
488-# Copyright (C) 2011, Stefano Rivera <stefanor@ubuntu.com>
489-# Inspired by a tool of the same name by Nathan Handler.
490+# pull-debian-source -- pull source package files for debian
491+# Basic usage: pull-debian-source <package name> [version|release]
492 #
493-# Permission to use, copy, modify, and/or distribute this software for any
494-# purpose with or without fee is hereby granted, provided that the above
495-# copyright notice and this permission notice appear in all copies.
496-#
497-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
498-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
499-# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
500-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
501-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
502-# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
503-# PERFORMANCE OF THIS SOFTWARE.
504-
505-import json
506-import optparse
507-import sys
508-import urllib2
509-
510-from distro_info import DebianDistroInfo, DistroDataOutdated
511+# See pull-pkg
512
513-from ubuntutools.archive import DebianSourcePackage, DownloadError, rmadison
514-from ubuntutools.config import UDTConfig
515+from ubuntutools import pullpkg
516 from ubuntutools.logger import Logger
517
518-
519-def is_suite(version):
520- """If version could be considered to be a Debian suite, return the
521- canonical suite name. Otherwise None
522- """
523- debian_info = DebianDistroInfo()
524- debian_releases = debian_info.all + ['experimental']
525-
526- if '-' in version:
527- release, pocket = version.split('-', 1)
528- release = debian_info.codename(release, default=release)
529- if release in debian_releases:
530- if pocket in ('proposed-updates', 'p-u'):
531- return (release + '-proposed-updates')
532- elif pocket == 'security':
533- return (release + '-security')
534- else:
535- release = debian_info.codename(version, default=version)
536- if release in debian_releases:
537- return release
538- return None
539-
540-
541-def source_package_for(binary, release):
542- """Query DDE to find the source package for a particular binary"""
543- try:
544- release = DebianDistroInfo().codename(release, default=release)
545- except DistroDataOutdated, e:
546- Logger.warn(e)
547- url = ('http://dde.debian.net/dde/q/udd/dist/d:debian/r:%s/p:%s/?t=json'
548- % (release, binary))
549- data = None
550- try:
551- data = json.load(urllib2.urlopen(url))['r']
552- except urllib2.URLError, e:
553- Logger.error('Unable to retrieve package information from DDE: '
554- '%s (%s)', url, str(e))
555- except ValueError, e:
556- Logger.error('Unable to parse JSON response from DDE: '
557- '%s (%s)', url, str(e))
558- if not data:
559- return None
560- return data[0]['source']
561-
562-
563-def main():
564- usage = 'Usage: %prog <package> [release|version]'
565- parser = optparse.OptionParser(usage)
566- parser.add_option('-d', '--download-only',
567- dest='download_only', default=False, action='store_true',
568- help='Do not extract the source package')
569- parser.add_option('-m', '--mirror', metavar='DEBIAN_MIRROR',
570- dest='debian_mirror',
571- help='Preferred Debian mirror (default: %s)'
572- % UDTConfig.defaults['DEBIAN_MIRROR'])
573- parser.add_option('-s', '--security-mirror', metavar='DEBSEC_MIRROR',
574- dest='debsec_mirror',
575- help='Preferred Debian Security mirror (default: %s)'
576- % UDTConfig.defaults['DEBSEC_MIRROR'])
577- parser.add_option('--no-conf',
578- dest='no_conf', default=False, action='store_true',
579- help="Don't read config files or environment variables")
580- (options, args) = parser.parse_args()
581- if not args:
582- parser.error('Must specify package name')
583- elif len(args) > 2:
584- parser.error('Too many arguments. '
585- 'Must only specify package and (optionally) release.')
586-
587- config = UDTConfig(options.no_conf)
588- if options.debian_mirror is None:
589- options.debian_mirror = config.get_value('DEBIAN_MIRROR')
590- if options.debsec_mirror is None:
591- options.debsec_mirror = config.get_value('DEBSEC_MIRROR')
592-
593- package = args[0].lower()
594-
595- version = args[1] if len(args) > 1 else 'unstable'
596- component = None
597-
598- suite = is_suite(version)
599- if suite is not None:
600- line = list(rmadison('debian', package, suite, 'source'))
601- if not line:
602- source_package = source_package_for(package, suite)
603- if source_package is not None and package != source_package:
604- package = source_package
605- line = list(rmadison('debian', package, suite, 'source'))
606- if not line:
607- Logger.error('Unable to find %s in Debian suite "%s".', package,
608- suite)
609- sys.exit(1)
610- line = line[-1]
611- version = line['version']
612- component = line['component']
613-
614- Logger.normal('Downloading %s version %s', package, version)
615- srcpkg = DebianSourcePackage(package, version, component=component,
616- mirrors=[options.debian_mirror,
617- options.debsec_mirror])
618- try:
619- srcpkg.pull()
620- except DownloadError, e:
621- Logger.error('Failed to download: %s', str(e))
622- sys.exit(1)
623- if not options.download_only:
624- srcpkg.unpack()
625-
626-
627 if __name__ == '__main__':
628 try:
629- main()
630+ parser = pullpkg.create_argparser(default_distro='debian', default_pull='source')
631+ pullpkg.pull(parser.parse_args())
632 except KeyboardInterrupt:
633 Logger.normal('User abort.')
634diff --git a/pull-debian-udebs b/pull-debian-udebs
635new file mode 100755
636index 0000000..808ad54
637--- /dev/null
638+++ b/pull-debian-udebs
639@@ -0,0 +1,16 @@
640+#!/usr/bin/python
641+#
642+# pull-debian-udebs -- pull udeb package files for debian
643+# Basic usage: pull-debian-udebs <package name> [version|release]
644+#
645+# See pull-pkg
646+
647+from ubuntutools import pullpkg
648+from ubuntutools.logger import Logger
649+
650+if __name__ == '__main__':
651+ try:
652+ parser = pullpkg.create_argparser(default_distro='debian', default_pull='udebs')
653+ pullpkg.pull(parser.parse_args())
654+ except KeyboardInterrupt:
655+ Logger.normal('User abort.')
656diff --git a/pull-lp-ddebs b/pull-lp-ddebs
657new file mode 100755
658index 0000000..39eb0f7
659--- /dev/null
660+++ b/pull-lp-ddebs
661@@ -0,0 +1,16 @@
662+#!/usr/bin/python
663+#
664+# pull-lp-ddebs -- pull ddeb package files for ubuntu
665+# Basic usage: pull-lp-ddebs <package name> [version|release]
666+#
667+# See pull-pkg
668+
669+from ubuntutools import pullpkg
670+from ubuntutools.logger import Logger
671+
672+if __name__ == '__main__':
673+ try:
674+ parser = pullpkg.create_argparser(default_distro='ubuntu', default_pull='ddebs')
675+ pullpkg.pull(parser.parse_args())
676+ except KeyboardInterrupt:
677+ Logger.normal('User abort.')
678diff --git a/pull-lp-debs b/pull-lp-debs
679new file mode 100755
680index 0000000..a66d470
681--- /dev/null
682+++ b/pull-lp-debs
683@@ -0,0 +1,16 @@
684+#!/usr/bin/python
685+#
686+# pull-lp-debs -- pull deb package files for ubuntu
687+# Basic usage: pull-lp-debs <package name> [version|release]
688+#
689+# See pull-pkg
690+
691+from ubuntutools import pullpkg
692+from ubuntutools.logger import Logger
693+
694+if __name__ == '__main__':
695+ try:
696+ parser = pullpkg.create_argparser(default_distro='ubuntu', default_pull='debs')
697+ pullpkg.pull(parser.parse_args())
698+ except KeyboardInterrupt:
699+ Logger.normal('User abort.')
700diff --git a/pull-lp-source b/pull-lp-source
701index 9cb865a..4a7f3cc 100755
702--- a/pull-lp-source
703+++ b/pull-lp-source
704@@ -1,149 +1,16 @@
705 #!/usr/bin/python
706 #
707-# pull-lp-source -- pull a source package from Launchpad
708-# Basic usage: pull-lp-source <source package> [<release>]
709+# pull-lp-source -- pull source package files for ubuntu
710+# Basic usage: pull-lp-source <package name> [version|release]
711 #
712-# Copyright (C) 2008, Iain Lane <iain@orangesquash.org.uk>,
713-# 2010-2011, Stefano Rivera <stefanor@ubuntu.com>
714-#
715-# ##################################################################
716-#
717-# This program is free software; you can redistribute it and/or
718-# modify it under the terms of the GNU General Public License
719-# as published by the Free Software Foundation; either version 3
720-# of the License, or (at your option) any later version.
721-#
722-# This program is distributed in the hope that it will be useful,
723-# but WITHOUT ANY WARRANTY; without even the implied warranty of
724-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
725-# GNU General Public License for more details.
726-#
727-# See file /usr/share/common-licenses/GPL for more details.
728-#
729-# ##################################################################
730-
731+# See pull-pkg
732
733-import json
734-import os
735-import sys
736-import urllib2
737-from optparse import OptionParser
738-
739-from distro_info import UbuntuDistroInfo, DistroDataOutdated
740-
741-from ubuntutools.archive import UbuntuSourcePackage, DownloadError
742-from ubuntutools.config import UDTConfig
743-from ubuntutools.lp.lpapicache import Distribution, Launchpad
744-from ubuntutools.lp.udtexceptions import (SeriesNotFoundException,
745- PackageNotFoundException,
746- PocketDoesNotExistError)
747+from ubuntutools import pullpkg
748 from ubuntutools.logger import Logger
749-from ubuntutools.misc import split_release_pocket
750-
751-
752-def source_package_for(binary, release):
753- """Query DDE to find the source package for a particular binary
754- Should really do this with LP, but it's not possible LP: #597041
755- """
756- url = ('http://dde.debian.net/dde/q/udd/dist/d:ubuntu/r:%s/p:%s/?t=json'
757- % (release, binary))
758- data = None
759- try:
760- data = json.load(urllib2.urlopen(url))['r']
761- except urllib2.URLError, e:
762- Logger.error('Unable to retrieve package information from DDE: '
763- '%s (%s)', url, str(e))
764- except ValueError, e:
765- Logger.error('Unable to parse JSON response from DDE: '
766- '%s (%s)', url, str(e))
767- if not data:
768- return None
769- return data[0]['source']
770-
771-
772-def main():
773- usage = "Usage: %prog <package> [release|version]"
774- opt_parser = OptionParser(usage)
775- opt_parser.add_option('-d', '--download-only',
776- dest='download_only', default=False,
777- action='store_true',
778- help="Do not extract the source package")
779- opt_parser.add_option('-m', '--mirror', metavar='UBUNTU_MIRROR',
780- dest='ubuntu_mirror',
781- help='Preferred Ubuntu mirror (default: Launchpad)')
782- opt_parser.add_option('--no-conf',
783- dest='no_conf', default=False, action='store_true',
784- help="Don't read config files or environment "
785- "variables")
786- (options, args) = opt_parser.parse_args()
787- if not args:
788- opt_parser.error("Must specify package name")
789-
790- config = UDTConfig(options.no_conf)
791- if options.ubuntu_mirror is None:
792- options.ubuntu_mirror = config.get_value('UBUNTU_MIRROR')
793-
794- # Login anonymously to LP
795- Launchpad.login_anonymously()
796-
797- package = str(args[0]).lower()
798-
799- ubuntu_info = UbuntuDistroInfo()
800- if len(args) > 1: # Custom distribution specified.
801- version = str(args[1])
802- else:
803- try:
804- version = os.getenv('DIST') or ubuntu_info.devel()
805- except DistroDataOutdated, e:
806- Logger.warn("%s\nOr specify a distribution.", e)
807- sys.exit(1)
808- component = None
809-
810- # Release, not package version number:
811- release = None
812- pocket = None
813- try:
814- (release, pocket) = split_release_pocket(version, default=None)
815- except PocketDoesNotExistError, e:
816- pass
817- if release in ubuntu_info.all:
818- archive = Distribution('ubuntu').getArchive()
819- try:
820- spph = archive.getSourcePackage(package, release, pocket)
821- except SeriesNotFoundException, e:
822- Logger.error(str(e))
823- sys.exit(1)
824- except PackageNotFoundException, e:
825- source_package = source_package_for(package, release)
826- if source_package is not None and source_package != package:
827- try:
828- spph = archive.getSourcePackage(source_package, release,
829- pocket)
830- package = source_package
831- except PackageNotFoundException:
832- Logger.error(str(e))
833- sys.exit(1)
834- else:
835- Logger.error(str(e))
836- sys.exit(1)
837-
838- version = spph.getVersion()
839- component = spph.getComponent()
840-
841- Logger.normal('Downloading %s version %s', package, version)
842- srcpkg = UbuntuSourcePackage(package, version, component=component,
843- mirrors=[options.ubuntu_mirror])
844- try:
845- srcpkg.pull()
846- except DownloadError, e:
847- Logger.error('Failed to download: %s', str(e))
848- sys.exit(1)
849- if not options.download_only:
850- srcpkg.unpack()
851-
852
853 if __name__ == '__main__':
854 try:
855- main()
856+ parser = pullpkg.create_argparser(default_distro='ubuntu', default_pull='source')
857+ pullpkg.pull(parser.parse_args())
858 except KeyboardInterrupt:
859 Logger.normal('User abort.')
860diff --git a/pull-lp-udebs b/pull-lp-udebs
861new file mode 100755
862index 0000000..d050b61
863--- /dev/null
864+++ b/pull-lp-udebs
865@@ -0,0 +1,16 @@
866+#!/usr/bin/python
867+#
868+# pull-lp-udebs -- pull udeb package files for ubuntu
869+# Basic usage: pull-lp-udebs <package name> [version|release]
870+#
871+# See pull-pkg
872+
873+from ubuntutools import pullpkg
874+from ubuntutools.logger import Logger
875+
876+if __name__ == '__main__':
877+ try:
878+ parser = pullpkg.create_argparser(default_distro='ubuntu', default_pull='udebs')
879+ pullpkg.pull(parser.parse_args())
880+ except KeyboardInterrupt:
881+ Logger.normal('User abort.')
882diff --git a/pull-pkg b/pull-pkg
883new file mode 100755
884index 0000000..98c5d22
885--- /dev/null
886+++ b/pull-pkg
887@@ -0,0 +1,33 @@
888+#!/usr/bin/python
889+#
890+# pull-pkg -- pull package files for debian/ubuntu/uca
891+# Basic usage: pull-pkg -D distro -p type <package name> [version|release]
892+#
893+# Copyright (C) 2008, Iain Lane <iain@orangesquash.org.uk>,
894+# 2010-2011, Stefano Rivera <stefanor@ubuntu.com>
895+# 2017, Dan Streetman <dan.streetman@canonical.com>
896+#
897+# ##################################################################
898+#
899+# This program is free software; you can redistribute it and/or
900+# modify it under the terms of the GNU General Public License
901+# as published by the Free Software Foundation; either version 3
902+# of the License, or (at your option) any later version.
903+#
904+# This program is distributed in the hope that it will be useful,
905+# but WITHOUT ANY WARRANTY; without even the implied warranty of
906+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
907+# GNU General Public License for more details.
908+#
909+# See file /usr/share/common-licenses/GPL for more details.
910+#
911+# ##################################################################
912+
913+from ubuntutools import pullpkg
914+from ubuntutools.logger import Logger
915+
916+if __name__ == '__main__':
917+ try:
918+ pullpkg.pull(pullpkg.create_argparser().parse_args())
919+ except KeyboardInterrupt:
920+ Logger.normal('User abort.')
921diff --git a/pull-uca-ddebs b/pull-uca-ddebs
922new file mode 100755
923index 0000000..baa6ace
924--- /dev/null
925+++ b/pull-uca-ddebs
926@@ -0,0 +1,16 @@
927+#!/usr/bin/python
928+#
929+# pull-uca-ddebs -- pull ddeb package files for ubuntu cloud archive
930+# Basic usage: pull-uca-ddebs <package name> [version|release]
931+#
932+# See pull-pkg
933+
934+from ubuntutools import pullpkg
935+from ubuntutools.logger import Logger
936+
937+if __name__ == '__main__':
938+ try:
939+ parser = pullpkg.create_argparser(default_distro='uca', default_pull='ddebs')
940+ pullpkg.pull(parser.parse_args())
941+ except KeyboardInterrupt:
942+ Logger.normal('User abort.')
943diff --git a/pull-uca-debs b/pull-uca-debs
944new file mode 100755
945index 0000000..76c72a7
946--- /dev/null
947+++ b/pull-uca-debs
948@@ -0,0 +1,16 @@
949+#!/usr/bin/python
950+#
951+# pull-uca-debs -- pull deb package files for ubuntu cloud archive
952+# Basic usage: pull-uca-debs <package name> [version|release]
953+#
954+# See pull-pkg
955+
956+from ubuntutools import pullpkg
957+from ubuntutools.logger import Logger
958+
959+if __name__ == '__main__':
960+ try:
961+ parser = pullpkg.create_argparser(default_distro='uca', default_pull='debs')
962+ pullpkg.pull(parser.parse_args())
963+ except KeyboardInterrupt:
964+ Logger.normal('User abort.')
965diff --git a/pull-uca-source b/pull-uca-source
966index 79a60f5..f2dbdf0 100755
967--- a/pull-uca-source
968+++ b/pull-uca-source
969@@ -1,157 +1,16 @@
970 #!/usr/bin/python
971 #
972-# pull-uca-source -- pull a source package from Ubuntu Cloud Archive
973-# Basic usage: pull-uca-source <source package> <openstack release> [version]
974+# pull-uca-source -- pull source package files for ubuntu cloud archive
975+# Basic usage: pull-uca-source <package name> [version|release]
976 #
977-# Copyright (C) 2008, Iain Lane <iain@orangesquash.org.uk>,
978-# 2010-2011, Stefano Rivera <stefanor@ubuntu.com>
979-# 2016, Corey Bryant <corey.bryant@ubuntu.com>
980-# 2016, Dan Streetman <dan.streetman@canonical.com>
981-#
982-# ##################################################################
983-#
984-# This program is free software; you can redistribute it and/or
985-# modify it under the terms of the GNU General Public License
986-# as published by the Free Software Foundation; either version 3
987-# of the License, or (at your option) any later version.
988-#
989-# This program is distributed in the hope that it will be useful,
990-# but WITHOUT ANY WARRANTY; without even the implied warranty of
991-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
992-# GNU General Public License for more details.
993-#
994-# See file /usr/share/common-licenses/GPL for more details.
995-#
996-# ##################################################################
997-
998+# See pull-pkg
999
1000-import re
1001-import sys
1002-from optparse import OptionParser
1003-
1004-from ubuntutools.archive import UbuntuCloudArchiveSourcePackage, DownloadError
1005-from ubuntutools.config import UDTConfig
1006-from ubuntutools.lp.lpapicache import Launchpad
1007-from ubuntutools.lp.udtexceptions import PocketDoesNotExistError
1008+from ubuntutools import pullpkg
1009 from ubuntutools.logger import Logger
1010-from ubuntutools.misc import split_release_pocket
1011-
1012-from lazr.restfulclient.errors import NotFound
1013-
1014-from launchpadlib.launchpad import Launchpad as LP
1015-
1016-
1017-def showOpenstackReleases(uca):
1018- releases = []
1019- for p in uca.ppas:
1020- if re.match(r"\w*-staging", p.name):
1021- releases.append(re.sub("-staging", "", p.name))
1022- Logger.error("Openstack releases are:\n\t%s", ", ".join(releases))
1023-
1024-
1025-def getSPPH(lp, archive, package, version=None, series=None, pocket=None, try_binary=True):
1026- params = {'exact_match': True, 'order_by_date': True}
1027- if pocket:
1028- params['pocket'] = pocket
1029- if series:
1030- params['distro_series'] = series()
1031- elif version:
1032- params['version'] = version
1033- Logger.normal("checking %s version %s pocket %s", package, version, pocket)
1034- spphs = archive.getPublishedSources(source_name=package, **params)
1035- if spphs:
1036- return spphs[0]
1037- if not try_binary:
1038- return None
1039-
1040- # Didn't find any, maybe the package is a binary package name
1041- if series:
1042- del params['distro_series']
1043- archs = lp.load(series().architectures_collection_link).entries
1044- params['distro_arch_series'] = archs[0]['self_link']
1045- bpphs = archive.getPublishedBinaries(binary_name=package, **params)
1046- if bpphs:
1047- bpph_build = lp.load(bpphs[0].build_link)
1048- source_package = bpph_build.source_package_name
1049- return getSPPH(lp, archive, source_package, version, series, pocket,
1050- try_binary=False)
1051-
1052- return None
1053-
1054-
1055-def main():
1056- usage = "Usage: %prog <package> <openstack release> [version]"
1057- opt_parser = OptionParser(usage)
1058- opt_parser.add_option('-d', '--download-only',
1059- dest='download_only', default=False,
1060- action='store_true',
1061- help="Do not extract the source package")
1062- opt_parser.add_option('-m', '--mirror', metavar='OPENSTACK_MIRROR',
1063- dest='openstack_mirror',
1064- help='Preferred Openstack mirror (default: Launchpad)')
1065- opt_parser.add_option('--no-conf',
1066- dest='no_conf', default=False, action='store_true',
1067- help="Don't read config files or environment "
1068- "variables")
1069- (options, args) = opt_parser.parse_args()
1070- if len(args) < 2:
1071- opt_parser.error("Must specify package name and openstack release")
1072-
1073- config = UDTConfig(options.no_conf)
1074- if options.openstack_mirror is None:
1075- options.openstack_mirror = config.get_value('OPENSTACK_MIRROR')
1076- mirrors = []
1077- if options.openstack_mirror:
1078- mirrors.append(options.openstack_mirror)
1079-
1080- # Login anonymously to LP
1081- Launchpad.login_anonymously()
1082- lp = LP.login_anonymously("pull-uca-source", "production")
1083- uca = lp.people("ubuntu-cloud-archive")
1084-
1085- package = str(args[0]).lower()
1086- release = str(args[1]).lower()
1087- version = None
1088- if len(args) > 2:
1089- version = str(args[2])
1090-
1091- pocket = None
1092- try:
1093- (release, pocket) = split_release_pocket(release, default=None)
1094- except PocketDoesNotExistError, e:
1095- pass
1096-
1097- try:
1098- archive = uca.getPPAByName(name="%s-staging" % release)
1099- except NotFound, e:
1100- Logger.error('Archive does not exist for Openstack release: %s',
1101- release)
1102- showOpenstackReleases(uca)
1103- sys.exit(1)
1104-
1105- spph = getSPPH(lp, archive, package, version, pocket=pocket)
1106- if not spph:
1107- Logger.error("Package %s in %s not found.", package, release)
1108- sys.exit(1)
1109-
1110- package = spph.source_package_name
1111- version = spph.source_package_version
1112- component = spph.component_name
1113- Logger.normal('Downloading %s version %s component %s', package, version, component)
1114- srcpkg = UbuntuCloudArchiveSourcePackage(release, package, version, component=component,
1115- mirrors=mirrors)
1116-
1117- try:
1118- srcpkg.pull()
1119- except DownloadError, e:
1120- Logger.error('Failed to download: %s', str(e))
1121- sys.exit(1)
1122- if not options.download_only:
1123- srcpkg.unpack()
1124-
1125
1126 if __name__ == '__main__':
1127 try:
1128- main()
1129+ parser = pullpkg.create_argparser(default_distro='uca', default_pull='source')
1130+ pullpkg.pull(parser.parse_args())
1131 except KeyboardInterrupt:
1132 Logger.normal('User abort.')
1133diff --git a/pull-uca-udebs b/pull-uca-udebs
1134new file mode 100755
1135index 0000000..a7008ff
1136--- /dev/null
1137+++ b/pull-uca-udebs
1138@@ -0,0 +1,16 @@
1139+#!/usr/bin/python
1140+#
1141+# pull-uca-udebs -- pull udeb package files for ubuntu cloud archive
1142+# Basic usage: pull-uca-udebs <package name> [version|release]
1143+#
1144+# See pull-pkg
1145+
1146+from ubuntutools import pullpkg
1147+from ubuntutools.logger import Logger
1148+
1149+if __name__ == '__main__':
1150+ try:
1151+ parser = pullpkg.create_argparser(default_distro='uca', default_pull='udebs')
1152+ pullpkg.pull(parser.parse_args())
1153+ except KeyboardInterrupt:
1154+ Logger.normal('User abort.')
1155diff --git a/setup.py b/setup.py
1156index 221e3a2..894a31f 100755
1157--- a/setup.py
1158+++ b/setup.py
1159@@ -34,11 +34,21 @@ else:
1160 'mk-sbuild',
1161 'pbuilder-dist',
1162 'pbuilder-dist-simple',
1163+ 'pull-pkg',
1164 'pull-debian-debdiff',
1165 'pull-debian-source',
1166+ 'pull-debian-debs',
1167+ 'pull-debian-ddebs',
1168+ 'pull-debian-udebs',
1169 'pull-lp-source',
1170+ 'pull-lp-debs',
1171+ 'pull-lp-ddebs',
1172+ 'pull-lp-udebs',
1173 'pull-revu-source',
1174 'pull-uca-source',
1175+ 'pull-uca-debs',
1176+ 'pull-uca-ddebs',
1177+ 'pull-uca-udebs',
1178 'requestbackport',
1179 'requestsync',
1180 'reverse-build-depends',
1181diff --git a/ubuntutools/archive.py b/ubuntutools/archive.py
1182index 9324313..dd1f54f 100644
1183--- a/ubuntutools/archive.py
1184+++ b/ubuntutools/archive.py
1185@@ -42,15 +42,22 @@ except ImportError:
1186 import re
1187 import sys
1188
1189-from debian.changelog import Changelog, Version
1190+from debian.changelog import Changelog
1191 import debian.deb822
1192-import debian.debian_support
1193 import httplib2
1194+import json
1195
1196 from ubuntutools.config import UDTConfig
1197-from ubuntutools.lp.lpapicache import (Launchpad, Distribution,
1198- SourcePackagePublishingHistory)
1199+from ubuntutools.lp.libsupport import get_system_arch
1200+from ubuntutools.lp.lpapicache import (Launchpad, Distribution, PersonTeam,
1201+ SourcePackagePublishingHistory,
1202+ BinaryPackagePublishingHistory)
1203+from ubuntutools.lp.udtexceptions import (PackageNotFoundException,
1204+ SeriesNotFoundException,
1205+ PocketDoesNotExistError)
1206 from ubuntutools.logger import Logger
1207+from ubuntutools.misc import split_release_pocket
1208+from ubuntutools.version import Version
1209 from ubuntutools import subprocess
1210
1211 if sys.version_info[0] >= 3:
1212@@ -130,58 +137,131 @@ class SourcePackage(object):
1213 distribution = None
1214
1215 def __init__(self, package=None, version=None, component=None,
1216- dscfile=None, lp=None, mirrors=(), workdir='.', quiet=False):
1217- "Can be initialised either using package, version or dscfile"
1218- assert ((package is not None and version is not None)
1219- or dscfile is not None)
1220+ dscfile=None, lp=None, mirrors=(), workdir='.', quiet=False,
1221+ series=None, pocket=None):
1222+ """Can be initialised using either package or dscfile.
1223+ If package is specified, either the version or series can also be
1224+ specified; using version will get the specific package version,
1225+ while using the series will get the latest version from that series.
1226+ Specifying only the package with no version or series will get the
1227+ latest version from the development series.
1228+ """
1229+ assert (package is not None or dscfile is not None)
1230
1231 self.source = package
1232 self._lp = lp
1233+ self.binary = None
1234+ self.try_binary = True
1235 self.workdir = workdir
1236 self.quiet = quiet
1237+ self._series = series
1238+ self._use_series = True
1239+ self._pocket = pocket
1240+ self._dsc_source = dscfile
1241
1242 # Cached values:
1243 self._component = component
1244 self._dsc = None
1245 self._spph = None
1246+ self._version = Version(version) if version else None
1247
1248 # State:
1249 self._dsc_fetched = False
1250
1251 # Mirrors
1252- self._dsc_source = dscfile
1253 self.mirrors = list(mirrors)
1254 if self.distribution:
1255 self.masters = [UDTConfig.defaults['%s_MIRROR'
1256 % self.distribution.upper()]]
1257- if dscfile is not None:
1258- if self.source is None:
1259- self.source = 'unknown'
1260- if version is None:
1261- version = 'unknown'
1262-
1263- self.version = debian.debian_support.Version(version)
1264
1265 # uses default proxies from the environment
1266 proxy = ProxyHandler()
1267 self.url_opener = build_opener(proxy)
1268
1269+ # if a dsc was specified, pull it to get the source/version info
1270+ if self._dsc_source:
1271+ self.pull_dsc()
1272+
1273 @property
1274 def lp_spph(self):
1275 "Return the LP Source Package Publishing History entry"
1276- if not self._spph:
1277- if not Launchpad.logged_in:
1278- if self._lp:
1279- Launchpad.login_existing(self._lp)
1280+ if self._spph:
1281+ return self._spph
1282+
1283+ if not Launchpad.logged_in:
1284+ if self._lp:
1285+ Launchpad.login_existing(self._lp)
1286+ else:
1287+ Launchpad.login_anonymously()
1288+
1289+ distro = Distribution(self.distribution)
1290+ series = None
1291+ archive = self.getArchive()
1292+ params = {'exact_match': True, 'order_by_date': True}
1293+ if self._version:
1294+ # if version was specified, use that
1295+ params['version'] = self._version.full_version
1296+ elif self._use_series:
1297+ if self._series:
1298+ # if version not specified, get the latest from this series
1299+ series = distro.getSeries(self._series)
1300+ else:
1301+ # if no version or series, get the latest from devel series
1302+ series = distro.getDevelopmentSeries()
1303+ params['distro_series'] = series()
1304+ if self._pocket:
1305+ params['pocket'] = self._pocket
1306+ spphs = archive.getPublishedSources(source_name=self.source, **params)
1307+ if spphs:
1308+ self._spph = SourcePackagePublishingHistory(spphs[0])
1309+ Logger.debug("Found SPPH {}".format(self._spph))
1310+ return self._spph
1311+
1312+ if self.try_binary and not self.binary:
1313+ if series:
1314+ arch_series = series.getArchSeries()
1315+ params['distro_arch_series'] = arch_series()
1316+ del params['distro_series']
1317+ bpphs = archive.getPublishedBinaries(binary_name=self.source, **params)
1318+ if bpphs:
1319+ bpph = BinaryPackagePublishingHistory(bpphs[0])
1320+ Logger.debug("Found BPPH {}".format(bpph))
1321+ self.binary = self.source
1322+ self.source = bpph.getSourcePackageName()
1323+ Logger.normal("Using source package '{}' for binary package '{}'"
1324+ .format(self.source, self.binary))
1325+ try:
1326+ spph = bpph.getBuild().getSourcePackagePublishingHistory()
1327+ except:
1328+ spph = None
1329+ if spph:
1330+ self._spph = spph
1331+ return self._spph
1332 else:
1333- Launchpad.login_anonymously()
1334- spph = Distribution(self.distribution).getArchive().getPublishedSources(
1335- source_name=self.source,
1336- version=self.version.full_version,
1337- exact_match=True,
1338- )
1339- self._spph = SourcePackagePublishingHistory(spph[0])
1340- return self._spph
1341+ # binary build didn't include source link, unfortunately
1342+ # so try again with the updated self.source name
1343+ if not self._version:
1344+ # Get version first if user didn't specify it, as some
1345+ # binaries have their version hardcoded in their name,
1346+ # such as the kernel package
1347+ self._version = Version(bpph.getVersion())
1348+ return self.lp_spph
1349+
1350+ msg = "No {} package found".format(self.source)
1351+ if self._version:
1352+ msg += " for version {}".format(self._version.full_version)
1353+ elif series:
1354+ msg += " in series {}".format(series.name)
1355+ if self._pocket:
1356+ msg += " pocket {}".format(self._pocket)
1357+ raise PackageNotFoundException(msg)
1358+
1359+ @property
1360+ def version(self):
1361+ "Return Package version"
1362+ if not self._version:
1363+ self._version = Version(self.lp_spph.getVersion())
1364+ return self._version
1365
1366 @property
1367 def component(self):
1368@@ -194,10 +274,7 @@ class SourcePackage(object):
1369 @property
1370 def dsc_name(self):
1371 "Return the source package dsc filename for the given package"
1372- version = self.version.upstream_version
1373- if self.version.debian_version:
1374- version += '-' + self.version.debian_version
1375- return '%s_%s.dsc' % (self.source, version)
1376+ return '%s_%s.dsc' % (self.source, self.version.strip_epoch())
1377
1378 @property
1379 def dsc_pathname(self):
1380@@ -211,6 +288,9 @@ class SourcePackage(object):
1381 self.pull_dsc()
1382 return self._dsc
1383
1384+ def getArchive(self):
1385+ return Distribution(self.distribution).getArchive()
1386+
1387 def _mirror_url(self, mirror, filename):
1388 "Build a source package URL on a mirror"
1389 if self.source.startswith('lib'):
1390@@ -236,6 +316,22 @@ class SourcePackage(object):
1391 yield self._mirror_url(mirror, name)
1392 yield self._lp_url(name)
1393
1394+ def _binary_urls(self, name, default_url):
1395+ "Generator of URLs for name"
1396+ for mirror in self.mirrors:
1397+ yield self._mirror_url(mirror, name)
1398+ for mirror in self.masters:
1399+ if mirror not in self.mirrors:
1400+ yield self._mirror_url(mirror, name)
1401+ yield self._lp_url(name)
1402+ yield default_url
1403+
1404+ def _binary_files(self, name, arch):
1405+ for bpph in self.lp_spph.getBinaries(arch):
1406+ if name and not re.match(name, bpph.binary_package_name):
1407+ continue
1408+ yield (bpph.getFileName(), bpph.getUrl(), 0)
1409+
1410 def pull_dsc(self):
1411 "Retrieve dscfile and parse"
1412 if self._dsc_source:
1413@@ -271,7 +367,7 @@ class SourcePackage(object):
1414 "Check that the dsc matches what we are expecting"
1415 assert self._dsc is not None
1416 self.source = self.dsc['Source']
1417- self.version = debian.debian_support.Version(self.dsc['Version'])
1418+ self._version = Version(self.dsc['Version'])
1419
1420 valid = False
1421 message = None
1422@@ -310,28 +406,41 @@ class SourcePackage(object):
1423 with open(self.dsc_pathname, 'wb') as f:
1424 f.write(self.dsc.raw_text)
1425
1426- def _download_file(self, url, filename):
1427+ def _download_file(self, url, filename, verify=True, size=0):
1428 "Download url to filename in workdir."
1429 pathname = os.path.join(self.workdir, filename)
1430- if self.dsc.verify_file(pathname):
1431- Logger.debug('Using existing %s', filename)
1432- return True
1433- size = [entry['size'] for entry in self.dsc['Files']
1434- if entry['name'] == filename]
1435- assert len(size) == 1
1436- size = int(size[0])
1437+ if verify:
1438+ if self.dsc.verify_file(pathname):
1439+ Logger.debug('Using existing %s', filename)
1440+ return True
1441+ size = [entry['size'] for entry in self.dsc['Files']
1442+ if entry['name'] == filename]
1443+ assert len(size) == 1
1444+ size = int(size[0])
1445+
1446 parsed = urlparse(url)
1447- if not self.quiet:
1448- Logger.normal('Downloading %s from %s (%0.3f MiB)',
1449- filename, parsed.hostname, size / 1024.0 / 1024)
1450
1451 if parsed.scheme == 'file':
1452 in_ = open(parsed.path, 'rb')
1453+ if not size:
1454+ size = int(os.stat(parsed.path).st_size)
1455 else:
1456 try:
1457 in_ = self.url_opener.open(url)
1458- except URLError:
1459+ Logger.debug("Using URL '%s'", url)
1460+ except URLError as e:
1461+ Logger.debug("URLError opening '%s': %s", url, e)
1462 return False
1463+ if not size:
1464+ contentlen = in_.info().get('Content-Length')
1465+ if not contentlen:
1466+ Logger.error("Invalid response, no Content-Length")
1467+ return False
1468+ size = int(contentlen)
1469+
1470+ if not self.quiet:
1471+ Logger.normal('Downloading %s from %s (%0.3f MiB)',
1472+ filename, parsed.hostname, size / 1024.0 / 1024)
1473
1474 downloaded = 0
1475 bar_width = 60
1476@@ -354,7 +463,7 @@ class SourcePackage(object):
1477 if not self.quiet:
1478 Logger.stdout.write(' ' * (bar_width + 7) + '\r')
1479 Logger.stdout.flush()
1480- if not self.dsc.verify_file(pathname):
1481+ if verify and not self.dsc.verify_file(pathname):
1482 Logger.error('Checksum for %s does not match.', filename)
1483 return False
1484 return True
1485@@ -375,6 +484,36 @@ class SourcePackage(object):
1486 else:
1487 raise DownloadError('File %s could not be found' % name)
1488
1489+ def pull_binaries(self, name=None, arch=None):
1490+ """Pull binary debs into workdir.
1491+ If name is specified, only binary packages matching the regex are included.
1492+ If arch is specified, only binary packages for that arch are included,
1493+ otherwise only binary packages for the current arch are included.
1494+ Returns the number of files downloaded.
1495+ """
1496+ total = 0
1497+
1498+ if not arch:
1499+ arch = get_system_arch()
1500+ Logger.debug("Using system arch %s", arch)
1501+
1502+ for (fname, furl, fsize) in self._binary_files(name, arch):
1503+ found = False
1504+ for url in self._binary_urls(fname, furl):
1505+ try:
1506+ if self._download_file(url, fname, False, fsize):
1507+ found = True
1508+ break
1509+ except HTTPError as e:
1510+ Logger.normal('HTTP Error %i: %s', e.code, str(e))
1511+ except URLError as e:
1512+ Logger.normal('URL Error: %s', e.reason)
1513+ if found:
1514+ total += 1
1515+ else:
1516+ Logger.normal("Could not download from any location: %s", fname)
1517+ return total
1518+
1519 def verify(self):
1520 """Verify that the source package in workdir matches the dsc.
1521 Return boolean
1522@@ -432,33 +571,28 @@ class DebianSourcePackage(SourcePackage):
1523 super(DebianSourcePackage, self).__init__(*args, **kwargs)
1524 self.masters.append(UDTConfig.defaults['DEBSEC_MIRROR'])
1525 # Cached values:
1526- self._snapshot_list = None
1527+ self._snapshot_package = None
1528+ self._snapshot_files = None
1529+ # Don't bother searching in LP for debian binaries, they aren't there
1530+ self.try_binary = False
1531
1532 # Overridden methods:
1533 @property
1534 def lp_spph(self):
1535 "Return the LP Source Package Publishing History entry"
1536- if not self._spph:
1537- try:
1538- return super(DebianSourcePackage, self).lp_spph
1539- except IndexError:
1540- pass
1541-
1542- Logger.normal('Using rmadison for component determination')
1543- comp = 'main'
1544- for record in rmadison(self.distribution, self.source):
1545- if record.get('source') != self.source:
1546- continue
1547- comp = record['component']
1548- if record['version'] == self.version.full_version:
1549- self._spph = FakeSPPH(record['source'], record['version'],
1550- comp, 'debian')
1551- return self._spph
1552+ if self._spph:
1553+ return self._spph
1554
1555- Logger.normal('Guessing component from most recent upload')
1556- self._spph = FakeSPPH(self.source, self.version.full_version, comp,
1557- 'debian')
1558- return self._spph
1559+ try:
1560+ self._spph = DebianSPPH(super(DebianSourcePackage, self).lp_spph())
1561+ return self._spph
1562+ except PackageNotFoundException:
1563+ pass
1564+ except SeriesNotFoundException:
1565+ pass
1566+
1567+ Logger.normal('Package not found in Launchpad, using snapshot')
1568+ return self.snapshot_package.getSPPH()
1569
1570 def _source_urls(self, name):
1571 "Generator of sources for name"
1572@@ -468,8 +602,13 @@ class DebianSourcePackage(SourcePackage):
1573 yield next(wrapped_iterator)
1574 except StopIteration:
1575 break
1576- if self.snapshot_list:
1577- yield self._snapshot_url(name)
1578+ yield self.snapshot_files[name]
1579+
1580+ def _binary_files(self, name, arch):
1581+ for f in self.snapshot_package.getBinaryFiles(arch):
1582+ if name and not re.match(name, f.package_name):
1583+ continue
1584+ yield (f.name, f.getUrl(), f.size)
1585
1586 def pull_dsc(self):
1587 "Retrieve dscfile and parse"
1588@@ -493,41 +632,39 @@ class DebianSourcePackage(SourcePackage):
1589
1590 # Local methods:
1591 @property
1592- def snapshot_list(self):
1593- "Return a filename -> hash dictionary from snapshot.debian.org"
1594- if self._snapshot_list is None:
1595- try:
1596- import json
1597- except ImportError:
1598- import simplejson as json
1599- except ImportError:
1600- Logger.error("Please install python-simplejson.")
1601- raise DownloadError("Unable to dowload from "
1602- "snapshot.debian.org without "
1603- "python-simplejson")
1604-
1605- try:
1606- data = self.url_opener.open(
1607- 'http://snapshot.debian.org/mr/package/%s/%s/srcfiles?fileinfo=1' %
1608- (self.source, self.version.full_version))
1609- reader = codecs.getreader('utf-8')
1610- srcfiles = json.load(reader(data))
1611-
1612- except HTTPError:
1613- Logger.error('Version %s of %s not found on '
1614- 'snapshot.debian.org',
1615- self.version.full_version, self.source)
1616- self._snapshot_list = False
1617- return False
1618- self._snapshot_list = dict((info[0]['name'], hash_)
1619- for hash_, info
1620- in srcfiles['fileinfo'].items())
1621- return self._snapshot_list
1622+ def snapshot_package(self):
1623+ if not self._snapshot_package:
1624+ if self._version or self._spph:
1625+ # as .version uses .lpph, and our .lpph might use us,
1626+ # only use .version if _version or _spph are set
1627+ version = self.version.full_version
1628+ srcpkgs = Snapshot.getSourcePackages(self.source, version=version)
1629+ if not srcpkgs:
1630+ msg = "Package {} {} not found".format(self.source, version)
1631+ raise PackageNotFoundException(msg)
1632+ self._snapshot_package = srcpkgs[0]
1633+ else:
1634+ # we have neither version nor spph, so look up our version using madison
1635+ Logger.normal('Using madison to find latest version number')
1636+ series = self._series
1637+ if self._pocket:
1638+ series += "-" + self._pocket
1639+ params = {'series': series} if series else {}
1640+ srcpkg = Madison(self.distribution).getSourcePackage(self.source, **params)
1641+ if not srcpkg:
1642+ raise PackageNotFoundException("Package {} not found".format(self.source))
1643+ if self.source != srcpkg.name:
1644+ self.binary = self.source
1645+ self.source = srcpkg.name
1646+ self._snapshot_package = srcpkg
1647+ return self._snapshot_package
1648
1649- def _snapshot_url(self, name):
1650- "Return the snapshot.debian.org URL for name"
1651- return os.path.join('http://snapshot.debian.org/file',
1652- self.snapshot_list[name])
1653+ @property
1654+ def snapshot_files(self):
1655+ if not self._snapshot_files:
1656+ files = self.snapshot_package.getFiles()
1657+ self._snapshot_files = {f.name: f.getUrl() for f in files}
1658+ return self._snapshot_files
1659
1660
1661 class UbuntuSourcePackage(SourcePackage):
1662@@ -537,37 +674,397 @@ class UbuntuSourcePackage(SourcePackage):
1663
1664 class UbuntuCloudArchiveSourcePackage(UbuntuSourcePackage):
1665 "Download / unpack an Ubuntu Cloud Archive source package"
1666- def __init__(self, uca_release, *args, **kwargs):
1667+ _ppas = None
1668+ _ppa_names = None
1669+
1670+ def __init__(self, *args, **kwargs):
1671 super(UbuntuCloudArchiveSourcePackage, self).__init__(*args, **kwargs)
1672- self._uca_release = uca_release
1673+ self._use_series = False # UCA doesn't really use distro series
1674+ self._uca_release = self._series
1675+ self._series = None
1676 self.masters = ["http://ubuntu-cloud.archive.canonical.com/ubuntu/"]
1677
1678+ @classmethod
1679+ def getArchives(self):
1680+ if not self._ppas:
1681+ ppas = filter(lambda p: p.name.endswith('-staging'),
1682+ PersonTeam.fetch('ubuntu-cloud-archive').getPPAs())
1683+ self._ppas = sorted(ppas, key=lambda p: p.name, reverse=True)
1684+ return self._ppas
1685+
1686+ @classmethod
1687+ def getReleaseNames(self):
1688+ if not self._ppa_names:
1689+ self._ppa_names = [p.name.split('-', 1)[0] for p in self.getArchives()]
1690+ return self._ppa_names
1691+
1692+ @classmethod
1693+ def getDevelopmentRelease(self):
1694+ return self.getReleaseNames()[0]
1695+
1696+ @property
1697+ def uca_release(self):
1698+ if not self._uca_release:
1699+ self._uca_release = self.getDevelopmentRelease()
1700+ Logger.normal('Using UCA release %s', self._uca_release)
1701+ return self._uca_release
1702+
1703+ def getArchive(self):
1704+ ppas = {p.name: p for p in self.getArchives()}
1705+ release = '{}-staging'.format(self.uca_release)
1706+ if release in ppas:
1707+ Logger.debug('UCA release {} at {}'.format(self.uca_release,
1708+ ppas[release]()))
1709+ return ppas[release]
1710+ raise SeriesNotFoundException('UCA release {} not found.'.format(self.uca_release))
1711+
1712 def _lp_url(self, filename):
1713 "Build a source package URL on Launchpad"
1714 return os.path.join('https://launchpad.net', "~ubuntu-cloud-archive",
1715- '+archive', ("%s-staging" % self._uca_release),
1716+ '+archive', ("%s-staging" % self.uca_release),
1717 '+files', filename)
1718
1719
1720-class FakeSPPH(object):
1721- """Provide the same interface as
1722- ubuntutools.lpapicache.SourcePackagePublishingHistory
1723+class DebianSPPH(SourcePackagePublishingHistory):
1724+ """SPPH with getBinaries() overridden,
1725+ as LP doesn't have Debian binaries
1726 """
1727- def __init__(self, name, version, component, distribution):
1728- self.name = name
1729- self.version = version
1730+ resource_type = 'source_package_publishing_history'
1731+
1732+ def getBinaries(self, arch=None):
1733+ if not self._binaries:
1734+ Logger.normal('Using Snapshot to find binary packages')
1735+ srcpkg = Snapshot.getSourcePackages(self.getPackageName(),
1736+ version=self.getVersion(),
1737+ component=self.getComponent())[0]
1738+ self._binaries = [b.getBPPH() for b in srcpkg.getBinaryFiles()]
1739+ return super(DebianSPPH, self).getBinaries(arch)
1740+
1741+
1742+class _WebJSON(object):
1743+ def __init__(self):
1744+ # uses default proxies from the environment
1745+ self.url_opener = build_opener(ProxyHandler())
1746+
1747+ def getHostUrl(self):
1748+ raise Exception("Not implemented")
1749+
1750+ def load(self, path=''):
1751+ url = self.getHostUrl() + path
1752+ Logger.debug("Loading %s", url)
1753+ data = self.url_opener.open(url)
1754+ reader = codecs.getreader('utf-8')
1755+ return json.load(reader(data))
1756+
1757+
1758+# DAKweb madison API
1759+# https://github.com/Debian/dak/blob/master/dakweb/queries/madison.py
1760+class Madison(_WebJSON):
1761+ urls = {
1762+ 'debian': 'https://api.ftp-master.debian.org/madison',
1763+ # This currently will NOT work with ubuntu; it doesn't support f=json
1764+ 'ubuntu': 'http://people.canonical.com/~ubuntu-archive/madison.cgi',
1765+ }
1766+
1767+ def __init__(self, distro='debian'):
1768+ super(Madison, self).__init__()
1769+ self._distro = distro
1770+
1771+ def getHostUrl(self):
1772+ return self.urls[self._distro]
1773+
1774+ def getSourcePackage(self, name, series='unstable'):
1775+ url = "?f=json&package={name}&s={series}".format(name=name, series=series)
1776+ try:
1777+ result = self.load(url)
1778+ except HTTPError:
1779+ result = None
1780+ if not result:
1781+ msg = "Package {} not found in '{}'".format(name, series)
1782+ raise PackageNotFoundException(msg)
1783+ versions = result[0][name].values()[0]
1784+ latest = versions[sorted(versions.keys(), reverse=True)[0]]
1785+ return Snapshot.getSourcePackages(name=latest['source'],
1786+ version=latest['source_version'],
1787+ component=latest['component'])[0]
1788+
1789+
1790+# Snapshot API
1791+# https://anonscm.debian.org/cgit/mirror/snapshot.debian.org.git/plain/API
1792+class _Snapshot(_WebJSON):
1793+ def getHostUrl(self):
1794+ return "http://snapshot.debian.org"
1795+
1796+ def _get_package(self, name, url, pkginit, version=None, series=None):
1797+ try:
1798+ results = self.load("/mr/{}/{}/".format(url, name))['result']
1799+ except HTTPError:
1800+ raise PackageNotFoundException("Package {} not found.".format(name))
1801+
1802+ results = [pkginit(r) for r in results]
1803+ if version:
1804+ results = [r for r in results if version == r.version]
1805+ if series:
1806+ if not version:
1807+ Logger.warn("Warning: searching Snapshot pkgs by series is VERY slow!")
1808+ results = [r for r in results if series == r.series]
1809+ if not results:
1810+ msg = "Package {} ".format(name)
1811+ if version:
1812+ msg += "version {} ".format(version)
1813+ if series:
1814+ msg += "in series {} ".format(series)
1815+ msg += "not found."
1816+ raise PackageNotFoundException(msg)
1817+ return sorted(results, key=lambda r: r.version, reverse=True)
1818+
1819+ def getSourcePackages(self, name, version=None, series=None, component=None):
1820+ return self._get_package(name, "package",
1821+ lambda obj: SnapshotSourcePackage(obj, name, component),
1822+ version=version, series=series)
1823+
1824+ def getBinaryPackages(self, name, version=None, series=None, component=None):
1825+ return self._get_package(name, "binary", SnapshotBinaryPackage,
1826+ lambda obj: SnapshotBinaryPackage(obj, component),
1827+ version=version, series=series)
1828+
1829+
1830+Snapshot = _Snapshot()
1831+
1832+
1833+class SnapshotPackage(object):
1834+ def __init__(self, obj, component):
1835+ self._obj = obj
1836+ self._files = None
1837+ self._series = None
1838 self.component = component
1839- self.distribution = distribution
1840- self._changelog = None
1841+
1842+ @property
1843+ def version(self):
1844+ return self._obj['version']
1845+
1846+ @property
1847+ def series(self):
1848+ if not self._series:
1849+ files = self.getFiles()
1850+ if not files:
1851+ Logger.error("Package {} {} has no files."
1852+ .format(self.name, self.version))
1853+ self._series = 'unknown'
1854+ else:
1855+ self._series = files[0].archive_name
1856+ if filter(lambda f: f.archive_name != self._series, files):
1857+ Logger.debug("Package {} {} has files in multiple series."
1858+ .format(self.name, self.version))
1859+ return self._series
1860+
1861+
1862+class SnapshotSourcePackage(SnapshotPackage):
1863+ def __init__(self, obj, name, component):
1864+ # obj required fields: 'version'
1865+ super(SnapshotSourcePackage, self).__init__(obj, component)
1866+ self.name = name
1867+ self._binary_files = None
1868+ self._spph = None
1869+
1870+ def getSPPH(self):
1871+ if not self._spph:
1872+ self._spph = SnapshotSPPH(self)
1873+ return self._spph
1874+
1875+ def getAllFiles(self):
1876+ return self.getFiles() + self.getBinaryFiles()
1877+
1878+ def getBinaryFiles(self, arch=None):
1879+ if not self._binary_files:
1880+ url = "/mr/package/{}/{}/allfiles".format(self.name, self.version)
1881+ response = Snapshot.load("{}?fileinfo=1".format(url))
1882+ info = response['fileinfo']
1883+ files = [SnapshotBinaryFile(b['name'], b['version'], self.component,
1884+ info[r['hash']][0], r['hash'],
1885+ r['architecture'], self.name)
1886+ for b in response['result']['binaries'] for r in b['files']]
1887+ self._binary_files = files
1888+ if not arch:
1889+ return list(self._binary_files)
1890+ return filter(lambda f: f.isArch(arch), self._binary_files)
1891+
1892+ def getFiles(self):
1893+ if not self._files:
1894+ url = "/mr/package/{}/{}/srcfiles".format(self.name, self.version)
1895+ response = Snapshot.load("{}?fileinfo=1".format(url))
1896+ info = response['fileinfo']
1897+ self._files = [SnapshotSourceFile(self.name, self.version, self.component,
1898+ info[r['hash']][0],
1899+ r['hash'])
1900+ for r in response['result']]
1901+ return list(self._files)
1902+
1903+
1904+class SnapshotBinaryPackage(SnapshotPackage):
1905+ def __init__(self, obj, component):
1906+ # obj required fields: 'version', 'binary_version', 'name', 'source'
1907+ super(SnapshotBinaryPackage, self).__init__(obj, component)
1908+
1909+ @property
1910+ def name(self):
1911+ return self._obj['name']
1912+
1913+ @property
1914+ def binary_version(self):
1915+ return self._obj['binary_version']
1916+
1917+ @property
1918+ def source(self):
1919+ return self._obj['source']
1920+
1921+ def getBPPH(self, arch):
1922+ f = self.getFiles(arch)
1923+ if not f:
1924+ return None
1925+ # Can only be 1 binary file for this pkg name/version/arch
1926+ return f[0].getBPPH()
1927+
1928+ def getFiles(self, arch=None):
1929+ if not self._files:
1930+ url = "/mr/binary/{}/{}/binfiles".format(self.name, self.version)
1931+ response = Snapshot.load("{}?fileinfo=1".format(url))
1932+ info = response['fileinfo']
1933+ self._files = [SnapshotBinaryFile(self.name, self.version, self.component,
1934+ info[r['hash']][0], r['hash'],
1935+ r['architecture'], self.source)
1936+ for r in response['result']]
1937+ if not arch:
1938+ return list(self._files)
1939+ return filter(lambda f: f.isArch(arch), self._files)
1940+
1941+
1942+class SnapshotFile(object):
1943+ def __init__(self, pkg_name, pkg_version, pkg_component, obj, h):
1944+ self.package_name = pkg_name
1945+ self.package_version = pkg_version
1946+ self.package_component = pkg_component
1947+ self._obj = obj
1948+ self._hash = h
1949+
1950+ @property
1951+ def getType(self):
1952+ return None
1953+
1954+ @property
1955+ def archive_name(self):
1956+ return self._obj['archive_name']
1957+
1958+ @property
1959+ def name(self):
1960+ return self._obj['name']
1961+
1962+ @property
1963+ def path(self):
1964+ return self._obj['path']
1965+
1966+ @property
1967+ def size(self):
1968+ return self._obj['size']
1969+
1970+ @property
1971+ def date(self):
1972+ if 'run' in self._obj:
1973+ return self._obj['run']
1974+ elif 'first_seen' in self._obj:
1975+ return self._obj['first_seen']
1976+ else:
1977+ Logger.error('File {} has no date information', self.name)
1978+ return 'unknown'
1979+
1980+ def getHash(self):
1981+ return self._hash
1982+
1983+ def getUrl(self):
1984+ return "{}/file/{}".format(Snapshot.getHostUrl(), self.getHash())
1985+
1986+ def __repr__(self):
1987+ return "{}/{} {} bytes {}".format(self.path, self.name, self.size, self.date)
1988+
1989+
1990+class SnapshotSourceFile(SnapshotFile):
1991+ def __init__(self, name, version, component, obj, h):
1992+ super(SnapshotSourceFile, self).__init__(name, version, component, obj, h)
1993+
1994+ def getType(self):
1995+ return 'source'
1996+
1997+
1998+class SnapshotBinaryFile(SnapshotFile):
1999+ def __init__(self, name, version, component, obj, h, arch, source):
2000+ super(SnapshotBinaryFile, self).__init__(name, version, component, obj, h)
2001+ self.source = source
2002+ self.arch = arch
2003+ self._bpph = None
2004+
2005+ def isArch(self, arch):
2006+ if not arch:
2007+ return True
2008+ if self.arch == 'all':
2009+ return True
2010+ return arch == self.arch
2011+
2012+ def getType(self):
2013+ return 'binary'
2014+
2015+ def getBPPH(self):
2016+ if not self._bpph:
2017+ self._bpph = SnapshotBPPH(self)
2018+ return self._bpph
2019+
2020+
2021+class SnapshotSPPH(object):
2022+ """Provide the same interface as SourcePackagePublishingHistory"""
2023+ def __init__(self, snapshot_pkg):
2024+ self._pkg = snapshot_pkg
2025+
2026+ # LP API defined fields
2027+
2028+ @property
2029+ def component_name(self):
2030+ return self.getComponent()
2031+
2032+ @property
2033+ def display_name(self):
2034+ return ("{name} {version} in {seriesandpocket}"
2035+ .format(name=self.getPackageName(),
2036+ version=self.getVersion(),
2037+ seriesandpocket=self.getSeriesAndPocket()))
2038+
2039+ @property
2040+ def pocket(self):
2041+ try:
2042+ (r, p) = split_release_pocket(self.getSeriesAndPocket(), default=None)
2043+ except PocketDoesNotExistError:
2044+ p = None
2045+ return p if p else 'Release'
2046+
2047+ @property
2048+ def source_package_name(self):
2049+ return self.getPackageName()
2050+
2051+ @property
2052+ def source_package_version(self):
2053+ return self.getVersion()
2054+
2055+ # SPPH functions
2056
2057 def getPackageName(self):
2058- return self.name
2059+ return self._pkg.name
2060
2061 def getVersion(self):
2062- return self.version
2063+ return self._pkg.version
2064
2065 def getComponent(self):
2066- return self.component
2067+ return self._pkg.component
2068+
2069+ def getSeriesAndPocket(self):
2070+ return self._pkg.series
2071
2072 def getChangelog(self, since_version=None):
2073 '''
2074@@ -575,27 +1072,22 @@ class FakeSPPH(object):
2075 May return None if the changelog isn't available
2076 '''
2077 if self._changelog is None:
2078- if self.name.startswith('lib'):
2079- subdir = 'lib%s' % self.name[3]
2080+ name = self.getPackageName()
2081+ if name.startswith('lib'):
2082+ subdir = 'lib%s' % name[3]
2083 else:
2084- subdir = self.name[0]
2085- # Strip epoch from version
2086- pkgversion = self.version.split(':', 1)[-1]
2087- extension = ''
2088- if self.distribution == 'debian':
2089- base = 'http://packages.debian.org/'
2090- extension = '.txt'
2091- elif self.distribution == 'ubuntu':
2092- base = 'http://changelogs.ubuntu.com/'
2093+ subdir = name[0]
2094+ pkgversion = Version(self.getVersion()).strip_epoch()
2095+ base = 'http://packages.debian.org/'
2096
2097 url = os.path.join(base, 'changelogs', 'pool',
2098- self.component, subdir, self.name,
2099- self.name + '_' + pkgversion,
2100- 'changelog' + extension)
2101+ self.getComponent(), subdir, name,
2102+ name + '_' + pkgversion,
2103+ 'changelog.txt')
2104 try:
2105 self._changelog = urlopen(url).read()
2106 except HTTPError as error:
2107- print(('%s: %s' % (url, error)), file=sys.stderr)
2108+ Logger.error('{}: {}'.format(url, error))
2109 return None
2110
2111 if since_version is None:
2112@@ -611,62 +1103,70 @@ class FakeSPPH(object):
2113 new_entries.append(unicode(block))
2114 return u''.join(new_entries)
2115
2116+ def getBinaries(self, arch=None):
2117+ return [b.getBPPH() for b in self._pkg.getBinaryFiles(arch)]
2118
2119-def rmadison(url, package, suite=None, arch=None):
2120- "Call rmadison and parse the result"
2121- cmd = ['rmadison', '-u', url]
2122- if suite:
2123- cmd += ['-s', suite]
2124- if arch:
2125- cmd += ['-a', arch]
2126- cmd.append(package)
2127- process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
2128- stderr=subprocess.PIPE, close_fds=True)
2129- output, error_output = process.communicate()
2130- if process.wait() != 0:
2131- if error_output:
2132- Logger.error('rmadison failed with: %s', error_output)
2133- else:
2134- Logger.error('rmadison failed')
2135- sys.exit(1)
2136-
2137- # rmadison uses some shorthand
2138- if suite:
2139- suite = suite.replace('-proposed-updates', '-p-u')
2140-
2141- # pylint bug: http://www.logilab.org/ticket/46273
2142- # pylint: disable=E1103
2143- for line in output.strip().splitlines():
2144- # pylint: enable=E1103
2145- pkg, ver, dist, archs = [x.strip() for x in line.split('|')]
2146- comp = 'main'
2147- if '/' in dist:
2148- dist, comp = dist.split('/')
2149- archs = set(x.strip() for x in archs.split(','))
2150-
2151- # rmadison returns some results outside the requested set.
2152- # It'll include backports, and when given an unknown suite,
2153- # it ignores that argument
2154- #
2155- # some versions (2.14.1ubuntu0.1) of rmadison return 'sid' when
2156- # asked about 'unstable'. Others return 'unstable'. Accept either.
2157- if (suite and dist != suite and not
2158- (suite == 'sid' and dist == 'unstable')):
2159- continue
2160-
2161- if 'source' in archs:
2162- yield {
2163- 'source': pkg,
2164- 'version': ver,
2165- 'suite': dist,
2166- 'component': comp,
2167- }
2168- archs.discard('source')
2169- if archs:
2170- yield {
2171- 'binary': pkg,
2172- 'version': ver,
2173- 'suite': dist,
2174- 'component': comp,
2175- 'architectures': archs,
2176- }
2177+
2178+class SnapshotBPPH(object):
2179+ """Provide the same interface as BinaryPackagePublishingHistory"""
2180+ def __init__(self, snapshot_binfile):
2181+ self._file = snapshot_binfile
2182+
2183+ # LP API defined fields
2184+ @property
2185+ def architecture_specific(self):
2186+ return self._file.arch != 'all'
2187+
2188+ @property
2189+ def binary_package_name(self):
2190+ return self.getPackageName()
2191+
2192+ @property
2193+ def binary_package_version(self):
2194+ return self.getVersion()
2195+
2196+ @property
2197+ def component_name(self):
2198+ return self.getComponent()
2199+
2200+ @property
2201+ def display_name(self):
2202+ return ("{name} {version} in {seriesandpocket}"
2203+ .format(name=self.getPackageName(),
2204+ version=self.getVersion(),
2205+ seriesandpocket=self._file.archive_name))
2206+
2207+ @property
2208+ def pocket(self):
2209+ try:
2210+ (r, p) = split_release_pocket(self._file.archive_name, default=None)
2211+ except PocketDoesNotExistError:
2212+ p = None
2213+ return p if p else 'Release'
2214+
2215+ # BPPH functions
2216+
2217+ @property
2218+ def arch(self):
2219+ return self._file.arch
2220+
2221+ def getSourcePackageName(self):
2222+ return self._file.source
2223+
2224+ def getPackageName(self):
2225+ return self._file.package_name
2226+
2227+ def getVersion(self):
2228+ return self._file.package_version
2229+
2230+ def getComponent(self):
2231+ return self._file.component
2232+
2233+ def getBuild(self):
2234+ return None
2235+
2236+ def getUrl(self):
2237+ return self._file.getUrl()
2238+
2239+ def getFileName(self):
2240+ return self._file.name
2241diff --git a/ubuntutools/config.py b/ubuntutools/config.py
2242index ba029fe..dfa53a3 100644
2243--- a/ubuntutools/config.py
2244+++ b/ubuntutools/config.py
2245@@ -37,10 +37,12 @@ class UDTConfig(object):
2246 'BUILDER': 'pbuilder',
2247 'DEBIAN_MIRROR': 'http://deb.debian.org/debian',
2248 'DEBSEC_MIRROR': 'http://security.debian.org',
2249+ 'DEBIAN_DDEBS_MIRROR': 'http://debug.mirrors.debian.org/debian-debug',
2250 'LPINSTANCE': 'production',
2251 'MIRROR_FALLBACK': True,
2252 'UBUNTU_MIRROR': 'http://archive.ubuntu.com/ubuntu',
2253 'UBUNTU_PORTS_MIRROR': 'http://ports.ubuntu.com',
2254+ 'UBUNTU_DDEBS_MIRROR': 'http://ddebs.ubuntu.com',
2255 'UPDATE_BUILDER': False,
2256 'WORKDIR': None,
2257 'KEYID': None,
2258diff --git a/ubuntutools/lp/libsupport.py b/ubuntutools/lp/libsupport.py
2259index 64f3cf4..73dbd4b 100644
2260--- a/ubuntutools/lp/libsupport.py
2261+++ b/ubuntutools/lp/libsupport.py
2262@@ -19,6 +19,8 @@
2263 #
2264
2265 # Modules.
2266+import platform
2267+
2268 try:
2269 from urllib.parse import urlsplit, urlencode, urlunsplit
2270 except ImportError:
2271@@ -53,3 +55,25 @@ def translate_web_api(url, launchpad):
2272 query = urlencode(query)
2273 url = urlunsplit((scheme, netloc, api_path + path.lstrip("/"), query, fragment))
2274 return url
2275+
2276+
2277+def get_system_arch():
2278+ # get the launchpad-named arch for the current system arch
2279+ arch = platform.machine()
2280+
2281+ # python's arch strings don't quite match launchpad's
2282+ # x86
2283+ if arch == "x86_64":
2284+ return "amd64"
2285+ if arch == "i686":
2286+ return "i386"
2287+
2288+ # PPC
2289+ if arch == "ppc64le":
2290+ return "ppc64el"
2291+
2292+ # ARM
2293+ if arch == "aarch64":
2294+ return "arm64"
2295+
2296+ return arch
2297diff --git a/ubuntutools/lp/lpapicache.py b/ubuntutools/lp/lpapicache.py
2298index 601bea8..ccf5d7e 100644
2299--- a/ubuntutools/lp/lpapicache.py
2300+++ b/ubuntutools/lp/lpapicache.py
2301@@ -30,13 +30,15 @@ from __future__ import print_function
2302 import collections
2303 import sys
2304
2305-from debian.changelog import Changelog, Version
2306+from debian.changelog import Changelog
2307 from httplib2 import Http, HttpLib2Error
2308 from launchpadlib.launchpad import Launchpad as LP
2309 from launchpadlib.errors import HTTPError
2310 from lazr.restfulclient.resource import Entry
2311
2312+from ubuntutools.version import Version
2313 from ubuntutools.lp import (service, api_version)
2314+from ubuntutools.lp.libsupport import get_system_arch
2315 from ubuntutools.lp.udtexceptions import (AlreadyLoggedInError,
2316 ArchiveNotFoundException,
2317 ArchSeriesNotFoundException,
2318@@ -73,6 +75,7 @@ __all__ = [
2319 'Distribution',
2320 'DistributionSourcePackage',
2321 'DistroSeries',
2322+ 'DistroArchSeries',
2323 'Launchpad',
2324 'PersonTeam',
2325 'SourcePackagePublishingHistory',
2326@@ -296,6 +299,12 @@ class DistroArchSeries(BaseWrapper):
2327 '''
2328 resource_type = 'distro_arch_series'
2329
2330+ def getSeries(self):
2331+ '''
2332+ Get DistroSeries for this.
2333+ '''
2334+ return DistroSeries(self._lpobject.distroseries_link)
2335+
2336
2337 class DistroSeries(BaseWrapper):
2338 '''
2339@@ -307,12 +316,16 @@ class DistroSeries(BaseWrapper):
2340 if "_architectures" not in self.__dict__:
2341 self._architectures = dict()
2342
2343- def getArchSeries(self, archtag):
2344+ def getArchSeries(self, archtag=None):
2345 '''
2346 Returns a DistroArchSeries object for an architecture passed by name
2347 (e.g. 'amd64').
2348+ If arch is not specified, get the DistroArchSeries for the system arch.
2349+ The special archtag 'all' will get the system arch.
2350 If the architecture is not found: raise ArchSeriesNotFoundException.
2351 '''
2352+ if not archtag or archtag == 'all':
2353+ archtag = get_system_arch()
2354 if archtag not in self._architectures:
2355 try:
2356 architecture = DistroArchSeries(
2357@@ -338,16 +351,22 @@ class Archive(BaseWrapper):
2358 self._pkgset_uploaders = {}
2359 self._component_uploaders = {}
2360
2361- def getSourcePackage(self, name, series=None, pocket=None):
2362+ def getSourcePackage(self, name, series=None, pocket=None, version=None,
2363+ status=None):
2364 '''
2365 Returns a SourcePackagePublishingHistory object for the most
2366 recent source package in the distribution 'dist', series and
2367 pocket.
2368
2369 series defaults to the current development series if not specified.
2370+ series must be either a series name string, or DistroSeries object.
2371+
2372+ pocket may be a string or a list. If a list, the highest version
2373+ will be returned. It defaults to all pockets except backports.
2374
2375- pocket may be a list, if so, the highest version will be returned.
2376- It defaults to all pockets except backports.
2377+ version may be specified to get only the exact version requested.
2378+
2379+ status is optional, to restrict search to a given status only.
2380
2381 If the requested source package doesn't exist a
2382 PackageNotFoundException is raised.
2383@@ -355,33 +374,48 @@ class Archive(BaseWrapper):
2384 return self._getPublishedItem(name, series, pocket, cache=self._srcpkgs,
2385 function='getPublishedSources',
2386 name_key='source_name',
2387- wrapper=SourcePackagePublishingHistory)
2388+ wrapper=SourcePackagePublishingHistory,
2389+ version=version,
2390+ status=status,
2391+ binary=False)
2392
2393- def getBinaryPackage(self, name, archtag=None, series=None, pocket=None):
2394+ def getBinaryPackage(self, name, archtag=None, series=None, pocket=None,
2395+ version=None, status=None):
2396 '''
2397 Returns a BinaryPackagePublishingHistory object for the most
2398 recent source package in the distribution 'dist', architecture
2399 'archtag', series and pocket.
2400
2401 series defaults to the current development series if not specified.
2402+ series must be either a series name string, or DistroArchSeries object.
2403+
2404+ pocket may be a string or a list. If a list, the highest version
2405+ will be returned. It defaults to all pockets except backports.
2406
2407- pocket may be a list, if so, the highest version will be returned.
2408- It defaults to all pockets except backports.
2409+ version may be specified to get only the exact version requested.
2410+
2411+ status is optional, to restrict search to a given status only.
2412
2413 If the requested binary package doesn't exist a
2414 PackageNotFoundException is raised.
2415 '''
2416- if archtag is None:
2417- archtag = []
2418 return self._getPublishedItem(name, series, pocket, archtag=archtag,
2419 cache=self._binpkgs,
2420 function='getPublishedBinaries',
2421 name_key='binary_name',
2422- wrapper=BinaryPackagePublishingHistory)
2423+ wrapper=BinaryPackagePublishingHistory,
2424+ version=version,
2425+ status=status,
2426+ binary=True)
2427
2428 def _getPublishedItem(self, name, series, pocket, cache,
2429- function, name_key, wrapper, archtag=None):
2430- '''Common code between getSourcePackage and getBinaryPackage
2431+ function, name_key, wrapper, archtag=None,
2432+ version=None, status=None,
2433+ binary=False):
2434+ '''
2435+ Common code between getSourcePackage and getBinaryPackage.
2436+
2437+ Don't use this directly.
2438 '''
2439 if pocket is None:
2440 pockets = frozenset(('Proposed', 'Updates', 'Security', 'Release'))
2441@@ -396,34 +430,44 @@ class Archive(BaseWrapper):
2442 pocket)
2443
2444 dist = Distribution(self.distribution_link)
2445- # Check if series is already a DistroSeries object or not
2446- if not isinstance(series, DistroSeries):
2447- if series:
2448- series = dist.getSeries(series)
2449- else:
2450- series = dist.getDevelopmentSeries()
2451
2452- # getPublishedSources requires a distro_series, while
2453- # getPublishedBinaries requires a distro_arch_series.
2454- # If archtag is not None, I'll assume it's getPublishedBinaries.
2455- if archtag is not None and archtag != []:
2456- if not isinstance(archtag, DistroArchSeries):
2457+ arch_series = None
2458+
2459+ # please don't pass DistroArchSeries as archtag!
2460+ # but, the code was like that before so keep
2461+ # backwards compatibility.
2462+ if isinstance(archtag, DistroArchSeries):
2463+ series = archtag
2464+ archtag = None
2465+
2466+ if isinstance(series, DistroArchSeries):
2467+ arch_series = series
2468+ series = series.getSeries()
2469+ elif isinstance(series, DistroSeries):
2470+ pass
2471+ elif series:
2472+ series = dist.getSeries(series)
2473+ else:
2474+ series = dist.getDevelopmentSeries()
2475+
2476+ if binary:
2477+ if arch_series is None:
2478 arch_series = series.getArchSeries(archtag=archtag)
2479- else:
2480- arch_series = archtag
2481+ if archtag is None:
2482+ archtag = arch_series.architecture_tag
2483
2484- if archtag is not None and archtag != []:
2485- index = (name, series.name, archtag, pockets)
2486- else:
2487- index = (name, series.name, pockets)
2488+ index = (name, series.name, archtag, pockets, status, version)
2489
2490 if index not in cache:
2491 params = {
2492 name_key: name,
2493- 'status': 'Published',
2494 'exact_match': True,
2495 }
2496- if archtag is not None and archtag != []:
2497+
2498+ if status:
2499+ params['status'] = status
2500+
2501+ if binary:
2502 params['distro_arch_series'] = arch_series()
2503 else:
2504 params['distro_series'] = series()
2505@@ -431,6 +475,9 @@ class Archive(BaseWrapper):
2506 if len(pockets) == 1:
2507 params['pocket'] = list(pockets)[0]
2508
2509+ if version:
2510+ params['version'] = version
2511+
2512 records = getattr(self, function)(**params)
2513
2514 latest = None
2515@@ -450,7 +497,7 @@ class Archive(BaseWrapper):
2516 package_type = "package"
2517 msg = ("The %s '%s' does not exist in the %s %s archive" %
2518 (package_type, name, dist.display_name, self.name))
2519- if archtag is not None and archtag != []:
2520+ if binary:
2521 msg += " for architecture %s" % archtag
2522 pockets = [series.name if pocket == 'Release'
2523 else '%s-%s' % (series.name, pocket.lower())
2524@@ -532,13 +579,23 @@ class SourcePackagePublishingHistory(BaseWrapper):
2525 resource_type = 'source_package_publishing_history'
2526
2527 def __init__(self, *args):
2528+ self._archive = None
2529 self._changelog = None
2530 self._binaries = None
2531+ self._distro_series = None
2532 # Don't share _builds between different
2533 # SourcePackagePublishingHistory objects
2534 if '_builds' not in self.__dict__:
2535 self._builds = dict()
2536
2537+ def getDistroSeries(self):
2538+ '''
2539+ Return the DistroSeries.
2540+ '''
2541+ if not self._distro_series:
2542+ self._distro_series = DistroSeries(self._lpobject.distro_series_link)
2543+ return self._distro_series
2544+
2545 def getPackageName(self):
2546 '''
2547 Returns the source package name.
2548@@ -557,16 +614,33 @@ class SourcePackagePublishingHistory(BaseWrapper):
2549 '''
2550 return self._lpobject.component_name
2551
2552+ def getSeriesName(self):
2553+ '''
2554+ Returns the series
2555+
2556+ Named getSeriesName() to avoid confusion with
2557+ getDistroSeries()
2558+ '''
2559+ return self.getDistroSeries().name
2560+
2561 def getSeriesAndPocket(self):
2562 '''
2563 Returns a human-readable release-pocket
2564 '''
2565- series = DistroSeries(self._lpobject.distro_series_link)
2566- release = series.name
2567- if self._lpobject.pocket != 'Release':
2568- release += '-' + self._lpobject.pocket.lower()
2569+ release = self.getSeriesName()
2570+ if self.pocket != 'Release':
2571+ release += '-' + self.pocket.lower()
2572 return release
2573
2574+ def getArchive(self):
2575+ '''
2576+ Get this SPPH's archive.
2577+ '''
2578+ if not self._archive:
2579+ self._archive = Archive(self._lpobject.archive_link)
2580+
2581+ return self._archive
2582+
2583 def getChangelog(self, since_version=None):
2584 '''
2585 Return the changelog, optionally since a particular version
2586@@ -603,15 +677,56 @@ class SourcePackagePublishingHistory(BaseWrapper):
2587 new_entries.append(unicode(block))
2588 return u''.join(new_entries)
2589
2590- def getBinaries(self):
2591+ def getBinaries(self, arch=None):
2592 '''
2593- Returns the resulting BinaryPackagePublishingHistorys
2594+ Returns the resulting BinaryPackagePublishingHistorys.
2595+ If arch is specified, only returns BPPH for that arch.
2596 '''
2597 if self._binaries is None:
2598- self._binaries = [BinaryPackagePublishingHistory(bpph)
2599- for bpph in
2600- self._lpobject.getPublishedBinaries()]
2601- return self._binaries
2602+ if self.status != "Pending" and self.status != "Published":
2603+ # great, this is an older version, so we can't use
2604+ # SPPH.getPublishedBinaries(). We have to do this the
2605+ # much more painful way, using SPPH.binaryFileUrls() to
2606+ # get the filenames and then parsing each one, and calling
2607+ # getPublishedBinaries() on the archive itself to get the
2608+ # BPPH url.
2609+ print("Older package version...this will take a while, sorry")
2610+ bpphs = []
2611+ archive = self.getArchive()
2612+ # This takes a while to query for each pkg, and it
2613+ # can be sped up for cases that are just looking for
2614+ # the binary file urls, by creating a 'lazy' loading
2615+ # BPPH created from the URL and other data used below
2616+ urls = self.binaryFileUrls()
2617+ print("Processing %d binary packages" % len(urls))
2618+ for url in urls:
2619+ # strip out the URL leading text.
2620+ filename = url.rsplit('/', 1)[1]
2621+ # strip the file suffix
2622+ name = filename.rsplit('.', 1)[0]
2623+ # split into name, version, arch
2624+ (n, v, a) = name.rsplit('_', 2)
2625+ # Note we ignore the version, as it may be missing epoch
2626+ params = {'name': n,
2627+ 'archtag': a,
2628+ 'series': self.getSeriesName(),
2629+ 'pocket': self.pocket,
2630+ 'version': self.getVersion(),
2631+ 'status': self.status}
2632+ bpph = archive.getBinaryPackage(**params)
2633+ bpphs.append(bpph)
2634+ print('Lookup: %s ' % filename, end='\r')
2635+ sys.stdout.flush()
2636+ print('')
2637+ else:
2638+ bpphs = map(BinaryPackagePublishingHistory,
2639+ self._lpobject.getPublishedBinaries())
2640+ self._binaries = bpphs
2641+
2642+ if not arch:
2643+ return list(self._binaries)
2644+
2645+ return [b for b in self._binaries if b.arch == arch]
2646
2647 def _fetch_builds(self):
2648 '''Populate self._builds with the build records.'''
2649@@ -671,6 +786,22 @@ class BinaryPackagePublishingHistory(BaseWrapper):
2650 '''
2651 resource_type = 'binary_package_publishing_history'
2652
2653+ def __init__(self, *args):
2654+ self._arch = None
2655+
2656+ @property
2657+ def arch(self):
2658+ if not self._arch:
2659+ das = DistroArchSeries(self._lpobject.distro_arch_series_link)
2660+ self._arch = das.architecture_tag
2661+ return self._arch
2662+
2663+ def getSourcePackageName(self):
2664+ '''
2665+ Returns the source package name.
2666+ '''
2667+ return self.getBuild().source_package_name
2668+
2669 def getPackageName(self):
2670 '''
2671 Returns the binary package name.
2672@@ -700,6 +831,54 @@ class BinaryPackagePublishingHistory(BaseWrapper):
2673 raise AttributeError("binaryFileUrls can only be found in lpapi "
2674 "devel, not 1.0. Login using devel to have it.")
2675
2676+ def getBuild(self):
2677+ '''
2678+ Returns the original build of the binary package.
2679+ '''
2680+ return Build(self._lpobject.build_link)
2681+
2682+ def getUrl(self):
2683+ '''
2684+ Returns the original build URL of the binary package.
2685+ '''
2686+ return "{build}/+files/{filename}".format(build=self.getBuild().getUrl(),
2687+ filename=self.getFileName())
2688+
2689+ def getFileVersion(self):
2690+ '''
2691+ Returns the file version, which is the package version without the epoch
2692+ '''
2693+ return Version(self.getVersion()).strip_epoch()
2694+
2695+ def getFileArch(self):
2696+ '''
2697+ Returns the file arch, which is 'all' if not arch-specific
2698+ '''
2699+ if bool(self._lpobject.architecture_specific):
2700+ return self.arch
2701+ else:
2702+ return 'all'
2703+
2704+ def getFileExt(self):
2705+ '''
2706+ Returns the file extension; "deb", "ddeb", or "udeb".
2707+ '''
2708+ if self.getPackageName().endswith("-dbgsym"):
2709+ return "ddeb"
2710+ elif self.getPackageName().endswith("-di"):
2711+ return "udeb"
2712+ else:
2713+ return "deb"
2714+
2715+ def getFileName(self):
2716+ '''
2717+ Returns the filename for this binary package.
2718+ '''
2719+ return "{name}_{version}_{arch}.{ext}".format(name=self.getPackageName(),
2720+ version=self.getFileVersion(),
2721+ arch=self.getFileArch(),
2722+ ext=self.getFileExt())
2723+
2724
2725 class MetaPersonTeam(MetaWrapper):
2726 @property
2727@@ -729,6 +908,7 @@ class PersonTeam(BaseWrapper):
2728
2729 def __init__(self, *args):
2730 # Don't share _upload between different PersonTeams
2731+ self._ppas = None
2732 if '_upload' not in self.__dict__:
2733 self._upload = dict()
2734
2735@@ -806,6 +986,12 @@ class PersonTeam(BaseWrapper):
2736
2737 return canUpload
2738
2739+ def getPPAs(self):
2740+ if not self._ppas:
2741+ ppas = Launchpad.load(self._lpobject.ppas_collection_link).entries
2742+ self._ppas = [Archive(a['self_link']) for a in ppas]
2743+ return self._ppas
2744+
2745
2746 class Build(BaseWrapper):
2747 '''
2748@@ -816,6 +1002,16 @@ class Build(BaseWrapper):
2749 def __str__(self):
2750 return u'%s: %s' % (self.arch_tag, self.buildstate)
2751
2752+ def getSourcePackagePublishingHistory(self):
2753+ link = self._lpobject.current_source_publication_link
2754+ if link:
2755+ return SourcePackagePublishingHistory(link)
2756+ else:
2757+ return None
2758+
2759+ def getUrl(self):
2760+ return self()
2761+
2762 def rescore(self, score):
2763 if self.can_be_rescored:
2764 self().rescore(score=score)
2765diff --git a/ubuntutools/pullpkg.py b/ubuntutools/pullpkg.py
2766new file mode 100755
2767index 0000000..4b94908
2768--- /dev/null
2769+++ b/ubuntutools/pullpkg.py
2770@@ -0,0 +1,281 @@
2771+# pullpkg.py -- pull package files for debian/ubuntu/uca
2772+# modified from ../pull-lp-source and converted to module
2773+#
2774+# Copyright (C) 2008, Iain Lane <iain@orangesquash.org.uk>,
2775+# 2010-2011, Stefano Rivera <stefanor@ubuntu.com>
2776+# 2017, Dan Streetman <dan.streetman@canonical.com>
2777+#
2778+# ##################################################################
2779+#
2780+# This program is free software; you can redistribute it and/or
2781+# modify it under the terms of the GNU General Public License
2782+# as published by the Free Software Foundation; either version 3
2783+# of the License, or (at your option) any later version.
2784+#
2785+# This program is distributed in the hope that it will be useful,
2786+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2787+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2788+# GNU General Public License for more details.
2789+#
2790+# See file /usr/share/common-licenses/GPL for more details.
2791+#
2792+# ##################################################################
2793+
2794+
2795+import re
2796+from argparse import ArgumentParser
2797+
2798+from distro_info import DebianDistroInfo
2799+
2800+from ubuntutools.archive import (UbuntuSourcePackage, DebianSourcePackage,
2801+ UbuntuCloudArchiveSourcePackage)
2802+from ubuntutools.config import UDTConfig
2803+from ubuntutools.lp.lpapicache import (Distribution, Launchpad)
2804+from ubuntutools.lp.udtexceptions import (SeriesNotFoundException,
2805+ PackageNotFoundException,
2806+ PocketDoesNotExistError)
2807+from ubuntutools.logger import Logger
2808+from ubuntutools.misc import split_release_pocket
2809+from ubuntutools.lp.libsupport import get_system_arch
2810+
2811+PULL_SOURCE = 'source'
2812+PULL_DEBS = 'debs'
2813+PULL_DDEBS = 'ddebs'
2814+PULL_UDEBS = 'udebs'
2815+PULL_LIST = 'list'
2816+
2817+VALID_PULLS = [PULL_SOURCE, PULL_DEBS, PULL_DDEBS, PULL_UDEBS, PULL_LIST]
2818+
2819+DISTRO_DEBIAN = 'debian'
2820+DISTRO_UBUNTU = 'ubuntu'
2821+DISTRO_UCA = 'uca'
2822+
2823+DISTRO_PKG_CLASS = {
2824+ DISTRO_DEBIAN: DebianSourcePackage,
2825+ DISTRO_UBUNTU: UbuntuSourcePackage,
2826+ DISTRO_UCA: UbuntuCloudArchiveSourcePackage,
2827+}
2828+VALID_DISTROS = DISTRO_PKG_CLASS.keys()
2829+
2830+
2831+class InvalidPullValueError(ValueError):
2832+ """ Thrown when --pull value is invalid """
2833+ pass
2834+
2835+
2836+class InvalidDistroValueError(ValueError):
2837+ """ Thrown when --distro value is invalid """
2838+ pass
2839+
2840+
2841+def create_argparser(default_pull=None, default_distro=None, default_arch=None):
2842+ help_default_pull = "What to pull: " + ", ".join(VALID_PULLS)
2843+ if default_pull:
2844+ help_default_pull += (" (default: %s)" % default_pull)
2845+ help_default_distro = "Pull from: " + ", ".join(VALID_DISTROS)
2846+ if default_distro:
2847+ help_default_distro += (" (default: %s)" % default_distro)
2848+ if not default_arch:
2849+ default_arch = get_system_arch()
2850+ help_default_arch = ("Get binary packages for arch (default: %s)" % default_arch)
2851+
2852+ parser = ArgumentParser()
2853+ parser.add_argument('-v', '--verbose', action='store_true',
2854+ help="Print verbose/debug messages")
2855+ parser.add_argument('-d', '--download-only', action='store_true',
2856+ help="Do not extract the source package")
2857+ parser.add_argument('-m', '--mirror', action='append',
2858+ help='Preferred mirror(s)')
2859+ parser.add_argument('--no-conf', action='store_true',
2860+ help="Don't read config files or environment variables")
2861+ parser.add_argument('-a', '--arch', default=default_arch,
2862+ help=help_default_arch)
2863+ parser.add_argument('-p', '--pull', default=default_pull,
2864+ help=help_default_pull)
2865+ parser.add_argument('-D', '--distro', default=default_distro,
2866+ help=help_default_distro)
2867+ parser.add_argument('package', help="Package name to pull")
2868+ parser.add_argument('release', nargs='?', help="Release to pull from")
2869+ parser.add_argument('version', nargs='?', help="Package version to pull")
2870+ return parser
2871+
2872+
2873+def parse_pull(pull):
2874+ if not pull:
2875+ raise InvalidPullValueError("Must specify --pull")
2876+
2877+ # allow 'dbgsym' as alias for 'ddebs'
2878+ if pull == 'dbgsym':
2879+ Logger.debug("Pulling '%s' for '%s'", PULL_DDEBS, pull)
2880+ pull = PULL_DDEBS
2881+ # assume anything starting with 'bin' means 'debs'
2882+ if str(pull).startswith('bin'):
2883+ Logger.debug("Pulling '%s' for '%s'", PULL_DEBS, pull)
2884+ pull = PULL_DEBS
2885+ # verify pull action is valid
2886+ if pull not in VALID_PULLS:
2887+ raise InvalidPullValueError("Invalid pull action '%s'", pull)
2888+
2889+ return pull
2890+
2891+
2892+def parse_distro(distro):
2893+ if not distro:
2894+ raise InvalidDistroValueError("Must specify --distro")
2895+
2896+ distro = distro.lower()
2897+
2898+ # allow 'lp' for 'ubuntu'
2899+ if distro == 'lp':
2900+ Logger.debug("Using distro '%s' for '%s'", DISTRO_UBUNTU, distro)
2901+ distro = DISTRO_UBUNTU
2902+ # assume anything with 'cloud' is UCA
2903+ if re.match(r'.*cloud.*', distro):
2904+ Logger.debug("Using distro '%s' for '%s'", DISTRO_UCA, distro)
2905+ distro = DISTRO_UCA
2906+ # verify distro is valid
2907+ if distro not in VALID_DISTROS:
2908+ raise InvalidDistroValueError("Invalid distro '%s'", distro)
2909+
2910+ return distro
2911+
2912+
2913+def parse_release(release, distro):
2914+ if distro == DISTRO_UCA:
2915+ # UCA is special; it is specified UBUNTURELEASE-UCARELEASE or just
2916+ # UCARELEASE. The user could also specify UCARELEASE-POCKET. But UCA
2917+ # archives always correspond to only one UBUNTURELEASE, and UCA archives
2918+ # have only the Release pocket, so only UCARELEASE matters to us.
2919+ for r in release.split('-'):
2920+ if r in UbuntuCloudArchiveSourcePackage.getReleaseNames():
2921+ Logger.debug("Using UCA release '%s'", r)
2922+ return (r, None)
2923+ raise SeriesNotFoundException('UCA release {} not found.'.format(release))
2924+
2925+ # Check if release[-pocket] is specified
2926+ (release, pocket) = split_release_pocket(release, default=None)
2927+ Logger.debug("Parsed release '%s' pocket '%s'", release, pocket)
2928+
2929+ if distro == DISTRO_DEBIAN:
2930+ # This converts from the aliases like 'unstable'
2931+ debian_info = DebianDistroInfo()
2932+ codename = debian_info.codename(release)
2933+ if codename:
2934+ Logger.normal("Using release '%s' for '%s'", codename, release)
2935+ release = codename
2936+
2937+ try:
2938+ d = Distribution(distro)
2939+ Logger.debug("Distro '%s' is valid", distro)
2940+ except:
2941+ Logger.debug("Distro '%s' not valid", distro)
2942+ raise SeriesNotFoundException("Distro {} not found".format(distro))
2943+
2944+ # let SeriesNotFoundException flow up
2945+ d.getSeries(release)
2946+
2947+ Logger.debug("Using distro '%s' release '%s' pocket '%s'",
2948+ distro, release, pocket)
2949+ return (release, pocket)
2950+
2951+
2952+def pull(options):
2953+ # required options asserted below
2954+ # 'release' and 'version' are optional strings
2955+ # 'mirror' is optional list of strings
2956+ assert hasattr(options, 'verbose') # bool
2957+ assert hasattr(options, 'download_only') # bool
2958+ assert hasattr(options, 'no_conf') # bool
2959+ assert hasattr(options, 'arch') # string
2960+ assert hasattr(options, 'pull') # string
2961+ assert hasattr(options, 'distro') # string
2962+ assert hasattr(options, 'package') # string
2963+
2964+ Logger.set_verbosity(options.verbose)
2965+
2966+ Logger.debug("pullpkg options: %s", options)
2967+
2968+ pull = parse_pull(options.pull)
2969+
2970+ distro = parse_distro(options.distro)
2971+
2972+ config = UDTConfig(options.no_conf)
2973+
2974+ mirrors = []
2975+ if hasattr(options, 'mirror') and options.mirror:
2976+ mirrors += options.mirror
2977+ if pull == PULL_DDEBS:
2978+ ddebs_mirror = config.get_value(distro.upper() + '_DDEBS_MIRROR')
2979+ if ddebs_mirror:
2980+ mirrors.append(ddebs_mirror)
2981+ if mirrors:
2982+ Logger.debug("using mirrors %s", ", ".join(mirrors))
2983+
2984+ # Login anonymously to LP
2985+ Launchpad.login_anonymously()
2986+
2987+ package = options.package
2988+ release = getattr(options, 'release', None)
2989+ version = getattr(options, 'version', None)
2990+ pocket = None
2991+
2992+ if release:
2993+ try:
2994+ (release, pocket) = parse_release(release, distro)
2995+ except (SeriesNotFoundException, PocketDoesNotExistError):
2996+ Logger.debug("Param '%s' not valid series, must be version", release)
2997+ release, version = version, release
2998+ if release:
2999+ try:
3000+ (release, pocket) = parse_release(release, distro)
3001+ except (SeriesNotFoundException, PocketDoesNotExistError):
3002+ Logger.error("Can't find series for '%s' or '%s'",
3003+ release, version)
3004+ raise
3005+
3006+ try:
3007+ pkgcls = DISTRO_PKG_CLASS[distro]
3008+ srcpkg = pkgcls(package=package, version=version,
3009+ series=release, pocket=pocket,
3010+ mirrors=mirrors)
3011+ spph = srcpkg.lp_spph
3012+ except PackageNotFoundException as e:
3013+ Logger.error(str(e))
3014+ raise
3015+
3016+ Logger.normal('Found %s-%s', spph.display_name, spph.pocket)
3017+
3018+ if pull == PULL_LIST:
3019+ Logger.normal("Source files:")
3020+ for f in srcpkg.dsc['Files']:
3021+ Logger.normal(" %s", f['name'])
3022+ Logger.normal("Binary files:")
3023+ for f in spph.getBinaries(options.arch):
3024+ Logger.normal(" %s", f.getFileName())
3025+ return
3026+
3027+ # allow DownloadError to flow up to caller
3028+ if pull == PULL_SOURCE:
3029+ srcpkg.pull()
3030+ if options.download_only:
3031+ Logger.debug("--download-only specified, not extracting")
3032+ else:
3033+ srcpkg.unpack()
3034+ else:
3035+ name = '.*'
3036+ if package != spph.getPackageName():
3037+ Logger.normal("Pulling binary package '%s'", package)
3038+ Logger.normal("Use package name '%s' to pull all binary packages",
3039+ spph.getPackageName())
3040+ name = package
3041+ if pull == PULL_DEBS:
3042+ name = r'{}(?<!-di)(?<!-dbgsym)$'.format(name)
3043+ elif pull == PULL_DDEBS:
3044+ name += '-dbgsym$'
3045+ elif pull == PULL_UDEBS:
3046+ name += '-di$'
3047+ else:
3048+ raise InvalidPullValueError("Invalid pull value %s", pull)
3049+ total = srcpkg.pull_binaries(name=name, arch=options.arch)
3050+ if total < 1:
3051+ Logger.error("No %s found for %s", pull, spph.display_name)
3052diff --git a/ubuntutools/requestsync/mail.py b/ubuntutools/requestsync/mail.py
3053index e3388f2..a79c9c3 100644
3054--- a/ubuntutools/requestsync/mail.py
3055+++ b/ubuntutools/requestsync/mail.py
3056@@ -29,10 +29,10 @@ import smtplib
3057 import socket
3058 import tempfile
3059
3060-from debian.changelog import Changelog, Version
3061+from debian.changelog import Changelog
3062 from distro_info import DebianDistroInfo, DistroDataOutdated
3063
3064-from ubuntutools.archive import rmadison, FakeSPPH
3065+from ubuntutools.archive import DebianSourcePackage, UbuntuSourcePackage
3066 from ubuntutools.lp.udtexceptions import PackageNotFoundException
3067 from ubuntutools.logger import Logger
3068 from ubuntutools.question import confirmation_prompt, YesNoQuestion
3069@@ -53,32 +53,21 @@ __all__ = [
3070 ]
3071
3072
3073-def _get_srcpkg(distro, name, release):
3074- if distro == 'debian':
3075- # Canonicalise release:
3076- debian_info = DebianDistroInfo()
3077- try:
3078- codename = debian_info.codename(release, default=release)
3079- except DistroDataOutdated as e:
3080- Logger.warn(e)
3081-
3082- lines = list(rmadison(distro, name, suite=codename, arch='source'))
3083- if not lines:
3084- lines = list(rmadison(distro, name, suite=release, arch='source'))
3085- if not lines:
3086- raise PackageNotFoundException("'%s' doesn't appear to exist in %s '%s'" %
3087- (name, distro.capitalize(), release))
3088- pkg = max(lines, key=lambda x: Version(x['version']))
3089-
3090- return FakeSPPH(pkg['source'], pkg['version'], pkg['component'], distro)
3091-
3092-
3093 def get_debian_srcpkg(name, release):
3094- return _get_srcpkg('debian', name, release)
3095+ # Canonicalise release:
3096+ debian_info = DebianDistroInfo()
3097+ try:
3098+ codename = debian_info.codename(release, default=release)
3099+ return DebianSourcePackage(package=name, series=codename).lp_spph
3100+ except DistroDataOutdated as e:
3101+ Logger.warn(e)
3102+ except PackageNotFoundException:
3103+ pass
3104+ return DebianSourcePackage(package=name, series=release).lp_spph
3105
3106
3107 def get_ubuntu_srcpkg(name, release):
3108- return _get_srcpkg('ubuntu', name, release)
3109+ return UbuntuSourcePackage(package=name, series=release).lp_spph
3110
3111
3112 def need_sponsorship(name, component, release):
3113diff --git a/ubuntutools/test/test_archive.py b/ubuntutools/test/test_archive.py
3114index 38c1a63..43222c9 100644
3115--- a/ubuntutools/test/test_archive.py
3116+++ b/ubuntutools/test/test_archive.py
3117@@ -97,7 +97,6 @@ class LocalSourcePackageTestCase(unittest.TestCase):
3118 self.workdir = tempfile.mkdtemp(prefix='udt-test')
3119
3120 self._stubout('ubuntutools.archive.Distribution')
3121- self._stubout('ubuntutools.archive.rmadison')
3122
3123 self.mock_http = self._stubout('httplib2.Http.request')
3124 self.mock_http.side_effect = self.request_proxy
3125@@ -246,8 +245,6 @@ class DebianLocalSourcePackageTestCase(LocalSourcePackageTestCase):
3126 sequence = [self.urlopen_null,
3127 self.urlopen_404,
3128 self.urlopen_404,
3129- self.urlopen_404,
3130- self.urlopen_404,
3131 lambda x: BytesIO(
3132 b'{"fileinfo": {"hashabc": [{"name": "example_1.0.orig.tar.gz"}]}}'),
3133 self.urlopen_file('example_1.0.orig.tar.gz'),

Subscribers

People subscribed via source and target branches