From 4127704c164f382b9d138df5777b2d27c4682a65 Mon Sep 17 00:00:00 2001 From: Weii Wang Date: Wed, 27 Nov 2024 20:42:20 +0800 Subject: [PATCH] Initial commit --- .github/ISSUE_TEMPLATE/bug_report.yml | 57 +++++ .../ISSUE_TEMPLATE/enhancement_proposal.yml | 17 ++ .github/pull_request_template.yaml | 32 +++ .github/workflows/auto_update_libs.yaml | 10 + .github/workflows/bot_pr_approval.yaml | 10 + .github/workflows/comment.yaml | 12 ++ .github/workflows/integration_test.yaml | 22 ++ .github/workflows/issues.yaml | 11 + .github/workflows/load_test.yaml | 13 ++ .github/workflows/promote_charm.yaml | 26 +++ .github/workflows/publish_charm.yaml | 14 ++ .github/workflows/test.yaml | 12 ++ .gitignore | 12 ++ .jujuignore | 4 + .licenserc.yaml | 23 ++ CODEOWNERS | 1 + CONTRIBUTING.md | 110 ++++++++++ LICENSE | 202 ++++++++++++++++++ README.md | 53 +++++ charmcraft.yaml | 13 ++ config.yaml | 16 ++ .../explanation/charm-architecture.md | 104 +++++++++ docs-template/how-to/contribute.md | 5 + docs-template/index.md | 58 +++++ docs-template/reference/actions.md | 5 + docs-template/reference/configurations.md | 5 + docs-template/reference/integrations.md | 16 ++ generate-src-docs.sh | 6 + metadata.yaml | 50 +++++ pyproject.toml | 79 +++++++ renovate.json | 32 +++ requirements.txt | 1 + src/charm.py | 117 ++++++++++ tests/conftest.py | 13 ++ tests/integration/__init__.py | 2 + tests/integration/test_charm.py | 39 ++++ tests/unit/__init__.py | 2 + tests/unit/test_base.py | 75 +++++++ tox.ini | 122 +++++++++++ trivy.yaml | 3 + zap_rules.tsv | 0 41 files changed, 1404 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/enhancement_proposal.yml create mode 100644 .github/pull_request_template.yaml create mode 100644 .github/workflows/auto_update_libs.yaml create mode 100644 .github/workflows/bot_pr_approval.yaml create mode 100644 .github/workflows/comment.yaml create mode 100644 .github/workflows/integration_test.yaml create mode 100644 .github/workflows/issues.yaml create mode 100644 .github/workflows/load_test.yaml create mode 100644 .github/workflows/promote_charm.yaml create mode 100644 .github/workflows/publish_charm.yaml create mode 100644 .github/workflows/test.yaml create mode 100644 .gitignore create mode 100644 .jujuignore create mode 100644 .licenserc.yaml create mode 100644 CODEOWNERS create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 charmcraft.yaml create mode 100644 config.yaml create mode 100644 docs-template/explanation/charm-architecture.md create mode 100644 docs-template/how-to/contribute.md create mode 100644 docs-template/index.md create mode 100644 docs-template/reference/actions.md create mode 100644 docs-template/reference/configurations.md create mode 100644 docs-template/reference/integrations.md create mode 100644 generate-src-docs.sh create mode 100644 metadata.yaml create mode 100644 pyproject.toml create mode 100644 renovate.json create mode 100644 requirements.txt create mode 100755 src/charm.py create mode 100644 tests/conftest.py create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/test_charm.py create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/test_base.py create mode 100644 tox.ini create mode 100644 trivy.yaml create mode 100644 zap_rules.tsv diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..466be6c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,57 @@ +name: Bug Report +description: File a bug report +labels: ["Type: Bug", "Status: Triage"] +body: + - type: markdown + attributes: + value: > + Thanks for taking the time to fill out this bug report! Before submitting your issue, please make + sure you are using the latest version of the charm. If not, please switch to this image prior to + posting your report to make sure it's not already solved. + - type: textarea + id: bug-description + attributes: + label: Bug Description + description: > + If applicable, add screenshots to help explain the problem you are facing. + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: To Reproduce + description: > + Please provide a step-by-step instruction of how to reproduce the behavior. + placeholder: | + 1. `juju deploy ...` + 2. `juju relate ...` + 3. `juju status --relations` + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment + description: > + We need to know a bit more about the context in which you run the charm. + - Are you running Juju locally, on lxd, in multipass or on some other platform? + - What track and channel you deployed the charm from (i.e. `latest/edge` or similar). + - Version of any applicable components, like the juju snap, the model controller, lxd, microk8s, and/or multipass. + validations: + required: true + - type: textarea + id: logs + attributes: + label: Relevant log output + description: > + Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + Fetch the logs using `juju debug-log --replay` and `kubectl logs ...`. Additional details available in the juju docs + at https://juju.is/docs/olm/juju-logs + render: shell + validations: + required: true + - type: textarea + id: additional-context + attributes: + label: Additional context + diff --git a/.github/ISSUE_TEMPLATE/enhancement_proposal.yml b/.github/ISSUE_TEMPLATE/enhancement_proposal.yml new file mode 100644 index 0000000..b2348b9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement_proposal.yml @@ -0,0 +1,17 @@ +name: Enhancement Proposal +description: File an enhancement proposal +labels: ["Type: Enhancement", "Status: Triage"] +body: + - type: markdown + attributes: + value: > + Thanks for taking the time to fill out this enhancement proposal! Before submitting your issue, please make + sure there isn't already a prior issue concerning this. If there is, please join that discussion instead. + - type: textarea + id: enhancement-proposal + attributes: + label: Enhancement Proposal + description: > + Describe the enhancement you would like to see in as much detail as needed. + validations: + required: true diff --git a/.github/pull_request_template.yaml b/.github/pull_request_template.yaml new file mode 100644 index 0000000..5ce31d9 --- /dev/null +++ b/.github/pull_request_template.yaml @@ -0,0 +1,32 @@ +Applicable spec: + +### Overview + + + +### Rationale + + + +### Juju Events Changes + + + +### Module Changes + + + +### Library Changes + + + +### Checklist + +- [ ] The [charm style guide](https://juju.is/docs/sdk/styleguide) was applied +- [ ] The [contributing guide](https://github.com/canonical/is-charms-contributing-guide) was applied +- [ ] The changes are compliant with [ISD054 - Managing Charm Complexity](https://discourse.charmhub.io/t/specification-isd014-managing-charm-complexity/11619) +- [ ] The documentation is generated using `src-docs` +- [ ] The documentation for charmhub is updated +- [ ] The PR is tagged with appropriate label (`urgent`, `trivial`, `complex`) + + diff --git a/.github/workflows/auto_update_libs.yaml b/.github/workflows/auto_update_libs.yaml new file mode 100644 index 0000000..02b7b20 --- /dev/null +++ b/.github/workflows/auto_update_libs.yaml @@ -0,0 +1,10 @@ +name: Auto-update charm libraries + +on: + schedule: + - cron: "0 1 * * *" + +jobs: + auto-update-libs: + uses: canonical/operator-workflows/.github/workflows/auto_update_charm_libs.yaml@main + secrets: inherit diff --git a/.github/workflows/bot_pr_approval.yaml b/.github/workflows/bot_pr_approval.yaml new file mode 100644 index 0000000..f284fd7 --- /dev/null +++ b/.github/workflows/bot_pr_approval.yaml @@ -0,0 +1,10 @@ +name: Provide approval for bot PRs + +on: + pull_request: + +jobs: + bot_pr_approval: + uses: canonical/operator-workflows/.github/workflows/bot_pr_approval.yaml@main + secrets: inherit + diff --git a/.github/workflows/comment.yaml b/.github/workflows/comment.yaml new file mode 100644 index 0000000..26ac226 --- /dev/null +++ b/.github/workflows/comment.yaml @@ -0,0 +1,12 @@ +name: Comment on the pull request + +on: + workflow_run: + workflows: ["Tests"] + types: + - completed + +jobs: + comment-on-pr: + uses: canonical/operator-workflows/.github/workflows/comment.yaml@main + secrets: inherit diff --git a/.github/workflows/integration_test.yaml b/.github/workflows/integration_test.yaml new file mode 100644 index 0000000..2f4fe63 --- /dev/null +++ b/.github/workflows/integration_test.yaml @@ -0,0 +1,22 @@ +name: Integration tests + +on: + pull_request: + +jobs: + integration-tests: + uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main + secrets: inherit + with: + load-test-enabled: false + load-test-run-args: "-e LOAD_TEST_HOST=localhost" + zap-before-command: "curl -H \"Host: indico.local\" http://localhost/bootstrap --data-raw 'csrf_token=00000000-0000-0000-0000-000000000000&first_name=admin&last_name=admin&email=admin%40admin.com&username=admin&password=lunarlobster&confirm_password=lunarlobster&affiliation=Canonical'" + zap-enabled: true + zap-cmd-options: '-T 60 -z "-addoninstall jython" --hook "/zap/wrk/tests/zap/hook.py"' + zap-target: localhost + zap-target-port: 80 + zap-rules-file-name: "zap_rules.tsv" + trivy-fs-enabled: true + trivy-image-config: "trivy.yaml" + self-hosted-runner: true + self-hosted-runner-label: "edge" diff --git a/.github/workflows/issues.yaml b/.github/workflows/issues.yaml new file mode 100644 index 0000000..138fe82 --- /dev/null +++ b/.github/workflows/issues.yaml @@ -0,0 +1,11 @@ +name: Sync issues to Jira + +on: + issues: + # available via github.event.action + types: [opened, reopened, closed] + +jobs: + issues-to-jira: + uses: canonical/operator-workflows/.github/workflows/jira.yaml@main + secrets: inherit diff --git a/.github/workflows/load_test.yaml b/.github/workflows/load_test.yaml new file mode 100644 index 0000000..de6f27f --- /dev/null +++ b/.github/workflows/load_test.yaml @@ -0,0 +1,13 @@ +name: Load tests + +on: + schedule: + - cron: "0 12 * * 0" + +jobs: + load-tests: + uses: canonical/operator-workflows/.github/workflows/integration_test.yaml@main + with: + load-test-enabled: true + load-test-run-args: "-e LOAD_TEST_HOST=localhost" + secrets: inherit diff --git a/.github/workflows/promote_charm.yaml b/.github/workflows/promote_charm.yaml new file mode 100644 index 0000000..66649de --- /dev/null +++ b/.github/workflows/promote_charm.yaml @@ -0,0 +1,26 @@ +name: Promote charm + +on: + workflow_dispatch: + inputs: + origin-channel: + type: choice + description: 'Origin Channel' + options: + - latest/edge + destination-channel: + type: choice + description: 'Destination Channel' + options: + - latest/stable + secrets: + CHARMHUB_TOKEN: + required: true + +jobs: + promote-charm: + uses: canonical/operator-workflows/.github/workflows/promote_charm.yaml@main + with: + origin-channel: ${{ github.event.inputs.origin-channel }} + destination-channel: ${{ github.event.inputs.destination-channel }} + secrets: inherit diff --git a/.github/workflows/publish_charm.yaml b/.github/workflows/publish_charm.yaml new file mode 100644 index 0000000..e14e332 --- /dev/null +++ b/.github/workflows/publish_charm.yaml @@ -0,0 +1,14 @@ +name: Publish to edge + +on: + workflow_dispatch: + push: + branches: + - main + +jobs: + publish-to-edge: + uses: canonical/operator-workflows/.github/workflows/publish_charm.yaml@main + secrets: inherit + with: + channel: latest/edge diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..bd1426c --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,12 @@ +name: Tests + +on: + pull_request: + +jobs: + unit-tests: + uses: canonical/operator-workflows/.github/workflows/test.yaml@main + secrets: inherit + with: + self-hosted-runner: true + self-hosted-runner-label: "edge" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8461f41 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +venv/ +build/ +*.charm +.tox/ +.coverage +__pycache__/ +*.py[cod] +.idea +.vscode +.mypy_cache +*.egg-info/ +*/*.rock diff --git a/.jujuignore b/.jujuignore new file mode 100644 index 0000000..65f4410 --- /dev/null +++ b/.jujuignore @@ -0,0 +1,4 @@ +/venv +*.py[cod] +*.charm +/.github diff --git a/.licenserc.yaml b/.licenserc.yaml new file mode 100644 index 0000000..ef7164e --- /dev/null +++ b/.licenserc.yaml @@ -0,0 +1,23 @@ +header: + license: + spdx-id: Apache-2.0 + copyright-owner: Canonical Ltd. + content: | + Copyright [year] [owner] + See LICENSE file for licensing details. + paths: + - '**' + paths-ignore: + - '.github/**' + - '**/*.json' + - '**/*.md' + - '**/*.txt' + - '.jujuignore' + - '.gitignore' + - '.licenserc.yaml' + - 'CODEOWNERS' + - 'LICENSE' + - 'trivy.yaml' + - 'pyproject.toml' + - 'zap_rules.tsv' + comment: on-failure diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..e3e8c01 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @canonical/is-charms \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b7e36eb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,110 @@ + + +# Contributing + +## Overview + +This document explains the processes and practices recommended for contributing enhancements to the charm. + +- Generally, before developing enhancements to this charm, you should consider [opening an issue + ](link to issues page) explaining your use case. +- If you would like to chat with us about your use-cases or proposed implementation, you can reach + us at [Canonical Matrix public channel](https://matrix.to/#/#charmhub-charmdev:ubuntu.com) + or [Discourse](https://discourse.charmhub.io/). +- Familiarising yourself with the [Charmed Operator Framework](https://juju.is/docs/sdk) library + will help you a lot when working on new features or bug fixes. +- All enhancements require review before being merged. Code review typically examines + - code quality + - test coverage + - user experience for Juju operators of this charm. +- Please help us out in ensuring easy to review branches by rebasing your pull request branch onto the `main` branch. This + also avoids merge commits and creates a linear Git commit history. +- Please generate src documentation for every commit. See the section below for more details. + +## Developing + +To make contributions to this charm, you'll need a working [development setup](https://juju.is/docs/sdk/dev-setup). + +The code for this charm can be downloaded as follows: + +``` +git clone https://github.com/canonical/ +``` + +You can use the environments created by `tox` for development: + +```shell +tox --notest -e unit +source .tox/unit/bin/activate +``` + + + +### Testing + +This project uses `tox` for managing test environments. There are some pre-configured environments +that can be used for linting and formatting code when you're preparing contributions to the charm: + +* `tox`: Runs all of the basic checks (`lint`, `unit`, `static`, and `coverage-report`). +* `tox -e fmt`: Runs formatting using `black` and `isort`. +* `tox -e lint`: Runs a range of static code analysis to check the code. +* `tox -e static`: Runs other checks such as `bandit` for security issues. +* `tox -e unit`: Runs the unit tests. +* `tox -e integration`: Runs the integration tests. + +### Generating src docs for every commit + +Run the following command: + +```bash +echo -e "tox -e src-docs\ngit add src-docs\n" >> .git/hooks/pre-commit +chmod +x .git/hooks/pre-commit +``` + +### Building the charm + +Build the charm in this git repository using: + +```shell +charmcraft pack +``` + + + +### Deploying + + + +```bash +# Create a model +juju add-model charm-dev +# Enable DEBUG logging +juju model-config logging-config="=INFO;unit=DEBUG" +# Deploy the charm (assuming you're on amd64) +juju deploy ./.charm +``` + +## Canonical Contributor Agreement + +Canonical welcomes contributions to the charm. Please check out our [contributor agreement](https://ubuntu.com/legal/contributors) if you're interested in contributing to the solution. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c4a371b --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Canonical Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2ecb087 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ + + +# is-charms-template + + +Describe your charm in 1-2 sentences. Include the software that the charm deploys (if applicable), and the substrate (VM/K8s). + +Like any Juju charm, this charm supports one-line deployment, configuration, integration, scaling, and more. For Charmed {Name}, this includes: +* list or summary of app-specific features + +For information about how to deploy, integrate, and manage this charm, see the Official [is-charms-template Documentation](external link). + +## Get started + + + + +### (Optional) Set up + + +### (Optional) Deploy + + +### Basic operations + + + + +## (Optional) Integrations + + +## Learn more +* [Read more]() +* [Developer documentation]() +* [Official webpage]() +* [Troubleshooting]() + +## Project and community +* [Issues]() +* [Contributing]() +* [Matrix]() +* [Launchpad]() + +## (Optional) Licensing and trademark + diff --git a/charmcraft.yaml b/charmcraft.yaml new file mode 100644 index 0000000..df6fdcb --- /dev/null +++ b/charmcraft.yaml @@ -0,0 +1,13 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. +# This file configures Charmcraft. +# See https://juju.is/docs/sdk/charmcraft-config for guidance. + +type: charm +bases: + - build-on: + - name: ubuntu + channel: "22.04" + run-on: + - name: ubuntu + channel: "22.04" diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..dd4dcdd --- /dev/null +++ b/config.yaml @@ -0,0 +1,16 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. +# This file defines charm config options, and populates the Configure tab on Charmhub. +# If your charm does not require configuration options, delete this file entirely. +# +# See https://juju.is/docs/config for guidance. + +options: + # An example config option to customise the log level of the workload + log-level: + description: | + Configures the log level of gunicorn. + + Acceptable values are: "info", "debug", "warning", "error" and "critical" + default: "info" + type: string diff --git a/docs-template/explanation/charm-architecture.md b/docs-template/explanation/charm-architecture.md new file mode 100644 index 0000000..c672a83 --- /dev/null +++ b/docs-template/explanation/charm-architecture.md @@ -0,0 +1,104 @@ +# Charm architecture + +Add overview material here: +1) What kind of application is it? What kind of software does it use? +2) Describe Pebble services. +3) Include an architecture diagram. + + + +## OCI images + +We use [Rockcraft](https://canonical-rockcraft.readthedocs-hosted.com/en/latest/) to build OCI Images for . +The images are defined in [ rock](link to rock). +They are published to [Charmhub](https://charmhub.io/), the official repository of charms. + +> See more: [How to publish your charm on Charmhub](https://juju.is/docs/sdk/publishing) + +## (Optional) Containers + +Configuration files for the containers can be found in the respective directories that define the rocks. + + + +## Metrics + + + + +## Juju events + +For this charm, the following Juju events are observed: + + + +> See more in the Juju docs: [Event](https://juju.is/docs/sdk/event) + +## Charm code overview + +The `src/charm.py` is the default entry point for a charm and has the Python class which inherits from CharmBase. CharmBase is the base class +from which all Charms are formed, defined by [Ops](https://juju.is/docs/sdk/ops) (Python framework for developing charms). + +> See more in the Juju docs: [Charm](https://juju.is/docs/sdk/constructs#heading--charm) + +The `__init__` method guarantees that the charm observes all events relevant to its operation and handles them. + +Take, for example, when a configuration is changed by using the CLI. + +1. User runs the configuration command: +```bash +juju config +``` +2. A `config-changed` event is emitted. +3. In the `__init__` method is defined how to handle this event like this: +```python +self.framework.observe(self.on.config_changed, self._on_config_changed) +``` +4. The method `_on_config_changed`, for its turn, will take the necessary actions such as waiting for all the relations to be ready and then configuring the containers. diff --git a/docs-template/how-to/contribute.md b/docs-template/how-to/contribute.md new file mode 100644 index 0000000..3680c83 --- /dev/null +++ b/docs-template/how-to/contribute.md @@ -0,0 +1,5 @@ +# How to contribute + + + +See the [contributing guide](https://github.com/canonical/is-charms-template-repo/blob/main/CONTRIBUTING.md) on GitHub. diff --git a/docs-template/index.md b/docs-template/index.md new file mode 100644 index 0000000..1585808 --- /dev/null +++ b/docs-template/index.md @@ -0,0 +1,58 @@ +# Operator + + + +A [Juju](https://juju.is/) [charm](https://juju.is/docs/olm/charmed-operators) deploying and managing on +Kubernetes. + + + +Like any Juju charm, this charm supports one-line deployment, configuration, integration, scaling, and more. +For , this includes: +* list or summary of app-specific features + +The charm allows for deployment on many different Kubernetes platforms, from [MicroK8s](https://microk8s.io/) to +[Charmed Kubernetes](https://ubuntu.com/kubernetes) to public cloud Kubernetes offerings. + + + +This charm will make operating simple and straightforward for DevOps or SRE teams through Juju's clean interface. + +## In this documentation + +| | | +|--|--| +| [Tutorials](link to tutorial)
Get started - a hands-on introduction to using the charm for new users
| [How-to guides](link to how-to guide)
Step-by-step guides covering key operations and common tasks | +| [Reference](link to reference)
Technical information - specifications, APIs, architecture | [Explanation](link to explanation)
Concepts - discussion and clarification of key topics | + +## Contributing to this documentation + +Documentation is an important part of this project, and we take the same open-source approach to the documentation as +the code. As such, we welcome community contributions, suggestions and constructive feedback on our documentation. +Our documentation is hosted on the [Charmhub forum](https://discourse.charmhub.io/) +to enable easy collaboration. Please use the "Help us improve this documentation" links on each documentation page to +either directly change something you see that's wrong, ask a question or make a suggestion about a potential change via +the comments section. + +If there's a particular area of documentation that you'd like to see that's missing, please +[file a bug](link to issues page). + +## Project and community + +The Operator is a member of the Ubuntu family. It's an open-source project that warmly welcomes community +projects, contributions, suggestions, fixes, and constructive feedback. + +- [Code of conduct](https://ubuntu.com/community/code-of-conduct) +- [Get support](https://discourse.charmhub.io/) +- [Join our online chat](https://matrix.to/#/#charmhub-charmdev:ubuntu.com) +- [Contribute](link to Contribute page) + +Thinking about using the Operator for your next project? +[Get in touch](https://matrix.to/#/#charmhub-charmdev:ubuntu.com)! + +# Contents + +1. [Tutorial](link to tutorial) +1. [How-to](link to how-to) +1. [Reference](link to reference) +1. [Explanation](link to explanation) diff --git a/docs-template/reference/actions.md b/docs-template/reference/actions.md new file mode 100644 index 0000000..140b95a --- /dev/null +++ b/docs-template/reference/actions.md @@ -0,0 +1,5 @@ +# Actions + +See [Actions](link to actions page). + +> Read more about actions in the Juju docs: [Action](https://juju.is/docs/juju/action) diff --git a/docs-template/reference/configurations.md b/docs-template/reference/configurations.md new file mode 100644 index 0000000..3f391be --- /dev/null +++ b/docs-template/reference/configurations.md @@ -0,0 +1,5 @@ +# Configurations + +See [Configurations](link to configurations page). + +> Read more about configurations in the Juju docs: [Configuration](https://juju.is/docs/juju/configuration) diff --git a/docs-template/reference/integrations.md b/docs-template/reference/integrations.md new file mode 100644 index 0000000..0d4d824 --- /dev/null +++ b/docs-template/reference/integrations.md @@ -0,0 +1,16 @@ +# Integrations + + + +### Integration example + +_Interface_: +_Supported charms_: + +Description here. + +Example integrate command: + +``` +juju integrate : +``` diff --git a/generate-src-docs.sh b/generate-src-docs.sh new file mode 100644 index 0000000..d13066a --- /dev/null +++ b/generate-src-docs.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. + +lazydocs --no-watermark --output-path src-docs src/* diff --git a/metadata.yaml b/metadata.yaml new file mode 100644 index 0000000..19585a7 --- /dev/null +++ b/metadata.yaml @@ -0,0 +1,50 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. +# This file populates the Overview on Charmhub. +# See https://juju.is/docs/sdk/metadata-reference for a checklist and guidance. + +# The charm package name, no spaces (required) +# See https://juju.is/docs/sdk/naming#heading--naming-charms for guidance. +name: is-charms-template + +# The following metadata are human-readable and will be published prominently on Charmhub. + +# (Recommended) +display-name: Charm Template + +# (Required) +summary: A very short one-line summary of the charm. +docs: https://discourse.charmhub.io +issues: https://github.com/canonical/is-charms-template-repo/issues +maintainers: + - https://launchpad.net/~canonical-is-devops +source: https://github.com/canonical/is-charms-template-repo + +description: | + A single sentence that says what the charm is, concisely and memorably. + + A paragraph of one to three short sentences, that describe what the charm does. + + A third paragraph that explains what need the charm meets. + + Finally, a paragraph that describes whom the charm is useful for. + +# The containers and resources metadata apply to Kubernetes charms only. +# Remove them if not required. + +# Your workload’s containers. +containers: + httpbin: + resource: httpbin-image + +# This field populates the Resources tab on Charmhub. +resources: + # An OCI image resource for each container listed above. + # You may remove this if your charm will run without a workload sidecar container. + httpbin-image: + type: oci-image + description: OCI image for httpbin + # The upstream-source field is ignored by Juju. It is included here as a reference + # so the integration testing suite knows which image to deploy during testing. This field + # is also used by the 'canonical/charming-actions' Github action for automated releasing. + upstream-source: kennethreitz/httpbin diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0fce3f3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,79 @@ +[tool.bandit] +exclude_dirs = ["/venv/"] +[tool.bandit.assert_used] +skips = ["*/*test.py", "*/test_*.py", "*tests/*.py"] + +# Testing tools configuration +[tool.coverage.run] +branch = true + +# Formatting tools configuration +[tool.black] +line-length = 99 +target-version = ["py38"] + +[tool.coverage.report] +show_missing = true + +# Linting tools configuration +[tool.flake8] +max-line-length = 99 +max-doc-length = 99 +max-complexity = 10 +exclude = [".git", "__pycache__", ".tox", "build", "dist", "*.egg_info", "venv"] +select = ["E", "W", "F", "C", "N", "R", "D", "H"] +# Ignore W503, E501 because using black creates errors with this +# Ignore D107 Missing docstring in __init__ +ignore = ["W503", "E501", "D107"] +# D100, D101, D102, D103: Ignore missing docstrings in tests +per-file-ignores = ["tests/*:D100,D101,D102,D103,D104,D205,D212,D415"] +docstring-convention = "google" + +[tool.isort] +line_length = 99 +profile = "black" + +[tool.mypy] +check_untyped_defs = true +disallow_untyped_defs = true +explicit_package_bases = true +ignore_missing_imports = true +namespace_packages = true + +[[tool.mypy.overrides]] +disallow_untyped_defs = false +module = "tests.*" + +[tool.pylint] +disable = "wrong-import-order" + +[tool.pytest.ini_options] +minversion = "6.0" +log_cli_level = "INFO" + +# Linting tools configuration +[tool.ruff] +line-length = 99 +select = ["E", "W", "F", "C", "N", "D", "I001"] +extend-ignore = [ + "D203", + "D204", + "D213", + "D215", + "D400", + "D404", + "D406", + "D407", + "D408", + "D409", + "D413", +] +ignore = ["E501", "D107"] +extend-exclude = ["__pycache__", "*.egg_info"] +per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]} + +[tool.ruff.mccabe] +max-complexity = 10 + +[tool.codespell] +skip = "build,lib,venv,icon.svg,.tox,.git,.mypy_cache,.ruff_cache,.coverage" diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..e6c587e --- /dev/null +++ b/renovate.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base" + ], + "regexManagers": [ + { + "fileMatch": ["(^|/)rockcraft.yaml$"], + "description": "Update base image references", + "matchStringsStrategy": "any", + "matchStrings": ["# renovate: build-base:\\s+(?[^:]*):(?[^\\s@]*)(@(?sha256:[0-9a-f]*))?", + "# renovate: base:\\s+(?[^:]*):(?[^\\s@]*)(@(?sha256:[0-9a-f]*))?"], + "datasourceTemplate": "docker", + "versioningTemplate": "ubuntu" + } + ], + "packageRules": [ + { + "enabled": true, + "matchDatasources": [ + "docker" + ], + "pinDigests": true + }, + { + "matchFiles": ["rockcraft.yaml"], + "matchUpdateTypes": ["major", "minor", "patch"], + "enabled": false + } + ] +} + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..aaa16b1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +ops >= 2.2.0 diff --git a/src/charm.py b/src/charm.py new file mode 100755 index 0000000..6f01414 --- /dev/null +++ b/src/charm.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 + +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. + +# Learn more at: https://juju.is/docs/sdk + +"""Charm the service. + +Refer to the following post for a quick-start guide that will help you +develop a new k8s charm using the Operator Framework: + +https://discourse.charmhub.io/t/4208 +""" + +import logging +import typing + +import ops +from ops import pebble + +# Log messages can be retrieved using juju debug-log +logger = logging.getLogger(__name__) + +VALID_LOG_LEVELS = ["info", "debug", "warning", "error", "critical"] + + +class IsCharmsTemplateCharm(ops.CharmBase): + """Charm the service.""" + + def __init__(self, *args: typing.Any): + """Construct. + + Args: + args: Arguments passed to the CharmBase parent constructor. + """ + super().__init__(*args) + self.framework.observe(self.on.httpbin_pebble_ready, self._on_httpbin_pebble_ready) + self.framework.observe(self.on.config_changed, self._on_config_changed) + + def _on_httpbin_pebble_ready(self, event: ops.PebbleReadyEvent) -> None: + """Define and start a workload using the Pebble API. + + Change this example to suit your needs. You'll need to specify the right entrypoint and + environment configuration for your specific workload. + + Learn more about interacting with Pebble at at https://juju.is/docs/sdk/pebble. + + Args: + event: event triggering the handler. + """ + # Get a reference the container attribute on the PebbleReadyEvent + container = event.workload + # Add initial Pebble config layer using the Pebble API + container.add_layer("httpbin", self._pebble_layer, combine=True) + # Make Pebble reevaluate its plan, ensuring any services are started if enabled. + container.replan() + # Learn more about statuses in the SDK docs: + # https://juju.is/docs/sdk/constructs#heading--statuses + self.unit.status = ops.ActiveStatus() + + def _on_config_changed(self, event: ops.ConfigChangedEvent) -> None: + """Handle changed configuration. + + Change this example to suit your needs. If you don't need to handle config, you can remove + this method. + + Learn more about config at https://juju.is/docs/sdk/config + + Args: + event: event triggering the handler. + """ + # Fetch the new config value + log_level = str(self.model.config["log-level"]).lower() + + # Do some validation of the configuration option + if log_level in VALID_LOG_LEVELS: + # The config is good, so update the configuration of the workload + container = self.unit.get_container("httpbin") + # Verify that we can connect to the Pebble API in the workload container + if container.can_connect(): + # Push an updated layer with the new config + container.add_layer("httpbin", self._pebble_layer, combine=True) + container.replan() + + logger.debug("Log level for gunicorn changed to '%s'", log_level) + self.unit.status = ops.ActiveStatus() + else: + # We were unable to connect to the Pebble API, so we defer this event + event.defer() + self.unit.status = ops.WaitingStatus("waiting for Pebble API") + else: + # In this case, the config option is bad, so block the charm and notify the operator. + self.unit.status = ops.BlockedStatus("invalid log level: '{log_level}'") + + @property + def _pebble_layer(self) -> pebble.LayerDict: + """Return a dictionary representing a Pebble layer.""" + return { + "summary": "httpbin layer", + "description": "pebble config layer for httpbin", + "services": { + "httpbin": { + "override": "replace", + "summary": "httpbin", + "command": "gunicorn -b 0.0.0.0:80 httpbin:app -k gevent", + "startup": "enabled", + "environment": { + "GUNICORN_CMD_ARGS": f"--log-level {self.model.config['log-level']}" + }, + } + }, + } + + +if __name__ == "__main__": # pragma: nocover + ops.main.main(IsCharmsTemplateCharm) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..ad7716b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,13 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. + +"""Fixtures for charm tests.""" + + +def pytest_addoption(parser): + """Parse additional pytest options. + + Args: + parser: Pytest parser. + """ + parser.addoption("--charm-file", action="store") diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..e3979c0 --- /dev/null +++ b/tests/integration/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py new file mode 100644 index 0000000..f212ec1 --- /dev/null +++ b/tests/integration/test_charm.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. + +"""Integration tests.""" + +import asyncio +import logging +from pathlib import Path + +import pytest +import yaml +from pytest_operator.plugin import OpsTest + +logger = logging.getLogger(__name__) + +METADATA = yaml.safe_load(Path("./metadata.yaml").read_text(encoding="utf-8")) +APP_NAME = METADATA["name"] + + +@pytest.mark.abort_on_fail +async def test_build_and_deploy(ops_test: OpsTest, pytestconfig: pytest.Config): + """Deploy the charm together with related charms. + + Assert on the unit status before any relations/configurations take place. + """ + # Deploy the charm and wait for active/idle status + charm = pytestconfig.getoption("--charm-file") + resources = {"httpbin-image": METADATA["resources"]["httpbin-image"]["upstream-source"]} + assert ops_test.model + await asyncio.gather( + ops_test.model.deploy( + f"./{charm}", resources=resources, application_name=APP_NAME, series="jammy" + ), + ops_test.model.wait_for_idle( + apps=[APP_NAME], status="active", raise_on_blocked=True, timeout=1000 + ), + ) diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..e3979c0 --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/tests/unit/test_base.py b/tests/unit/test_base.py new file mode 100644 index 0000000..c1ce697 --- /dev/null +++ b/tests/unit/test_base.py @@ -0,0 +1,75 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. + +# Learn more about testing at: https://juju.is/docs/sdk/testing + +# pylint: disable=duplicate-code,missing-function-docstring +"""Unit tests.""" + +import unittest + +import ops +import ops.testing + +from charm import IsCharmsTemplateCharm + + +class TestCharm(unittest.TestCase): + """Test class.""" + + def setUp(self): + """Set up the testing environment.""" + self.harness = ops.testing.Harness(IsCharmsTemplateCharm) + self.addCleanup(self.harness.cleanup) + self.harness.begin() + + def test_httpbin_pebble_ready(self): + # Expected plan after Pebble ready with default config + expected_plan = { + "services": { + "httpbin": { + "override": "replace", + "summary": "httpbin", + "command": "gunicorn -b 0.0.0.0:80 httpbin:app -k gevent", + "startup": "enabled", + "environment": {"GUNICORN_CMD_ARGS": "--log-level info"}, + } + }, + } + # Simulate the container coming up and emission of pebble-ready event + self.harness.container_pebble_ready("httpbin") + # Get the plan now we've run PebbleReady + updated_plan = self.harness.get_container_pebble_plan("httpbin").to_dict() + # Check we've got the plan we expected + self.assertEqual(expected_plan, updated_plan) + # Check the service was started + service = self.harness.model.unit.get_container("httpbin").get_service("httpbin") + self.assertTrue(service.is_running()) + # Ensure we set an ActiveStatus with no message + self.assertEqual(self.harness.model.unit.status, ops.ActiveStatus()) + + def test_config_changed_valid_can_connect(self): + # Ensure the simulated Pebble API is reachable + self.harness.set_can_connect("httpbin", True) + # Trigger a config-changed event with an updated value + self.harness.update_config({"log-level": "debug"}) + # Get the plan now we've run PebbleReady + updated_plan = self.harness.get_container_pebble_plan("httpbin").to_dict() + updated_env = updated_plan["services"]["httpbin"]["environment"] + # Check the config change was effective + self.assertEqual(updated_env, {"GUNICORN_CMD_ARGS": "--log-level debug"}) + self.assertEqual(self.harness.model.unit.status, ops.ActiveStatus()) + + def test_config_changed_valid_cannot_connect(self): + # Trigger a config-changed event with an updated value + self.harness.update_config({"log-level": "debug"}) + # Check the charm is in WaitingStatus + self.assertIsInstance(self.harness.model.unit.status, ops.WaitingStatus) + + def test_config_changed_invalid(self): + # Ensure the simulated Pebble API is reachable + self.harness.set_can_connect("httpbin", True) + # Trigger a config-changed event with an updated value + self.harness.update_config({"log-level": "foobar"}) + # Check the charm is in BlockedStatus + self.assertIsInstance(self.harness.model.unit.status, ops.BlockedStatus) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..202340f --- /dev/null +++ b/tox.ini @@ -0,0 +1,122 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. + +[tox] +skipsdist=True +skip_missing_interpreters = True +envlist = lint, unit, static, coverage-report + +[vars] +src_path = {toxinidir}/src/ +tst_path = {toxinidir}/tests/ +;lib_path = {toxinidir}/lib/charms/operator_name_with_underscores +all_path = {[vars]src_path} {[vars]tst_path} + +[testenv] +setenv = + PYTHONPATH = {toxinidir}:{toxinidir}/lib:{[vars]src_path} + PYTHONBREAKPOINT=ipdb.set_trace + PY_COLORS=1 +passenv = + PYTHONPATH + CHARM_BUILD_DIR + MODEL_SETTINGS + +[testenv:fmt] +description = Apply coding style standards to code +deps = + black + isort +commands = + isort {[vars]all_path} + black {[vars]all_path} + +[testenv:lint] +description = Check code against coding style standards +deps = + black + codespell + flake8<6.0.0 + flake8-builtins + flake8-copyright<6.0.0 + flake8-docstrings>=1.6.0 + flake8-docstrings-complete>=1.0.3 + flake8-test-docs>=1.0 + isort + mypy + pep8-naming + pydocstyle>=2.10 + pylint + pyproject-flake8<6.0.0 + pytest + pytest-asyncio + pytest-operator + requests + types-PyYAML + types-requests + -r{toxinidir}/requirements.txt +commands = + pydocstyle {[vars]src_path} + # uncomment the following line if this charm owns a lib + # codespell {[vars]lib_path} + codespell {toxinidir} --skip {toxinidir}/.git --skip {toxinidir}/.tox \ + --skip {toxinidir}/build --skip {toxinidir}/lib --skip {toxinidir}/venv \ + --skip {toxinidir}/.mypy_cache --skip {toxinidir}/icon.svg + # pflake8 wrapper supports config from pyproject.toml + pflake8 {[vars]all_path} --ignore=W503 + isort --check-only --diff {[vars]all_path} + black --check --diff {[vars]all_path} + mypy {[vars]all_path} + pylint {[vars]all_path} + +[testenv:unit] +description = Run unit tests +deps = + coverage[toml] + pytest + -r{toxinidir}/requirements.txt +commands = + coverage run --source={[vars]src_path} \ + -m pytest --ignore={[vars]tst_path}integration -v --tb native -s {posargs} + coverage report + +[testenv:coverage-report] +description = Create test coverage report +deps = + coverage[toml] + pytest + -r{toxinidir}/requirements.txt +commands = + coverage report + +[testenv:static] +description = Run static analysis tests +deps = + bandit[toml] + -r{toxinidir}/requirements.txt +commands = + bandit -c {toxinidir}/pyproject.toml -r {[vars]src_path} {[vars]tst_path} + +[testenv:integration] +description = Run integration tests +deps = + # Last compatible version with Juju 2.9 + juju==3.0.4 + pytest + pytest-asyncio + pytest-operator + -r{toxinidir}/requirements.txt +commands = + pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} + +[testenv:src-docs] +allowlist_externals=sh +setenv = + PYTHONPATH = {toxinidir}:{toxinidir}/lib:{[vars]src_path} +description = Generate documentation for src +deps = + lazydocs + -r{toxinidir}/requirements.txt +commands = + ; can't run lazydocs directly due to needing to run it on src/* which produces an invocation error in tox + sh generate-src-docs.sh diff --git a/trivy.yaml b/trivy.yaml new file mode 100644 index 0000000..c895d69 --- /dev/null +++ b/trivy.yaml @@ -0,0 +1,3 @@ +timeout: 20m +scan: + offline-scan: true diff --git a/zap_rules.tsv b/zap_rules.tsv new file mode 100644 index 0000000..e69de29