:image |
{
} |
||||||||||||||
:args |
(
) |
This guide glosses over language semantics in favor of being a quick reference for common tasks. If you'd like to learn the language, see bassics.
Bass is shipped as a single bass binary which needs to be installed somewhere in your $PATH.
To run Bass you'll need either Docker Engine (Linux), Docker Desktop (OS X, Windows), or Buildkit running.
With everything installed, try one of the demos:
bass demos/git-lib.bassIf you see bass: command not found, it's not in your $PATH.
If you see some other kind of error you're welcome to ask for help in GitHub or Discord.
Bass is built around thunks. Thunks are cacheable commands that produce files and/or a stream of values.
Throughout this documentation, thunks will be rendered as space invaders to make them easier to identify.
┣─╮│ ▼ run {{thunk SOOINRF4LGA54: echo "Hello, world!"}}│ █ [0.01s] resolve image config for docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.01s] docker-image://docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ ┣ [0.01s] resolve docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.12s] echo "Hello, world!"│ ┃ Hello, world!┻ ┻
null
┣─╮│ ▼ run {{thunk CI43558RV4D52: sh -c "exit 1"}}│ █ [0.01s] resolve image config for docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.01s] docker-image://docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ ┣ [0.01s] resolve docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.13s] ERROR sh -c "exit 1"┻ ┻
false
Thunks are cached forever. They can be cleared with bass --prune, but this should only be necessary for regaining disk space.
If you want to run a thunk multiple times, just set a different value as an environment variable. Tip: use now to control cache granularity.
┣─╮│ ▼ run {{thunk 0KLNHMGOSJRC4: echo "Hi again!"}}│ █ [0.01s] resolve image config for docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.01s] docker-image://docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ ┣ [0.01s] resolve docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.13s] echo "Hi again!"│ ┃ Hi again!┻ ┻
null
┣─╮│ ▼ read {{thunk 10M3L6SAN5DF2: .cat}}│ █ [0.00s] resolve image config for docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.01s] docker-image://docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ ┣ [0.01s] resolve docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.12s] .cat│ ┃ "hello"│ ┃ "goodbye"┻ ┻
("hello""goodbye"end)To read output line-by-line, set the protocol to :lines:
┣─╮│ ▼ read {{thunk PR0R6KDUVRJNA: ls -r /usr/bin}}│ █ [0.01s] resolve image config for docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.01s] docker-image://docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ ┣ [0.01s] resolve docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.13s] ls -r /usr/bin│ ┃ yes│ ┃ xzcat│ ┃ xxd│ ┃ xargs│ ┃ whois│ ┃ whoami│ ┃ who│ ┃ which│ ┃ wget│ ┃ wc│ ┃ volname│ ┃ vlock│ ┃ vi│ ┃ uuencode│ ┃ uudecode│ ┃ uptime│ ┃ unzip│ ┃ unxz│ ┃ unshare│ ┃ unlzop│ ┃ unlzma│ ┃ unlink│ ┃ unix2dos│ ┃ uniq│ ┃ unexpand│ ┃ udhcpc6│ ┃ ttysize│ ┃ tty│ ┃ truncate│ ┃ tree│ ┃ traceroute6│ ┃ traceroute│ ┃ tr│ ┃ top│ ┃ timeout│ ┃ time│ ┃ test│ ┃ tee│ ┃ tail│ ┃ tac│ ┃ sum│ ┃ strings│ ┃ ssl_client│ ┃ split│ ┃ sort│ ┃ shuf│ ┃ shred│ ┃ showkey│ ┃ sha512sum│ ┃ sha3sum│ ┃ sha256sum│ ┃ sha1sum│ ┃ setsid│ ┃ setkeycodes│ ┃ seq│ ┃ scanelf│ ┃ resize│ ┃ reset│ ┃ renice│ ┃ realpath│ ┃ readlink│ ┃ pwdx│ ┃ pstree│ ┃ pscan│ ┃ printf│ ┃ pmap│ ┃ pkill│ ┃ pgrep│ ┃ paste│ ┃ passwd│ ┃ openvt│ ┃ od│ ┃ nslookup│ ┃ nsenter│ ┃ nproc│ ┃ nohup│ ┃ nmeter│ ┃ nl│ ┃ nc│ ┃ mkpasswd│ ┃ mkfifo│ ┃ microcom│ ┃ mesg│ ┃ md5sum│ ┃ lzopcat│ ┃ lzma│ ┃ lzcat│ ┃ lsusb│ ┃ lsof│ ┃ logger│ ┃ less│ ┃ ldd│ ┃ last│ ┃ killall│ ┃ ipcs│ ┃ ipcrm│ ┃ install│ ┃ id│ ┃ iconv│ ┃ hostid│ ┃ hexdump│ ┃ head│ ┃ hd│ ┃ groups│ ┃ getent│ ┃ getconf│ ┃ fuser│ ┃ free│ ┃ fold│ ┃ flock│ ┃ find│ ┃ fallocate│ ┃ factor│ ┃ expr│ ┃ expand│ ┃ env│ ┃ eject│ ┃ du│ ┃ dos2unix│ ┃ dirname│ ┃ diff│ ┃ deallocvt│ ┃ dc│ ┃ cut│ ┃ cryptpw│ ┃ crontab│ ┃ cpio│ ┃ comm│ ┃ cmp│ ┃ clear│ ┃ cksum│ ┃ chvt│ ┃ cal│ ┃ bzip2│ ┃ bzcat│ ┃ bunzip2│ ┃ beep│ ┃ bc│ ┃ basename│ ┃ awk│ ┃ [[│ ┃ [┻ ┻
"yes"
To parse UNIX style tabular output, set the protocol to :unix-table:
┣─╮│ ▼ read {{thunk PR0R6KDUVRJNA: ls -r /usr/bin}}│ █ [0.01s] resolve image config for docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.00s] docker-image://docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ ┣ [0.00s] resolve docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.12s] ls -r /usr/bin│ ┃ yes│ ┃ xzcat│ ┃ xxd│ ┃ xargs│ ┃ whois│ ┃ whoami│ ┃ who│ ┃ which│ ┃ wget│ ┃ wc│ ┃ volname│ ┃ vlock│ ┃ vi│ ┃ uuencode│ ┃ uudecode│ ┃ uptime│ ┃ unzip│ ┃ unxz│ ┃ unshare│ ┃ unlzop│ ┃ unlzma│ ┃ unlink│ ┃ unix2dos│ ┃ uniq│ ┃ unexpand│ ┃ udhcpc6│ ┃ ttysize│ ┃ tty│ ┃ truncate│ ┃ tree│ ┃ traceroute6│ ┃ traceroute│ ┃ tr│ ┃ top│ ┃ timeout│ ┃ time│ ┃ test│ ┃ tee│ ┃ tail│ ┃ tac│ ┃ sum│ ┃ strings│ ┃ ssl_client│ ┃ split│ ┃ sort│ ┃ shuf│ ┃ shred│ ┃ showkey│ ┃ sha512sum│ ┃ sha3sum│ ┃ sha256sum│ ┃ sha1sum│ ┃ setsid│ ┃ setkeycodes│ ┃ seq│ ┃ scanelf│ ┃ resize│ ┃ reset│ ┃ renice│ ┃ realpath│ ┃ readlink│ ┃ pwdx│ ┃ pstree│ ┃ pscan│ ┃ printf│ ┃ pmap│ ┃ pkill│ ┃ pgrep│ ┃ paste│ ┃ passwd│ ┃ openvt│ ┃ od│ ┃ nslookup│ ┃ nsenter│ ┃ nproc│ ┃ nohup│ ┃ nmeter│ ┃ nl│ ┃ nc│ ┃ mkpasswd│ ┃ mkfifo│ ┃ microcom│ ┃ mesg│ ┃ md5sum│ ┃ lzopcat│ ┃ lzma│ ┃ lzcat│ ┃ lsusb│ ┃ lsof│ ┃ logger│ ┃ less│ ┃ ldd│ ┃ last│ ┃ killall│ ┃ ipcs│ ┃ ipcrm│ ┃ install│ ┃ id│ ┃ iconv│ ┃ hostid│ ┃ hexdump│ ┃ head│ ┃ hd│ ┃ groups│ ┃ getent│ ┃ getconf│ ┃ fuser│ ┃ free│ ┃ fold│ ┃ flock│ ┃ find│ ┃ fallocate│ ┃ factor│ ┃ expr│ ┃ expand│ ┃ env│ ┃ eject│ ┃ du│ ┃ dos2unix│ ┃ dirname│ ┃ diff│ ┃ deallocvt│ ┃ dc│ ┃ cut│ ┃ cryptpw│ ┃ crontab│ ┃ cpio│ ┃ comm│ ┃ cmp│ ┃ clear│ ┃ cksum│ ┃ chvt│ ┃ cal│ ┃ bzip2│ ┃ bzcat│ ┃ bunzip2│ ┃ beep│ ┃ bc│ ┃ basename│ ┃ awk│ ┃ [[│ ┃ [┻ ┻
("yes")To collect all output into one big string, set the protocol to :raw:
┣─╮│ ▼ read {{thunk SOOINRF4LGA54: echo "Hello, world!"}}│ █ [0.01s] resolve image config for docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.00s] docker-image://docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ ┣ [0.00s] resolve docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.10s] echo "Hello, world!"│ ┃ Hello, world!┻ ┻
"Hello, world!\n"
(mask "hunter2" :nickserv)
<secret: nickserv (7 bytes)>
Secrets can be passed to thunks as regular strings. When serialized, a secret's value is omitted.
Bass does not mask the secret from the command's output. This may come in the future.
Sensitive values can end up in all sorts of sneaky places. Bass does its best to prevent that from happening.
bass are only provided to the entrypoint script (as *env*). They are also removed from the bass process so that they can't be sneakily accessed at runtime.With the above precautions, passing secrets to thunks as env vars may often be most ergonomic approach. If you have more ideas, please suggest them!
To pass a secret to a command using a secret mount, use with-mount:
(-> ($ cat /secret)
(with-mount (mask "hello" :shh) /secret)
(with-image (linux/alpine))
run)
┣─╮│ ▼ run {{thunk KCSL5Q1GS342G: cat /secret}}│ █ [0.01s] resolve image config for docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.01s] docker-image://docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ ┣ [0.01s] resolve docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.12s] cat /secret│ ┃ hello┻ ┻
null
* This is all obviously to the best of my ability - I can't promise it's perfect. If you find other ways to make Bass safer, please share them!
Cache paths may be created using cache-dir and passed to thunks like any other path. Any data written to a cache path persists until cleared by bass --prune.
┣─╮│ ▼ read {{thunk G781R2G91SDVI: sh -c "echo $0 >> /var/cache/file; cat /var/cache/file | wc -l" once}}│ █ [0.01s] resolve image config for docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.01s] docker-image://docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ ┣ [0.01s] resolve docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ ┣─╮ docker-image://docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █◀┤ [0.11s] sh -c "echo $0 >> /var/cache/file; cat /var/cache/file | wc -l" once│ ┃ │ 1│ ┻ │┣─╮ ││ ▼ │ read {{thunk CUOK1ATO52RIG: sh -c "echo $0 >> /var/cache/file; cat /var/cache/file | wc -l" twice}}│ █◀┤ [0.12s] sh -c "echo $0 >> /var/cache/file; cat /var/cache/file | wc -l" twice│ ┃ │ 1│ ┻ │┣─╮ ││ ▼ │ read {{thunk D0G1T3U645334: sh -c "echo $0 >> /var/cache/file; cat /var/cache/file | wc -l" thrice}}│ █◀╯ [0.13s] sh -c "echo $0 >> /var/cache/file; cat /var/cache/file | wc -l" thrice│ ┃ 1┻ ┻
(111)Currently only one thunk can access a cache path at a time. This may become configurable in the future.
Thunks run in an initial working directory controlled by Bass. Files created within this directory can be passed to other thunks by using thunk paths.
Thunk paths are created by using a thunk with path notation:
{:image |
{
} |
||||||||||||||
:args |
(
) |
||||||||||||||
:stdin |
(
) |
}./file(-> ($ sh -c "cat > ./file")
(with-image (linux/alpine))
(with-stdin ["hello" "goodbye"])
(subpath ./file))
{:image |
{
} |
||||||||||||||
:args |
(
) |
||||||||||||||
:stdin |
(
) |
}./fileJust like thunks, a thunk path is just an object. Its underlying thunk won't run until the path is needed by something.
When you read a thunk path, Bass runs its thunk and reads the content of the path using the same protocols for reading output:
┣─╮│ ▼ export path {{thunk RH8AK5OJKKD76: sh -c "cat > ./file"}}/file│ █ [0.00s] resolve image config for docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.01s] docker-image://docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ ┣ [0.01s] resolve docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.13s] sh -c "cat > ./file"┻ ┻
"hello"
When you pass a thunk path to an outer thunk, Bass runs the path's thunk and mounts the path into the outer thunk's working directory under a hashed directory name:
(-> ($ ls -al meowed/file)
(with-image (linux/alpine))
run)
┣─╮│ ▼ run {{thunk 2K4BEOLLE98F2: ls -al {{thunk RH8AK5OJKKD76: sh -c "cat > ./file"}}/file}}│ █ [0.01s] docker-image://docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ ┣ [0.01s] resolve docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.00s] resolve image config for docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.13s] sh -c "cat > ./file"│ █ [0.14s] ls -al {{thunk RH8AK5OJKKD76: sh -c "cat > ./file"}}/file│ ┃ -rw-r--r-- 1 root root 18 Oct 26 1985 ./RH8AK5OJKKD76/file┻ ┻
null
If the outer thunk sets a thunk path as its working directory (viw cd or with-dir), you can use ../ to refer back to the original working directory.
{:image |
{
} |
||||||||||||||||||||||
:args |
(
) |
||||||||||||||||||||||
:mounts |
(
) |
}./out/Note that any modifications made to an input thunk path will not propagate to subsequent thunks.
Astute observers will note that cloned above is not a hermetic, because it doesn't specify a version.
The .git module provides basic tools for cloning Git repositories in a hermetic manner.
┣─╮│ ▼ read {{thunk KDD582G7FDIVQ: git ls-remote https://github.com/vito/bass HEAD}}│ █ [0.01s] resolve image config for docker.io/alpine/git@sha256:d453f54c83320412aa89c391b076930bd8569bc1012285e8c68ce2d4435826a3│ █ [0.01s] docker-image://docker.io/alpine/git@sha256:d453f54c83320412aa89c391b076930bd8569bc1012285e8c68ce2d4435826a3│ ┣ [0.01s] resolve docker.io/alpine/git@sha256:d453f54c83320412aa89c391b076930bd8569bc1012285e8c68ce2d4435826a3│ █ [0.26s] git ls-remote https://github.com/vito/bass HEAD│ ┃ d32b936ceed8a32b5ef455ec70263d293ff6f1bf HEAD┻ ┻
{:image |
{
} |
||||||||||||||||||||||||||
:args |
(
) |
}./The .git module also provides github, a path root for repositories hosted at GitHub.
git:github/vito/bass/ref/HEAD/
┣─╮│ ▼ read {{thunk 3U6L4QTCNO4O0: git ls-remote https://github.com/vito/bass HEAD}}│ █ [0.00s] resolve image config for docker.io/alpine/git@sha256:d453f54c83320412aa89c391b076930bd8569bc1012285e8c68ce2d4435826a3│ █ [0.01s] docker-image://docker.io/alpine/git@sha256:d453f54c83320412aa89c391b076930bd8569bc1012285e8c68ce2d4435826a3│ ┣ [0.01s] resolve docker.io/alpine/git@sha256:d453f54c83320412aa89c391b076930bd8569bc1012285e8c68ce2d4435826a3│ █ [0.30s] git ls-remote https://github.com/vito/bass HEAD│ ┃ d32b936ceed8a32b5ef455ec70263d293ff6f1bf HEAD┻ ┻
{:image |
{
} |
||||||||||||||||||||||||||
:args |
(
) |
}./When something goes wrong, Bass tries to provide an ergonomic error message. Backtraces show annotated source code complete with syntax highlighting. When a thunk fails its output is included in the error message at the bottom of the screen so you don't have to skim the whole output.
(defn echo-sleep-exit [msg seconds exit-code]
(subpath
(from (linux/alpine)
(with-env
($ sleep (str seconds))
{:AT (now 0)})
($ sh -c (str "echo \"$0\"; exit " exit-code) $msg))
./))
(defn ls paths
(run (from (linux/alpine)
($ ls & $paths))))
(defn main []
(ls (echo-sleep-exit "hello\nanother line" 1 1)
(echo-sleep-exit "oh no" 3 42)))
┣─╮│ ▼ run {{thunk 2TADOMO26JOG0: ls {{thunk MQHB05LBV95GG: sh -c "echo \"$0\"; exit 1" "hello\nanother line"}}/ {{thunk PMERTAR29Q17E: sh -c "echo \"$0\"; exit 42" "oh no"}}/}}│ █ [0.01s] docker-image://docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ ┣ [0.01s] resolve docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [0.00s] resolve image config for docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659│ █ [1.13s] sleep 1│ █ [1.34s] CANCELED sleep 3│ █ [0.14s] ERROR sh -c "echo \"$0\"; exit 1" "hello\nanother line"│ ┃ hello│ ┃ another line┻ ┻error! call trace (oldest first):┆ <fs>/multi-fail.bass:15:2..16:3714 │ (defn main []15 │ (ls (echo-sleep-exit "hello\nanother line" 1 1)16 │ (echo-sleep-exit "oh no" 3 42)))^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^┆ <fs>/multi-fail.bass:11:2..12:2610 │ (defn ls paths11 │ (run (from (linux/alpine)12 │ ($ ls & $paths))))^^^^^^^^^^^^^^^^^^^^^^^^^resolve failed: exit code: 1run summary:[0.01s] docker-image://docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659[0.01s] resolve docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659○─docker-image://docker.io/library/alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659┤ [1.13s] sleep 1│ ○─sleep 1╯ │ [1.34s] CANCELED sleep 3──╯ [0.14s] ERROR sh -c "echo \"$0\"; exit 1" "hello\nanother line"helloanother linefor more information, refer to the full output above
That being said, there's a good chance you'll run into a cryptic error message now and then while I work towards making them friendly. If you find one, please open an issue.
Thunk paths can be saved in JSON format for archival, auditing, efficient distribution, or just for funsies.
{
"thunk": {
"image": {
"thunk": {
"image": {
"ref": {
"platform": {
"os": "linux",
"arch": "amd64"
},
"repository": "golang",
"tag": "latest",
"digest": "sha256:c7e98cc0fd4dfb71ee7465fee6c9a5f079163307e4bf141b336bb9dae00159a5"
}
}
}
},
"args": [
{
"string": {
"value": "go"
}
},
{
"string": {
"value": "build"
}
},
{
"string": {
"value": "-o"
}
},
{
"dirPath": {
"path": "../out"
}
},
{
"string": {
"value": "./cmd/..."
}
}
],
"dir": {
"thunk": {
"thunk": {
"image": {
"thunk": {
"image": {
"thunk": {
"image": {
"thunk": {
"image": {
"thunk": {
"image": {
"ref": {
"platform": {
"os": "linux",
"arch": "amd64"
},
"repository": "alpine/git",
"tag": "latest",
"digest": "sha256:d453f54c83320412aa89c391b076930bd8569bc1012285e8c68ce2d4435826a3"
}
}
}
},
"args": [
{
"string": {
"value": "git"
}
},
{
"string": {
"value": "clone"
}
},
{
"string": {
"value": "https://github.com/vito/bass"
}
},
{
"dirPath": {
"path": "."
}
}
]
}
},
"args": [
{
"string": {
"value": "git"
}
},
{
"string": {
"value": "fetch"
}
},
{
"string": {
"value": "origin"
}
},
{
"string": {
"value": "d32b936ceed8a32b5ef455ec70263d293ff6f1bf"
}
}
]
}
},
"args": [
{
"string": {
"value": "git"
}
},
{
"string": {
"value": "checkout"
}
},
{
"string": {
"value": "d32b936ceed8a32b5ef455ec70263d293ff6f1bf"
}
}
]
}
},
"args": [
{
"string": {
"value": "git"
}
},
{
"string": {
"value": "submodule"
}
},
{
"string": {
"value": "update"
}
},
{
"string": {
"value": "--init"
}
},
{
"string": {
"value": "--recursive"
}
}
]
},
"path": {
"dir": {
"path": "."
}
}
}
}
},
"path": {
"dir": {
"path": "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.
{
"image": {
"thunk": {
"image": {
"thunk": {
"image": {
"ref": {
"platform": {
"os": "linux",
"arch": "amd64"
},
"repository": "ubuntu",
"tag": "latest",
"digest": "sha256:d1e2e92c075e5ca139d51a140fff46f84315c0fdce203eab2807c7e495eff4f9"
}
}
}
},
"args": [
{
"string": {
"value": "apt-get"
}
},
{
"string": {
"value": "update"
}
}
]
}
},
"args": [
{
"string": {
"value": "apt-get"
}
},
{
"string": {
"value": "-y"
}
},
{
"string": {
"value": "install"
}
},
{
"string": {
"value": "git"
}
}
]
}
null
bass.lock
Bass comes with baby's first dependency pinning solution: memo. It works by storing results of functions loaded from Bass modules into a file typically called bass.lock and committed to your repository.
(memo-ls-remote "https://github.com/moby/buildkit" "HEAD")
┣─╮│ ▼ read {{thunk 082H6MST9K7SS: git ls-remote https://github.com/moby/buildkit HEAD}}│ █ [0.01s] resolve image config for docker.io/alpine/git@sha256:d453f54c83320412aa89c391b076930bd8569bc1012285e8c68ce2d4435826a3│ █ [0.01s] docker-image://docker.io/alpine/git@sha256:d453f54c83320412aa89c391b076930bd8569bc1012285e8c68ce2d4435826a3│ ┣ [0.01s] resolve docker.io/alpine/git@sha256:d453f54c83320412aa89c391b076930bd8569bc1012285e8c68ce2d4435826a3│ █ [0.36s] git ls-remote https://github.com/moby/buildkit HEAD│ ┃ c0a4bd9e75b2b80f9b87705a8647f6a7a9a6a752 HEAD┻ ┻
"c0a4bd9e75b2b80f9b87705a8647f6a7a9a6a752"
When the function is called again with the same arguments, the cached response value is returned instead of making the call again:
(memo-ls-remote "https://github.com/moby/buildkit" "HEAD")
"c0a4bd9e75b2b80f9b87705a8647f6a7a9a6a752"
Use bass --bump to refresh every dependency in a bass.lock file:
bass --bump bass.lockThe bass --bump command re-loads all embedded module thunks and calls each function with each of its its associated arguments, updating the file in-place.
Memoization is mostly leveraged for caching dependency version resolution. For this, your module must define the bass.lock path as a special binding: *memos*.
(def *memos* *dir*/bass.lock)
*memos*
┣─╮│ ▼ read {{thunk LJJLB5U936PT4: git ls-remote https://github.com/vito/bass main}}┣─┼─╮│ │ ▼ resolve {{thunk 27OTTG8R8CRVM: }}│ │ █ [0.08s] resolve image config for docker.io/alpine/git:latest│ │ ┻│ █ [0.01s] resolve image config for docker.io/alpine/git@sha256:d453f54c83320412aa89c391b076930bd8569bc1012285e8c68ce2d4435826a3│ █ [0.01s] docker-image://docker.io/alpine/git@sha256:d453f54c83320412aa89c391b076930bd8569bc1012285e8c68ce2d4435826a3│ ┣ [0.01s] resolve docker.io/alpine/git@sha256:d453f54c83320412aa89c391b076930bd8569bc1012285e8c68ce2d4435826a3│ █ [0.27s] git ls-remote https://github.com/vito/bass main│ ┃ d32b936ceed8a32b5ef455ec70263d293ff6f1bf refs/heads/main┻ ┻
{:image |
{
} |
||||||||||||||||||||||||||
:args |
(
) |
}./Using bass.lock files lets you share and reuse Bass code in git repos:
bass demos/git-lib.bass┣─╮│ ▼ export path {{thunk M8SI6J1M2237G: git submodule update --init --recursive}}/wget.bass│ █ [0.01s] resolve image config for docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d23275658bd3f2│ █ [0.01s] docker-image://docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d23275658bd3f2│ ┣ [0.00s] resolve docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d23275658bd3f2│ ┣ [0.08s] sha256:b4f093b99828d544282f6ae73a3790bf797054598ba594ca61fc01461ed8398c│ ┣ [0.08s] sha256:ca7dd9ec2225f2385955c43b2379305acd51543c28cf1d4e94522b3d94cce3ce│ ┣ [0.03s] extracting sha256:ca7dd9ec2225f2385955c43b2379305acd51543c28cf1d4e94522b3d94cce3ce│ ┣ [0.28s] sha256:eb878e0a08e4e9249bc186e41811e9a281e6016d9ddb39af1df449cfbec600fc│ ┣ [0.15s] extracting sha256:eb878e0a08e4e9249bc186e41811e9a281e6016d9ddb39af1df449cfbec600fc│ ┣ [0.00s] extracting sha256:b4f093b99828d544282f6ae73a3790bf797054598ba594ca61fc01461ed8398c│ ┣─╮ docker-image://docker.io/alpine/git@sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d23275658bd3f2│ █◀┤ [0.59s] git clone https://github.com/vito/tabs ./│ ┃ │ Cloning into '.'...│ █ │ [0.38s] git fetch origin 6ca3f15d70b739a7929886e20b61dbc4cbdc4e22│ ┃ │ From https://github.com/vito/tabs│ ┃ │ * branch 6ca3f15d70b739a7929886e20b61dbc4cbdc4e22 -> FETCH_HEAD│ █ │ [0.20s] git checkout 6ca3f15d70b739a7929886e20b61dbc4cbdc4e22│ ┃ │ Note: switching to '6ca3f15d70b739a7929886e20b61dbc4cbdc4e22'.│ ┃ ││ ┃ │ You are in 'detached HEAD' state. You can look around, make experimental│ ┃ │ changes and commit them, and you can discard any commits you make in this│ ┃ │ state without impacting any branches by switching back to a branch.│ ┃ ││ ┃ │ If you want to create a new branch to retain commits you create, you may│ ┃ │ do so (now or later) by using -c with the switch command. Example:│ ┃ ││ ┃ │ git switch -c <new-branch-name>│ ┃ ││ ┃ │ Or undo this operation with:│ ┃ ││ ┃ │ git switch -│ ┃ ││ ┃ │ Turn off this advice by setting config variable advice.detachedHead to false│ ┃ ││ ┃ │ HEAD is now at 6ca3f15 nix/linux: build FHS layout│ █ │ [0.21s] git submodule update --init --recursive│ ┻ │┣─╮ ││ ▼ │ export path {{thunk M8SI6J1M2237G: git submodule update --init --recursive}}/nix.bass│ █◀┤ [0.45s] git clone https://github.com/vito/tabs ./│ ┃ │ Cloning into '.'...│ █ │ [0.40s] git fetch origin 6ca3f15d70b739a7929886e20b61dbc4cbdc4e22│ ┃ │ From https://github.com/vito/tabs│ ┃ │ * branch 6ca3f15d70b739a7929886e20b61dbc4cbdc4e22 -> FETCH_HEAD│ █ │ [0.21s] git checkout 6ca3f15d70b739a7929886e20b61dbc4cbdc4e22│ ┃ │ Note: switching to '6ca3f15d70b739a7929886e20b61dbc4cbdc4e22'.│ ┃ ││ ┃ │ You are in 'detached HEAD' state. You can look around, make experimental│ ┃ │ changes and commit them, and you can discard any commits you make in this│ ┃ │ state without impacting any branches by switching back to a branch.│ ┃ ││ ┃ │ If you want to create a new branch to retain commits you create, you may│ ┃ │ do so (now or later) by using -c with the switch command. Example:│ ┃ ││ ┃ │ git switch -c <new-branch-name>│ ┃ ││ ┃ │ Or undo this operation with:│ ┃ ││ ┃ │ git switch -│ ┃ ││ ┃ │ Turn off this advice by setting config variable advice.detachedHead to false│ ┃ ││ ┃ │ HEAD is now at 6ca3f15 nix/linux: build FHS layout│ █ │ [0.24s] git submodule update --init --recursive│ ┻ │┣─╮ ││ ▼ │ export path {{thunk M8SI6J1M2237G: git submodule update --init --recursive}}/nix.lock│ █◀╯ [0.87s] git clone https://github.com/vito/tabs ./│ ┃ Cloning into '.'...│ █ [0.34s] git fetch origin 6ca3f15d70b739a7929886e20b61dbc4cbdc4e22│ ┃ From https://github.com/vito/tabs│ ┃ * branch 6ca3f15d70b739a7929886e20b61dbc4cbdc4e22 -> FETCH_HEAD│ █ [0.15s] git checkout 6ca3f15d70b739a7929886e20b61dbc4cbdc4e22│ ┃ Note: switching to '6ca3f15d70b739a7929886e20b61dbc4cbdc4e22'.│ ┃│ ┃ You are in 'detached HEAD' state. You can look around, make experimental│ ┃ changes and commit them, and you can discard any commits you make in this│ ┃ state without impacting any branches by switching back to a branch.│ ┃│ ┃ If you want to create a new branch to retain commits you create, you may│ ┃ do so (now or later) by using -c with the switch command. Example:│ ┃│ ┃ git switch -c <new-branch-name>│ ┃│ ┃ Or undo this operation with:│ ┃│ ┃ git switch -│ ┃│ ┃ Turn off this advice by setting config variable advice.detachedHead to false│ ┃│ ┃ HEAD is now at 6ca3f15 nix/linux: build FHS layout│ █ [0.16s] git submodule update --init --recursive┻ ┻
{
"thunk": {
"image": {
"thunk": {
"image": {
"archive": {
"platform": {
"os": "linux",
"arch": "amd64"
},
"file": {
"thunk": {
"thunk": {
"image": {
"thunk": {
"image": {
"thunk": {
"image": {
"thunk": {
"image": {
"thunk": {
"image": {
"ref": {
"platform": {
"os": "linux",
"arch": "amd64"
},
"repository": "nixos/nix",
"tag": "latest",
"digest": "sha256:1f8fa57de6f2f9ea5ea8d115b339fa68d2f98f20b59438bdb9d3a082ad64d4bf"
}
},
"args": [
{
"string": {
"value": "cp"
}
},
{
"string": {
"value": "-anT"
}
},
{
"dirPath": {
"path": "/nix"
}
},
{
"dirPath": {
"path": "/cache"
}
}
],
"mounts": [
{
"source": {
"cache": {
"id": "nix-cache:nixos/nix:latest@sha256:1f8fa57de6f2f9ea5ea8d115b339fa68d2f98f20b59438bdb9d3a082ad64d4bf",
"path": {
"dir": {
"path": "."
}
}
}
},
"target": {
"dir": {
"path": "/cache"
}
}
}
]
}
},
"args": [
{
"string": {
"value": "sh"
}
},
{
"string": {
"value": "-c"
}
},
{
"string": {
"value": "echo accept-flake-config = true >> /etc/nix/nix.conf"
}
}
],
"mounts": [
{
"source": {
"cache": {
"id": "nix-cache:nixos/nix:latest@sha256:1f8fa57de6f2f9ea5ea8d115b339fa68d2f98f20b59438bdb9d3a082ad64d4bf",
"path": {
"dir": {
"path": "."
}
}
}
},
"target": {
"dir": {
"path": "/nix"
}
}
}
]
}
},
"args": [
{
"string": {
"value": "sh"
}
},
{
"string": {
"value": "-c"
}
},
{
"string": {
"value": "echo experimental-features = nix-command flakes >> /etc/nix/nix.conf"
}
}
],
"mounts": [
{
"source": {
"cache": {
"id": "nix-cache:nixos/nix:latest@sha256:1f8fa57de6f2f9ea5ea8d115b339fa68d2f98f20b59438bdb9d3a082ad64d4bf",
"path": {
"dir": {
"path": "."
}
}
}
},
"target": {
"dir": {
"path": "/nix"
}
}
}
]
}
},
"args": [
{
"string": {
"value": "nix"
}
},
{
"string": {
"value": "build"
}
},
{
"string": {
"value": "-f"
}
},
{
"filePath": {
"path": "image.nix"
}
}
],
"mounts": [
{
"source": {
"logical": {
"file": {
"name": "image.nix",
"content": "bGV0CiAgZmxha2UgPSBidWlsdGlucy5nZXRGbGFrZSAiL2ZsYWtlIjsKICBpbnB1dHMgPSBmbGFrZS5pbnB1dHM7CiAgcGtncyA9IGltcG9ydCBpbnB1dHMubml4cGtncyB7fTsKICBmaHMgPSAocGtncy5jYWxsUGFja2FnZSA8bml4cGtncy9wa2dzL2J1aWxkLXN1cHBvcnQvYnVpbGQtZmhzLXVzZXJlbnYvZW52Lm5peD4ge30pIHsKICAgIG5hbWUgPSAiY29udGFpbmVyLWZocyI7CiAgICB0YXJnZXRQa2dzID0gcGtnczogd2l0aCBwa2dzOyBbd2dldF07CiAgICBtdWx0aVBrZ3MgPSBudWxsOyAgIyBEb24ndCBpbmNsdWRlIGdsaWJjJ3MgbXVsdGlsaWIKICB9OwogIHN0cmVhbSA9IHBrZ3MuZG9ja2VyVG9vbHMuc3RyZWFtTGF5ZXJlZEltYWdlIHsKICAgIG5hbWUgPSAibml4cGtncy93Z2V0IjsKICAgIGNvbnRlbnRzID0gcGtncy5zeW1saW5rSm9pbiB7CiAgICAgIG5hbWUgPSAiY29udGVudHMiOwogICAgICBwYXRocyA9IFsgZmhzIF07CiAgICB9OwogICAgY29uZmlnID0gewogICAgICBFbnYgPSBbCiAgICAgICAgIlBBVEg9L2JpbiIKICAgICAgICAiU1NMX0NFUlRfRklMRT0ke3BrZ3MuY2FjZXJ0fS9ldGMvc3NsL2NlcnRzL2NhLWJ1bmRsZS5jcnQiCiAgICAgIF07CiAgICB9OwogIH07CmluCnBrZ3MucnVuQ29tbWFuZCAid3JpdGUtZG9ja2VyLXRhciIge30gJycKICAke3N0cmVhbX0gPiAkb3V0Cicn"
}
}
},
"target": {
"file": {
"path": "image.nix"
}
}
},
{
"source": {
"thunk": {
"thunk": {
"image": {
"thunk": {
"image": {
"thunk": {
"image": {
"thunk": {
"image": {
"thunk": {
"image": {
"ref": {
"platform": {
"os": "linux",
"arch": "amd64"
},
"repository": "alpine/git",
"tag": "latest",
"digest": "sha256:66b210a97bc07bfd4019826bcd13a488b371a6cbe2630a4b37d23275658bd3f2"
}
}
}
},
"args": [
{
"string": {
"value": "git"
}
},
{
"string": {
"value": "clone"
}
},
{
"string": {
"value": "https://github.com/vito/tabs"
}
},
{
"dirPath": {
"path": "."
}
}
]
}
},
"args": [
{
"string": {
"value": "git"
}
},
{
"string": {
"value": "fetch"
}
},
{
"string": {
"value": "origin"
}
},
{
"string": {
"value": "6ca3f15d70b739a7929886e20b61dbc4cbdc4e22"
}
}
]
}
},
"args": [
{
"string": {
"value": "git"
}
},
{
"string": {
"value": "checkout"
}
},
{
"string": {
"value": "6ca3f15d70b739a7929886e20b61dbc4cbdc4e22"
}
}
]
}
},
"args": [
{
"string": {
"value": "git"
}
},
{
"string": {
"value": "submodule"
}
},
{
"string": {
"value": "update"
}
},
{
"string": {
"value": "--init"
}
},
{
"string": {
"value": "--recursive"
}
}
]
},
"path": {
"dir": {
"path": "."
}
}
}
},
"target": {
"dir": {
"path": "/flake"
}
}
},
{
"source": {
"cache": {
"id": "nix-cache:nixos/nix:latest@sha256:1f8fa57de6f2f9ea5ea8d115b339fa68d2f98f20b59438bdb9d3a082ad64d4bf",
"path": {
"dir": {
"path": "."
}
}
}
},
"target": {
"dir": {
"path": "/nix"
}
}
}
]
}
},
"args": [
{
"string": {
"value": "cp"
}
},
{
"string": {
"value": "-aL"
}
},
{
"filePath": {
"path": "result"
}
},
{
"filePath": {
"path": "image.tar"
}
}
],
"mounts": [
{
"source": {
"cache": {
"id": "nix-cache:nixos/nix:latest@sha256:1f8fa57de6f2f9ea5ea8d115b339fa68d2f98f20b59438bdb9d3a082ad64d4bf",
"path": {
"dir": {
"path": "."
}
}
}
},
"target": {
"dir": {
"path": "/nix"
}
}
}
]
},
"path": {
"file": {
"path": "image.tar"
}
}
}
}
}
}
}
},
"args": [
{
"string": {
"value": "wget"
}
},
{
"string": {
"value": "https://example.com"
}
},
{
"string": {
"value": "-O"
}
},
{
"filePath": {
"path": "index.html"
}
}
]
},
"path": {
"file": {
"path": "index.html"
}
}
}
I'm not sure if this is the right design for this yet, but it seems nifty and it works. Expect this to change at any moment. Suggestions welcome!
To serve Bass scripts in ./srv/ over HTTP on port 6455 ("bass"), run:
bass --serve 6455 ./srv/This is particularly handy for cobbling together endpoints for receiving webhooks (e.g. a GitHub App for CI/CD).
HTTP requests sent to http://localhost:6455/foo will run the ./srv/foo Bass script.
The HTTP request sent on *stdin* as a structure like the following:
{:headers {:Accept "application/json"}
:body "{\"foo\":1}"}
Values emitted to *stdout* will be sent as the response. If the script fails a 500 status code will be returned.
The UX here is very spartan at the moment. Notably there is no way to view progress over HTTP; it's only rendered server-side in the console.
I'd like the server-side to self-update somehow, but haven't figured that out yet.
The Bass project uses Bass Loop to receive GitHub webhooks and run its own builds. Docs coming soon - see the announcement for now.
Trigging builds on push is just one form of CI/CD. What if you have external dependencies you'd like to trigger builds from? What if you want to write sophisticated pipelines with fan-in and fan-out semantics?
Dunno yet! I think we're a few steps away from this, but we need to figure out the best steps.
Ideas for the future: