Act is a task runner and supervisor with some great features like act name matching, subacts, etc. We use this in nosebit workspaces.

Act

Act is a task runner and supervisor tool written in Go which aims to provide the following features:

  • process supervision in a project level
  • allow tasks to be written as simple bash scripting if wanted
  • sub tasks which can be invoked with act run foo.bar where bar is a sub task of foo task (tasks are called acts here)
  • dynamically include tasks defined in other locations allowing spliting task definition
  • regex match task names
  • and much more :)

Instalation

@TODO : We need to compile binaries and have a nice way to install act like we have for volta https://volta.sh/.

Download Binary

Linux

wget -q -O - https://github.com/nosebit/act/releases/download/v1.3.0/act-1.3.0-linux-amd64.tar.gz | sudo tar -xzf - -C /usr/local/bin

MacOS

wget -q -O - https://github.com/nosebit/act/releases/download/v1.3.0/act-1.3.0-darwin-amd64.tar.gz | sudo tar -xzf - -C /usr/local/bin

From Source

First you need to have go >= 1.16 installed in your machine. Then after cloning this repo you can build act binary by doing:

cd /path/to/act/folder

GOROOT=/usr/local/bin go install ./...

Feel free to change GOROOT to whatever destination you want.

How to Use

After installing act we create an actfile.yml file in root dir of our project so we can describe the acts we can run like the following:

# actfile.yml
version: 1

acts:
  foo:
    desc: This is a simple hello world act example
    start:
      - echo "Hello foo"

and then we run the act with the following command:

act run foo

or shortly we can do just:

actr foo

If we need to specify a diferent actfile to be used we can do it like this:

act run -f=/path/to/actfile.yml foo

If we need to specify a more powerful command we can use shell scripting directly in start field like this:

# actfile.yml
version: 1

acts:
  build-deps:
    desc: This act going to build dependencies in a workspace and optionally clean everything before start.
    start: |
      echo "args=$@"
      yarn install --ignore-engines $@

Notice that acts can receive command line arguments which are being used in build-deps command via $@.

Running Scripts as Commands

If we don't want to "polute" the actfile with a lot of scripting like we did for build-deps we can provide a script file using the script field of a command like this:

# actfile.yml
version: 1

acts:
  build-deps:
    desc: This act going to build dependencies in a workspace and optionally clean everything before start.
    start:
      - script: /path/to/script.sh

By default Act going to use bash as it's default shell but you can customize the shell to use via shell field in actfile, act or command levels like this:

# actfile.yml
version: 1

acts:
  foo:
    start:
      - cmd: echo "hello"
        shell: sh
      - script: /path/to/script.sh
        shell: bash # default
  bar:
    shell: bash # default
    start:
      - echo "bar 1"
      - echo "bar 2"

Before Commands

If we need to run commands before any act executed we can do it like this:

# actfile.yml
version: 1

before-all:
  cmds:
    - echo "running before"
    - act: bar inline-arg-1 inline-arg-2
    - act: zoo
      args:
        - non-inline-arg-1
        - non-inline-arg-2

acts:
  foo:
    start: echo "im foo"
  bar:
    start: echo "im bar with args=$@"
  zoo:
    start: echo "im zoo with args=$@"

Commands Parallel Execution

By default Act going to run commands in sequence but if we want commands to be executed in parallel we can do like this:

# actfile.yml
version: 1

acts:
  foo:
    start:
      parallel: true
      cmds:
        - |
          echo "running cmd1"

          for i in {1..5}; do
            echo "cmd1 $i"; sleep 2
          done
        - |
          echo "running cmd2"

          for i in {1..5}; do
            echo "cmd2 $i"; sleep 2
          done

Act Name Matching

The act name we use in actfile.yml is actually a regex we going to match against the name use provide to act run command. That way if we have:

# actfile.yml
version: 1

acts:
  foo-.+:
    desc: This is a generic foo act.
    start:
      - echo "i'm $ACT_NAME"

we can call

act run foo-bar

and see i'm foo-bar printed in the screen.

Subacts

We can defined subacts like this:

# actfile.yml
version: 1

acts:
  foo:
    desc: Act with subacts.
    acts:
      bar:
        start:
          - echo "im bar subact of foo"

which we can run like this:

act run foo.bar

A specicial index subact named _ can be provided to match the parent act name like this:

# actfile.yml
version: 1

acts:
  foo:
    desc: Act with subacts and index subact.
    acts:
      _:
        start: echo "im foo"
      bar:
        start: echo "im bar subact of foo"

Now we can run act run foo to see im foo printde to the screen and act run foo.bar to see im bar subact of foo.

Including Acts

We can even include acts from another actfile as subacts in the following way:

# actfile.yml
version: 1

acts:
  foo:
    desc: This is an act that include subacts.
    include: another/actfile.yml
# another/actfile.yml
version: 1

acts:
  foo:
    desc: This is a sample act.
    start:
      - echo "im bar"

and then we can call

act run foo.bar

Inclusion and act name matching allow us to do some interesting things like this:

# actfile.yml
version: 1

acts:
  .+:
    desc: Scope acts from a sub directory.
    include: "{{.ActName}}/actfile.yml"

and then in a subdirectory called backend for example we can have:

# backend/acfile.yml
version: 1

acts:
  up:
    desc: Start backend service written in nodejs.
    start:
      - node index.js

This way we can start the backend from the root project directory by running:

act run backend.up

Redirect Act Call To Another Actfile

If we need to redirect the call to a foo act to an act with same name in another actfile we can use it like this:

# acfile.yml
version: 1

acts:
  foo:
    redirect: another/actfile.yml

and

# another/actfile.yml
version: 1

acts:
  foo:
    start: echo "im foo in another/actfile.yml"

This way when we call act run foo in the folder containing actfile.yml we going to see im foo in another/actfile.yml printed to the screen. When used with regex name matching feature of Act this can be very powerful because we can redirect a group of call to another actfile. Suppose we have the following folder structure:

my-worspace
  |-- backend
  |   |-- actfile.yml
  |-- frontned
  |   |-- actfile.yml
  |-- actfile.yml

with following actfile for backend:

# backend/actfile.yml
version: 1

acts:
  up:
    start: node index.js
  build:
    start: echo "lets transpile ts to js"

We can redirect all act calls to reach backend acts by default like this:

# actfile.yml
version: 1

acts:
  .+:
    redirect: backend/actfile.yml

This way if we run act run start we going to start backend service.

Act Called From Command

We can call another act from on act command like this:

# actfile.yml
version: 1

acts:
  foo:
    desc: This is an act that include subacts.
    start:
      - echo "foo before bar"
      - act: bar
      - echo "foo after bar"
  bar:
    start:
      - echo "im bar"

and then when we call act run foo we going to see

foo before bar
im bar
foo after bar

Variables

We have some variables at our disposition like the following:

  • ActName : The matched act name.
  • ActFilePath: The full actfile name which is being used (where act were matched).
  • ActFileDir: The base directory path of the actfile.
  • ActEnv: Path to a runtime env file which can be used in commands to share variables at execution time.

@TODO : We need to allow user specifying variables directly in actfile.

Sharing Env Vars Between Commands

Act going to run commands in independent shell environments to allow parallel execution as discussed in the previous section. That way if we need to share variables between commands (or acts) we can write variables as key=val strings to a special dotenv file which location is provided by $ACT_ENV var. Here an example:

# actfile.yml
version: 1

acts:
  foo:
    start:
      - grep -q MY_VAR $ACT_ENV || printf "MY_VAR=Bruno\n" >> $ACT_ENV
      - echo "MY_VAR is $MY_VAR"

The first command going to check if MY_VAR is already set in ACT_ENV and if not we going to add this variable and its value to ACT_ENV. This way when we run act run foo we should see MY_VAR is nosebit printed to the screen.

NOTE: Variables set to ACT_ENV going to be persisted as long the act is running. On stop/finish of act execution the main act process going to deleted all variables set. If we need to persist variables see the next section on loading variables from a specific file (not managed by act process itself).

WARNING: Remember to always add a line break char \n at the end of text you are appending to $ACT_ENV. Otherwise the variable not going to be loaded correctly.

WARNING: Be careful with race conditions when reading/write variables to $ACT_ENV when running commands in parallel.

Loading Variables From File

Act support dotenv vars file to be loaded before the execution of an act. So suppose we have the following .vars file in the root of our project:

MY_VAR=my-val

then we can load this variable like this:

# actfile.yml
version: 1

envfile: .vars

acts:
  foo:
    start:
      - echo "my rendered var is {{.MY_VAR}}"
      - echo "my env var is $MY_VAR"

and we can even use those loaded env vars in other fields like include/from via go template language like this:

# actfile.yml
version: 1

env: .vars

acts:
  foo:
    include: "{{.MY_VAR}}/actfile.yml"

With this env file set we can persist variables between different executions of an act. So, if we have the following:

# actfile.yml
version: 1

envfile: .var

acts:
  foo:
    start:
      - grep -q MY_VAR $ACT_ENV_FILE || printf "MY_VAR=Bruno\n" >> $ACT_ENV_FILE
      - echo "MY_VAR is $MY_VAR"

then we can execute act run foo multiple times and MY_VAR going to be persisted. Note that we ca use ACT_ENV_FILE variable to reference our env file.

WARNING: Remember to always add a line break char \n at the end of text you are appending to $ACT_ENV_FILE. Otherwise the variable not going to be loaded correctly.

Command Line Flags

If we want to support command line flags in our acts we can do it like the following:

# actfile.yml
version: 1

env: .vars

acts:
  foo:
    flags:
      - daemon:false
      - name
    start:
      - echo "daemon boolean flag => $FLAG_DAEMON"
      - echo "name string flag => $FLAG_NAME"
      - echo "other args are => $@"

This way we can run act run foo -daemon -name=Bruno arg1 arg2. Note that boolean flags which can be provided without values should have a default false added.

Command Loops

If we need to run multiple commands that are very similar we can use loop functionality like this:

# actfile.yml
version: 1

acts:
  foo:
    start:
      - cmd: echo {{.LoopItem}}
        loop:
          items:
            - name1
            - name2
            - name3

The loop field also accepts a glob option we can use to set items to be the list of matched file paths like this:

# actfile.yml
version: 1

acts:
  setup:
    start:
      - act: setup
        from: "{{.LoopItem}}"
        loop:
          glob: "**/actfile.yml"
        mismatch: allow

This way we going to loop over all subdiretories that has an actfile.yml in it and run the act named setup in those actfiles. Notice we used the mismatch field to prevent error in case actfile does not provide a setup rule.

Log Mode

By default Act going to output logs in raw mode without any info about the act or timestamp. If we need prefix log output with act name and timestamp we can set log field to prefixed at act or actfile levels like this:

# actfile.yml
version: 1

acts:
  foo:
    log: prefixed
    start:
      - echo "im prefixed"

We can use the command line flag l as well to set log mode like this:

act run -l=prefixed test-unit

Long Running Acts

If an act is written to be a long running process like the following:

# actfile.yml
version: 1

acts:
  foo:
    desc: This is a simple long running act example
    start:
      - while true; echo "Hello long running"; sleep 5; done

we can run it as a daemon using the following command:

act run -d foo

To list all running acts we can use:

act list

and finally to stop an act by it's name we can use:

act stop foo

Keep in mind that if we run foo act multiple times as daemons we going to endup having multiple running instances of the same act which is totally fine. But when running act stop foo we going to kill all foo instances at once. We can distinguish foo instances using tags flag like the following:

act run -d -t=foo-1 foo
act run -d -t=foo-2 foo

and then if we want to stop just foo-1 instance we can use

act stop -t=foo-1 foo

which going to stop all instances of foo act which has tag foo-1.

Detached Long Running Acts

If we need to run subacts as detached act processes which can be managed independently we can do like this:

# actfile.yml
version: 1

acts:
  long1:
    start: while true; do echo "hello long1"; sleep 4; done
  long2:
    start: while true; do echo "hello long2"; sleep 2; done

  all:
    log: prefixed
    start:
      parallel: true
      cmds:
        - act: long1
          detach: true

        - act: long2
          detach: true

This way if we run act run all we going to run long1 and long2 as different act processes and we can stop only one of those with act stop all::long1 for example. If we want to kill everything we can do act stop all.

Teardown

If we need to run commands at the very end of the act execution we can use teardown commands like the following:

# actfile.yml
version: 1

acts:
  foo:
    start: echo "started"
    teardown: echo "cleaning up"

Remember that teardown commands run if start command finish successfully or if it fails as well.

Owner
Nosebit
It's a blazing future!
Nosebit
Similar Resources

A plugin for argo which behaves like I'd like

argocd-lovely-plugin An ArgoCD plugin to perform various manipulations in a sensible order to ultimately output YAML for Argo CD to put into your clus

Dec 27, 2022

A small application to find pod ip address by service name using go

k8s podipfinder This is a small application to find pod ip address by service na

Dec 29, 2021

Returns which registry from the container image name

Returns which registry from the container image name

Jan 23, 2022

Open Service Mesh (OSM) is a lightweight, extensible, cloud native service mesh that allows users to uniformly manage, secure, and get out-of-the-box observability features for highly dynamic microservice environments.

Open Service Mesh (OSM) is a lightweight, extensible, cloud native service mesh that allows users to uniformly manage, secure, and get out-of-the-box observability features for highly dynamic microservice environments.

Open Service Mesh (OSM) Open Service Mesh (OSM) is a lightweight, extensible, Cloud Native service mesh that allows users to uniformly manage, secure,

Jan 2, 2023

Raspberry Pi Archlinux Automated Offline Installer with Wi-Fi. Windows, Mac and more features coming.

Raspberry Pi Archlinux Automated Offline Installer with Wi-Fi. Windows, Mac and more features coming.

Raspberry Pi Archlinux Automated Installer with Wi-Fi. Windows, Mac and more features coming. Download Go to releases page and download the zip file f

Nov 22, 2022

Provide task runtime implementation with pidfd and eBPF sched_process_exit tracepoint to manage deamonless container with low overhead.

embedshim The embedshim is the kind of task runtime implementation, which can be used as plugin in containerd. With current shim design, it is used to

Dec 18, 2022

Go-indexeddb - This expirement will attempt to implement IndexedDB features for Go

go-indexeddb This expirement will attempt to implement IndexedDB features for Go

May 29, 2022

Nomad-driver-await-dependency - A Nomad driver that acts as blocker for subsequent task until a given Consul service has reached a given state

Nomad Skeleton Driver Plugin Skeleton project for Nomad task driver plugins. Thi

Feb 12, 2022

An operator which complements grafana-operator for custom features which are not feasible to be merged into core operator

Grafana Complementary Operator A grafana which complements grafana-operator for custom features which are not feasible to be merged into core operator

Aug 16, 2022
Comments
  • [BUG] Interactive command not working

    [BUG] Interactive command not working

    Bug Description

    Commands that fire interactive programs do not work properly. It seems this is because we spawn commands with new process group id (using SysProcAttr.Setpgid) and for some reason we loose stdout/stdin connection somehow.

    Steps To Reproduce

    1. Create the following actfile:
    # actfile.yml
    version: 1
    
    acts:
      vim:
        cmds: vim
    
    1. Run act run vim
    2. We going to see a warning telling that input is not from a terminal and vim does not work properly.

    Expected Behaviour

    The interactive program should be indeed interactive and does not show any errors.

    Screenshots

    screen

    Environment

    • OS: MacOS
    • OS Version: 10.15.7
    • Act Version: 1.1.7
  • NPM package name

    NPM package name

    Olá, tudo bem?

    tenho um projeto que também se chama darch. Notei que não usam mais o pacote com o mesmo nome, teriam como transferir no NPM?

    este é o meu projeto: https://github.com/antoniopresto/darch/tree/feature/2.0

    Muito obrigado ☺️

A task runner / simpler Make alternative written in Go
A task runner / simpler Make alternative written in Go

Task Task is a task runner / build tool that aims to be simpler and easier to use than, for example, GNU Make. See taskfile.dev for the documentation.

Jan 8, 2023
Just a playground with some interesting concepts like pipelines aka middleware, handleFuncs, request validations etc. Check it out.

Pipeline a.k.a middleware in Go Just a playground with some interesting concepts like pipelines aka middleware, handleFuncs, request validations etc.

Dec 9, 2021
Censors or hides shell / Bash / console output based on defined patterns - great for hiding secrets in demos!
Censors or hides shell / Bash / console output based on defined patterns - great for hiding secrets in demos!

censor-shell Installation go install Usage Make the file ~/.censor-shell as an INI file with the following content: [nameofmyreplacement] pattern = b

Nov 11, 2022
easyssh-proxy provides a simple implementation of some SSH protocol features in Go

easyssh-proxy easyssh-proxy provides a simple implementation of some SSH protocol features in Go. Feature This project is forked from easyssh but add

Dec 30, 2022
Golang CRUD using database PostgreSQL, adding some fremework like mux and pq.

Golang CRUD with PostgreSQL Table of contents ?? General info Technologies Blog Setup General info GOPOST or Go-Post is a Golang REST API made to show

Nov 27, 2021
I'd like to share random apps in the spare times. Thus, I'm going to try learning some concepts of Go and as much as I can I try to clarify each line.

go-samples I'd like to share random apps in the spare times. Thus, I'm going to try learning some concepts of Go and as much as I can I try to clarify

Mar 16, 2022
A simple go tool, that calculates the allocated resources from all nodes matching the label selector.

kube-allocated-resources This is a simple go tool, that calculates the allocated resources from all nodes matching the label selector. Build Build on

Jan 12, 2022
Docker-based remote code runner / 基于 Docker 的远程代码运行器
Docker-based remote code runner / 基于 Docker 的远程代码运行器

Docker-based remote code runner / 基于 Docker 的远程代码运行器

Nov 9, 2022
Small monitor of pulseaudio volume etc. for use in xmobar, as CommandReader input

Simple PulseAudio volume monitor for xmobar This little monitor is my attempt to read the current volume and mute setting of the default sink from Pul

Feb 16, 2022
An app that fetches a random name and joke, and combines them.
An app that fetches a random name and joke, and combines them.

Wildfire Backend Assessment An app that fetches a random name and joke, and combines them.

Jan 29, 2022