bass

Bass is a low-fidelity Lisp dialect for scripting the infrastructure beneath your project.

Bass's goal is to make the path to production predictable, verifiable, flexible, and most importantly, fun.

This project is pre-alpha. These docs are up to date, but incomplete. See the README for more info.

𝄢

Bass is built around thunks. Thunks are cacheable commands that produce files and/or a stream of values.

(from (linux/alpine)
  ($ echo "Hello, world!"))
{
args
(
  1. "Hello, world!"
)
cmd
{
command "echo"
}
image
{
digest "sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300"
platform
{
os "linux"
}
repository "alpine"
tag "latest"
}
}
𝄢

The thunk above is just a data structure - to actually run it, call run:

(run (from (linux/alpine)
       ($ echo "Hello, world!")))
=> docker-image://docker.io/library/alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300 CACHED [0.00s]
--> resolve docker.io/library/alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300 [0.31s]
=> echo Hello, world! [0.17s]
Hello, world!
=> exporting to client [0.00s]
--> copying files [0.00s]
null
𝄢

To parse values from a thunk's stdout, call read:

(def cat-thunk
  (from (linux/alpine)
    (.cat "hello" "goodbye")))

(let [stream (read cat-thunk :json)]
  [(next stream :end)
   (next stream :end)
   (next stream :end)])
=> docker-image://docker.io/library/alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300 CACHED [0.00s]
--> resolve docker.io/library/alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300 [0.02s]
=> cat [0.11s]
=> exporting to client [0.00s]
--> copying files 65B [0.00s]
(
  1. "hello"
  2. "goodbye"
  3. end
)
𝄢

To refer to files created by a thunk, use thunk paths:

(def meowed
  (from (linux/alpine)
    (-> ($ sh -c "cat > ./file")
        (with-stdin "hello" "goodbye"))))

meowed/file
{
args
(
  1. "-c"
  2. "cat > ./file"
)
cmd
{
command "sh"
}
image
{
digest "sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300"
platform
{
os "linux"
}
repository "alpine"
tag "latest"
}
stdin
(
  1. "hello"
  2. "goodbye"
)
}
./file
𝄢

As with thunks, constructing a thunk path doesn't actually cause the thunk to run. Calling read will, though:

(next (read meowed/file :json))
=> docker-image://docker.io/library/alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300 CACHED [0.00s]
--> resolve docker.io/library/alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300 [0.01s]
=> sh -c cat > ./file [0.15s]
=> copy /file / CACHED [0.00s]
=> exporting to client [0.00s]
--> sending tarball [0.00s]
"hello"
𝄢

Thunk paths may be passed into other thunks, and so on, forming one big thunk.

(defn go-build [src pkg]
  (-> (from (linux/golang)
        (cd src
          ($ go build -o ../out/)))
      (path ./out/)))

(def cloned
  (from (linux/alpine/git)
    ($ git clone "https://github.com/vito/bass" ./repo/)))

(go-build cloned/repo/ "./cmd/...")
{
args
(
  1. "build"
  2. "-o"
  3. {
    dir "../out"
    }
)
cmd
{
command "go"
}
dir
{
args
(
  1. "clone"
  2. "https://github.com/vito/bass"
  3. {
    dir "repo"
    }
)
cmd
{
command "git"
}
image
{
digest "sha256:d4740deff7f05d2d48771ff66d9d9c26dfb76f0db0aa833b1ea0ee346fa1e48f"
platform
{
os "linux"
}
repository "alpine/git"
tag "latest"
}
}
./repo/
image
{
digest "sha256:c72fa9afc50b3303e8044cf28fb358b48032a548e1825819420fd40155a131cb"
platform
{
os "linux"
}
repository "golang"
tag "latest"
}
}
./out/
𝄢

Astute observers will note that cloned above is not a hermetic thunk, because it doesn't specify a version.

𝄢

The .git module provides basic tools for cloning Git repositories in a hermetic manner.

(use (.git (linux/alpine/git)))

(let [uri "https://github.com/vito/bass"]
  (git:checkout uri (git:ls-remote uri "HEAD")))
=> docker-image://docker.io/alpine/git@sha256:d4740deff7f05d2d48771ff66d9d9c26dfb76f0db0aa833b1ea0ee346fa1e48f CACHED [0.00s]
--> resolve docker.io/alpine/git@sha256:d4740deff7f05d2d48771ff66d9d9c26dfb76f0db0aa833b1ea0ee346fa1e48f [0.14s]
=> git ls-remote https://github.com/vito/bass HEAD [0.38s]
=> exporting to client [0.00s]
--> copying files 72B [0.00s]
{
args
(
  1. "submodule"
  2. "update"
  3. "--init"
  4. "--recursive"
)
cmd
{
command "git"
}
image
{
args
(
  1. "checkout"
  2. "eca25c29d33840e12dc3ea1d6f727830129e063e"
)
cmd
{
command "git"
}
image
{
args
(
  1. "clone"
  2. "https://github.com/vito/bass"
  3. {
    dir "."
    }
)
cmd
{
command "git"
}
image
{
digest "sha256:d4740deff7f05d2d48771ff66d9d9c26dfb76f0db0aa833b1ea0ee346fa1e48f"
platform
{
os "linux"
}
repository "alpine/git"
tag "latest"
}
labels
{
for "eca25c29d33840e12dc3ea1d6f727830129e063e"
}
}
}
}
./
𝄢

The .git module also provides github, a path root for repositories hosted at GitHub.

git:github/vito/bass/ref/HEAD/
=> docker-image://docker.io/alpine/git@sha256:d4740deff7f05d2d48771ff66d9d9c26dfb76f0db0aa833b1ea0ee346fa1e48f CACHED [0.00s]
--> resolve docker.io/alpine/git@sha256:d4740deff7f05d2d48771ff66d9d9c26dfb76f0db0aa833b1ea0ee346fa1e48f [0.02s]
=> git ls-remote https://github.com/vito/bass HEAD [0.37s]
=> exporting to client [0.00s]
--> copying files 72B [0.00s]
{
args
(
  1. "submodule"
  2. "update"
  3. "--init"
  4. "--recursive"
)
cmd
{
command "git"
}
image
{
args
(
  1. "checkout"
  2. "eca25c29d33840e12dc3ea1d6f727830129e063e"
)
cmd
{
command "git"
}
image
{
args
(
  1. "clone"
  2. "https://github.com/vito/bass"
  3. {
    dir "."
    }
)
cmd
{
command "git"
}
image
{
digest "sha256:d4740deff7f05d2d48771ff66d9d9c26dfb76f0db0aa833b1ea0ee346fa1e48f"
platform
{
os "linux"
}
repository "alpine/git"
tag "latest"
}
labels
{
for "eca25c29d33840e12dc3ea1d6f727830129e063e"
}
}
}
}
./
𝄢

Thunk paths can be saved in JSON format for archival, auditing, efficient distribution, or just for funsies.

(emit (go-build git:github/vito/bass/ref/HEAD/ "./cmd/...")
      *stdout*)
=> docker-image://docker.io/alpine/git@sha256:d4740deff7f05d2d48771ff66d9d9c26dfb76f0db0aa833b1ea0ee346fa1e48f CACHED [0.00s]
--> resolve docker.io/alpine/git@sha256:d4740deff7f05d2d48771ff66d9d9c26dfb76f0db0aa833b1ea0ee346fa1e48f [0.02s]
=> git ls-remote https://github.com/vito/bass HEAD [0.36s]
=> exporting to client [0.00s]
--> copying files 72B [0.00s]
{
  "thunk": {
    "image": {
      "digest": "sha256:c72fa9afc50b3303e8044cf28fb358b48032a548e1825819420fd40155a131cb",
      "platform": {
        "os": "linux"
      },
      "repository": "golang",
      "tag": "latest"
    },
    "cmd": {
      "command": "go"
    },
    "args": [
      "build",
      "-o",
      {
        "dir": "../out"
      }
    ],
    "dir": {
      "thunk": {
        "image": {
          "image": {
            "image": {
              "digest": "sha256:d4740deff7f05d2d48771ff66d9d9c26dfb76f0db0aa833b1ea0ee346fa1e48f",
              "platform": {
                "os": "linux"
              },
              "repository": "alpine/git",
              "tag": "latest"
            },
            "cmd": {
              "command": "git"
            },
            "args": [
              "clone",
              "https://github.com/vito/bass",
              {
                "dir": "."
              }
            ],
            "labels": {
              "for": "eca25c29d33840e12dc3ea1d6f727830129e063e"
            }
          },
          "cmd": {
            "command": "git"
          },
          "args": [
            "checkout",
            "eca25c29d33840e12dc3ea1d6f727830129e063e"
          ]
        },
        "cmd": {
          "command": "git"
        },
        "args": [
          "submodule",
          "update",
          "--init",
          "--recursive"
        ]
      },
      "path": {
        "dir": "."
      }
    }
  },
  "path": {
    "dir": "out"
  }
}
null
𝄢

Feeding thunk path JSON to bass --export will print a tar stream containing the file tree.

𝄢

Feeding thunk JSON to bass --export will print an OCI image tar stream, which can be piped to docker load for troubleshooting with docker run. This will be made easier in the future.

(emit
  (from (linux/ubuntu)
    ($ apt-get update)
    ($ apt-get -y install git))
  *stdout*)
{
  "image": {
    "image": {
      "digest": "sha256:626ffe58f6e7566e00254b638eb7e0f3b11d4da9675088f4781a50ae288f3322",
      "platform": {
        "os": "linux"
      },
      "repository": "ubuntu",
      "tag": "latest"
    },
    "cmd": {
      "command": "apt-get"
    },
    "args": [
      "update"
    ]
  },
  "cmd": {
    "command": "apt-get"
  },
  "args": [
    "-y",
    "install",
    "git"
  ]
}
null

Intrigued? Check out the other demos!