Merge ~mthaddon/charm-k8s-openldap/+git/charm-k8s-openldap:password-action into charm-k8s-openldap:master
- Git
- lp:~mthaddon/charm-k8s-openldap/+git/charm-k8s-openldap
- password-action
- Merge into master
Proposed by
Tom Haddon
Status: | Merged |
---|---|
Approved by: | Tom Haddon |
Approved revision: | e5a26456306d412b7f8347c311f767f9f2856987 |
Merged at revision: | 82adb10deb3df836125a4b4638ca7a7b54ff99e3 |
Proposed branch: | ~mthaddon/charm-k8s-openldap/+git/charm-k8s-openldap:password-action |
Merge into: | charm-k8s-openldap:master |
Diff against target: |
649 lines (+322/-123) 8 files modified
README.md (+6/-1) actions.yaml (+3/-0) config.yaml (+0/-5) image-scripts/build-openldap.sh (+2/-0) requirements.txt (+1/-0) src/charm.py (+26/-34) src/leadership.py (+218/-0) tests/unit/test_charm.py (+66/-83) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Stuart Bishop (community) | Approve | ||
Canonical IS Reviewers | Pending | ||
Review via email: mp+395003@code.launchpad.net |
Commit message
Remove admin_password as a config option, add an action to retrieve it
Description of the change
Remove admin_password as a config option, add an action to retrieve it
To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Revision history for this message
Stuart Bishop (stub) wrote : | # |
Looks fine, comments inline. If you decide to drop RichLeaderData, the changes will be minor and unlikely to need a re-review.
review:
Approve
Revision history for this message
Stuart Bishop (stub) wrote : | # |
Still looks good, ta.
review:
Approve
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Change successfully merged at revision 82adb10deb3df83
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/README.md b/README.md |
2 | index bcde053..5fd477d 100644 |
3 | --- a/README.md |
4 | +++ b/README.md |
5 | @@ -11,10 +11,15 @@ cloud, attached to a controller using `juju add-k8s`. |
6 | |
7 | To deploy this charm with both OpenLDAP and PostgreSQL inside a k8s model, run: |
8 | |
9 | - juju deploy cs:~openldap-charmers/openldap --admin_password=admin |
10 | + juju deploy cs:~openldap-charmers/openldap |
11 | juju deploy cs:~postgresql-charmers/postgresql-k8s postgresql |
12 | juju add-relation openldap:db postgresql:db |
13 | |
14 | +To retrieve the auto-generated LDAP admin password, run, assuming you're using |
15 | +Juju 2.x: |
16 | + |
17 | + juju run-action openldap/0 --wait get-admin-password |
18 | + |
19 | ### Developing |
20 | |
21 | Notes for deploying a test setup locally using microk8s: |
22 | diff --git a/actions.yaml b/actions.yaml |
23 | new file mode 100644 |
24 | index 0000000..04086e6 |
25 | --- /dev/null |
26 | +++ b/actions.yaml |
27 | @@ -0,0 +1,3 @@ |
28 | +get-admin-password: |
29 | + description: > |
30 | + Retrieve the auto-generated LDAP admin password. |
31 | diff --git a/config.yaml b/config.yaml |
32 | index 5db4484..c442062 100644 |
33 | --- a/config.yaml |
34 | +++ b/config.yaml |
35 | @@ -18,8 +18,3 @@ options: |
36 | description: | |
37 | The password associated with image_username for accessing the registry specified in image_path. |
38 | default: '' |
39 | - admin_password: |
40 | - type: string |
41 | - description: | |
42 | - The password for the OpenLDAP admin user. |
43 | - default: '' |
44 | diff --git a/image-scripts/build-openldap.sh b/image-scripts/build-openldap.sh |
45 | index e696847..283466a 100755 |
46 | --- a/image-scripts/build-openldap.sh |
47 | +++ b/image-scripts/build-openldap.sh |
48 | @@ -6,6 +6,8 @@ apt-get update |
49 | apt-get -y dist-upgrade |
50 | apt-get --purge autoremove -y |
51 | apt-get install -y wget unixodbc make gcc unixodbc-dev groff-base ldap-utils odbc-postgresql gettext postgresql-client |
52 | +# Needed for juju actions to work. |
53 | +apt-get install -y python3-yaml |
54 | |
55 | echo "Making build dir" |
56 | mkdir -p /srv/build |
57 | diff --git a/requirements.txt b/requirements.txt |
58 | index fd6adcd..12aa474 100644 |
59 | --- a/requirements.txt |
60 | +++ b/requirements.txt |
61 | @@ -1,2 +1,3 @@ |
62 | +charmhelpers |
63 | ops |
64 | ops-lib-pgsql |
65 | diff --git a/src/charm.py b/src/charm.py |
66 | index 069d3dd..f8d5489 100755 |
67 | --- a/src/charm.py |
68 | +++ b/src/charm.py |
69 | @@ -4,6 +4,7 @@ |
70 | |
71 | import logging |
72 | |
73 | +from charmhelpers.core import host |
74 | import ops.lib |
75 | from ops.charm import ( |
76 | CharmBase, |
77 | @@ -17,10 +18,10 @@ from ops.framework import ( |
78 | ) |
79 | from ops.model import ( |
80 | ActiveStatus, |
81 | - BlockedStatus, |
82 | MaintenanceStatus, |
83 | WaitingStatus, |
84 | ) |
85 | +from leadership import LeadershipSettings |
86 | |
87 | |
88 | pgsql = ops.lib.use("pgsql", 1, "postgresql-charmers@lists.launchpad.net") |
89 | @@ -28,7 +29,6 @@ pgsql = ops.lib.use("pgsql", 1, "postgresql-charmers@lists.launchpad.net") |
90 | logger = logging.getLogger(__name__) |
91 | |
92 | |
93 | -REQUIRED_SETTINGS = ['admin_password'] |
94 | DATABASE_NAME = 'openldap' |
95 | |
96 | |
97 | @@ -50,10 +50,13 @@ class OpenLDAPK8sCharm(CharmBase): |
98 | def __init__(self, *args): |
99 | super().__init__(*args) |
100 | |
101 | + self.leader_data = LeadershipSettings() |
102 | + |
103 | self.framework.observe(self.on.start, self._configure_pod) |
104 | self.framework.observe(self.on.config_changed, self._configure_pod) |
105 | self.framework.observe(self.on.leader_elected, self._configure_pod) |
106 | self.framework.observe(self.on.upgrade_charm, self._configure_pod) |
107 | + self.framework.observe(self.on.get_admin_password_action, self._on_get_admin_password_action) |
108 | |
109 | # database |
110 | self._state.set_default(postgres=None) |
111 | @@ -93,30 +96,6 @@ class OpenLDAPK8sCharm(CharmBase): |
112 | |
113 | self.on.db_master_available.emit() |
114 | |
115 | - def _check_for_config_problems(self): |
116 | - """Check for some simple configuration problems and return a |
117 | - string describing them, otherwise return an empty string.""" |
118 | - problems = [] |
119 | - |
120 | - missing = self._missing_charm_settings() |
121 | - if missing: |
122 | - problems.append('required setting(s) empty: {}'.format(', '.join(sorted(missing)))) |
123 | - |
124 | - return '; '.join(filter(None, problems)) |
125 | - |
126 | - def _missing_charm_settings(self): |
127 | - """Check configuration setting dependencies and return a list of |
128 | - missing settings; otherwise return an empty list.""" |
129 | - config = self.model.config |
130 | - |
131 | - # Options in config.yaml are always present as at least "" |
132 | - # so config[setting] will not fail with a KeyError if unset via juju. |
133 | - # We define missing in terms of being required by the charm and explicitly |
134 | - # set rather than default from config.yaml. |
135 | - missing = {setting for setting in REQUIRED_SETTINGS if not config[setting]} |
136 | - |
137 | - return sorted(missing) |
138 | - |
139 | def _make_pod_spec(self): |
140 | """Return a pod spec with some core configuration.""" |
141 | config = self.model.config |
142 | @@ -142,9 +121,28 @@ class OpenLDAPK8sCharm(CharmBase): |
143 | ], |
144 | } |
145 | |
146 | + def _on_get_admin_password_action(self, event): |
147 | + """Handle on get-admin-password action.""" |
148 | + admin_password = self.get_admin_password() |
149 | + if admin_password: |
150 | + event.set_results({"admin-password": self.get_admin_password()}) |
151 | + else: |
152 | + event.fail("LDAP admin password has not yet been set, please retry later.") |
153 | + |
154 | + def get_admin_password(self): |
155 | + """Get the LDAP admin password. |
156 | + |
157 | + If a password hasn't been set yet, create one if we're the leader, |
158 | + or return an empty string if we're not.""" |
159 | + admin_password = self.leader_data["admin_password"] |
160 | + if not admin_password: |
161 | + if self.unit.is_leader: |
162 | + admin_password = host.pwgen(40) |
163 | + self.leader_data["admin_password"] = admin_password |
164 | + return admin_password |
165 | + |
166 | def _make_pod_config(self): |
167 | """Return an envConfig with some core configuration.""" |
168 | - config = self.model.config |
169 | pod_config = { |
170 | 'POSTGRES_NAME': self._state.postgres['dbname'], |
171 | 'POSTGRES_USER': self._state.postgres['user'], |
172 | @@ -153,8 +151,7 @@ class OpenLDAPK8sCharm(CharmBase): |
173 | 'POSTGRES_PORT': self._state.postgres['port'], |
174 | } |
175 | |
176 | - if 'admin_password' in config: |
177 | - pod_config['LDAP_ADMIN_PASSWORD'] = config['admin_password'] |
178 | + pod_config['LDAP_ADMIN_PASSWORD'] = self.get_admin_password() |
179 | |
180 | return pod_config |
181 | |
182 | @@ -169,11 +166,6 @@ class OpenLDAPK8sCharm(CharmBase): |
183 | self.unit.status = ActiveStatus() |
184 | return |
185 | |
186 | - problems = self._check_for_config_problems() |
187 | - if problems: |
188 | - self.unit.status = BlockedStatus(problems) |
189 | - return |
190 | - |
191 | self.unit.status = MaintenanceStatus('Assembling pod spec') |
192 | pod_spec = self._make_pod_spec() |
193 | |
194 | diff --git a/src/leadership.py b/src/leadership.py |
195 | new file mode 100644 |
196 | index 0000000..8e88779 |
197 | --- /dev/null |
198 | +++ b/src/leadership.py |
199 | @@ -0,0 +1,218 @@ |
200 | +# This file is part of the PostgreSQL k8s Charm for Juju. |
201 | +# Copyright 2020 Canonical Ltd. |
202 | +# |
203 | +# This program is free software: you can redistribute it and/or modify |
204 | +# it under the terms of the GNU General Public License version 3, as |
205 | +# published by the Free Software Foundation. |
206 | +# |
207 | +# This program is distributed in the hope that it will be useful, but |
208 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
209 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
210 | +# PURPOSE. See the GNU General Public License for more details. |
211 | +# |
212 | +# You should have received a copy of the GNU General Public License |
213 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
214 | + |
215 | +# TODO: Most of all of this module should move into the Operator Framework core |
216 | + |
217 | +import collections.abc |
218 | +import subprocess |
219 | +from typing import Any, Iterable, Dict, MutableMapping, Protocol |
220 | + |
221 | +import ops |
222 | +import yaml |
223 | + |
224 | + |
225 | +class _Codec(Protocol): |
226 | + def encode(self, value: Any) -> str: |
227 | + raise NotImplementedError("encode") |
228 | + |
229 | + def decode(self, key: str, value: str) -> Any: |
230 | + raise NotImplementedError("decode") |
231 | + |
232 | + |
233 | +class _ObjectABCMeta(type(ops.framework.Object), type(collections.abc.MutableMapping)): |
234 | + """This metaclass can go once the Operator Framework drops Python 3.5 support. |
235 | + |
236 | + Per ops.framework._Metaclass docstring. |
237 | + """ |
238 | + |
239 | + pass |
240 | + |
241 | + |
242 | +class _PeerData(ops.framework.Object, collections.abc.MutableMapping, metaclass=_ObjectABCMeta): |
243 | + """A bag of data shared between peer units. |
244 | + |
245 | + Only the leader can set data. All peer units can read. |
246 | + """ |
247 | + |
248 | + def __init__(self, parent: ops.framework.Object, key: str, _store: MutableMapping, _codec: _Codec): |
249 | + super().__init__(parent, key) |
250 | + self._store = _store |
251 | + self._codec = _codec |
252 | + self._prefix = self.handle.path |
253 | + |
254 | + def _prefixed_key(self, key: str) -> str: |
255 | + return self._prefix + "/" + key |
256 | + |
257 | + def __getitem__(self, key: str) -> Any: |
258 | + if not isinstance(key, str): |
259 | + raise TypeError(f"key must be a string, got {repr(key)} {type(key)}") |
260 | + raw = self._store[self._prefixed_key(key)] |
261 | + return self._codec.decode(key, raw) |
262 | + |
263 | + def __setitem__(self, key: str, value: Any) -> None: |
264 | + if not isinstance(key, str): |
265 | + raise TypeError(f"key must be a string, got {repr(key)} {type(key)}") |
266 | + if not self.model.unit.is_leader(): |
267 | + raise RuntimeError("non-leader attempting to set peer data") |
268 | + self._store[self._prefixed_key(key)] = self._codec.encode(value) |
269 | + |
270 | + def __delitem__(self, key: str) -> None: |
271 | + if not isinstance(key, str): |
272 | + raise TypeError(f"key must be a string, got {repr(key)} {type(key)}") |
273 | + if not self.model.unit.is_leader(): |
274 | + raise RuntimeError("non-leader attempting to set peer data") |
275 | + del self._store[self._prefixed_key(key)] |
276 | + |
277 | + def __iter__(self) -> Iterable[str]: |
278 | + return iter(self._store) |
279 | + |
280 | + def __len__(self) -> int: |
281 | + return len(self._store) |
282 | + |
283 | + |
284 | +class LegacyLeaderData(_PeerData): |
285 | + """Raw Juju Leadership settings, a bag of data shared between peers. |
286 | + |
287 | + Only the leader can set data. All peer units can read. |
288 | + |
289 | + Behavior matches the Juju leader-get and leader-set tools; keys and |
290 | + values must be strings, Setting an value to the empty string is the |
291 | + same as deleting the entry, and accessing a missing entry will |
292 | + return an empty string. |
293 | + |
294 | + This class provides access to legacy Juju Leadership data, and namespace |
295 | + collisions may occur if multiple components attempt to use the same key. |
296 | + """ |
297 | + |
298 | + def __init__(self, parent, key=""): |
299 | + super().__init__(parent, key, LeadershipSettings(), _RawCodec()) |
300 | + |
301 | + def _prefixed_key(self, key: str) -> str: |
302 | + return key |
303 | + |
304 | + |
305 | +class RawLeaderData(_PeerData): |
306 | + """Raw Juju Leadership settings, a bag of data shared between peers. |
307 | + |
308 | + Only the leader can set data. All peer units can read. |
309 | + |
310 | + Behavior matches the Juju leader-get and leader-set tools; keys and |
311 | + values must be strings, Setting an value to the empty string is the |
312 | + same as deleting the entry, and accessing a missing entry will |
313 | + return an empty string. |
314 | + |
315 | + Keys are automatically prefixed to avoid namespace collisions in the |
316 | + Juju Leadership settings. |
317 | + """ |
318 | + |
319 | + def __init__(self, parent, key=""): |
320 | + super().__init__(parent, key, LeadershipSettings(), _RawCodec()) |
321 | + |
322 | + |
323 | +class RichLeaderData(_PeerData): |
324 | + """Encoded Juju Leadership settings, a bag of data shared between peers. |
325 | + |
326 | + Only the leader can set data. All peer units can read. |
327 | + |
328 | + Operates as a standard Python MutableMapping. Keys must be strings. |
329 | + Values may be anything that the yaml library can marshal. |
330 | + |
331 | + Keys are automatically prefixed to avoid namespace collisions in the |
332 | + Juju Leadership settings. |
333 | + """ |
334 | + |
335 | + def __init__(self, parent, key=""): |
336 | + super().__init__(parent, key, LeadershipSettings(), _YAMLCodec()) |
337 | + |
338 | + |
339 | +class _YAMLCodec(object): |
340 | + def encode(self, value: Any) -> str: |
341 | + return yaml.safe_dump(value) |
342 | + |
343 | + def decode(self, key: str, value: str) -> Any: |
344 | + if not value: |
345 | + # Key never existed or was deleted. If set to |
346 | + # empty string or none, value will contain |
347 | + # the YAML representation. |
348 | + raise KeyError(key) |
349 | + return yaml.safe_load(value) |
350 | + |
351 | + |
352 | +class _RawCodec(object): |
353 | + def encode(self, value: Any) -> str: |
354 | + if not isinstance(value, str): |
355 | + raise TypeError(f"{self.__class__.__name__} only supports str values, got {type(value)}") |
356 | + return value |
357 | + |
358 | + def decode(self, value: str) -> Any: |
359 | + return value |
360 | + |
361 | + |
362 | +class LeadershipSettings(collections.abc.MutableMapping): |
363 | + """Juju Leadership Settings data. |
364 | + |
365 | + This class provides direct access to the Juju Leadership Settings, |
366 | + a bag of data shared between peer units. Only the leader can set |
367 | + items. Keys all share the same namespace, so beware of collisions. |
368 | + |
369 | + This MutableMapping implements Juju behavior. Only strings are |
370 | + supported as keys and values. Deleting an entry is the same as |
371 | + setting it to the empty string. Attempting to read a missing |
372 | + key will return the empty string (this class will never raise |
373 | + a KeyError). |
374 | + """ |
375 | + |
376 | + __cls_cache = None |
377 | + |
378 | + @property |
379 | + def _cache_loaded(self) -> bool: |
380 | + return self.__class__.__cls_cache is not None |
381 | + |
382 | + @property |
383 | + def _cache(self) -> Dict[str, str]: |
384 | + # There might be multiple instances of LeadershipSettings, but |
385 | + # the backend is shared, so the cache needs to be a class |
386 | + # attribute. |
387 | + cls = self.__class__ |
388 | + if cls.__cls_cache is None: |
389 | + cmd = ["leader-get", "--format=yaml"] |
390 | + cls.__cls_cache = yaml.safe_load(subprocess.check_output(cmd).decode("UTF-8")) or {} |
391 | + return cls.__cls_cache |
392 | + |
393 | + def __getitem__(self, key: str) -> str: |
394 | + return self._cache.get(key, "") |
395 | + |
396 | + def __setitem__(self, key: str, value: str): |
397 | + if "=" in key: |
398 | + # Leave other validation to the leader-set tool |
399 | + raise RuntimeError(f"LeadershipSettings keys may not contain '=', got {key}") |
400 | + if value is None: |
401 | + value = "" |
402 | + cmd = ["leader-set", f"{key}={value}"] |
403 | + subprocess.check_call(cmd) |
404 | + if self._cache_loaded: |
405 | + if value == "": |
406 | + del self._cache[key] |
407 | + else: |
408 | + self._cache[key] = value |
409 | + |
410 | + def __delitem__(self, key: str): |
411 | + self[key] = "" |
412 | + |
413 | + def __iter__(self) -> Iterable[str]: |
414 | + return iter(self._cache) |
415 | + |
416 | + def __len__(self) -> int: |
417 | + return len(self._cache) |
418 | diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py |
419 | index 717c937..00aba40 100644 |
420 | --- a/tests/unit/test_charm.py |
421 | +++ b/tests/unit/test_charm.py |
422 | @@ -2,13 +2,13 @@ |
423 | # See LICENSE file for licensing details. |
424 | |
425 | import unittest |
426 | +from unittest.mock import patch |
427 | |
428 | from charm import OpenLDAPK8sCharm |
429 | from collections import namedtuple |
430 | from ops import testing |
431 | from ops.model import ( |
432 | ActiveStatus, |
433 | - BlockedStatus, |
434 | WaitingStatus, |
435 | ) |
436 | |
437 | @@ -18,34 +18,24 @@ CONFIG_ALL = { |
438 | 'image_path': 'example.com/openldap:latest', |
439 | 'image_username': 'image_user', |
440 | 'image_password': 'image_pass', |
441 | - 'admin_password': 'badmin_password', |
442 | } |
443 | |
444 | CONFIG_IMAGE_NO_CREDS = { |
445 | 'image_path': 'example.com/openldap:latest', |
446 | 'image_username': '', |
447 | 'image_password': '', |
448 | - 'admin_password': 'badmin_password', |
449 | } |
450 | |
451 | CONFIG_IMAGE_NO_IMAGE = { |
452 | 'image_path': '', |
453 | 'image_username': '', |
454 | 'image_password': '', |
455 | - 'admin_password': 'badmin_password', |
456 | } |
457 | |
458 | CONFIG_IMAGE_NO_PASSWORD = { |
459 | 'image_path': 'example.com/openldap:latest', |
460 | 'image_username': 'production', |
461 | 'image_password': '', |
462 | - 'admin_password': 'badmin_password', |
463 | -} |
464 | - |
465 | -CONFIG_NO_ADMIN_PASSWORD = { |
466 | - 'image_path': 'example.com/openldap:latest', |
467 | - 'image_username': 'production', |
468 | - 'image_password': '', |
469 | } |
470 | |
471 | DB_URI = { |
472 | @@ -63,12 +53,6 @@ class TestOpenLDAPK8sCharmHooksDisabled(unittest.TestCase): |
473 | self.harness.begin() |
474 | self.harness.disable_hooks() |
475 | |
476 | - def test_check_for_config_problems(self): |
477 | - """Config problems as a string.""" |
478 | - self.harness.update_config(CONFIG_NO_ADMIN_PASSWORD) |
479 | - expected = 'required setting(s) empty: admin_password' |
480 | - self.assertEqual(self.harness.charm._check_for_config_problems(), expected) |
481 | - |
482 | def test_make_pod_config(self): |
483 | """Make basic, correct pod config.""" |
484 | self.harness.update_config(CONFIG_IMAGE_NO_CREDS) |
485 | @@ -81,66 +65,58 @@ class TestOpenLDAPK8sCharmHooksDisabled(unittest.TestCase): |
486 | 'POSTGRES_PORT': '5432', |
487 | 'LDAP_ADMIN_PASSWORD': 'badmin_password', |
488 | } |
489 | - self.assertEqual(self.harness.charm._make_pod_config(), expected) |
490 | - |
491 | - def test_make_pod_config_no_password(self): |
492 | - """Missing admin password in config shouldn't explode at least.""" |
493 | - self.harness.update_config(CONFIG_NO_ADMIN_PASSWORD) |
494 | - self.harness.charm._state.postgres = DB_URI |
495 | - expected = { |
496 | - 'LDAP_ADMIN_PASSWORD': '', |
497 | - 'POSTGRES_NAME': 'openldap', |
498 | - 'POSTGRES_USER': 'ldap_user', |
499 | - 'POSTGRES_PASSWORD': 'ldap_password', |
500 | - 'POSTGRES_HOST': '1.1.1.1', |
501 | - 'POSTGRES_PORT': '5432', |
502 | - } |
503 | - self.assertEqual(self.harness.charm._make_pod_config(), expected) |
504 | + with patch.object(self.harness.charm, "get_admin_password") as get_admin_password: |
505 | + get_admin_password.return_value = 'badmin_password' |
506 | + self.assertEqual(self.harness.charm._make_pod_config(), expected) |
507 | |
508 | def test_make_pod_spec(self): |
509 | """Basic, correct pod spec.""" |
510 | self.harness.update_config(CONFIG_ALL) |
511 | self.harness.charm._state.postgres = DB_URI |
512 | - expected = { |
513 | - 'version': 3, |
514 | - 'containers': [ |
515 | - { |
516 | - 'name': 'openldap', |
517 | - 'imageDetails': { |
518 | - 'imagePath': 'example.com/openldap:latest', |
519 | - 'username': 'image_user', |
520 | - 'password': 'image_pass', |
521 | - }, |
522 | - 'ports': [{'containerPort': 389, 'protocol': 'TCP'}], |
523 | - 'envConfig': self.harness.charm._make_pod_config(), |
524 | - 'kubernetes': { |
525 | - 'readinessProbe': {'tcpSocket': {'port': 389}}, |
526 | - }, |
527 | - } |
528 | - ], |
529 | - } |
530 | - self.assertEqual(self.harness.charm._make_pod_spec(), expected) |
531 | + with patch.object(self.harness.charm, "get_admin_password") as get_admin_password: |
532 | + get_admin_password.return_value = 'badmin_password' |
533 | + expected = { |
534 | + 'version': 3, |
535 | + 'containers': [ |
536 | + { |
537 | + 'name': 'openldap', |
538 | + 'imageDetails': { |
539 | + 'imagePath': 'example.com/openldap:latest', |
540 | + 'username': 'image_user', |
541 | + 'password': 'image_pass', |
542 | + }, |
543 | + 'ports': [{'containerPort': 389, 'protocol': 'TCP'}], |
544 | + 'envConfig': self.harness.charm._make_pod_config(), |
545 | + 'kubernetes': { |
546 | + 'readinessProbe': {'tcpSocket': {'port': 389}}, |
547 | + }, |
548 | + } |
549 | + ], |
550 | + } |
551 | + self.assertEqual(self.harness.charm._make_pod_spec(), expected) |
552 | |
553 | def test_make_pod_spec_no_image_creds(self): |
554 | self.harness.update_config(CONFIG_IMAGE_NO_CREDS) |
555 | self.harness.charm._state.postgres = DB_URI |
556 | - expected = { |
557 | - 'version': 3, |
558 | - 'containers': [ |
559 | - { |
560 | - 'name': 'openldap', |
561 | - 'imageDetails': { |
562 | - 'imagePath': 'example.com/openldap:latest', |
563 | - }, |
564 | - 'ports': [{'containerPort': 389, 'protocol': 'TCP'}], |
565 | - 'envConfig': self.harness.charm._make_pod_config(), |
566 | - 'kubernetes': { |
567 | - 'readinessProbe': {'tcpSocket': {'port': 389}}, |
568 | - }, |
569 | - } |
570 | - ], |
571 | - } |
572 | - self.assertEqual(self.harness.charm._make_pod_spec(), expected) |
573 | + with patch.object(self.harness.charm, "get_admin_password") as get_admin_password: |
574 | + get_admin_password.return_value = 'badmin_password' |
575 | + expected = { |
576 | + 'version': 3, |
577 | + 'containers': [ |
578 | + { |
579 | + 'name': 'openldap', |
580 | + 'imageDetails': { |
581 | + 'imagePath': 'example.com/openldap:latest', |
582 | + }, |
583 | + 'ports': [{'containerPort': 389, 'protocol': 'TCP'}], |
584 | + 'envConfig': self.harness.charm._make_pod_config(), |
585 | + 'kubernetes': { |
586 | + 'readinessProbe': {'tcpSocket': {'port': 389}}, |
587 | + }, |
588 | + } |
589 | + ], |
590 | + } |
591 | + self.assertEqual(self.harness.charm._make_pod_spec(), expected) |
592 | |
593 | def test_configure_pod_no_postgres_relation(self): |
594 | """Check that we block correctly without a Postgres relation.""" |
595 | @@ -161,17 +137,6 @@ class TestOpenLDAPK8sCharmHooksDisabled(unittest.TestCase): |
596 | self.harness.charm._configure_pod(mock_event) |
597 | self.assertEqual(self.harness.charm.unit.status, expected) |
598 | |
599 | - def test_configure_pod_config_problems(self): |
600 | - """Test pod config with missing juju config options.""" |
601 | - mock_event = MagicMock() |
602 | - |
603 | - self.harness.update_config(CONFIG_NO_ADMIN_PASSWORD) |
604 | - self.harness.charm._state.postgres = DB_URI |
605 | - self.harness.set_leader(True) |
606 | - expected = BlockedStatus('required setting(s) empty: admin_password') |
607 | - self.harness.charm._configure_pod(mock_event) |
608 | - self.assertEqual(self.harness.charm.unit.status, expected) |
609 | - |
610 | def test_configure_pod(self): |
611 | """Test pod configuration with everything working appropriately.""" |
612 | mock_event = MagicMock() |
613 | @@ -180,8 +145,10 @@ class TestOpenLDAPK8sCharmHooksDisabled(unittest.TestCase): |
614 | self.harness.charm._state.postgres = DB_URI |
615 | self.harness.set_leader(True) |
616 | expected = ActiveStatus() |
617 | - self.harness.charm._configure_pod(mock_event) |
618 | - self.assertEqual(self.harness.charm.unit.status, expected) |
619 | + with patch.object(self.harness.charm, "get_admin_password") as get_admin_password: |
620 | + get_admin_password.return_value = 'badmin_password' |
621 | + self.harness.charm._configure_pod(mock_event) |
622 | + self.assertEqual(self.harness.charm.unit.status, expected) |
623 | |
624 | def test_on_database_relation_joined(self): |
625 | mock_event = MagicMock() |
626 | @@ -206,5 +173,21 @@ class TestOpenLDAPK8sCharmHooksDisabled(unittest.TestCase): |
627 | mock_event.master = master |
628 | mock_event.database = "openldap" |
629 | |
630 | - self.harness.charm._on_master_changed(mock_event) |
631 | - self.assertEqual(self.harness.charm._state.postgres['dbname'], "openldap") |
632 | + with patch.object(self.harness.charm, "get_admin_password") as get_admin_password: |
633 | + get_admin_password.return_value = 'badmin_password' |
634 | + self.harness.charm._on_master_changed(mock_event) |
635 | + self.assertEqual(self.harness.charm._state.postgres['dbname'], "openldap") |
636 | + |
637 | + def test_on_get_admin_password_action(self): |
638 | + mock_event = MagicMock() |
639 | + |
640 | + self.harness.update_config(CONFIG_ALL) |
641 | + with patch.object(self.harness.charm, "get_admin_password") as get_admin_password: |
642 | + get_admin_password.return_value = 'badmin_password' |
643 | + self.harness.charm._on_get_admin_password_action(mock_event) |
644 | + mock_event.set_results.assert_called_with({"admin-password": "badmin_password"}) |
645 | + # And now return an empty result. |
646 | + get_admin_password.return_value = "" |
647 | + mock_event.reset_mock() |
648 | + self.harness.charm._on_get_admin_password_action(mock_event) |
649 | + mock_event.fail.assert_called_with("LDAP admin password has not yet been set, please retry later.") |
This merge proposal is being monitored by mergebot. Change the status to Approved to merge.