{"id":13501309,"url":"https://github.com/canonical/operator","last_synced_at":"2026-04-02T18:51:55.384Z","repository":{"id":37458190,"uuid":"212098176","full_name":"canonical/operator","owner":"canonical","description":"Pure Python framework for writing Juju charms.","archived":false,"fork":false,"pushed_at":"2026-03-25T23:02:42.000Z","size":8188,"stargazers_count":260,"open_issues_count":62,"forks_count":129,"subscribers_count":15,"default_branch":"main","last_synced_at":"2026-03-26T21:44:06.610Z","etag":null,"topics":["charms","juju","python"],"latest_commit_sha":null,"homepage":"https://documentation.ubuntu.com/ops","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/canonical.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2019-10-01T13:06:11.000Z","updated_at":"2026-03-25T23:02:47.000Z","dependencies_parsed_at":"2023-10-11T02:50:47.283Z","dependency_job_id":"e00155ad-4dff-4056-bdab-6a81c4c1da97","html_url":"https://github.com/canonical/operator","commit_stats":{"total_commits":517,"total_committers":68,"mean_commits":7.602941176470588,"dds":0.8375241779497099,"last_synced_commit":"790473901224e99ef0e66087f12e6b662c144a69"},"previous_names":[],"tags_count":82,"template":false,"template_full_name":null,"purl":"pkg:github/canonical/operator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canonical%2Foperator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canonical%2Foperator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canonical%2Foperator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canonical%2Foperator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/canonical","download_url":"https://codeload.github.com/canonical/operator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canonical%2Foperator/sbom","scorecard":{"id":264626,"data":{"date":"2025-08-11","repo":{"name":"github.com/canonical/operator","commit":"59856580d90a8f2b705d19ecd755b12d0badda06"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":8.2,"checks":[{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: SECURITY.md:1","Info: Found linked content: SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: SECURITY.md:1","Info: Found text in security policy: SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Maintained","score":10,"reason":"30 commit(s) and 11 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":10,"reason":"all changesets reviewed","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":10,"reason":"GitHub workflow tokens follow principle of least privilege","details":["Info: jobLevel 'contents' permission set to 'read': .github/workflows/publish.yaml:17","Info: found token with 'none' permissions: .github/workflows/charmcraft-pack.yaml:1","Info: found token with 'none' permissions: .github/workflows/db-charm-tests.yaml:1","Info: found token with 'none' permissions: .github/workflows/example-charm-tests.yaml:1","Info: found token with 'none' permissions: .github/workflows/framework-tests.yaml:1","Info: found token with 'none' permissions: .github/workflows/hello-charm-tests.yaml:1","Info: found token with 'none' permissions: .github/workflows/integration.yaml:1","Info: found token with 'none' permissions: .github/workflows/observability-charm-tests.yaml:1","Info: found token with 'none' permissions: .github/workflows/publish.yaml:1","Info: found token with 'none' permissions: .github/workflows/published-charms-tests.yaml:1","Info: found token with 'none' permissions: .github/workflows/sbom-secscan.yaml:1","Info: found token with 'none' permissions: .github/workflows/smoke.yaml:1","Info: topLevel 'contents' permission set to 'read': .github/workflows/test-publish.yaml:8","Info: found token with 'none' permissions: .github/workflows/tiobe.yaml:1","Info: found token with 'none' permissions: .github/workflows/update-charm-tests.yaml:1","Info: topLevel 'pull-requests' permission set to 'read': .github/workflows/validate-pr-title.yaml:10","Info: found token with 'none' permissions: .github/workflows/zizmor.yaml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":2,"reason":"badge detected: InProgress","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":3,"reason":"dependency not pinned by hash detected -- score normalized to 3","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/charmcraft-pack.yaml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/charmcraft-pack.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/charmcraft-pack.yaml:66: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/charmcraft-pack.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/db-charm-tests.yaml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/db-charm-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/db-charm-tests.yaml:36: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/db-charm-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/example-charm-tests.yaml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/example-charm-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/example-charm-tests.yaml:33: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/example-charm-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/framework-tests.yaml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/framework-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/framework-tests.yaml:32: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/framework-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/framework-tests.yaml:53: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/framework-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/framework-tests.yaml:76: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/framework-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/framework-tests.yaml:84: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/framework-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/framework-tests.yaml:108: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/framework-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/framework-tests.yaml:113: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/framework-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/hello-charm-tests.yaml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/hello-charm-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/hello-charm-tests.yaml:30: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/hello-charm-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/integration.yaml:32: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/integration.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/observability-charm-tests.yaml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/observability-charm-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish.yaml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/publish.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish.yaml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/publish.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/publish.yaml:30: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/publish.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/published-charms-tests.yaml:68: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/published-charms-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/sbom-secscan.yaml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/sbom-secscan.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/sbom-secscan.yaml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/sbom-secscan.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/sbom-secscan.yaml:57: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/sbom-secscan.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/smoke.yaml:28: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/smoke.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-publish.yaml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/test-publish.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test-publish.yaml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/test-publish.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tiobe.yaml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/tiobe.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/update-charm-tests.yaml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/update-charm-tests.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/zizmor.yaml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/zizmor.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/zizmor.yaml:31: update your workflow using https://app.stepsecurity.io/secureworkflow/canonical/operator/zizmor.yaml/main?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/db-charm-tests.yaml:43","Warn: pipCommand not pinned by hash: .github/workflows/db-charm-tests.yaml:56","Warn: pipCommand not pinned by hash: .github/workflows/example-charm-tests.yaml:38","Warn: goCommand not pinned by hash: .github/workflows/framework-tests.yaml:92","Warn: pipCommand not pinned by hash: .github/workflows/framework-tests.yaml:123","Warn: pipCommand not pinned by hash: .github/workflows/hello-charm-tests.yaml:44","Warn: pipCommand not pinned by hash: .github/workflows/observability-charm-tests.yaml:37","Warn: pipCommand not pinned by hash: .github/workflows/published-charms-tests.yaml:75","Warn: pipCommand not pinned by hash: .github/workflows/published-charms-tests.yaml:110","Warn: pipCommand not pinned by hash: .github/workflows/published-charms-tests.yaml:159","Warn: pipCommand not pinned by hash: .github/workflows/tiobe.yaml:28","Info:   0 out of  29 GitHub-owned GitHubAction dependencies pinned","Info:  15 out of  17 third-party GitHubAction dependencies pinned","Info:   0 out of  10 pipCommand dependencies pinned","Info:   0 out of   1 goCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/publish.yaml:11"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Branch-Protection","score":4,"reason":"branch protection is not maximal on development and all release branches","details":["Info: 'allow deletion' disabled on branch 'main'","Info: 'allow deletion' disabled on branch '2.23-maintenance'","Info: 'allow deletion' disabled on branch '2.19-maintenance'","Info: 'allow deletion' disabled on branch '2.16-maintenance'","Info: 'force pushes' disabled on branch 'main'","Info: 'force pushes' disabled on branch '2.23-maintenance'","Info: 'force pushes' disabled on branch '2.19-maintenance'","Info: 'force pushes' disabled on branch '2.16-maintenance'","Warn: 'branch protection settings apply to administrators' is disabled on branch 'main'","Warn: 'branch protection settings apply to administrators' is disabled on branch '2.23-maintenance'","Warn: 'branch protection settings apply to administrators' is disabled on branch '2.19-maintenance'","Warn: 'branch protection settings apply to administrators' is disabled on branch '2.16-maintenance'","Warn: 'stale review dismissal' is disabled on branch 'main'","Warn: 'stale review dismissal' is disabled on branch '2.23-maintenance'","Warn: 'stale review dismissal' is disabled on branch '2.19-maintenance'","Warn: 'stale review dismissal' is disabled on branch '2.16-maintenance'","Info: required approving review count is 2 on branch 'main'","Info: required approving review count is 2 on branch '2.23-maintenance'","Info: required approving review count is 2 on branch '2.19-maintenance'","Info: required approving review count is 2 on branch '2.16-maintenance'","Warn: codeowners review is not required on branch 'main'","Warn: codeowners review is not required on branch '2.23-maintenance'","Warn: codeowners review is not required on branch '2.19-maintenance'","Warn: codeowners review is not required on branch '2.16-maintenance'","Warn: 'last push approval' is disabled on branch 'main'","Warn: 'last push approval' is disabled on branch '2.23-maintenance'","Warn: 'last push approval' is disabled on branch '2.19-maintenance'","Warn: 'last push approval' is disabled on branch '2.16-maintenance'","Warn: 'up-to-date branches' is disabled on branch 'main'","Info: status check found to merge onto on branch 'main'","Warn: no status checks found to merge onto branch '2.23-maintenance'","Warn: no status checks found to merge onto branch '2.19-maintenance'","Warn: no status checks found to merge onto branch '2.16-maintenance'","Info: PRs are required in order to make changes on branch 'main'","Info: PRs are required in order to make changes on branch '2.23-maintenance'","Info: PRs are required in order to make changes on branch '2.19-maintenance'","Info: PRs are required in order to make changes on branch '2.16-maintenance'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":10,"reason":"SAST tool is run on all commits","details":["Info: all commits (30) are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-17T11:40:05.394Z","repository_id":37458190,"created_at":"2025-08-17T11:40:05.394Z","updated_at":"2025-08-17T11:40:05.394Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31313495,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["charms","juju","python"],"created_at":"2024-07-31T22:01:32.621Z","updated_at":"2026-04-02T18:51:55.376Z","avatar_url":"https://github.com/canonical.png","language":"Python","funding_links":[],"categories":["Python","python"],"sub_categories":[],"readme":"# The `ops` library\n\n![CI Status](https://github.com/canonical/operator/actions/workflows/framework-tests.yaml/badge.svg)\n\nThe `ops` library is a Python framework for developing and testing Kubernetes and machine [charms](https://charmhub.io/). While charms can be written in any language, `ops` defines the latest standard, and charmers are encouraged to use Python with `ops` for all charms. The library is an official component of the Charm SDK, itself a part of [the Juju universe](https://canonical.com/juju).\n\n\u003e - `ops` is  [available on PyPI](https://pypi.org/project/ops/).\n\u003e - The latest version of `ops` requires Python 3.10 or above.\n\u003e - Read our [docs](https://documentation.ubuntu.com/ops/latest/) for tutorials, how-to guides, the library reference, and more.\n\n## Give it a try\n\nLet's use `ops` to build a Kubernetes charm:\n\n### Set up\n\n\u003e See [Juju | Set things up](https://documentation.ubuntu.com/juju/3.6/howto/manage-your-juju-deployment/set-up-your-juju-deployment/). \u003cbr\u003e Choose the automatic track and MicroK8s.\n\n\n### Write your charm\n\nOn your Multipass VM, create a charm directory and use Charmcraft to initialise your charm file structure:\n\n```shell-script\nmkdir ops-example\ncd ops-example\ncharmcraft init\n```\nThis has created a standard charm directory structure:\n\n```shell-script\n$ ls -R\n.:\nCONTRIBUTING.md  README.md        pyproject.toml    src    tox.ini\nLICENSE          charmcraft.yaml  requirements.txt  tests\n\n./src:\ncharm.py\n\n./tests:\nintegration  unit\n\n./tests/integration:\ntest_charm.py\n\n./tests/unit:\ntest_charm.py\n```\n\nThings to note:\n\n- The `charmcraft.yaml` file shows that what we have is an example charm called `ops-example`, which uses an OCI image resource `httpbin` from `kennethreitz/httpbin`.\n\n- The `requirements.txt` file lists the version of `ops` to use.\n\n- The `src/charm.py` file imports `ops` and uses `ops` constructs to create a charm class `OpsExampleCharm`, observe Juju events, and pair them to event handlers:\n\n```python\nimport ops\n\nclass OpsExampleCharm(ops.CharmBase):\n    \"\"\"Charm the service.\"\"\"\n\n    def __init__(self, *args):\n        super().__init__(*args)\n        self.framework.observe(self.on['httpbin'].pebble_ready, self._on_httpbin_pebble_ready)\n        self.framework.observe(self.on.config_changed, self._on_config_changed)\n\n    def _on_httpbin_pebble_ready(self, event: ops.PebbleReadyEvent):\n        \"\"\"Define and start a workload using the Pebble API.\n\n        Change this example to suit your needs. You'll need to specify the right entrypoint and\n        environment configuration for your specific workload.\n\n        Learn more about interacting with Pebble at\n            https://documentation.ubuntu.com/ops/latest/reference/pebble/\n        \"\"\"\n        # Get a reference the container attribute on the PebbleReadyEvent\n        container = event.workload\n        # Add initial Pebble config layer using the Pebble API\n        container.add_layer(\"httpbin\", self._pebble_layer, combine=True)\n        # Make Pebble reevaluate its plan, ensuring any services are started if enabled.\n        container.replan()\n        # Learn more about statuses at\n        # https://documentation.ubuntu.com/juju/3.6/reference/status/\n        self.unit.status = ops.ActiveStatus()\n```\n\n\u003e See more: [`ops.PebbleReadyEvent`](https://documentation.ubuntu.com/ops/latest/reference/ops/#ops.PebbleReadyEvent)\n\n- The `tests/unit/test_charm.py` file imports `ops.testing` and uses it to set up a unit test:\n\n```python\nimport ops\nfrom ops import testing\n\nfrom charm import OpsExampleCharm\n\n\ndef test_httpbin_pebble_ready():\n    # Arrange:\n    ctx = testing.Context(OpsExampleCharm)\n    container = testing.Container(\"httpbin\", can_connect=True)\n    state_in = testing.State(containers={container})\n\n    # Act:\n    state_out = ctx.run(ctx.on.pebble_ready(container), state_in)\n\n    # Assert:\n    updated_plan = state_out.get_container(container.name).plan\n    expected_plan = {\n        \"services\": {\n            \"httpbin\": {\n                \"override\": \"replace\",\n                \"summary\": \"httpbin\",\n                \"command\": \"gunicorn -b 0.0.0.0:80 httpbin:app -k gevent\",\n                \"startup\": \"enabled\",\n                \"environment\": {\"GUNICORN_CMD_ARGS\": \"--log-level info\"},\n            }\n        },\n    }\n    assert expected_plan == updated_plan\n    assert (\n        state_out.get_container(container.name).service_statuses[\"httpbin\"]\n        == ops.pebble.ServiceStatus.ACTIVE\n    )\n    assert state_out.unit_status == testing.ActiveStatus()\n```\n\n\u003e See more: [`ops.testing`](https://documentation.ubuntu.com/ops/latest/reference/ops-testing/)\n\n\nExplore further, start editing the files, or skip ahead and pack the charm:\n\n```shell-script\ncharmcraft pack\n```\n\nIf you didn't take any wrong turn or simply left the charm exactly as it was, this has created a file called `ops-example_ubuntu-22.04-amd64.charm` (the architecture bit may be different depending on your system's architecture). Use this name and the resource from the `metadata.yaml` to deploy your example charm to your local MicroK8s cloud:\n\n```shell-script\njuju deploy ./ops-example_ubuntu-22.04-amd64.charm --resource httpbin-image=kennethreitz/httpbin\n```\n\nCongratulations, you’ve just built your first Kubernetes charm using `ops`!\n\n### Clean up\n\n\u003e See [Juju | Tear things down](https://documentation.ubuntu.com/juju/3.6/howto/manage-your-juju-deployment/tear-down-your-juju-deployment-local-testing-and-development/). \u003cbr\u003e Choose the automatic track.\n\n## Next steps\n\n- Read the [docs](https://documentation.ubuntu.com/ops/latest/).\n- Read our [Code of conduct](https://ubuntu.com/community/code-of-conduct) and join our [chat](https://matrix.to/#/#charmhub-ops:ubuntu.com) and [forum](https://discourse.charmhub.io/) or [open an issue](https://github.com/canonical/operator/issues).\n- Read our [CONTRIBUTING guide](https://github.com/canonical/operator/blob/main/HACKING.md) and contribute!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcanonical%2Foperator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcanonical%2Foperator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcanonical%2Foperator/lists"}