Merge lp:~pfalcon/linaro-android-build-tools/cmdline-client into lp:linaro-android-build-tools

Proposed by Paul Sokolovsky
Status: Merged
Merged at revision: 671
Proposed branch: lp:~pfalcon/linaro-android-build-tools/cmdline-client
Merge into: lp:linaro-android-build-tools
Diff against target: 271 lines (+261/-0)
2 files modified
utils/cmdline-client/README (+62/-0)
utils/cmdline-client/android-build-client (+199/-0)
To merge this branch: bzr merge lp:~pfalcon/linaro-android-build-tools/cmdline-client
Reviewer Review Type Date Requested Status
Paul Sokolovsky Approve
Milo Casagrande (community) Needs Fixing
Review via email: mp+182969@code.launchpad.net

Description of the change

android-build command line client. This implements https://cards.linaro.org/browse/LAVA-540 . See README for usage instructions - worth given a try to it, if possible.

To post a comment you must log in.
Revision history for this message
Milo Casagrande (milo) wrote :
Download full text (4.5 KiB)

Hey Paul,

following my comments, there's some typos that need fixing and a couple of suggestion/thoughts.
Marking as fix needed, but once you cleaned the typos it should be ready to go.

+Basic android-build.linaro.org command-line client
+==================================================
+
+This tool provides proof-of-concept command-line client for Linaro Android
+Build Jenkins master (android-build.linaro.org), as an alternative for
+existing Web UI interface. One particular feature the command-line client
+has and Web UI lacks is support for creating completely private builds (such
+builds are not availabel via Web UI at all). At the same time, this tool is
+so far in the proof of concept stage and provides only basic job management
+actions (namely, create a job and schedule its build). If you find this tools

s/this tools/this tool

+useful, please share you comments and suggestion using this bugtracker:

s/you comments/your comments

+
+https://bugs.launchpad.net/linaro-android-infrastructure
+
+to help its improvement and evolution.
+
+
+Quick start
+-----------
+
+1. You should have access to Jenkins at:
+
+http://android-build.linaro.org/jenkins/
+
+(Generally available for Linaro Android team members).
+
+2. Download android-build-client tool. As it is written to depend only

s/to depend/it depends

+on Python standard library, an easy way to get it is to download seperate
+file via Launchpad BZR viewer:
+
+wget <fill in link once branch lands>
+chmod +x android-build-client
+
+3. Look up your Jenkins API token by visiting
+https://android-build.linaro.org/jenkins/me/configure and clicking
+"Show API Token..." button.
+
+4. Run:
+
+./android-build-client authorize
+
+Enter you Jenkins username and API token. Note that this will cache these

s/you Jenkins/your Jenkins

+credentials in your home dir, so use this only on your personal well-protected
+workstation. The alternative is to use --user switch and input API key
+interactively.
+
+5. To create a new job, prepare an Android job config (see
+https://wiki.linaro.org/Platform/Android/LinaroAndroidBuildService for more
+info) in a file. Run:
+
+./android-build-client create <job_name> <job_config_file>
+
+Note that job name should conform to Android Build Service naming
+converntions, see documentation link above for more info. To create a job
+private for particular group, pass --private=<group> switch to create command.

s/job private/private job

+
+6. To schedule a build, run:
+
+./android-build-client build <job_name>
+
+
+Run ./android-build-client --help to see all options.

I tried it, but it does not show me all the available "commands", just the normale options.

It's early in the development stage, but it might be worth to take a look at how lava-tool is handling the commands definition.

There's a lot more code to write to achieve that, but it is a well structured pattern. Just food for thoughts, and something to keep in mind.

=== added file 'utils/cmdline-client/android-build-client'
--- utils/cmdline-client/android-build-client 1970-01-01 00:00:00 +0000
+++ utils/cmdline-client/android-build-client 2013-08-29 17:39:17 +0000
@@ -0,0 +1,199 @@
+#!/usr/bin/env py...

Read more...

review: Needs Fixing
Revision history for this message
Paul Sokolovsky (pfalcon) wrote :
Download full text (6.5 KiB)

Hello Milo,

Thanks for comments!

On Fri, 30 Aug 2013 14:31:38 -0000
Milo Casagrande <email address hidden> wrote:

> Review: Needs Fixing
>
> Hey Paul,
>
> following my comments, there's some typos that need fixing and a
> couple of suggestion/thoughts. Marking as fix needed, but once you
> cleaned the typos it should be ready to go.
>
> +Basic android-build.linaro.org command-line client
> +==================================================
> +
> +This tool provides proof-of-concept command-line client for Linaro
> Android +Build Jenkins master (android-build.linaro.org), as an
> alternative for +existing Web UI interface. One particular feature
> the command-line client +has and Web UI lacks is support for creating
> completely private builds (such +builds are not availabel via Web UI
> at all). At the same time, this tool is +so far in the proof of
> concept stage and provides only basic job management +actions
> (namely, create a job and schedule its build). If you find this tools
>
> s/this tools/this tool

fixed

>
> +useful, please share you comments and suggestion using this
> bugtracker:
>
> s/you comments/your comments

fixed

>
> +
> +https://bugs.launchpad.net/linaro-android-infrastructure
> +
> +to help its improvement and evolution.
> +
> +
> +Quick start
> +-----------
> +
> +1. You should have access to Jenkins at:
> +
> +http://android-build.linaro.org/jenkins/
> +
> +(Generally available for Linaro Android team members).
> +
> +2. Download android-build-client tool. As it is written to depend
> only
>
> s/to depend/it depends

You probably mean replacing the whole starting phrase? Nope, that
loses meaning. It not just randomly depends only on stdlib, it's
purposedly written to ;-).

>
> +on Python standard library, an easy way to get it is to download
> seperate +file via Launchpad BZR viewer:
> +
> +wget <fill in link once branch lands>
> +chmod +x android-build-client
> +
> +3. Look up your Jenkins API token by visiting
> +https://android-build.linaro.org/jenkins/me/configure and clicking
> +"Show API Token..." button.
> +
> +4. Run:
> +
> +./android-build-client authorize
> +
> +Enter you Jenkins username and API token. Note that this will cache
> these
>
> s/you Jenkins/your Jenkins

fixed

>
> +credentials in your home dir, so use this only on your personal
> well-protected +workstation. The alternative is to use --user switch
> and input API key +interactively.
> +
> +5. To create a new job, prepare an Android job config (see
> +https://wiki.linaro.org/Platform/Android/LinaroAndroidBuildService
> for more +info) in a file. Run:
> +
> +./android-build-client create <job_name> <job_config_file>
> +
> +Note that job name should conform to Android Build Service naming
> +converntions, see documentation link above for more info. To create
> a job +private for particular group, pass --private=<group> switch to
> create command.
>
> s/job private/private job

Nope, again changes meaning ;-). "job private for particular group" ==
"job which is private for particular group"

>
> +
> +6. To schedule a build, run:
> +
> +./android-build-client build <job_name>
> +
> +
> +Run ./android-build-client --help to s...

Read more...

663. By Paul Sokolovsky

Fixed typos, thanks Milo for review.

664. By Paul Sokolovsky

Make --help show which commands are available.

665. By Paul Sokolovsky

Print error messages to stderr.

666. By Paul Sokolovsky

Fix typo.

Revision history for this message
Paul Sokolovsky (pfalcon) wrote :

<pfalcon> Gwaihir: thanks for review on https://code.launchpad.net/~pfalcon/linaro-android-build-tools/cmdline-client/+merge/182969 . I acted on most comments. The only serious one IMHO is licensing, but as I replied, it's the license of linaro-android-build-tools ;-I. So, unless you strongly suggest to use diff. lic. just for that script, it could be merged
<Gwaihir> pfalcon, sure, lets not block on that, it is an open source license after all, non GPL compatible, but OSI approved... will raise this with Alan and see if we need to use just one license for all our projects or if it is fine to use others as long as they are open source

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'utils/cmdline-client'
2=== added file 'utils/cmdline-client/README'
3--- utils/cmdline-client/README 1970-01-01 00:00:00 +0000
4+++ utils/cmdline-client/README 2013-09-03 10:45:47 +0000
5@@ -0,0 +1,62 @@
6+Basic android-build.linaro.org command-line client
7+==================================================
8+
9+This tool provides proof-of-concept command-line client for Linaro Android
10+Build Jenkins master (android-build.linaro.org), as an alternative for
11+existing Web UI interface. One particular feature the command-line client
12+has and Web UI lacks is support for creating completely private builds (such
13+builds are not availabel via Web UI at all). At the same time, this tool is
14+so far in the proof of concept stage and provides only basic job management
15+actions (namely, create a job and schedule its build). If you find this tool
16+useful, please share your comments and suggestion using this bugtracker:
17+
18+https://bugs.launchpad.net/linaro-android-infrastructure
19+
20+to help its improvement and evolution.
21+
22+
23+Quick start
24+-----------
25+
26+1. You should have access to Jenkins at:
27+
28+http://android-build.linaro.org/jenkins/
29+
30+(Generally available for Linaro Android team members).
31+
32+2. Download android-build-client tool. As it is written to depend only
33+on Python standard library, an easy way to get it is to download seperate
34+file via Launchpad BZR viewer:
35+
36+wget <fill in link once branch lands>
37+chmod +x android-build-client
38+
39+3. Look up your Jenkins API token by visiting
40+https://android-build.linaro.org/jenkins/me/configure and clicking
41+"Show API Token..." button.
42+
43+4. Run:
44+
45+./android-build-client authorize
46+
47+Enter yous Jenkins username and API token. Note that this will cache these
48+credentials in your home dir, so use this only on your personal well-protected
49+workstation. The alternative is to use --user switch and input API key
50+interactively.
51+
52+5. To create a new job, prepare an Android job config (see
53+https://wiki.linaro.org/Platform/Android/LinaroAndroidBuildService for more
54+info) in a file. Run:
55+
56+./android-build-client create <job_name> <job_config_file>
57+
58+Note that job name should conform to Android Build Service naming
59+converntions, see documentation link above for more info. To create a job
60+private for a particular group, pass --private=<group> switch to create command.
61+
62+6. To schedule a build, run:
63+
64+./android-build-client build <job_name>
65+
66+
67+Run ./android-build-client --help to see all options.
68
69=== added file 'utils/cmdline-client/android-build-client'
70--- utils/cmdline-client/android-build-client 1970-01-01 00:00:00 +0000
71+++ utils/cmdline-client/android-build-client 2013-09-03 10:45:47 +0000
72@@ -0,0 +1,199 @@
73+#!/usr/bin/env python
74+###############################################################################
75+# Copyright (c) 2013 Linaro
76+# All rights reserved. This program and the accompanying materials
77+# are made available under the terms of the Eclipse Public License v1.0
78+# which accompanies this distribution, and is available at
79+# http://www.eclipse.org/legal/epl-v10.html
80+###############################################################################
81+
82+import base64
83+import json
84+import os
85+import sys
86+import urllib2
87+import optparse
88+import getpass
89+from xml.dom import minidom
90+
91+PRIVATE_ACL = """\
92+ <hudson.security.AuthorizationMatrixProperty>
93+ <permission>hudson.model.Item.Build:%(group)s</permission>
94+ <permission>hudson.model.Item.Configure:%(group)s</permission>
95+ <permission>hudson.model.Item.Read:%(group)s</permission>
96+ <permission>hudson.model.Item.Cancel:%(group)s</permission>
97+ <permission>hudson.model.Item.Discover:%(group)s</permission>
98+ </hudson.security.AuthorizationMatrixProperty>
99+"""
100+
101+
102+class Jenkins(object):
103+
104+ def __init__(self, base_url, username, passwd):
105+ self.base = base_url
106+ self.auth_headers = {
107+ 'Authorization': 'Basic %s' % (
108+ base64.b64encode('%s:%s' % (username, passwd)))}
109+ self.csrf = None
110+
111+ def jenkins_rest(self, jenkins_path, data=None, extra_headers=None):
112+ """Make an authenticated request to jenkins.
113+
114+ @param jenkins_path: The path on the Jenkins instance to make the
115+ request to.
116+ @param data: Data to include in the request (if this is not None the
117+ request will be a POST).
118+ @param extra_headers: A dictionary of extra headers that will passed in
119+ addition to Authorization.
120+ @raises urllib2.HTTPError: If the response is not a HTTP 200.
121+ @returns: the body of the response.
122+ """
123+ headers = self.auth_headers.copy()
124+ if extra_headers:
125+ headers.update(extra_headers)
126+ url = self.base + jenkins_path
127+ print url
128+ req = urllib2.Request(
129+ url, data, headers)
130+ resp = urllib2.urlopen(req)
131+ data = resp.read()
132+ resp.close()
133+ return data
134+
135+ def get_csrf_headers(self):
136+ if self.csrf is None:
137+ try:
138+ crumb_data = self.jenkins_rest('crumbIssuer/api/json')
139+ data = json.loads(crumb_data)
140+ self.csrf = {data['crumbRequestField']: data['crumb']}
141+ except urllib2.HTTPError:
142+ # Ignore errors in case CSRF protection is not enabled
143+ self.csrf = {}
144+ return self.csrf
145+
146+ def jenkins_rest_post(self, jenkins_path, data, extra_headers=None):
147+ if extra_headers is None:
148+ extra_headers = {}
149+ extra_headers.update(self.get_csrf_headers())
150+ return self.jenkins_rest(jenkins_path, data, extra_headers)
151+
152+ def get_job_config(self, job_name):
153+ return self.jenkins_rest('job/' + job_name + '/config.xml')
154+
155+ def post_config(self, url, config_xml):
156+ return self.jenkins_rest_post(url, config_xml,
157+ {'Content-Type': 'text/xml'})
158+
159+ def set_job_config(self, job_name, config_xml):
160+ return self.post_config('job/' + job_name + '/config.xml', config_xml)
161+
162+ def create_job(self, job_name, config_xml):
163+ return self.post_config('createItem?name=' + job_name, config_xml)
164+
165+ def build_job(self, job_name):
166+ self.jenkins_rest_post(
167+ 'job/' + job_name + '/buildWithParameters?delay=0sec', '')
168+
169+
170+def job2user_group(job_group):
171+ if job_group == "linaro-android":
172+ return "linaro-android-builders"
173+ return job_group
174+
175+
176+def error(msg):
177+ print >>sys.stderr, msg
178+ sys.exit(1)
179+
180+
181+def main():
182+ global options
183+ optparser = optparse.OptionParser(usage="%prog authorize|create|build <args>...")
184+ optparser.add_option("--url",
185+ default="https://android-build.linaro.org/jenkins",
186+ help="Jenkins base url, default: %default")
187+ optparser.add_option("--user",
188+ help="Jenkins username, default: $USER")
189+ optparser.add_option("--apikey-file", metavar="FILE",
190+ help="File holding Jenkins API key")
191+ optparser.add_option("--cred-file", metavar="FILE",
192+ help="File holding Jenkins username:API key pair")
193+ optparser.add_option("--private", metavar="GROUP",
194+ help="Create private job accessible to GROUP")
195+
196+ options, args = optparser.parse_args(sys.argv[1:])
197+ if len(args) < 1:
198+ optparser.error("Wrong number of arguments")
199+
200+ config_dir = os.path.expanduser("~/.config/android-build-client")
201+
202+ password = None
203+ if options.cred_file:
204+ options.user, password = open(options.cred_file).read().strip().split(":")
205+ elif options.apikey_file:
206+ password = open(options.passwd_file).read().strip()
207+ elif os.path.exists(config_dir + "/cred"):
208+ options.user, password = open(config_dir + "/cred").read().strip().split(":")
209+ print "INFO: Using cached authorization for: %s" % options.user
210+ elif args[0] != "authorize":
211+ password = getpass.getpass("API Token:")
212+
213+ if options.url[-1] != '/':
214+ options.url += '/'
215+
216+ j = Jenkins(options.url, options.user, password)
217+
218+ if args[0] == "authorize":
219+ if len(args) != 1:
220+ optparser.error("Usage: authorize")
221+ import getpass
222+ user = raw_input("Enter user name: ")
223+ while True:
224+ key = getpass.getpass("API key: ")
225+ if len(key) == 32:
226+ break
227+ print "This does not look like Jenkisn API key, please try again"
228+ try:
229+ os.makedirs(config_dir)
230+ except OSError:
231+ pass
232+ f = open(config_dir + "/cred", "w")
233+ print >>f, "%s:%s" % (user, key)
234+ f.close()
235+ print "Credentials cached for future use"
236+ elif args[0] == "create":
237+ if len(args) != 3:
238+ optparser.error("Usage: create <job_name> <build_config_file>")
239+ job_group, job_subname = args[1].split("/")
240+ template = j.get_job_config("template_" + job2user_group(job_group))
241+ dom = minidom.parseString(template)
242+ nodes = dom.getElementsByTagName("hudson.model.StringParameterDefinition")
243+ assert len(nodes) == 1
244+ n = nodes[0].getElementsByTagName("defaultValue")[0]
245+ build_config = open(args[2]).read()
246+ n.childNodes[0].data = base64.encodestring(build_config)
247+
248+ if options.private:
249+ n = dom.getElementsByTagName("properties")[0]
250+ c = n.getElementsByTagName("hudson.security.AuthorizationMatrixProperty")[0]
251+ acl_dom = minidom.parseString(PRIVATE_ACL % {"group": options.private})
252+ n.replaceChild(acl_dom.documentElement, c)
253+
254+ job_conf = dom.toxml()
255+ jenkins_job = args[1].replace("/", "_")
256+ try:
257+ j.create_job(jenkins_job, job_conf)
258+ except urllib2.HTTPError:
259+ error("Error creating job '%s' (job exists?)" % jenkins_job)
260+ print "Job created successfully: %s/job/%s/" % (options.url, jenkins_job)
261+ elif args[0] == "build":
262+ if len(args) != 2:
263+ optparser.error("Usage: build <job_name>")
264+ j.build_job(args[1].replace("/", "_"))
265+ print "Build queued"
266+ else:
267+ optparser.error("Unknown command '%s'" % args[0])
268+
269+
270+if __name__ == "__main__":
271+ main()

Subscribers

People subscribed via source and target branches