Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JUJU-4553] Update README.md to simplify and better align with docs #1052

Merged
merged 2 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 123 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,94 +1,157 @@
# The ops library
# The `ops` library

<!-- The text below is also at the top of ops/__init__.py. Keep in sync! -->

tmihoc marked this conversation as resolved.
Show resolved Hide resolved
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
tmihoc marked this conversation as resolved.
Show resolved Hide resolved
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/).
tmihoc marked this conversation as resolved.
Show resolved Hide resolved

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.

tmihoc marked this conversation as resolved.
Show resolved Hide resolved
Charms written with ops support Kubernetes using Juju's "sidecar charm"
pattern, as well as charms that deploy to Linux-based machines and containers.
||||
|-|-|- |
tmihoc marked this conversation as resolved.
Show resolved Hide resolved
|| [Juju](https://juju.is/docs/juju) | Learn how to quickly deploy, integrate, and manage charms on any cloud with Juju. <br> _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. <br> _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&nbsp;SDK](https://juju.is/docs/sdk) | Write your own charm! <br> _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:

tmihoc marked this conversation as resolved.
Show resolved Hide resolved
To learn more about Juju, visit https://juju.is/docs/olm.
### Set up

tmihoc marked this conversation as resolved.
Show resolved Hide resolved
> 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). <br> 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:
benhoyt marked this conversation as resolved.
Show resolved Hide resolved

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
benhoyt marked this conversation as resolved.
Show resolved Hide resolved
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:
tmihoc marked this conversation as resolved.
Show resolved Hide resolved

```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

tmihoc marked this conversation as resolved.
Show resolved Hide resolved
> 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.
2 changes: 0 additions & 2 deletions ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading