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
1diff --git a/README.md b/README.md
2index fbbd51a..710b241 100644
3--- a/README.md
4+++ b/README.md
5@@ -6,8 +6,12 @@ A charm that allows you to deploy your gunicorn application in kubernetes.
6
7 ## Usage
8
9+By default, the charm will deploy a simple docker image that contains a
10+gunicorn app that displays a short message and its environment variables. The
11+image is built using an OCI Recipe on Launchpad and published to dockerhub
12+[here](https://hub.docker.com/r/gunicorncharmers/gunicorn-app).
13 ```
14-juju deploy cs:~gunicorn-charmers/gunicorn my-awesome-app --config image_path=localhost:32000/myapp --config external_hostname=my-awesome-app.com
15+juju deploy cs:~gunicorn-charmers/gunicorn my-awesome-app
16 ```
17
18 ### Scale Out Usage
19@@ -21,7 +25,9 @@ juju add-unit my-awesome-app
20 ### Using your own image
21
22 You can, of course, supply our own OCI image. gunicorn is expected to listen on
23-port 80.
24+port 80. To do so, specify `--resource gunicorn-image='image-location'` at
25+deploy time, or use `juju attach-resource` if you want to switch images after
26+initial deployment.
27
28 ### Using gunicorn-base to build an image
29
30@@ -45,7 +51,7 @@ added to the environment of your pods.
31 The context used to render the Jinja2 template is constructed from relation
32 data. For example, if you're relating with influxdb, you could do the following :
33 ```
34-juju deploy cs:~gunicorn-charmers/gunicorn my-awesome-app --config image_path=localhost:32000/myapp --config external_hostname=my-awesome-app.com
35+juju deploy cs:~gunicorn-charmers/gunicorn my-awesome-app
36 juju config my-awesome-app environment="INFLUXDB_HOST: {{influxdb.hostname}}"
37 ```
38
39diff --git a/config.yaml b/config.yaml
40index 0754ab4..7276c75 100644
41--- a/config.yaml
42+++ b/config.yaml
43@@ -1,21 +1,4 @@
44 options:
45- image_path:
46- type: string
47- description: >
48- The location of the image to use, e.g. "registry.example.com/my_gunicorn_app:v1".
49-
50- This setting is required.
51- default: ''
52- image_username:
53- type: string
54- description: >
55- The username for accessing the registry specified in image_path.
56- default: ''
57- image_password:
58- type: string
59- description: >
60- The password associated with image_username for accessing the registry specified in image_path.
61- default: ''
62 environment:
63 type: string
64 description: >
65@@ -31,5 +14,5 @@ options:
66 external_hostname:
67 type: string
68 description: >
69- External hostname this gunicorn app should respond to.
70- default: ''
71+ External hostname this gunicorn app should respond to (required).
72+ default: 'foo.internal'
73diff --git a/metadata.yaml b/metadata.yaml
74index 4e6d497..b1a1aa7 100644
75--- a/metadata.yaml
76+++ b/metadata.yaml
77@@ -7,6 +7,12 @@ summary: |
78 Gunicorn charm
79 series: [kubernetes]
80 min-juju-version: 2.8.0 # charm storage in state
81+resources:
82+ gunicorn-image:
83+ type: oci-image
84+ description: docker image for Gunicorn
85+ auto-fetch: true
86+ upstream-source: 'gunicorncharmers/gunicorn-app:20.0.4-20.04_edge'
87 requires:
88 pg:
89 interface: pgsql
90diff --git a/requirements.txt b/requirements.txt
91index fd6adcd..6b14e66 100644
92--- a/requirements.txt
93+++ b/requirements.txt
94@@ -1,2 +1,3 @@
95 ops
96 ops-lib-pgsql
97+https://github.com/juju-solutions/resource-oci-image/archive/master.zip
98diff --git a/src/charm.py b/src/charm.py
99index 3772ef0..c25df78 100755
100--- a/src/charm.py
101+++ b/src/charm.py
102@@ -7,6 +7,7 @@ import logging
103 import yaml
104
105 import ops
106+from oci_image import OCIImageResource, OCIImageResourceError
107 from ops.framework import StoredState
108 from ops.charm import CharmBase
109 from ops.main import main
110@@ -20,7 +21,7 @@ import pgsql
111
112 logger = logging.getLogger(__name__)
113
114-REQUIRED_JUJU_CONFIG = ['image_path', 'external_hostname']
115+REQUIRED_JUJU_CONFIG = ['external_hostname']
116 JUJU_CONFIG_YAML_DICT_ITEMS = ['environment']
117
118
119@@ -42,6 +43,8 @@ class GunicornK8sCharm(CharmBase):
120 def __init__(self, *args):
121 super().__init__(*args)
122
123+ self.image = OCIImageResource(self, 'gunicorn-image')
124+
125 self.framework.observe(self.on.start, self._configure_pod)
126 self.framework.observe(self.on.config_changed, self._configure_pod)
127 self.framework.observe(self.on.leader_elected, self._configure_pod)
128@@ -259,12 +262,14 @@ class GunicornK8sCharm(CharmBase):
129 :returns: A pod spec
130 """
131
132- config = self.model.config
133- image_details = {
134- 'imagePath': config['image_path'],
135- }
136- if config.get('image_username', None):
137- image_details.update({'username': config['image_username'], 'password': config['image_password']})
138+ try:
139+ image_details = self.image.fetch()
140+ logging.info("using imageDetails: {}")
141+ except OCIImageResourceError:
142+ logging.exception('An error occurred while fetching the image info')
143+ self.unit.status = BlockedStatus('Error fetching image information')
144+ return {}
145+
146 pod_env = self._make_pod_env()
147
148 return {
149diff --git a/tests/unit/scenario.py b/tests/unit/scenario.py
150index f71494f..3402589 100644
151--- a/tests/unit/scenario.py
152+++ b/tests/unit/scenario.py
153@@ -35,34 +35,23 @@ TEST_PG_CONNSTR = 'dbname=gunicorn host=1.2.3.4 password=pwd port=5432 user=usr'
154 TEST_JUJU_CONFIG = {
155 'defaults': {
156 'config': {},
157- 'logger': [
158- "ERROR:charm:Required Juju config item not set : image_path",
159- 'ERROR:charm:Required Juju config item not set : external_hostname',
160- ],
161- 'expected': 'Required Juju config item(s) not set : external_hostname, image_path',
162- },
163- 'missing_image_path': {
164- 'config': {
165- 'external_hostname': 'example.com',
166- },
167- 'logger': ["ERROR:charm:Required Juju config item not set : image_path"],
168- 'expected': 'Required Juju config item(s) not set : image_path',
169+ 'logger': [],
170+ 'expected': False,
171 },
172 'missing_external_hostname': {
173 'config': {
174- 'image_path': 'my_gunicorn_app:devel',
175+ 'external_hostname': '',
176 },
177 'logger': ["ERROR:charm:Required Juju config item not set : external_hostname"],
178 'expected': 'Required Juju config item(s) not set : external_hostname',
179 },
180 'good_config_no_env': {
181- 'config': {'image_path': 'my_gunicorn_app:devel', 'external_hostname': 'example.com'},
182+ 'config': {'external_hostname': 'example.com'},
183 'logger': [],
184 'expected': False,
185 },
186 'good_config_with_env': {
187 'config': {
188- 'image_path': 'my_gunicorn_app:devel',
189 'environment': 'MYENV: foo',
190 'external_hostname': 'example.com',
191 },
192@@ -72,16 +61,8 @@ TEST_JUJU_CONFIG = {
193 }
194
195 TEST_CONFIGURE_POD = {
196- 'bad_config': {
197- 'config': {
198- 'external_hostname': 'example.com',
199- },
200- '_leader_get': "5:\n database: gunicorn\n extensions: ''\n roles: ''",
201- 'expected': 'Required Juju config item(s) not set : image_path',
202- },
203 'good_config_no_env': {
204 'config': {
205- 'image_path': 'my_gunicorn_app:devel',
206 'external_hostname': 'example.com',
207 },
208 '_leader_get': "5:\n database: gunicorn\n extensions: ''\n roles: ''",
209@@ -89,7 +70,6 @@ TEST_CONFIGURE_POD = {
210 },
211 'good_config_with_env': {
212 'config': {
213- 'image_path': 'my_gunicorn_app:devel',
214 'external_hostname': 'example.com',
215 'environment': 'MYENV: foo',
216 },
217@@ -101,7 +81,6 @@ TEST_CONFIGURE_POD = {
218 TEST_MAKE_POD_SPEC = {
219 'basic_no_env': {
220 'config': {
221- 'image_path': 'my_gunicorn_app:devel',
222 'external_hostname': 'example.com',
223 },
224 'pod_spec': {
225@@ -110,7 +89,9 @@ TEST_MAKE_POD_SPEC = {
226 {
227 'name': 'gunicorn',
228 'imageDetails': {
229- 'imagePath': 'my_gunicorn_app:devel',
230+ 'imagePath': 'registrypath',
231+ 'password': 'password',
232+ 'username': 'username',
233 },
234 'imagePullPolicy': 'Always',
235 'ports': [{'containerPort': 80, 'protocol': 'TCP'}],
236@@ -122,7 +103,6 @@ TEST_MAKE_POD_SPEC = {
237 },
238 'basic_with_env': {
239 'config': {
240- 'image_path': 'my_gunicorn_app:devel',
241 'external_hostname': 'example.com',
242 'environment': 'MYENV: foo',
243 },
244@@ -132,7 +112,9 @@ TEST_MAKE_POD_SPEC = {
245 {
246 'name': 'gunicorn',
247 'imageDetails': {
248- 'imagePath': 'my_gunicorn_app:devel',
249+ 'imagePath': 'registrypath',
250+ 'password': 'password',
251+ 'username': 'username',
252 },
253 'imagePullPolicy': 'Always',
254 'ports': [{'containerPort': 80, 'protocol': 'TCP'}],
255@@ -142,38 +124,12 @@ TEST_MAKE_POD_SPEC = {
256 ],
257 },
258 },
259- 'private_registry': {
260- 'config': {
261- 'image_path': 'my_gunicorn_app:devel',
262- 'image_username': 'foo',
263- 'image_password': 'bar',
264- 'external_hostname': 'example.com',
265- },
266- 'pod_spec': {
267- 'version': 3, # otherwise resources are ignored
268- 'containers': [
269- {
270- 'name': 'gunicorn',
271- 'imageDetails': {
272- 'imagePath': 'my_gunicorn_app:devel',
273- 'username': 'foo',
274- 'password': 'bar',
275- },
276- 'imagePullPolicy': 'Always',
277- 'ports': [{'containerPort': 80, 'protocol': 'TCP'}],
278- 'envConfig': {},
279- 'kubernetes': {'readinessProbe': {'httpGet': {'path': '/', 'port': 80}}},
280- }
281- ],
282- },
283- },
284 }
285
286
287 TEST_MAKE_K8S_INGRESS = {
288 'basic': {
289 'config': {
290- 'image_path': 'my_gunicorn_app:devel',
291 'external_hostname': 'example.com',
292 },
293 'expected': [
294diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py
295index 746dbd3..b5e1f87 100755
296--- a/tests/unit/test_charm.py
297+++ b/tests/unit/test_charm.py
298@@ -34,6 +34,7 @@ class TestGunicornK8sCharm(unittest.TestCase):
299 """Setup the harness object."""
300 self.harness = testing.Harness(GunicornK8sCharm)
301 self.harness.begin()
302+ self.harness.add_oci_resource('gunicorn-image')
303
304 def tearDown(self):
305 """Cleanup the harness."""

Subscribers

People subscribed via source and target branches

to all changes: