All Projects → stepchowfun → Toast

stepchowfun / Toast

Licence: other
Containerize your development and continuous integration environments. 🥂

Programming Languages

rust
11053 projects

Projects that are alternatives of or similar to Toast

Lastbackend
System for containerized apps management. From build to scaling.
Stars: ✭ 1,536 (+105.35%)
Mutual labels:  ci, continuous-integration, containers
Iceci
IceCI is a continuous integration system designed for Kubernetes from the ground up.
Stars: ✭ 29 (-96.12%)
Mutual labels:  ci, continuous-integration, containers
Flubucore
A cross platform build and deployment automation system for building projects and executing deployment scripts using C# code.
Stars: ✭ 695 (-7.09%)
Mutual labels:  build-tool, build-system, continuous-integration
Rok8s Scripts
Opinionated scripts for managing application deployment lifecycle in Kubernetes
Stars: ✭ 248 (-66.84%)
Mutual labels:  ci, continuous-integration, containers
Buildbot
Python-based continuous integration testing framework; your pull requests are more than welcome!
Stars: ✭ 4,735 (+533.02%)
Mutual labels:  ci, continuous-integration
Cbt
CBT - fun, fast, intuitive, compositional, statically checked builds written in Scala
Stars: ✭ 489 (-34.63%)
Mutual labels:  build-tool, build-system
Drone
Drone is a Container-Native, Continuous Delivery Platform
Stars: ✭ 24,287 (+3146.93%)
Mutual labels:  ci, continuous-integration
Binci
🐳 Containerize your development workflow.
Stars: ✭ 671 (-10.29%)
Mutual labels:  continuous-integration, containers
Gitlab Ci Pipeline Php
☕️ Docker images for test PHP applications with Gitlab CI (or any other CI platform!)
Stars: ✭ 451 (-39.71%)
Mutual labels:  ci, continuous-integration
Php Censor
PHP Censor is an open source self-hosted continuous integration server for PHP projects.
Stars: ✭ 619 (-17.25%)
Mutual labels:  ci, continuous-integration
Awesome Bazel
A curated list of Bazel rules, tooling and resources.
Stars: ✭ 640 (-14.44%)
Mutual labels:  build-system, continuous-integration
Modern Cpp Template
A template for modern C++ projects using CMake, Clang-Format, CI, unit testing and more, with support for downstream inclusion.
Stars: ✭ 690 (-7.75%)
Mutual labels:  ci, continuous-integration
Bzppx Codepub
暴走皮皮虾之代码发布系统,是现代的持续集成发布系统,由后台管理系统和agent两部分组成,一个运行着的agent就是一个节点,本系统并不是造轮子,是"鸟枪"到"大炮"的创新,对"前朝遗老"的革命.
Stars: ✭ 471 (-37.03%)
Mutual labels:  ci, continuous-integration
Erlang.mk
A build tool for Erlang that just works.
Stars: ✭ 538 (-28.07%)
Mutual labels:  build-tool, build-system
Xcov
Nice code coverage reporting without hassle
Stars: ✭ 467 (-37.57%)
Mutual labels:  ci, continuous-integration
Crossbuild
🌍 multiarch cross compiling environments
Stars: ✭ 632 (-15.51%)
Mutual labels:  build-tool, build-system
Concourse
Concourse is a container-based continuous thing-doer written in Go.
Stars: ✭ 6,070 (+711.5%)
Mutual labels:  ci, continuous-integration
Abstruse
Abstruse is a free and open-source CI/CD platform that tests your models and code.
Stars: ✭ 704 (-5.88%)
Mutual labels:  ci, continuous-integration
Gocd
Main repository for GoCD - Continuous Delivery server
Stars: ✭ 6,314 (+744.12%)
Mutual labels:  ci, continuous-integration
Realize
Realize is the #1 Golang Task Runner which enhance your workflow by automating the most common tasks and using the best performing Golang live reloading.
Stars: ✭ 4,162 (+456.42%)
Mutual labels:  build-tool, build-system

Toast 🥂

Build status

Toast is a tool for doing work in containers. You define tasks in a YAML file called a toastfile, and Toast runs them in a containerized environment based on a Docker image of your choosing. What constitutes a "task" is up to you: tasks can install system packages, build an application, run a test suite, or even serve web pages.

Welcome to Toast.

Here's the toastfile for the example shown above:

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 caches each task by committing the container to an image. The image is tagged with a cryptographic hash of the shell command for the task, the contents of the files copied into the container, and all the other task inputs. This hash allows Toast to skip tasks that haven't changed since the last run.

In addition to local caching, Toast can use a Docker registry as a remote cache. You, your teammates, and your continuous integration (CI) system can all share the same remote cache. Used in this way, your CI system can do all the heavy lifting like building and installing dependencies so you and your team can focus on development.

Related tools:

  • Docker Compose: Docker Compose is a convenient Docker-based development environment which shares many features with Toast. However, it doesn't support defining tasks (like lint, test, run, etc.) or remote caching.
  • Nix: Nix achieves reproducible builds by leveraging ideas from functional programming rather than containerization. We're big fans of Nix. However, Nix requires a larger commitment compared to Toast because you have to use the Nix package manager or write your own Nix derivations. For better or worse, Toast allows you to use familiar idioms like apt-get install ....

To prevent Docker images from accumulating on your machine when using Docker-related tools such as Toast or Docker Compose, we recommend using Docuum to perform least recently used (LRU) image eviction.

Table of contents

Tutorial

Defining a simple task

Let's create a toastfile. Create a file named toast.yml with the following contents:

image: ubuntu
tasks:
  greet:
    command: echo 'Hello, World!' # Toast will run this in a container.

Now run toast. You should see the following:

Defining a simple task.

If you run it again, Toast will find that nothing has changed and skip the task:

Caching.

Toast caches tasks to save you time. For example, you don't want to reinstall your dependencies every time you run your tests. However, caching may not be appropriate for some tasks, like running your development server. You can disable caching for a specific task and all tasks that depend on it with the cache option:

image: ubuntu
tasks:
  greet:
    cache: false # Don't cache this task.
    command: echo 'Hello, World!'

Adding a dependency

Let's make the greeting more fun with a program called figlet. We'll add a task to install figlet, and we'll change the greet task to depend on it:

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!'

Run toast to see a marvelous greeting:

Adding a dependency.

Importing files from the host

Here's a more realistic example. Suppose you want to compile and run a simple C program. Create a file called main.c:

#include <stdio.h>

int main(void) {
  printf("Hello, World!\n");
}

Update toast.yml to compile and run the program:

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

Notice the input_paths array in the build task. Here we are copying a single file into the container, but we could instead import the entire directory containing the toastfile with .. By default, the files will be copied into a directory called /scratch in the container. The commands will be run in that directory as well.

Now if you run toast, you'll see this:

Importing files from the host.

For subsequent runs, Toast will skip the task if nothing has changed. But if you update the greeting in main.c, Toast will detect the change and rerun the build and run tasks on the next invocation.

Exporting files from the container

A common use case for Toast is to build a project. Naturally, you might wonder how to access the build artifacts produced inside the container from the host machine. It's easy to do with 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

When Toast runs the build task, it will copy the a.out file to the host.

Exporting files from the container.

Passing arguments to a task

Sometimes it's useful for tasks to take arguments. For example, a deploy task might want to know whether you want to deploy to the staging or production cluster. To do this, add an environment section to your task:

image: ubuntu
tasks:
  deploy:
    cache: false
    environment:
      CLUSTER: staging # Deploy to staging by default.
    command: echo "Deploying to $CLUSTER..."

When you run this task, Toast will read the value from the environment:

Passing arguments to a task.

If the variable does not exist in the environment, Toast will use the default value:

Using argument defaults.

If you don't want to have a default, set it to null:

image: ubuntu
tasks:
  deploy:
    cache: false
    environment:
      CLUSTER: null # No default; this variable must be provided at runtime.
    command: echo "Deploying to $CLUSTER..."

Now if you run toast deploy without specifying a CLUSTER, Toast will complain about the missing variable and refuse to run the task.

Environment variables listed in a task are also set for any tasks that run after it.

Running a server and mounting paths into the container

Toast can be used for more than just building a project. Suppose you're developing a website. You can define a Toast task to run your web server! Create a file called index.html with the following contents:

<!DOCTYPE html>
<html>
  <head>
    <title>Welcome to Toast!</title>
  </head>
  <body>
    <p>Hello, World!</p>
  </body>
</html>

We can use a web server like nginx. The official nginx Docker image will do, but you could also use a more general image and define a Toast task to install nginx.

In our toast.yml file, we'll use the ports field to make the website accessible outside the container. We'll also use mount_paths rather than input_paths to synchronize files between the host and the container while the server is running.

image: nginx
tasks:
  serve:
    cache: false # It doesn't make sense to cache this task.
    mount_paths:
      - index.html # Synchronize this file between the host and 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.

Now you can use Toast to run the server:

Running a server.

Dropping into a shell

If you run Toast with --shell, Toast will drop you into an interactive shell inside the container when the requested tasks are finished, or if any of them fails. This feature is useful for debugging tasks or exploring what's in the container. Suppose you have the following toastfile:

image: ubuntu
tasks:
  install_figlet:
    command: |
      apt-get update
      apt-get install --yes figlet

You can run toast --shell to play with the figlet program:

Dropping into a shell.

When you're done, the container is deleted automatically.

How Toast works

Given a set of tasks to run, Toast computes a topological sort of the dependency DAG to determine in what order to run the tasks. Toast builds a Docker image for each task based on the image from the previous task, or the base image in the case of the first task. Because Docker doesn't support combining two arbitrary images into one (for good reasons), Toast doesn't run tasks in parallel. You're free to use parallelism within individual tasks, of course.

The topological sort of an arbitrary DAG is not necessarily unique. Toast uses an algorithm based on depth-first search, traversing children in lexicographical order. The algorithm is deterministic and invariant to the order in which tasks and dependencies are listed, so reordering tasks in a toastfile will not invalidate the cache. Furthermore, toast foo bar and toast bar foo are guaranteed to produce identical schedules to maximize cache utilization.

For each task in the schedule, Toast first computes a cache key based on a hash of the shell command, the contents of the input_paths, the cache key of the previous task in the schedule, etc. Toast will then look for a Docker image tagged with that cache key. If the image is found, Toast will skip the task. Otherwise, Toast will create a container, copy any input_paths into it, run the shell command, copy any output_paths from the container to the host, commit the container to an image, and delete the container. The image is tagged with the cache key so the task can be skipped for subsequent runs.

Toast aims to make as few assumptions about the container environment as possible. Toast only assumes there is a program at /bin/su which can be invoked as su -c COMMAND USER. This program is used to run commands for tasks in the container as the appropriate user with their preferred shell. Every popular Linux distribution has a su utility that supports this usage. Toast has integration tests to ensure it works with popular base images such as debian, alpine, busybox, etc.

Toastfiles

A toastfile is a YAML file (typically named toast.yml) that defines tasks and their dependencies. The schema contains three top-level keys:

image:   <Docker image name with optional tag or digest>
default: <name of default task to run or `null` to run all tasks by default>
tasks:   <map from task name to task>

Tasks have the following schema and defaults:

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
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: /scratch          # Path in the container for running this task
user: root                  # Name of the user in the container for running this task
command: ''                 # Shell command to run in the container

The toastfile for Toast itself is a comprehensive real-world example.

Cache configuration

Toast supports local and remote caching. By default, only local caching is enabled. Remote caching requires that the Docker Engine is logged into a Docker registry (e.g., via docker login).

The caching behavior can be customized with a configuration file. The default location of the configuration file depends on the operating system:

  • For macOS, the default location is ~/Library/Preferences/toast/toast.yml.
  • For other Unix platforms, Toast follows the XDG Base Directory Specification. The default location is ~/.config/toast/toast.yml unless overridden by the XDG_CONFIG_HOME environment variable.

The configuration file has the following schema and defaults:

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

Each of these options can be overridden via command-line options (see below).

A typical configuration for a CI environment will enable all forms of caching, whereas for local development you may want to set write_remote_cache: false to avoid waiting for remote cache writes.

Command-line options

By default, Toast looks for a toastfile called toast.yml in the working directory, then in the parent directory, and so on. Any paths in the toastfile are relative to where the toastfile lives, not the working directory. This means you can run Toast from anywhere in your project and get the same results.

Run toast with no arguments to execute the default task, or all the tasks if the toastfile doesn't define a default. You can also execute specific tasks and their dependencies:

toast task1 task2 task3…

Here are all the supported command-line options:

USAGE:
    toast [OPTIONS] [TASKS]...

OPTIONS:
    -c, --config-file <PATH>
            Sets the path of the config file

    -f, --file <PATH>
            Sets the path to the toastfile

    -h, --help
            Prints help information

    -l, --list
            Lists the tasks in the toastfile

        --read-local-cache <BOOL>
            Sets whether local cache reading is enabled

        --read-remote-cache <BOOL>
            Sets whether remote cache reading is enabled

    -r, --repo <REPO>
            Sets the Docker repository

    -s, --shell
            Drops you into a 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

Installation

Easy installation

If you are running macOS or a GNU-based Linux on an x86-64 CPU, you can install Toast with this command:

curl https://raw.githubusercontent.com/stepchowfun/toast/main/install.sh -LSfs | sh

The same command can be used again to update Toast to the latest version.

NOTE: Piping curl to sh is dangerous since the server might be compromised. If you're concerned about this, you can download and inspect the installation script or choose one of the other installation methods.

The installation script can be customized with the following environment variables:

  • VERSION=x.y.z (defaults to the latest version)
  • PREFIX=/path/to/install (defaults to /usr/local/bin)

For example, the following will install Toast into the working directory:

curl https://raw.githubusercontent.com/stepchowfun/toast/main/install.sh -LSfs | PREFIX=. sh

Installation with Homebrew

On macOS with Homebrew installed, you can install Toast by running:

brew install toast

Installation with Cargo

If you have Cargo, you can install Toast as follows:

cargo install toast

You can run that command with --force to update an existing installation.

Manual installation

The releases page has precompiled binaries for macOS or Linux systems running on an x86-64 CPU. You can download one of them and place it in a directory listed in your PATH.

Running Toast in CI

The easiest way to run Toast in CI is to use GitHub Actions. Toast provides a convenient GitHub action that you can use in your workflows. Here's a simple workflow that runs Toast with no arguments:

# .github/workflows/ci.yml
name: Continuous integration
on: [push, pull_request]
jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/[email protected]
    - uses: stepchowfun/toast/.github/actions/[email protected]

Here's a more customized workflow that showcases all the options:

# .github/workflows/ci.yml
name: Continuous integration
on: [push, pull_request]
jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/[email protected]
    - uses: azure/[email protected]
      with:
        username: DOCKER_USERNAME
        password: ${{ secrets.DOCKER_PASSWORD }}
      if: github.event_name == 'push'
    - uses: stepchowfun/toast/.github/actions/[email protected]
      with:
        file: toastfiles/toast.yml
        tasks: build lint test
        repo: DOCKER_USERNAME/DOCKER_REPO
        write_remote_cache: ${{ github.event_name == 'push' }}

Requirements

  • Toast requires Docker Engine 17.06.0 or later.
  • Only Linux-based Docker images are supported. Toast can run on any Unix-based platform capable of running such images, e.g., macOS with Docker Desktop.

Acknowledgements

Toast was inspired by an in-house tool used at Airbnb for CI jobs. The design was heavily influenced by the lessons I learned working on that tool and building out Airbnb's CI system with the fabulous CI Infrastructure Team.

Special thanks to Julia Wang (@juliahw) for valuable early feedback. Thanks to her and Mark Tai (@marktai) for coming up with the name Toast.

The terminal animations were produced with asciinema and svg-term-cli.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].