Merge ~mthaddon/charm-k8s-discourse/+git/charm-k8s-discourse:tox into charm-k8s-discourse:master

Proposed by Tom Haddon
Status: Merged
Approved by: Jay Kuri
Approved revision: debbfc5fe5bfc42e3d9519f5dafc0ec6eaade3b5
Merged at revision: 36e8e36547e6b6e2dca17c0cadab09c04b975ef2
Proposed branch: ~mthaddon/charm-k8s-discourse/+git/charm-k8s-discourse:tox
Merge into: charm-k8s-discourse:master
Diff against target: 269 lines (+128/-43)
9 files modified
.gitignore (+6/-0)
.gitmodules (+1/-1)
Makefile (+23/-0)
pyproject.toml (+3/-0)
requirements.txt (+1/-0)
src/charm.py (+17/-42)
tests/unit/requirements.txt (+4/-0)
tests/unit/test_charm.py (+25/-0)
tox.ini (+48/-0)
Reviewer Review Type Date Requested Status
Jay Kuri (community) Approve
Stuart Bishop (community) Approve
Review via email: mp+387758@code.launchpad.net

Commit message

Add tox and black, along with initial unit test

Description of the change

Add tox and black, along with initial unit test

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

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

Revision history for this message
Stuart Bishop (stub) wrote :

Yup. Inline comments, same as last MP.

review: Approve
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change cannot be self approved, setting status to needs review.

Revision history for this message
Jay Kuri (jk0ne) wrote :

LGTM.

review: Approve
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision 36e8e36547e6b6e2dca17c0cadab09c04b975ef2

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.gitignore b/.gitignore
2new file mode 100644
3index 0000000..490cc43
4--- /dev/null
5+++ b/.gitignore
6@@ -0,0 +1,6 @@
7+*~
8+*.charm
9+.tox
10+.coverage
11+__pycache__
12+build
13diff --git a/.gitmodules b/.gitmodules
14index 75335ec..d3eb83f 100644
15--- a/.gitmodules
16+++ b/.gitmodules
17@@ -3,4 +3,4 @@
18 url = https://github.com/canonical/operator
19 [submodule "mod/resource-oci-image"]
20 path = mod/resource-oci-image
21- url = git@github.com:johnsca/resource-oci-image.git
22+ url = https://github.com/johnsca/resource-oci-image
23diff --git a/Makefile b/Makefile
24new file mode 100644
25index 0000000..78822d3
26--- /dev/null
27+++ b/Makefile
28@@ -0,0 +1,23 @@
29+blacken:
30+ @echo "Normalising python layout with black."
31+ @tox -e black
32+
33+lint: blacken
34+ @echo "Running flake8"
35+ @tox -e lint
36+
37+# We actually use the build directory created by charmcraft,
38+# but the .charm file makes a much more convenient sentinel.
39+unittest: discourse-charm.charm
40+ @tox -e unit
41+
42+test: lint unittest
43+
44+clean:
45+ @echo "Cleaning files"
46+ @git clean -fXd
47+
48+discourse-charm.charm: src/*.py requirements.txt
49+ charmcraft build
50+
51+.PHONY: lint test unittest clean
52diff --git a/pyproject.toml b/pyproject.toml
53new file mode 100644
54index 0000000..d2f23b9
55--- /dev/null
56+++ b/pyproject.toml
57@@ -0,0 +1,3 @@
58+[tool.black]
59+skip-string-normalization = true
60+line-length = 120
61diff --git a/requirements.txt b/requirements.txt
62new file mode 100644
63index 0000000..2d81d3b
64--- /dev/null
65+++ b/requirements.txt
66@@ -0,0 +1 @@
67+ops
68diff --git a/src/charm.py b/src/charm.py
69index 29db945..75f4d26 100755
70--- a/src/charm.py
71+++ b/src/charm.py
72@@ -1,7 +1,8 @@
73 #!/usr/bin/env python3
74
75 import sys
76-sys.path.append('lib')
77+
78+sys.path.append('lib') # noqa: E402
79
80 from ops.charm import CharmBase
81 from ops.framework import StoredState
82@@ -10,8 +11,6 @@ from ops.model import (
83 ActiveStatus,
84 BlockedStatus,
85 MaintenanceStatus,
86- ModelError,
87- WaitingStatus,
88 )
89
90
91@@ -42,20 +41,10 @@ def create_ingress_config(app_name, config):
92 "rules": [
93 {
94 "host": config['external_hostname'],
95- "http": {
96- "paths": [
97- {
98- "path": "/",
99- "backend": {
100- "serviceName": app_name,
101- "servicePort": 3000
102- }
103- }
104- ]
105- }
106+ "http": {"paths": [{"path": "/", "backend": {"serviceName": app_name, "servicePort": 3000}}]},
107 }
108 ]
109- }
110+ },
111 }
112 return ingressResource
113
114@@ -63,29 +52,17 @@ def create_ingress_config(app_name, config):
115 def get_pod_spec(app_name, config):
116 pod_spec = {
117 "version": 3,
118- "containers": [{
119- "name": app_name,
120- "imageDetails": {"imagePath": config['discourse_image']},
121- "imagePullPolicy": "IfNotPresent",
122- "ports": [{
123- "containerPort": 3000,
124- "protocol": "TCP",
125- }],
126- "envConfig": create_discourse_pod_config(config),
127- "kubernetes": {
128- "readinessProbe": {
129- "httpGet": {
130- "path": "/srv/status",
131- "port": 3000,
132- }
133- }
134- },
135- }],
136- "kubernetesResources": {
137- "ingressResources": [
138- create_ingress_config(app_name, config)
139- ]
140- }
141+ "containers": [
142+ {
143+ "name": app_name,
144+ "imageDetails": {"imagePath": config['discourse_image']},
145+ "imagePullPolicy": "IfNotPresent",
146+ "ports": [{"containerPort": 3000, "protocol": "TCP",}],
147+ "envConfig": create_discourse_pod_config(config),
148+ "kubernetes": {"readinessProbe": {"httpGet": {"path": "/srv/status", "port": 3000}}},
149+ }
150+ ],
151+ "kubernetesResources": {"ingressResources": [create_ingress_config(app_name, config)]},
152 }
153 # This handles when we are trying to get an image from a private
154 # registry.
155@@ -109,8 +86,7 @@ def check_for_config_problems(config):
156 def check_for_missing_config_fields(config):
157 missing_fields = []
158
159- needed_fields = ['db_user', 'db_password', 'db_host', 'db_name', 'smtp_address',
160- 'redis_host']
161+ needed_fields = ['db_user', 'db_password', 'db_host', 'db_name', 'smtp_address', 'redis_host']
162 for key in needed_fields:
163 if len(config[key]) == 0:
164 missing_fields.append(key)
165@@ -163,8 +139,7 @@ class DiscourseCharm(CharmBase):
166 if not self.state.is_started:
167 return event.defer()
168
169- event.client.serve(hosts=[event.client.ingress_address],
170- port=self.model.config['http_port'])
171+ event.client.serve(hosts=[event.client.ingress_address], port=self.model.config['http_port'])
172
173
174 if __name__ == '__main__':
175diff --git a/tests/unit/requirements.txt b/tests/unit/requirements.txt
176new file mode 100644
177index 0000000..65431fc
178--- /dev/null
179+++ b/tests/unit/requirements.txt
180@@ -0,0 +1,4 @@
181+mock
182+pytest
183+pytest-cov
184+pyyaml
185diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py
186new file mode 100644
187index 0000000..56bd348
188--- /dev/null
189+++ b/tests/unit/test_charm.py
190@@ -0,0 +1,25 @@
191+import unittest
192+import sys
193+
194+sys.path.append('src') # noqa: E402
195+
196+from charm import create_ingress_config
197+
198+
199+class TestDiscourseCharm(unittest.TestCase):
200+ def test_create_ingress_config(self):
201+ config = {
202+ "external_hostname": "testhost",
203+ }
204+ expected = {
205+ "name": "test-app-ingress",
206+ "spec": {
207+ "rules": [
208+ {
209+ "host": "testhost",
210+ "http": {"paths": [{"path": "/", "backend": {"serviceName": "test-app", "servicePort": 3000}}]},
211+ }
212+ ]
213+ },
214+ }
215+ self.assertEqual(create_ingress_config("test-app", config), expected)
216diff --git a/tox.ini b/tox.ini
217new file mode 100644
218index 0000000..0997650
219--- /dev/null
220+++ b/tox.ini
221@@ -0,0 +1,48 @@
222+[tox]
223+skipsdist=True
224+envlist = unit, functional
225+
226+[testenv]
227+basepython = python3
228+setenv =
229+ PYTHONPATH = {toxinidir}/build/lib:{toxinidir}/build/venv
230+
231+[testenv:unit]
232+commands =
233+ pytest --ignore mod --ignore {toxinidir}/tests/functional \
234+ {posargs:-v --cov=src --cov-report=term-missing --cov-branch}
235+deps = -r{toxinidir}/tests/unit/requirements.txt
236+ -r{toxinidir}/requirements.txt
237+setenv =
238+ PYTHONPATH={toxinidir}/src:{toxinidir}/build/lib:{toxinidir}/build/venv
239+ TZ=UTC
240+
241+[testenv:functional]
242+passenv =
243+ HOME
244+ JUJU_REPOSITORY
245+ PATH
246+commands =
247+ pytest -v --ignore mod --ignore {toxinidir}/tests/unit {posargs}
248+deps = -r{toxinidir}/tests/functional/requirements.txt
249+ -r{toxinidir}/requirements.txt
250+
251+[testenv:black]
252+commands = black src/ tests/
253+deps = black
254+
255+[testenv:lint]
256+commands = flake8 src/ tests/
257+# Pin flake8 to 3.7.9 to match focal
258+deps =
259+ flake8==3.7.9
260+
261+[flake8]
262+exclude =
263+ .git,
264+ __pycache__,
265+ .tox,
266+# Ignore E231 because using black creates errors with this
267+ignore = E231
268+max-line-length = 120
269+max-complexity = 10

Subscribers

People subscribed via source and target branches