Merge lp:~jamestait/click-reviewers-tools/enable-store-metadata-report into lp:click-reviewers-tools

Proposed by James Tait
Status: Work in progress
Proposed branch: lp:~jamestait/click-reviewers-tools/enable-store-metadata-report
Merge into: lp:click-reviewers-tools
Diff against target: 345 lines (+271/-0)
5 files modified
bin/click-metadata (+27/-0)
clickreviews/common.py (+66/-0)
clickreviews/cr_common.py (+39/-0)
clickreviews/cr_metadata.py (+106/-0)
clickreviews/sr_common.py (+33/-0)
To merge this branch: bzr merge lp:~jamestait/click-reviewers-tools/enable-store-metadata-report
Reviewer Review Type Date Requested Status
Canonical Store Reviewers Pending
Review via email: mp+291092@code.launchpad.net

Commit message

Add support for extracting package metadata for the Store.

Description of the change

This branch adds a new script that the Store can use to extract metadata from packages and have it returned in a consistent format.

To post a comment you must log in.

Unmerged revisions

617. By James Tait

Add support for extracting package metadata for the Store.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'bin/click-metadata'
2--- bin/click-metadata 1970-01-01 00:00:00 +0000
3+++ bin/click-metadata 2016-04-06 09:31:46 +0000
4@@ -0,0 +1,27 @@
5+#!/usr/bin/python3
6+
7+import json
8+import sys
9+
10+from clickreviews import (
11+ common,
12+ cr_common,
13+ sr_common,
14+)
15+
16+
17+if __name__ == '__main__':
18+ if len(sys.argv) < 2:
19+ common.error("Must give path to package")
20+
21+ review = common.Review(sys.argv[1], '')
22+
23+ if review.is_click or review.is_snap1:
24+ metadata = cr_common.ClickReview(sys.argv[1], 'metadata')
25+ elif review.is_snap2:
26+ metadata = sr_common.SnapReview(sys.argv[1], 'metadata')
27+ else:
28+ common.error('Unknown package type.')
29+
30+ print(json.dumps(metadata.get_metadata(), sort_keys=True,
31+ indent=2, separators=(',', ': ')))
32
33=== modified file 'clickreviews/common.py'
34--- clickreviews/common.py 2016-02-23 21:09:07 +0000
35+++ clickreviews/common.py 2016-04-06 09:31:46 +0000
36@@ -373,6 +373,62 @@
37 '''Set review name'''
38 self.review_type = name
39
40+ #
41+ # Package Metadata extraction, primarily for the Store.
42+ #
43+
44+ def get_metadata(self):
45+ metadata = {
46+ 'name': self.get_name(),
47+ 'version': self.get_version(),
48+ 'title': self.get_title(),
49+ 'description': self.get_description(),
50+ 'architecture': self.pkg_arch,
51+ }
52+ framework = self.get_framework()
53+ if framework is not None:
54+ metadata['framework'] = framework
55+ icon = self.get_icon()
56+ if icon['path'] is not None:
57+ metadata['icon'] = icon
58+ installed_size = self.get_installed_size()
59+ if installed_size is not None:
60+ metadata['installed-size'] = installed_size
61+ maintainer = self.get_maintainer()
62+ if maintainer is not None:
63+ metadata['maintainer'] = maintainer
64+ return [
65+ metadata, {
66+ 'is_snap': self.is_snap1 or self.is_snap2,
67+ 'is_squashfs': self.is_snap2
68+ }]
69+
70+ def get_name(self):
71+ return None
72+
73+ def get_version(self):
74+ return None
75+
76+ def get_installed_size(self):
77+ return None
78+
79+ def get_title(self):
80+ return None
81+
82+ def get_description(self):
83+ return None
84+
85+ def get_framework(self):
86+ return None
87+
88+ def get_icon(self):
89+ return None
90+
91+ def get_architecture(self):
92+ return None
93+
94+ def get_maintainer(self):
95+ return None
96
97 #
98 # Utility functions
99@@ -541,6 +597,16 @@
100 return dest
101
102
103+def open_binary_file_read(path):
104+ '''Open specified binary file read-only'''
105+ try:
106+ orig = codecs.open(path, 'rb')
107+ except Exception:
108+ raise
109+
110+ return orig
111+
112+
113 def create_tempdir():
114 '''Create/reuse a temporary directory that is automatically cleaned up'''
115 global TMP_DIR
116
117=== modified file 'clickreviews/cr_common.py'
118--- clickreviews/cr_common.py 2016-02-09 22:49:57 +0000
119+++ clickreviews/cr_common.py 2016-04-06 09:31:46 +0000
120@@ -16,6 +16,7 @@
121
122 from __future__ import print_function
123 from debian.deb822 import Deb822
124+import base64
125 import glob
126 import json
127 import os
128@@ -28,6 +29,7 @@
129 Review,
130 ReviewException,
131 error,
132+ open_binary_file_read,
133 open_file_read,
134 )
135
136@@ -391,6 +393,43 @@
137 d[name][key] = entry[key]
138 return d
139
140+ def get_name(self):
141+ return self.click_pkgname
142+
143+ def get_version(self):
144+ return self.click_version
145+
146+ def get_installed_size(self):
147+ return self.manifest.get('installed-size')
148+
149+ def get_title(self):
150+ return self.manifest.get('title')
151+
152+ def get_description(self):
153+ return self.manifest.get('description')
154+
155+ def get_framework(self):
156+ return self.manifest.get('framework')
157+
158+ def get_icon(self):
159+ icon_path = self.manifest.get('icon')
160+ icon_data = None
161+ if icon_path:
162+ full_path = os.path.join(self.unpack_dir, icon_path)
163+ with open_binary_file_read(full_path) as f:
164+ icon_data = base64.b64encode(f.read())
165+ return {
166+ 'path': icon_path,
167+ 'data': str(icon_data) if icon_data else None,
168+ 'filename': os.path.basename(icon_path) if icon_path else None,
169+ }
170+
171+ def get_architecture(self):
172+ return self.manifest.get('architecture')
173+
174+ def get_maintainer(self):
175+ return self.manifest.get('maintainer')
176+
177 def check_peer_hooks(self, hooks_sublist=[]):
178 '''Check if peer hooks are valid'''
179 # Nothing to verify
180
181=== added file 'clickreviews/cr_metadata.py'
182--- clickreviews/cr_metadata.py 1970-01-01 00:00:00 +0000
183+++ clickreviews/cr_metadata.py 2016-04-06 09:31:46 +0000
184@@ -0,0 +1,106 @@
185+'''cr_content_hub.py: click content-hub checks'''
186+#
187+# Copyright (C) 2014-2015 Canonical Ltd.
188+#
189+# This program is free software: you can redistribute it and/or modify
190+# it under the terms of the GNU General Public License as published by
191+# the Free Software Foundation; version 3 of the License.
192+#
193+# This program is distributed in the hope that it will be useful,
194+# but WITHOUT ANY WARRANTY; without even the implied warranty of
195+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
196+# GNU General Public License for more details.
197+#
198+# You should have received a copy of the GNU General Public License
199+# along with this program. If not, see <http://www.gnu.org/licenses/>.
200+
201+from __future__ import print_function
202+
203+from clickreviews.cr_common import ClickReview, error, open_file_read
204+import json
205+import os
206+
207+
208+class ClickReviewMetadata(ClickReview):
209+ '''This class represents click lint reviews'''
210+ def __init__(self, fn, overrides=None):
211+ ClickReview.__init__(self, fn, "metadata", overrides=overrides)
212+ if not self.is_click and not self.is_snap1:
213+ return
214+
215+ self.valid_keys = ['destination', 'share', 'source']
216+
217+ self.content_hub_files = dict() # click-show-files and tests
218+ self.content_hub = dict()
219+
220+ if self.manifest is None:
221+ return
222+
223+ for app in self.manifest['hooks']:
224+ if 'content-hub' not in self.manifest['hooks'][app]:
225+ # msg("Skipped missing content-hub hook for '%s'" % app)
226+ continue
227+ if not isinstance(self.manifest['hooks'][app]['content-hub'], str):
228+ error("manifest malformed: hooks/%s/urls is not str" % app)
229+ (full_fn, jd) = self._extract_content_hub(app)
230+ self.content_hub_files[app] = full_fn
231+ self.content_hub[app] = jd
232+
233+ def _extract_content_hub(self, app):
234+ '''Get content-hub hook content'''
235+ c = self.manifest['hooks'][app]['content-hub']
236+ fn = os.path.join(self.unpack_dir, c)
237+
238+ bn = os.path.basename(fn)
239+ if not os.path.exists(fn):
240+ error("Could not find '%s'" % bn)
241+
242+ fh = open_file_read(fn)
243+ contents = ""
244+ for line in fh.readlines():
245+ contents += line
246+ fh.close()
247+
248+ try:
249+ jd = json.loads(contents)
250+ except Exception as e:
251+ error("content-hub json unparseable: %s (%s):\n%s" % (bn,
252+ str(e), contents))
253+
254+ if not isinstance(jd, dict):
255+ error("content-hub json is malformed: %s:\n%s" % (bn, contents))
256+
257+ return (fn, jd)
258+
259+ def check_valid(self):
260+ '''Check validity of content-hub entries'''
261+ if not self.is_click and not self.is_snap1:
262+ return
263+
264+ for app in sorted(self.content_hub):
265+ for k in self.content_hub[app].keys():
266+ t = "info"
267+ n = self._get_check_name('valid', app=app, extra=k)
268+ s = "OK"
269+
270+ if not isinstance(self.content_hub[app][k], list):
271+ t = "error"
272+ s = "'%s' is not a list" % k
273+ elif len(self.content_hub[app][k]) < 1:
274+ t = "error"
275+ s = "'%s' is empty" % k
276+ self._add_result(t, n, s)
277+ if t == "error":
278+ continue
279+
280+ for v in self.content_hub[app][k]:
281+ t = "info"
282+ n = self._get_check_name('valid_value', app=app, extra=k)
283+ s = "OK"
284+ if not isinstance(v, str):
285+ t = "error"
286+ s = "'%s' is not a string" % k
287+ elif v == "":
288+ t = "error"
289+ s = "'%s' is empty" % k
290+ self._add_result(t, n, s)
291
292=== modified file 'clickreviews/sr_common.py'
293--- clickreviews/sr_common.py 2016-03-14 19:20:55 +0000
294+++ clickreviews/sr_common.py 2016-04-06 09:31:46 +0000
295@@ -15,6 +15,7 @@
296 # along with this program. If not, see <http://www.gnu.org/licenses/>.
297
298 from __future__ import print_function
299+import base64
300 import os
301 import re
302 import yaml
303@@ -24,6 +25,7 @@
304 Review,
305 ReviewException,
306 error,
307+ open_binary_file_read,
308 open_file_read,
309 )
310
311@@ -129,3 +131,34 @@
312 if pat.search(n):
313 return True
314 return False
315+
316+ def get_name(self):
317+ return self.snap_yaml.get('name')
318+
319+ def get_version(self):
320+ return str(self.snap_yaml.get('version'))
321+
322+ def get_description(self):
323+ return self.snap_yaml.get('description')
324+
325+ def get_icon(self):
326+ icon_path = None
327+ for ext in ('svg', 'png'):
328+ path = 'meta/icon.' + ext
329+ full_path = os.path.join(self.unpack_dir, path)
330+ if os.path.isfile(full_path):
331+ icon_path = path
332+ break
333+ icon_data = None
334+ if icon_path:
335+ full_path = os.path.join(self.unpack_dir, icon_path)
336+ with open_binary_file_read(full_path) as f:
337+ icon_data = base64.b64encode(f.read())
338+ return {
339+ 'path': icon_path,
340+ 'data': str(icon_data) if icon_data else None,
341+ 'filename': os.path.basename(icon_path) if icon_path else None,
342+ }
343+
344+ def get_architecture(self):
345+ return self.pkg_arch

Subscribers

People subscribed via source and target branches