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
diff --git a/.gitignore b/.gitignore
0new file mode 1006440new file mode 100644
index 0000000..490cc43
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
1*~
2*.charm
3.tox
4.coverage
5__pycache__
6build
diff --git a/.gitmodules b/.gitmodules
index 75335ec..d3eb83f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -3,4 +3,4 @@
3 url = https://github.com/canonical/operator3 url = https://github.com/canonical/operator
4[submodule "mod/resource-oci-image"]4[submodule "mod/resource-oci-image"]
5 path = mod/resource-oci-image5 path = mod/resource-oci-image
6 url = git@github.com:johnsca/resource-oci-image.git6 url = https://github.com/johnsca/resource-oci-image
diff --git a/Makefile b/Makefile
7new file mode 1006447new file mode 100644
index 0000000..78822d3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,23 @@
1blacken:
2 @echo "Normalising python layout with black."
3 @tox -e black
4
5lint: blacken
6 @echo "Running flake8"
7 @tox -e lint
8
9# We actually use the build directory created by charmcraft,
10# but the .charm file makes a much more convenient sentinel.
11unittest: discourse-charm.charm
12 @tox -e unit
13
14test: lint unittest
15
16clean:
17 @echo "Cleaning files"
18 @git clean -fXd
19
20discourse-charm.charm: src/*.py requirements.txt
21 charmcraft build
22
23.PHONY: lint test unittest clean
diff --git a/pyproject.toml b/pyproject.toml
0new file mode 10064424new file mode 100644
index 0000000..d2f23b9
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,3 @@
1[tool.black]
2skip-string-normalization = true
3line-length = 120
diff --git a/requirements.txt b/requirements.txt
0new file mode 1006444new file mode 100644
index 0000000..2d81d3b
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
1ops
diff --git a/src/charm.py b/src/charm.py
index 29db945..75f4d26 100755
--- a/src/charm.py
+++ b/src/charm.py
@@ -1,7 +1,8 @@
1#!/usr/bin/env python31#!/usr/bin/env python3
22
3import sys3import sys
4sys.path.append('lib')4
5sys.path.append('lib') # noqa: E402
56
6from ops.charm import CharmBase7from ops.charm import CharmBase
7from ops.framework import StoredState8from ops.framework import StoredState
@@ -10,8 +11,6 @@ from ops.model import (
10 ActiveStatus,11 ActiveStatus,
11 BlockedStatus,12 BlockedStatus,
12 MaintenanceStatus,13 MaintenanceStatus,
13 ModelError,
14 WaitingStatus,
15)14)
1615
1716
@@ -42,20 +41,10 @@ def create_ingress_config(app_name, config):
42 "rules": [41 "rules": [
43 {42 {
44 "host": config['external_hostname'],43 "host": config['external_hostname'],
45 "http": {44 "http": {"paths": [{"path": "/", "backend": {"serviceName": app_name, "servicePort": 3000}}]},
46 "paths": [
47 {
48 "path": "/",
49 "backend": {
50 "serviceName": app_name,
51 "servicePort": 3000
52 }
53 }
54 ]
55 }
56 }45 }
57 ]46 ]
58 }47 },
59 }48 }
60 return ingressResource49 return ingressResource
6150
@@ -63,29 +52,17 @@ def create_ingress_config(app_name, config):
63def get_pod_spec(app_name, config):52def get_pod_spec(app_name, config):
64 pod_spec = {53 pod_spec = {
65 "version": 3,54 "version": 3,
66 "containers": [{55 "containers": [
67 "name": app_name, 56 {
68 "imageDetails": {"imagePath": config['discourse_image']},57 "name": app_name,
69 "imagePullPolicy": "IfNotPresent",58 "imageDetails": {"imagePath": config['discourse_image']},
70 "ports": [{59 "imagePullPolicy": "IfNotPresent",
71 "containerPort": 3000,60 "ports": [{"containerPort": 3000, "protocol": "TCP",}],
72 "protocol": "TCP",61 "envConfig": create_discourse_pod_config(config),
73 }],62 "kubernetes": {"readinessProbe": {"httpGet": {"path": "/srv/status", "port": 3000}}},
74 "envConfig": create_discourse_pod_config(config),63 }
75 "kubernetes": {64 ],
76 "readinessProbe": {65 "kubernetesResources": {"ingressResources": [create_ingress_config(app_name, config)]},
77 "httpGet": {
78 "path": "/srv/status",
79 "port": 3000,
80 }
81 }
82 },
83 }],
84 "kubernetesResources": {
85 "ingressResources": [
86 create_ingress_config(app_name, config)
87 ]
88 }
89 }66 }
90 # This handles when we are trying to get an image from a private67 # This handles when we are trying to get an image from a private
91 # registry.68 # registry.
@@ -109,8 +86,7 @@ def check_for_config_problems(config):
109def check_for_missing_config_fields(config):86def check_for_missing_config_fields(config):
110 missing_fields = []87 missing_fields = []
11188
112 needed_fields = ['db_user', 'db_password', 'db_host', 'db_name', 'smtp_address',89 needed_fields = ['db_user', 'db_password', 'db_host', 'db_name', 'smtp_address', 'redis_host']
113 'redis_host']
114 for key in needed_fields:90 for key in needed_fields:
115 if len(config[key]) == 0:91 if len(config[key]) == 0:
116 missing_fields.append(key)92 missing_fields.append(key)
@@ -163,8 +139,7 @@ class DiscourseCharm(CharmBase):
163 if not self.state.is_started:139 if not self.state.is_started:
164 return event.defer()140 return event.defer()
165141
166 event.client.serve(hosts=[event.client.ingress_address],142 event.client.serve(hosts=[event.client.ingress_address], port=self.model.config['http_port'])
167 port=self.model.config['http_port'])
168143
169144
170if __name__ == '__main__':145if __name__ == '__main__':
diff --git a/tests/unit/requirements.txt b/tests/unit/requirements.txt
171new file mode 100644146new file mode 100644
index 0000000..65431fc
--- /dev/null
+++ b/tests/unit/requirements.txt
@@ -0,0 +1,4 @@
1mock
2pytest
3pytest-cov
4pyyaml
diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py
0new file mode 1006445new file mode 100644
index 0000000..56bd348
--- /dev/null
+++ b/tests/unit/test_charm.py
@@ -0,0 +1,25 @@
1import unittest
2import sys
3
4sys.path.append('src') # noqa: E402
5
6from charm import create_ingress_config
7
8
9class TestDiscourseCharm(unittest.TestCase):
10 def test_create_ingress_config(self):
11 config = {
12 "external_hostname": "testhost",
13 }
14 expected = {
15 "name": "test-app-ingress",
16 "spec": {
17 "rules": [
18 {
19 "host": "testhost",
20 "http": {"paths": [{"path": "/", "backend": {"serviceName": "test-app", "servicePort": 3000}}]},
21 }
22 ]
23 },
24 }
25 self.assertEqual(create_ingress_config("test-app", config), expected)
diff --git a/tox.ini b/tox.ini
0new file mode 10064426new file mode 100644
index 0000000..0997650
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,48 @@
1[tox]
2skipsdist=True
3envlist = unit, functional
4
5[testenv]
6basepython = python3
7setenv =
8 PYTHONPATH = {toxinidir}/build/lib:{toxinidir}/build/venv
9
10[testenv:unit]
11commands =
12 pytest --ignore mod --ignore {toxinidir}/tests/functional \
13 {posargs:-v --cov=src --cov-report=term-missing --cov-branch}
14deps = -r{toxinidir}/tests/unit/requirements.txt
15 -r{toxinidir}/requirements.txt
16setenv =
17 PYTHONPATH={toxinidir}/src:{toxinidir}/build/lib:{toxinidir}/build/venv
18 TZ=UTC
19
20[testenv:functional]
21passenv =
22 HOME
23 JUJU_REPOSITORY
24 PATH
25commands =
26 pytest -v --ignore mod --ignore {toxinidir}/tests/unit {posargs}
27deps = -r{toxinidir}/tests/functional/requirements.txt
28 -r{toxinidir}/requirements.txt
29
30[testenv:black]
31commands = black src/ tests/
32deps = black
33
34[testenv:lint]
35commands = flake8 src/ tests/
36# Pin flake8 to 3.7.9 to match focal
37deps =
38 flake8==3.7.9
39
40[flake8]
41exclude =
42 .git,
43 __pycache__,
44 .tox,
45# Ignore E231 because using black creates errors with this
46ignore = E231
47max-line-length = 120
48max-complexity = 10

Subscribers

People subscribed via source and target branches