Merge ~mthaddon/charm-k8s-openldap/+git/charm-k8s-openldap:password-action into charm-k8s-openldap: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)
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 :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

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 82adb10deb3df836125a4b4638ca7a7b54ff99e3

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/README.md b/README.md
index bcde053..5fd477d 100644
--- a/README.md
+++ b/README.md
@@ -11,10 +11,15 @@ cloud, attached to a controller using `juju add-k8s`.
1111
12To deploy this charm with both OpenLDAP and PostgreSQL inside a k8s model, run:12To deploy this charm with both OpenLDAP and PostgreSQL inside a k8s model, run:
1313
14 juju deploy cs:~openldap-charmers/openldap --admin_password=admin14 juju deploy cs:~openldap-charmers/openldap
15 juju deploy cs:~postgresql-charmers/postgresql-k8s postgresql15 juju deploy cs:~postgresql-charmers/postgresql-k8s postgresql
16 juju add-relation openldap:db postgresql:db16 juju add-relation openldap:db postgresql:db
1717
18To retrieve the auto-generated LDAP admin password, run, assuming you're using
19Juju 2.x:
20
21 juju run-action openldap/0 --wait get-admin-password
22
18### Developing23### Developing
1924
20Notes for deploying a test setup locally using microk8s:25Notes for deploying a test setup locally using microk8s:
diff --git a/actions.yaml b/actions.yaml
21new file mode 10064426new file mode 100644
index 0000000..04086e6
--- /dev/null
+++ b/actions.yaml
@@ -0,0 +1,3 @@
1get-admin-password:
2 description: >
3 Retrieve the auto-generated LDAP admin password.
diff --git a/config.yaml b/config.yaml
index 5db4484..c442062 100644
--- a/config.yaml
+++ b/config.yaml
@@ -18,8 +18,3 @@ options:
18 description: |18 description: |
19 The password associated with image_username for accessing the registry specified in image_path.19 The password associated with image_username for accessing the registry specified in image_path.
20 default: ''20 default: ''
21 admin_password:
22 type: string
23 description: |
24 The password for the OpenLDAP admin user.
25 default: ''
diff --git a/image-scripts/build-openldap.sh b/image-scripts/build-openldap.sh
index e696847..283466a 100755
--- a/image-scripts/build-openldap.sh
+++ b/image-scripts/build-openldap.sh
@@ -6,6 +6,8 @@ apt-get update
6apt-get -y dist-upgrade6apt-get -y dist-upgrade
7apt-get --purge autoremove -y7apt-get --purge autoremove -y
8apt-get install -y wget unixodbc make gcc unixodbc-dev groff-base ldap-utils odbc-postgresql gettext postgresql-client8apt-get install -y wget unixodbc make gcc unixodbc-dev groff-base ldap-utils odbc-postgresql gettext postgresql-client
9# Needed for juju actions to work.
10apt-get install -y python3-yaml
911
10echo "Making build dir"12echo "Making build dir"
11mkdir -p /srv/build13mkdir -p /srv/build
diff --git a/requirements.txt b/requirements.txt
index fd6adcd..12aa474 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,3 @@
1charmhelpers
1ops2ops
2ops-lib-pgsql3ops-lib-pgsql
diff --git a/src/charm.py b/src/charm.py
index 069d3dd..f8d5489 100755
--- a/src/charm.py
+++ b/src/charm.py
@@ -4,6 +4,7 @@
44
5import logging5import logging
66
7from charmhelpers.core import host
7import ops.lib8import ops.lib
8from ops.charm import (9from ops.charm import (
9 CharmBase,10 CharmBase,
@@ -17,10 +18,10 @@ from ops.framework import (
17)18)
18from ops.model import (19from ops.model import (
19 ActiveStatus,20 ActiveStatus,
20 BlockedStatus,
21 MaintenanceStatus,21 MaintenanceStatus,
22 WaitingStatus,22 WaitingStatus,
23)23)
24from leadership import LeadershipSettings
2425
2526
26pgsql = ops.lib.use("pgsql", 1, "postgresql-charmers@lists.launchpad.net")27pgsql = ops.lib.use("pgsql", 1, "postgresql-charmers@lists.launchpad.net")
@@ -28,7 +29,6 @@ pgsql = ops.lib.use("pgsql", 1, "postgresql-charmers@lists.launchpad.net")
28logger = logging.getLogger(__name__)29logger = logging.getLogger(__name__)
2930
3031
31REQUIRED_SETTINGS = ['admin_password']
32DATABASE_NAME = 'openldap'32DATABASE_NAME = 'openldap'
3333
3434
@@ -50,10 +50,13 @@ class OpenLDAPK8sCharm(CharmBase):
50 def __init__(self, *args):50 def __init__(self, *args):
51 super().__init__(*args)51 super().__init__(*args)
5252
53 self.leader_data = LeadershipSettings()
54
53 self.framework.observe(self.on.start, self._configure_pod)55 self.framework.observe(self.on.start, self._configure_pod)
54 self.framework.observe(self.on.config_changed, self._configure_pod)56 self.framework.observe(self.on.config_changed, self._configure_pod)
55 self.framework.observe(self.on.leader_elected, self._configure_pod)57 self.framework.observe(self.on.leader_elected, self._configure_pod)
56 self.framework.observe(self.on.upgrade_charm, self._configure_pod)58 self.framework.observe(self.on.upgrade_charm, self._configure_pod)
59 self.framework.observe(self.on.get_admin_password_action, self._on_get_admin_password_action)
5760
58 # database61 # database
59 self._state.set_default(postgres=None)62 self._state.set_default(postgres=None)
@@ -93,30 +96,6 @@ class OpenLDAPK8sCharm(CharmBase):
9396
94 self.on.db_master_available.emit()97 self.on.db_master_available.emit()
9598
96 def _check_for_config_problems(self):
97 """Check for some simple configuration problems and return a
98 string describing them, otherwise return an empty string."""
99 problems = []
100
101 missing = self._missing_charm_settings()
102 if missing:
103 problems.append('required setting(s) empty: {}'.format(', '.join(sorted(missing))))
104
105 return '; '.join(filter(None, problems))
106
107 def _missing_charm_settings(self):
108 """Check configuration setting dependencies and return a list of
109 missing settings; otherwise return an empty list."""
110 config = self.model.config
111
112 # Options in config.yaml are always present as at least ""
113 # so config[setting] will not fail with a KeyError if unset via juju.
114 # We define missing in terms of being required by the charm and explicitly
115 # set rather than default from config.yaml.
116 missing = {setting for setting in REQUIRED_SETTINGS if not config[setting]}
117
118 return sorted(missing)
119
120 def _make_pod_spec(self):99 def _make_pod_spec(self):
121 """Return a pod spec with some core configuration."""100 """Return a pod spec with some core configuration."""
122 config = self.model.config101 config = self.model.config
@@ -142,9 +121,28 @@ class OpenLDAPK8sCharm(CharmBase):
142 ],121 ],
143 }122 }
144123
124 def _on_get_admin_password_action(self, event):
125 """Handle on get-admin-password action."""
126 admin_password = self.get_admin_password()
127 if admin_password:
128 event.set_results({"admin-password": self.get_admin_password()})
129 else:
130 event.fail("LDAP admin password has not yet been set, please retry later.")
131
132 def get_admin_password(self):
133 """Get the LDAP admin password.
134
135 If a password hasn't been set yet, create one if we're the leader,
136 or return an empty string if we're not."""
137 admin_password = self.leader_data["admin_password"]
138 if not admin_password:
139 if self.unit.is_leader:
140 admin_password = host.pwgen(40)
141 self.leader_data["admin_password"] = admin_password
142 return admin_password
143
145 def _make_pod_config(self):144 def _make_pod_config(self):
146 """Return an envConfig with some core configuration."""145 """Return an envConfig with some core configuration."""
147 config = self.model.config
148 pod_config = {146 pod_config = {
149 'POSTGRES_NAME': self._state.postgres['dbname'],147 'POSTGRES_NAME': self._state.postgres['dbname'],
150 'POSTGRES_USER': self._state.postgres['user'],148 'POSTGRES_USER': self._state.postgres['user'],
@@ -153,8 +151,7 @@ class OpenLDAPK8sCharm(CharmBase):
153 'POSTGRES_PORT': self._state.postgres['port'],151 'POSTGRES_PORT': self._state.postgres['port'],
154 }152 }
155153
156 if 'admin_password' in config:154 pod_config['LDAP_ADMIN_PASSWORD'] = self.get_admin_password()
157 pod_config['LDAP_ADMIN_PASSWORD'] = config['admin_password']
158155
159 return pod_config156 return pod_config
160157
@@ -169,11 +166,6 @@ class OpenLDAPK8sCharm(CharmBase):
169 self.unit.status = ActiveStatus()166 self.unit.status = ActiveStatus()
170 return167 return
171168
172 problems = self._check_for_config_problems()
173 if problems:
174 self.unit.status = BlockedStatus(problems)
175 return
176
177 self.unit.status = MaintenanceStatus('Assembling pod spec')169 self.unit.status = MaintenanceStatus('Assembling pod spec')
178 pod_spec = self._make_pod_spec()170 pod_spec = self._make_pod_spec()
179171
diff --git a/src/leadership.py b/src/leadership.py
180new file mode 100644172new file mode 100644
index 0000000..8e88779
--- /dev/null
+++ b/src/leadership.py
@@ -0,0 +1,218 @@
1# This file is part of the PostgreSQL k8s Charm for Juju.
2# Copyright 2020 Canonical Ltd.
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License version 3, as
6# published by the Free Software Foundation.
7#
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16# TODO: Most of all of this module should move into the Operator Framework core
17
18import collections.abc
19import subprocess
20from typing import Any, Iterable, Dict, MutableMapping, Protocol
21
22import ops
23import yaml
24
25
26class _Codec(Protocol):
27 def encode(self, value: Any) -> str:
28 raise NotImplementedError("encode")
29
30 def decode(self, key: str, value: str) -> Any:
31 raise NotImplementedError("decode")
32
33
34class _ObjectABCMeta(type(ops.framework.Object), type(collections.abc.MutableMapping)):
35 """This metaclass can go once the Operator Framework drops Python 3.5 support.
36
37 Per ops.framework._Metaclass docstring.
38 """
39
40 pass
41
42
43class _PeerData(ops.framework.Object, collections.abc.MutableMapping, metaclass=_ObjectABCMeta):
44 """A bag of data shared between peer units.
45
46 Only the leader can set data. All peer units can read.
47 """
48
49 def __init__(self, parent: ops.framework.Object, key: str, _store: MutableMapping, _codec: _Codec):
50 super().__init__(parent, key)
51 self._store = _store
52 self._codec = _codec
53 self._prefix = self.handle.path
54
55 def _prefixed_key(self, key: str) -> str:
56 return self._prefix + "/" + key
57
58 def __getitem__(self, key: str) -> Any:
59 if not isinstance(key, str):
60 raise TypeError(f"key must be a string, got {repr(key)} {type(key)}")
61 raw = self._store[self._prefixed_key(key)]
62 return self._codec.decode(key, raw)
63
64 def __setitem__(self, key: str, value: Any) -> None:
65 if not isinstance(key, str):
66 raise TypeError(f"key must be a string, got {repr(key)} {type(key)}")
67 if not self.model.unit.is_leader():
68 raise RuntimeError("non-leader attempting to set peer data")
69 self._store[self._prefixed_key(key)] = self._codec.encode(value)
70
71 def __delitem__(self, key: str) -> None:
72 if not isinstance(key, str):
73 raise TypeError(f"key must be a string, got {repr(key)} {type(key)}")
74 if not self.model.unit.is_leader():
75 raise RuntimeError("non-leader attempting to set peer data")
76 del self._store[self._prefixed_key(key)]
77
78 def __iter__(self) -> Iterable[str]:
79 return iter(self._store)
80
81 def __len__(self) -> int:
82 return len(self._store)
83
84
85class LegacyLeaderData(_PeerData):
86 """Raw Juju Leadership settings, a bag of data shared between peers.
87
88 Only the leader can set data. All peer units can read.
89
90 Behavior matches the Juju leader-get and leader-set tools; keys and
91 values must be strings, Setting an value to the empty string is the
92 same as deleting the entry, and accessing a missing entry will
93 return an empty string.
94
95 This class provides access to legacy Juju Leadership data, and namespace
96 collisions may occur if multiple components attempt to use the same key.
97 """
98
99 def __init__(self, parent, key=""):
100 super().__init__(parent, key, LeadershipSettings(), _RawCodec())
101
102 def _prefixed_key(self, key: str) -> str:
103 return key
104
105
106class RawLeaderData(_PeerData):
107 """Raw Juju Leadership settings, a bag of data shared between peers.
108
109 Only the leader can set data. All peer units can read.
110
111 Behavior matches the Juju leader-get and leader-set tools; keys and
112 values must be strings, Setting an value to the empty string is the
113 same as deleting the entry, and accessing a missing entry will
114 return an empty string.
115
116 Keys are automatically prefixed to avoid namespace collisions in the
117 Juju Leadership settings.
118 """
119
120 def __init__(self, parent, key=""):
121 super().__init__(parent, key, LeadershipSettings(), _RawCodec())
122
123
124class RichLeaderData(_PeerData):
125 """Encoded Juju Leadership settings, a bag of data shared between peers.
126
127 Only the leader can set data. All peer units can read.
128
129 Operates as a standard Python MutableMapping. Keys must be strings.
130 Values may be anything that the yaml library can marshal.
131
132 Keys are automatically prefixed to avoid namespace collisions in the
133 Juju Leadership settings.
134 """
135
136 def __init__(self, parent, key=""):
137 super().__init__(parent, key, LeadershipSettings(), _YAMLCodec())
138
139
140class _YAMLCodec(object):
141 def encode(self, value: Any) -> str:
142 return yaml.safe_dump(value)
143
144 def decode(self, key: str, value: str) -> Any:
145 if not value:
146 # Key never existed or was deleted. If set to
147 # empty string or none, value will contain
148 # the YAML representation.
149 raise KeyError(key)
150 return yaml.safe_load(value)
151
152
153class _RawCodec(object):
154 def encode(self, value: Any) -> str:
155 if not isinstance(value, str):
156 raise TypeError(f"{self.__class__.__name__} only supports str values, got {type(value)}")
157 return value
158
159 def decode(self, value: str) -> Any:
160 return value
161
162
163class LeadershipSettings(collections.abc.MutableMapping):
164 """Juju Leadership Settings data.
165
166 This class provides direct access to the Juju Leadership Settings,
167 a bag of data shared between peer units. Only the leader can set
168 items. Keys all share the same namespace, so beware of collisions.
169
170 This MutableMapping implements Juju behavior. Only strings are
171 supported as keys and values. Deleting an entry is the same as
172 setting it to the empty string. Attempting to read a missing
173 key will return the empty string (this class will never raise
174 a KeyError).
175 """
176
177 __cls_cache = None
178
179 @property
180 def _cache_loaded(self) -> bool:
181 return self.__class__.__cls_cache is not None
182
183 @property
184 def _cache(self) -> Dict[str, str]:
185 # There might be multiple instances of LeadershipSettings, but
186 # the backend is shared, so the cache needs to be a class
187 # attribute.
188 cls = self.__class__
189 if cls.__cls_cache is None:
190 cmd = ["leader-get", "--format=yaml"]
191 cls.__cls_cache = yaml.safe_load(subprocess.check_output(cmd).decode("UTF-8")) or {}
192 return cls.__cls_cache
193
194 def __getitem__(self, key: str) -> str:
195 return self._cache.get(key, "")
196
197 def __setitem__(self, key: str, value: str):
198 if "=" in key:
199 # Leave other validation to the leader-set tool
200 raise RuntimeError(f"LeadershipSettings keys may not contain '=', got {key}")
201 if value is None:
202 value = ""
203 cmd = ["leader-set", f"{key}={value}"]
204 subprocess.check_call(cmd)
205 if self._cache_loaded:
206 if value == "":
207 del self._cache[key]
208 else:
209 self._cache[key] = value
210
211 def __delitem__(self, key: str):
212 self[key] = ""
213
214 def __iter__(self) -> Iterable[str]:
215 return iter(self._cache)
216
217 def __len__(self) -> int:
218 return len(self._cache)
diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py
index 717c937..00aba40 100644
--- a/tests/unit/test_charm.py
+++ b/tests/unit/test_charm.py
@@ -2,13 +2,13 @@
2# See LICENSE file for licensing details.2# See LICENSE file for licensing details.
33
4import unittest4import unittest
5from unittest.mock import patch
56
6from charm import OpenLDAPK8sCharm7from charm import OpenLDAPK8sCharm
7from collections import namedtuple8from collections import namedtuple
8from ops import testing9from ops import testing
9from ops.model import (10from ops.model import (
10 ActiveStatus,11 ActiveStatus,
11 BlockedStatus,
12 WaitingStatus,12 WaitingStatus,
13)13)
1414
@@ -18,34 +18,24 @@ CONFIG_ALL = {
18 'image_path': 'example.com/openldap:latest',18 'image_path': 'example.com/openldap:latest',
19 'image_username': 'image_user',19 'image_username': 'image_user',
20 'image_password': 'image_pass',20 'image_password': 'image_pass',
21 'admin_password': 'badmin_password',
22}21}
2322
24CONFIG_IMAGE_NO_CREDS = {23CONFIG_IMAGE_NO_CREDS = {
25 'image_path': 'example.com/openldap:latest',24 'image_path': 'example.com/openldap:latest',
26 'image_username': '',25 'image_username': '',
27 'image_password': '',26 'image_password': '',
28 'admin_password': 'badmin_password',
29}27}
3028
31CONFIG_IMAGE_NO_IMAGE = {29CONFIG_IMAGE_NO_IMAGE = {
32 'image_path': '',30 'image_path': '',
33 'image_username': '',31 'image_username': '',
34 'image_password': '',32 'image_password': '',
35 'admin_password': 'badmin_password',
36}33}
3734
38CONFIG_IMAGE_NO_PASSWORD = {35CONFIG_IMAGE_NO_PASSWORD = {
39 'image_path': 'example.com/openldap:latest',36 'image_path': 'example.com/openldap:latest',
40 'image_username': 'production',37 'image_username': 'production',
41 'image_password': '',38 'image_password': '',
42 'admin_password': 'badmin_password',
43}
44
45CONFIG_NO_ADMIN_PASSWORD = {
46 'image_path': 'example.com/openldap:latest',
47 'image_username': 'production',
48 'image_password': '',
49}39}
5040
51DB_URI = {41DB_URI = {
@@ -63,12 +53,6 @@ class TestOpenLDAPK8sCharmHooksDisabled(unittest.TestCase):
63 self.harness.begin()53 self.harness.begin()
64 self.harness.disable_hooks()54 self.harness.disable_hooks()
6555
66 def test_check_for_config_problems(self):
67 """Config problems as a string."""
68 self.harness.update_config(CONFIG_NO_ADMIN_PASSWORD)
69 expected = 'required setting(s) empty: admin_password'
70 self.assertEqual(self.harness.charm._check_for_config_problems(), expected)
71
72 def test_make_pod_config(self):56 def test_make_pod_config(self):
73 """Make basic, correct pod config."""57 """Make basic, correct pod config."""
74 self.harness.update_config(CONFIG_IMAGE_NO_CREDS)58 self.harness.update_config(CONFIG_IMAGE_NO_CREDS)
@@ -81,66 +65,58 @@ class TestOpenLDAPK8sCharmHooksDisabled(unittest.TestCase):
81 'POSTGRES_PORT': '5432',65 'POSTGRES_PORT': '5432',
82 'LDAP_ADMIN_PASSWORD': 'badmin_password',66 'LDAP_ADMIN_PASSWORD': 'badmin_password',
83 }67 }
84 self.assertEqual(self.harness.charm._make_pod_config(), expected)68 with patch.object(self.harness.charm, "get_admin_password") as get_admin_password:
8569 get_admin_password.return_value = 'badmin_password'
86 def test_make_pod_config_no_password(self):70 self.assertEqual(self.harness.charm._make_pod_config(), expected)
87 """Missing admin password in config shouldn't explode at least."""
88 self.harness.update_config(CONFIG_NO_ADMIN_PASSWORD)
89 self.harness.charm._state.postgres = DB_URI
90 expected = {
91 'LDAP_ADMIN_PASSWORD': '',
92 'POSTGRES_NAME': 'openldap',
93 'POSTGRES_USER': 'ldap_user',
94 'POSTGRES_PASSWORD': 'ldap_password',
95 'POSTGRES_HOST': '1.1.1.1',
96 'POSTGRES_PORT': '5432',
97 }
98 self.assertEqual(self.harness.charm._make_pod_config(), expected)
9971
100 def test_make_pod_spec(self):72 def test_make_pod_spec(self):
101 """Basic, correct pod spec."""73 """Basic, correct pod spec."""
102 self.harness.update_config(CONFIG_ALL)74 self.harness.update_config(CONFIG_ALL)
103 self.harness.charm._state.postgres = DB_URI75 self.harness.charm._state.postgres = DB_URI
104 expected = {76 with patch.object(self.harness.charm, "get_admin_password") as get_admin_password:
105 'version': 3,77 get_admin_password.return_value = 'badmin_password'
106 'containers': [78 expected = {
107 {79 'version': 3,
108 'name': 'openldap',80 'containers': [
109 'imageDetails': {81 {
110 'imagePath': 'example.com/openldap:latest',82 'name': 'openldap',
111 'username': 'image_user',83 'imageDetails': {
112 'password': 'image_pass',84 'imagePath': 'example.com/openldap:latest',
113 },85 'username': 'image_user',
114 'ports': [{'containerPort': 389, 'protocol': 'TCP'}],86 'password': 'image_pass',
115 'envConfig': self.harness.charm._make_pod_config(),87 },
116 'kubernetes': {88 'ports': [{'containerPort': 389, 'protocol': 'TCP'}],
117 'readinessProbe': {'tcpSocket': {'port': 389}},89 'envConfig': self.harness.charm._make_pod_config(),
118 },90 'kubernetes': {
119 }91 'readinessProbe': {'tcpSocket': {'port': 389}},
120 ],92 },
121 }93 }
122 self.assertEqual(self.harness.charm._make_pod_spec(), expected)94 ],
95 }
96 self.assertEqual(self.harness.charm._make_pod_spec(), expected)
12397
124 def test_make_pod_spec_no_image_creds(self):98 def test_make_pod_spec_no_image_creds(self):
125 self.harness.update_config(CONFIG_IMAGE_NO_CREDS)99 self.harness.update_config(CONFIG_IMAGE_NO_CREDS)
126 self.harness.charm._state.postgres = DB_URI100 self.harness.charm._state.postgres = DB_URI
127 expected = {101 with patch.object(self.harness.charm, "get_admin_password") as get_admin_password:
128 'version': 3,102 get_admin_password.return_value = 'badmin_password'
129 'containers': [103 expected = {
130 {104 'version': 3,
131 'name': 'openldap',105 'containers': [
132 'imageDetails': {106 {
133 'imagePath': 'example.com/openldap:latest',107 'name': 'openldap',
134 },108 'imageDetails': {
135 'ports': [{'containerPort': 389, 'protocol': 'TCP'}],109 'imagePath': 'example.com/openldap:latest',
136 'envConfig': self.harness.charm._make_pod_config(),110 },
137 'kubernetes': {111 'ports': [{'containerPort': 389, 'protocol': 'TCP'}],
138 'readinessProbe': {'tcpSocket': {'port': 389}},112 'envConfig': self.harness.charm._make_pod_config(),
139 },113 'kubernetes': {
140 }114 'readinessProbe': {'tcpSocket': {'port': 389}},
141 ],115 },
142 }116 }
143 self.assertEqual(self.harness.charm._make_pod_spec(), expected)117 ],
118 }
119 self.assertEqual(self.harness.charm._make_pod_spec(), expected)
144120
145 def test_configure_pod_no_postgres_relation(self):121 def test_configure_pod_no_postgres_relation(self):
146 """Check that we block correctly without a Postgres relation."""122 """Check that we block correctly without a Postgres relation."""
@@ -161,17 +137,6 @@ class TestOpenLDAPK8sCharmHooksDisabled(unittest.TestCase):
161 self.harness.charm._configure_pod(mock_event)137 self.harness.charm._configure_pod(mock_event)
162 self.assertEqual(self.harness.charm.unit.status, expected)138 self.assertEqual(self.harness.charm.unit.status, expected)
163139
164 def test_configure_pod_config_problems(self):
165 """Test pod config with missing juju config options."""
166 mock_event = MagicMock()
167
168 self.harness.update_config(CONFIG_NO_ADMIN_PASSWORD)
169 self.harness.charm._state.postgres = DB_URI
170 self.harness.set_leader(True)
171 expected = BlockedStatus('required setting(s) empty: admin_password')
172 self.harness.charm._configure_pod(mock_event)
173 self.assertEqual(self.harness.charm.unit.status, expected)
174
175 def test_configure_pod(self):140 def test_configure_pod(self):
176 """Test pod configuration with everything working appropriately."""141 """Test pod configuration with everything working appropriately."""
177 mock_event = MagicMock()142 mock_event = MagicMock()
@@ -180,8 +145,10 @@ class TestOpenLDAPK8sCharmHooksDisabled(unittest.TestCase):
180 self.harness.charm._state.postgres = DB_URI145 self.harness.charm._state.postgres = DB_URI
181 self.harness.set_leader(True)146 self.harness.set_leader(True)
182 expected = ActiveStatus()147 expected = ActiveStatus()
183 self.harness.charm._configure_pod(mock_event)148 with patch.object(self.harness.charm, "get_admin_password") as get_admin_password:
184 self.assertEqual(self.harness.charm.unit.status, expected)149 get_admin_password.return_value = 'badmin_password'
150 self.harness.charm._configure_pod(mock_event)
151 self.assertEqual(self.harness.charm.unit.status, expected)
185152
186 def test_on_database_relation_joined(self):153 def test_on_database_relation_joined(self):
187 mock_event = MagicMock()154 mock_event = MagicMock()
@@ -206,5 +173,21 @@ class TestOpenLDAPK8sCharmHooksDisabled(unittest.TestCase):
206 mock_event.master = master173 mock_event.master = master
207 mock_event.database = "openldap"174 mock_event.database = "openldap"
208175
209 self.harness.charm._on_master_changed(mock_event)176 with patch.object(self.harness.charm, "get_admin_password") as get_admin_password:
210 self.assertEqual(self.harness.charm._state.postgres['dbname'], "openldap")177 get_admin_password.return_value = 'badmin_password'
178 self.harness.charm._on_master_changed(mock_event)
179 self.assertEqual(self.harness.charm._state.postgres['dbname'], "openldap")
180
181 def test_on_get_admin_password_action(self):
182 mock_event = MagicMock()
183
184 self.harness.update_config(CONFIG_ALL)
185 with patch.object(self.harness.charm, "get_admin_password") as get_admin_password:
186 get_admin_password.return_value = 'badmin_password'
187 self.harness.charm._on_get_admin_password_action(mock_event)
188 mock_event.set_results.assert_called_with({"admin-password": "badmin_password"})
189 # And now return an empty result.
190 get_admin_password.return_value = ""
191 mock_event.reset_mock()
192 self.harness.charm._on_get_admin_password_action(mock_event)
193 mock_event.fail.assert_called_with("LDAP admin password has not yet been set, please retry later.")

Subscribers

People subscribed via source and target branches

to all changes: