Merge ~mthaddon/charm-k8s-discourse/+git/charm-k8s-discourse:redis-relation into charm-k8s-discourse:master
- Git
- lp:~mthaddon/charm-k8s-discourse/+git/charm-k8s-discourse
- redis-relation
- Merge into master
Status: | Merged |
---|---|
Approved by: | Tom Haddon |
Approved revision: | 8635ad01c30596a19ce219a4a470c96e81ed218b |
Merged at revision: | ab22995f6d560f85d94adec0d2a9d4d1b8813221 |
Proposed branch: | ~mthaddon/charm-k8s-discourse/+git/charm-k8s-discourse:redis-relation |
Merge into: | charm-k8s-discourse:master |
Diff against target: |
506 lines (+173/-38) 12 files modified
Makefile (+2/-2) README.md (+7/-16) config.yaml (+4/-4) lib/charms/redis_k8s/v0/redis.py (+97/-0) metadata.yaml (+4/-1) src/charm.py (+42/-7) tests/unit/fixtures/config_valid_complete.yaml (+4/-3) tests/unit/fixtures/config_valid_no_saml_fingerprint.yaml (+1/-0) tests/unit/fixtures/config_valid_no_saml_target_url.yaml (+1/-0) tests/unit/fixtures/config_valid_no_tls.yaml (+4/-3) tests/unit/fixtures/config_valid_with_tls.yaml (+1/-0) tests/unit/test_charm.py (+6/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Joel Sing (community) | +1 | Approve | |
🤖 prod-jenkaas-is (community) | continuous-integration | Approve | |
Canonical IS Reviewers | Pending | ||
Review via email: mp+402637@code.launchpad.net |
Commit message
Add a redis relation, using the new redis-k8s library, but continue to support static redis config as well
Description of the change
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
🤖 prod-jenkaas-is (prod-jenkaas-is) wrote : | # |
A CI job is currently in progress. A follow up comment will be added when it completes.
🤖 prod-jenkaas-is (prod-jenkaas-is) wrote : | # |
FAILED: Continuous integration, rev:74fc0f5f551
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
🤖 prod-jenkaas-is (prod-jenkaas-is) wrote : | # |
A CI job is currently in progress. A follow up comment will be added when it completes.
🤖 prod-jenkaas-is (prod-jenkaas-is) wrote : | # |
FAILED: Continuous integration, rev:08af48d0f81
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
🤖 prod-jenkaas-is (prod-jenkaas-is) wrote : | # |
A CI job is currently in progress. A follow up comment will be added when it completes.
🤖 prod-jenkaas-is (prod-jenkaas-is) wrote : | # |
PASSED: Continuous integration, rev:2ef40686420
https:/
Executed test runs:
SUCCESS: https:/
None: https:/
Click here to trigger a rebuild:
https:/
Joel Sing (jsing) wrote : | # |
LGTM - see various minor comments inline.
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Change successfully merged at revision ab22995f6d560f8
Preview Diff
1 | diff --git a/Makefile b/Makefile | |||
2 | index 95aa3b7..6a7bbe1 100644 | |||
3 | --- a/Makefile | |||
4 | +++ b/Makefile | |||
5 | @@ -12,7 +12,7 @@ lint: blacken | |||
6 | 12 | 12 | ||
7 | 13 | # We actually use the build directory created by charmcraft, | 13 | # We actually use the build directory created by charmcraft, |
8 | 14 | # but the .charm file makes a much more convenient sentinel. | 14 | # but the .charm file makes a much more convenient sentinel. |
10 | 15 | unittest: discourse.charm | 15 | unittest: discourse-k8s.charm |
11 | 16 | @tox -e unit | 16 | @tox -e unit |
12 | 17 | 17 | ||
13 | 18 | test: lint unittest | 18 | test: lint unittest |
14 | @@ -21,7 +21,7 @@ clean: | |||
15 | 21 | @echo "Cleaning files" | 21 | @echo "Cleaning files" |
16 | 22 | @git clean -fXd | 22 | @git clean -fXd |
17 | 23 | 23 | ||
19 | 24 | discourse.charm: src/*.py requirements.txt | 24 | discourse-k8s.charm: src/*.py requirements.txt |
20 | 25 | charmcraft build | 25 | charmcraft build |
21 | 26 | 26 | ||
22 | 27 | build-image: | 27 | build-image: |
23 | diff --git a/README.md b/README.md | |||
24 | index 09898e4..b49813d 100644 | |||
25 | --- a/README.md | |||
26 | +++ b/README.md | |||
27 | @@ -15,22 +15,13 @@ Discourse than the one currently deployed. | |||
28 | 15 | For details on using Kubernetes with Juju [see here](https://juju.is/docs/kubernetes), and for | 15 | For details on using Kubernetes with Juju [see here](https://juju.is/docs/kubernetes), and for |
29 | 16 | details on using Juju with MicroK8s for easy local testing [see here](https://juju.is/docs/microk8s-cloud). | 16 | details on using Juju with MicroK8s for easy local testing [see here](https://juju.is/docs/microk8s-cloud). |
30 | 17 | 17 | ||
47 | 18 | To get started with a test environment, first deploy Redis in an IaaS model: | 18 | To deploy into a Juju K8s model: |
48 | 19 | 19 | ||
49 | 20 | juju deploy cs:~redis-charmers/redis # Note the deployed IP as ${REDIS_IP} | 20 | juju deploy postgresql-k8s |
50 | 21 | 21 | juju deploy redis-k8s | |
51 | 22 | Now deploy the Discourse and PostgreSQL charms within a Juju Kubernetes model | 22 | juju deploy discourse-k8s |
52 | 23 | as follows: | 23 | juju relate discourse-k8s postgresql-k8s:db-admin |
53 | 24 | 24 | juju relate discourse-k8s redis-k8s | |
38 | 25 | juju deploy cs:~postgresql-charmers/postgresql-k8s postgresql | ||
39 | 26 | juju deploy cs:~discourse-charmers/discourse-k8s discourse \ | ||
40 | 27 | --config redis_host=${REDIS_IP} \ | ||
41 | 28 | --config developer_emails="user@foo.internal" \ | ||
42 | 29 | --config external_hostname="foo.internal" \ | ||
43 | 30 | --config smtp_address="127.0.0.1" \ | ||
44 | 31 | --config smtp_domain="foo.internal" | ||
45 | 32 | juju add-relation discourse postgresql:db-admin | ||
46 | 33 | juju expose discourse | ||
54 | 34 | 25 | ||
55 | 35 | Once the deployment is completed and the "discourse" workload state in `juju | 26 | Once the deployment is completed and the "discourse" workload state in `juju |
56 | 36 | status` has changed to "active" you can visit http://{$discourse_ip}:3000 in a | 27 | status` has changed to "active" you can visit http://{$discourse_ip}:3000 in a |
57 | diff --git a/config.yaml b/config.yaml | |||
58 | index a55dafd..85f0677 100644 | |||
59 | --- a/config.yaml | |||
60 | +++ b/config.yaml | |||
61 | @@ -18,7 +18,7 @@ options: | |||
62 | 18 | external_hostname: | 18 | external_hostname: |
63 | 19 | type: string | 19 | type: string |
64 | 20 | description: "External hostname this discourse instance should respond to" | 20 | description: "External hostname this discourse instance should respond to" |
66 | 21 | default: "" | 21 | default: "foo.internal" |
67 | 22 | enable_cors: | 22 | enable_cors: |
68 | 23 | type: boolean | 23 | type: boolean |
69 | 24 | description: "Enable Cross-origin Resource Sharing (CORS) at the application level (required for SSO)" | 24 | description: "Enable Cross-origin Resource Sharing (CORS) at the application level (required for SSO)" |
70 | @@ -30,15 +30,15 @@ options: | |||
71 | 30 | developer_emails: | 30 | developer_emails: |
72 | 31 | type: string | 31 | type: string |
73 | 32 | description: "Comma delimited list of email addresses that should have developer level access" | 32 | description: "Comma delimited list of email addresses that should have developer level access" |
75 | 33 | default: "" | 33 | default: "user@foo.internal" |
76 | 34 | smtp_domain: | 34 | smtp_domain: |
77 | 35 | type: string | 35 | type: string |
78 | 36 | description: "Hostname that email sent by this discourse should appear to come from" | 36 | description: "Hostname that email sent by this discourse should appear to come from" |
80 | 37 | default: "" | 37 | default: "foo.internal" |
81 | 38 | smtp_address: | 38 | smtp_address: |
82 | 39 | type: string | 39 | type: string |
83 | 40 | description: "Hostname / IP that should be used to send SMTP mail" | 40 | description: "Hostname / IP that should be used to send SMTP mail" |
85 | 41 | default: "" | 41 | default: "127.0.0.1" |
86 | 42 | smtp_port: | 42 | smtp_port: |
87 | 43 | type: int | 43 | type: int |
88 | 44 | description: "Port to use when connecting to SMTP server" | 44 | description: "Port to use when connecting to SMTP server" |
89 | diff --git a/lib/charms/redis_k8s/v0/redis.py b/lib/charms/redis_k8s/v0/redis.py | |||
90 | 45 | new file mode 100644 | 45 | new file mode 100644 |
91 | index 0000000..70787b6 | |||
92 | --- /dev/null | |||
93 | +++ b/lib/charms/redis_k8s/v0/redis.py | |||
94 | @@ -0,0 +1,97 @@ | |||
95 | 1 | """Library for the redis relation. | ||
96 | 2 | |||
97 | 3 | This library contains the Requires and Provides classes for handling the | ||
98 | 4 | redis interface. | ||
99 | 5 | |||
100 | 6 | Import `RedisRequires` in your charm by adding the following to `src/charm.py`: | ||
101 | 7 | ``` | ||
102 | 8 | from charms.redis_k8s.v0.redis import RedisRequires | ||
103 | 9 | |||
104 | 10 | # In your charm's `__init__` method. | ||
105 | 11 | self.redis = RedisRequires(self, self._stored) | ||
106 | 12 | ``` | ||
107 | 13 | And then wherever you need to reference the relation data: | ||
108 | 14 | ``` | ||
109 | 15 | for redis_unit in self._stored.redis_relation: | ||
110 | 16 | redis_host = self._stored.redis_relation[redis_unit]["hostname"] | ||
111 | 17 | redis_port = self._stored.redis_relation[redis_unit]["port"] | ||
112 | 18 | ``` | ||
113 | 19 | """ | ||
114 | 20 | import logging | ||
115 | 21 | |||
116 | 22 | from ops.charm import CharmEvents | ||
117 | 23 | from ops.framework import EventBase, EventSource, Object | ||
118 | 24 | |||
119 | 25 | # The unique Charmhub library identifier, never change it | ||
120 | 26 | LIBID = "fe18a608cec5465fa5153e419abcad7b" | ||
121 | 27 | |||
122 | 28 | # Increment this major API version when introducing breaking changes | ||
123 | 29 | LIBAPI = 0 | ||
124 | 30 | |||
125 | 31 | # Increment this PATCH version before using `charmcraft publish-lib` or reset | ||
126 | 32 | # to 0 if you are raising the major API version | ||
127 | 33 | LIBPATCH = 1 | ||
128 | 34 | |||
129 | 35 | logger = logging.getLogger(__name__) | ||
130 | 36 | |||
131 | 37 | |||
132 | 38 | class RedisRelationUpdatedEvent(EventBase): | ||
133 | 39 | pass | ||
134 | 40 | |||
135 | 41 | |||
136 | 42 | class RedisRelationCharmEvents(CharmEvents): | ||
137 | 43 | redis_relation_updated = EventSource(RedisRelationUpdatedEvent) | ||
138 | 44 | |||
139 | 45 | |||
140 | 46 | class RedisRequires(Object): | ||
141 | 47 | def __init__(self, charm, _stored): | ||
142 | 48 | # Define a constructor that takes the charm and it's StoredState | ||
143 | 49 | super().__init__(charm, "redis") | ||
144 | 50 | self.framework.observe(charm.on.redis_relation_changed, self._on_relation_changed) | ||
145 | 51 | self.framework.observe(charm.on.redis_relation_broken, self._on_relation_broken) | ||
146 | 52 | self._stored = _stored | ||
147 | 53 | self.charm = charm | ||
148 | 54 | |||
149 | 55 | def _on_relation_changed(self, event): | ||
150 | 56 | if not event.unit: | ||
151 | 57 | return | ||
152 | 58 | |||
153 | 59 | hostname = event.relation.data[event.unit].get("hostname") | ||
154 | 60 | port = event.relation.data[event.unit].get("port") | ||
155 | 61 | # Store some data from the relation in local state | ||
156 | 62 | self._stored.redis_relation[event.relation.id] = {"hostname": hostname, "port": port} | ||
157 | 63 | |||
158 | 64 | # Trigger an event that our charm can react to. | ||
159 | 65 | self.charm.on.redis_relation_updated.emit() | ||
160 | 66 | |||
161 | 67 | def _on_relation_broken(self, event): | ||
162 | 68 | # Remove the unit data from local state | ||
163 | 69 | self._stored.redis_relation.pop(event.relation.id, None) | ||
164 | 70 | |||
165 | 71 | # Trigger an event that our charm can react to. | ||
166 | 72 | self.charm.on.redis_relation_updated.emit() | ||
167 | 73 | |||
168 | 74 | |||
169 | 75 | class RedisProvides(Object): | ||
170 | 76 | def __init__(self, charm, port): | ||
171 | 77 | super().__init__(charm, "redis") | ||
172 | 78 | self.framework.observe(charm.on.redis_relation_changed, self._on_relation_changed) | ||
173 | 79 | self._port = port | ||
174 | 80 | |||
175 | 81 | def _on_relation_changed(self, event): | ||
176 | 82 | if not self.model.unit.is_leader(): | ||
177 | 83 | logger.debug("Relation changes ignored by non-leader") | ||
178 | 84 | return | ||
179 | 85 | |||
180 | 86 | event.relation.data[self.model.unit]['hostname'] = str(self._bind_address(event)) | ||
181 | 87 | event.relation.data[self.model.unit]['port'] = str(self._port) | ||
182 | 88 | # The reactive Redis charm exposes also 'password'. When tackling | ||
183 | 89 | # https://github.com/canonical/redis-operator/issues/7 add 'password' | ||
184 | 90 | # field so that it matches the exposed interface information from it. | ||
185 | 91 | # event.relation.data[self.unit]['password'] = '' | ||
186 | 92 | |||
187 | 93 | def _bind_address(self, event): | ||
188 | 94 | relation = self.model.get_relation(event.relation.name, event.relation.id) | ||
189 | 95 | if address := self.model.get_binding(relation).network.bind_address: | ||
190 | 96 | return address | ||
191 | 97 | return self.app.name | ||
192 | diff --git a/metadata.yaml b/metadata.yaml | |||
193 | index 82e1aa8..0c8beb2 100644 | |||
194 | --- a/metadata.yaml | |||
195 | +++ b/metadata.yaml | |||
196 | @@ -1,4 +1,5 @@ | |||
198 | 1 | name: discourse | 1 | name: discourse-k8s |
199 | 2 | display-name: Discourse | ||
200 | 2 | summary: Discourse is the 100% open source discussion platform built for the next decade of the Internet. | 3 | summary: Discourse is the 100% open source discussion platform built for the next decade of the Internet. |
201 | 3 | description: | | 4 | description: | |
202 | 4 | Discourse is the 100% open source discussion platform built for the next decade of the Internet. | 5 | Discourse is the 100% open source discussion platform built for the next decade of the Internet. |
203 | @@ -15,6 +16,8 @@ series: | |||
204 | 15 | - kubernetes | 16 | - kubernetes |
205 | 16 | min-juju-version: 2.8.0 | 17 | min-juju-version: 2.8.0 |
206 | 17 | requires: | 18 | requires: |
207 | 19 | redis: | ||
208 | 20 | interface: redis | ||
209 | 18 | db: | 21 | db: |
210 | 19 | interface: pgsql | 22 | interface: pgsql |
211 | 20 | limit: 1 | 23 | limit: 1 |
212 | diff --git a/src/charm.py b/src/charm.py | |||
213 | index d8f210b..0ef9f01 100755 | |||
214 | --- a/src/charm.py | |||
215 | +++ b/src/charm.py | |||
216 | @@ -2,7 +2,13 @@ | |||
217 | 2 | # Copyright 2020 Canonical Ltd. | 2 | # Copyright 2020 Canonical Ltd. |
218 | 3 | # See LICENSE file for licensing details. | 3 | # See LICENSE file for licensing details. |
219 | 4 | 4 | ||
220 | 5 | import logging | ||
221 | 6 | |||
222 | 5 | import ops.lib | 7 | import ops.lib |
223 | 8 | from charms.redis_k8s.v0.redis import ( | ||
224 | 9 | RedisRelationCharmEvents, | ||
225 | 10 | RedisRequires, | ||
226 | 11 | ) | ||
227 | 6 | from ops.charm import CharmBase | 12 | from ops.charm import CharmBase |
228 | 7 | from ops.main import main | 13 | from ops.main import main |
229 | 8 | from ops.framework import StoredState | 14 | from ops.framework import StoredState |
230 | @@ -10,6 +16,9 @@ from ops.framework import StoredState | |||
231 | 10 | from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus, WaitingStatus | 16 | from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus, WaitingStatus |
232 | 11 | 17 | ||
233 | 12 | 18 | ||
234 | 19 | logger = logging.getLogger(__name__) | ||
235 | 20 | |||
236 | 21 | |||
237 | 13 | pgsql = ops.lib.use("pgsql", 1, "postgresql-charmers@lists.launchpad.net") | 22 | pgsql = ops.lib.use("pgsql", 1, "postgresql-charmers@lists.launchpad.net") |
238 | 14 | 23 | ||
239 | 15 | THROTTLE_LEVELS = { | 24 | THROTTLE_LEVELS = { |
240 | @@ -52,7 +61,7 @@ def create_discourse_pod_config(config): | |||
241 | 52 | 'DISCOURSE_SMTP_USER_NAME': config['smtp_username'], | 61 | 'DISCOURSE_SMTP_USER_NAME': config['smtp_username'], |
242 | 53 | 'DISCOURSE_SMTP_PASSWORD': config['smtp_password'], | 62 | 'DISCOURSE_SMTP_PASSWORD': config['smtp_password'], |
243 | 54 | 'DISCOURSE_REDIS_HOST': config['redis_host'], | 63 | 'DISCOURSE_REDIS_HOST': config['redis_host'], |
245 | 55 | 'DISCOURSE_REDIS_PORT': 6379, | 64 | 'DISCOURSE_REDIS_PORT': config['redis_port'], |
246 | 56 | 'DISCOURSE_ENABLE_CORS': config['enable_cors'], | 65 | 'DISCOURSE_ENABLE_CORS': config['enable_cors'], |
247 | 57 | 'DISCOURSE_CORS_ORIGIN': config['cors_origin'], | 66 | 'DISCOURSE_CORS_ORIGIN': config['cors_origin'], |
248 | 58 | 'DISCOURSE_REFRESH_MAXMIND_DB_DURING_PRECOMPILE_DAYS': "0", | 67 | 'DISCOURSE_REFRESH_MAXMIND_DB_DURING_PRECOMPILE_DAYS': "0", |
249 | @@ -161,7 +170,7 @@ def get_pod_spec(app_name, config): | |||
250 | 161 | return pod_spec | 170 | return pod_spec |
251 | 162 | 171 | ||
252 | 163 | 172 | ||
254 | 164 | def check_for_config_problems(config): | 173 | def check_for_config_problems(config, stored): |
255 | 165 | """Check if there are issues with the juju config. | 174 | """Check if there are issues with the juju config. |
256 | 166 | 175 | ||
257 | 167 | - Primarily looks for missing config options using check_for_missing_config_fields() | 176 | - Primarily looks for missing config options using check_for_missing_config_fields() |
258 | @@ -169,7 +178,7 @@ def check_for_config_problems(config): | |||
259 | 169 | - Returns a list of errors if any were found. | 178 | - Returns a list of errors if any were found. |
260 | 170 | """ | 179 | """ |
261 | 171 | errors = [] | 180 | errors = [] |
263 | 172 | missing_fields = check_for_missing_config_fields(config) | 181 | missing_fields = check_for_missing_config_fields(config, stored) |
264 | 173 | 182 | ||
265 | 174 | if missing_fields: | 183 | if missing_fields: |
266 | 175 | errors.append('Required configuration missing: {}'.format(" ".join(missing_fields))) | 184 | errors.append('Required configuration missing: {}'.format(" ".join(missing_fields))) |
267 | @@ -183,7 +192,7 @@ def check_for_config_problems(config): | |||
268 | 183 | return errors | 192 | return errors |
269 | 184 | 193 | ||
270 | 185 | 194 | ||
272 | 186 | def check_for_missing_config_fields(config): | 195 | def check_for_missing_config_fields(config, stored): |
273 | 187 | """Check for missing fields in juju config. | 196 | """Check for missing fields in juju config. |
274 | 188 | 197 | ||
275 | 189 | - Returns a list of required fields that are either not present | 198 | - Returns a list of required fields that are either not present |
276 | @@ -194,13 +203,18 @@ def check_for_missing_config_fields(config): | |||
277 | 194 | needed_fields = [ | 203 | needed_fields = [ |
278 | 195 | 'db_name', | 204 | 'db_name', |
279 | 196 | 'smtp_address', | 205 | 'smtp_address', |
280 | 197 | 'redis_host', | ||
281 | 198 | 'cors_origin', | 206 | 'cors_origin', |
282 | 199 | 'developer_emails', | 207 | 'developer_emails', |
283 | 200 | 'smtp_domain', | 208 | 'smtp_domain', |
284 | 201 | 'discourse_image', | 209 | 'discourse_image', |
285 | 202 | 'external_hostname', | 210 | 'external_hostname', |
286 | 203 | ] | 211 | ] |
287 | 212 | # See if Redis connection information has been provided via a relation. | ||
288 | 213 | redis_hostname = None | ||
289 | 214 | for redis_unit in stored.redis_relation: | ||
290 | 215 | redis_hostname = stored.redis_relation[redis_unit]["hostname"] | ||
291 | 216 | if not redis_hostname: | ||
292 | 217 | needed_fields.append("redis_host") | ||
293 | 204 | for key in needed_fields: | 218 | for key in needed_fields: |
294 | 205 | if not config.get(key): | 219 | if not config.get(key): |
295 | 206 | missing_fields.append(key) | 220 | missing_fields.append(key) |
296 | @@ -209,6 +223,7 @@ def check_for_missing_config_fields(config): | |||
297 | 209 | 223 | ||
298 | 210 | 224 | ||
299 | 211 | class DiscourseCharm(CharmBase): | 225 | class DiscourseCharm(CharmBase): |
300 | 226 | on = RedisRelationCharmEvents() | ||
301 | 212 | stored = StoredState() | 227 | stored = StoredState() |
302 | 213 | 228 | ||
303 | 214 | def __init__(self, *args): | 229 | def __init__(self, *args): |
304 | @@ -220,7 +235,13 @@ class DiscourseCharm(CharmBase): | |||
305 | 220 | super().__init__(*args) | 235 | super().__init__(*args) |
306 | 221 | 236 | ||
307 | 222 | self.stored.set_default( | 237 | self.stored.set_default( |
309 | 223 | db_name=None, db_user=None, db_password=None, db_host=None, has_db_relation=False, has_db_credentials=False | 238 | db_name=None, |
310 | 239 | db_user=None, | ||
311 | 240 | db_password=None, | ||
312 | 241 | db_host=None, | ||
313 | 242 | has_db_relation=False, | ||
314 | 243 | has_db_credentials=False, | ||
315 | 244 | redis_relation={}, | ||
316 | 224 | ) | 245 | ) |
317 | 225 | self.framework.observe(self.on.leader_elected, self.configure_pod) | 246 | self.framework.observe(self.on.leader_elected, self.configure_pod) |
318 | 226 | self.framework.observe(self.on.config_changed, self.configure_pod) | 247 | self.framework.observe(self.on.config_changed, self.configure_pod) |
319 | @@ -230,6 +251,9 @@ class DiscourseCharm(CharmBase): | |||
320 | 230 | self.framework.observe(self.db.on.database_relation_joined, self.on_database_relation_joined) | 251 | self.framework.observe(self.db.on.database_relation_joined, self.on_database_relation_joined) |
321 | 231 | self.framework.observe(self.db.on.master_changed, self.on_database_changed) | 252 | self.framework.observe(self.db.on.master_changed, self.on_database_changed) |
322 | 232 | 253 | ||
323 | 254 | self.redis = RedisRequires(self, self.stored) | ||
324 | 255 | self.framework.observe(self.on.redis_relation_updated, self.configure_pod) | ||
325 | 256 | |||
326 | 233 | def check_config_is_valid(self, config): | 257 | def check_config_is_valid(self, config): |
327 | 234 | """Check that the provided config is valid. | 258 | """Check that the provided config is valid. |
328 | 235 | 259 | ||
329 | @@ -238,7 +262,7 @@ class DiscourseCharm(CharmBase): | |||
330 | 238 | - Sets model status as appropriate. | 262 | - Sets model status as appropriate. |
331 | 239 | """ | 263 | """ |
332 | 240 | valid_config = True | 264 | valid_config = True |
334 | 241 | errors = check_for_config_problems(config) | 265 | errors = check_for_config_problems(config, self.stored) |
335 | 242 | 266 | ||
336 | 243 | # Set status if we have a bad config. | 267 | # Set status if we have a bad config. |
337 | 244 | if errors: | 268 | if errors: |
338 | @@ -266,6 +290,15 @@ class DiscourseCharm(CharmBase): | |||
339 | 266 | 290 | ||
340 | 267 | - Configures pod using pod_spec generated from config. | 291 | - Configures pod using pod_spec generated from config. |
341 | 268 | """ | 292 | """ |
342 | 293 | # Get redis connection information from config but allow overriding | ||
343 | 294 | # via a relation. | ||
344 | 295 | redis_hostname = self.config["redis_host"] | ||
345 | 296 | redis_port = 6379 | ||
346 | 297 | for redis_unit in self.stored.redis_relation: | ||
347 | 298 | redis_hostname = self.stored.redis_relation[redis_unit]["hostname"] | ||
348 | 299 | redis_port = self.stored.redis_relation[redis_unit]["port"] | ||
349 | 300 | logging.debug("Got redis connection details from relation of %s:%s", redis_hostname, redis_port) | ||
350 | 301 | |||
351 | 269 | # Set our status while we get configured. | 302 | # Set our status while we get configured. |
352 | 270 | self.model.unit.status = MaintenanceStatus('Configuring pod') | 303 | self.model.unit.status = MaintenanceStatus('Configuring pod') |
353 | 271 | 304 | ||
354 | @@ -282,6 +315,8 @@ class DiscourseCharm(CharmBase): | |||
355 | 282 | config["db_user"] = self.stored.db_user | 315 | config["db_user"] = self.stored.db_user |
356 | 283 | config["db_password"] = self.stored.db_password | 316 | config["db_password"] = self.stored.db_password |
357 | 284 | config["db_host"] = self.stored.db_host | 317 | config["db_host"] = self.stored.db_host |
358 | 318 | config["redis_host"] = redis_hostname | ||
359 | 319 | config["redis_port"] = redis_port | ||
360 | 285 | # Get our spec definition. | 320 | # Get our spec definition. |
361 | 286 | if self.check_config_is_valid(config): | 321 | if self.check_config_is_valid(config): |
362 | 287 | # Get pod spec using our app name and config | 322 | # Get pod spec using our app name and config |
363 | diff --git a/tests/unit/fixtures/config_valid_complete.yaml b/tests/unit/fixtures/config_valid_complete.yaml | |||
364 | index 7815983..946388d 100644 | |||
365 | --- a/tests/unit/fixtures/config_valid_complete.yaml | |||
366 | +++ b/tests/unit/fixtures/config_valid_complete.yaml | |||
367 | @@ -12,6 +12,7 @@ config: | |||
368 | 12 | image_user: 'someuser' | 12 | image_user: 'someuser' |
369 | 13 | max_body_size: 25 | 13 | max_body_size: 25 |
370 | 14 | redis_host: 10.9.89.197 | 14 | redis_host: 10.9.89.197 |
371 | 15 | redis_port: 6379 | ||
372 | 15 | smtp_address: 167.89.123.58 | 16 | smtp_address: 167.89.123.58 |
373 | 16 | smtp_authentication: login | 17 | smtp_authentication: login |
374 | 17 | smtp_domain: example.com | 18 | smtp_domain: example.com |
375 | @@ -51,7 +52,7 @@ pod_config: | |||
376 | 51 | pod_spec: | 52 | pod_spec: |
377 | 52 | version: 3 | 53 | version: 3 |
378 | 53 | containers: | 54 | containers: |
380 | 54 | - name: discourse | 55 | - name: discourse-k8s |
381 | 55 | envConfig: | 56 | envConfig: |
382 | 56 | DISCOURSE_CORS_ORIGIN: '*' | 57 | DISCOURSE_CORS_ORIGIN: '*' |
383 | 57 | DISCOURSE_DEVELOPER_EMAILS: some.person@example.com | 58 | DISCOURSE_DEVELOPER_EMAILS: some.person@example.com |
384 | @@ -100,14 +101,14 @@ pod_spec: | |||
385 | 100 | nginx.ingress.kubernetes.io/session-cookie-max-age: '3600' | 101 | nginx.ingress.kubernetes.io/session-cookie-max-age: '3600' |
386 | 101 | nginx.ingress.kubernetes.io/session-cookie-name: 'DISCOURSE_AFFINITY' | 102 | nginx.ingress.kubernetes.io/session-cookie-name: 'DISCOURSE_AFFINITY' |
387 | 102 | nginx.ingress.kubernetes.io/session-cookie-samesite: 'Lax' | 103 | nginx.ingress.kubernetes.io/session-cookie-samesite: 'Lax' |
389 | 103 | name: discourse-ingress | 104 | name: discourse-k8s-ingress |
390 | 104 | spec: | 105 | spec: |
391 | 105 | rules: | 106 | rules: |
392 | 106 | - host: discourse.local | 107 | - host: discourse.local |
393 | 107 | http: | 108 | http: |
394 | 108 | paths: | 109 | paths: |
395 | 109 | - backend: | 110 | - backend: |
397 | 110 | serviceName: discourse | 111 | serviceName: discourse-k8s |
398 | 111 | servicePort: 3000 | 112 | servicePort: 3000 |
399 | 112 | path: '/' | 113 | path: '/' |
400 | 113 | tls: | 114 | tls: |
401 | diff --git a/tests/unit/fixtures/config_valid_no_saml_fingerprint.yaml b/tests/unit/fixtures/config_valid_no_saml_fingerprint.yaml | |||
402 | index 4f7365a..cc76606 100644 | |||
403 | --- a/tests/unit/fixtures/config_valid_no_saml_fingerprint.yaml | |||
404 | +++ b/tests/unit/fixtures/config_valid_no_saml_fingerprint.yaml | |||
405 | @@ -11,6 +11,7 @@ config: | |||
406 | 11 | image_pass: '' | 11 | image_pass: '' |
407 | 12 | image_user: '' | 12 | image_user: '' |
408 | 13 | redis_host: 10.9.89.197 | 13 | redis_host: 10.9.89.197 |
409 | 14 | redis_port: 6379 | ||
410 | 14 | smtp_address: 167.89.123.58 | 15 | smtp_address: 167.89.123.58 |
411 | 15 | smtp_authentication: login | 16 | smtp_authentication: login |
412 | 16 | smtp_domain: example.com | 17 | smtp_domain: example.com |
413 | diff --git a/tests/unit/fixtures/config_valid_no_saml_target_url.yaml b/tests/unit/fixtures/config_valid_no_saml_target_url.yaml | |||
414 | index 2f31a4f..1364998 100644 | |||
415 | --- a/tests/unit/fixtures/config_valid_no_saml_target_url.yaml | |||
416 | +++ b/tests/unit/fixtures/config_valid_no_saml_target_url.yaml | |||
417 | @@ -11,6 +11,7 @@ config: | |||
418 | 11 | image_pass: '' | 11 | image_pass: '' |
419 | 12 | image_user: '' | 12 | image_user: '' |
420 | 13 | redis_host: 10.9.89.197 | 13 | redis_host: 10.9.89.197 |
421 | 14 | redis_port: 6379 | ||
422 | 14 | smtp_address: 167.89.123.58 | 15 | smtp_address: 167.89.123.58 |
423 | 15 | smtp_authentication: login | 16 | smtp_authentication: login |
424 | 16 | smtp_domain: example.com | 17 | smtp_domain: example.com |
425 | diff --git a/tests/unit/fixtures/config_valid_no_tls.yaml b/tests/unit/fixtures/config_valid_no_tls.yaml | |||
426 | index 1f7e021..8179e1a 100644 | |||
427 | --- a/tests/unit/fixtures/config_valid_no_tls.yaml | |||
428 | +++ b/tests/unit/fixtures/config_valid_no_tls.yaml | |||
429 | @@ -12,6 +12,7 @@ config: | |||
430 | 12 | image_user: '' | 12 | image_user: '' |
431 | 13 | max_body_size: 20 | 13 | max_body_size: 20 |
432 | 14 | redis_host: 10.9.89.197 | 14 | redis_host: 10.9.89.197 |
433 | 15 | redis_port: 6379 | ||
434 | 15 | smtp_address: 167.89.123.58 | 16 | smtp_address: 167.89.123.58 |
435 | 16 | smtp_authentication: login | 17 | smtp_authentication: login |
436 | 17 | smtp_domain: example.com | 18 | smtp_domain: example.com |
437 | @@ -51,7 +52,7 @@ pod_config: | |||
438 | 51 | pod_spec: | 52 | pod_spec: |
439 | 52 | version: 3 | 53 | version: 3 |
440 | 53 | containers: | 54 | containers: |
442 | 54 | - name: discourse | 55 | - name: discourse-k8s |
443 | 55 | envConfig: | 56 | envConfig: |
444 | 56 | DISCOURSE_CORS_ORIGIN: '*' | 57 | DISCOURSE_CORS_ORIGIN: '*' |
445 | 57 | DISCOURSE_DEVELOPER_EMAILS: some.person@example.com | 58 | DISCOURSE_DEVELOPER_EMAILS: some.person@example.com |
446 | @@ -99,13 +100,13 @@ pod_spec: | |||
447 | 99 | nginx.ingress.kubernetes.io/session-cookie-name: 'DISCOURSE_AFFINITY' | 100 | nginx.ingress.kubernetes.io/session-cookie-name: 'DISCOURSE_AFFINITY' |
448 | 100 | nginx.ingress.kubernetes.io/session-cookie-samesite: 'Lax' | 101 | nginx.ingress.kubernetes.io/session-cookie-samesite: 'Lax' |
449 | 101 | nginx.ingress.kubernetes.io/ssl-redirect: 'false' | 102 | nginx.ingress.kubernetes.io/ssl-redirect: 'false' |
451 | 102 | name: discourse-ingress | 103 | name: discourse-k8s-ingress |
452 | 103 | spec: | 104 | spec: |
453 | 104 | rules: | 105 | rules: |
454 | 105 | - host: discourse.local | 106 | - host: discourse.local |
455 | 106 | http: | 107 | http: |
456 | 107 | paths: | 108 | paths: |
457 | 108 | - backend: | 109 | - backend: |
459 | 109 | serviceName: discourse | 110 | serviceName: discourse-k8s |
460 | 110 | servicePort: 3000 | 111 | servicePort: 3000 |
461 | 111 | path: '/' | 112 | path: '/' |
462 | diff --git a/tests/unit/fixtures/config_valid_with_tls.yaml b/tests/unit/fixtures/config_valid_with_tls.yaml | |||
463 | index 8e9e225..a4eff75 100644 | |||
464 | --- a/tests/unit/fixtures/config_valid_with_tls.yaml | |||
465 | +++ b/tests/unit/fixtures/config_valid_with_tls.yaml | |||
466 | @@ -11,6 +11,7 @@ config: | |||
467 | 11 | image_pass: 'discourse123' | 11 | image_pass: 'discourse123' |
468 | 12 | image_user: 'discourse_role' | 12 | image_user: 'discourse_role' |
469 | 13 | redis_host: 10.9.89.197 | 13 | redis_host: 10.9.89.197 |
470 | 14 | redis_port: 6379 | ||
471 | 14 | smtp_address: 167.89.123.58 | 15 | smtp_address: 167.89.123.58 |
472 | 15 | smtp_authentication: login | 16 | smtp_authentication: login |
473 | 16 | smtp_domain: example.com | 17 | smtp_domain: example.com |
474 | diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py | |||
475 | index 20fe84e..40da7db 100644 | |||
476 | --- a/tests/unit/test_charm.py | |||
477 | +++ b/tests/unit/test_charm.py | |||
478 | @@ -56,7 +56,7 @@ class TestDiscourseK8sCharmHooksDisabled(unittest.TestCase): | |||
479 | 56 | if config_key.startswith('config_valid_'): | 56 | if config_key.startswith('config_valid_'): |
480 | 57 | config_valid = self.harness.charm.check_config_is_valid(self.configs[config_key]['config']) | 57 | config_valid = self.harness.charm.check_config_is_valid(self.configs[config_key]['config']) |
481 | 58 | pod_config = create_discourse_pod_config(self.configs[config_key]['config']) | 58 | pod_config = create_discourse_pod_config(self.configs[config_key]['config']) |
483 | 59 | self.assertEqual(config_valid, True, 'Valid config is not recognized as valid') | 59 | self.assertEqual(config_valid, True, 'Valid config {} is not recognized as valid'.format(config_key)) |
484 | 60 | self.assertEqual( | 60 | self.assertEqual( |
485 | 61 | pod_config, | 61 | pod_config, |
486 | 62 | self.configs[config_key]['pod_config'], | 62 | self.configs[config_key]['pod_config'], |
487 | @@ -77,7 +77,9 @@ class TestDiscourseK8sCharmHooksDisabled(unittest.TestCase): | |||
488 | 77 | for config_key in self.configs: | 77 | for config_key in self.configs: |
489 | 78 | if config_key.startswith('config_invalid_'): | 78 | if config_key.startswith('config_invalid_'): |
490 | 79 | config_valid = self.harness.charm.check_config_is_valid(self.configs[config_key]['config']) | 79 | config_valid = self.harness.charm.check_config_is_valid(self.configs[config_key]['config']) |
492 | 80 | missing_fields = check_for_missing_config_fields(self.configs[config_key]['config']) | 80 | stored = SimpleNamespace() |
493 | 81 | stored.redis_relation = {} | ||
494 | 82 | missing_fields = check_for_missing_config_fields(self.configs[config_key]['config'], stored) | ||
495 | 81 | self.assertEqual(config_valid, False, 'Invalid config is not recognized as invalid') | 83 | self.assertEqual(config_valid, False, 'Invalid config is not recognized as invalid') |
496 | 82 | self.assertEqual( | 84 | self.assertEqual( |
497 | 83 | missing_fields, | 85 | missing_fields, |
498 | @@ -91,6 +93,8 @@ class TestDiscourseK8sCharmHooksDisabled(unittest.TestCase): | |||
499 | 91 | test_config['db_user'] = 'discourse_m' | 93 | test_config['db_user'] = 'discourse_m' |
500 | 92 | test_config['db_password'] = 'a_real_password' | 94 | test_config['db_password'] = 'a_real_password' |
501 | 93 | test_config['db_host'] = '10.9.89.237' | 95 | test_config['db_host'] = '10.9.89.237' |
502 | 96 | test_config['redis_host'] = '10.9.89.197' | ||
503 | 97 | test_config['redis_port'] = 6379 | ||
504 | 94 | db_event = SimpleNamespace() | 98 | db_event = SimpleNamespace() |
505 | 95 | db_event.master = SimpleNamespace() | 99 | db_event.master = SimpleNamespace() |
506 | 96 | db_event.master.user = 'discourse_m' | 100 | db_event.master.user = 'discourse_m' |
This merge proposal is being monitored by mergebot. Change the status to Approved to merge.