Merge ~racb/usd-importer:queue into usd-importer:master

Proposed by Robie Basak on 2017-04-05
Status: Merged
Approved by: Nish Aravamudan on 2017-04-05
Approved revision: 542e518cba86082c4c590a9163ebf04b3e354ffe
Merged at revision: 075fcf8f37e4c3ee1064e7cc78a88da49d2ad99f
Proposed branch: ~racb/usd-importer:queue
Merge into: usd-importer:master
Diff against target: 204 lines (+176/-2)
2 files modified
usd/__main__.py (+4/-2)
usd/queue.py (+172/-0)
Reviewer Review Type Date Requested Status
Nish Aravamudan 2017-04-05 Pending
Review via email: mp+322042@code.launchpad.net

Description of the Change

Add import of Launchpad "Unapproved" and "New" queues.

19:41 <rbasak> I want it to autodetect the package name from debian/changelog so it doesn't need to be specified

19:41 <rbasak> And then replace the existing "usd queue <package>" with "usd queue sync". Then I think it'll be ready to land.

19:41 <nacc> rbasak: ok, i can probably do that locally if you want to propose a MR?

To post a comment you must log in.
Robie Basak (racb) wrote :

I just noticed that FIX_SERIES_LIST should probably be fixed before landing, too.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/usd/__main__.py b/usd/__main__.py
2index 2cad560..ba602ad 100644
3--- a/usd/__main__.py
4+++ b/usd/__main__.py
5@@ -13,6 +13,7 @@ def main():
6 from usd.merge import USDMerge
7 from usd.clone import USDClone
8 from usd.tag import USDTag
9+ from usd.queue import USDQueueImporter
10
11 try:
12 pkg = 'python3-pkg-resources'
13@@ -28,10 +29,11 @@ def main():
14 'build-source':USDBuildSource,
15 'merge':USDMerge,
16 'clone':USDClone,
17- 'tag':USDTag
18+ 'tag':USDTag,
19+ 'queue':USDQueueImporter,
20 }
21
22- known_network_subcommands = {'import', 'clone', 'build', 'build-source'}
23+ known_network_subcommands = {'import', 'clone', 'build', 'build-source', 'queue'}
24
25 parser = argparse.ArgumentParser(
26 description='Ubuntu Server Dev git tool',
27diff --git a/usd/queue.py b/usd/queue.py
28new file mode 100644
29index 0000000..5166c4e
30--- /dev/null
31+++ b/usd/queue.py
32@@ -0,0 +1,172 @@
33+import argparse
34+import itertools
35+import logging
36+import os
37+import re
38+import tempfile
39+import urllib
40+
41+import pygit2
42+
43+from usd.git_repository import USDGitRepository
44+import usd.importer
45+import usd.source_information
46+
47+FIX_SERIES_LIST = ['precise', 'trusty', 'xenial', 'yakkety']
48+
49+class USDQueueImporter:
50+ MATCH_TAG_URI = re.compile(r'^Launchpad-URI:\s*(.*)$', flags=re.MULTILINE)
51+ DOWNLOAD_MATCHES = {
52+ 'changes': re.compile(r'_source\.changes$'),
53+ 'dsc': re.compile(r'\.dsc$'),
54+ }
55+
56+ def parse_args(self, subparsers=None, base_subparsers=None):
57+ kwargs = dict(description='Import unapproved and new uploads',
58+ formatter_class=argparse.RawTextHelpFormatter,
59+ )
60+ if base_subparsers:
61+ kwargs['parents'] = base_subparsers
62+ if subparsers:
63+ parser = subparsers.add_parser('queue', **kwargs)
64+ parser.set_defaults(func=self.main)
65+ else:
66+ parser = argparse.ArgumentParser(**kwargs)
67+
68+ parser.add_argument('package')
69+ parser.add_argument('-d', '--directory', type=str,
70+ help='Use git repository at specified location.',
71+ default=os.path.abspath(os.getcwd())
72+ )
73+
74+ if not subparsers:
75+ return parser.parse_args()
76+ return 'queue - %s' % kwargs['description']
77+
78+ @staticmethod
79+ def _fetch_lp_queue_items(lp, series, package):
80+ '''Fetch unapproved and new entries from Launchpad
81+
82+ Returns a dictionary of Launchpad upload objects keyed by persistent
83+ unique ID.
84+ '''
85+ s = lp.distributions['ubuntu'].getSeries(name_or_version=series)
86+ us = itertools.chain(*(
87+ s.getPackageUploads(
88+ pocket='Proposed',
89+ status=status,
90+ exact_match=True,
91+ name=package,
92+ ) for status in ['Unapproved', 'New']
93+ ))
94+ lp_queue_items = {x.self_link: x for x in us}
95+ return lp_queue_items
96+
97+ @staticmethod
98+ def _fetch_queue_tag_names(repo, series):
99+ regexp = re.compile(r'^refs/tags/queue/%s/' % series)
100+ return repo.listall_references_matching_regexp(regexp)
101+
102+ @classmethod
103+ def _get_queue_tag_uri(self, tag):
104+ match = self.MATCH_TAG_URI.search(tag.message)
105+ assert match, "Tag URI not found"
106+ return match.group(1)
107+
108+ @staticmethod
109+ def _download_url(url, destdir):
110+ parsed_url = urllib.parse.urlparse(url)
111+ # Require secure URL
112+ if parsed_url.scheme != 'https':
113+ raise ValueError("URL scheme is not HTTPS: %s" % url)
114+ # Find and require a basename to store the file locally with a sensible
115+ # filename
116+ basename = parsed_url.path.split('/')[-1]
117+ if not basename:
118+ raise ValueError("Provided URL %s has no basename component" % url)
119+ with urllib.request.urlopen(url) as response:
120+ with open(os.path.join(destdir, basename), 'wb') as f:
121+ f.write(response.read())
122+
123+ @classmethod
124+ def _commit_upload(self, repo, upload, parent_commit):
125+ with tempfile.TemporaryDirectory() as tmpdir:
126+ self._download_url(upload.changes_file_url, tmpdir)
127+ for url in upload.sourceFileUrls():
128+ self._download_url(url, tmpdir)
129+
130+ download_key = {}
131+ for filename in os.listdir(tmpdir):
132+ for file_type, regexp in self.DOWNLOAD_MATCHES.items():
133+ if regexp.search(filename):
134+ download_key[file_type] = filename
135+
136+ # We should have both a changes file and a dsc file
137+ assert set(download_key.keys()) == {'changes', 'dsc'}
138+
139+ # Import the source package into a git tree object
140+ tree_hash = usd.importer.dsc_to_tree_hash(
141+ repo,
142+ os.path.join(tmpdir, download_key['dsc']),
143+ )
144+
145+ # Ensure parents contains a string if specified
146+ parents = [str(parent_commit)] if parent_commit else []
147+ commit_hash = repo.commit_tree_hash(
148+ tree_hash=tree_hash,
149+ parents=parents,
150+ log_message='Queue import'.encode(),
151+ )
152+
153+ return commit_hash
154+
155+ def main(self, args):
156+ repo = USDGitRepository(args.directory)
157+ lp = usd.source_information.launchpad_login()
158+ for series in FIX_SERIES_LIST:
159+ devel_head_ref = repo.get_head_by_name('lpusip/ubuntu/%s-devel' % series)
160+ devel_head = devel_head_ref.target if devel_head_ref else None
161+ lp_queue_items = self._fetch_lp_queue_items(lp, series, args.package)
162+ all_tag_names = self._fetch_queue_tag_names(repo, series)
163+ found_uris_in_repo = set()
164+ for tag_name in all_tag_names:
165+ logging.debug('Considering %s', tag_name)
166+ tag_ref = repo.lookup_reference(tag_name)
167+ try:
168+ tag = tag_ref.peel(pygit2.Tag)
169+ except ValueError:
170+ print('%s is invalid; deleting' % tag_name)
171+ tag_ref.delete()
172+ continue
173+ tag_uri = self._get_queue_tag_uri(tag)
174+ if tag_uri not in lp_queue_items:
175+ print('%s is no longer in the queue; deleting' % tag_name)
176+ tag_ref.delete()
177+ continue
178+ if lp_queue_items[tag_uri].status == 'Unapproved' and tag.peel(pygit2.Commit).parent_ids != [devel_head]:
179+ print('%s is no longer based correctly; deleting' % tag_name)
180+ tag_ref.delete()
181+ continue
182+ found_uris_in_repo.add(tag_uri)
183+ for uri, upload in lp_queue_items.items():
184+ if upload.status == 'Unapproved':
185+ assert devel_head
186+ parent_commit = devel_head
187+ tag_type = 'unapproved'
188+ elif upload.status == 'New':
189+ parent_commit = None
190+ tag_type = 'new'
191+ else:
192+ raise RuntimeError('Unknown status for %s: %s' % (uri, upload.status))
193+ if uri in found_uris_in_repo:
194+ logging.debug('Skipping %s: already imported', uri)
195+ continue # already imported
196+ logging.debug('Importing %s', uri)
197+ commit_hash = self._commit_upload(repo, upload, parent_commit)
198+ unique_id = repo.get_short_hash(commit_hash)
199+ repo.annotated_tag(
200+ 'queue/%s/%s/%s' % (series, tag_type, unique_id),
201+ commit_hash,
202+ force=False,
203+ msg=('Launchpad-URI: %s' % uri),
204+ )

Subscribers

People subscribed via source and target branches