Merge lp:~mbruzek/charms/trusty/kubernetes-master/trunk into lp:~kubernetes/charms/trusty/kubernetes-master/trunk
- Trusty Tahr (14.04)
- trunk
- Merge into trunk
Proposed by
Matt Bruzek
Status: | Merged |
---|---|
Merged at revision: | 14 |
Proposed branch: | lp:~mbruzek/charms/trusty/kubernetes-master/trunk |
Merge into: | lp:~kubernetes/charms/trusty/kubernetes-master/trunk |
Diff against target: |
979 lines (+383/-164) 16 files modified
Makefile (+2/-1) README.md (+42/-17) config.yaml (+24/-0) copyright (+1/-1) files/apiserver.upstart.tmpl (+8/-9) files/controller-manager.upstart.tmpl (+1/-8) files/distribution.conf.tmpl (+5/-1) files/nginx.conf.tmpl (+25/-37) files/scheduler.upstart.tmpl (+1/-8) hooks/__init__.py (+16/-0) hooks/hooks.py (+118/-26) hooks/install.py (+35/-10) hooks/kubernetes_installer.py (+17/-3) hooks/setup.py (+14/-0) unit_tests/kubernetes_installer_test.py (+25/-13) unit_tests/test_install.py (+49/-30) |
To merge this branch: | bzr merge lp:~mbruzek/charms/trusty/kubernetes-master/trunk |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Charles Butler (community) | Approve | ||
Review via email: mp+273994@code.launchpad.net |
Commit message
Description of the change
Synched this charm with the content on github. Then ran the lint and CI tests against the code.
Had to clean up quite a few failures to get here, but this charm passes all tests in CI and is synchronized with the content in github.
http://
To post a comment you must log in.
- 19. By Matt Bruzek
-
Changing the flake8 rules to avoid conflict with verify-boilerplate k8s tool.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' |
2 | --- Makefile 2015-09-24 19:50:43 +0000 |
3 | +++ Makefile 2015-10-09 16:18:58 +0000 |
4 | @@ -6,7 +6,7 @@ |
5 | .venv/bin/pip install -q -r requirements.txt |
6 | |
7 | lint: virtualenv |
8 | - @.venv/bin/flake8 hooks --exclude=charmhelpers |
9 | + @.venv/bin/flake8 hooks --exclude=charmhelpers --ignore=W391 |
10 | @.venv/bin/charm proof |
11 | |
12 | test: virtualenv |
13 | @@ -27,3 +27,4 @@ |
14 | clean: |
15 | rm -rf .venv |
16 | find -name *.pyc -delete |
17 | + rm -rf unit_tests/.cache |
18 | |
19 | === modified file 'README.md' |
20 | --- README.md 2015-04-10 21:42:42 +0000 |
21 | +++ README.md 2015-10-09 16:18:58 +0000 |
22 | @@ -1,6 +1,6 @@ |
23 | # Kubernetes Master Charm |
24 | |
25 | -[Kubernetes](https://github.com/googlecloudplatform/kubernetes) is an open |
26 | +[Kubernetes](https://github.com/kubernetes/kubernetes) is an open |
27 | source system for managing containerized applications across multiple hosts. |
28 | Kubernetes uses [Docker](http://www.docker.io/) to package, instantiate and run |
29 | containerized applications. |
30 | @@ -56,22 +56,44 @@ |
31 | |
32 | #### Deploying the recommended configuration |
33 | |
34 | -A bundle can be used to deploy Kubernetes onto any cloud it can be |
35 | -orchestrated directly in the Juju Graphical User Interface, when using |
36 | -`juju quickstart`: |
37 | - |
38 | - juju quickstart https://raw.githubusercontent.com/whitmo/bundle-kubernetes/master/bundles.yaml |
39 | - |
40 | - |
41 | -For more information on the recommended bundle deployment, see the |
42 | -[Kubernetes bundle documentation](https://github.com/whitmo/bundle-kubernetes) |
43 | +Use the 'juju quickstart' command to deploy a Kubernetes cluster to any cloud |
44 | +supported by Juju. |
45 | + |
46 | +The charm store version of the Kubernetes bundle can be deployed as follows: |
47 | + |
48 | + juju quickstart u/kubernetes/kubernetes-cluster |
49 | + |
50 | +> Note: The charm store bundle may be locked to a specific Kubernetes release. |
51 | + |
52 | +Alternately you could deploy a Kubernetes bundle straight from github or a file: |
53 | + |
54 | + juju quickstart https://raw.githubusercontent.com/kubernetes/kubernetes/master/cluster/juju/bundles/local.yaml |
55 | + |
56 | +The command above does few things for you: |
57 | + |
58 | +- Starts a curses based gui for managing your cloud or MAAS credentials |
59 | +- Looks for a bootstrapped deployment environment, and bootstraps if |
60 | + required. This will launch a bootstrap node in your chosen |
61 | + deployment environment (machine 0). |
62 | +- Deploys the Juju GUI to your environment onto the bootstrap node. |
63 | +- Provisions 4 machines, and deploys the Kubernetes services on top of |
64 | + them (Kubernetes-master, two Kubernetes minions using flannel, and etcd). |
65 | +- Orchestrates the relations among the services, and exits. |
66 | + |
67 | +Now you should have a running Kubernetes. Run `juju status |
68 | +--format=oneline` to see the address of your kubernetes-master unit. |
69 | + |
70 | +For further reading on [Juju Quickstart](https://pypi.python.org/pypi/juju-quickstart) |
71 | + |
72 | +Go to the [Getting started with Juju guide](https://github.com/kubernetes/kubernetes/blob/master/docs/getting-started-guides/juju.md) |
73 | +for more information about deploying a development Kubernetes cluster. |
74 | |
75 | |
76 | #### Post Deployment |
77 | |
78 | To interact with the kubernetes environment, either build or |
79 | -[download](https://github.com/GoogleCloudPlatform/kubernetes/releases) the |
80 | -[kubectl](https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/kubectl.md) |
81 | +[download](https://github.com/kubernetes/kubernetes/releases) the |
82 | +[kubectl](https://github.com/kubernetes/kubernetes/blob/master/docs/user-guide/kubectl/kubectl.md) |
83 | binary (available in the releases binary tarball) and point it to the master with : |
84 | |
85 | |
86 | @@ -90,12 +112,15 @@ |
87 | command. |
88 | |
89 | Congratulations you know have deployed a Kubernetes environment! Use the |
90 | -[kubectl](https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/kubectl.md) |
91 | +[kubectl](https://github.com/kubernetes/kubernetes/blob/master/docs/user-guide/kubectl/kubectl.md) |
92 | to interact with the environment. |
93 | |
94 | # Kubernetes information |
95 | |
96 | -- [Kubernetes github project](https://github.com/GoogleCloudPlatform/kubernetes) |
97 | -- [Kubernetes issue tracker](https://github.com/GoogleCloudPlatform/kubernetes/issues) |
98 | -- [Kubernetes Documenation](https://github.com/GoogleCloudPlatform/kubernetes/tree/master/docs) |
99 | -- [Kubernetes releases](https://github.com/GoogleCloudPlatform/kubernetes/releases) |
100 | +- [Kubernetes github project](https://github.com/kubernetes/kubernetes) |
101 | +- [Kubernetes issue tracker](https://github.com/kubernetes/kubernetes/issues) |
102 | +- [Kubernetes Documenation](https://github.com/kubernetes/kubernetes/tree/master/docs) |
103 | +- [Kubernetes releases](https://github.com/kubernetes/kubernetes/releases) |
104 | + |
105 | + |
106 | +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/cluster/juju/charms/trusty/kubernetes-master/README.md?pixel)]() |
107 | |
108 | === modified file 'config.yaml' |
109 | --- config.yaml 2015-07-17 20:01:20 +0000 |
110 | +++ config.yaml 2015-10-09 16:18:58 +0000 |
111 | @@ -7,3 +7,27 @@ |
112 | compiled from the source identified by this tag in github. Using the |
113 | value of "source" will use the master kubernetes branch when compiling |
114 | the binaries. |
115 | + username: |
116 | + type: string |
117 | + default: "admin" |
118 | + description: | |
119 | + The initial user for the kubernetes basic authentication file. |
120 | + password: |
121 | + type: string |
122 | + default: "" |
123 | + description: | |
124 | + The password for the kubernetes basic authentication. If this value is |
125 | + empty, a password will be generated at random for the username. |
126 | + apiserver-cert: |
127 | + type: string |
128 | + default: "" |
129 | + description: | |
130 | + The ssl certificate to use for tls communication to the Kubernetes api |
131 | + server. If this value is empty a self signed certificate and key will |
132 | + be generated. |
133 | + apiserver-key: |
134 | + type: string |
135 | + default: "" |
136 | + description: | |
137 | + The private key to use for tls communication to the Kubernetes api |
138 | + server. If this value is empty a key and certificate will be generated. |
139 | |
140 | === modified file 'copyright' |
141 | --- copyright 2015-07-17 20:01:20 +0000 |
142 | +++ copyright 2015-10-09 16:18:58 +0000 |
143 | @@ -1,4 +1,4 @@ |
144 | -Copyright 2015 Canonical Ltd. |
145 | +Copyright 2015 Google Inc. All rights reserved. |
146 | |
147 | Licensed under the Apache License, Version 2.0 (the "License"); |
148 | you may not use this file except in compliance with the License. |
149 | |
150 | === modified file 'files/apiserver.upstart.tmpl' |
151 | --- files/apiserver.upstart.tmpl 2015-07-17 20:01:20 +0000 |
152 | +++ files/apiserver.upstart.tmpl 2015-10-09 16:18:58 +0000 |
153 | @@ -8,13 +8,12 @@ |
154 | kill timeout 30 # wait 30s between SIGTERM and SIGKILL. |
155 | |
156 | exec /usr/local/bin/apiserver \ |
157 | - --address=%(api_bind_address)s \ |
158 | - --etcd_servers=%(etcd_servers)s \ |
159 | + --basic-auth-file=/srv/kubernetes/basic-auth.csv \ |
160 | + --bind-address=%(api_private_address)s \ |
161 | + --etcd-servers=%(etcd_servers)s \ |
162 | + --insecure-bind-address=%(api_private_address)s \ |
163 | --logtostderr=true \ |
164 | - --service-cluster-ip-range=10.244.240.0/20 |
165 | - |
166 | - |
167 | - |
168 | - |
169 | - |
170 | - |
171 | + --secure-port=6443 \ |
172 | + --service-cluster-ip-range=10.244.240.0/20 \ |
173 | + --tls-cert-file=/srv/kubernetes/apiserver.crt \ |
174 | + --tls-private-key-file=/srv/kubernetes/apiserver.key |
175 | |
176 | === modified file 'files/controller-manager.upstart.tmpl' |
177 | --- files/controller-manager.upstart.tmpl 2015-04-10 21:12:25 +0000 |
178 | +++ files/controller-manager.upstart.tmpl 2015-10-09 16:18:58 +0000 |
179 | @@ -10,11 +10,4 @@ |
180 | exec /usr/local/bin/controller-manager \ |
181 | --address=%(bind_address)s \ |
182 | --logtostderr=true \ |
183 | - --master=%(api_server_address)s |
184 | - |
185 | - |
186 | - |
187 | - |
188 | - |
189 | - |
190 | - |
191 | + --master=%(api_http_uri)s |
192 | |
193 | === modified file 'files/distribution.conf.tmpl' |
194 | --- files/distribution.conf.tmpl 2015-07-17 20:01:20 +0000 |
195 | +++ files/distribution.conf.tmpl 2015-10-09 16:18:58 +0000 |
196 | @@ -1,5 +1,9 @@ |
197 | +# This file configures ngnix to serve Kubernetes binaries using http. |
198 | +# The charms find the location path from the api relation to the charm. |
199 | server { |
200 | - listen %(api_bind_address)s:80; |
201 | + listen 80 default_server; |
202 | + root %(alias)s; |
203 | + |
204 | location %(web_uri)s { |
205 | alias %(alias)s; |
206 | } |
207 | |
208 | === modified file 'files/nginx.conf.tmpl' |
209 | --- files/nginx.conf.tmpl 2015-01-27 17:59:40 +0000 |
210 | +++ files/nginx.conf.tmpl 2015-10-09 16:18:58 +0000 |
211 | @@ -1,39 +1,27 @@ |
212 | -# HTTP/HTTPS server |
213 | -# |
214 | +# Proxy HTTPS from the public address to the kube-apiserver running at 6443. |
215 | server { |
216 | - listen 80; |
217 | - server_name localhost; |
218 | - |
219 | - root html; |
220 | - index index.html index.htm; |
221 | - |
222 | -# ssl on; |
223 | -# ssl_certificate /usr/share/nginx/server.cert; |
224 | -# ssl_certificate_key /usr/share/nginx/server.key; |
225 | - |
226 | -# ssl_session_timeout 5m; |
227 | -# ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; |
228 | -# ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS; |
229 | -# ssl_prefer_server_ciphers on; |
230 | - |
231 | - location / { |
232 | -# auth_basic "Restricted"; |
233 | -# auth_basic_user_file /usr/share/nginx/htpasswd; |
234 | - |
235 | - # Proxy settings |
236 | - # disable buffering so that watch works |
237 | - proxy_buffering off; |
238 | - proxy_pass %(api_server_address)s; |
239 | - proxy_connect_timeout 159s; |
240 | - proxy_send_timeout 600s; |
241 | - proxy_read_timeout 600s; |
242 | - |
243 | - # Disable retry |
244 | - proxy_next_upstream off; |
245 | - |
246 | - # Support web sockets |
247 | - proxy_http_version 1.1; |
248 | - proxy_set_header Upgrade $http_upgrade; |
249 | - proxy_set_header Connection "upgrade"; |
250 | - } |
251 | + listen 443; |
252 | + server_name localhost; |
253 | + |
254 | + root html; |
255 | + index index.html index.htm; |
256 | + |
257 | + ssl on; |
258 | + ssl_certificate /srv/kubernetes/apiserver.crt; |
259 | + ssl_certificate_key /srv/kubernetes/apiserver.key; |
260 | + ssl_session_timeout 5m; |
261 | + |
262 | + # don't use SSLv3 because of POODLE |
263 | + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; |
264 | + ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS; |
265 | + ssl_prefer_server_ciphers on; |
266 | + |
267 | + location / { |
268 | + proxy_buffering off; |
269 | + proxy_pass %(api_https_uri)s; |
270 | + proxy_connect_timeout 159s; |
271 | + proxy_send_timeout 600s; |
272 | + proxy_read_timeout 600s; |
273 | + proxy_redirect off; |
274 | + } |
275 | } |
276 | |
277 | === modified file 'files/scheduler.upstart.tmpl' |
278 | --- files/scheduler.upstart.tmpl 2015-04-10 21:12:25 +0000 |
279 | +++ files/scheduler.upstart.tmpl 2015-10-09 16:18:58 +0000 |
280 | @@ -10,11 +10,4 @@ |
281 | exec /usr/local/bin/scheduler \ |
282 | --address=%(bind_address)s \ |
283 | --logtostderr=true \ |
284 | - --master=%(api_server_address)s |
285 | - |
286 | - |
287 | - |
288 | - |
289 | - |
290 | - |
291 | - |
292 | + --master=%(api_http_uri)s |
293 | |
294 | === modified file 'hooks/__init__.py' |
295 | --- hooks/__init__.py 2015-09-24 19:50:43 +0000 |
296 | +++ hooks/__init__.py 2015-10-09 16:18:58 +0000 |
297 | @@ -0,0 +1,16 @@ |
298 | +#!/usr/bin/env python |
299 | + |
300 | +# Copyright 2015 The Kubernetes Authors All rights reserved. |
301 | +# |
302 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
303 | +# you may not use this file except in compliance with the License. |
304 | +# You may obtain a copy of the License at |
305 | +# |
306 | +# http://www.apache.org/licenses/LICENSE-2.0 |
307 | +# |
308 | +# Unless required by applicable law or agreed to in writing, software |
309 | +# distributed under the License is distributed on an "AS IS" BASIS, |
310 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
311 | +# See the License for the specific language governing permissions and |
312 | +# limitations under the License. |
313 | + |
314 | |
315 | === modified file 'hooks/hooks.py' |
316 | --- hooks/hooks.py 2015-09-24 19:50:43 +0000 |
317 | +++ hooks/hooks.py 2015-10-09 16:18:58 +0000 |
318 | @@ -1,5 +1,19 @@ |
319 | #!/usr/bin/env python |
320 | |
321 | +# Copyright 2015 The Kubernetes Authors All rights reserved. |
322 | +# |
323 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
324 | +# you may not use this file except in compliance with the License. |
325 | +# You may obtain a copy of the License at |
326 | +# |
327 | +# http://www.apache.org/licenses/LICENSE-2.0 |
328 | +# |
329 | +# Unless required by applicable law or agreed to in writing, software |
330 | +# distributed under the License is distributed on an "AS IS" BASIS, |
331 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
332 | +# See the License for the specific language governing permissions and |
333 | +# limitations under the License. |
334 | + |
335 | """ |
336 | The main hook file is called by Juju. |
337 | """ |
338 | @@ -9,8 +23,9 @@ |
339 | import subprocess |
340 | import sys |
341 | from charmhelpers.core import hookenv, host |
342 | +from charmhelpers.contrib import ssl |
343 | from kubernetes_installer import KubernetesInstaller |
344 | -from path import path |
345 | +from path import Path |
346 | |
347 | hooks = hookenv.Hooks() |
348 | |
349 | @@ -41,10 +56,14 @@ |
350 | create kubernetes binary files. |
351 | """ |
352 | hookenv.log('Starting config-changed') |
353 | - charm_dir = path(hookenv.charm_dir()) |
354 | + charm_dir = Path(hookenv.charm_dir()) |
355 | config = hookenv.config() |
356 | # Get the version of kubernetes to install. |
357 | version = config['version'] |
358 | + username = config['username'] |
359 | + password = config['password'] |
360 | + certificate = config['apiserver-cert'] |
361 | + key = config['apiserver-key'] |
362 | |
363 | if version == 'master': |
364 | # The 'master' branch of kuberentes is used when master is configured. |
365 | @@ -56,32 +75,59 @@ |
366 | # Create a branch to a tag to get the release version. |
367 | branch = 'tags/{0}'.format(version) |
368 | |
369 | - # Get the package architecture |
370 | + cert_file = '/srv/kubernetes/apiserver.crt' |
371 | + key_file = '/srv/kubernetes/apiserver.key' |
372 | + # When the cert or key changes we need to restart the apiserver. |
373 | + if config.changed('apiserver-cert') or config.changed('apiserver-key'): |
374 | + hookenv.log('Certificate or key has changed.') |
375 | + if not certificate or not key: |
376 | + generate_cert(key=key_file, cert=cert_file) |
377 | + else: |
378 | + hookenv.log('Writing new certificate and key to server.') |
379 | + with open(key_file, 'w') as file: |
380 | + file.write(key) |
381 | + with open(cert_file, 'w') as file: |
382 | + file.write(certificate) |
383 | + # Restart apiserver as the certificate or key has changed. |
384 | + if host.service_running('apiserver'): |
385 | + host.service_restart('apiserver') |
386 | + # Reload nginx because it proxies https to apiserver. |
387 | + if host.service_running('nginx'): |
388 | + host.service_reload('nginx') |
389 | + |
390 | + if config.changed('username') or config.changed('password'): |
391 | + hookenv.log('Username or password changed, creating authentication.') |
392 | + basic_auth(username, username, password) |
393 | + if host.service_running('apiserver'): |
394 | + host.service_restart('apiserver') |
395 | + |
396 | + # Get package architecture, rather than arch from the kernel (uname -m). |
397 | arch = subprocess.check_output(['dpkg', '--print-architecture']).strip() |
398 | |
399 | if not branch: |
400 | output_path = charm_dir / 'files/output' |
401 | - installer = KubernetesInstaller(arch, version, output_path) |
402 | + kube_installer = KubernetesInstaller(arch, version, output_path) |
403 | else: |
404 | |
405 | # Build the kuberentes binaries from source on the units. |
406 | - kubernetes_dir = path('/opt/kubernetes') |
407 | + kubernetes_dir = Path('/opt/kubernetes') |
408 | |
409 | # Construct the path to the binaries using the arch. |
410 | output_path = kubernetes_dir / '_output/local/bin/linux' / arch |
411 | - installer = KubernetesInstaller(arch, version, output_path) |
412 | + kube_installer = KubernetesInstaller(arch, version, output_path) |
413 | |
414 | if not kubernetes_dir.exists(): |
415 | - print('The source dir {0} does not exist'.format(kubernetes_dir)) |
416 | - print('Was the kubernetes code cloned during install?') |
417 | + message = 'The kubernetes source directory {0} does not exist. ' \ |
418 | + 'Was the kubernetes repository cloned during the install?' |
419 | + print(message.format(kubernetes_dir)) |
420 | exit(1) |
421 | |
422 | # Change to the kubernetes directory (git repository). |
423 | with kubernetes_dir: |
424 | - |
425 | # Create a command to get the current branch. |
426 | git_branch = 'git branch | grep "\*" | cut -d" " -f2' |
427 | - current_branch = subprocess.check_output(git_branch, shell=True).strip() # noqa |
428 | + current_branch = subprocess.check_output(git_branch, shell=True) |
429 | + current_branch = current_branch.strip() |
430 | print('Current branch: ', current_branch) |
431 | # Create the path to a file to indicate if the build was broken. |
432 | broken_build = charm_dir / '.broken_build' |
433 | @@ -90,12 +136,12 @@ |
434 | print('Last build failed: ', last_build_failed) |
435 | # Rebuild if current version is different or last build failed. |
436 | if current_branch != version or last_build_failed: |
437 | - installer.build(branch) |
438 | + kube_installer.build(branch) |
439 | if not output_path.isdir(): |
440 | broken_build.touch() |
441 | |
442 | # Create the symoblic links to the right directories. |
443 | - installer.install() |
444 | + kube_installer.install() |
445 | |
446 | relation_changed() |
447 | |
448 | @@ -109,10 +155,10 @@ |
449 | # Check required keys |
450 | for k in ('etcd_servers',): |
451 | if not template_data.get(k): |
452 | - print "Missing data for", k, template_data |
453 | + print 'Missing data for', k, template_data |
454 | return |
455 | |
456 | - print "Running with\n", template_data |
457 | + print 'Running with\n', template_data |
458 | |
459 | # Render and restart as needed |
460 | for n in ('apiserver', 'controller-manager', 'scheduler'): |
461 | @@ -143,7 +189,7 @@ |
462 | |
463 | |
464 | def notify_minions(): |
465 | - print("Notify minions.") |
466 | + print('Notify minions.') |
467 | config = hookenv.config() |
468 | for r in hookenv.relation_ids('minions-api'): |
469 | hookenv.relation_set( |
470 | @@ -151,7 +197,49 @@ |
471 | hostname=hookenv.unit_private_ip(), |
472 | port=8080, |
473 | version=config['version']) |
474 | - print("Notified minions of version " + config['version']) |
475 | + print('Notified minions of version ' + config['version']) |
476 | + |
477 | + |
478 | +def basic_auth(name, id, pwd=None, file='/srv/kubernetes/basic-auth.csv'): |
479 | + """ |
480 | + Create a basic authentication file for kubernetes. The file is a csv file |
481 | + with 3 columns: password, user name, user id. From the Kubernetes docs: |
482 | + The basic auth credentials last indefinitely, and the password cannot be |
483 | + changed without restarting apiserver. |
484 | + """ |
485 | + if not pwd: |
486 | + import random |
487 | + import string |
488 | + alphanumeric = string.ascii_letters + string.digits |
489 | + pwd = ''.join(random.choice(alphanumeric) for _ in range(16)) |
490 | + lines = [] |
491 | + auth_file = Path(file) |
492 | + if auth_file.isfile(): |
493 | + lines = auth_file.lines() |
494 | + for line in lines: |
495 | + target = ',{0},{1}'.format(name, id) |
496 | + if target in line: |
497 | + lines.remove(line) |
498 | + auth_line = '{0},{1},{2}'.format(pwd, name, id) |
499 | + lines.append(auth_line) |
500 | + auth_file.write_lines(lines) |
501 | + |
502 | + |
503 | +def generate_cert(common_name=None, |
504 | + key='/srv/kubernetes/apiserver.key', |
505 | + cert='/srv/kubernetes/apiserver.crt'): |
506 | + """ |
507 | + Create the certificate and key for the Kubernetes tls enablement. |
508 | + """ |
509 | + hookenv.log('Generating new self signed certificate and key', 'INFO') |
510 | + if not common_name: |
511 | + common_name = hookenv.unit_get('public-address') |
512 | + if os.path.isfile(key) or os.path.isfile(cert): |
513 | + hookenv.log('Overwriting the existing certificate or key', 'WARNING') |
514 | + hookenv.log('Generating certificate for {0}'.format(common_name), 'INFO') |
515 | + # Generate the self signed certificate with the public address as CN. |
516 | + # https://pythonhosted.org/charmhelpers/api/charmhelpers.contrib.ssl.html |
517 | + ssl.generate_selfsigned(key, cert, cn=common_name) |
518 | |
519 | |
520 | def get_template_data(): |
521 | @@ -159,18 +247,21 @@ |
522 | config = hookenv.config() |
523 | version = config['version'] |
524 | template_data = {} |
525 | - template_data['etcd_servers'] = ",".join([ |
526 | - "http://%s:%s" % (s[0], s[1]) for s in sorted( |
527 | + template_data['etcd_servers'] = ','.join([ |
528 | + 'http://%s:%s' % (s[0], s[1]) for s in sorted( |
529 | get_rel_hosts('etcd', rels, ('hostname', 'port')))]) |
530 | - template_data['minions'] = ",".join(get_rel_hosts('minions-api', rels)) |
531 | + template_data['minions'] = ','.join(get_rel_hosts('minions-api', rels)) |
532 | + private_ip = hookenv.unit_private_ip() |
533 | + public_ip = hookenv.unit_public_ip() |
534 | + template_data['api_public_address'] = _bind_addr(public_ip) |
535 | + template_data['api_private_address'] = _bind_addr(private_ip) |
536 | + template_data['bind_address'] = '127.0.0.1' |
537 | + template_data['api_http_uri'] = 'http://%s:%s' % (private_ip, 8080) |
538 | + template_data['api_https_uri'] = 'https://%s:%s' % (private_ip, 6443) |
539 | |
540 | - template_data['api_bind_address'] = _bind_addr(hookenv.unit_private_ip()) |
541 | - template_data['bind_address'] = "127.0.0.1" |
542 | - template_data['api_server_address'] = "http://%s:%s" % ( |
543 | - hookenv.unit_private_ip(), 8080) |
544 | arch = subprocess.check_output(['dpkg', '--print-architecture']).strip() |
545 | |
546 | - template_data['web_uri'] = "/kubernetes/%s/local/bin/linux/%s/" % (version, |
547 | + template_data['web_uri'] = '/kubernetes/%s/local/bin/linux/%s/' % (version, |
548 | arch) |
549 | if version == 'local': |
550 | template_data['alias'] = hookenv.charm_dir() + '/files/output/' |
551 | @@ -187,7 +278,7 @@ |
552 | try: |
553 | return socket.gethostbyname(addr) |
554 | except socket.error: |
555 | - raise ValueError("Could not resolve private address") |
556 | + raise ValueError('Could not resolve address %s' % addr) |
557 | |
558 | |
559 | def _encode(d): |
560 | @@ -209,7 +300,7 @@ |
561 | return hosts |
562 | |
563 | |
564 | -def render_file(name, data, src_suffix="upstart.tmpl", tgt_path=None): |
565 | +def render_file(name, data, src_suffix='upstart.tmpl', tgt_path=None): |
566 | tmpl_path = os.path.join( |
567 | os.environ.get('CHARM_DIR'), 'files', '%s.%s' % (name, src_suffix)) |
568 | |
569 | @@ -230,5 +321,6 @@ |
570 | fh.write(rendered) |
571 | return True |
572 | |
573 | + |
574 | if __name__ == '__main__': |
575 | hooks.execute(sys.argv) |
576 | |
577 | === modified file 'hooks/install.py' |
578 | --- hooks/install.py 2015-09-28 15:23:30 +0000 |
579 | +++ hooks/install.py 2015-10-09 16:18:58 +0000 |
580 | @@ -1,13 +1,27 @@ |
581 | #!/usr/bin/env python |
582 | |
583 | +# Copyright 2015 The Kubernetes Authors All rights reserved. |
584 | +# |
585 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
586 | +# you may not use this file except in compliance with the License. |
587 | +# You may obtain a copy of the License at |
588 | +# |
589 | +# http://www.apache.org/licenses/LICENSE-2.0 |
590 | +# |
591 | +# Unless required by applicable law or agreed to in writing, software |
592 | +# distributed under the License is distributed on an "AS IS" BASIS, |
593 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
594 | +# See the License for the specific language governing permissions and |
595 | +# limitations under the License. |
596 | + |
597 | import setup |
598 | setup.pre_install() |
599 | import subprocess |
600 | |
601 | +from charmhelpers import fetch |
602 | from charmhelpers.core import hookenv |
603 | -from charmhelpers import fetch |
604 | from charmhelpers.fetch import archiveurl |
605 | -from path import path |
606 | +from path import Path |
607 | |
608 | |
609 | def install(): |
610 | @@ -16,18 +30,24 @@ |
611 | download_go() |
612 | |
613 | hookenv.log('Adding kubernetes and go to the path') |
614 | - |
615 | + address = hookenv.unit_private_ip() |
616 | strings = [ |
617 | 'export GOROOT=/usr/local/go\n', |
618 | 'export PATH=$PATH:$GOROOT/bin\n', |
619 | - 'export KUBE_MASTER_IP=0.0.0.0\n', |
620 | - 'export KUBERNETES_MASTER=http://$KUBE_MASTER_IP\n', |
621 | - ] |
622 | + 'export KUBERNETES_MASTER=http://{0}:8080\n'.format(address), |
623 | + ] |
624 | update_rc_files(strings) |
625 | hookenv.log('Downloading kubernetes code') |
626 | clone_repository() |
627 | |
628 | + # Create the directory to store the keys and auth files. |
629 | + srv = Path('/srv/kubernetes') |
630 | + if not srv.isdir(): |
631 | + srv.makedirs_p() |
632 | + |
633 | hookenv.open_port(8080) |
634 | + hookenv.open_port(6443) |
635 | + hookenv.open_port(443) |
636 | |
637 | hookenv.log('Install complete') |
638 | |
639 | @@ -51,7 +71,7 @@ |
640 | """ |
641 | |
642 | repository = 'https://github.com/kubernetes/kubernetes.git' |
643 | - kubernetes_directory = path('/opt/kubernetes') |
644 | + kubernetes_directory = Path('/opt/kubernetes') |
645 | # Since we can not clone twice, check for the directory and remove it. |
646 | if kubernetes_directory.isdir(): |
647 | kubernetes_directory.rmtree_p() |
648 | @@ -69,8 +89,13 @@ |
649 | """ |
650 | hookenv.log('Installing Debian packages') |
651 | # Create the list of packages to install. |
652 | - apt_packages = ['build-essential', 'git', 'make', 'nginx', 'python-pip', |
653 | - 'docker.io'] |
654 | + apt_packages = ['apache2-utils', |
655 | + 'build-essential', |
656 | + 'docker.io', |
657 | + 'git', |
658 | + 'make', |
659 | + 'nginx', |
660 | + 'python-pip', ] |
661 | fetch.apt_install(fetch.filter_installed_packages(apt_packages)) |
662 | |
663 | |
664 | @@ -80,7 +105,7 @@ |
665 | make interfacing with the api easier. (see: kubectrl docs) |
666 | """ |
667 | if not rc_files: |
668 | - rc_files = [path('/home/ubuntu/.bashrc'), path('/root/.bashrc')] |
669 | + rc_files = [Path('/home/ubuntu/.bashrc'), Path('/root/.bashrc')] |
670 | |
671 | for rc_file in rc_files: |
672 | lines = rc_file.lines() |
673 | |
674 | === modified file 'hooks/kubernetes_installer.py' |
675 | --- hooks/kubernetes_installer.py 2015-07-17 20:01:20 +0000 |
676 | +++ hooks/kubernetes_installer.py 2015-10-09 16:18:58 +0000 |
677 | @@ -1,9 +1,23 @@ |
678 | #!/usr/bin/env python |
679 | |
680 | +# Copyright 2015 The Kubernetes Authors All rights reserved. |
681 | +# |
682 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
683 | +# you may not use this file except in compliance with the License. |
684 | +# You may obtain a copy of the License at |
685 | +# |
686 | +# http://www.apache.org/licenses/LICENSE-2.0 |
687 | +# |
688 | +# Unless required by applicable law or agreed to in writing, software |
689 | +# distributed under the License is distributed on an "AS IS" BASIS, |
690 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
691 | +# See the License for the specific language governing permissions and |
692 | +# limitations under the License. |
693 | + |
694 | import os |
695 | import shlex |
696 | import subprocess |
697 | -from path import path |
698 | +from path import Path |
699 | |
700 | |
701 | def run(command, shell=False): |
702 | @@ -32,7 +46,7 @@ |
703 | 'kubelet': 'kubelet'} |
704 | self.arch = arch |
705 | self.version = version |
706 | - self.output_dir = path(output_dir) |
707 | + self.output_dir = Path(output_dir) |
708 | |
709 | def build(self, branch): |
710 | """ Build kubernetes from a github repository using the Makefile. """ |
711 | @@ -74,7 +88,7 @@ |
712 | print(make_what) |
713 | rc = subprocess.call(shlex.split(make_what), env=go_env) |
714 | |
715 | - def install(self, install_dir=path('/usr/local/bin')): |
716 | + def install(self, install_dir=Path('/usr/local/bin')): |
717 | """ Install kubernetes binary files from the output directory. """ |
718 | |
719 | if not install_dir.isdir(): |
720 | |
721 | === modified file 'hooks/setup.py' |
722 | --- hooks/setup.py 2015-09-24 19:50:43 +0000 |
723 | +++ hooks/setup.py 2015-10-09 16:18:58 +0000 |
724 | @@ -1,5 +1,19 @@ |
725 | #!/usr/bin/env python |
726 | |
727 | +# Copyright 2015 The Kubernetes Authors All rights reserved. |
728 | +# |
729 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
730 | +# you may not use this file except in compliance with the License. |
731 | +# You may obtain a copy of the License at |
732 | +# |
733 | +# http://www.apache.org/licenses/LICENSE-2.0 |
734 | +# |
735 | +# Unless required by applicable law or agreed to in writing, software |
736 | +# distributed under the License is distributed on an "AS IS" BASIS, |
737 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
738 | +# See the License for the specific language governing permissions and |
739 | +# limitations under the License. |
740 | + |
741 | |
742 | def pre_install(): |
743 | """ |
744 | |
745 | === modified file 'unit_tests/kubernetes_installer_test.py' |
746 | --- unit_tests/kubernetes_installer_test.py 2015-09-24 19:50:43 +0000 |
747 | +++ unit_tests/kubernetes_installer_test.py 2015-10-09 16:18:58 +0000 |
748 | @@ -1,8 +1,21 @@ |
749 | #!/usr/bin/env python |
750 | |
751 | +# Copyright 2015 The Kubernetes Authors All rights reserved. |
752 | +# |
753 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
754 | +# you may not use this file except in compliance with the License. |
755 | +# You may obtain a copy of the License at |
756 | +# |
757 | +# http://www.apache.org/licenses/LICENSE-2.0 |
758 | +# |
759 | +# Unless required by applicable law or agreed to in writing, software |
760 | +# distributed under the License is distributed on an "AS IS" BASIS, |
761 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
762 | +# See the License for the specific language governing permissions and |
763 | +# limitations under the License. |
764 | + |
765 | from mock import patch |
766 | from mock import ANY |
767 | -from path import path |
768 | from path import Path |
769 | import pytest |
770 | import subprocess |
771 | @@ -25,7 +38,7 @@ |
772 | assert output |
773 | assert 'kubernetes_installer.py' in output |
774 | |
775 | - invalid_directory = path('/not/a/real/directory') |
776 | + invalid_directory = Path('/not/a/real/directory') |
777 | assert not invalid_directory.exists() |
778 | invalid_command = 'ls {0}'.format(invalid_directory) |
779 | with pytest.raises(subprocess.CalledProcessError) as error: |
780 | @@ -54,24 +67,24 @@ |
781 | assert 'kubelet' in ki.aliases |
782 | assert ki.arch == 'i386' |
783 | assert ki.version == '3.0.1' |
784 | - assert ki.output_dir == path('/tmp/does_not_exist') |
785 | + assert ki.output_dir == Path('/tmp/does_not_exist') |
786 | |
787 | @patch('kubernetes_installer.run') |
788 | @patch('kubernetes_installer.subprocess.call') |
789 | def test_build(self, cmock, rmock): |
790 | """ Test the build method with master and non-master branches. """ |
791 | - directory = path('/tmp/kubernetes_installer_test/build') |
792 | + directory = Path('/tmp/kubernetes_installer_test/build') |
793 | ki = self.makeone('amd64', 'v99.00.11', directory) |
794 | assert not directory.exists(), 'The %s directory exists!' % directory |
795 | # Call the build method with "master" branch. |
796 | ki.build("master") |
797 | # TODO: run is called many times but mock only remembers last one. |
798 | rmock.assert_called_with('git reset --hard origin/master') |
799 | - |
800 | - # TODO: call is complex and hard to verify with mock, fix that. |
801 | - # this isn't oding what we think it should b edoing, magic mock |
802 | - # makes this tricky. |
803 | - # list['foo', 'baz'], env = ANY |
804 | + |
805 | + # TODO: call is complex and hard to verify with mock, fix that. |
806 | + # this is not doing what we think it should be doing, magic mock |
807 | + # makes this tricky. |
808 | + # list['foo', 'baz'], env = ANY |
809 | make_args = ['make', 'all', 'WHAT=cmd/kube-apiserver cmd/kubectl cmd/kube-controller-manager plugin/cmd/kube-scheduler cmd/kubelet cmd/kube-proxy'] # noqa |
810 | cmock.assert_called_once_with(make_args, env=ANY) |
811 | |
812 | @@ -79,7 +92,7 @@ |
813 | @patch('kubernetes_installer.subprocess.call') |
814 | def test_schenanigans(self, cmock, rmock): |
815 | """ Test the build method with master and non-master branches. """ |
816 | - directory = path('/tmp/kubernetes_installer_test/build') |
817 | + directory = Path('/tmp/kubernetes_installer_test/build') |
818 | ki = self.makeone('amd64', 'v99.00.11', directory) |
819 | assert not directory.exists(), 'The %s directory exists!' % directory |
820 | |
821 | @@ -88,14 +101,13 @@ |
822 | # TODO: run is called many times, but mock only remembers last one. |
823 | rmock.assert_called_with('git checkout -b v99.00.11 branch') |
824 | # TODO: call is complex and hard to verify with mock, fix that. |
825 | - assert cmock.called |
826 | - |
827 | + assert cmock.called |
828 | |
829 | directory.rmtree_p() |
830 | |
831 | def test_install(self): |
832 | """ Test the install method that it creates the correct links. """ |
833 | - directory = path('/tmp/kubernetes_installer_test/install') |
834 | + directory = Path('/tmp/kubernetes_installer_test/install') |
835 | ki = self.makeone('ppc64le', '1.2.3', directory) |
836 | assert not directory.exists(), 'The %s directory exits!' % directory |
837 | directory.makedirs_p() |
838 | |
839 | === modified file 'unit_tests/test_install.py' |
840 | --- unit_tests/test_install.py 2015-09-28 15:23:30 +0000 |
841 | +++ unit_tests/test_install.py 2015-10-09 16:18:58 +0000 |
842 | @@ -1,5 +1,19 @@ |
843 | #!/usr/bin/env python |
844 | |
845 | +# Copyright 2015 The Kubernetes Authors All rights reserved. |
846 | +# |
847 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
848 | +# you may not use this file except in compliance with the License. |
849 | +# You may obtain a copy of the License at |
850 | +# |
851 | +# http://www.apache.org/licenses/LICENSE-2.0 |
852 | +# |
853 | +# Unless required by applicable law or agreed to in writing, software |
854 | +# distributed under the License is distributed on an "AS IS" BASIS, |
855 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
856 | +# See the License for the specific language governing permissions and |
857 | +# limitations under the License. |
858 | + |
859 | from mock import patch, Mock, MagicMock |
860 | from path import Path |
861 | import pytest |
862 | @@ -12,26 +26,27 @@ |
863 | # Import the modules from the hook |
864 | import install |
865 | |
866 | + |
867 | class TestInstallHook(): |
868 | |
869 | - @patch('install.path') |
870 | + @patch('install.Path') |
871 | def test_update_rc_files(self, pmock): |
872 | """ |
873 | Test happy path on updating env files. Assuming everything |
874 | exists and is in place. |
875 | """ |
876 | - pmock.return_value.lines.return_value = ['line1', 'line2'] |
877 | + pmock.return_value.lines.return_value = ['line1', 'line2'] |
878 | install.update_rc_files(['test1', 'test2']) |
879 | pmock.return_value.write_lines.assert_called_with(['line1', 'line2', |
880 | 'test1', 'test2']) |
881 | |
882 | - def test_update_rc_files_with_nonexistant_path(self): |
883 | + def test_update_rc_files_with_nonexistent_path(self): |
884 | """ |
885 | Test an unhappy path if the bashrc/users do not exist. |
886 | """ |
887 | - p = [Path('/home/deadbeefdoesnotexist/.bashrc')] |
888 | + p = [Path('/home/deadbeefdoesnotexist/.bashrc')] |
889 | with pytest.raises(OSError) as exinfo: |
890 | - install.update_rc_files(['test1','test2'], rc_files=p) |
891 | + install.update_rc_files(['test1', 'test2'], rc_files=p) |
892 | |
893 | @patch('install.fetch') |
894 | @patch('install.hookenv') |
895 | @@ -40,8 +55,13 @@ |
896 | Verify we are calling the known essentials to build and syndicate |
897 | kubes. |
898 | """ |
899 | - pkgs = ['build-essential', 'git', |
900 | - 'make', 'nginx', 'python-pip', 'docker.io'] |
901 | + pkgs = ['apache2-utils', |
902 | + 'build-essential', |
903 | + 'docker.io', |
904 | + 'git', |
905 | + 'make', |
906 | + 'nginx', |
907 | + 'python-pip',] |
908 | install.install_packages() |
909 | hemock.log.assert_called_with('Installing Debian packages') |
910 | ftmock.filter_installed_packages.assert_called_with(pkgs) |
911 | @@ -49,22 +69,22 @@ |
912 | @patch('install.archiveurl.ArchiveUrlFetchHandler') |
913 | def test_go_download(self, aumock): |
914 | """ |
915 | - Test that we are actually handing off to charm-helpers to |
916 | - download a specific archive of Go. This is non-configurable so |
917 | - its reasonably safe to assume we're going to always do this, |
918 | - and when it changes we shall curse the brittleness of this test. |
919 | + Test that we are actually handing off to charm-helpers to |
920 | + download a specific archive of Go. This is non-configurable so |
921 | + its reasonably safe to assume we're going to always do this, |
922 | + and when it changes we shall curse the brittleness of this test. |
923 | """ |
924 | ins_mock = aumock.return_value.install |
925 | install.download_go() |
926 | - url = 'https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz' |
927 | - sha1='5020af94b52b65cc9b6f11d50a67e4bae07b0aff' |
928 | + url = 'https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz' # noqa |
929 | + sha1 = '5020af94b52b65cc9b6f11d50a67e4bae07b0aff' |
930 | ins_mock.assert_called_with(url, '/usr/local', sha1, 'sha1') |
931 | |
932 | @patch('install.subprocess') |
933 | def test_clone_repository(self, spmock): |
934 | """ |
935 | - We're not using a unit-tested git library - so ensure our subprocess |
936 | - call is consistent. If we change this, we want to know we've broken it. |
937 | + We're not using a unit-tested git library - so ensure our subprocess |
938 | + call is consistent. If we change this, we want to know we've broken it. |
939 | """ |
940 | install.clone_repository() |
941 | repo = 'https://github.com/kubernetes/kubernetes.git' |
942 | @@ -75,23 +95,22 @@ |
943 | @patch('install.download_go') |
944 | @patch('install.clone_repository') |
945 | @patch('install.update_rc_files') |
946 | + @patch('install.Path') |
947 | @patch('install.hookenv') |
948 | - def test_install_main(self, hemock, urmock, crmock, dgmock, ipmock): |
949 | + def test_install_main(self, hemock, pmock, urmock, crmock, dgmock, ipmock): |
950 | """ |
951 | Ensure the driver/main method is calling all the supporting methods. |
952 | """ |
953 | - strings = [ |
954 | - 'export GOROOT=/usr/local/go\n', |
955 | - 'export PATH=$PATH:$GOROOT/bin\n', |
956 | - 'export KUBE_MASTER_IP=0.0.0.0\n', |
957 | - 'export KUBERNETES_MASTER=http://$KUBE_MASTER_IP\n', |
958 | - ] |
959 | - |
960 | install.install() |
961 | - # magic mock overwrites method signatures, this isn't doing what |
962 | - # we think it should be doing |
963 | - # crmock.assert_called_once() |
964 | - # dgmock.assert_called_once() |
965 | - # crmock.assert_called_once() |
966 | - urmock.assert_called_with(strings) |
967 | - hemock.open_port.assert_called_with(8080) |
968 | + |
969 | + assert(ipmock.called) |
970 | + assert(dgmock.called) |
971 | + assert(crmock.called) |
972 | + assert(urmock.called) |
973 | + |
974 | + assert(pmock.called) |
975 | + pmock.assert_called_with('/srv/kubernetes') |
976 | + |
977 | + hemock.open_port.assert_any_call(443) |
978 | + hemock.open_port.assert_any_call(8080) |
979 | + hemock.open_port.assert_any_call(6443) |
+1 lGTM
Thanks for the hard work Matt.