Merge lp:~matsubara/charms/trusty/jenkins/jenkins-bundle into lp:charms/trusty/jenkins

Proposed by Diogo Matsubara
Status: Merged
Merged at revision: 36
Proposed branch: lp:~matsubara/charms/trusty/jenkins/jenkins-bundle
Merge into: lp:charms/trusty/jenkins
Diff against target: 285 lines (+136/-16)
5 files modified
config.yaml (+4/-0)
hooks/jenkins_hooks.py (+99/-14)
hooks/jenkins_utils.py (+29/-2)
metadata.yaml (+3/-0)
test-requirements.txt (+1/-0)
To merge this branch: bzr merge lp:~matsubara/charms/trusty/jenkins/jenkins-bundle
Reviewer Review Type Date Requested Status
Cory Johns (community) Approve
Review Queue (community) automated testing Approve
Review via email: mp+270415@code.launchpad.net

Description of the change

Hi, this branch ports a few features from the shell based precise jenkins charm into the python based jenkins charm.

- Add a way to deploy jenkins from a bundled jenkins.deb package. Useful for environments where the jenkins node can't reach http://jenkins-ci.org;
- add extension-relation-* so the charm can be used together with the ci-configurator charm;
- export credentials to jenkins slave as part of the fix for bug 1232886. I'll merge propose the jenkins-slave charm fix separately;
- fix an UnboundLocalError: local variable 'PASSWORD' referenced before assignment.

To post a comment you must log in.
Revision history for this message
Diogo Matsubara (matsubara) wrote :
Revision history for this message
Cory Johns (johnsca) wrote :

Diogo,

Thank you for this submission. On the whole, this looks good, but I have some inline comments I've made below that need to be addressed. Nothing major, a couple of small lint errors introduced, some questions / comments in the handling of the extension relation, and what looks to be a carry-over of a bug with the password handling in one spot. Again, thanks for this work!

review: Needs Fixing
Revision history for this message
Dan Watkins (oddbloke) :
Revision history for this message
Review Queue (review-queue) wrote :

The results (PASS) are in and available here: http://juju-ci.vapour.ws:8080/job/charm-bundle-test-lxc/1429/

review: Approve (automated testing)
Revision history for this message
Review Queue (review-queue) wrote :

The results (PASS) are in and available here: http://juju-ci.vapour.ws:8080/job/charm-bundle-test-aws/1417/

review: Approve (automated testing)
Revision history for this message
Diogo Matsubara (matsubara) wrote :

Hi Cory,

thanks for the review. I think I've addressed all your points and made comments inline below. Please take another look.

Cheers,

Diogo

42. By Diogo Matsubara

address review comments: improve documentation of bundle option, fix logic of existing password, fix lint errors, add zuul interface

43. By Diogo Matsubara

port zuul-relation-joined hook from bash to python

Revision history for this message
Cory Johns (johnsca) wrote :

Diogo,

Thanks for addressing the comments in my previous review! However, I see an issue with the Zuul addition, which I mentioned on IRC and added as a line comment below. I'd also like some direction, if not an actual test case in the charm, on how to test the extension and zuul relation additions.

review: Needs Fixing
44. By Diogo Matsubara

oops forgot to pass jenkins_uid and jenkins_gid to install_jenkins_plugins()

Revision history for this message
Diogo Matsubara (matsubara) wrote :

Hi Cory,

thanks for catching that. I updated the code to pass the correct args to the install_jenkins_plugin() function.

I'll give it a try on full deployment and comment back with instructions.

45. By Diogo Matsubara

required_plugins needs to be a string rather than a list

46. By Diogo Matsubara

pass a list to hooks.execute

Revision history for this message
Diogo Matsubara (matsubara) wrote :

Hi Cory,

you'll need to grab lp:~paulgear/charms/precise/zuul/trunk and deploy from it. I'm using this bundle[1] to deploy and had to run make installdeps in zuul charm. It deployed successfully for me on canonistack.

[1] https://pastebin.canonical.com/144291/

Revision history for this message
Cory Johns (johnsca) wrote :

Diogo,

Thank you for your work on this. I have finished my review and testing, and this looks good. It is now merged.

As an aside, to get Zuul to deploy without the `make installdeps` step (which I missed), I had to make a change, which I submitted as an MP against that charm: https://code.launchpad.net/~johnsca/charms/precise/zuul/install-403/+merge/278072 (in case anyone else runs into this issue).

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'config.yaml'
2--- config.yaml 2014-11-18 23:06:36 +0000
3+++ config.yaml 2015-11-17 21:39:31 +0000
4@@ -10,6 +10,10 @@
5 Source of Jenkins, options include:
6 - lts: use the most recent Jenkins LTS release.
7 - trunk: use the most recent Jenkins release.
8+ - bundle: use a bundled deb package. The jenkins deb package needs to be
9+ manually copied over to the charm files/ dir before deployment
10+ and named jenkins.deb. It can be downloaded from
11+ http://pkg.jenkins-ci.org/debian/
12 username:
13 type: string
14 default: admin
15
16=== added symlink 'hooks/extension-relation-changed'
17=== target is u'jenkins_hooks.py'
18=== added symlink 'hooks/extension-relation-joined'
19=== target is u'jenkins_hooks.py'
20=== modified file 'hooks/jenkins_hooks.py'
21--- hooks/jenkins_hooks.py 2015-01-23 09:36:49 +0000
22+++ hooks/jenkins_hooks.py 2015-11-17 21:39:31 +0000
23@@ -12,6 +12,7 @@
24 UnregisteredHookError,
25 config,
26 remote_unit,
27+ related_units,
28 relation_get,
29 relation_set,
30 relation_ids,
31@@ -26,6 +27,7 @@
32 apt_update,
33 )
34 from charmhelpers.core.host import (
35+ service_restart,
36 service_start,
37 service_stop,
38 )
39@@ -36,7 +38,9 @@
40 TEMPLATES_DIR,
41 add_node,
42 del_node,
43+ get_jenkins_password,
44 setup_source,
45+ install_from_bundle,
46 install_jenkins_plugins,
47 )
48
49@@ -46,9 +50,13 @@
50 @hooks.hook('install')
51 def install():
52 execd_preinstall('hooks/install.d')
53- # Only setup the source if jenkins is not already installed i.e. makes the
54- # config 'release' immutable so you can't change source once deployed
55- setup_source(config('release'))
56+ if config('release') == 'bundle':
57+ install_from_bundle()
58+ else:
59+ # Only setup the source if jenkins is not already installed i.e. makes
60+ # the config 'release' immutable so you can't change source once
61+ # deployed.
62+ setup_source(config('release'))
63 config_changed()
64 open_port(8080)
65
66@@ -160,10 +168,12 @@
67
68 @hooks.hook('master-relation-changed')
69 def master_relation_changed():
70- PASSWORD = config('password')
71- if PASSWORD:
72- with open('/var/lib/jenkins/.admin_password', 'r') as fd:
73- PASSWORD = fd.read()
74+ password = get_jenkins_password()
75+ # Once we have the password, export credentials to the slave so it can
76+ # download slave-agent.jnlp from the master.
77+ username = config('username')
78+ relation_set(username=username)
79+ relation_set(password=password)
80
81 required_settings = ['executors', 'labels', 'slavehost']
82 settings = relation_get()
83@@ -183,7 +193,7 @@
84 return
85
86 log("Adding slave with hostname %s." % (slavehost), level=DEBUG)
87- add_node(slavehost, executors, labels, config('username'), PASSWORD)
88+ add_node(slavehost, executors, labels, username, password)
89 log("Node slave %s added." % (slavehost), level=DEBUG)
90
91
92@@ -198,16 +208,12 @@
93
94 @hooks.hook('master-relation-broken')
95 def master_relation_broken():
96- password = config('password')
97- if not password:
98- passwd_file = os.path.join(JENKINS_HOME, '.admin_password')
99- with open(passwd_file, 'w+') as fd:
100- PASSWORD = fd.read()
101+ password = get_jenkins_password()
102
103 for member in relation_ids():
104 member = member.replace('/', '-')
105 log("Removing node %s from Jenkins master." % (member), level=DEBUG)
106- del_node(member, config('username'), PASSWORD)
107+ del_node(member, config('username'), password)
108
109
110 @hooks.hook('website-relation-joined')
111@@ -217,6 +223,85 @@
112 relation_set(port=8080, hostname=hostname)
113
114
115+@hooks.hook('extension-relation-joined')
116+def extension_relation_joined():
117+ log("Updating extension interface with up-to-date data.")
118+ # Fish out the current zuul address from any relation we have.
119+ zuul_address = ""
120+ for rid in relation_ids('zuul'):
121+ for unit in related_units(rid):
122+ zuul_address = relation_get(
123+ rid=rid, unit=unit, attribute='private-address')
124+
125+ for rid in relation_ids('extension'):
126+ r_settings = {
127+ 'admin_username': config('username'),
128+ 'admin_password': get_jenkins_password(),
129+ 'jenkins_url': 'http://%s:8080' % unit_get('private-address'),
130+ 'jenkins-admin-user': config('jenkins-admin-user'),
131+ 'jenkins-token': config('jenkins-token')
132+ }
133+ relation_set(relation_id=rid, relation_settings=r_settings)
134+ if zuul_address:
135+ relation_set(relation_id=rid, zuul_address=zuul_address)
136+
137+
138+@hooks.hook('extension-relation-changed')
139+def extension_relation_changed():
140+ # extension subordinates may request the principle service install
141+ # specified jenkins plugins
142+ if relation_get('required_plugins'):
143+ log("Installing required plugins as requested by jenkins-extension "
144+ "subordinate.")
145+ jenkins_uid = pwd.getpwnam('jenkins').pw_uid
146+ jenkins_gid = grp.getgrnam('jenkins').gr_gid
147+ install_jenkins_plugins(
148+ jenkins_uid, jenkins_gid,
149+ plugins=relation_get('required_plugins'))
150+
151+
152+ZUUL_CONFIG_SNIPPET = """
153+<hudson.plugins.gearman.GearmanPluginConfig>
154+ <enablePlugin>true</enablePlugin>
155+ <host>{}</host>
156+ <port>4730</port>
157+</hudson.plugins.gearman.GearmanPluginConfig>
158+"""
159+
160+
161+@hooks.hook('zuul-relation-joined')
162+def zuul_relation_joined():
163+ log("Installing and configuring gearman-plugin for Zuul communication")
164+ # zuul relation requires we install the required plugins and set the
165+ # address of the remote zuul/gearman service in the plugin setting.
166+ required_plugins = (
167+ "credentials ssh-credentials ssh-agent gearman-plugin git-client git")
168+
169+ # Grab jenkins uid and gid.
170+ jenkins_uid = pwd.getpwnam('jenkins').pw_uid
171+ jenkins_gid = grp.getgrnam('jenkins').gr_gid
172+ log("Installing and configuring gearman-plugin for Zuul communication")
173+ install_jenkins_plugins(
174+ jenkins_uid, jenkins_gid, plugins=required_plugins)
175+ # Generate plugin config with address of remote unit.
176+ zuul_config = ZUUL_CONFIG_SNIPPET.format(relation_get('private-address'))
177+ config_path = os.path.join(
178+ JENKINS_HOME, "hudson.plugins.gearman.GearmanPluginConfig.xml")
179+ with open(config_path, 'w') as f:
180+ f.write(zuul_config)
181+
182+ # Change permission of config file.
183+ nogroup_gid = grp.getgrnam('nogroup').gr_gid
184+ os.chown(config_path, jenkins_uid, nogroup_gid)
185+
186+ # Restart jenkins so changes will take efect.
187+ service_restart('jenkins')
188+
189+ # Trigger the extension hook to update it with zuul relation data, if its
190+ # coded to do so.
191+ hooks.execute(['extension-relation-joined'])
192+
193+
194 if __name__ == '__main__':
195 try:
196 hooks.execute(sys.argv)
197
198=== modified file 'hooks/jenkins_utils.py'
199--- hooks/jenkins_utils.py 2015-01-20 18:33:34 +0000
200+++ hooks/jenkins_utils.py 2015-11-17 21:39:31 +0000
201@@ -6,6 +6,7 @@
202 import tempfile
203
204 from charmhelpers.core.hookenv import (
205+ charm_dir,
206 config,
207 log,
208 DEBUG,
209@@ -13,6 +14,7 @@
210 WARNING,
211 )
212 from charmhelpers.fetch import (
213+ apt_install,
214 apt_update,
215 add_source,
216 )
217@@ -60,6 +62,21 @@
218 log("Node '%s' does not exist - not deleting" % (host), level=INFO)
219
220
221+def install_from_bundle():
222+ """Install Jenkins from bundled package."""
223+ # Check bundled package exists.
224+ bundle_path = os.path.join(charm_dir(), 'files', 'jenkins.deb')
225+ if not os.path.isfile(bundle_path):
226+ errmsg = "'%s' doesn't exist. No package bundled." % (bundle_path)
227+ raise Exception(errmsg)
228+ log('Installing from bundled Jenkins package: %s' % bundle_path)
229+ # Install bundle deps.
230+ apt_install(['daemon', 'adduser', 'psmisc', 'default-jre'], fatal=True)
231+ # Run dpkg to install bundled deb.
232+ env = os.environ.copy()
233+ subprocess.call(['dpkg', '-i', bundle_path], env=env)
234+
235+
236 def setup_source(release):
237 """Install Jenkins archive."""
238 log("Configuring source of jenkins as %s" % release, level=INFO)
239@@ -108,8 +125,8 @@
240 apt_update(fatal=True)
241
242
243-def install_jenkins_plugins(jenkins_uid, jenkins_gid):
244- plugins = config('plugins')
245+def install_jenkins_plugins(jenkins_uid, jenkins_gid, plugins=None):
246+ plugins = plugins or config('plugins')
247 if plugins:
248 plugins = plugins.split()
249 else:
250@@ -176,3 +193,13 @@
251 finally:
252 # Delete install refs
253 shutil.rmtree(track_dir)
254+
255+
256+def get_jenkins_password():
257+ """Return password from the config or the one saved on file."""
258+ password = config('password')
259+ if not password:
260+ passwd_file = os.path.join(JENKINS_HOME, '.admin_password')
261+ with open(passwd_file, 'r') as fd:
262+ password = fd.read()
263+ return password
264
265=== added symlink 'hooks/zuul-relation-joined'
266=== target is u'jenkins_hooks.py'
267=== modified file 'metadata.yaml'
268--- metadata.yaml 2015-03-16 20:37:46 +0000
269+++ metadata.yaml 2015-11-17 21:39:31 +0000
270@@ -25,3 +25,6 @@
271 master:
272 interface: jenkins-slave
273 optional: true
274+ zuul:
275+ interface: zuul
276+ optional: true
277
278=== modified file 'test-requirements.txt'
279--- test-requirements.txt 2015-01-26 11:19:23 +0000
280+++ test-requirements.txt 2015-11-17 21:39:31 +0000
281@@ -1,3 +1,4 @@
282+coverage
283 distribute
284 flake8
285 nose

Subscribers

People subscribed via source and target branches

to all changes: