Command-line interface

Opslib provides a powerful command-line interface to operate on the infrastructure. Besides the basic deployment operations, it allows direct interaction with components, and defining custom commands.

Argument structure

Unlike most shell commands, which follow an <executable> <command> <target> structure, opslib expects <executable> <target> <command>, because the available commands are different depending on the target.

The first argument is the dotted name of the target component. Therefore, can run commands on any component in the stack.

stack.py
from opslib.components import Component, Stack

def get_stack():
    stack = Stack()
    stack.foo = Component()
    stack.foo.bar = Component()

With the stack defined above, we can run the id command (which simply prints the repr() of the target) on the bar component:

$ opslib foo.bar id
<Component foo.bar>

We can also specify - as a target. It will select the root element, the stack itself:

$ opslib - id
<Stack __root__>

Most of the examples below will target the whole stack, but any command can be run on any component in the stack, so you can for example do a partial deploy.

Checking differences

Before applying actual changes to infrastructure, it’s a good idea to check what is going to happen. We can run diff to see.

$ opslib - diff
[...]
app.app_py.action AnsibleAction [changed]
--- /opt/prj/opslib/examples/compose/target/opslib-examples-compose/app.py
+++ /opt/prj/opslib/examples/compose/target/opslib-examples-compose/app.py
@@ -5,4 +5,4 @@

 @app.route("/")
 def hello_world():
-    return "<p>Hello, World!</p>"
+    return "<p>Hello, World! Changes are afoot.</p>"

app.compose_file.action AnsibleAction [ok]
[...]
9 ok
1 changed
<class 'opslib.ansible.AnsibleAction'>: 1

Note

Some components, e.g. File, cache the fact that they have been deployed successfully. If the remote file is changed, opslib won’t pick up the change, unless you run refresh.

If, however, the component props change, opslib will pick up the difference, and will update the remote file.

Refreshing local state

Sometimes the infrastructure changes and opslib needs to update its state. This is done with the refresh command:

opslib - refresh

Deploying

The deploy command visits each component in sequence, depth-first, and performs some specific action on the infrastructure. The action depends on the type of component; it may be creating a directory or writing a file, or spinning up a VM.

Components are visited in the order they are attached to their parent. If the application of a component fails, the process stops.

opslib - deploy

Defining custom commands

Sometimes it helps to provide special commands on a component. For example, a component representing a Docker Compose project might define a compose command that executes docker compose in the context of the project. Or a component implementing a systemd unit might define a systemctl command that runs systemctl with that unit as first argument.

When the CLI for a component is invoked, opslib prepares a click.Group object, that implements the component’s CLI. It adds the default commands for deploy, diff, etc. It then calls add_commands() with a single argument, the click.Group object, so you can attach additional commands. Refer to the Click documentation for details on implementing commands.

import click
from opslib.components import Component

class MyComponent(Component):
    def add_commands(self, cli):
        @cli.command()
        @click.argument("message")
        def speak(message):
            click.echo(click.style(message, fg="red"))