StackStorm: Hello World

Author: Carl Montanari
Date: 2019-07-19

Introduction

Writing scripts or playbooks is only one piece of the greater puzzle that is automation. When do these scripts get executed, what arguments get passed, how can they be made consumable by non-developer team members, and best of all--how can they automatically be executed in response to some event?

Recently we began addressing some of these challenges internally--specifically, how can we abstract away, or simplify, some of our script argument passing, and how can we automate execution of scripts in response to events.

There are many ways to tackle these problems, but in our case we elected to use Stackstorm.

Stackstorm, or st2, is an open-source "event-driven" automation platform (note, StackStorm also has a commercial offering). Being "event-driven" means that given some event, Stackstorm can be configured to execute a corresponding task(s) in response. Similarly, if you don't want to wait for events, you can schedule tasks to run (cron), or simply manually execute them in the StackStorm web UI or CLI.

One last item that is pretty cool about st2—even the open-source flavor comes with a UI that allows users to execute tasks (actions in Stackstorm terminology). Not only can users execute tasks via the UI, but administrators can define what arguments are required and the data type of those arguments (bool, string, list, etc.). This could allows scripts to be used by a broader set of team members.

Getting Going with Stackstorm

Stackstorm has a great "Getting Started" guide that covers installation on multiple systems. For purposes of this blog post, we'll use the "one-line install"—which is literally one line!

To get started it really is one line (of course, substitute whichever password you'd like):

$ curl -sSL https://stackstorm.com/packages/install.sh | bash -s -- --user=st2admin --password='password'

Once the deployment completes you should be able to log into the Stackstorm web interface or simply SSH/console to your server.

Before we go about making Stackstorm respond to events, let's execute something very simple.

Hello World!

Actions are one of the primary components of Stackstorm. Actions are things that Stackstorm can execute. Perhaps the simplest action is the "core.echo" action which literally echos a message to the local shell. Other actions include remote shell commands, python scripts, and more complicated, but powerful, work-flows (ActionChain, Orquestra, Mistral).

For now, let's just use the "core.echo" action to echo a simple message and see st2 in action (see what I did there? /bad puns). Note, action names are the "pack" they belong to, then "dot", then name of the action. So in this case ("core.echo"), "core" is the pack, and "echo" is the action. A "pack" is just a collection of actions (and other things which we'll investigate later).

To execute the action from the terminal on the st2 host, we can use the st2 command:

$ st2 action execute core.echo message="Hello, World"

To get the results, execute:
 st2 execution get 5d250d7468770c33f5e9178f

To view output in real-time, execute:
 st2 execution tail 5d250d7468770c33f5e9178f

Stackstorm will then tell us how we can get the results, or see the real-time execution of this action. Since this action likely finished almost instantly, we won't see any real-time execution, but we can certainly check the result of our action.

$ st2 execution get 5d250d7468770c33f5e9178f
id: 5d250d7468770c33f5e9178f
status: succeeded (1s elapsed)
parameters:
  message: Hello, World
result:
  failed: false
  return_code: 0
  stderr: ''
  stdout: Hello, World
  succeeded: true

Right on, we can see that we had a "0" for the return_code which indicates a success. Additionally, we see that the status is "succeeded" and our execution took 1s (almost certainly rounded up!).

Note that if you view this action in the web UI you can see that there are fields to supply the required argument of "message" as well as some other optional arguments (such as the working directory for the task to execute in). In this example this doesn't seem like a huge win, but for more complicated tasks with multiple arguments it can be nice to have this in a simple UI for more junior members or for people less comfortable with executing scripts via command line.

With that simple example out of the way, let's build a simple custom pack with a custom action inside of it.

Our First Pack

As we've seen, a "pack" is just a collection of actions. A pack can be installed via the Stackstorm "Exchange", pointing to a Git URL, or by pointing to a directory which is a git repository with the appropriate data inside. For this example, let's create a simple directory on the st2 host where we will build our pack.

Our pack folder will contain a subdirectory for "actions" and a YAML file that describes it, "pack.yaml":

$ cd ~/
$ mkdir super_cool_python
$ mkdir super_cool_python/actions
$ touch super_cool_python/pack.yaml

The data in the "pack.yaml" file will contain a "ref" which is the internal st2 name, a "name" which is a user friendly name, a description, a version, an author, an email, and a list of python versions which the pack supports.

---
ref: super_cool_python
name : super_cool_python
description : A super cool python pack
keywords:
  - python3
version: 0.0.1
python_versions:
  - "3"
author : carl
email : person@internet.com

With that out of the way, we can now create our first action. An action, just like the pack, is defined in a YAML file. We can take a look at the YAML file for "core.echo" to get an idea of what is required:

---
description: Action that executes the Linux echo command on the localhost.
runner_type: "local-shell-cmd"
enabled: true
entry_point: ''
name: echo
parameters:
  message:
    description: The message that the command will echo.
    type: string
    required: true
  cmd:
    description: Arbitrary Linux command to be executed on the local host.
    required: true
    type: string
    default: 'echo ""'
    immutable: true
  kwarg_op:
    immutable: true
  sudo:
    default: false
    immutable: true
  sudo_password:
    immutable: true

One of the most important components of an action is its action "type". In this example, we can see that this is a "local-shell-cmd" action. As stated earlier, there are other types such as "remote-shell-cmd" and (more interesting to us) "python-script".

In the action YAML file that we are creating, we'll want to use the "python-script" type. This also implies that we need to set our "entry_point" to point at some python script. This script will be executed when the action is run. For now, let's name our entry script, "hello_st2.py".

Our action YAML file will be called "hello_st2.yaml" and it will live in the "actions" directory that we created previously.

---
name: hello_st2
enabled: True
description: say hello to stackstorm
runner_type: python-script
entry_point: hello_st2.py

In general you will probably be adding additional data to this YAML file. This data will outline which parameters are accepted, which parameters are required, what order should the parameters be in, et cetera. For now, we will ignore these additional items.

Now we need to create our Python script. Understandably this isn't *quite* as simple as a Python script that just prints "hello st2". Our script needs to be formatted in a way that is understandable to Stackstorm. A very simple example of this is the "python3_test" action that you can find here

In this script, we can see that a class is created which inherits from the "st2common" Action class. Within this class, a "run()" method exists and this run() method returns a bool and a string. The run() method is ultimately what Stackstorm executes. The bool and string combination indicate the status of the execution and a result message.

Armed with this knowledge let's create a simple script that says hello to Stackstorm:

from st2common.runners.base_action import Action

class HelloStackStorm(Action):
    def run(self):
        print("Hello Stackstorm, want to be friends?")
        return True, "Let's be friends!"

With that out of the way, let's initialize our pack as a git repository and add all our files into it.

$ cd ~/super_cool_python/
$ git init
$ git add actions/
$ git add pack.yaml
$ git commit -m "creating a st2 pack"

And then using the st2 commands, let's install our pack:

$ st2 pack install ~/super_cool_python/

If everything goes well, then we should see that our pack has been successfully installed. We can check this by looking at the list of installed packs via the CLI or in the Web UI:

$ st2 pack list | grep super_cool_python
| super_cool_python | super_cool_python | A super cool python pack | 0.0.1 | carl |

Similarly we can check to see if our action was created:

$ st2 action list | grep super_cool_python
| super_cool_python.hello_st2 | super_cool_python | say hello to stackstorm |

And, finally, we can execute our action:

$ st2 action execute super_cool_python.hello_st2

To get the results, execute:
 st2 execution get 5d25179b68770c33f5e91797

To view output in real-time, execute:
         st2 execution tail 5d25179b68770c33f5e91797

You might also be interested in: