Merge ~axino/charm-canonical-is-reviewbot/+git/charm-canonical-is-reviewbot:axino into charm-canonical-is-reviewbot:main
Proposed by
Junien F
Status: | Merged |
---|---|
Approved by: | Junien F |
Approved revision: | 971bbcf68ce690076c434f1c292c6d71ddd11148 |
Merged at revision: | 9763831c66fb1ba544d4b657de58f8bac49ee979 |
Proposed branch: | ~axino/charm-canonical-is-reviewbot/+git/charm-canonical-is-reviewbot:axino |
Merge into: | charm-canonical-is-reviewbot:main |
Diff against target: |
735 lines (+663/-0) 12 files modified
.gitignore (+9/-0) CONTRIBUTING.md (+33/-0) LICENSE (+202/-0) README.md (+7/-0) charmcraft.yaml (+11/-0) config.yaml (+30/-0) metadata.yaml (+18/-0) pyproject.toml (+39/-0) requirements.txt (+1/-0) src/charm.py (+121/-0) tests/unit/test_charm.py (+108/-0) tox.ini (+84/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Barry Price | Approve | ||
Review via email: mp+436216@code.launchpad.net |
Commit message
initial commit
Description of the change
To post a comment you must log in.
Revision history for this message
Junien F (axino) wrote : | # |
Fixed, thanks !
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
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Change has no commit message, setting status to needs review.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Change successfully merged at revision 9763831c66fb1ba
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/.gitignore b/.gitignore |
2 | new file mode 100644 |
3 | index 0000000..a26d707 |
4 | --- /dev/null |
5 | +++ b/.gitignore |
6 | @@ -0,0 +1,9 @@ |
7 | +venv/ |
8 | +build/ |
9 | +*.charm |
10 | +.tox/ |
11 | +.coverage |
12 | +__pycache__/ |
13 | +*.py[cod] |
14 | +.idea |
15 | +.vscode/ |
16 | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md |
17 | new file mode 100644 |
18 | index 0000000..2969183 |
19 | --- /dev/null |
20 | +++ b/CONTRIBUTING.md |
21 | @@ -0,0 +1,33 @@ |
22 | +# Contributing |
23 | + |
24 | +To make contributions to this charm, you'll need a working [development setup](https://juju.is/docs/sdk/dev-setup). |
25 | + |
26 | +You can use the environments created by `tox` for development: |
27 | + |
28 | +```shell |
29 | +tox --notest -e unit |
30 | +source .tox/unit/bin/activate |
31 | +``` |
32 | + |
33 | +## Testing |
34 | + |
35 | +This project uses `tox` for managing test environments. There are some pre-configured environments |
36 | +that can be used for linting and formatting code when you're preparing contributions to the charm: |
37 | + |
38 | +```shell |
39 | +tox -e fmt # update your code according to linting rules |
40 | +tox -e lint # code style |
41 | +tox -e unit # unit tests |
42 | +tox -e integration # integration tests |
43 | +tox # runs 'lint' and 'unit' environments |
44 | +``` |
45 | + |
46 | +## Build the charm |
47 | + |
48 | +Build the charm in this git repository using: |
49 | + |
50 | +```shell |
51 | +charmcraft pack |
52 | +``` |
53 | + |
54 | +<!-- You may want to include any contribution/style guidelines in this document> |
55 | diff --git a/LICENSE b/LICENSE |
56 | new file mode 100644 |
57 | index 0000000..ecbda55 |
58 | --- /dev/null |
59 | +++ b/LICENSE |
60 | @@ -0,0 +1,202 @@ |
61 | + |
62 | + Apache License |
63 | + Version 2.0, January 2004 |
64 | + http://www.apache.org/licenses/ |
65 | + |
66 | + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
67 | + |
68 | + 1. Definitions. |
69 | + |
70 | + "License" shall mean the terms and conditions for use, reproduction, |
71 | + and distribution as defined by Sections 1 through 9 of this document. |
72 | + |
73 | + "Licensor" shall mean the copyright owner or entity authorized by |
74 | + the copyright owner that is granting the License. |
75 | + |
76 | + "Legal Entity" shall mean the union of the acting entity and all |
77 | + other entities that control, are controlled by, or are under common |
78 | + control with that entity. For the purposes of this definition, |
79 | + "control" means (i) the power, direct or indirect, to cause the |
80 | + direction or management of such entity, whether by contract or |
81 | + otherwise, or (ii) ownership of fifty percent (50%) or more of the |
82 | + outstanding shares, or (iii) beneficial ownership of such entity. |
83 | + |
84 | + "You" (or "Your") shall mean an individual or Legal Entity |
85 | + exercising permissions granted by this License. |
86 | + |
87 | + "Source" form shall mean the preferred form for making modifications, |
88 | + including but not limited to software source code, documentation |
89 | + source, and configuration files. |
90 | + |
91 | + "Object" form shall mean any form resulting from mechanical |
92 | + transformation or translation of a Source form, including but |
93 | + not limited to compiled object code, generated documentation, |
94 | + and conversions to other media types. |
95 | + |
96 | + "Work" shall mean the work of authorship, whether in Source or |
97 | + Object form, made available under the License, as indicated by a |
98 | + copyright notice that is included in or attached to the work |
99 | + (an example is provided in the Appendix below). |
100 | + |
101 | + "Derivative Works" shall mean any work, whether in Source or Object |
102 | + form, that is based on (or derived from) the Work and for which the |
103 | + editorial revisions, annotations, elaborations, or other modifications |
104 | + represent, as a whole, an original work of authorship. For the purposes |
105 | + of this License, Derivative Works shall not include works that remain |
106 | + separable from, or merely link (or bind by name) to the interfaces of, |
107 | + the Work and Derivative Works thereof. |
108 | + |
109 | + "Contribution" shall mean any work of authorship, including |
110 | + the original version of the Work and any modifications or additions |
111 | + to that Work or Derivative Works thereof, that is intentionally |
112 | + submitted to Licensor for inclusion in the Work by the copyright owner |
113 | + or by an individual or Legal Entity authorized to submit on behalf of |
114 | + the copyright owner. For the purposes of this definition, "submitted" |
115 | + means any form of electronic, verbal, or written communication sent |
116 | + to the Licensor or its representatives, including but not limited to |
117 | + communication on electronic mailing lists, source code control systems, |
118 | + and issue tracking systems that are managed by, or on behalf of, the |
119 | + Licensor for the purpose of discussing and improving the Work, but |
120 | + excluding communication that is conspicuously marked or otherwise |
121 | + designated in writing by the copyright owner as "Not a Contribution." |
122 | + |
123 | + "Contributor" shall mean Licensor and any individual or Legal Entity |
124 | + on behalf of whom a Contribution has been received by Licensor and |
125 | + subsequently incorporated within the Work. |
126 | + |
127 | + 2. Grant of Copyright License. Subject to the terms and conditions of |
128 | + this License, each Contributor hereby grants to You a perpetual, |
129 | + worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
130 | + copyright license to reproduce, prepare Derivative Works of, |
131 | + publicly display, publicly perform, sublicense, and distribute the |
132 | + Work and such Derivative Works in Source or Object form. |
133 | + |
134 | + 3. Grant of Patent License. Subject to the terms and conditions of |
135 | + this License, each Contributor hereby grants to You a perpetual, |
136 | + worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
137 | + (except as stated in this section) patent license to make, have made, |
138 | + use, offer to sell, sell, import, and otherwise transfer the Work, |
139 | + where such license applies only to those patent claims licensable |
140 | + by such Contributor that are necessarily infringed by their |
141 | + Contribution(s) alone or by combination of their Contribution(s) |
142 | + with the Work to which such Contribution(s) was submitted. If You |
143 | + institute patent litigation against any entity (including a |
144 | + cross-claim or counterclaim in a lawsuit) alleging that the Work |
145 | + or a Contribution incorporated within the Work constitutes direct |
146 | + or contributory patent infringement, then any patent licenses |
147 | + granted to You under this License for that Work shall terminate |
148 | + as of the date such litigation is filed. |
149 | + |
150 | + 4. Redistribution. You may reproduce and distribute copies of the |
151 | + Work or Derivative Works thereof in any medium, with or without |
152 | + modifications, and in Source or Object form, provided that You |
153 | + meet the following conditions: |
154 | + |
155 | + (a) You must give any other recipients of the Work or |
156 | + Derivative Works a copy of this License; and |
157 | + |
158 | + (b) You must cause any modified files to carry prominent notices |
159 | + stating that You changed the files; and |
160 | + |
161 | + (c) You must retain, in the Source form of any Derivative Works |
162 | + that You distribute, all copyright, patent, trademark, and |
163 | + attribution notices from the Source form of the Work, |
164 | + excluding those notices that do not pertain to any part of |
165 | + the Derivative Works; and |
166 | + |
167 | + (d) If the Work includes a "NOTICE" text file as part of its |
168 | + distribution, then any Derivative Works that You distribute must |
169 | + include a readable copy of the attribution notices contained |
170 | + within such NOTICE file, excluding those notices that do not |
171 | + pertain to any part of the Derivative Works, in at least one |
172 | + of the following places: within a NOTICE text file distributed |
173 | + as part of the Derivative Works; within the Source form or |
174 | + documentation, if provided along with the Derivative Works; or, |
175 | + within a display generated by the Derivative Works, if and |
176 | + wherever such third-party notices normally appear. The contents |
177 | + of the NOTICE file are for informational purposes only and |
178 | + do not modify the License. You may add Your own attribution |
179 | + notices within Derivative Works that You distribute, alongside |
180 | + or as an addendum to the NOTICE text from the Work, provided |
181 | + that such additional attribution notices cannot be construed |
182 | + as modifying the License. |
183 | + |
184 | + You may add Your own copyright statement to Your modifications and |
185 | + may provide additional or different license terms and conditions |
186 | + for use, reproduction, or distribution of Your modifications, or |
187 | + for any such Derivative Works as a whole, provided Your use, |
188 | + reproduction, and distribution of the Work otherwise complies with |
189 | + the conditions stated in this License. |
190 | + |
191 | + 5. Submission of Contributions. Unless You explicitly state otherwise, |
192 | + any Contribution intentionally submitted for inclusion in the Work |
193 | + by You to the Licensor shall be under the terms and conditions of |
194 | + this License, without any additional terms or conditions. |
195 | + Notwithstanding the above, nothing herein shall supersede or modify |
196 | + the terms of any separate license agreement you may have executed |
197 | + with Licensor regarding such Contributions. |
198 | + |
199 | + 6. Trademarks. This License does not grant permission to use the trade |
200 | + names, trademarks, service marks, or product names of the Licensor, |
201 | + except as required for reasonable and customary use in describing the |
202 | + origin of the Work and reproducing the content of the NOTICE file. |
203 | + |
204 | + 7. Disclaimer of Warranty. Unless required by applicable law or |
205 | + agreed to in writing, Licensor provides the Work (and each |
206 | + Contributor provides its Contributions) on an "AS IS" BASIS, |
207 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
208 | + implied, including, without limitation, any warranties or conditions |
209 | + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
210 | + PARTICULAR PURPOSE. You are solely responsible for determining the |
211 | + appropriateness of using or redistributing the Work and assume any |
212 | + risks associated with Your exercise of permissions under this License. |
213 | + |
214 | + 8. Limitation of Liability. In no event and under no legal theory, |
215 | + whether in tort (including negligence), contract, or otherwise, |
216 | + unless required by applicable law (such as deliberate and grossly |
217 | + negligent acts) or agreed to in writing, shall any Contributor be |
218 | + liable to You for damages, including any direct, indirect, special, |
219 | + incidental, or consequential damages of any character arising as a |
220 | + result of this License or out of the use or inability to use the |
221 | + Work (including but not limited to damages for loss of goodwill, |
222 | + work stoppage, computer failure or malfunction, or any and all |
223 | + other commercial damages or losses), even if such Contributor |
224 | + has been advised of the possibility of such damages. |
225 | + |
226 | + 9. Accepting Warranty or Additional Liability. While redistributing |
227 | + the Work or Derivative Works thereof, You may choose to offer, |
228 | + and charge a fee for, acceptance of support, warranty, indemnity, |
229 | + or other liability obligations and/or rights consistent with this |
230 | + License. However, in accepting such obligations, You may act only |
231 | + on Your own behalf and on Your sole responsibility, not on behalf |
232 | + of any other Contributor, and only if You agree to indemnify, |
233 | + defend, and hold each Contributor harmless for any liability |
234 | + incurred by, or claims asserted against, such Contributor by reason |
235 | + of your accepting any such warranty or additional liability. |
236 | + |
237 | + END OF TERMS AND CONDITIONS |
238 | + |
239 | + APPENDIX: How to apply the Apache License to your work. |
240 | + |
241 | + To apply the Apache License to your work, attach the following |
242 | + boilerplate notice, with the fields enclosed by brackets "[]" |
243 | + replaced with your own identifying information. (Don't include |
244 | + the brackets!) The text should be enclosed in the appropriate |
245 | + comment syntax for the file format. We also recommend that a |
246 | + file or class name and description of purpose be included on the |
247 | + same "printed page" as the copyright notice for easier |
248 | + identification within third-party archives. |
249 | + |
250 | + Copyright 2022 Ubuntu |
251 | + |
252 | + Licensed under the Apache License, Version 2.0 (the "License"); |
253 | + you may not use this file except in compliance with the License. |
254 | + You may obtain a copy of the License at |
255 | + |
256 | + http://www.apache.org/licenses/LICENSE-2.0 |
257 | + |
258 | + Unless required by applicable law or agreed to in writing, software |
259 | + distributed under the License is distributed on an "AS IS" BASIS, |
260 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
261 | + See the License for the specific language governing permissions and |
262 | + limitations under the License. |
263 | diff --git a/README.md b/README.md |
264 | new file mode 100644 |
265 | index 0000000..6cca19d |
266 | --- /dev/null |
267 | +++ b/README.md |
268 | @@ -0,0 +1,7 @@ |
269 | +# charm-canonical-is-reviewbot |
270 | + |
271 | +Charmhub package name: mm-reviewbot-charmers-canonical-is-reviewbot |
272 | +More information: https://charmhub.io/mm-reviewbot-charmers-canonical-is-reviewbot |
273 | + |
274 | +This charm deploys the Canonical IS reviewbot, which helps doing reviews of |
275 | +Launchpad merge proposals for the IS team. |
276 | diff --git a/charmcraft.yaml b/charmcraft.yaml |
277 | new file mode 100644 |
278 | index 0000000..900d34a |
279 | --- /dev/null |
280 | +++ b/charmcraft.yaml |
281 | @@ -0,0 +1,11 @@ |
282 | +# This file configures Charmcraft. |
283 | +# See https://juju.is/docs/sdk/charmcraft-config for guidance. |
284 | + |
285 | +type: charm |
286 | +bases: |
287 | + - build-on: |
288 | + - name: ubuntu |
289 | + channel: "22.04" |
290 | + run-on: |
291 | + - name: ubuntu |
292 | + channel: "22.04" |
293 | diff --git a/config.yaml b/config.yaml |
294 | new file mode 100644 |
295 | index 0000000..79e1a0d |
296 | --- /dev/null |
297 | +++ b/config.yaml |
298 | @@ -0,0 +1,30 @@ |
299 | +# This file defines charm config options, and populates the Configure tab on Charmhub. |
300 | +# If your charm does not require configuration options, delete this file entirely. |
301 | +# |
302 | +# See https://juju.is/docs/config for guidance. |
303 | + |
304 | +options: |
305 | + launchpad_secret: |
306 | + description: "Launchpad credential secret" |
307 | + default: "" |
308 | + type: string |
309 | + launchpad_token: |
310 | + description: "Launchpad credential token" |
311 | + default: "" |
312 | + type: string |
313 | + mattermost_token: |
314 | + description: "Mattermost API token" |
315 | + default: "" |
316 | + type: string |
317 | + mattermost_channel: |
318 | + description: "Mattermost channel in which to expect review requests" |
319 | + default: "" |
320 | + type: string |
321 | + mattermost_url: |
322 | + description: "Mattermost URL" |
323 | + default: "" |
324 | + type: string |
325 | + mattermost_team: |
326 | + description: "Mattermost team" |
327 | + default: "" |
328 | + type: string |
329 | diff --git a/metadata.yaml b/metadata.yaml |
330 | new file mode 100644 |
331 | index 0000000..83d6842 |
332 | --- /dev/null |
333 | +++ b/metadata.yaml |
334 | @@ -0,0 +1,18 @@ |
335 | +name: canonical-is-reviewbot |
336 | +display-name: Mattermost Review Bot |
337 | +summary: MatterMost Review Bot charm |
338 | +maintainers: |
339 | + - https://launchpad.net/~mm-reviewbot-charmers |
340 | +description: | |
341 | + A charm which deploys Mattermost Review Bot on kubernetes. |
342 | + Mattermost is a flexible, open source messaging platform that enables |
343 | + secure team collaboration. |
344 | + This bot allows reviewing Launchpad merge proposals on Mattermost |
345 | +containers: |
346 | + reviewbot: |
347 | + resource: reviewbot-image |
348 | +resources: |
349 | + reviewbot-image: |
350 | + type: oci-image |
351 | + description: Reviewbot docker image |
352 | + auto-fetch: true |
353 | diff --git a/pyproject.toml b/pyproject.toml |
354 | new file mode 100644 |
355 | index 0000000..3f51442 |
356 | --- /dev/null |
357 | +++ b/pyproject.toml |
358 | @@ -0,0 +1,39 @@ |
359 | +# Testing tools configuration |
360 | +[tool.coverage.run] |
361 | +branch = true |
362 | + |
363 | +[tool.coverage.report] |
364 | +show_missing = true |
365 | + |
366 | +[tool.pytest.ini_options] |
367 | +minversion = "6.0" |
368 | +log_cli_level = "INFO" |
369 | + |
370 | +# Formatting tools configuration |
371 | +[tool.black] |
372 | +line-length = 99 |
373 | +target-version = ["py38"] |
374 | + |
375 | +# Linting tools configuration |
376 | +[tool.ruff] |
377 | +line-length = 99 |
378 | +select = ["E", "W", "F", "C", "N", "D", "I001"] |
379 | +extend-ignore = [ |
380 | + "D203", |
381 | + "D204", |
382 | + "D213", |
383 | + "D215", |
384 | + "D400", |
385 | + "D404", |
386 | + "D406", |
387 | + "D407", |
388 | + "D408", |
389 | + "D409", |
390 | + "D413", |
391 | +] |
392 | +ignore = ["E501", "D107"] |
393 | +extend-exclude = ["__pycache__", "*.egg_info"] |
394 | +per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]} |
395 | + |
396 | +[tool.ruff.mccabe] |
397 | +max-complexity = 10 |
398 | diff --git a/requirements.txt b/requirements.txt |
399 | new file mode 100644 |
400 | index 0000000..56f5f64 |
401 | --- /dev/null |
402 | +++ b/requirements.txt |
403 | @@ -0,0 +1 @@ |
404 | +ops >= 1.5.0 |
405 | diff --git a/src/charm.py b/src/charm.py |
406 | new file mode 100755 |
407 | index 0000000..e2bf66d |
408 | --- /dev/null |
409 | +++ b/src/charm.py |
410 | @@ -0,0 +1,121 @@ |
411 | +#!/usr/bin/env python3 |
412 | +# Copyright 2022 Ubuntu |
413 | +# See LICENSE file for licensing details. |
414 | +# |
415 | +# Learn more at: https://juju.is/docs/sdk |
416 | + |
417 | +"""Charm the service. |
418 | + |
419 | +Refer to the following post for a quick-start guide that will help you |
420 | +develop a new k8s charm using the Operator Framework: |
421 | + |
422 | +https://discourse.charmhub.io/t/4208 |
423 | +""" |
424 | + |
425 | +import logging |
426 | + |
427 | +from ops.charm import CharmBase |
428 | +from ops.main import main |
429 | +from ops.model import ActiveStatus, BlockedStatus, WaitingStatus |
430 | + |
431 | +# Log messages can be retrieved using juju debug-log |
432 | +logger = logging.getLogger(__name__) |
433 | + |
434 | +VALID_LOG_LEVELS = ["info", "debug", "warning", "error", "critical"] |
435 | + |
436 | + |
437 | +class CharmReviewbotCharm(CharmBase): |
438 | + """Charm the service.""" |
439 | + |
440 | + def __init__(self, *args): |
441 | + super().__init__(*args) |
442 | + self.framework.observe(self.on.reviewbot_pebble_ready, self._on_reviewbot_pebble_ready) |
443 | + self.framework.observe(self.on.config_changed, self._on_config_changed) |
444 | + |
445 | + def _missing_config(self): |
446 | + # All config options must be set |
447 | + missing_config = [] |
448 | + for k, v in self.model.config.items(): |
449 | + if not v: |
450 | + missing_config.append(k) |
451 | + return missing_config |
452 | + |
453 | + def _on_reviewbot_pebble_ready(self, event): |
454 | + """Define and start a workload using the Pebble API. |
455 | + |
456 | + Change this example to suit your needs. You'll need to specify the right entrypoint and |
457 | + environment configuration for your specific workload. |
458 | + |
459 | + Learn more about interacting with Pebble at at https://juju.is/docs/sdk/pebble. |
460 | + """ |
461 | + missing_config = self._missing_config() |
462 | + if missing_config: |
463 | + self.unit.status = BlockedStatus(f"required config not set: '{missing_config}'") |
464 | + return |
465 | + # Get a reference the container attribute on the PebbleReadyEvent |
466 | + container = event.workload |
467 | + # Add initial Pebble config layer using the Pebble API |
468 | + container.add_layer("reviewbot", self._pebble_layer, combine=True) |
469 | + # Make Pebble reevaluate its plan, ensuring any services are started if enabled. |
470 | + container.replan() |
471 | + # Learn more about statuses in the SDK docs: |
472 | + # https://juju.is/docs/sdk/constructs#heading--statuses |
473 | + self.unit.status = ActiveStatus() |
474 | + |
475 | + def _get_reviewbot_env_config(self) -> dict: |
476 | + """Return an envConfig with configuration.""" |
477 | + env_config = { |
478 | + "REVIEWBOT_LP_SECRET": self.config["launchpad_secret"], |
479 | + "REVIEWBOT_LP_TOKEN": self.config["launchpad_token"], |
480 | + "REVIEWBOT_MM_CHANNEL": self.config["mattermost_channel"], |
481 | + "REVIEWBOT_MM_TEAM": self.config["mattermost_team"], |
482 | + "REVIEWBOT_MM_TOKEN": self.config["mattermost_token"], |
483 | + "REVIEWBOT_MM_URL": self.config["mattermost_url"], |
484 | + "HOME": "/home/reviewbot", # work around https://github.com/canonical/pebble/issues/183 |
485 | + } |
486 | + |
487 | + return env_config |
488 | + |
489 | + def _on_config_changed(self, event): |
490 | + """Handle changed configuration.""" |
491 | + missing_config = self._missing_config() |
492 | + if missing_config: |
493 | + self.unit.status = BlockedStatus(f"required config not set: '{missing_config}'") |
494 | + return |
495 | + |
496 | + # The config is good, so update the configuration of the workload |
497 | + container = self.unit.get_container("reviewbot") |
498 | + # Verify that we can connect to the Pebble API in the workload container |
499 | + if container.can_connect(): |
500 | + # Push an updated layer with the new config |
501 | + container.add_layer("reviewbot", self._pebble_layer, combine=True) |
502 | + container.replan() |
503 | + |
504 | + self.unit.status = ActiveStatus() |
505 | + else: |
506 | + # We were unable to connect to the Pebble API, so we defer this event |
507 | + event.defer() |
508 | + self.unit.status = WaitingStatus("waiting for Pebble API") |
509 | + |
510 | + @property |
511 | + def _pebble_layer(self): |
512 | + """Return a dictionary representing a Pebble layer.""" |
513 | + return { |
514 | + "summary": "reviewbot layer", |
515 | + "description": "pebble config layer for reviewbot", |
516 | + "services": { |
517 | + "reviewbot": { |
518 | + "override": "replace", |
519 | + "summary": "reviewbot", |
520 | + "user": "reviewbot", |
521 | + "group": "reviewbot", |
522 | + "command": "/app/bot.py", |
523 | + "startup": "enabled", |
524 | + "environment": self._get_reviewbot_env_config(), |
525 | + }, |
526 | + }, |
527 | + } |
528 | + |
529 | + |
530 | +if __name__ == "__main__": # pragma: nocover |
531 | + main(CharmReviewbotCharm) |
532 | diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py |
533 | new file mode 100644 |
534 | index 0000000..3399c04 |
535 | --- /dev/null |
536 | +++ b/tests/unit/test_charm.py |
537 | @@ -0,0 +1,108 @@ |
538 | +# Copyright 2022 Ubuntu |
539 | +# See LICENSE file for licensing details. |
540 | +# |
541 | +# Learn more about testing at: https://juju.is/docs/sdk/testing |
542 | + |
543 | +import unittest |
544 | + |
545 | +import ops.testing |
546 | +from charm import CharmReviewbotCharm |
547 | +from ops.model import ActiveStatus, BlockedStatus, WaitingStatus |
548 | +from ops.testing import Harness |
549 | + |
550 | + |
551 | +class TestCharm(unittest.TestCase): |
552 | + base_config = { |
553 | + "launchpad_secret": "lps", |
554 | + "launchpad_token": "lpt", |
555 | + "mattermost_token": "mmtok", |
556 | + "mattermost_channel": "mmc", |
557 | + "mattermost_url": "mmu", |
558 | + "mattermost_team": "mmt", |
559 | + } |
560 | + |
561 | + def setUp(self): |
562 | + # Enable more accurate simulation of container networking. |
563 | + # For more information, see https://juju.is/docs/sdk/testing#heading--simulate-can-connect |
564 | + ops.testing.SIMULATE_CAN_CONNECT = True |
565 | + self.addCleanup(setattr, ops.testing, "SIMULATE_CAN_CONNECT", False) |
566 | + |
567 | + self.harness = Harness(CharmReviewbotCharm) |
568 | + self.addCleanup(self.harness.cleanup) |
569 | + self.harness.begin() |
570 | + |
571 | + def test_reviewbot_pebble_ready(self): |
572 | + # Expected plan after Pebble ready with default config |
573 | + expected_plan = { |
574 | + "services": { |
575 | + "reviewbot": { |
576 | + "override": "replace", |
577 | + "summary": "reviewbot", |
578 | + "command": "/app/bot.py", |
579 | + "startup": "enabled", |
580 | + "environment": { |
581 | + "REVIEWBOT_LP_SECRET": "lps", |
582 | + "REVIEWBOT_LP_TOKEN": "lpt", |
583 | + "REVIEWBOT_MM_CHANNEL": "mmc", |
584 | + "REVIEWBOT_MM_TEAM": "mmt", |
585 | + "REVIEWBOT_MM_TOKEN": "mmtok", |
586 | + "REVIEWBOT_MM_URL": "mmu", |
587 | + "HOME": "/home/reviewbot", |
588 | + }, |
589 | + "user": "reviewbot", |
590 | + "group": "reviewbot", |
591 | + } |
592 | + }, |
593 | + } |
594 | + # Simulate the container coming up and emission of pebble-ready event |
595 | + self.harness.update_config(self.base_config) |
596 | + self.harness.container_pebble_ready("reviewbot") |
597 | + # Get the plan now we've run PebbleReady |
598 | + updated_plan = self.harness.get_container_pebble_plan("reviewbot").to_dict() |
599 | + # Check we've got the plan we expected |
600 | + self.assertEqual(expected_plan, updated_plan) |
601 | + # Check the service was started |
602 | + service = self.harness.model.unit.get_container("reviewbot").get_service("reviewbot") |
603 | + self.assertTrue(service.is_running()) |
604 | + # Ensure we set an ActiveStatus with no message |
605 | + self.assertEqual(self.harness.model.unit.status, ActiveStatus()) |
606 | + |
607 | + def test_reviewbot_pebble_ready_no_config(self): |
608 | + # Expect an empty plan when a config item is missing |
609 | + expected_plan = {} |
610 | + # Simulate the container coming up and emission of pebble-ready event |
611 | + self.harness.container_pebble_ready("reviewbot") |
612 | + # Get the plan now we've run PebbleReady |
613 | + updated_plan = self.harness.get_container_pebble_plan("reviewbot").to_dict() |
614 | + # Check we've got the plan we expected |
615 | + self.assertEqual(expected_plan, updated_plan) |
616 | + # Ensure we got a BlockedStatus |
617 | + self.assertIsInstance(self.harness.model.unit.status, BlockedStatus) |
618 | + |
619 | + def test_config_changed_valid_can_connect(self): |
620 | + # Ensure the simulated Pebble API is reachable |
621 | + self.harness.set_can_connect("reviewbot", True) |
622 | + # Trigger a config-changed event with an updated value |
623 | + changed_config = self.base_config.copy() |
624 | + changed_config["launchpad_secret"] = "CHANGED" |
625 | + self.harness.update_config(changed_config) |
626 | + # Get the plan now we've run PebbleReady |
627 | + updated_plan = self.harness.get_container_pebble_plan("reviewbot").to_dict() |
628 | + updated_env = updated_plan["services"]["reviewbot"]["environment"] |
629 | + # Check the config change was effective |
630 | + self.assertEqual(updated_env["REVIEWBOT_LP_SECRET"], "CHANGED") |
631 | + self.assertEqual(self.harness.model.unit.status, ActiveStatus()) |
632 | + |
633 | + def test_config_changed_valid_cannot_connect(self): |
634 | + # Trigger a config-changed event with an updated value |
635 | + self.harness.update_config(self.base_config) |
636 | + # Check the charm is in WaitingStatus |
637 | + self.assertIsInstance(self.harness.model.unit.status, WaitingStatus) |
638 | + |
639 | + def test_config_changed_invalid(self): |
640 | + # Ensure the simulated Pebble API is reachable |
641 | + self.harness.set_can_connect("reviewbot", True) |
642 | + # Trigger a config-changed event with an updated value |
643 | + self.harness.update_config({}) |
644 | + # Check the charm is in BlockedStatus |
645 | + self.assertIsInstance(self.harness.model.unit.status, BlockedStatus) |
646 | diff --git a/tox.ini b/tox.ini |
647 | new file mode 100644 |
648 | index 0000000..d4284f8 |
649 | --- /dev/null |
650 | +++ b/tox.ini |
651 | @@ -0,0 +1,84 @@ |
652 | +# Copyright 2022 Ubuntu |
653 | +# See LICENSE file for licensing details. |
654 | + |
655 | +[tox] |
656 | +skipsdist=True |
657 | +skip_missing_interpreters = True |
658 | +envlist = fmt, lint, unit |
659 | + |
660 | +[vars] |
661 | +src_path = {toxinidir}/src/ |
662 | +tst_path = {toxinidir}/tests/ |
663 | +;lib_path = {toxinidir}/lib/charms/operator_name_with_underscores |
664 | +all_path = {[vars]src_path} {[vars]tst_path} |
665 | + |
666 | +[testenv] |
667 | +setenv = |
668 | + PYTHONPATH = {toxinidir}:{toxinidir}/lib:{[vars]src_path} |
669 | + PYTHONBREAKPOINT=pdb.set_trace |
670 | + PY_COLORS=1 |
671 | +passenv = |
672 | + PYTHONPATH |
673 | + CHARM_BUILD_DIR |
674 | + MODEL_SETTINGS |
675 | + |
676 | +[testenv:fmt] |
677 | +description = Apply coding style standards to code |
678 | +deps = |
679 | + black |
680 | + ruff |
681 | +commands = |
682 | + black {[vars]all_path} |
683 | + ruff --fix {[vars]all_path} |
684 | + |
685 | +[testenv:lint] |
686 | +description = Check code against coding style standards |
687 | +deps = |
688 | + black |
689 | + ruff |
690 | + codespell |
691 | +commands = |
692 | + # uncomment the following line if this charm owns a lib |
693 | + # codespell {[vars]lib_path} |
694 | + codespell {toxinidir} \ |
695 | + --skip {toxinidir}/.git \ |
696 | + --skip {toxinidir}/.tox \ |
697 | + --skip {toxinidir}/build \ |
698 | + --skip {toxinidir}/lib \ |
699 | + --skip {toxinidir}/venv \ |
700 | + --skip {toxinidir}/.mypy_cache \ |
701 | + --skip {toxinidir}/icon.svg |
702 | + |
703 | + ruff {[vars]all_path} |
704 | + black --check --diff {[vars]all_path} |
705 | + |
706 | +[testenv:unit] |
707 | +description = Run unit tests |
708 | +deps = |
709 | + pytest |
710 | + coverage[toml] |
711 | + -r{toxinidir}/requirements.txt |
712 | +commands = |
713 | + coverage run --source={[vars]src_path} \ |
714 | + -m pytest \ |
715 | + --ignore={[vars]tst_path}integration \ |
716 | + --tb native \ |
717 | + -v \ |
718 | + -s \ |
719 | + {posargs} |
720 | + coverage report |
721 | + |
722 | +[testenv:integration] |
723 | +description = Run integration tests |
724 | +deps = |
725 | + pytest |
726 | + juju |
727 | + pytest-operator |
728 | + -r{toxinidir}/requirements.txt |
729 | +commands = |
730 | + pytest -v \ |
731 | + -s \ |
732 | + --tb native \ |
733 | + --ignore={[vars]tst_path}unit \ |
734 | + --log-cli-level=INFO \ |
735 | + {posargs} |
Tiny nitpick inline, LGTM otherwise