45个版本 (破坏性)
0.47.6 | 2024年2月22日 |
---|---|
0.47.5 | 2023年6月18日 |
0.47.3 | 2023年5月23日 |
0.46.2 | 2023年3月8日 |
0.29.0 | 2019年7月11日 |
#207 in 开发工具
230KB
5K SLoC
Toast 🥂
Toast 是一个用于将您的构建和测试等工作流程容器化的工具。您在名为 toastfile 的 YAML 文件中定义任务,Toast 将它们在您选择的 Docker 镜像中运行。构成“任务”的内容由您决定:任务可以安装系统包、编译应用程序、运行测试套件,甚至提供网页服务。任务可以依赖于其他任务,因此 Toast 可以被视为一个高级的容器化构建系统。
以下是上述示例的 toastfile
image: ubuntu
tasks:
install_gcc:
command: |
apt-get update
apt-get install --yes gcc
build:
dependencies:
- install_gcc
input_paths:
- main.c
command: gcc main.c
run:
dependencies:
- build
command: ./a.out
Toast 通过将容器提交为镜像来缓存每个任务。该镜像被标记为任务的 shell 命令的加密哈希值、复制到容器中的文件内容以及所有其他任务输入。此哈希值允许 Toast 跳过自上次运行以来未更改的任务。
除了本地缓存之外,Toast 还可以使用 Docker 仓库作为远程缓存。您、您的团队成员以及您的持续集成(CI)系统都可以共享相同的远程缓存。以这种方式使用,您的 CI 系统可以执行所有繁重的工作,例如构建和安装依赖项,这样您和您的团队能够专注于开发。
相关工具
- Docker Compose: Docker Compose 是一个方便的基于 Docker 的开发环境,与 Toast 共享许多功能。然而,它不支持定义任务(如
lint
、test
、run
等)或远程缓存。 - Nix: Nix 通过利用函数式编程的概念而不是容器化来实现可重复构建。我们是 Nix 的大粉丝。然而,与 Toast 相比,Nix 需要更大的承诺,因为您必须使用 Nix 包管理器或编写自己的 Nix 派生。无论好坏,Toast 允许您使用熟悉的语法,如
apt-get install ...
。
为了防止在使用Toast或Docker Compose等Docker相关工具时,在您的机器上积累Docker镜像,我们建议使用Docuum来执行最少最近使用(LRU)镜像淘汰。
教程
定义一个简单的任务
让我们创建一个toastfile。创建一个名为toast.yml
的文件,内容如下
image: ubuntu
tasks:
greet:
command: echo 'Hello, World!' # Toast will run this in a container.
现在运行toast
。你应该会看到以下内容
如果你再次运行它,Toast会找到没有任何变化并跳过任务
Toast缓存任务以节省你的时间。例如,你不需要每次运行测试时都重新安装你的依赖项。但是,缓存可能不适合某些任务,如运行开发服务器。你可以使用cache
选项禁用特定任务的缓存以及所有依赖于它的任务
image: ubuntu
tasks:
greet:
cache: false # Don't cache this task.
command: echo 'Hello, World!'
添加依赖项
让我们使用名为figlet
的程序让问候更有趣。我们将添加一个安装figlet
的任务,并将greet
任务改为依赖于它
image: ubuntu
tasks:
install_figlet:
command: |
apt-get update
apt-get install --yes figlet
greet:
dependencies:
- install_figlet # Toast will run this task first.
command: figlet 'Hello, World!'
运行toast
来看到一个惊人的问候
从主机导入文件
这里有一个更实际的例子。假设你想编译和运行一个简单的C程序。创建一个名为main.c
的文件
#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
return 0;
}
更新toast.yml
来编译和运行程序
image: ubuntu
tasks:
install_gcc:
command: |
apt-get update
apt-get install --yes gcc
build:
dependencies:
- install_gcc
input_paths:
- main.c # Toast will copy this file into the container before running the command.
command: gcc main.c
run:
dependencies:
- build
command: ./a.out
注意build
任务中的input_paths
数组。在这里,我们将一个文件复制到容器中,但我们可以用.
导入包含toastfile的整个目录。默认情况下,文件将被复制到容器中的/scratch
目录。命令将在该目录中运行。
现在,如果你运行toast
,你会看到以下内容
对于后续的运行,如果没有任何变化,Toast将跳过任务。但如果你更新了main.c
中的问候,Toast将在下一次调用时检测到更改并重新运行build
和run
任务。
从容器导出文件
Toast的一个常见用例是构建项目。当然,你可能想知道如何从主机机器访问容器内部产生的构建工件。使用output_paths
很容易做到
image: ubuntu
tasks:
install_gcc:
command: |
apt-get update
apt-get install --yes gcc
build:
dependencies:
- install_gcc
input_paths:
- main.c
output_paths:
- a.out # Toast will copy this file onto the host after running the command.
command: gcc main.c
当Toast运行build
任务时,它将a.out
文件复制到主机。
向任务传递参数
有时,任务需要接受参数。例如,一个deploy
任务可能想知道你是否想部署到staging
或production
集群。为此,向你的任务添加一个environment
部分
image: ubuntu
tasks:
deploy:
cache: false
environment:
CLUSTER: staging # Deploy to staging by default.
command: echo "Deploying to $CLUSTER..."
当你运行这个任务时,Toast将读取环境中的值
如果变量不存在于环境中,Toast将使用默认值
如果你想不设置默认值,将其设置为null
image: ubuntu
tasks:
deploy:
cache: false
environment:
CLUSTER: null # No default; this variable must be provided at runtime.
command: echo "Deploying to $CLUSTER..."
现在,如果你不指定CLUSTER
而运行toast deploy
,Toast会抱怨变量缺失并拒绝运行任务。
任务中列出的环境变量也适用于任何在其之后运行的任务。
运行服务器并将路径挂载到容器中
Toast不仅可以用于构建项目。假设你正在开发一个网站。你可以定义一个Toast任务来运行你的web服务器!创建一个名为index.html
的文件,内容如下
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Toast!</title>
</head>
<body>
<p>Hello, World!</p>
</body>
</html>
我们可以使用像nginx这样的Web服务器。官方的nginx
Docker镜像就足够了,但您也可以使用更通用的镜像,并定义一个Toast任务来安装nginx。
在我们的toast.yml
文件中,我们将使用ports
字段使网站在容器外部可访问。我们还将使用mount_paths
而不是input_paths
,这样我们就可以在不重新启动服务器的情况下编辑网页。
image: nginx
tasks:
serve:
cache: false # It doesn't make sense to cache this task.
mount_paths:
- index.html # Updates to this file will be visible inside the container.
ports:
- 3000:80 # Expose port 80 in the container as port 3000 on the host.
location: /usr/share/nginx/html/ # Nginx will serve the files in here.
command: nginx -g 'daemon off;' # Run in foreground mode.
现在您可以使用Toast运行服务器
配置shell
在运行任何命令之前,通常需要以某种方式配置shell。shell通常使用所谓的“启动文件”进行配置(例如,~/.bashrc
)。然而,许多shell在以非交互式、非登录模式运行时跳过了加载此类配置文件,这是Toast调用shell的方式。Toast提供了一个替代机制来配置shell,而无需创建任何特殊文件或以特定方式调用shell。
考虑以下使用Bash作为shell的toastfile,因为这是Ubuntu中默认首选的登录shell
image: ubuntu
tasks:
install_figlet:
command: |
apt-get update
apt-get install --yes figlet
如果apt-get update
失败会发生什么?由于Bash的工作方式,失败将被忽略,并继续执行后续行。您可以使用以下方式使用set -e
进行修复
image: ubuntu
tasks:
install_figlet:
command: |
set -e # Make Bash fail fast.
apt-get update
apt-get install --yes figlet
但是,将其分别添加到每个任务中是繁琐且容易出错的。相反,您可以通过以下方式一次将此添加到每个任务中,设置command_prefix
image: ubuntu
command_prefix: set -e # Make Bash fail fast.
tasks:
install_figlet:
command: |
apt-get update
apt-get install --yes figlet
对于Bash来说,我们建议更进一步,设置set -euo pipefail
而不是仅仅设置set -e
。
进入交互式shell
如果您以--shell
运行Toast,当请求的任务完成时,或者如果其中任何一个任务失败,Toast将将您放入容器内的交互式shell。此功能对于调试任务或探索容器中的内容非常有用。假设您有以下toastfile
image: ubuntu
tasks:
install_figlet:
command: |
apt-get update
apt-get install --yes figlet
您可以使用toast --shell
来玩figlet
程序
完成操作后,容器将自动删除。
Toast是如何工作的
给定要运行的一组任务,Toast计算依赖DAG的拓扑排序,以确定任务的运行顺序。然后Toast根据拓扑排序中上一个任务的镜像构建每个任务的Docker镜像,或对于第一个任务,使用基础镜像。
任意DAG的拓扑排序不一定唯一。Toast使用基于深度优先搜索的算法,按字典顺序遍历子节点。该算法是确定性的,与任务和依赖列表的顺序无关,因此重新排序toastfile中的任务不会使缓存无效。此外,toast foo bar
和toast bar foo
将保证产生相同的计划,以最大程度地利用缓存。
对于计划中的每个任务,Toast首先根据shell命令的哈希值、input_paths
的内容、计划中上一个任务的缓存键等计算一个缓存键。然后Toast将寻找带有该缓存键标记的Docker镜像。如果找到该镜像,Toast将跳过该任务。否则,Toast将创建一个容器,将任何input_paths
复制到其中,运行shell命令,将任何output_paths
从容器复制到主机,将容器提交到镜像,并删除容器。该镜像带有缓存键标记,以便任务可以在后续运行中跳过。
Toast旨在尽可能少地对容器环境做出假设。Toast仅假设存在一个程序在/bin/su
,它可以以su -c COMMAND USER
的方式调用。该程序用于以适当用户及其首选的shell运行容器中的命令。每个流行的Linux发行版都有一个支持此用法的su
实用程序。Toast包含集成测试,以确保它能与流行的基本镜像(如debian
、alpine
、busybox
等)一起工作。
Toastfile参考
一个toastfile是一个YAML文件(通常命名为toast.yml
),用于定义任务及其依赖关系。该架构包含以下顶级键和默认值
image: <required> # Docker image name with optional tag or digest
default: null # Name of default task to run or `null` to run all tasks by default
location: /scratch # Path in the container for running tasks
user: root # Name of the user in the container for running tasks
command_prefix: '' # A string to be prepended to all commands by default
tasks: {} # Map from task name to task
任务具有以下架构和默认值
description: null # A description of the task for the `--list` option
dependencies: [] # Names of dependencies
cache: true # Whether a task can be cached
environment: {} # Map from environment variable to optional default
input_paths: [] # Paths to copy into the container
excluded_input_paths: [] # A denylist for `input_paths`
output_paths: [] # Paths to copy out of the container if the task succeeds
output_paths_on_failure: [] # Paths to copy out of the container if the task fails
mount_paths: [] # Paths to mount into the container
mount_readonly: false # Whether to mount the `mount_paths` as readonly
ports: [] # Port mappings to publish
location: null # Overrides the corresponding top-level value
user: null # Overrides the corresponding top-level value
command: '' # Shell command to run in the container
command_prefix: null # Overrides the corresponding top-level value
extra_docker_arguments: [] # Additional arguments for `docker container create`
Toast自身的toastfile是一个全面的实际示例。
配置
Toast可以通过YAML配置文件进行自定义。配置文件的默认位置取决于操作系统
- 对于macOS,默认位置是
$HOME/Library/Application Support/toast/toast.yml
。 - 对于其他Unix平台,Toast遵循XDG Base Directory Specification。默认位置是
$XDG_CONFIG_HOME/toast/toast.yml
或$HOME/.config/toast/toast.yml
,如果XDG_CONFIG_HOME
未设置为绝对路径。 - 对于Windows,默认位置是
{FOLDERID_RoamingAppData}\toast\toast.yml
。
配置文件的架构将在下面的子节中描述。
缓存配置
Toast支持本地和远程缓存。默认情况下,仅启用本地缓存。远程缓存需要Docker Engine登录到Docker注册库(例如,通过docker login
)。
以下是与缓存相关的字段及其默认值
docker_repo: toast # Docker repository
read_local_cache: true # Whether Toast should read from local cache
write_local_cache: true # Whether Toast should write to local cache
read_remote_cache: false # Whether Toast should read from remote cache
write_remote_cache: false # Whether Toast should write to remote cache
每个选项都可以通过命令行选项覆盖(见下文)。
对于CI环境,通常将启用所有形式的缓存,而对于本地开发,您可能希望设置write_remote_cache: false
以避免等待远程缓存写入。
Docker CLI
您可以配置Toast使用的Docker CLI二进制文件。Toast使用PATH
环境变量来搜索指定的二进制文件。您可以使用此机制切换到Docker CLI的替换品,如Podman。
以下是与之相关的字段及其默认值
docker_cli: docker
命令行选项
默认情况下,Toast会在工作目录中查找名为toast.yml
的toastfile,然后是父目录,依此类推。toastfile中的任何路径都是相对于toastfile的位置,而不是工作目录。这意味着您可以从项目的任何位置运行Toast并获得相同的结果。
无参数运行toast
以执行默认任务,或者如果toastfile未定义默认任务,则执行所有任务。您还可以执行特定的任务及其依赖关系
toast task1 task2 task3…
以下都是支持的命令行选项
USAGE:
toast [OPTIONS] [--] [TASKS]...
OPTIONS:
-c, --config-file <PATH>
Sets the path of the config file
--docker-cli <CLI>
Sets the Docker CLI binary
-r, --docker-repo <REPO>
Sets the Docker repository for remote caching
-f, --file <PATH>
Sets the path to the toastfile
--force <TASK>...
Runs a task unconditionally, even if it’s cached
--force-all
Pulls the base image and runs all tasks unconditionally
-h, --help
Prints help information
-l, --list
Lists the tasks that have a description
-o, --output-dir <PATH>
Sets the output directory
--read-local-cache <BOOL>
Sets whether local cache reading is enabled
--read-remote-cache <BOOL>
Sets whether remote cache reading is enabled
-s, --shell
Drops you into a containerized shell after the tasks are finished
-v, --version
Prints version information
--write-local-cache <BOOL>
Sets whether local cache writing is enabled
--write-remote-cache <BOOL>
Sets whether remote cache writing is enabled
ARGS:
<TASKS>...
Sets the tasks to run
安装说明
在macOS或Linux(AArch64或x86-64)上的安装
如果您正在使用 macOS 或 Linux(AArch64 或 x86-64),可以使用以下命令安装 Toast
curl https://raw.githubusercontent.com/stepchowfun/toast/main/install.sh -LSfs | sh
相同的命令可以再次使用来更新到最新版本。
安装脚本支持以下可选环境变量
VERSION=x.y.z
(默认为最新版本)PREFIX=/path/to/install
(默认为/usr/local/bin
)
例如,以下命令将 Toast 安装到工作目录
curl https://raw.githubusercontent.com/stepchowfun/toast/main/install.sh -LSfs | PREFIX=. sh
如果您不希望使用此安装方法,可以从发布页面下载二进制文件,使用 chmod
使其可执行,并将其放置在您的 PATH
(例如,/usr/local/bin
)中的某个目录。
Windows(AArch64 或 x86-64)上的安装
如果您正在使用 Windows(AArch64 或 x86-64),请从发布页面下载最新二进制文件,并将其重命名为 toast
(如果您文件扩展名可见,则为 toast.exe
)。在您的 %PROGRAMFILES%
目录中创建一个名为 Toast
的目录(例如,C:\Program Files\Toast
),并将重命名的二进制文件放在那里。然后,在控制面板的“系统属性”部分的“高级”选项卡中,单击“环境变量...”,并将新 Toast
目录的完整路径添加到“系统变量”下的 PATH
变量中。请注意,如果 Windows 配置为非英语语言,则“程序文件”目录可能有不同的名称。
要更新现有安装,只需替换现有二进制文件即可。
使用 Homebrew 安装
如果您有 Homebrew,可以按以下方式安装 Toast
brew install toast
您可以使用 brew upgrade toast
更新现有安装。
使用 MacPorts 安装
在 macOS 上,您还可以通过 MacPorts 安装 Toast,如下所示
sudo port install toast
您可以通过以下方式更新现有安装
sudo port selfupdate
sudo port upgrade toast
使用 Cargo 安装
如果您有 Cargo,可以按以下方式安装 Toast
cargo install toast
您可以使用 --force
运行该命令来更新现有安装。
在 CI 中运行 Toast
在 CI 中运行 Toast 的最简单方法是使用 GitHub Actions。Toast 提供了一个方便的 GitHub action,您可以在您的 workflows 中使用。以下是一个简单的 workflow,其中 Toast 无参数运行
# .github/workflows/ci.yml
name: Continuous integration
on:
pull_request:
push:
branches:
- main
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: stepchowfun/toast/.github/actions/toast@main
以下是一个展示所有选项的更定制化的 workflow
# .github/workflows/ci.yml
name: Continuous integration
on:
pull_request:
push:
branches:
- main
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- if: github.event_name == 'push'
uses: docker/login-action@v3
with:
username: DOCKER_USERNAME
password: ${{ secrets.DOCKER_PASSWORD }}
- uses: stepchowfun/toast/.github/actions/toast@main
with:
file: toastfiles/toast.yml
tasks: build lint test
docker_repo: DOCKER_USERNAME/DOCKER_REPO
read_remote_cache: true
write_remote_cache: ${{ github.event_name == 'push' }}
要求
- Toast 需要 17.06.0 或更高版本的 Docker Engine。
- Toast 只与 Linux 容器一起工作;目前不支持 Windows 容器。然而,Toast 除了 Linux 主机外,还支持具有适当虚拟化能力的 macOS 和 Windows 主机,这归功于 Docker Desktop。
致谢
Toast 是受到 Airbnb CI 作业中使用的内部工具的启发。设计受到了我在开发该工具和与出色的 CI 基础设施团队一起构建 Airbnb 的 CI 系统时所学到的大量教训的影响。
特别感谢Julia Wang(《@juliahw》)提供的宝贵早期反馈。感谢她和Mark Tai(《@marktai》)想出了《Toast》这个名字。
终端动画是用asciinema和svg-term-cli制作的。
依赖项
~10–22MB
~308K SLoC