diff --git a/README.md b/README.md index 5376825ca..fdc2de606 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # The Operator Framework -The Operator Framework provides a simple, lightweight, and powerful way of encapsulating operational experience in code. +The Operator Framework provides a simple, lightweight, and powerful way of +writing Juju charms, the best way to encapsulate operational experience in code. The framework will help you to: @@ -11,79 +12,62 @@ The framework will help you to: ## Getting Started -The following overall structure for your charm directory is recommended: +Charms written using the operator framework are just Python code. The intention +is for it to feel very natural for somebody used to coding in Python, and +reasonably easy to pick up for somebody who might be a domain expert but not +necessarily a pythonista themselves. -``` -. -├── config.yaml -├── metadata.yaml -├── mod/ -├── lib/ -│ └── ops -> ../mod/operator/ops -├── src/ -│ └── charm.py -└── hooks/ - ├── install -> ../src/charm.py - └── start -> ../src/charm.py # for k8s charms per below -``` +The dependencies of the operator framework are kept as minimal as possible; +currently that's Python 3.5 or greater, and `PyYAML` (both are included by +default in Ubuntu's cloud images from 16.04 on). -The `mod/` directory should contain the operator framework dependency as a git -submodule: + +## A Quick Introduction -``` -ln -s ../mod/operator/ops lib/ops -``` +Operator framework charms are just Python code. The entry point to your charm is +a particular Python file. It could be anything that makes sense to your project, +but let's assume this is `src/charm.py`. This file must be executable (and it +must have the appropriate shebang line). -Other dependencies included as git submodules can be added in the `mod/` -directory and symlinked into `lib/` as well. +This file must be symlinked from `dispatch` (supported by Juju 2.8; otherwise +from `hooks/install` and `hooks/upgrade-charm`, and `hooks/start` if it is a k8s +charm). -You can sync subsequent changes from the framework and other submodule -dependencies by running: +You also need the usual `metadata.yaml` and `config.yaml` files, and any Python +dependencies (maybe using some kind of virtualenv). In other words, your project +might look like this: ``` -git submodule update +. +├── config.yaml +├── metadata.yaml +├── env/ +│ └── # ... your Python dependencies, including the operator framework itself +├── src/ +│ └── charm.py +└── dispatch -> src/charm.py ``` -Those cloning and checking out the source for your charm for the first time -will need to run: - -``` -git submodule update --init -``` +> 🛈 once `charmcraft build` works the layout will get simpler! -Your `src/charm.py` is the entry point for your charm logic. It should be set -to executable and use Python 3.6 or greater. At a minimum, it needs to define -a subclass of `CharmBase` and pass that into the framework's `main` function: +`src/charm.py` here is the entry point to your charm code. At a minimum, it +needs to define a subclass of `CharmBase` and pass that into the framework's +`main` function: ```python -import sys -sys.path.append('lib') # noqa: E402 - from ops.charm import CharmBase from ops.main import main - -class MyCharm(CharmBase): - pass - - -if __name__ == "__main__": - main(MyCharm) -``` - -This charm does nothing, because the `MyCharm` class passed to the operator -framework's `main` function is empty. Functionality can be added to the charm -by instructing it to observe particular Juju events when the `MyCharm` object -is initialized. For example, - -```python class MyCharm(CharmBase): def __init__(self, *args): super().__init__(*args) @@ -91,50 +75,67 @@ class MyCharm(CharmBase): def on_start(self, event): # Handle the start event here. + +if __name__ == "__main__": + main(MyCharm) ``` -Every standard event in Juju may be observed that way, and you can also easily -define your own events in your custom types. +That should be enough for you to be able to run -> The second argument to `observe` can be either the handler as a bound -> method, or the observer itself if the handler is a method of the observer -> that follows the conventional naming pattern. That is, in this case, we -> could have called just `self.framework.observe(self.on.start, self)`. +``` +$ juju deploy . +``` -The `hooks/` directory must contain a symlink to your `src/charm.py` entry -point so that Juju can call it. You only need to set up the `hooks/install` link -(`hooks/start` for K8s charms, until [lp#1854635](https://bugs.launchpad.net/juju/+bug/1854635) -is resolved), and the framework will create all others at runtime. +Happy charming! -Once your charm is ready, upload it to the charm store and deploy it as -normal with: +## Testing your charms -``` -# Replace ${CHARM} with the name of the charm. -charm push . cs:~${USER}/${CHARM} -# Replace ${VERSION} with the version created by `charm push`. -charm release cs:~${USER}/${CHARM}-${VERSION} -charm grant cs:~${USER}/${CHARM}-${VERSION} everyone -# And now deploy your charm. -juju deploy cs:~${USER}/$CHARM +The operator framework provides a testing harness, so that you can test that +your charm does the right thing when presented with different scenarios, without +having to have a full deployment to do so. `pydoc3 ops.testing` has the details +for that, including this example: + +```python +harness = Harness(MyCharm) +# Do initial setup here +relation_id = harness.add_relation('db', 'postgresql') +# Now instantiate the charm to see events as the model changes +harness.begin() +harness.add_relation_unit(relation_id, 'postgresql/0') +harness.update_relation_data(relation_id, 'postgresql/0', {'key': 'val'}) +# Check that charm has properly handled the relation_joined event for postgresql/0 +self.assertEqual(harness.charm. ...) ``` -Alternatively, to deploy directly from local disk, run: +## Talk to us -``` -juju deploy . -``` +If you need help, have ideas, or would just like to chat with us, reach out on +IRC: we're in [#smooth-operator] on freenode (or try the [webchat]). -# Operator Framework development +We also pay attention to Juju's [discourse], but currently we don't actively +post there outside of our little corner of the [docs]; most discussion at this +stage is on IRC. -If you want to work in the framework *itself* you will need Python >= 3.5 and -the dependencies declared in requirements-dev.txt installed in your system. Or you -can use a virtualenv: +[webchat]: https://webchat.freenode.net/#smooth-operator +[#smooth-operator]: irc://chat.freenode.net/%23smooth-operator +[discourse]: https://discourse.juju.is/c/charming +[docs]: https://discourse.juju.is/c/docs/operator-framework -``` -virtualenv --python=python3 env -source env/bin/activate -pip install -r requirements-dev.txt -``` +## Operator Framework development + +If you want to work in the framework *itself* you will need Python >= 3.5 and +the dependencies declared in `requirements-dev.txt` installed in your system. +Or you can use a virtualenv: + + virtualenv --python=python3 env + source env/bin/activate + pip install -r requirements-dev.txt Then you can try `./run_tests`, it should all go green. + +If you want to build the documentation you'll need the requirements from +`docs/requirements.txt`, or in your virtualenv + + pip install -r docs/requirements.txt + +and then you can run `./build_docs`.