Merge ~mthaddon/charm-k8s-gunicorn/+git/charm-k8s-gunicorn:image-resource into charm-k8s-gunicorn:master

Proposed by Tom Haddon
Status: Merged
Approved by: Tom Haddon
Approved revision: 5bb4bdfbc895a31f459f9c5ec42cd014aeee73cc
Merged at revision: d030817c6f41df25bf6fb9f412a0104e23395ccf
Proposed branch: ~mthaddon/charm-k8s-gunicorn/+git/charm-k8s-gunicorn:image-resource
Merge into: charm-k8s-gunicorn:master
Diff against target: 305 lines (+41/-83)
7 files modified
README.md (+9/-3)
config.yaml (+2/-19)
metadata.yaml (+6/-0)
requirements.txt (+1/-0)
src/charm.py (+12/-7)
tests/unit/scenario.py (+10/-54)
tests/unit/test_charm.py (+1/-0)
Reviewer Review Type Date Requested Status
Stuart Bishop (community) Approve
🤖 prod-jenkaas-is (community) continuous-integration Approve
gunicorn-charmers Pending
Review via email: mp+399853@code.launchpad.net

Commit message

Switch the charm to use OCI Resource for image

Description of the change

Switch the charm to use OCI Resource for image.

We're in the process of converting this charm to the new sidecar/pebble approach. Making these changes now will lessen the diff to the main repo when we're ready to merge.

To post a comment you must log in.
Revision history for this message
🤖 prod-jenkaas-is (prod-jenkaas-is) wrote :

A CI job is currently in progress. A follow up comment will be added when it completes.

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
🤖 prod-jenkaas-is (prod-jenkaas-is) wrote :

FAILED: Continuous integration, rev:5bb4bdfbc895a31f459f9c5ec42cd014aeee73cc
https://jenkins.canonical.com/is/job/lp-charm-k8s-gunicorn-ci/5/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/is/job/lp-charm-test/46/
    None: https://jenkins.canonical.com/is/job/lp-update-mp/59496/

Click here to trigger a rebuild:
https://jenkins.canonical.com/is/job/lp-charm-k8s-gunicorn-ci/5//rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
🤖 prod-jenkaas-is (prod-jenkaas-is) wrote :

PASSED: Continuous integration, rev:5bb4bdfbc895a31f459f9c5ec42cd014aeee73cc
https://jenkins.canonical.com/is/job/lp-charm-k8s-gunicorn-ci/6/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/is/job/lp-charm-test/47/
    None: https://jenkins.canonical.com/is/job/lp-update-mp/59534/

Click here to trigger a rebuild:
https://jenkins.canonical.com/is/job/lp-charm-k8s-gunicorn-ci/6//rebuild

review: Approve (continuous-integration)
Revision history for this message
Stuart Bishop (stub) wrote :

This all looks fine. I expect this will need a further revision when Operator Framework supports OCI image resources properly, so this change may be premature in that way. But overall it seems a good idea as it means the deployment documentation will remain consistent for Juju 2.9.

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

Change successfully merged at revision d030817c6f41df25bf6fb9f412a0104e23395ccf

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/README.md b/README.md
index fbbd51a..710b241 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,12 @@ A charm that allows you to deploy your gunicorn application in kubernetes.
66
7## Usage7## Usage
88
9By default, the charm will deploy a simple docker image that contains a
10gunicorn app that displays a short message and its environment variables. The
11image is built using an OCI Recipe on Launchpad and published to dockerhub
12[here](https://hub.docker.com/r/gunicorncharmers/gunicorn-app).
9```13```
10juju deploy cs:~gunicorn-charmers/gunicorn my-awesome-app --config image_path=localhost:32000/myapp --config external_hostname=my-awesome-app.com14juju deploy cs:~gunicorn-charmers/gunicorn my-awesome-app
11```15```
1216
13### Scale Out Usage17### Scale Out Usage
@@ -21,7 +25,9 @@ juju add-unit my-awesome-app
21### Using your own image25### Using your own image
2226
23You can, of course, supply our own OCI image. gunicorn is expected to listen on27You can, of course, supply our own OCI image. gunicorn is expected to listen on
24port 80.28port 80. To do so, specify `--resource gunicorn-image='image-location'` at
29deploy time, or use `juju attach-resource` if you want to switch images after
30initial deployment.
2531
26### Using gunicorn-base to build an image32### Using gunicorn-base to build an image
2733
@@ -45,7 +51,7 @@ added to the environment of your pods.
45The context used to render the Jinja2 template is constructed from relation51The context used to render the Jinja2 template is constructed from relation
46data. For example, if you're relating with influxdb, you could do the following :52data. For example, if you're relating with influxdb, you could do the following :
47```53```
48juju deploy cs:~gunicorn-charmers/gunicorn my-awesome-app --config image_path=localhost:32000/myapp --config external_hostname=my-awesome-app.com54juju deploy cs:~gunicorn-charmers/gunicorn my-awesome-app
49juju config my-awesome-app environment="INFLUXDB_HOST: {{influxdb.hostname}}"55juju config my-awesome-app environment="INFLUXDB_HOST: {{influxdb.hostname}}"
50```56```
5157
diff --git a/config.yaml b/config.yaml
index 0754ab4..7276c75 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,21 +1,4 @@
1options:1options:
2 image_path:
3 type: string
4 description: >
5 The location of the image to use, e.g. "registry.example.com/my_gunicorn_app:v1".
6
7 This setting is required.
8 default: ''
9 image_username:
10 type: string
11 description: >
12 The username for accessing the registry specified in image_path.
13 default: ''
14 image_password:
15 type: string
16 description: >
17 The password associated with image_username for accessing the registry specified in image_path.
18 default: ''
19 environment:2 environment:
20 type: string3 type: string
21 description: >4 description: >
@@ -31,5 +14,5 @@ options:
31 external_hostname:14 external_hostname:
32 type: string15 type: string
33 description: >16 description: >
34 External hostname this gunicorn app should respond to.17 External hostname this gunicorn app should respond to (required).
35 default: ''18 default: 'foo.internal'
diff --git a/metadata.yaml b/metadata.yaml
index 4e6d497..b1a1aa7 100644
--- a/metadata.yaml
+++ b/metadata.yaml
@@ -7,6 +7,12 @@ summary: |
7 Gunicorn charm7 Gunicorn charm
8series: [kubernetes]8series: [kubernetes]
9min-juju-version: 2.8.0 # charm storage in state9min-juju-version: 2.8.0 # charm storage in state
10resources:
11 gunicorn-image:
12 type: oci-image
13 description: docker image for Gunicorn
14 auto-fetch: true
15 upstream-source: 'gunicorncharmers/gunicorn-app:20.0.4-20.04_edge'
10requires:16requires:
11 pg:17 pg:
12 interface: pgsql18 interface: pgsql
diff --git a/requirements.txt b/requirements.txt
index fd6adcd..6b14e66 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,3 @@
1ops1ops
2ops-lib-pgsql2ops-lib-pgsql
3https://github.com/juju-solutions/resource-oci-image/archive/master.zip
diff --git a/src/charm.py b/src/charm.py
index 3772ef0..c25df78 100755
--- a/src/charm.py
+++ b/src/charm.py
@@ -7,6 +7,7 @@ import logging
7import yaml7import yaml
88
9import ops9import ops
10from oci_image import OCIImageResource, OCIImageResourceError
10from ops.framework import StoredState11from ops.framework import StoredState
11from ops.charm import CharmBase12from ops.charm import CharmBase
12from ops.main import main13from ops.main import main
@@ -20,7 +21,7 @@ import pgsql
2021
21logger = logging.getLogger(__name__)22logger = logging.getLogger(__name__)
2223
23REQUIRED_JUJU_CONFIG = ['image_path', 'external_hostname']24REQUIRED_JUJU_CONFIG = ['external_hostname']
24JUJU_CONFIG_YAML_DICT_ITEMS = ['environment']25JUJU_CONFIG_YAML_DICT_ITEMS = ['environment']
2526
2627
@@ -42,6 +43,8 @@ class GunicornK8sCharm(CharmBase):
42 def __init__(self, *args):43 def __init__(self, *args):
43 super().__init__(*args)44 super().__init__(*args)
4445
46 self.image = OCIImageResource(self, 'gunicorn-image')
47
45 self.framework.observe(self.on.start, self._configure_pod)48 self.framework.observe(self.on.start, self._configure_pod)
46 self.framework.observe(self.on.config_changed, self._configure_pod)49 self.framework.observe(self.on.config_changed, self._configure_pod)
47 self.framework.observe(self.on.leader_elected, self._configure_pod)50 self.framework.observe(self.on.leader_elected, self._configure_pod)
@@ -259,12 +262,14 @@ class GunicornK8sCharm(CharmBase):
259 :returns: A pod spec262 :returns: A pod spec
260 """263 """
261264
262 config = self.model.config265 try:
263 image_details = {266 image_details = self.image.fetch()
264 'imagePath': config['image_path'],267 logging.info("using imageDetails: {}")
265 }268 except OCIImageResourceError:
266 if config.get('image_username', None):269 logging.exception('An error occurred while fetching the image info')
267 image_details.update({'username': config['image_username'], 'password': config['image_password']})270 self.unit.status = BlockedStatus('Error fetching image information')
271 return {}
272
268 pod_env = self._make_pod_env()273 pod_env = self._make_pod_env()
269274
270 return {275 return {
diff --git a/tests/unit/scenario.py b/tests/unit/scenario.py
index f71494f..3402589 100644
--- a/tests/unit/scenario.py
+++ b/tests/unit/scenario.py
@@ -35,34 +35,23 @@ TEST_PG_CONNSTR = 'dbname=gunicorn host=1.2.3.4 password=pwd port=5432 user=usr'
35TEST_JUJU_CONFIG = {35TEST_JUJU_CONFIG = {
36 'defaults': {36 'defaults': {
37 'config': {},37 'config': {},
38 'logger': [38 'logger': [],
39 "ERROR:charm:Required Juju config item not set : image_path",39 'expected': False,
40 'ERROR:charm:Required Juju config item not set : external_hostname',
41 ],
42 'expected': 'Required Juju config item(s) not set : external_hostname, image_path',
43 },
44 'missing_image_path': {
45 'config': {
46 'external_hostname': 'example.com',
47 },
48 'logger': ["ERROR:charm:Required Juju config item not set : image_path"],
49 'expected': 'Required Juju config item(s) not set : image_path',
50 },40 },
51 'missing_external_hostname': {41 'missing_external_hostname': {
52 'config': {42 'config': {
53 'image_path': 'my_gunicorn_app:devel',43 'external_hostname': '',
54 },44 },
55 'logger': ["ERROR:charm:Required Juju config item not set : external_hostname"],45 'logger': ["ERROR:charm:Required Juju config item not set : external_hostname"],
56 'expected': 'Required Juju config item(s) not set : external_hostname',46 'expected': 'Required Juju config item(s) not set : external_hostname',
57 },47 },
58 'good_config_no_env': {48 'good_config_no_env': {
59 'config': {'image_path': 'my_gunicorn_app:devel', 'external_hostname': 'example.com'},49 'config': {'external_hostname': 'example.com'},
60 'logger': [],50 'logger': [],
61 'expected': False,51 'expected': False,
62 },52 },
63 'good_config_with_env': {53 'good_config_with_env': {
64 'config': {54 'config': {
65 'image_path': 'my_gunicorn_app:devel',
66 'environment': 'MYENV: foo',55 'environment': 'MYENV: foo',
67 'external_hostname': 'example.com',56 'external_hostname': 'example.com',
68 },57 },
@@ -72,16 +61,8 @@ TEST_JUJU_CONFIG = {
72}61}
7362
74TEST_CONFIGURE_POD = {63TEST_CONFIGURE_POD = {
75 'bad_config': {
76 'config': {
77 'external_hostname': 'example.com',
78 },
79 '_leader_get': "5:\n database: gunicorn\n extensions: ''\n roles: ''",
80 'expected': 'Required Juju config item(s) not set : image_path',
81 },
82 'good_config_no_env': {64 'good_config_no_env': {
83 'config': {65 'config': {
84 'image_path': 'my_gunicorn_app:devel',
85 'external_hostname': 'example.com',66 'external_hostname': 'example.com',
86 },67 },
87 '_leader_get': "5:\n database: gunicorn\n extensions: ''\n roles: ''",68 '_leader_get': "5:\n database: gunicorn\n extensions: ''\n roles: ''",
@@ -89,7 +70,6 @@ TEST_CONFIGURE_POD = {
89 },70 },
90 'good_config_with_env': {71 'good_config_with_env': {
91 'config': {72 'config': {
92 'image_path': 'my_gunicorn_app:devel',
93 'external_hostname': 'example.com',73 'external_hostname': 'example.com',
94 'environment': 'MYENV: foo',74 'environment': 'MYENV: foo',
95 },75 },
@@ -101,7 +81,6 @@ TEST_CONFIGURE_POD = {
101TEST_MAKE_POD_SPEC = {81TEST_MAKE_POD_SPEC = {
102 'basic_no_env': {82 'basic_no_env': {
103 'config': {83 'config': {
104 'image_path': 'my_gunicorn_app:devel',
105 'external_hostname': 'example.com',84 'external_hostname': 'example.com',
106 },85 },
107 'pod_spec': {86 'pod_spec': {
@@ -110,7 +89,9 @@ TEST_MAKE_POD_SPEC = {
110 {89 {
111 'name': 'gunicorn',90 'name': 'gunicorn',
112 'imageDetails': {91 'imageDetails': {
113 'imagePath': 'my_gunicorn_app:devel',92 'imagePath': 'registrypath',
93 'password': 'password',
94 'username': 'username',
114 },95 },
115 'imagePullPolicy': 'Always',96 'imagePullPolicy': 'Always',
116 'ports': [{'containerPort': 80, 'protocol': 'TCP'}],97 'ports': [{'containerPort': 80, 'protocol': 'TCP'}],
@@ -122,7 +103,6 @@ TEST_MAKE_POD_SPEC = {
122 },103 },
123 'basic_with_env': {104 'basic_with_env': {
124 'config': {105 'config': {
125 'image_path': 'my_gunicorn_app:devel',
126 'external_hostname': 'example.com',106 'external_hostname': 'example.com',
127 'environment': 'MYENV: foo',107 'environment': 'MYENV: foo',
128 },108 },
@@ -132,7 +112,9 @@ TEST_MAKE_POD_SPEC = {
132 {112 {
133 'name': 'gunicorn',113 'name': 'gunicorn',
134 'imageDetails': {114 'imageDetails': {
135 'imagePath': 'my_gunicorn_app:devel',115 'imagePath': 'registrypath',
116 'password': 'password',
117 'username': 'username',
136 },118 },
137 'imagePullPolicy': 'Always',119 'imagePullPolicy': 'Always',
138 'ports': [{'containerPort': 80, 'protocol': 'TCP'}],120 'ports': [{'containerPort': 80, 'protocol': 'TCP'}],
@@ -142,38 +124,12 @@ TEST_MAKE_POD_SPEC = {
142 ],124 ],
143 },125 },
144 },126 },
145 'private_registry': {
146 'config': {
147 'image_path': 'my_gunicorn_app:devel',
148 'image_username': 'foo',
149 'image_password': 'bar',
150 'external_hostname': 'example.com',
151 },
152 'pod_spec': {
153 'version': 3, # otherwise resources are ignored
154 'containers': [
155 {
156 'name': 'gunicorn',
157 'imageDetails': {
158 'imagePath': 'my_gunicorn_app:devel',
159 'username': 'foo',
160 'password': 'bar',
161 },
162 'imagePullPolicy': 'Always',
163 'ports': [{'containerPort': 80, 'protocol': 'TCP'}],
164 'envConfig': {},
165 'kubernetes': {'readinessProbe': {'httpGet': {'path': '/', 'port': 80}}},
166 }
167 ],
168 },
169 },
170}127}
171128
172129
173TEST_MAKE_K8S_INGRESS = {130TEST_MAKE_K8S_INGRESS = {
174 'basic': {131 'basic': {
175 'config': {132 'config': {
176 'image_path': 'my_gunicorn_app:devel',
177 'external_hostname': 'example.com',133 'external_hostname': 'example.com',
178 },134 },
179 'expected': [135 'expected': [
diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py
index 746dbd3..b5e1f87 100755
--- a/tests/unit/test_charm.py
+++ b/tests/unit/test_charm.py
@@ -34,6 +34,7 @@ class TestGunicornK8sCharm(unittest.TestCase):
34 """Setup the harness object."""34 """Setup the harness object."""
35 self.harness = testing.Harness(GunicornK8sCharm)35 self.harness = testing.Harness(GunicornK8sCharm)
36 self.harness.begin()36 self.harness.begin()
37 self.harness.add_oci_resource('gunicorn-image')
3738
38 def tearDown(self):39 def tearDown(self):
39 """Cleanup the harness."""40 """Cleanup the harness."""

Subscribers

People subscribed via source and target branches

to all changes: