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
diff --git a/.jujuignore b/.jujuignore
index 128851d..2a8f147 100644
--- a/.jujuignore
+++ b/.jujuignore
@@ -1,9 +1,14 @@
1*~
2*.charm1*.charm
2*.py[cod]
3*~
3.coverage4.coverage
4.gitignore5.gitignore
5__pycache__6/Dockerfile
6/dockerfile
7/image-scripts/
8/tests/
9/Makefile7/Makefile
8/env
9/htmlcov/
10/image-scripts/
11__pycache__
12requirements.txt
13tests/
14tox.ini
diff --git a/Makefile b/Makefile
index 564aeb9..9869bc8 100644
--- a/Makefile
+++ b/Makefile
@@ -5,11 +5,16 @@ blacken:
5 @echo "Normalising python layout with black."5 @echo "Normalising python layout with black."
6 @tox -e black6 @tox -e black
77
8
9lint: blacken8lint: blacken
10 @echo "Running flake8"9 @echo "Running flake8"
11 @tox -e lint10 @tox -e lint
1211
12deps:
13 @echo "Checking charmcraft is present."
14 @command -v charmcraft >/dev/null || { echo "Please install charmcraft to continue ('sudo snap install charmcraft')" && false; }
15 @echo "Checking tox is present."
16 @command -v tox >/dev/null || { echo "Please install tox to continue ('sudo apt-get install tox')" && false; }
17
13# We actually use the build directory created by charmcraft,18# We actually use the build directory created by charmcraft,
14# but the .charm file makes a much more convenient sentinel.19# but the .charm file makes a much more convenient sentinel.
15unittest: bind-k8s.charm20unittest: bind-k8s.charm
@@ -22,11 +27,13 @@ clean:
22 @git clean -fXd27 @git clean -fXd
2328
24bind-k8s.charm: src/*.py requirements.txt29bind-k8s.charm: src/*.py requirements.txt
25 charmcraft build30 charmcraft pack
2631
27image-deps:32image-deps:
28 @echo "Checking shellcheck is present."33 @echo "Checking shellcheck is present."
29 @command -v shellcheck >/dev/null || { echo "Please install shellcheck to continue ('sudo snap install shellcheck')" && false; }34 @command -v shellcheck >/dev/null || { echo "Please install shellcheck to continue ('sudo snap install shellcheck')" && false; }
35 @echo "Checking docker is present."
36 @command -v docker >/dev/null || { echo "Please install docker to continue ('sudo apt-get install docker.io')" && false; }
3037
31image-lint: image-deps38image-lint: image-deps
32 @echo "Running shellcheck."39 @echo "Running shellcheck."
@@ -43,4 +50,4 @@ image-build: image-lint
43 -t bind:$(DIST_RELEASE)-latest \50 -t bind:$(DIST_RELEASE)-latest \
44 .51 .
4552
46.PHONY: blacken lint unittest test clean image-deps image-lint image-build53.PHONY: blacken lint deps unittest test clean image-deps image-lint image-build
diff --git a/README.md b/README.md
index 8a2b7e6..c4f750f 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ details on using Juju with MicroK8s for easy local testing [see here](https://ju
1717
18To deploy this charm in a juju k8s model:18To deploy this charm in a juju k8s model:
19```19```
20juju deploy bind-k8s20juju deploy bind-k8s --channel edge
21```21```
22The charm will deploy bind with its stock Ubuntu package configuration, which22The charm will deploy bind with its stock Ubuntu package configuration, which
23will forward all queries to root name servers. DNSSEC is enabled by default.23will forward all queries to root name servers. DNSSEC is enabled by default.
diff --git a/config.yaml b/config.yaml
index 9b1de1d..e65e986 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,33 +1,4 @@
1options:1options:
2 bind_image_path:
3 type: string
4 description: |
5 The location of the image to use, e.g. "registry.example.com/bind:v1".
6
7 This setting is required.
8 default: "bindcharmers/bind:v1.0-20.04_edge"
9 bind_image_username:
10 type: string
11 description: "Username to use for the configured image registry, if required"
12 default: ""
13 bind_image_password:
14 type: string
15 description: "Password to use for the configured image registry, if required"
16 default: ""
17 container_config:
18 type: string
19 description: >
20 YAML formatted map of container config keys & values. These are
21 generally accessed from inside the image as environment variables.
22 Use to configure customized Wordpress images. This configuration
23 gets logged; use container_secrets for secrets.
24 default: ""
25 container_secrets:
26 type: string
27 description: >
28 YAML formatted map of secrets. Works just like container_config,
29 except that values should not be logged.
30 default: ""
31 custom_config_repo:2 custom_config_repo:
32 type: string3 type: string
33 description: |4 description: |
diff --git a/metadata.yaml b/metadata.yaml
index 6c09e29..87d17fc 100644
--- a/metadata.yaml
+++ b/metadata.yaml
@@ -5,11 +5,15 @@ docs: https://discourse.charmhub.io/t/bind-documentation-overview/3973
5description: |5description: |
6 The original, complete open source DNS implementation.6 The original, complete open source DNS implementation.
7 https://www.isc.org/bind/7 https://www.isc.org/bind/
8min-juju-version: 2.8.0
9maintainers:8maintainers:
10 - https://launchpad.net/~bind-charmers <bind-charmers@lists.launchpad.net>9 - https://launchpad.net/~bind-charmers <bind-charmers@lists.launchpad.net>
11tags:10tags:
12 - network11 - network
13 - ops12 - ops
14series:13containers:
15 - kubernetes14 bind:
15 resource: bind-image
16resources:
17 bind-image:
18 type: oci-image
19 description: Docker image for Bind to run
diff --git a/requirements.txt b/requirements.txt
index 2d81d3b..fcf2df8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1 @@
1ops1https://github.com/canonical/operator/archive/refs/heads/master.zip
diff --git a/src/charm.py b/src/charm.py
index 964922c..c9383e0 100755
--- a/src/charm.py
+++ b/src/charm.py
@@ -5,29 +5,96 @@
55
6import logging6import logging
7from ops.charm import CharmBase7from ops.charm import CharmBase
8from ops.framework import StoredState
8from ops.main import main9from ops.main import main
9from ops.model import ActiveStatus, MaintenanceStatus10from ops.model import ActiveStatus, BlockedStatus, MaintenanceStatus, WaitingStatus
10from pprint import pformat
11from yaml import safe_load
1211
13logger = logging.getLogger()12logger = logging.getLogger(__name__)
1413
15REQUIRED_SETTINGS = ['bind_image_path']14CONTAINER_NAME = 'bind' # per metadata.yaml
15REQUIRED_SETTINGS = []
1616
1717
18class BindK8sCharm(CharmBase):18class BindK8sCharm(CharmBase):
19
20 state = StoredState()
21
19 def __init__(self, *args):22 def __init__(self, *args):
20 """Initialise our class, we only care about 'start' and 'config-changed' hooks."""23 """Initialise our class, we only care about 'start' and 'config-changed' hooks."""
21 super().__init__(*args)24 super().__init__(*args)
22 self.framework.observe(self.on.start, self.on_config_changed)25
23 self.framework.observe(self.on.config_changed, self.on_config_changed)26 self.framework.observe(self.on.config_changed, self._on_config_changed)
27 self.framework.observe(self.on.bind_pebble_ready, self._on_bind_pebble_ready)
28
29 # state
30 self.state.set_default(bind_pebble_ready=False)
31
32 def _get_pebble_config(self):
33 """Generate our pebble config."""
34 logger.debug('Generating pebble config')
35 env_config = self._generate_env_config()
36 pebble_config = {
37 "summary": "Bind layer",
38 "description": "Bind layer",
39 "services": {
40 "bind": {
41 "override": "replace",
42 "summary": "Bind service",
43 "command": 'bash -c "/usr/local/bin/docker-wrapper.sh &> /proc/1/fd/1"',
44 "startup": "enabled",
45 "environment": env_config,
46 }
47 },
48 }
49 return pebble_config
50
51 def _on_bind_pebble_ready(self, event):
52 """Handle the on pebble ready event."""
53 logger.debug('Setting self.state.bind_pebble_ready to True')
54 self.state.bind_pebble_ready = True
55
56 def _on_config_changed(self, event):
57 """Handle config-changed event."""
58 problems = self._check_for_config_problems()
59 if problems:
60 self.unit.status = BlockedStatus(problems)
61 return
62
63 if not self.state.bind_pebble_ready:
64 logger.debug('Waiting for pebble')
65 self.unit.status = WaitingStatus('Waiting for pebble')
66 event.defer()
67 return
68
69 pebble_config = self._get_pebble_config()
70 if not pebble_config:
71 # Charm will be in blocked status.
72 event.defer()
73 return
74
75 container = self.unit.get_container(CONTAINER_NAME)
76 services = container.get_plan().to_dict().get("services", {})
77 if services != pebble_config["services"]:
78 logger.debug('Adding layer to pebble')
79 self.unit.status = MaintenanceStatus('Adding layer to pebble')
80 container.add_layer("bind", pebble_config, combine=True)
81
82 self.unit.status = MaintenanceStatus('Restarting bind')
83 service = container.get_service("bind")
84 if service.is_running():
85 logger.debug('Stopping bind')
86 container.stop("bind")
87 logger.debug('Starting bind')
88 container.start("bind")
89
90 self.unit.status = ActiveStatus()
2491
25 def _check_for_config_problems(self):92 def _check_for_config_problems(self):
26 """Return a string describing any configuration problems (or an empty string if none)."""93 """Return a string describing any configuration problems (or an empty string if none)."""
27 problems = ''94 problems = ''
2895
29 missing = self._missing_charm_settings()96 missing = self._missing_charm_settings()
30 if missing:97 if missing: # pragma: no cover
31 problems = 'required setting(s) empty: {}'.format(', '.join(sorted(missing)))98 problems = 'required setting(s) empty: {}'.format(', '.join(sorted(missing)))
3299
33 return problems100 return problems
@@ -38,104 +105,27 @@ class BindK8sCharm(CharmBase):
38105
39 missing = {setting for setting in REQUIRED_SETTINGS if not config[setting]}106 missing = {setting for setting in REQUIRED_SETTINGS if not config[setting]}
40107
41 if config['bind_image_username'] and not config['bind_image_password']:
42 missing.add('bind_image_password')
43
44 return missing108 return missing
45109
46 def on_config_changed(self, event):110 def _generate_env_config(self):
47 """Check that we're leader, and if so, set up the pod."""111 """Kubernetes environment config generator."""
48 if self.model.unit.is_leader():
49 # Only the leader can set_spec().
50 resources = self.make_pod_resources()
51 spec = self.make_pod_spec()
52 spec.update(resources)
53
54 msg = "Configuring pod"
55 logger.info(msg)
56 self.model.unit.status = MaintenanceStatus(msg)
57 self.model.pod.set_spec(spec)
58
59 msg = "Pod configured"
60 logger.info(msg)
61 self.model.unit.status = ActiveStatus(msg)
62 else:
63 logger.info("Spec changes ignored by non-leader")
64 self.model.unit.status = ActiveStatus()
65
66 def make_pod_resources(self):
67 """Compile and return our pod resources (e.g. ingresses)."""
68 # LP#1889746: We need to define a manual ingress here to work around LP#1889703.
69 resources = {} # TODO
70 logger.info("This is the Kubernetes Pod resources <<EOM\n{}\nEOM".format(pformat(resources)))
71 return resources
72
73 def generate_pod_config(self, secured=True):
74 """Kubernetes pod config generator.
75
76 generate_pod_config generates Kubernetes deployment config.
77 If the secured keyword is set then it will return a sanitised copy
78 without exposing secrets.
79 """
80 config = self.model.config112 config = self.model.config
81 pod_config = {}113 env_config = {}
82 if config["container_config"].strip():
83 pod_config = safe_load(config["container_config"])
84
85 if config["custom_config_repo"].strip():114 if config["custom_config_repo"].strip():
86 pod_config["CUSTOM_CONFIG_REPO"] = config["custom_config_repo"]115 logger.debug('Custom config repo set')
87116 env_config["CUSTOM_CONFIG_REPO"] = config["custom_config_repo"]
88 if config["enable_rfc1918_recursion"]:117 elif config["enable_rfc1918_recursion"]:
89 pod_config["ENABLE_RFC1918_RECURSION"] = 1118 logger.debug('No custom config repo set, recursion will be enabled')
90119 env_config["ENABLE_RFC1918_RECURSION"] = 1
91 if config["https_proxy"].strip():
92 pod_config["http_proxy"] = config["https_proxy"]
93 pod_config["https_proxy"] = config["https_proxy"]
94
95 if secured:
96 return pod_config
97
98 if config["container_secrets"].strip():
99 container_secrets = safe_load(config["container_secrets"])
100 else:120 else:
101 container_secrets = {}121 logger.debug('No custom config repo set, recursion disabled too, deploying with stock config')
102122 pass
103 pod_config.update(container_secrets)
104 return pod_config
105
106 def make_pod_spec(self):
107 """Set up and return our full pod spec here."""
108 config = self.model.config
109 full_pod_config = self.generate_pod_config(secured=False)
110 secure_pod_config = self.generate_pod_config(secured=True)
111
112 ports = [
113 {"name": "domain-tcp", "containerPort": 53, "protocol": "TCP"},
114 {"name": "domain-udp", "containerPort": 53, "protocol": "UDP"},
115 ]
116
117 spec = {
118 "version": 2,
119 "containers": [
120 {
121 "name": self.app.name,
122 "imageDetails": {"imagePath": config["bind_image_path"]},
123 "ports": ports,
124 "config": secure_pod_config,
125 "kubernetes": {"readinessProbe": {"exec": {"command": ["/usr/local/bin/dns-check.sh"]}}},
126 }
127 ],
128 }
129
130 logger.info("This is the Kubernetes Pod spec config (sans secrets) <<EOM\n{}\nEOM".format(pformat(spec)))
131123
132 if config.get("bind_image_username") and config.get("bind_image_password"):124 if config["https_proxy"].strip():
133 spec.get("containers")[0].get("imageDetails")["username"] = config["bind_image_username"]125 env_config["http_proxy"] = config["https_proxy"]
134 spec.get("containers")[0].get("imageDetails")["password"] = config["bind_image_password"]126 env_config["https_proxy"] = config["https_proxy"]
135
136 secure_pod_config.update(full_pod_config)
137127
138 return spec128 return env_config
139129
140130
141if __name__ == "__main__": # pragma: no cover131if __name__ == "__main__": # pragma: no cover
diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py
index c9834d1..1736527 100644
--- a/tests/unit/test_charm.py
+++ b/tests/unit/test_charm.py
@@ -3,268 +3,127 @@
33
4import unittest4import unittest
55
6from unittest.mock import MagicMock
7
6from charm import BindK8sCharm8from charm import BindK8sCharm
79
8from ops import testing10from ops import testing
9from ops.model import ActiveStatus11from ops.model import WaitingStatus
1012
11CONFIG_EMPTY = {13CONFIG_VALID = {
12 'bind_image_path': '',
13 'bind_image_username': '',
14 'bind_image_password': '',
15 'container_config': '',
16 'container_secrets': '',
17 'custom_config_repo': '',14 'custom_config_repo': '',
15 'enable_rfc1918_recursion': True,
18 'https_proxy': '',16 'https_proxy': '',
19}17}
2018
21CONFIG_IMAGE_PASSWORD_MISSING = {
22 'bind_image_path': 'example.com/bind:v1',
23 'bind_image_username': 'username',
24 'bind_image_password': '',
25 'container_config': '',
26 'container_secrets': '',
27 'custom_config_repo': '',
28 'https_proxy': '',
29}
3019
31CONFIG_VALID = {20def _pebble_services_bind(pebble_config):
32 'bind_image_path': 'example.com/bind:v1',21 """Extract bind's service from the given pebble_config."""
33 'bind_image_username': '',22 return pebble_config['services']['bind']
34 'bind_image_password': '',
35 'container_config': '',
36 'container_secrets': '',
37 'custom_config_repo': '',
38 'https_proxy': '',
39}
4023
41CONFIG_VALID_WITHOUT_RECURSION = {
42 'bind_image_path': 'example.com/bind:v1',
43 'bind_image_username': '',
44 'bind_image_password': '',
45 'enable_rfc1918_recursion': False,
46 'container_config': '',
47 'container_secrets': '',
48 'custom_config_repo': '',
49 'https_proxy': '',
50}
5124
52CONFIG_VALID_WITH_CREDS = {25def _pebble_services_bind_env(pebble_config):
53 'bind_image_path': 'secure.example.com/bind:v1',26 """Extract bind's service environment from the given pebble_config."""
54 'bind_image_username': 'test-user',27 return pebble_config['services']['bind']['environment']
55 'bind_image_password': 'test-password',
56 'container_config': '',
57 'container_secrets': '',
58 'custom_config_repo': '',
59 'https_proxy': '',
60}
6128
62CONFIG_VALID_WITH_CONTAINER_CONFIG = {
63 'bind_image_path': 'example.com/bind:v1',
64 'bind_image_username': '',
65 'bind_image_password': '',
66 'container_config': '"magic_number": 123',
67 'container_secrets': '',
68 'custom_config_repo': '',
69 'https_proxy': '',
70}
7129
72CONFIG_VALID_WITH_CONTAINER_CONFIG_AND_SECRETS = {30def _pebble_summary(pebble_config):
73 'bind_image_path': 'example.com/bind:v1',31 """Extract bind's summary from the given pebble_config."""
74 'bind_image_username': '',32 return pebble_config['summary']
75 'bind_image_password': '',
76 'container_config': '"magic_number": 123',
77 'container_secrets': '"secret_password": "xyzzy"',
78 'custom_config_repo': '',
79 'https_proxy': '',
80}
8133
82CONFIG_VALID_WITH_CUSTOM_CONFIG_REPO_AND_PROXY = {34
83 'bind_image_path': 'example.com/bind:v1',35def _pebble_description(pebble_config):
84 'bind_image_username': '',36 """Extract bind's description from the given pebble_config."""
85 'bind_image_password': '',37 return pebble_config['description']
86 'container_config': '',
87 'container_secrets': '',
88 'custom_config_repo': 'https://git.example.com/example-bind-config.git',
89 'https_proxy': 'http://webproxy.example.com:3128/',
90}
9138
9239
93class TestBindK8s(unittest.TestCase):40class TestBindK8s(unittest.TestCase):
94 maxDiff = None41 maxDiff = None
9542
96 def setUp(self):43 def setUp(self):
44 """Setup the harness object."""
97 self.harness = testing.Harness(BindK8sCharm)45 self.harness = testing.Harness(BindK8sCharm)
98 self.harness.begin()46 self.harness.begin()
99 self.harness.disable_hooks()47 self.harness.add_oci_resource('bind-image')
10048
101 def test_check_for_config_problems_empty_image_path(self):49 self._expected = dict()
102 """Confirm that we generate an error if we're not told what image to use."""
103 self.harness.update_config(CONFIG_EMPTY)
104 expected = 'required setting(s) empty: bind_image_path'
105 self.assertEqual(self.harness.charm._check_for_config_problems(), expected)
10650
107 def test_check_for_config_problems_empty_image_password(self):51 def tearDown(self):
108 """Confirm that we generate an error if we're not given valid registry creds."""52 """Cleanup the harness."""
109 self.harness.update_config(CONFIG_IMAGE_PASSWORD_MISSING)53 self.harness.cleanup()
110 expected = 'required setting(s) empty: bind_image_password'
111 self.assertEqual(self.harness.charm._check_for_config_problems(), expected)
11254
113 def test_check_for_config_problems_none(self):55 def test_check_for_config_problems_none(self):
114 """Confirm that we accept valid config."""56 """Confirm that we accept valid config."""
115 self.harness.update_config(CONFIG_VALID)57 self.harness.update_config(CONFIG_VALID)
116 expected = ''58 self._expected = ''
117 self.assertEqual(self.harness.charm._check_for_config_problems(), expected)59 self.assertEqual(self.harness.charm._check_for_config_problems(), self._expected)
11860
119 def test_make_pod_resources(self):61 def test_on_bind_pebble_ready_false(self):
120 """Confirm that we generate the expected pod resources (see LP#1889746)."""
121 expected = {}
122 self.assertEqual(self.harness.charm.make_pod_resources(), expected)
123
124 def test_make_pod_spec_basic(self):
125 """Confirm that we generate the expected pod spec from valid config."""
126 self.harness.update_config(CONFIG_VALID)62 self.harness.update_config(CONFIG_VALID)
127 expected = {63 # the event hasn't run yet, so we expect this to be False
128 'version': 2,64 self._expected = False
129 'containers': [65 self.assertEqual(self.harness.charm.state.bind_pebble_ready, self._expected)
130 {
131 'name': 'bind-k8s',
132 'imageDetails': {'imagePath': 'example.com/bind:v1'},
133 'ports': [
134 {'containerPort': 53, 'name': 'domain-tcp', 'protocol': 'TCP'},
135 {'containerPort': 53, 'name': 'domain-udp', 'protocol': 'UDP'},
136 ],
137 'config': {'ENABLE_RFC1918_RECURSION': 1},
138 'kubernetes': {'readinessProbe': {'exec': {'command': ['/usr/local/bin/dns-check.sh']}}},
139 }
140 ],
141 }
142 self.assertEqual(self.harness.charm.make_pod_spec(), expected)
143
144 def test_make_pod_spec_without_recursion(self):
145 """Confirm that we generate the expected pod spec from config disabling recursion."""
146 self.harness.update_config(CONFIG_VALID_WITHOUT_RECURSION)
147 expected = {
148 'version': 2,
149 'containers': [
150 {
151 'name': 'bind-k8s',
152 'imageDetails': {'imagePath': 'example.com/bind:v1'},
153 'ports': [
154 {'containerPort': 53, 'name': 'domain-tcp', 'protocol': 'TCP'},
155 {'containerPort': 53, 'name': 'domain-udp', 'protocol': 'UDP'},
156 ],
157 'config': {},
158 'kubernetes': {'readinessProbe': {'exec': {'command': ['/usr/local/bin/dns-check.sh']}}},
159 }
160 ],
161 }
162 self.assertEqual(self.harness.charm.make_pod_spec(), expected)
16366
164 def test_make_pod_spec_with_creds(self):67 def test_on_config_changed(self):
165 """Confirm that we generate the expected pod spec from config containing credentials."""68 self.harness.update_config(CONFIG_VALID)
166 self.harness.update_config(CONFIG_VALID_WITH_CREDS)69 self._expected = WaitingStatus('Waiting for pebble')
167 expected = {70 self.assertEqual(self.harness.model.unit.status, self._expected)
168 'version': 2,
169 'containers': [
170 {
171 'name': 'bind-k8s',
172 'imageDetails': {
173 'imagePath': 'secure.example.com/bind:v1',
174 'username': 'test-user',
175 'password': 'test-password',
176 },
177 'ports': [
178 {'containerPort': 53, 'name': 'domain-tcp', 'protocol': 'TCP'},
179 {'containerPort': 53, 'name': 'domain-udp', 'protocol': 'UDP'},
180 ],
181 'config': {'ENABLE_RFC1918_RECURSION': 1},
182 'kubernetes': {'readinessProbe': {'exec': {'command': ['/usr/local/bin/dns-check.sh']}}},
183 }
184 ],
185 }
186 self.assertEqual(self.harness.charm.make_pod_spec(), expected)
187
188 def test_make_pod_spec_with_extra_config(self):
189 """Confirm that we generate the expected pod spec from a more involved valid config."""
190 self.harness.update_config(CONFIG_VALID_WITH_CONTAINER_CONFIG)
191 expected = {
192 'version': 2,
193 'containers': [
194 {
195 'name': 'bind-k8s',
196 'imageDetails': {'imagePath': 'example.com/bind:v1'},
197 'ports': [
198 {'containerPort': 53, 'name': 'domain-tcp', 'protocol': 'TCP'},
199 {'containerPort': 53, 'name': 'domain-udp', 'protocol': 'UDP'},
200 ],
201 'config': {
202 'ENABLE_RFC1918_RECURSION': 1,
203 'magic_number': 123,
204 },
205 'kubernetes': {'readinessProbe': {'exec': {'command': ['/usr/local/bin/dns-check.sh']}}},
206 }
207 ],
208 }
209 self.assertEqual(self.harness.charm.make_pod_spec(), expected)
21071
211 def test_make_pod_spec_with_extra_config_and_secrets(self):72 def test_missing_charm_settings(self):
212 """Confirm that we generate the expected pod spec from a more involved valid config that includes secrets."""73 """Confirm that we're missing no settings."""
213 self.harness.update_config(CONFIG_VALID_WITH_CONTAINER_CONFIG_AND_SECRETS)74 self.harness.update_config(CONFIG_VALID)
214 expected = {75 self._expected = set()
215 'version': 2,76 self.assertEqual(self.harness.charm._missing_charm_settings(), self._expected)
216 'containers': [
217 {
218 'name': 'bind-k8s',
219 'imageDetails': {'imagePath': 'example.com/bind:v1'},
220 'ports': [
221 {'containerPort': 53, 'name': 'domain-tcp', 'protocol': 'TCP'},
222 {'containerPort': 53, 'name': 'domain-udp', 'protocol': 'UDP'},
223 ],
224 'config': {'ENABLE_RFC1918_RECURSION': 1, 'magic_number': 123, 'secret_password': 'xyzzy'},
225 'kubernetes': {'readinessProbe': {'exec': {'command': ['/usr/local/bin/dns-check.sh']}}},
226 }
227 ],
228 }
229 self.assertEqual(self.harness.charm.make_pod_spec(), expected)
23077
231 def test_make_pod_spec_with_custom_config_repo(self):78 def test_get_pebble_config_description(self):
232 """Confirm that we generate the expected pod spec from a config that includes a custom repo."""79 self.harness.update_config(CONFIG_VALID)
233 self.harness.update_config(CONFIG_VALID_WITH_CUSTOM_CONFIG_REPO_AND_PROXY)80 self._expected = "Bind layer"
234 expected = {81 self.assertEqual(_pebble_description(self.harness.charm._get_pebble_config()), self._expected)
235 'version': 2,
236 'containers': [
237 {
238 'name': 'bind-k8s',
239 'imageDetails': {'imagePath': 'example.com/bind:v1'},
240 'ports': [
241 {'containerPort': 53, 'name': 'domain-tcp', 'protocol': 'TCP'},
242 {'containerPort': 53, 'name': 'domain-udp', 'protocol': 'UDP'},
243 ],
244 'config': {
245 'CUSTOM_CONFIG_REPO': 'https://git.example.com/example-bind-config.git',
246 'ENABLE_RFC1918_RECURSION': 1,
247 'http_proxy': 'http://webproxy.example.com:3128/',
248 'https_proxy': 'http://webproxy.example.com:3128/',
249 },
250 'kubernetes': {'readinessProbe': {'exec': {'command': ['/usr/local/bin/dns-check.sh']}}},
251 }
252 ],
253 }
254 self.assertEqual(self.harness.charm.make_pod_spec(), expected)
25582
256 def test_configure_pod_as_leader(self):83 def test_get_pebble_config_summary(self):
257 """Confirm that our status is set correctly when we're the leader."""
258 self.harness.enable_hooks()
259 self.harness.set_leader(True)
260 self.harness.update_config(CONFIG_VALID)84 self.harness.update_config(CONFIG_VALID)
261 expected = ActiveStatus('Pod configured')85 self._expected = "Bind layer"
262 self.assertEqual(self.harness.model.unit.status, expected)86 self.assertEqual(_pebble_summary(self.harness.charm._get_pebble_config()), self._expected)
26387
264 def test_configure_pod_as_non_leader(self):88 def test_get_pebble_config_services_bind(self):
265 """Confirm that our status is set correctly when we're not the leader."""
266 self.harness.enable_hooks()
267 self.harness.set_leader(False)
268 self.harness.update_config(CONFIG_VALID)89 self.harness.update_config(CONFIG_VALID)
269 expected = ActiveStatus()90 self._expected = {
270 self.assertEqual(self.harness.model.unit.status, expected)91 "override": "replace",
92 "summary": "Bind service",
93 "command": 'bash -c "/usr/local/bin/docker-wrapper.sh &> /proc/1/fd/1"',
94 "startup": "enabled",
95 "environment": {'ENABLE_RFC1918_RECURSION': 1},
96 }
97 self.assertEqual(_pebble_services_bind(self.harness.charm._get_pebble_config()), self._expected)
98
99 def test_generate_env_config_custom_repo(self):
100 self.harness.update_config({"custom_config_repo": "https://git.example.com/example-bind-config.git"})
101 self._expected['CUSTOM_CONFIG_REPO'] = "https://git.example.com/example-bind-config.git"
102 self.assertEqual(_pebble_services_bind_env(self.harness.charm._get_pebble_config()), self._expected)
103
104 def test_generage_env_config_custom_repo_and_proxy(self):
105 self.harness.update_config(
106 {
107 "custom_config_repo": "https://git.example.com/example-bind-config.git",
108 "https_proxy": "http://webproxy.example.com:3128/",
109 }
110 )
111 self._expected['CUSTOM_CONFIG_REPO'] = "https://git.example.com/example-bind-config.git"
112 self._expected['http_proxy'] = "http://webproxy.example.com:3128/"
113 self._expected['https_proxy'] = "http://webproxy.example.com:3128/"
114 self.assertEqual(_pebble_services_bind_env(self.harness.charm._get_pebble_config()), self._expected)
115
116 def test_generate_env_config_just_proxy(self):
117 self.harness.update_config({})
118 self._expected['ENABLE_RFC1918_RECURSION'] = 1
119 self.assertEqual(_pebble_services_bind_env(self.harness.charm._get_pebble_config()), self._expected)
120
121 def test_on_bind_pebble_ready(self):
122 """Test the _on_bind_pebble_ready function."""
123
124 # No problem
125 mock_event = MagicMock()
126 expected_ret = None
127
128 r = self.harness.charm._on_bind_pebble_ready(mock_event)
129 self.assertEqual(r, expected_ret)
diff --git a/tox.ini b/tox.ini
index 0de5149..fb74730 100644
--- a/tox.ini
+++ b/tox.ini
@@ -10,7 +10,7 @@ setenv =
10[testenv:unit]10[testenv:unit]
11commands =11commands =
12 pytest --ignore mod --ignore {toxinidir}/tests/functional \12 pytest --ignore mod --ignore {toxinidir}/tests/functional \
13 {posargs:-v --cov=src --cov-report=term-missing --cov-branch}13 {posargs:-v --cov=src --cov-report=term-missing --cov-branch --cov-report=html}
14deps = -r{toxinidir}/tests/unit/requirements.txt14deps = -r{toxinidir}/tests/unit/requirements.txt
15 -r{toxinidir}/requirements.txt15 -r{toxinidir}/requirements.txt
16setenv =16setenv =

Subscribers

People subscribed via source and target branches

to all changes: