diff --git a/README.md b/README.md index b8cd27a96..1909e03d4 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,157 @@ -# The ops library +# The `ops` library - -The ops library is a Python framework ([`available on PyPI`](https://pypi.org/project/ops/)) for developing -and testing [Juju](https://juju.is/) charms in a consistent way, using standard Python constructs -to allow for clean, maintainable, and reusable code. +The `ops` library is a Python framework for developing and testing Kubernetes and machine [charms](https://juju.is/docs/sdk/charmed-operators). 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://juju.is/). -A charm is an operator -- business logic encapsulated in a reusable software -package that automates every aspect of an application's life. +> - `ops` is [available on PyPI](https://pypi.org/project/ops/). +> - The latest version of `ops` requires Python 3.8 or above. -Charms written with ops support Kubernetes using Juju's "sidecar charm" -pattern, as well as charms that deploy to Linux-based machines and containers. +|||| +|-|-|- | +|| [Juju](https://juju.is/docs/juju) | Learn how to quickly deploy, integrate, and manage charms on any cloud with Juju.
_It's as simple as `juju deploy foo`, `juju integrate foo bar`, and so on -- on any cloud._ | +|||| +|| [Charmhub](https://charmhub.io/) | Sample our existing charms on Charmhub.
_A charm can be a cluster ([OpenStack](https://charmhub.io/openstack-base), [Kubernetes](https://charmhub.io/charmed-kubernetes)), a data platform ([PostgreSQL](https://charmhub.io/postgresql-k8s), [MongoDB](https://charmhub.io/mongodb), etc.), an observability stack ([Canonical Observability Stack](https://charmhub.io/cos-lite)), an MLOps solution ([Kubeflow](https://charmhub.io/kubeflow)), and so much more._ | +|||| +|:point_right:| [Charm SDK](https://juju.is/docs/sdk) | Write your own charm!
_Juju is written in Go, but our SDK supports easy charm development in Python._ | -Charms should do one thing and do it well. Each charm drives a single -application and can be integrated with other charms to deliver a complex -system. A charm handles creating the application in addition to scaling, -configuration, optimisation, networking, service mesh, observability, and other -day-2 operations specific to the application. +## Give it a try -The ops library is part of the Charm SDK (the other part being Charmcraft). -Full developer documentation for the Charm SDK is available at -https://juju.is/docs/sdk. +Let's use `ops` to build a Kubernetes charm: -To learn more about Juju, visit https://juju.is/docs/olm. +### Set up +> See [Charm SDK | Set up an Ubuntu `charm-dev` VM with Multipass](https://juju.is/docs/sdk/dev-setup#heading--automatic-set-up-an-ubuntu-charm-dev-vm-with-multipass).
Choose the MicroK8s track. -## Pure Python -The framework provides a standardised Python object model that represents the -application graph, as well as an event-handling mechanism for distributed -system coordination and communication. +### Write your charm -The latest version of ops requires Python 3.8 or above. +On your Multipass VM, create a charm directory and use Charmcraft to initialise your charm file structure: -Juju itself is written in Go for efficient concurrency even in large -deployments. Charms can be written in any language, however, we recommend using -Python with this framework to make development easier and more standardised. -All new charms at Canonical are written using it. +```shell-script +mkdir ops-example +cd ops-example +charmcraft init +``` +This has created a standard charm directory structure. Poke around. + +Things to note: +- The `metadata.yaml` file shows that what we have is an example charm called `ops-example`, which uses an OCI image resource `httpbin` from `kennethreitz/httpbin`. -## Getting started +- The `requirements.txt` file lists the version of `ops` to use. -A package of operator code is called a charmed operator or simply "charm". -You'll use [charmcraft](https://juju.is/docs/sdk/install-charmcraft) to -register your charm name and publish it when you are ready. You can follow one -of our [charming tutorials](https://juju.is/docs/sdk/tutorials) to get started -writing your first charm. +- 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: +```python +import ops + +class OpsExampleCharm(ops.CharmBase): + """Charm the service.""" + + def __init__(self, *args): + 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): + """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. + """ + # 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() +``` -## Testing your charms +> See more: [`ops.PebbleReadyEvent`](https://ops.readthedocs.io/en/latest/index.html#ops.PebbleReadyEvent) -The framework provides a testing harness, so you can ensure that your charm -does the right thing in different scenarios, without having to create -a full deployment. Our [API documentation](https://ops.readthedocs.io/en/latest/#module-ops.testing) -has the details, including this example: +- The `tests/unit/test_charm.py` file imports `ops.testing` and uses it to set up a testing harness: ```python +import ops.testing + class TestCharm(unittest.TestCase): - def test_foo(self): - harness = Harness(MyCharm) - self.addCleanup(harness.cleanup) # always clean up after ourselves + def setUp(self): + self.harness = ops.testing.Harness(OpsExampleCharm) + 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()) +``` - # Instantiate the charm and trigger events that Juju would on startup - harness.begin_with_initial_hooks() +> See more: [`ops.testing.Harness`](https://ops.readthedocs.io/en/latest/#ops.testing.Harness) - # Update charm config and trigger config-changed - harness.update_config({'log_level': 'warn'}) - # Check that charm properly handled config-changed, for example, - # the charm added the correct Pebble layer - plan = harness.get_container_pebble_plan('prometheus') - self.assertIn('--log.level=warn', plan.services['prometheus'].command) +Explore further, start editing the files, or skip ahead and pack the charm: + +```shell-script +charmcraft pack +``` + +If 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: + +```shell-script +juju deploy ./ops-example_ubuntu-22.04-amd64.charm --resource httpbin-image=kennethreitz/httpbin ``` +Congratulations, you’ve just built your first Kubernetes charm using `ops`! + +### Clean up + +> See [Charm SDK | Clean up](https://juju.is/docs/sdk/dev-setup#heading--automatic-set-up-an-ubuntu-charm-dev-vm-with-multipass). -## Talk to us +## Next steps -If you need help, have ideas, or would just like to chat with us, reach out on -the Charmhub [Mattermost]. +### Learn more +- Read our [user documentation](https://juju.is/docs/sdk/ops), which includes other guides showing `ops` in action +- Dig into the [`ops` API reference](https://ops.readthedocs.io/en/latest/) -We also pay attention to the Charmhub [Discourse]. +### Chat with us -And of course you can deep dive into the [API reference]. +Read our [Code of conduct](https://ubuntu.com/community/code-of-conduct) and: +- Join our chat: [Mattermost](https://chat.charmhub.io/charmhub/channels/ops) +- Join our forum: [Discourse](https://discourse.charmhub.io/) + +### File an issue -[Discourse]: https://discourse.charmhub.io/ -[API reference]: https://ops.readthedocs.io/ -[Mattermost]: https://chat.charmhub.io/charmhub/channels/charm-dev +- Report an `ops` bug using [GitHub issues](https://github.com/canonical/operator/issues) +- Raise a general https://juju.is/docs documentation issue on [GitHub | juju/docs](https://github.com/juju/docs) +### Make your mark -## Development of the framework +- Read our [documentation contributor guidelines](https://discourse.charmhub.io/t/documentation-guidelines-for-contributors/1245) and help improve a doc +- Read our [codebase contributor guidelines](HACKING.md) and help improve the codebase +- Write a charm and publish it on [Charmhub](https://charmhub.io/) -See [HACKING.md](HACKING.md) for details on dev environments, testing, and so -on. diff --git a/ops/__init__.py b/ops/__init__.py index bb900bf1f..17b035244 100644 --- a/ops/__init__.py +++ b/ops/__init__.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# NOTE: The text below is also at the top of README.md. Keep in sync! - """The ops library: a Python framework for writing Juju charms. The ops library is a Python framework (`available on PyPI`_) for developing