Merge lp:~cjohnston/ubuntu-ci-services-itself/cli-stuff into lp:ubuntu-ci-services-itself

Proposed by Chris Johnston
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
Reviewer Review Type Date Requested Status
Chris Johnston (community) Approve
Andy Doan (community) Approve
Joe Talbott (community) Approve
Review via email: mp+200840@code.launchpad.net

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.

To post a comment you must log in.
Revision history for this message
Joe Talbott (joetalbott) wrote :

L274 shouldn't this use API_URL?

+1.

review: Approve
Revision history for this message
Andy Doan (doanac) wrote :

20 --- cli/cli/changes_processor.py 2014-01-08 13:27:56 +0000
21 +++ cli/ci_libs/file_handler.py 2014-01-10 15:39:09 +0000
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.ticket_status and there are _post/_patch in ticket.py. Not sure the where the code is going long-term, but it might be worth moving those to your utils module and have utils.[get|post|patch].

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/test_get_ticket_status.py: you should add a test for a ticket that doesn't exist. I think your code is currently just going to throw an exception. So fixing the code and adding a test could ensure it handles the issue gracefully.

Revision history for this message
Ursula Junque (ursinha) wrote :

You might want to rename test_changes_processor.py to test_file_handler.py as well, but besides that (and Andy's comments), looks good to me.

Revision history for this message
Chris Johnston (cjohnston) :
review: Needs Resubmitting
Revision history for this message
Andy Doan (doanac) wrote :

we need a better solution long-term for these required config files. but this is good enough for now.

review: Approve
Revision history for this message
Chris Johnston (cjohnston) :
review: Approve
Revision history for this message
Chris Johnston (cjohnston) wrote :
Download full text (30.1 KiB)

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.LGdE2Upwbu/bin/python
Installing distribute.............................................................................................................................................................................................done.
Installing pip...............done.
== Testing ci-utils ....
running develop
running egg_info
creating ci_utils.egg-info
writing requirements to ci_utils.egg-info/requires.txt
writing ci_utils.egg-info/PKG-INFO
writing top-level names to ci_utils.egg-info/top_level.txt
writing dependency_links to ci_utils.egg-info/dependency_links.txt
writing manifest file 'ci_utils.egg-info/SOURCES.txt'
reading manifest file 'ci_utils.egg-info/SOURCES.txt'
writing manifest file 'ci_utils.egg-info/SOURCES.txt'
running build_ext
Creating /tmp/tmp.LGdE2Upwbu/lib/python2.7/site-packages/ci-utils.egg-link (link to .)
Adding ci-utils 0.1 to easy-install.pth file

Installed /tmp/tarmac/branch.gamSyP/ci-utils
Processing dependencies for ci-utils==0.1
Searching for testtools
Reading http://pypi.python.org/simple/testtools/
Best match: testtools 0.9.34
Downloading https://pypi.python.org/packages/source/t/testtools/testtools-0.9.34.tar.gz#md5=51d37e7376a70cee40cf17b44889fc88
Processing testtools-0.9.34.tar.gz
Running testtools-0.9.34/setup.py -q bdist_egg --dist-dir /tmp/easy_install-JKiMLI/testtools-0.9.34/egg-dist-tmp-aaSFFd
Adding testtools 0.9.34 to easy-install.pth file

Installed /tmp/tmp.LGdE2Upwbu/lib/python2.7/site-packages/testtools-0.9.34-py2.7.egg
Searching for restish==0.12.1
Reading http://pypi.python.org/simple/restish/
Best match: restish 0.12.1
Downloading https://pypi.python.org/packages/source/r/restish/restish-0.12.1.tar.gz#md5=c29e0b755c44c21659de8e463093ea47
Processing restish-0.12.1.tar.gz
Running restish-0.12.1/setup.py -q bdist_egg --dist-dir /tmp/easy_install-HNBei3/restish-0.12.1/egg-dist-tmp-8KaZsE
Adding restish 0.12.1 to easy-install.pth file

Installed /tmp/tmp.LGdE2Upwbu/lib/python2.7/site-packages/restish-0.12.1-py2.7.egg
Searching for python-subunit
Reading http://pypi.python.org/simple/python-subunit/
Reading http://launchpad.net/subunit
Best match: python-subunit 0.0.16
Downloading https://pypi.python.org/packages/source/p/python-subunit/python-subunit-0.0.16.tar.gz#md5=c0ec919f8a1051de4ad89000f95324aa
Processing python-subunit-0.0.16.tar.gz
Running python-subunit-0.0.16/setup.py -q bdist_egg --dist-dir /tmp/easy_install-nv_Wtk/python-subunit-0.0.16/egg-dist-tmp-wYgO3q
Adding python-subunit 0.0.16 to easy-install.pth file
Installing subunit-tags script to /tmp/tmp.LGdE2Upwbu/bin
Installing subunit-notify script to /tmp/tmp.LGdE2Upwbu/bin
Installing subunit-1to2 script to /tmp/tmp.LGdE2Upwbu/bin
Installing tap2subunit script to /tmp/tmp.LGdE2Upwbu/bin
Installing subunit2gtk script to /tmp/tmp.LGdE2Upwbu/bin
Installing subunit-ls script to /tmp/tmp.LGdE2Upwbu/bin
Installing subunit-stats script to /tmp/tmp.LGdE2Upwbu/bin
Installing subunit2pyunit script to /tmp/tmp.LGdE2Upwbu/bin
Installing subunit-filter script to...

Revision history for this message
Vincent Ladeuil (vila) wrote :

> we need a better solution long-term for these required config files. but this
> is good enough for now.

lp:uci-config ?

Revision history for this message
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.

Revision history for this message
Chris Johnston (cjohnston) :
review: Needs Resubmitting
Revision history for this message
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

review: Approve
Revision history for this message
Chris Johnston (cjohnston) wrote :
Download full text (31.4 KiB)

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.UZDuboVgh5/bin/python
Installing distribute.............................................................................................................................................................................................done.
Installing pip...............done.
== Testing ci-utils ....
running develop
running egg_info
creating ci_utils.egg-info
writing requirements to ci_utils.egg-info/requires.txt
writing ci_utils.egg-info/PKG-INFO
writing top-level names to ci_utils.egg-info/top_level.txt
writing dependency_links to ci_utils.egg-info/dependency_links.txt
writing manifest file 'ci_utils.egg-info/SOURCES.txt'
reading manifest file 'ci_utils.egg-info/SOURCES.txt'
writing manifest file 'ci_utils.egg-info/SOURCES.txt'
running build_ext
Creating /tmp/tmp.UZDuboVgh5/lib/python2.7/site-packages/ci-utils.egg-link (link to .)
Adding ci-utils 0.1 to easy-install.pth file

Installed /tmp/tarmac/branch.P1YFA7/ci-utils
Processing dependencies for ci-utils==0.1
Searching for testtools
Reading http://pypi.python.org/simple/testtools/
Best match: testtools 0.9.34
Downloading https://pypi.python.org/packages/source/t/testtools/testtools-0.9.34.tar.gz#md5=51d37e7376a70cee40cf17b44889fc88
Processing testtools-0.9.34.tar.gz
Running testtools-0.9.34/setup.py -q bdist_egg --dist-dir /tmp/easy_install-hOCEAC/testtools-0.9.34/egg-dist-tmp-CHqHOP
Adding testtools 0.9.34 to easy-install.pth file

Installed /tmp/tmp.UZDuboVgh5/lib/python2.7/site-packages/testtools-0.9.34-py2.7.egg
Searching for restish==0.12.1
Reading http://pypi.python.org/simple/restish/
Best match: restish 0.12.1
Downloading https://pypi.python.org/packages/source/r/restish/restish-0.12.1.tar.gz#md5=c29e0b755c44c21659de8e463093ea47
Processing restish-0.12.1.tar.gz
Running restish-0.12.1/setup.py -q bdist_egg --dist-dir /tmp/easy_install-r0NdpI/restish-0.12.1/egg-dist-tmp-QRnD3d
Adding restish 0.12.1 to easy-install.pth file

Installed /tmp/tmp.UZDuboVgh5/lib/python2.7/site-packages/restish-0.12.1-py2.7.egg
Searching for python-subunit
Reading http://pypi.python.org/simple/python-subunit/
Reading http://launchpad.net/subunit
Best match: python-subunit 0.0.16
Downloading https://pypi.python.org/packages/source/p/python-subunit/python-subunit-0.0.16.tar.gz#md5=c0ec919f8a1051de4ad89000f95324aa
Processing python-subunit-0.0.16.tar.gz
Running python-subunit-0.0.16/setup.py -q bdist_egg --dist-dir /tmp/easy_install-VTu0Tq/python-subunit-0.0.16/egg-dist-tmp-RKtYf1
Adding python-subunit 0.0.16 to easy-install.pth file
Installing subunit-filter script to /tmp/tmp.UZDuboVgh5/bin
Installing subunit-2to1 script to /tmp/tmp.UZDuboVgh5/bin
Installing subunit-ls script to /tmp/tmp.UZDuboVgh5/bin
Installing tap2subunit script to /tmp/tmp.UZDuboVgh5/bin
Installing subunit-1to2 script to /tmp/tmp.UZDuboVgh5/bin
Installing subunit2pyunit script to /tmp/tmp.UZDuboVgh5/bin
Installing subunit2junitxml script to /tmp/tmp.UZDuboVgh5/bin
Installing subunit-tags script to /tmp/tmp.UZDuboVgh5/bin
Installing subunit-notify scrip...

Revision history for this message
Chris Johnston (cjohnston) wrote :

Attempt to merge into lp:ubuntu-ci-services-itself failed due to conflicts:

text conflict in cli/setup.py

Revision history for this message
Chris Johnston (cjohnston) :
review: Approve
Revision history for this message
Chris Johnston (cjohnston) wrote :
Download full text (32.9 KiB)

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.7S1uJS61m8/bin/python
Installing distribute.............................................................................................................................................................................................done.
Installing pip...............done.
== Testing ci-utils ....
running develop
running egg_info
creating ci_utils.egg-info
writing requirements to ci_utils.egg-info/requires.txt
writing ci_utils.egg-info/PKG-INFO
writing top-level names to ci_utils.egg-info/top_level.txt
writing dependency_links to ci_utils.egg-info/dependency_links.txt
writing manifest file 'ci_utils.egg-info/SOURCES.txt'
reading manifest file 'ci_utils.egg-info/SOURCES.txt'
writing manifest file 'ci_utils.egg-info/SOURCES.txt'
running build_ext
Creating /tmp/tmp.7S1uJS61m8/lib/python2.7/site-packages/ci-utils.egg-link (link to .)
Adding ci-utils 0.1 to easy-install.pth file

Installed /tmp/tarmac/branch.QUaOVb/ci-utils
Processing dependencies for ci-utils==0.1
Searching for testtools
Reading http://pypi.python.org/simple/testtools/
Best match: testtools 0.9.34
Downloading https://pypi.python.org/packages/source/t/testtools/testtools-0.9.34.tar.gz#md5=51d37e7376a70cee40cf17b44889fc88
Processing testtools-0.9.34.tar.gz
Running testtools-0.9.34/setup.py -q bdist_egg --dist-dir /tmp/easy_install-FSuYqU/testtools-0.9.34/egg-dist-tmp-LazfIA
Adding testtools 0.9.34 to easy-install.pth file

Installed /tmp/tmp.7S1uJS61m8/lib/python2.7/site-packages/testtools-0.9.34-py2.7.egg
Searching for restish==0.12.1
Reading http://pypi.python.org/simple/restish/
Best match: restish 0.12.1
Downloading https://pypi.python.org/packages/source/r/restish/restish-0.12.1.tar.gz#md5=c29e0b755c44c21659de8e463093ea47
Processing restish-0.12.1.tar.gz
Running restish-0.12.1/setup.py -q bdist_egg --dist-dir /tmp/easy_install-YQWlPk/restish-0.12.1/egg-dist-tmp-rXiEOR
Adding restish 0.12.1 to easy-install.pth file

Installed /tmp/tmp.7S1uJS61m8/lib/python2.7/site-packages/restish-0.12.1-py2.7.egg
Searching for python-subunit
Reading http://pypi.python.org/simple/python-subunit/
Reading http://launchpad.net/subunit
Best match: python-subunit 0.0.16
Downloading https://pypi.python.org/packages/source/p/python-subunit/python-subunit-0.0.16.tar.gz#md5=c0ec919f8a1051de4ad89000f95324aa
Processing python-subunit-0.0.16.tar.gz
Running python-subunit-0.0.16/setup.py -q bdist_egg --dist-dir /tmp/easy_install-pFkVuU/python-subunit-0.0.16/egg-dist-tmp-oMPxQr
Adding python-subunit 0.0.16 to easy-install.pth file
Installing subunit-filter script to /tmp/tmp.7S1uJS61m8/bin
Installing subunit-2to1 script to /tmp/tmp.7S1uJS61m8/bin
Installing subunit-ls script to /tmp/tmp.7S1uJS61m8/bin
Installing tap2subunit script to /tmp/tmp.7S1uJS61m8/bin
Installing subunit-1to2 script to /tmp/tmp.7S1uJS61m8/bin
Installing subunit2pyunit script to /tmp/tmp.7S1uJS61m8/bin
Installing subunit2junitxml script to /tmp/tmp.7S1uJS61m8/bin
Installing subunit-tags script to /tmp/tmp.7S1uJS61m8/bin
Installing subunit-notify scrip...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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()

Subscribers

People subscribed via source and target branches