Merge lp:~cjohnston/ubuntu-ci-services-itself/cli-stuff into lp:ubuntu-ci-services-itself
- cli-stuff
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Chris Johnston |
Approved revision: | 71 |
Merged at revision: | 104 |
Proposed branch: | lp:~cjohnston/ubuntu-ci-services-itself/cli-stuff |
Merge into: | lp:ubuntu-ci-services-itself |
Diff against target: |
763 lines (+590/-28) 13 files modified
cli/README (+11/-1) cli/ci_libs/__init__.py (+1/-1) cli/ci_libs/file_handler.py (+32/-15) cli/ci_libs/status.py (+39/-0) cli/ci_libs/ticket.py (+109/-0) cli/ci_libs/utils.py (+74/-0) cli/setup.py (+15/-3) cli/tests/__init__.py (+1/-1) cli/tests/test_file_handler.py (+11/-7) cli/tests/test_get_ticket_status.py (+109/-0) cli/tests/test_ticket.py (+71/-0) cli/tests/test_utils.py (+48/-0) cli/ubuntu-ci (+69/-0) |
To merge this branch: | bzr merge lp:~cjohnston/ubuntu-ci-services-itself/cli-stuff |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Chris Johnston (community) | Approve | ||
Andy Doan (community) | Approve | ||
Joe Talbott (community) | Approve | ||
Review via email:
|
Commit message
Adds the ability to create a ticket and check ticket status to the CLI
Description of the change
Adds the ability to create a ticket and check ticket status to the CLI. Presently it is run by:
python ubuntu-ci <args>
Eventually it will be packaged as a Debian package for install.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andy Doan (doanac) wrote : | # |
20 --- cli/cli/
21 +++ cli/ci_
22 @@ -1,5 +1,6 @@
23 +#!/usr/bin/env python
Not a big deal - but since this is not a main entry point, you don't need line 23.
I see the basics of an http-get in status.
You assert the auth_config from file_handler.py but the config from utils.py. Maybe just do that in a single place?
cli/tests/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ursula Junque (ursinha) wrote : | # |
You might want to rename test_changes_
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Johnston (cjohnston) : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andy Doan (doanac) wrote : | # |
we need a better solution long-term for these required config files. but this is good enough for now.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Johnston (cjohnston) : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Johnston (cjohnston) wrote : | # |
The attempt to merge lp:~cjohnston/ubuntu-ci-services-itself/cli-stuff into lp:ubuntu-ci-services-itself failed. Below is the output from the failed tests.
New python executable in /tmp/tmp.
Installing distribute.
Installing pip....
== Testing ci-utils ....
running develop
running egg_info
creating ci_utils.egg-info
writing requirements to ci_utils.
writing ci_utils.
writing top-level names to ci_utils.
writing dependency_links to ci_utils.
writing manifest file 'ci_utils.
reading manifest file 'ci_utils.
writing manifest file 'ci_utils.
running build_ext
Creating /tmp/tmp.
Adding ci-utils 0.1 to easy-install.pth file
Installed /tmp/tarmac/
Processing dependencies for ci-utils==0.1
Searching for testtools
Reading http://
Best match: testtools 0.9.34
Downloading https:/
Processing testtools-
Running testtools-
Adding testtools 0.9.34 to easy-install.pth file
Installed /tmp/tmp.
Searching for restish==0.12.1
Reading http://
Best match: restish 0.12.1
Downloading https:/
Processing restish-
Running restish-
Adding restish 0.12.1 to easy-install.pth file
Installed /tmp/tmp.
Searching for python-subunit
Reading http://
Reading http://
Best match: python-subunit 0.0.16
Downloading https:/
Processing python-
Running python-
Adding python-subunit 0.0.16 to easy-install.pth file
Installing subunit-tags script to /tmp/tmp.
Installing subunit-notify script to /tmp/tmp.
Installing subunit-1to2 script to /tmp/tmp.
Installing tap2subunit script to /tmp/tmp.
Installing subunit2gtk script to /tmp/tmp.
Installing subunit-ls script to /tmp/tmp.
Installing subunit-stats script to /tmp/tmp.
Installing subunit2pyunit script to /tmp/tmp.
Installing subunit-filter script to...
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Vincent Ladeuil (vila) wrote : | # |
> we need a better solution long-term for these required config files. but this
> is good enough for now.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andy Doan (doanac) wrote : | # |
On 01/14/2014 02:13 AM, Vincent Ladeuil wrote:
>> we need a better solution long-term for these required config files. but this
>> >is good enough for now.
> lp:uci-config ?
The comment was less about how to manage a config file and more about
the fact that the test cases won't pass unless a config file exists
*before* the test is run. The setup function for the test in question
needs to be doing testing where it tests that things fail gracefully if
the config doesn't exist, fail gracefully if the config is wrong, or
works when then config is what's expected.
uci-config seems like it could work. I think bzr uses configglue. I'd
probably lean to configglue since there's already a package for it.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Johnston (cjohnston) : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Andy Doan (doanac) wrote : | # |
I hate the python-switfclient hack for revno 70, but as I came up with the workaround i'll have to +1
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Johnston (cjohnston) wrote : | # |
The attempt to merge lp:~cjohnston/ubuntu-ci-services-itself/cli-stuff into lp:ubuntu-ci-services-itself failed. Below is the output from the failed tests.
New python executable in /tmp/tmp.
Installing distribute.
Installing pip....
== Testing ci-utils ....
running develop
running egg_info
creating ci_utils.egg-info
writing requirements to ci_utils.
writing ci_utils.
writing top-level names to ci_utils.
writing dependency_links to ci_utils.
writing manifest file 'ci_utils.
reading manifest file 'ci_utils.
writing manifest file 'ci_utils.
running build_ext
Creating /tmp/tmp.
Adding ci-utils 0.1 to easy-install.pth file
Installed /tmp/tarmac/
Processing dependencies for ci-utils==0.1
Searching for testtools
Reading http://
Best match: testtools 0.9.34
Downloading https:/
Processing testtools-
Running testtools-
Adding testtools 0.9.34 to easy-install.pth file
Installed /tmp/tmp.
Searching for restish==0.12.1
Reading http://
Best match: restish 0.12.1
Downloading https:/
Processing restish-
Running restish-
Adding restish 0.12.1 to easy-install.pth file
Installed /tmp/tmp.
Searching for python-subunit
Reading http://
Reading http://
Best match: python-subunit 0.0.16
Downloading https:/
Processing python-
Running python-
Adding python-subunit 0.0.16 to easy-install.pth file
Installing subunit-filter script to /tmp/tmp.
Installing subunit-2to1 script to /tmp/tmp.
Installing subunit-ls script to /tmp/tmp.
Installing tap2subunit script to /tmp/tmp.
Installing subunit-1to2 script to /tmp/tmp.
Installing subunit2pyunit script to /tmp/tmp.
Installing subunit2junitxml script to /tmp/tmp.
Installing subunit-tags script to /tmp/tmp.
Installing subunit-notify scrip...
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Johnston (cjohnston) wrote : | # |
Attempt to merge into lp:ubuntu-ci-services-itself failed due to conflicts:
text conflict in cli/setup.py
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Johnston (cjohnston) : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chris Johnston (cjohnston) wrote : | # |
The attempt to merge lp:~cjohnston/ubuntu-ci-services-itself/cli-stuff into lp:ubuntu-ci-services-itself failed. Below is the output from the failed tests.
New python executable in /tmp/tmp.
Installing distribute.
Installing pip....
== Testing ci-utils ....
running develop
running egg_info
creating ci_utils.egg-info
writing requirements to ci_utils.
writing ci_utils.
writing top-level names to ci_utils.
writing dependency_links to ci_utils.
writing manifest file 'ci_utils.
reading manifest file 'ci_utils.
writing manifest file 'ci_utils.
running build_ext
Creating /tmp/tmp.
Adding ci-utils 0.1 to easy-install.pth file
Installed /tmp/tarmac/
Processing dependencies for ci-utils==0.1
Searching for testtools
Reading http://
Best match: testtools 0.9.34
Downloading https:/
Processing testtools-
Running testtools-
Adding testtools 0.9.34 to easy-install.pth file
Installed /tmp/tmp.
Searching for restish==0.12.1
Reading http://
Best match: restish 0.12.1
Downloading https:/
Processing restish-
Running restish-
Adding restish 0.12.1 to easy-install.pth file
Installed /tmp/tmp.
Searching for python-subunit
Reading http://
Reading http://
Best match: python-subunit 0.0.16
Downloading https:/
Processing python-
Running python-
Adding python-subunit 0.0.16 to easy-install.pth file
Installing subunit-filter script to /tmp/tmp.
Installing subunit-2to1 script to /tmp/tmp.
Installing subunit-ls script to /tmp/tmp.
Installing tap2subunit script to /tmp/tmp.
Installing subunit-1to2 script to /tmp/tmp.
Installing subunit2pyunit script to /tmp/tmp.
Installing subunit2junitxml script to /tmp/tmp.
Installing subunit-tags script to /tmp/tmp.
Installing subunit-notify scrip...
Preview Diff
1 | === modified file 'cli/README' |
2 | --- cli/README 2014-01-08 19:44:59 +0000 |
3 | +++ cli/README 2014-01-14 19:35:30 +0000 |
4 | @@ -1,1 +1,11 @@ |
5 | -# TODO |
6 | +To use this app, you will need to create the files |
7 | +~/.ubuntu-ci/auth_config.yaml and ~/.ubuntu-ci/config.yaml. |
8 | + |
9 | +auth_config.yaml needs to include: |
10 | +- 'auth_url' |
11 | +- 'auth_user' |
12 | +- 'auth_password' |
13 | +- 'auth_tenant_name' |
14 | + |
15 | +config.yaml needs to include: |
16 | +- 'ci_url' |
17 | |
18 | === renamed directory 'cli/cli' => 'cli/ci_libs' |
19 | === modified file 'cli/ci_libs/__init__.py' |
20 | --- cli/cli/__init__.py 2013-12-21 00:59:08 +0000 |
21 | +++ cli/ci_libs/__init__.py 2014-01-14 19:35:30 +0000 |
22 | @@ -1,5 +1,5 @@ |
23 | # Ubuntu Continuous Integration Engine |
24 | -# Copyright 2013 Canonical Ltd. |
25 | +# Copyright 2014 Canonical Ltd. |
26 | |
27 | # This program is free software: you can redistribute it and/or modify it |
28 | # under |
29 | |
30 | === renamed file 'cli/cli/changes_processor.py' => 'cli/ci_libs/file_handler.py' |
31 | --- cli/cli/changes_processor.py 2014-01-08 13:27:56 +0000 |
32 | +++ cli/ci_libs/file_handler.py 2014-01-14 19:35:30 +0000 |
33 | @@ -1,5 +1,5 @@ |
34 | # Ubuntu Continuous Integration Engine |
35 | -# Copyright 2013 Canonical Ltd. |
36 | +# Copyright 2014 Canonical Ltd. |
37 | |
38 | # This program is free software: you can redistribute it and/or modify it under |
39 | # the terms of the GNU General Public License version 3, as published by the |
40 | @@ -13,8 +13,22 @@ |
41 | # You should have received a copy of the GNU General Public License along with |
42 | # this program. If not, see <http://www.gnu.org/licenses/>. |
43 | |
44 | -from dput.changes import Changes, parse_changes_file |
45 | -from os import path |
46 | +import os |
47 | +import sys |
48 | +import yaml |
49 | + |
50 | +from dput.changes import parse_changes_file |
51 | + |
52 | +from ci_utils.data_store import DataStore |
53 | + |
54 | +auth = os.path.join(os.environ["HOME"], '.ubuntu-ci/auth_config.yaml') |
55 | +try: |
56 | + with open(auth) as fp: |
57 | + auth_config = yaml.safe_load(fp) |
58 | +except IOError, e: |
59 | + sys.exit("Missing authentication info. " + |
60 | + "Add 'auth_url', 'auth_user', 'auth_password', " + |
61 | + "and 'auth_tenant_name' to {}".format(auth)) |
62 | |
63 | |
64 | class ChangesProcessor: |
65 | @@ -22,17 +36,18 @@ |
66 | |
67 | def __init__(self, changes_filepath, files_to_upload_dir=None): |
68 | # Needs to be provided with the full path to the changes file. |
69 | - if not path.exists(changes_filepath): |
70 | + if not os.path.exists(changes_filepath): |
71 | raise IOError("%s not found" % changes_filepath) |
72 | self.changes_filepath = changes_filepath |
73 | |
74 | if files_to_upload_dir is not None: |
75 | - if not path.exists(files_to_upload_dir): |
76 | + if not os.path.exists(files_to_upload_dir): |
77 | raise IOError("%s not found" % files_to_upload_dir) |
78 | self.files_to_upload_dir = files_to_upload_dir |
79 | else: |
80 | - self.files_to_upload_dir = path.dirname(changes_filepath) |
81 | + self.files_to_upload_dir = os.path.dirname(changes_filepath) |
82 | self.files_to_upload = [] |
83 | + self.sourcepackage = '' |
84 | self.changes = None |
85 | |
86 | def validate(self, check_signature=False): |
87 | @@ -40,9 +55,9 @@ |
88 | try: |
89 | # We don't care if signatures fail for now |
90 | changes.validate(check_signature=check_signature) |
91 | - self.files_to_upload = [path.basename(file_) for file_ in |
92 | - changes.get_files()] |
93 | - self.files_to_upload.append(changes.get_filename()) |
94 | + self.files_to_upload = changes.get_files() |
95 | + self.files_to_upload.append(changes.get_changes_file()) |
96 | + self.sourcepackage = self.changes.get_package_name() |
97 | except IOError, exc: |
98 | # File not found. Aborting upload |
99 | raise exc |
100 | @@ -55,12 +70,14 @@ |
101 | except Exception, exc: |
102 | raise exc |
103 | |
104 | - def upload_files(self): |
105 | - for file_ in self.files_to_upload: |
106 | - # Do something |
107 | - pass |
108 | - |
109 | def process(self, check_signature=False): |
110 | self.parse() |
111 | self.validate(check_signature=check_signature) |
112 | - self.upload_files() |
113 | + return self.files_to_upload, self.sourcepackage |
114 | + |
115 | + |
116 | +def upload_files(file, ticket_id): |
117 | + ds = DataStore('ticket-system.{}'.format(ticket_id), auth_config) |
118 | + with open(file, 'r') as fp: |
119 | + location = ds.put_file(file, fp) |
120 | + return location |
121 | |
122 | === added file 'cli/ci_libs/status.py' |
123 | --- cli/ci_libs/status.py 1970-01-01 00:00:00 +0000 |
124 | +++ cli/ci_libs/status.py 2014-01-14 19:35:30 +0000 |
125 | @@ -0,0 +1,39 @@ |
126 | +# Ubuntu Continuous Integration Engine |
127 | +# Copyright 2014 Canonical Ltd. |
128 | +# |
129 | +# This program is free software: you can redistribute it and/or modify it under |
130 | +# the terms of the GNU General Public License version 3, as published by the |
131 | +# Free Software Foundation. |
132 | +# |
133 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
134 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
135 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
136 | +# General Public License for more details. |
137 | +# |
138 | +# You should have received a copy of the GNU General Public License along |
139 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
140 | + |
141 | +import sys |
142 | +import urllib2 |
143 | +from ci_libs.utils import CI_URL, TICKET_BASE, get |
144 | + |
145 | + |
146 | +def ticket_status(args): |
147 | + if args.ticket: |
148 | + url = CI_URL + TICKET_BASE + args.ticket |
149 | + try: |
150 | + data = get(url) |
151 | + except urllib2.HTTPError: |
152 | + sys.exit("Ticket number {} not found.".format(args.ticket)) |
153 | + else: |
154 | + url = CI_URL + TICKET_BASE |
155 | + data = get(url) |
156 | + if args.ticket: |
157 | + print("Ticket #{}, {} is {} {}".format(args.ticket, data[u'title'], |
158 | + data[u'current_workflow_step'], |
159 | + data[u'status'])) |
160 | + else: |
161 | + for d in data['objects']: |
162 | + print("Ticket #{}, {} is {} {}".format( |
163 | + d['id'], d['title'], d['current_workflow_step'], |
164 | + d['status'])) |
165 | |
166 | === added file 'cli/ci_libs/ticket.py' |
167 | --- cli/ci_libs/ticket.py 1970-01-01 00:00:00 +0000 |
168 | +++ cli/ci_libs/ticket.py 2014-01-14 19:35:30 +0000 |
169 | @@ -0,0 +1,109 @@ |
170 | +# Ubuntu Continuous Integration Engine |
171 | +# Copyright 2014 Canonical Ltd. |
172 | +# |
173 | +# This program is free software: you can redistribute it and/or modify it under |
174 | +# the terms of the GNU General Public License version 3, as published by the |
175 | +# Free Software Foundation. |
176 | +# |
177 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
178 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
179 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
180 | +# General Public License for more details. |
181 | +# |
182 | +# You should have received a copy of the GNU General Public License along |
183 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
184 | + |
185 | +import os |
186 | + |
187 | +from ci_libs.file_handler import ChangesProcessor, upload_files |
188 | +from ci_libs.utils import (CI_URL, API_URL, TICKET_BASE, SUBTICKET_BASE, |
189 | + ARTIFACT_BASE, SPU_BASE, parse_id, |
190 | + get_sourcepackage_uri, post, patch) |
191 | + |
192 | + |
193 | +class Ticket(): |
194 | + |
195 | + def __init__(self): |
196 | + self.ticket_id = '' |
197 | + self.ticket_uri = '' |
198 | + self.uploads = [] |
199 | + self.subticket_uri = '' |
200 | + self.spu_uri = '' |
201 | + self.sourcepackage = '' |
202 | + self.sourcepackage_uri = '' |
203 | + self.files = '' |
204 | + |
205 | + def _parse_changes(self, args): |
206 | + changes_filepath = args.source |
207 | + changes = ChangesProcessor(changes_filepath=changes_filepath) |
208 | + self.files, self.sourcepackage = changes.process() |
209 | + |
210 | + def _process(self, args): |
211 | + self._create_spu(args) |
212 | + self._create_subticket(args) |
213 | + for file_path in self.files: |
214 | + file_name = os.path.basename(file_path) |
215 | + location = upload_files(file_name, self.ticket_id) |
216 | + self._create_artifact(file_name, location) |
217 | + |
218 | + def _create_ticket(self, args): |
219 | + data = { |
220 | + "title": args.title, |
221 | + "owner": args.owner, |
222 | + "description": args.description, |
223 | + "bug_id": args.bug, |
224 | + "added_binaries": args.add, |
225 | + "removed_binaries": args.remove, |
226 | + } |
227 | + location = post(CI_URL + TICKET_BASE, data=data) |
228 | + self.ticket_id = parse_id(location) |
229 | + self.ticket_uri = TICKET_BASE + self.ticket_id + '/' |
230 | + |
231 | + def _create_spu(self, args): |
232 | + self.sourcepackage_uri = get_sourcepackage_uri(self.sourcepackage) |
233 | + data = { |
234 | + "sourcepackage": self.sourcepackage_uri, |
235 | + "version": args.version, |
236 | + } |
237 | + location = post(CI_URL + SPU_BASE, data=data) |
238 | + self.spu_id = parse_id(location) |
239 | + self.spu_uri = SPU_BASE + self.spu_id + '/' |
240 | + |
241 | + def _create_subticket(self, args): |
242 | + data = { |
243 | + "ticket": self.ticket_uri, |
244 | + "assignee": args.owner, |
245 | + "source_package_upload": self.spu_uri, |
246 | + } |
247 | + location = post(CI_URL + SUBTICKET_BASE, data=data) |
248 | + self.subticket_id = parse_id(location) |
249 | + self.subticket_uri = SUBTICKET_BASE + self.subticket_id + '/' |
250 | + |
251 | + def _create_artifact(self, file, location): |
252 | + data = { |
253 | + "type": "SPU", |
254 | + "reference": location, |
255 | + "name": file, |
256 | + "subticket": self.subticket_uri, |
257 | + } |
258 | + post(CI_URL + ARTIFACT_BASE, data=data) |
259 | + |
260 | + def _update_ticket(self): |
261 | + """ |
262 | + After a ticket has been created, it needs to be updated to have a |
263 | + status of QUEUED in order to be processed. |
264 | + """ |
265 | + data = { |
266 | + "current_workflow_step": "100", |
267 | + "status": "110", |
268 | + } |
269 | + patch(CI_URL + API_URL + 'updatestatus/' + self.ticket_id + '/', |
270 | + data=data) |
271 | + print("You have successfully submitted a ticket to the Ubuntu CI " |
272 | + "Engine. Your ticket number is %s" % self.ticket_id) |
273 | + |
274 | + def add_new_ticket(self, args): |
275 | + self._create_ticket(args) |
276 | + self._parse_changes(args) |
277 | + self._process(args) |
278 | + self._update_ticket() |
279 | |
280 | === added file 'cli/ci_libs/utils.py' |
281 | --- cli/ci_libs/utils.py 1970-01-01 00:00:00 +0000 |
282 | +++ cli/ci_libs/utils.py 2014-01-14 19:35:30 +0000 |
283 | @@ -0,0 +1,74 @@ |
284 | +# Ubuntu Continuous Integration Engine |
285 | +# Copyright 2014 Canonical Ltd. |
286 | +# |
287 | +# This program is free software: you can redistribute it and/or modify it under |
288 | +# the terms of the GNU General Public License version 3, as published by the |
289 | +# Free Software Foundation. |
290 | +# |
291 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
292 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
293 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
294 | +# General Public License for more details. |
295 | +# |
296 | +# You should have received a copy of the GNU General Public License along |
297 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
298 | + |
299 | +import os |
300 | +import sys |
301 | +import yaml |
302 | +import json |
303 | +import urllib2 |
304 | + |
305 | +HEADERS = {'content-type': 'application/json'} |
306 | +API_URL = '/api/v1/' |
307 | +TICKET_BASE = API_URL + 'ticket/' |
308 | +SPU_BASE = API_URL + 'spu/' |
309 | +SOURCEPACKAGE_BASE = API_URL + 'sourcepackage/' |
310 | +ARTIFACT_BASE = API_URL + 'artifact/' |
311 | +SUBTICKET_BASE = API_URL + 'subticket/' |
312 | + |
313 | +config_file = os.path.join(os.environ["HOME"], '.ubuntu-ci/config.yaml') |
314 | +try: |
315 | + with open(config_file) as fp: |
316 | + config = yaml.safe_load(fp) |
317 | +except IOError, e: |
318 | + sys.exit("Missing config info. " + |
319 | + "Add 'ci_url' to {}".format(config_file)) |
320 | + |
321 | +CI_URL = config['ci_url'] |
322 | + |
323 | + |
324 | +def post(url, data, headers=HEADERS, patch=False): |
325 | + req = urllib2.Request(url=url, data=json.dumps(data), headers=HEADERS) |
326 | + f = urllib2.urlopen(req) |
327 | + return f.info().dict['location'] |
328 | + |
329 | + |
330 | +def patch(url, data, headers=HEADERS): |
331 | + req = urllib2.Request(url=url, data=json.dumps(data), headers=HEADERS) |
332 | + req.get_method = lambda: 'PATCH' |
333 | + urllib2.urlopen(req) |
334 | + |
335 | + |
336 | +def get(url): |
337 | + f = urllib2.urlopen(url) |
338 | + |
339 | + json_data = f.read() |
340 | + return json.loads(json_data) |
341 | + |
342 | + |
343 | +def parse_id(location): |
344 | + try: |
345 | + id = location.split('/')[6] |
346 | + int(id) |
347 | + except ValueError, exc: |
348 | + raise exc |
349 | + return id |
350 | + |
351 | + |
352 | +def get_sourcepackage_uri(sourcepackage): |
353 | + url = CI_URL + SOURCEPACKAGE_BASE + '?name__iexact=' + sourcepackage |
354 | + f = urllib2.urlopen(url) |
355 | + json_data = f.read() |
356 | + data = json.loads(json_data) |
357 | + return data['objects'][0]['resource_uri'] |
358 | |
359 | === modified file 'cli/setup.py' |
360 | --- cli/setup.py 2014-01-14 19:16:51 +0000 |
361 | +++ cli/setup.py 2014-01-14 19:35:30 +0000 |
362 | @@ -15,6 +15,8 @@ |
363 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
364 | |
365 | import os |
366 | +import subprocess |
367 | +import sys |
368 | |
369 | from setuptools import find_packages, setup |
370 | |
371 | @@ -24,9 +26,19 @@ |
372 | packages = find_packages(here) |
373 | |
374 | requires = [ |
375 | - 'chardet==2.2.1', |
376 | - 'dput==1.6', |
377 | -] |
378 | + 'chardet>=2.0.1', |
379 | + 'dput>=1.6', |
380 | + 'PyYAML==3.10', |
381 | +] |
382 | + |
383 | +test_requires = [ |
384 | + 'mock==1.0.1', |
385 | +] |
386 | + |
387 | +if len(sys.argv) > 1 and sys.argv[1] in ('test', 'develop'): |
388 | + # python-swiftclient has problems in its setup.py |
389 | + # this hacks around it: |
390 | + subprocess.check_call(['pip', 'install', 'python-swiftclient==1.8.0']) |
391 | |
392 | setup( |
393 | name='ci-engine-cli', |
394 | |
395 | === modified file 'cli/tests/__init__.py' |
396 | --- cli/tests/__init__.py 2014-01-08 18:57:01 +0000 |
397 | +++ cli/tests/__init__.py 2014-01-14 19:35:30 +0000 |
398 | @@ -1,5 +1,5 @@ |
399 | # Ubuntu Continuous Integration Engine |
400 | -# Copyright 2013 Canonical Ltd. |
401 | +# Copyright 2014 Canonical Ltd. |
402 | |
403 | # This program is free software: you can redistribute it and/or modify it |
404 | # under |
405 | |
406 | === renamed file 'cli/tests/test_changes_processor.py' => 'cli/tests/test_file_handler.py' |
407 | --- cli/tests/test_changes_processor.py 2014-01-08 13:27:56 +0000 |
408 | +++ cli/tests/test_file_handler.py 2014-01-14 19:35:30 +0000 |
409 | @@ -1,5 +1,6 @@ |
410 | +#!/usr/bin/env python |
411 | # Ubuntu Continuous Integration Engine |
412 | -# Copyright 2013 Canonical Ltd. |
413 | +# Copyright 2014 Canonical Ltd. |
414 | |
415 | # This program is free software: you can redistribute it and/or modify it |
416 | # under |
417 | @@ -14,10 +15,9 @@ |
418 | # You should have received a copy of the GNU General Public License along with |
419 | # this program. If not, see <http://www.gnu.org/licenses/>. |
420 | |
421 | -from os import listdir |
422 | -from os.path import basename, join |
423 | +from os.path import join |
424 | from unittest import TestCase |
425 | -from cli.changes_processor import ChangesProcessor |
426 | +from ci_libs.file_handler import ChangesProcessor |
427 | from dput.changes import Changes, ChangesFileException |
428 | |
429 | |
430 | @@ -86,9 +86,13 @@ |
431 | |
432 | # The three files (dsc, debian.tar.gz and orig) plus changes file |
433 | self.assertEqual(len(processor.files_to_upload), 4) |
434 | - self.assertTrue(self.dsc_file in processor.files_to_upload) |
435 | - self.assertTrue(self.debian_tar_gz_file in processor.files_to_upload) |
436 | - self.assertTrue(self.orig_file in processor.files_to_upload) |
437 | + self.assertTrue( |
438 | + processor.changes.get_dsc() in processor.files_to_upload) |
439 | + self.assertTrue( |
440 | + processor.changes.get_diff() in processor.files_to_upload) |
441 | + self.assertTrue( |
442 | + self._get_test_fullpath(self.orig_file) in |
443 | + processor.files_to_upload) |
444 | |
445 | def test_files_to_upload_dir_not_provided(self): |
446 | processor = ChangesProcessor( |
447 | |
448 | === added file 'cli/tests/test_get_ticket_status.py' |
449 | --- cli/tests/test_get_ticket_status.py 1970-01-01 00:00:00 +0000 |
450 | +++ cli/tests/test_get_ticket_status.py 2014-01-14 19:35:30 +0000 |
451 | @@ -0,0 +1,109 @@ |
452 | +#!/usr/bin/env python |
453 | +# Ubuntu Continuous Integration Engine |
454 | +# Copyright 2014 Canonical Ltd. |
455 | +# |
456 | +# This program is free software: you can redistribute it and/or modify it under |
457 | +# the terms of the GNU General Public License version 3, as published by the |
458 | +# Free Software Foundation. |
459 | +# |
460 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
461 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
462 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
463 | +# General Public License for more details. |
464 | +# |
465 | +# You should have received a copy of the GNU General Public License along |
466 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
467 | + |
468 | +import os |
469 | +import mock |
470 | +import json |
471 | +import imp |
472 | +import urllib2 |
473 | +import StringIO |
474 | + |
475 | +from unittest import TestCase |
476 | + |
477 | + |
478 | +class GetTicketStatus(TestCase): |
479 | + |
480 | + def setUp(self): |
481 | + self.cli = imp.load_source( |
482 | + "ubuntu-ci", |
483 | + os.path.join(os.path.dirname(__file__), "../ubuntu-ci") |
484 | + ) |
485 | + |
486 | + @mock.patch('urllib2.urlopen') |
487 | + def test_get_single_ticket_status(self, mock_urlopen): |
488 | + buff = StringIO.StringIO() |
489 | + data = { |
490 | + "current_workflow_step": 100, |
491 | + "status": 110, |
492 | + "title": "My cool feature", |
493 | + } |
494 | + resp = mock.Mock() |
495 | + resp.read.return_value = json.dumps(data) |
496 | + mock_urlopen.return_value = resp |
497 | + args = self.cli.parse_arguments(['status', '-t', '100']) |
498 | + with mock.patch('sys.stdout') as stdout: |
499 | + stdout.write = buff.write |
500 | + resp = args.func(args) |
501 | + mock_urlopen.assert_called_once() |
502 | + self.assertEqual("Ticket #100, My cool feature is 100 110\n", |
503 | + buff.getvalue()) |
504 | + |
505 | + @mock.patch('urllib2.urlopen') |
506 | + def test_get_all_ticket_status(self, mock_urlopen): |
507 | + buff = StringIO.StringIO() |
508 | + data = { |
509 | + "objects": [ |
510 | + { |
511 | + "id": 5, |
512 | + "current_workflow_step": 100, |
513 | + "status": 110, |
514 | + "title": "My cool feature", |
515 | + }, |
516 | + { |
517 | + "id": 6, |
518 | + "current_workflow_step": 100, |
519 | + "status": 110, |
520 | + "title": "Ubuntu bug fix", |
521 | + }, |
522 | + { |
523 | + "id": 7, |
524 | + "current_workflow_step": 100, |
525 | + "status": 110, |
526 | + "title": "Upstream bug fix", |
527 | + }, |
528 | + { |
529 | + "id": 9, |
530 | + "current_workflow_step": 100, |
531 | + "status": 110, |
532 | + "title": "Other new feature", |
533 | + }, |
534 | + ] |
535 | + } |
536 | + resp = mock.Mock() |
537 | + resp.read.return_value = json.dumps(data) |
538 | + mock_urlopen.return_value = resp |
539 | + args = self.cli.parse_arguments(['status']) |
540 | + with mock.patch('sys.stdout') as stdout: |
541 | + stdout.write = buff.write |
542 | + resp = args.func(args) |
543 | + mock_urlopen.assert_called_once() |
544 | + self.assertEqual( |
545 | + "Ticket #5, My cool feature is 100 110\n" + |
546 | + "Ticket #6, Ubuntu bug fix is 100 110\n" + |
547 | + "Ticket #7, Upstream bug fix is 100 110\n" + |
548 | + "Ticket #9, Other new feature is 100 110\n", |
549 | + buff.getvalue()) |
550 | + |
551 | + @mock.patch('urllib2.urlopen') |
552 | + def test_get_status_404_response(self, mock_urlopen): |
553 | + mock_urlopen.side_effect = urllib2.HTTPError("http://example.com", |
554 | + 404, "Not Found", "", |
555 | + None) |
556 | + args = self.cli.parse_arguments(['status', '-t', '99']) |
557 | + with self.assertRaises(SystemExit) as cm: |
558 | + args.func(args) |
559 | + mock_urlopen.assert_called_once() |
560 | + self.assertEqual("Ticket number 99 not found.", cm.exception.message) |
561 | |
562 | === added file 'cli/tests/test_ticket.py' |
563 | --- cli/tests/test_ticket.py 1970-01-01 00:00:00 +0000 |
564 | +++ cli/tests/test_ticket.py 2014-01-14 19:35:30 +0000 |
565 | @@ -0,0 +1,71 @@ |
566 | +#!/usr/bin/env python |
567 | +# Ubuntu Continuous Integration Engine |
568 | +# Copyright 2014 Canonical Ltd. |
569 | + |
570 | +# This program is free software: you can redistribute it and/or modify it |
571 | +# under |
572 | +# the terms of the GNU General Public License version 3, as published by the |
573 | +# Free Software Foundation. |
574 | + |
575 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
576 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
577 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
578 | +# General Public License for more details. |
579 | + |
580 | +# You should have received a copy of the GNU General Public License along with |
581 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
582 | + |
583 | +import os |
584 | +import mock |
585 | +import imp |
586 | + |
587 | +from unittest import TestCase |
588 | + |
589 | +from ci_libs import ticket |
590 | + |
591 | + |
592 | +class TicketTestCase(TestCase): |
593 | + |
594 | + def setUp(self): |
595 | + self.cli = imp.load_source( |
596 | + "ubuntu-ci", |
597 | + os.path.join(os.path.dirname(__file__), "../ubuntu-ci") |
598 | + ) |
599 | + self.changes = os.path.join(os.path.dirname(__file__), |
600 | + 'data/foobar_0.1-1_source.changes') |
601 | + self.args = ['create_ticket', '-t', 'New feature', '-b', '1234', '-o', |
602 | + 'someone@example.com', '-d', 'This is a cool new feature', |
603 | + '-r', 'baz', '--version', '0.1-1', '-s', |
604 | + self.changes] |
605 | + |
606 | + @mock.patch('ci_libs.ticket.post') |
607 | + def test_create_ticket(self, mock_post): |
608 | + new_ticket = ticket.Ticket() |
609 | + args = self.cli.parse_arguments(self.args) |
610 | + mock_post.return_value = 'http://www.example.com/api/v1/ticket/38/' |
611 | + new_ticket._create_ticket(args) |
612 | + |
613 | + @mock.patch('ci_libs.ticket.get_sourcepackage_uri') |
614 | + @mock.patch('ci_libs.ticket.post') |
615 | + def test_create_spu(self, mock_post, mock_sp_uri): |
616 | + new_ticket = ticket.Ticket() |
617 | + args = self.cli.parse_arguments(self.args) |
618 | + mock_sp_uri.return_value = '/api/v1/sourcepackage/5/' |
619 | + mock_post.return_value = 'http://www.example.com/api/v1/spu/38/' |
620 | + new_ticket._create_spu(args) |
621 | + |
622 | + @mock.patch('ci_libs.ticket.post') |
623 | + def test_create_artifact(self, mock_post): |
624 | + new_ticket = ticket.Ticket() |
625 | + file = 'foobar_source.changes' |
626 | + location = 'http://www.example.com/path/to/foobar_source.changes' |
627 | + mock_post.return_value = 'http://www.example.com/api/v1/artifact/38/' |
628 | + new_ticket._create_artifact(file, location) |
629 | + mock_post.assert_called_once() |
630 | + |
631 | + @mock.patch('ci_libs.ticket.post') |
632 | + def test_create_subticket(self, mock_post): |
633 | + new_ticket = ticket.Ticket() |
634 | + args = self.cli.parse_arguments(self.args) |
635 | + mock_post.return_value = 'http://www.example.com/api/v1/spu/38/' |
636 | + new_ticket._create_subticket(args) |
637 | |
638 | === added file 'cli/tests/test_utils.py' |
639 | --- cli/tests/test_utils.py 1970-01-01 00:00:00 +0000 |
640 | +++ cli/tests/test_utils.py 2014-01-14 19:35:30 +0000 |
641 | @@ -0,0 +1,48 @@ |
642 | +#!/usr/bin/env python |
643 | +# Ubuntu Continuous Integration Engine |
644 | +# Copyright 2014 Canonical Ltd. |
645 | + |
646 | +# This program is free software: you can redistribute it and/or modify it |
647 | +# under |
648 | +# the terms of the GNU General Public License version 3, as published by the |
649 | +# Free Software Foundation. |
650 | + |
651 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
652 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
653 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
654 | +# General Public License for more details. |
655 | + |
656 | +# You should have received a copy of the GNU General Public License along with |
657 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
658 | + |
659 | +import json |
660 | +import mock |
661 | + |
662 | +import unittest |
663 | + |
664 | +from ci_libs import utils |
665 | + |
666 | + |
667 | +class UtilsTestCase(unittest.TestCase): |
668 | + def test_parse_id(self): |
669 | + location = 'http://www.example.com/api/v1/ticket/5/' |
670 | + id = utils.parse_id(location) |
671 | + self.assertEqual(id, '5') |
672 | + |
673 | + def test_parse_id_invalid_response(self): |
674 | + location = 'http://www.example.com/api/v1/ticket/' |
675 | + self.assertRaises(ValueError, utils.parse_id, location) |
676 | + |
677 | + def test_parse_id_not_int(self): |
678 | + location = 'http://www.example.com/api/v1/ticket/abc/123/' |
679 | + self.assertRaises(ValueError, utils.parse_id, location) |
680 | + |
681 | + @mock.patch('urllib2.urlopen') |
682 | + def test_get_sourcepackage_uri(self, mock_urlopen): |
683 | + data = {"objects": [{"resource_uri": "/api/v1/sourcepackage/4/"}]} |
684 | + resp = mock.Mock() |
685 | + resp.read.return_value = json.dumps(data) |
686 | + mock_urlopen.return_value = resp |
687 | + uri = utils.get_sourcepackage_uri('foobar') |
688 | + mock_urlopen.assert_called_once() |
689 | + self.assertEquals(uri, '/api/v1/sourcepackage/4/') |
690 | |
691 | === added file 'cli/ubuntu-ci' |
692 | --- cli/ubuntu-ci 1970-01-01 00:00:00 +0000 |
693 | +++ cli/ubuntu-ci 2014-01-14 19:35:30 +0000 |
694 | @@ -0,0 +1,69 @@ |
695 | +#!/usr/bin/env python |
696 | +# Ubuntu Continuous Integration Engine |
697 | +# Copyright 2014 Canonical Ltd. |
698 | +# |
699 | +# This program is free software: you can redistribute it and/or modify it under |
700 | +# the terms of the GNU General Public License version 3, as published by the |
701 | +# Free Software Foundation. |
702 | +# |
703 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
704 | +# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, |
705 | +# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
706 | +# General Public License for more details. |
707 | +# |
708 | +# You should have received a copy of the GNU General Public License along |
709 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
710 | + |
711 | +import sys |
712 | +import argparse |
713 | +import logging |
714 | + |
715 | +from ci_libs import status, ticket |
716 | + |
717 | +logging.basicConfig(level=logging.INFO) |
718 | +log = logging.getLogger() |
719 | +log.name = 'ubuntu-ci' |
720 | + |
721 | + |
722 | +def new_ticket(args): |
723 | + new_ticket = ticket.Ticket() |
724 | + new_ticket.add_new_ticket(args) |
725 | + |
726 | +def parse_arguments(args=None): |
727 | + parser = argparse.ArgumentParser() |
728 | + subparsers = parser.add_subparsers(title='actions', help='commands') |
729 | + ticket_parser = subparsers.add_parser('create_ticket', |
730 | + help='Create a new ticket') |
731 | + ticket_parser.add_argument('-t', '--title', |
732 | + help='Ticket title') |
733 | + ticket_parser.add_argument('-d', '--description', |
734 | + help='Ticket description') |
735 | + ticket_parser.add_argument('-b', '--bug', |
736 | + help='Related bug number') |
737 | + ticket_parser.add_argument('-o', '--owner', |
738 | + help='Email address of the ticket owner') |
739 | + ticket_parser.add_argument('-a', '--add', |
740 | + help='Binaries to be added to the install') |
741 | + ticket_parser.add_argument('-r', '--remove', |
742 | + help='Binaries to be removed from the install') |
743 | + ticket_parser.add_argument('--version', |
744 | + help='Version number of new package') |
745 | + ticket_parser.add_argument('-s', '--source', |
746 | + help='full path to source.changes file') |
747 | + ticket_parser.set_defaults(func=new_ticket) |
748 | + status_parser = subparsers.add_parser('status', |
749 | + help='Get ticket status. Use no ' |
750 | + 'flags for all tickets') |
751 | + status_parser.add_argument('-t', '--ticket', |
752 | + help='Ticket to display status of. Leave off ' |
753 | + 'for all tickets') |
754 | + status_parser.set_defaults(func=status.ticket_status) |
755 | + return parser.parse_args(args) |
756 | + |
757 | + |
758 | +def main(): |
759 | + args = parse_arguments() |
760 | + args.func(args) |
761 | + |
762 | +if __name__ == "__main__": |
763 | + main() |
L274 shouldn't this use API_URL?
+1.