Merge ~barryprice/charm-k8s-bind/+git/charm-k8s-bind:sidecar into charm-k8s-bind:master

Proposed by Barry Price
Status: Work in progress
Proposed branch: ~barryprice/charm-k8s-bind/+git/charm-k8s-bind:sidecar
Merge into: charm-k8s-bind:master
Diff against target: 749 lines (+206/-370)
9 files modified
.jujuignore (+10/-5)
Makefile (+10/-3)
README.md (+1/-1)
config.yaml (+0/-29)
metadata.yaml (+7/-3)
requirements.txt (+1/-1)
src/charm.py (+89/-99)
tests/unit/test_charm.py (+87/-228)
tox.ini (+1/-1)
Reviewer Review Type Date Requested Status
Bind Charmers Pending
Review via email: mp+402876@code.launchpad.net

Commit message

WIP sidecar conversion for bind (test coverage not yet complete), with a few tweaks to base files

To post a comment you must log in.
492731a... by Barry Price

Rename charm for consistency with others

66bae80... by Barry Price

Bugfixes

0511d12... by Barry Price

Bind, not Mattermost!

0ae5151... by Barry Price

Remove duplicate hook

270c6f0... by Barry Price

Use the new image spec with a single wrapper to apply config and run the service

3c06f70... by Barry Price

Remove obsolete unit tests (i.e. most of them)

0056497... by Barry Price

Address conflicts

a353662... by Barry Price

Merge from master, address conflicts

1646220... by Barry Price

Add tests, bring us up to 55% coverage

1594dbe... by Barry Price

Add HTML test coverage report

eb36238... by Barry Price

Add a few more tests

059eb95... by Barry Price

More test fixes, giving us 68% coverage

6a7adf0... by Barry Price

Pull from upstream, enable RFC1918 net recursion by default, add tests to 69% coverage

7f3608e... by Barry Price

Pull from master, fix conflicts

3334949... by Barry Price

Pull from master, fix conflicts

e9fb7e4... by Barry Price

Charmcraft should pack, not build

44c1096... by Barry Price

Add logging/verbosity throughout

74d5bf1... by Barry Price

Move wrapper logging inside the script, fix the exec bug

6c1203c... by Barry Price

Move wrapper logging into the pebble command

6baed5e... by Barry Price

Restore semicolons to wrapper script

9e4f75e... by Barry Price

Restore default config if recursion is toggled off post-install

3e2c7cb... by Barry Price

Merge from master, address conflicts

3f0d665... by Barry Price

Merge from master, address conflicts

f524d71... by Barry Price

Merge branch 'master' into sidecar

Unmerged commits

f524d71... by Barry Price

Merge branch 'master' into sidecar

3f0d665... by Barry Price

Merge from master, address conflicts

3e2c7cb... by Barry Price

Merge from master, address conflicts

9e4f75e... by Barry Price

Restore default config if recursion is toggled off post-install

6baed5e... by Barry Price

Restore semicolons to wrapper script

6c1203c... by Barry Price

Move wrapper logging into the pebble command

74d5bf1... by Barry Price

Move wrapper logging inside the script, fix the exec bug

44c1096... by Barry Price

Add logging/verbosity throughout

e9fb7e4... by Barry Price

Charmcraft should pack, not build

3334949... by Barry Price

Pull from master, fix conflicts

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.jujuignore b/.jujuignore
2index 128851d..2a8f147 100644
3--- a/.jujuignore
4+++ b/.jujuignore
5@@ -1,9 +1,14 @@
6-*~
7 *.charm
8+*.py[cod]
9+*~
10 .coverage
11 .gitignore
12-__pycache__
13-/dockerfile
14-/image-scripts/
15-/tests/
16+/Dockerfile
17 /Makefile
18+/env
19+/htmlcov/
20+/image-scripts/
21+__pycache__
22+requirements.txt
23+tests/
24+tox.ini
25diff --git a/Makefile b/Makefile
26index 564aeb9..9869bc8 100644
27--- a/Makefile
28+++ b/Makefile
29@@ -5,11 +5,16 @@ blacken:
30 @echo "Normalising python layout with black."
31 @tox -e black
32
33-
34 lint: blacken
35 @echo "Running flake8"
36 @tox -e lint
37
38+deps:
39+ @echo "Checking charmcraft is present."
40+ @command -v charmcraft >/dev/null || { echo "Please install charmcraft to continue ('sudo snap install charmcraft')" && false; }
41+ @echo "Checking tox is present."
42+ @command -v tox >/dev/null || { echo "Please install tox to continue ('sudo apt-get install tox')" && false; }
43+
44 # We actually use the build directory created by charmcraft,
45 # but the .charm file makes a much more convenient sentinel.
46 unittest: bind-k8s.charm
47@@ -22,11 +27,13 @@ clean:
48 @git clean -fXd
49
50 bind-k8s.charm: src/*.py requirements.txt
51- charmcraft build
52+ charmcraft pack
53
54 image-deps:
55 @echo "Checking shellcheck is present."
56 @command -v shellcheck >/dev/null || { echo "Please install shellcheck to continue ('sudo snap install shellcheck')" && false; }
57+ @echo "Checking docker is present."
58+ @command -v docker >/dev/null || { echo "Please install docker to continue ('sudo apt-get install docker.io')" && false; }
59
60 image-lint: image-deps
61 @echo "Running shellcheck."
62@@ -43,4 +50,4 @@ image-build: image-lint
63 -t bind:$(DIST_RELEASE)-latest \
64 .
65
66-.PHONY: blacken lint unittest test clean image-deps image-lint image-build
67+.PHONY: blacken lint deps unittest test clean image-deps image-lint image-build
68diff --git a/README.md b/README.md
69index 8a2b7e6..c4f750f 100644
70--- a/README.md
71+++ b/README.md
72@@ -17,7 +17,7 @@ details on using Juju with MicroK8s for easy local testing [see here](https://ju
73
74 To deploy this charm in a juju k8s model:
75 ```
76-juju deploy bind-k8s
77+juju deploy bind-k8s --channel edge
78 ```
79 The charm will deploy bind with its stock Ubuntu package configuration, which
80 will forward all queries to root name servers. DNSSEC is enabled by default.
81diff --git a/config.yaml b/config.yaml
82index 9b1de1d..e65e986 100644
83--- a/config.yaml
84+++ b/config.yaml
85@@ -1,33 +1,4 @@
86 options:
87- bind_image_path:
88- type: string
89- description: |
90- The location of the image to use, e.g. "registry.example.com/bind:v1".
91-
92- This setting is required.
93- default: "bindcharmers/bind:v1.0-20.04_edge"
94- bind_image_username:
95- type: string
96- description: "Username to use for the configured image registry, if required"
97- default: ""
98- bind_image_password:
99- type: string
100- description: "Password to use for the configured image registry, if required"
101- default: ""
102- container_config:
103- type: string
104- description: >
105- YAML formatted map of container config keys & values. These are
106- generally accessed from inside the image as environment variables.
107- Use to configure customized Wordpress images. This configuration
108- gets logged; use container_secrets for secrets.
109- default: ""
110- container_secrets:
111- type: string
112- description: >
113- YAML formatted map of secrets. Works just like container_config,
114- except that values should not be logged.
115- default: ""
116 custom_config_repo:
117 type: string
118 description: |
119diff --git a/metadata.yaml b/metadata.yaml
120index 6c09e29..87d17fc 100644
121--- a/metadata.yaml
122+++ b/metadata.yaml
123@@ -5,11 +5,15 @@ docs: https://discourse.charmhub.io/t/bind-documentation-overview/3973
124 description: |
125 The original, complete open source DNS implementation.
126 https://www.isc.org/bind/
127-min-juju-version: 2.8.0
128 maintainers:
129 - https://launchpad.net/~bind-charmers <bind-charmers@lists.launchpad.net>
130 tags:
131 - network
132 - ops
133-series:
134- - kubernetes
135+containers:
136+ bind:
137+ resource: bind-image
138+resources:
139+ bind-image:
140+ type: oci-image
141+ description: Docker image for Bind to run
142diff --git a/requirements.txt b/requirements.txt
143index 2d81d3b..fcf2df8 100644
144--- a/requirements.txt
145+++ b/requirements.txt
146@@ -1 +1 @@
147-ops
148+https://github.com/canonical/operator/archive/refs/heads/master.zip
149diff --git a/src/charm.py b/src/charm.py
150index 964922c..c9383e0 100755
151--- a/src/charm.py
152+++ b/src/charm.py
153@@ -5,29 +5,96 @@
154
155 import logging
156 from ops.charm import CharmBase
157+from ops.framework import StoredState
158 from ops.main import main
159-from ops.model import ActiveStatus, MaintenanceStatus
160-from pprint import pformat
161-from yaml import safe_load
162+from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus, WaitingStatus
163
164-logger = logging.getLogger()
165+logger = logging.getLogger(__name__)
166
167-REQUIRED_SETTINGS = ['bind_image_path']
168+CONTAINER_NAME = 'bind' # per metadata.yaml
169+REQUIRED_SETTINGS = []
170
171
172 class BindK8sCharm(CharmBase):
173+
174+ state = StoredState()
175+
176 def __init__(self, *args):
177 """Initialise our class, we only care about 'start' and 'config-changed' hooks."""
178 super().__init__(*args)
179- self.framework.observe(self.on.start, self.on_config_changed)
180- self.framework.observe(self.on.config_changed, self.on_config_changed)
181+
182+ self.framework.observe(self.on.config_changed, self._on_config_changed)
183+ self.framework.observe(self.on.bind_pebble_ready, self._on_bind_pebble_ready)
184+
185+ # state
186+ self.state.set_default(bind_pebble_ready=False)
187+
188+ def _get_pebble_config(self):
189+ """Generate our pebble config."""
190+ logger.debug('Generating pebble config')
191+ env_config = self._generate_env_config()
192+ pebble_config = {
193+ "summary": "Bind layer",
194+ "description": "Bind layer",
195+ "services": {
196+ "bind": {
197+ "override": "replace",
198+ "summary": "Bind service",
199+ "command": 'bash -c "/usr/local/bin/docker-wrapper.sh &> /proc/1/fd/1"',
200+ "startup": "enabled",
201+ "environment": env_config,
202+ }
203+ },
204+ }
205+ return pebble_config
206+
207+ def _on_bind_pebble_ready(self, event):
208+ """Handle the on pebble ready event."""
209+ logger.debug('Setting self.state.bind_pebble_ready to True')
210+ self.state.bind_pebble_ready = True
211+
212+ def _on_config_changed(self, event):
213+ """Handle config-changed event."""
214+ problems = self._check_for_config_problems()
215+ if problems:
216+ self.unit.status = BlockedStatus(problems)
217+ return
218+
219+ if not self.state.bind_pebble_ready:
220+ logger.debug('Waiting for pebble')
221+ self.unit.status = WaitingStatus('Waiting for pebble')
222+ event.defer()
223+ return
224+
225+ pebble_config = self._get_pebble_config()
226+ if not pebble_config:
227+ # Charm will be in blocked status.
228+ event.defer()
229+ return
230+
231+ container = self.unit.get_container(CONTAINER_NAME)
232+ services = container.get_plan().to_dict().get("services", {})
233+ if services != pebble_config["services"]:
234+ logger.debug('Adding layer to pebble')
235+ self.unit.status = MaintenanceStatus('Adding layer to pebble')
236+ container.add_layer("bind", pebble_config, combine=True)
237+
238+ self.unit.status = MaintenanceStatus('Restarting bind')
239+ service = container.get_service("bind")
240+ if service.is_running():
241+ logger.debug('Stopping bind')
242+ container.stop("bind")
243+ logger.debug('Starting bind')
244+ container.start("bind")
245+
246+ self.unit.status = ActiveStatus()
247
248 def _check_for_config_problems(self):
249 """Return a string describing any configuration problems (or an empty string if none)."""
250 problems = ''
251
252 missing = self._missing_charm_settings()
253- if missing:
254+ if missing: # pragma: no cover
255 problems = 'required setting(s) empty: {}'.format(', '.join(sorted(missing)))
256
257 return problems
258@@ -38,104 +105,27 @@ class BindK8sCharm(CharmBase):
259
260 missing = {setting for setting in REQUIRED_SETTINGS if not config[setting]}
261
262- if config['bind_image_username'] and not config['bind_image_password']:
263- missing.add('bind_image_password')
264-
265 return missing
266
267- def on_config_changed(self, event):
268- """Check that we're leader, and if so, set up the pod."""
269- if self.model.unit.is_leader():
270- # Only the leader can set_spec().
271- resources = self.make_pod_resources()
272- spec = self.make_pod_spec()
273- spec.update(resources)
274-
275- msg = "Configuring pod"
276- logger.info(msg)
277- self.model.unit.status = MaintenanceStatus(msg)
278- self.model.pod.set_spec(spec)
279-
280- msg = "Pod configured"
281- logger.info(msg)
282- self.model.unit.status = ActiveStatus(msg)
283- else:
284- logger.info("Spec changes ignored by non-leader")
285- self.model.unit.status = ActiveStatus()
286-
287- def make_pod_resources(self):
288- """Compile and return our pod resources (e.g. ingresses)."""
289- # LP#1889746: We need to define a manual ingress here to work around LP#1889703.
290- resources = {} # TODO
291- logger.info("This is the Kubernetes Pod resources <<EOM\n{}\nEOM".format(pformat(resources)))
292- return resources
293-
294- def generate_pod_config(self, secured=True):
295- """Kubernetes pod config generator.
296-
297- generate_pod_config generates Kubernetes deployment config.
298- If the secured keyword is set then it will return a sanitised copy
299- without exposing secrets.
300- """
301+ def _generate_env_config(self):
302+ """Kubernetes environment config generator."""
303 config = self.model.config
304- pod_config = {}
305- if config["container_config"].strip():
306- pod_config = safe_load(config["container_config"])
307-
308+ env_config = {}
309 if config["custom_config_repo"].strip():
310- pod_config["CUSTOM_CONFIG_REPO"] = config["custom_config_repo"]
311-
312- if config["enable_rfc1918_recursion"]:
313- pod_config["ENABLE_RFC1918_RECURSION"] = 1
314-
315- if config["https_proxy"].strip():
316- pod_config["http_proxy"] = config["https_proxy"]
317- pod_config["https_proxy"] = config["https_proxy"]
318-
319- if secured:
320- return pod_config
321-
322- if config["container_secrets"].strip():
323- container_secrets = safe_load(config["container_secrets"])
324+ logger.debug('Custom config repo set')
325+ env_config["CUSTOM_CONFIG_REPO"] = config["custom_config_repo"]
326+ elif config["enable_rfc1918_recursion"]:
327+ logger.debug('No custom config repo set, recursion will be enabled')
328+ env_config["ENABLE_RFC1918_RECURSION"] = 1
329 else:
330- container_secrets = {}
331-
332- pod_config.update(container_secrets)
333- return pod_config
334-
335- def make_pod_spec(self):
336- """Set up and return our full pod spec here."""
337- config = self.model.config
338- full_pod_config = self.generate_pod_config(secured=False)
339- secure_pod_config = self.generate_pod_config(secured=True)
340-
341- ports = [
342- {"name": "domain-tcp", "containerPort": 53, "protocol": "TCP"},
343- {"name": "domain-udp", "containerPort": 53, "protocol": "UDP"},
344- ]
345-
346- spec = {
347- "version": 2,
348- "containers": [
349- {
350- "name": self.app.name,
351- "imageDetails": {"imagePath": config["bind_image_path"]},
352- "ports": ports,
353- "config": secure_pod_config,
354- "kubernetes": {"readinessProbe": {"exec": {"command": ["/usr/local/bin/dns-check.sh"]}}},
355- }
356- ],
357- }
358-
359- logger.info("This is the Kubernetes Pod spec config (sans secrets) <<EOM\n{}\nEOM".format(pformat(spec)))
360+ logger.debug('No custom config repo set, recursion disabled too, deploying with stock config')
361+ pass
362
363- if config.get("bind_image_username") and config.get("bind_image_password"):
364- spec.get("containers")[0].get("imageDetails")["username"] = config["bind_image_username"]
365- spec.get("containers")[0].get("imageDetails")["password"] = config["bind_image_password"]
366-
367- secure_pod_config.update(full_pod_config)
368+ if config["https_proxy"].strip():
369+ env_config["http_proxy"] = config["https_proxy"]
370+ env_config["https_proxy"] = config["https_proxy"]
371
372- return spec
373+ return env_config
374
375
376 if __name__ == "__main__": # pragma: no cover
377diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py
378index c9834d1..1736527 100644
379--- a/tests/unit/test_charm.py
380+++ b/tests/unit/test_charm.py
381@@ -3,268 +3,127 @@
382
383 import unittest
384
385+from unittest.mock import MagicMock
386+
387 from charm import BindK8sCharm
388
389 from ops import testing
390-from ops.model import ActiveStatus
391+from ops.model import WaitingStatus
392
393-CONFIG_EMPTY = {
394- 'bind_image_path': '',
395- 'bind_image_username': '',
396- 'bind_image_password': '',
397- 'container_config': '',
398- 'container_secrets': '',
399+CONFIG_VALID = {
400 'custom_config_repo': '',
401+ 'enable_rfc1918_recursion': True,
402 'https_proxy': '',
403 }
404
405-CONFIG_IMAGE_PASSWORD_MISSING = {
406- 'bind_image_path': 'example.com/bind:v1',
407- 'bind_image_username': 'username',
408- 'bind_image_password': '',
409- 'container_config': '',
410- 'container_secrets': '',
411- 'custom_config_repo': '',
412- 'https_proxy': '',
413-}
414
415-CONFIG_VALID = {
416- 'bind_image_path': 'example.com/bind:v1',
417- 'bind_image_username': '',
418- 'bind_image_password': '',
419- 'container_config': '',
420- 'container_secrets': '',
421- 'custom_config_repo': '',
422- 'https_proxy': '',
423-}
424+def _pebble_services_bind(pebble_config):
425+ """Extract bind's service from the given pebble_config."""
426+ return pebble_config['services']['bind']
427
428-CONFIG_VALID_WITHOUT_RECURSION = {
429- 'bind_image_path': 'example.com/bind:v1',
430- 'bind_image_username': '',
431- 'bind_image_password': '',
432- 'enable_rfc1918_recursion': False,
433- 'container_config': '',
434- 'container_secrets': '',
435- 'custom_config_repo': '',
436- 'https_proxy': '',
437-}
438
439-CONFIG_VALID_WITH_CREDS = {
440- 'bind_image_path': 'secure.example.com/bind:v1',
441- 'bind_image_username': 'test-user',
442- 'bind_image_password': 'test-password',
443- 'container_config': '',
444- 'container_secrets': '',
445- 'custom_config_repo': '',
446- 'https_proxy': '',
447-}
448+def _pebble_services_bind_env(pebble_config):
449+ """Extract bind's service environment from the given pebble_config."""
450+ return pebble_config['services']['bind']['environment']
451
452-CONFIG_VALID_WITH_CONTAINER_CONFIG = {
453- 'bind_image_path': 'example.com/bind:v1',
454- 'bind_image_username': '',
455- 'bind_image_password': '',
456- 'container_config': '"magic_number": 123',
457- 'container_secrets': '',
458- 'custom_config_repo': '',
459- 'https_proxy': '',
460-}
461
462-CONFIG_VALID_WITH_CONTAINER_CONFIG_AND_SECRETS = {
463- 'bind_image_path': 'example.com/bind:v1',
464- 'bind_image_username': '',
465- 'bind_image_password': '',
466- 'container_config': '"magic_number": 123',
467- 'container_secrets': '"secret_password": "xyzzy"',
468- 'custom_config_repo': '',
469- 'https_proxy': '',
470-}
471+def _pebble_summary(pebble_config):
472+ """Extract bind's summary from the given pebble_config."""
473+ return pebble_config['summary']
474
475-CONFIG_VALID_WITH_CUSTOM_CONFIG_REPO_AND_PROXY = {
476- 'bind_image_path': 'example.com/bind:v1',
477- 'bind_image_username': '',
478- 'bind_image_password': '',
479- 'container_config': '',
480- 'container_secrets': '',
481- 'custom_config_repo': 'https://git.example.com/example-bind-config.git',
482- 'https_proxy': 'http://webproxy.example.com:3128/',
483-}
484+
485+def _pebble_description(pebble_config):
486+ """Extract bind's description from the given pebble_config."""
487+ return pebble_config['description']
488
489
490 class TestBindK8s(unittest.TestCase):
491 maxDiff = None
492
493 def setUp(self):
494+ """Setup the harness object."""
495 self.harness = testing.Harness(BindK8sCharm)
496 self.harness.begin()
497- self.harness.disable_hooks()
498+ self.harness.add_oci_resource('bind-image')
499
500- def test_check_for_config_problems_empty_image_path(self):
501- """Confirm that we generate an error if we're not told what image to use."""
502- self.harness.update_config(CONFIG_EMPTY)
503- expected = 'required setting(s) empty: bind_image_path'
504- self.assertEqual(self.harness.charm._check_for_config_problems(), expected)
505+ self._expected = dict()
506
507- def test_check_for_config_problems_empty_image_password(self):
508- """Confirm that we generate an error if we're not given valid registry creds."""
509- self.harness.update_config(CONFIG_IMAGE_PASSWORD_MISSING)
510- expected = 'required setting(s) empty: bind_image_password'
511- self.assertEqual(self.harness.charm._check_for_config_problems(), expected)
512+ def tearDown(self):
513+ """Cleanup the harness."""
514+ self.harness.cleanup()
515
516 def test_check_for_config_problems_none(self):
517 """Confirm that we accept valid config."""
518 self.harness.update_config(CONFIG_VALID)
519- expected = ''
520- self.assertEqual(self.harness.charm._check_for_config_problems(), expected)
521+ self._expected = ''
522+ self.assertEqual(self.harness.charm._check_for_config_problems(), self._expected)
523
524- def test_make_pod_resources(self):
525- """Confirm that we generate the expected pod resources (see LP#1889746)."""
526- expected = {}
527- self.assertEqual(self.harness.charm.make_pod_resources(), expected)
528-
529- def test_make_pod_spec_basic(self):
530- """Confirm that we generate the expected pod spec from valid config."""
531+ def test_on_bind_pebble_ready_false(self):
532 self.harness.update_config(CONFIG_VALID)
533- expected = {
534- 'version': 2,
535- 'containers': [
536- {
537- 'name': 'bind-k8s',
538- 'imageDetails': {'imagePath': 'example.com/bind:v1'},
539- 'ports': [
540- {'containerPort': 53, 'name': 'domain-tcp', 'protocol': 'TCP'},
541- {'containerPort': 53, 'name': 'domain-udp', 'protocol': 'UDP'},
542- ],
543- 'config': {'ENABLE_RFC1918_RECURSION': 1},
544- 'kubernetes': {'readinessProbe': {'exec': {'command': ['/usr/local/bin/dns-check.sh']}}},
545- }
546- ],
547- }
548- self.assertEqual(self.harness.charm.make_pod_spec(), expected)
549-
550- def test_make_pod_spec_without_recursion(self):
551- """Confirm that we generate the expected pod spec from config disabling recursion."""
552- self.harness.update_config(CONFIG_VALID_WITHOUT_RECURSION)
553- expected = {
554- 'version': 2,
555- 'containers': [
556- {
557- 'name': 'bind-k8s',
558- 'imageDetails': {'imagePath': 'example.com/bind:v1'},
559- 'ports': [
560- {'containerPort': 53, 'name': 'domain-tcp', 'protocol': 'TCP'},
561- {'containerPort': 53, 'name': 'domain-udp', 'protocol': 'UDP'},
562- ],
563- 'config': {},
564- 'kubernetes': {'readinessProbe': {'exec': {'command': ['/usr/local/bin/dns-check.sh']}}},
565- }
566- ],
567- }
568- self.assertEqual(self.harness.charm.make_pod_spec(), expected)
569+ # the event hasn't run yet, so we expect this to be False
570+ self._expected = False
571+ self.assertEqual(self.harness.charm.state.bind_pebble_ready, self._expected)
572
573- def test_make_pod_spec_with_creds(self):
574- """Confirm that we generate the expected pod spec from config containing credentials."""
575- self.harness.update_config(CONFIG_VALID_WITH_CREDS)
576- expected = {
577- 'version': 2,
578- 'containers': [
579- {
580- 'name': 'bind-k8s',
581- 'imageDetails': {
582- 'imagePath': 'secure.example.com/bind:v1',
583- 'username': 'test-user',
584- 'password': 'test-password',
585- },
586- 'ports': [
587- {'containerPort': 53, 'name': 'domain-tcp', 'protocol': 'TCP'},
588- {'containerPort': 53, 'name': 'domain-udp', 'protocol': 'UDP'},
589- ],
590- 'config': {'ENABLE_RFC1918_RECURSION': 1},
591- 'kubernetes': {'readinessProbe': {'exec': {'command': ['/usr/local/bin/dns-check.sh']}}},
592- }
593- ],
594- }
595- self.assertEqual(self.harness.charm.make_pod_spec(), expected)
596-
597- def test_make_pod_spec_with_extra_config(self):
598- """Confirm that we generate the expected pod spec from a more involved valid config."""
599- self.harness.update_config(CONFIG_VALID_WITH_CONTAINER_CONFIG)
600- expected = {
601- 'version': 2,
602- 'containers': [
603- {
604- 'name': 'bind-k8s',
605- 'imageDetails': {'imagePath': 'example.com/bind:v1'},
606- 'ports': [
607- {'containerPort': 53, 'name': 'domain-tcp', 'protocol': 'TCP'},
608- {'containerPort': 53, 'name': 'domain-udp', 'protocol': 'UDP'},
609- ],
610- 'config': {
611- 'ENABLE_RFC1918_RECURSION': 1,
612- 'magic_number': 123,
613- },
614- 'kubernetes': {'readinessProbe': {'exec': {'command': ['/usr/local/bin/dns-check.sh']}}},
615- }
616- ],
617- }
618- self.assertEqual(self.harness.charm.make_pod_spec(), expected)
619+ def test_on_config_changed(self):
620+ self.harness.update_config(CONFIG_VALID)
621+ self._expected = WaitingStatus('Waiting for pebble')
622+ self.assertEqual(self.harness.model.unit.status, self._expected)
623
624- def test_make_pod_spec_with_extra_config_and_secrets(self):
625- """Confirm that we generate the expected pod spec from a more involved valid config that includes secrets."""
626- self.harness.update_config(CONFIG_VALID_WITH_CONTAINER_CONFIG_AND_SECRETS)
627- expected = {
628- 'version': 2,
629- 'containers': [
630- {
631- 'name': 'bind-k8s',
632- 'imageDetails': {'imagePath': 'example.com/bind:v1'},
633- 'ports': [
634- {'containerPort': 53, 'name': 'domain-tcp', 'protocol': 'TCP'},
635- {'containerPort': 53, 'name': 'domain-udp', 'protocol': 'UDP'},
636- ],
637- 'config': {'ENABLE_RFC1918_RECURSION': 1, 'magic_number': 123, 'secret_password': 'xyzzy'},
638- 'kubernetes': {'readinessProbe': {'exec': {'command': ['/usr/local/bin/dns-check.sh']}}},
639- }
640- ],
641- }
642- self.assertEqual(self.harness.charm.make_pod_spec(), expected)
643+ def test_missing_charm_settings(self):
644+ """Confirm that we're missing no settings."""
645+ self.harness.update_config(CONFIG_VALID)
646+ self._expected = set()
647+ self.assertEqual(self.harness.charm._missing_charm_settings(), self._expected)
648
649- def test_make_pod_spec_with_custom_config_repo(self):
650- """Confirm that we generate the expected pod spec from a config that includes a custom repo."""
651- self.harness.update_config(CONFIG_VALID_WITH_CUSTOM_CONFIG_REPO_AND_PROXY)
652- expected = {
653- 'version': 2,
654- 'containers': [
655- {
656- 'name': 'bind-k8s',
657- 'imageDetails': {'imagePath': 'example.com/bind:v1'},
658- 'ports': [
659- {'containerPort': 53, 'name': 'domain-tcp', 'protocol': 'TCP'},
660- {'containerPort': 53, 'name': 'domain-udp', 'protocol': 'UDP'},
661- ],
662- 'config': {
663- 'CUSTOM_CONFIG_REPO': 'https://git.example.com/example-bind-config.git',
664- 'ENABLE_RFC1918_RECURSION': 1,
665- 'http_proxy': 'http://webproxy.example.com:3128/',
666- 'https_proxy': 'http://webproxy.example.com:3128/',
667- },
668- 'kubernetes': {'readinessProbe': {'exec': {'command': ['/usr/local/bin/dns-check.sh']}}},
669- }
670- ],
671- }
672- self.assertEqual(self.harness.charm.make_pod_spec(), expected)
673+ def test_get_pebble_config_description(self):
674+ self.harness.update_config(CONFIG_VALID)
675+ self._expected = "Bind layer"
676+ self.assertEqual(_pebble_description(self.harness.charm._get_pebble_config()), self._expected)
677
678- def test_configure_pod_as_leader(self):
679- """Confirm that our status is set correctly when we're the leader."""
680- self.harness.enable_hooks()
681- self.harness.set_leader(True)
682+ def test_get_pebble_config_summary(self):
683 self.harness.update_config(CONFIG_VALID)
684- expected = ActiveStatus('Pod configured')
685- self.assertEqual(self.harness.model.unit.status, expected)
686+ self._expected = "Bind layer"
687+ self.assertEqual(_pebble_summary(self.harness.charm._get_pebble_config()), self._expected)
688
689- def test_configure_pod_as_non_leader(self):
690- """Confirm that our status is set correctly when we're not the leader."""
691- self.harness.enable_hooks()
692- self.harness.set_leader(False)
693+ def test_get_pebble_config_services_bind(self):
694 self.harness.update_config(CONFIG_VALID)
695- expected = ActiveStatus()
696- self.assertEqual(self.harness.model.unit.status, expected)
697+ self._expected = {
698+ "override": "replace",
699+ "summary": "Bind service",
700+ "command": 'bash -c "/usr/local/bin/docker-wrapper.sh &> /proc/1/fd/1"',
701+ "startup": "enabled",
702+ "environment": {'ENABLE_RFC1918_RECURSION': 1},
703+ }
704+ self.assertEqual(_pebble_services_bind(self.harness.charm._get_pebble_config()), self._expected)
705+
706+ def test_generate_env_config_custom_repo(self):
707+ self.harness.update_config({"custom_config_repo": "https://git.example.com/example-bind-config.git"})
708+ self._expected['CUSTOM_CONFIG_REPO'] = "https://git.example.com/example-bind-config.git"
709+ self.assertEqual(_pebble_services_bind_env(self.harness.charm._get_pebble_config()), self._expected)
710+
711+ def test_generage_env_config_custom_repo_and_proxy(self):
712+ self.harness.update_config(
713+ {
714+ "custom_config_repo": "https://git.example.com/example-bind-config.git",
715+ "https_proxy": "http://webproxy.example.com:3128/",
716+ }
717+ )
718+ self._expected['CUSTOM_CONFIG_REPO'] = "https://git.example.com/example-bind-config.git"
719+ self._expected['http_proxy'] = "http://webproxy.example.com:3128/"
720+ self._expected['https_proxy'] = "http://webproxy.example.com:3128/"
721+ self.assertEqual(_pebble_services_bind_env(self.harness.charm._get_pebble_config()), self._expected)
722+
723+ def test_generate_env_config_just_proxy(self):
724+ self.harness.update_config({})
725+ self._expected['ENABLE_RFC1918_RECURSION'] = 1
726+ self.assertEqual(_pebble_services_bind_env(self.harness.charm._get_pebble_config()), self._expected)
727+
728+ def test_on_bind_pebble_ready(self):
729+ """Test the _on_bind_pebble_ready function."""
730+
731+ # No problem
732+ mock_event = MagicMock()
733+ expected_ret = None
734+
735+ r = self.harness.charm._on_bind_pebble_ready(mock_event)
736+ self.assertEqual(r, expected_ret)
737diff --git a/tox.ini b/tox.ini
738index 0de5149..fb74730 100644
739--- a/tox.ini
740+++ b/tox.ini
741@@ -10,7 +10,7 @@ setenv =
742 [testenv:unit]
743 commands =
744 pytest --ignore mod --ignore {toxinidir}/tests/functional \
745- {posargs:-v --cov=src --cov-report=term-missing --cov-branch}
746+ {posargs:-v --cov=src --cov-report=term-missing --cov-branch --cov-report=html}
747 deps = -r{toxinidir}/tests/unit/requirements.txt
748 -r{toxinidir}/requirements.txt
749 setenv =

Subscribers

People subscribed via source and target branches

to all changes: