Ignoring Mixin Errors and Idempotent Actions

Ignoring Mixin Errors and Idempotent Actions

January 25, 2022

Previously, if you needed to handle an error in a bundle, you had to switch from Porter’s declarative mixin syntax to a bash script. But not anymore! The exec mixin has just added support for determining if the error returned by a command is fatal, and should stop the bundle, or if the error can be ignored.

A common scenario for ignoring errors is during the install action. When an install fails halfway through, the user will try again by repeating the install command. If a resource was created during the first install, repeating a command that creates a resource could result in an error because the resource already exists. Some command-line tools handle this gracefully, while others return an error. The exec mixin now lets you decide if that error is fatal based on the command’s return code or output.

Let’s take a look at a few different ways that you can handle errors from the exec mixin.

Ignore All Errors

You can ignore all errors that are returned by a command, and continue executing the next step in the action. This can be useful in a couple scenarios such as debugging while writing a bundle, when a command always returns a non-zero exit code, or when you want to try calling a command and keep going regardless of whether it worked.

install:
  - exec:
      description: "This may not work but such is life"
      command: ./buy-me-coffee.sh
      ignoreError:
        all: true # Ignore any errors from this command

Ignore Exit Codes

Sometimes you get lucky, and the command that you are using has well-defined exit codes. For the following example, the behavior of thing responds with the exit code [2] when the resource already exists.

install:
  - exec:
      description: "Ensure thing exists"
      command: thing
      arguments:
        - create
      ignoreError:
        exitCodes: [2]

In this example, since you don’t care about the implied error as the desired outcome is the existence of resource, you configure porter to ignore the [2] exit code.

You can ignore multiple exit codes, and if any match, then the command’s error is ignored.

Ignore Output Containing a String

Usually we aren’t so lucky, and we have to scrape the contents of standard error to figure out what went wrong. Continuing our efforts to create idempotent resources, we can ignore the error when it contains “thing already exits”.

install:
  - exec:
      description: "Ensure thing exists"
      command: thing
      arguments:
        - create
      ignoreError:
        output:
          contains: ["thing already exists"]

Ignore Output Matching a Regular Expression

Finally, there are times when the error message is a bit more difficult to parse, so we fall back to our favorite hammer: regular expressions. In the example below, when we delete a thing that has already been deleted, “thing NAME not found” is printed to standard error.

Regular expressions being the tricky devils they are, I recommend using regex101.com to quickly test and iterate on your regular expression.

uninstall:
  - exec:
      description: "Make the thing go away"
      command: thing
      arguments:
        - remove
      ignoreError:
        output:
          regex: "thing (.*) not found"

Create Custom Idempotent Mixin Commands

Whenever possible, I encourage you to avoid the exec mixin and use a custom mixin for the tooling that you are automating. For example, if you are automating terraform, use the terraform mixin. Mixins are meant to adapt a tool to work well inside a bundle.

I used the new ignore errors capability of the exec mixin’s library to create a custom command for the az mixin. The group command allows you to declare a resource group, and the mixin will handle creating it if it doesn’t exist, and cleaning it up when the bundle is uninstalled.

install:
  - az:
      description: "Ensure my resource group exists"
      group:
        name: mygroup
        location: eastus2

uninstall:
  - az:
      description: "Remove my resource group"
      group:
        name: mygroup

These mixin commands are idempotent and handle errors automatically for you. This lets you focus on the resources you need, and spend less time figuring out how to automate a command-line tool to work in a way it wasn’t designed for.

Try it out

Bundle authors, try moving some of that custom error handling logic out of bash scripts and into your exec mixin calls. Mixin authors, take a look at how the Skeletor mixin template source uses the get.porter.sh/porter/pkg/exec/builder package to include error handling. You can quickly add the same error handling behavior to your mixin, or create a custom command that handles errors automatically by looking at the source for the az group command.

Give it a try and let us know how it works for you! If there is a mixin that you would like to use this new error handling with, let us know, and we can help make that happen more quickly.