runn

repository·main·Indexed 20 days ago

https://github.com/k1low/runn

A tool and Go package for executing operations based on defined YAML scenarios (runbooks). It supports multiple protocols including HTTP, gRPC, DB queries, SSH, and Chrome DevTools Protocol (CDP). runn can be used as a CLI tool for workflow automation and scenario-based testing, or integrated as a Go test helper package to execute runbooks against live servers and databases.

Tokens
33.4K
Snippets
112
Records
170
Agent score
70%

What's inside runn

  1. Configure runn scopes

    main

    Certain features in runn require explicit permission via scopes. You can specify these via the --scopes CLI flag, the RUNN_SCOPES environment variable, or the runn.Scopes() option in Go.

    Available Scopes:

    • read:parent: Required to read files above the current working directory.
    • read:remote: Required to read remote files.
    • run:exec: Required for using the Exec runner.

    CLI Usage:

    $ runn run path/to/**/*.yml --scopes read:parent,read:remote

    Environment Variable:

    $ env RUNN_SCOPES=read:parent,read:remote runn run path/to/**/*.yml

    Go Usage:

    o, err := runn.Load("path/to/**/*.yml", runn.Scopes(runn.AllowReadParent, runn.AllowReadRemote))

    Disabling a scope: Use the ! prefix (e.g., --scopes '!read:parent').

  2. How to identify specific steps within a runbook

    main

    To identify a specific step within a runbook (or a step within an included runbook), runn uses a URL query format appended to the Runbook ID.

    Steps are indexed starting from 0. If a runbook includes another runbook, you can target a specific step in the parent and a specific step in the child using multiple step keys.

    Format: [runbook ID]?step=[parent_step_index]&step=[child_step_index]

    • The only allowed key is step.
    • Multiple step keys are allowed and are processed in order.

    Example: If you want to target step 2 of the main runbook, which is an Include runner pointing to step 0 of an included runbook, the identifier would be: [runbook ID]?step=2&step=0

  3. Understand the difference between operator and operatorN

    main

    In runn, there are two primary entities responsible for executing runbooks:

    • runn.operator: Responsible for operating the execution of a single runbook.
    • runn.operatorN: Responsible for operating multiple runbooks (multiple operators) together.

    You can convert a single operator into an operatorN using the operator.toOperatorN method.

  4. How `defer:` steps behave in runn

    main

    The defer: true option allows you to define post-processing steps that run after the main execution flow of a runbook is complete. This is inspired by the defer keyword in Go.

    Key behaviors:

    • Execution Timing: Steps marked with defer: true are executed only after all non-deferred steps in the runbook (and any included runbooks) have finished.
    • Error Resilience: Deferred steps are always executed, even if an intermediate step in the main execution flow fails.
    • LIFO Order: If multiple steps are marked with defer: true, they are executed in Last-In, First-Out (LIFO) order (the last deferred step defined is the first one to run).
    • Include Integration: When an include: step contains deferred steps, those steps are added to the parent runbook's deferred execution sequence, respecting the LIFO order across the entire runbook hierarchy.
    # Example of deferred execution order
    steps:
      - desc: step 1
        test: true
      - desc: step 2
        defer: true
        test: true
      - desc: step 3
        defer: true
        test: true
      - desc: step 4
        test: true
    
    # Execution order will be:
    # 1. step 1
    # 2. step 4
    # 3. step 3 (deferred)
    # 4. step 2 (deferred)
  5. How runbook IDs are generated

    main

    Runbook IDs are generated using an algorithm that relies on the file path structure rather than absolute paths, ensuring IDs remain stable across different execution environments (as long as the directory layout of the runbooks remains the same).

    The Algorithm

    1. Path Reversal: The absolute path of the runbook is split into components and reversed (e.g., [root, path, to, books, a, a1.yml] becomes [a1.yml, a, books, to, path, root]).
    2. Collision Resolution: To ensure uniqueness, runn compares the reversed path components across all runbooks. If the first component (the filename) is not unique among all runbooks, it includes the second component, then the third, and so on, until every runbook has a unique identifier.
    3. Hashing: The resulting unique sequence of path components is encoded using SHA-1 to produce the final ID string.

    Key Characteristics

    • Stability: The ID does not change based on the execution path or the specific execution environment (unless the directory layout changes).
    • Dependency: The ID is determined based on the set of runbooks being executed at the same time. Adding or removing runbooks from the execution set may change the IDs.
  6. Understand the properties of a runbook

    main

    A runbook is the fundamental unit of execution in runn. When designing or writing runbooks, keep the following execution properties in mind:

    • Sequential Execution: Steps within a runbook are always executed sequentially. runn does not assume or support concurrent running of steps within a single runbook.
    • Lifecycle Management: When a runbook completes, all steps that have already started running will be stopped (for example, processes started via exec.background:).
    • Deferred Steps: Steps marked with the defer: keyword do not start immediately. Instead, their execution is delayed until the parent runbook has completed its primary execution flow.
  7. Use Loops and Retries in Steps

    main

    The loop: setting allows you to repeat a step or an entire runbook.

    Simple Loop

    Repeat a step a fixed number of times. Inside the loop, the variable {{ i }} represents the current index (starting at 0).

    Retry Mechanism

    Use the until: condition to implement retries. The loop will break as soon as the condition is met. If the loop finishes without meeting the condition, the step fails.

    Important: When using until:, do not use a test: assertion in the same step. The test: assertion runs on every iteration and may cause the step to fail before the retry logic can succeed.

    steps:
      # Simple loop
      multicartin:
        loop: 10
        req:
          /cart/in:
            post:
              body:
                product_id: "{{ i }}"
    
      # Retry logic
      waitingroom:
        loop:
          count: 10
          until: 'steps.waitingroom.res.status == "201"'
          minInterval: 500ms
          maxInterval: 10s
        req:
          /cart/in:
            post: {}
  8. Expand Objects and Convert to JSON Strings

    main

    Object Expansion

    When an object variable is used directly in headers: or body:, runn expands it as a map structure rather than a JSON string.

    JSON Serialization

    To force an object to be serialized into a JSON string (e.g., for a header value), wrap the template expression in single quotes.

    vars:
      auth_headers:
        X-Token: xxx
        X-Api-Key: yyy
      metadata:
        user: alice
        role: admin
    
    steps:
      - req:
          /api:
            get:
              # Expands as a map (multiple headers)
              headers: "{{vars.auth_headers}}"
              # Serializes to a JSON string
              headers:
                X-Metadata: "'{{vars.metadata}}'"
  9. Define a Runbook (Scenario File)

    main

    A runbook is a YAML file that defines a sequence of steps to execute. The steps: section can be defined as either a List (ordered) or a Map (named keys).

    List Format

    Steps are executed in order. Use index-based access to retrieve results from previous steps (e.g., {{ steps[0].rows[0].email }}).

    Map Format

    Steps are identified by unique keys. Use key-based access to retrieve results (e.g., {{ steps.find_user.rows[0].email }}).

    # List format
    steps:
      - db:
          query: SELECT * FROM users
      - req:
          /profile: 
            get:
              headers:
                id: "{{ steps[0].rows[0].id }}"
    
    # Map format
    steps:
      find_user:
        db:
          query: SELECT * FROM users
      get_profile:
        req:
          /profile:
            get:
              headers:
                id: "{{ steps.find_user.rows[0].id }}"
  10. Quickstart: Create scenarios from commands or access logs

    main

    You can quickly generate runbook YAML files using the runn new command.

    From a CLI command: Use runn new followed by --desc (description), --out (output file), and the command you want to wrap (e.g., curl or grpcurl). Adding the --and-run flag will execute the command immediately after creating the file.

    From an access log: Pipe an access log directly into runn new --out <filename>.yml to automatically generate a scenario based on the requests found in the log.

    # Create and run a scenario from a curl command
    runn new --and-run --desc 'httpbin.org GET' --out http.yml -- curl https://httpbin.org/json -H "accept: application/json"
    
    # Create a scenario from an access log
    cat access_log | runn new --out axslog.yml
  11. Capture runbook runs

    main

    To record and save the results of runbook executions, use the --capture flag or the runn.Capture option.

    Via CLI:

    $ runn run path/to/**/*.yml --capture path/to/dir

    Via Go API:

    opts := []runn.Option{
    	runn.T(t),
    	runn.Capture(capture.Runbook("path/to/dir")),
    }
    o, err := runn.Load("testdata/books/**/*.yml", opts...)
  12. Install runn

    main

    CLI Installation

    Homebrew (macOS/Linux):

    $ brew install k1LoW/tap/runn

    Go Install:

    $ go install github.com/k1LoW/runn/cmd/runn@latest

    Docker:

    $ docker container run -it --rm --name runn -v $PWD:/books ghcr.io/k1low/runn:latest list /books/*.yml

    Package Managers:

    • deb: Download .deb from releases and use dpkg -i.
    • RPM: Use yum install <url_to_rpm>.
    • apk: Download .apk from releases and use apk add.
    • aqua: aqua g -i k1LoW/runn

    Go Library Installation

    To use runn as a dependency in your Go projects:

    $ go get github.com/k1LoW/runn