Home

Awesome

šŸ¦„ Makes Example

This is the official hands-on example for Makes.

Why

As a framework designed to simplify the development of secure and high-quality applications, Makes comprises a wide range of functionalities that may nonetheless overwhelm those who are just starting to learn how to use it.

That is why we created an example that focuses on using the most basic yet powerful builtins Makes has to offer.

This example simulates a real application with several deployments a day.

We will show how Makes is:

Secure

<img src="https://img.shields.io/badge/attr-secure-brightgreen.svg" alt="secure">

On Makes, direct and indirect dependencies for both applications and CI/CD pipelines are cryptographically signed, granting an immutable software supply chain.

Easy

<img src="https://img.shields.io/badge/attr-easy-orange.svg" alt="easy">

Makes can be installed with just one command and has dozens of generic CI/CD builtins.

Fast

<img src="https://img.shields.io/badge/attr-fast-blueviolet.svg" alt="fast">

Makes supports a distributed and completely granular cache.

Portable

<img src="https://img.shields.io/badge/attr-portable-violet.svg" alt="portable">

Makes runs on Docker, VMs, and any Linux-based OS. Such a feature greatly simplifies the task of running applications and CI/CD jobs on both local (developer machines) and remote (dev, staging, prod) environments.

Extensible

<img src="https://img.shields.io/badge/attr-extensible-blue.svg" alt="extensible">

Makes can be extended to work with any technology.

How

We will achieve this by implementing

  1. the FastAPI example;
  2. an isolated, cryptographically-signed environment for running our API;
  3. development and production environments for our API using Stackhero and Docker Compose;
  4. general-purpose linters and formatters to ensure code quality and security;
  5. a distributed cache for high build performance, and
  6. a CI/CD workflow using GitHub Actions for orchestrating all the previous items.
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

Contents

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Prerequisites

Concepts

Having a basic understanding of the following concepts will probably make this example much easier to grasp:

Below, we give a very brief introduction to

  1. Nix and
  2. Nixpkgs

as they are foundational components of Makes.

Software

<img src="https://img.shields.io/badge/attr-easy-orange.svg" alt="easy">

You just need to either have Makes or Docker installed on your system.

Backbone: Nix and Nixpkgs

<img src="https://img.shields.io/badge/attr-secure-brightgreen.svg" alt="secure"> <img src="https://img.shields.io/badge/attr-fast-blueviolet.svg" alt="fast"> <img src="https://img.shields.io/badge/attr-portable-violet.svg" alt="portable">

Makes relies on some core technology in order to work.

Nix is a package manager that treats packages in a purely functional manner. That is, packages are built by functions that do not have secondary effects. They never change once built. Nix can be installed on any Linux-based OS.

Makes relies on Nix to build reproducible and immutable workflows and environments. It also takes advantage of its granular cache obtained by having isolated packages.

Nixpkgs "is a collection of over 80,000 software packages that can be installed with the Nix package manager." The main advantage of Nixpkgs over other package repositories is that packages are reproducible and pinned to an exact commit version. Compiled binaries for such packages are also accessible through cache.nixos.org.

Makes uses Nixpkgs for provisioning OS dependencies.

Running any Makes job

<img src="https://img.shields.io/badge/attr-easy-orange.svg" alt="easy"> <img src="https://img.shields.io/badge/attr-portable-violet.svg" alt="portable">

Makes has the ability to fetch any repository that supports it, so you won't have to clone this example unless you want to modify it.

Locally:

m github:fluidattacks/makes-example@main

Using Docker:

docker run ghcr.io/fluidattacks/makes/amd64:latest m github:fluidattacks/makes-example@main

Running Makes on containers

<img src="https://img.shields.io/badge/attr-easy-orange.svg" alt="easy"> <img src="https://img.shields.io/badge/attr-portable-violet.svg" alt="portable"> <img src="https://img.shields.io/badge/attr-extensible-blue.svg" alt="extensible">

As mentioned before, Makes also has a Docker container. We can take advantage of this on CI/CD providers, Kubernetes, Nomad, Stackhero, and basically anything that runs containers.

In this example, we have a development and a production pipeline using GitHub Actions. Both of them can be found under .github/workflows.

Let's take a look at this job in .github/workflows/dev.yml:

formatNix:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@f095bcc56b7c2baf48f3ac70d6d6782f4f553222
    - uses: docker://ghcr.io/fluidattacks/makes/amd64:latest
      name: /formatNix
      with:
        args: sh -c "chown -R root:root /github/workspace && m . /formatNix"

By looking at this portion of code we can see that we use the Makes container to run the m . /formatNix command.

Thanks to this feature you can make your entire ecosystem reproducible on any remote environment that supports containers.

The makes.nix file

You will find this file in the root of the repository. According to the documentation, in this file you can specify any builtin supported by Makes and configure it to run on your project.

Example builtin

<img src="https://img.shields.io/badge/attr-easy-orange.svg" alt="easy">

Let's review one of the builtins used:

{
  lintBash = {
    enable = true;
    targets = ["/"];
  };
}

The lintBash builtin lints all bash files within the specified path /.

Let's try running it!

$ m github:fluidattacks/makes-example@main /lintBash

                               šŸ¦„ Makes
                             v22.11-linux

ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ Fetching github:fluidattacks/makes-example@main ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€

Initialized empty Git repository in /tmp/makes-a7nxrsao/.git/
Cached from /home/nixos/.makes/cache/sources/github-fluidattacks-makes-example-main
remote: Enumerating objects: 33, done.
remote: Counting objects: 100% (33/33), done.
remote: Compressing objects: 100% (28/28), done.
remote: Total 33 (delta 1), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (33/33), 54.52 KiB | 9.09 MiB/s, done.
From /home/nixos/.makes/cache/sources/github-fluidattacks-makes-example-main
 * [new branch]      main       -> main
Switched to branch 'main'

ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ Building project configuration ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€

/nix/store/1z12m8gfyp0wsc9wx9b01vxfci9872hp-config.json

ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ Building /lintBash ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€

/nix/store/6nkq7ykmn6l177zri1hh9wdng1q0468x-lint-bash
ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ Provenance ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€
Attestation: /home/nixos/.makes/provenance-lintBash.json
SHA-256: 427e69ed2e200ec82cd9bf33fa015d4afa40480020db1cd6a6ea47b91b996147

ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ šŸ¤™ Success! ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€

It temporarily clones the provided repository and then executes /lintBash within it, granting compliance with good practices in all bash files.

Working with a Nixpkgs version

<img src="https://img.shields.io/badge/attr-secure-brightgreen.svg" alt="secure">

We can also specify what version of Nixpkgs we want to use by using fetchNixpkgs.

{
  inputs = {
    nixpkgs = fetchNixpkgs {
      rev = "f88fc7a04249cf230377dd11e04bf125d45e9abe";
      sha256 = "1dkwcsgwyi76s1dqbrxll83a232h9ljwn4cps88w9fam68rf8qv3";
    };
  };
}

We can later reference this version of Nixpkgs to install any package we want.

Using imports

<img src="https://img.shields.io/badge/attr-easy-orange.svg" alt="easy"> <img src="https://img.shields.io/badge/attr-extensible-blue.svg" alt="extensible">

Another important builtin is:

{
  imports = [
    ./api/makes.nix
  ];
}

The imports builtin serves a very simple purpose, which is being able to specify other makes.nix files for Makes to import them.

Any supported builtin can be configured by either adding it to the main makes.nix file or to an imported one.

Configuring the cache

<img src="https://img.shields.io/badge/attr-secure-brightgreen.svg" alt="secure"> <img src="https://img.shields.io/badge/attr-easy-orange.svg" alt="easy"> <img src="https://img.shields.io/badge/attr-fast-blueviolet.svg" alt="fast">

A decentralized cache for speeding up builds that relies on Cachix can be configured as follows:

{
  cache = {
    readNixos = true;
    extra = {
      makes = {
        enable = true;
        pubKey = "makes.cachix.org-1:zO7UjWLTRR8Vfzkgsu1PESjmb6ymy1e4OE9YfMmCQR4=";
        token = "CACHIX_AUTH_TOKEN";
        type = "cachix";
        url = "https://makes.cachix.org";
        write = true;
      };
    };
  };
}

This allows anyone running Makes to pull already-built Nix derivations so they don't have to build the same thing twice. All derivations are cryptographically signed, which helps avoid cache tampering.

In case you did not know, Cachix offers a free tier for open-source projects!

The example API

In the api directory you will find several relevant paths. Such paths represent the core components required to make the API work.

API Source Code

The path api/src contains the source code for the example API.

API Environment

<img src="https://img.shields.io/badge/attr-secure-brightgreen.svg" alt="secure"> <img src="https://img.shields.io/badge/attr-fast-blueviolet.svg" alt="fast"> <img src="https://img.shields.io/badge/attr-extensible-blue.svg" alt="extensible">

Under the path api/env, you will find the implementation of an isolated environment for all the dependencies required by the API to work.

API makes.nix

<img src="https://img.shields.io/badge/attr-easy-orange.svg" alt="easy"> <img src="https://img.shields.io/badge/attr-extensible-blue.svg" alt="extensible">

This makes.nix file contains several linters that run on the API source code.

An interesting job is this one:

{
  lintPython = {
    modules = {
      api = {
        searchPaths.source = [outputs."/api/env"];
        src = "/api/src";
      };
    };
  };
}

Notice that it uses searchPaths.source for loading the API environment previously described. The linter needs this environment to run some checks like static type checking.

API main.nix

<img src="https://img.shields.io/badge/attr-secure-brightgreen.svg" alt="secure"> <img src="https://img.shields.io/badge/attr-portable-violet.svg" alt="portable"> <img src="https://img.shields.io/badge/attr-extensible-blue.svg" alt="extensible">

This is where a lot of the magic happens.

{
  inputs,
  makeScript,
  outputs,
  projectPath,
  ...
}:
makeScript {
  replace = {
    __argApiSrc__ = projectPath "/api/src";
  };
  name = "api";
  searchPaths.source = [outputs."/api/env"];
  entrypoint = ./entrypoint.sh;
}

This file uses makeScript to serve the API. Here is a detailed description of every parameter

Let's run it!

$ m github:fluidattacks/makes-example@main /api

                                    šŸ¦„ Makes
                                  v22.11-linux

ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ Fetching github:fluidattacks/makes-example@main ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€

Initialized empty Git repository in /tmp/makes-tz6mczs4/.git/
Cached from /home/dsalazar/.makes/cache/sources/github-fluidattacks-makes-example-main
remote: Enumerating objects: 26, done.
remote: Counting objects: 100% (26/26), done.
remote: Compressing objects: 100% (22/22), done.
remote: Total 26 (delta 1), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (26/26), 9.05 KiB | 2.26 MiB/s, done.
From /home/dsalazar/.makes/cache/sources/github-fluidattacks-makes-example-main
 * [new branch]      main       -> main
Switched to branch 'main'

ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ Building project configuration ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€

/nix/store/2mnjjd4gkzrbyr7g97yl19n2y4zv0hi3-config.json

ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ Building /api ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€

/nix/store/sbi0rf8x3a5p9kyxhv8s0q2sxxmg8fsv-api

ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ Running ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€

/nix/store/aim1v9k173mrnsi8qdngj0q42miladdg-src /home/dsalazar/fluidattacks/makes-example
INFO:     Will watch for changes in these directories: ['/nix/store/aim1v9k173mrnsi8qdngj0q42miladdg-src']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [58876] using statreload
INFO:     Started server process [58878]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

That's it!

Now you can get a running environment of the example API with just one command, anywhere. The entire environment exists within Nix, granting full portability and ensuring that no dependencies are directly installed on your system. The entire dependency tree is fully pinned and cryptographically signed. If a component changes, Makes will rebuild from there downwards, reusing all caches that remain unchanged and making sure all signatures are correct.

API Deployments

<img src="https://img.shields.io/badge/attr-easy-orange.svg" alt="easy"> <img src="https://img.shields.io/badge/attr-portable-violet.svg" alt="portable"> <img src="https://img.shields.io/badge/attr-extensible-blue.svg" alt="extensible">

In the api/deploy path we will find the job for deploying the API.

The job takes one of these two parameters

It requires these external variables to be exported

Its relevant files are these:

Let's give it a try!

$ export STACKHERO_SERVICE_ID=XXXXXXXXXXX
$ export STACKHERO_PASSWORD=XXXXXXXXXXX
$ m github:fluidattacks/makes-example@main /api/deploy prod

                                    šŸ¦„ Makes
                                  v22.11-linux

ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ Fetching github:fluidattacks/makes-example@main ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€

Initialized empty Git repository in /tmp/makes-_molt5o0/.git/
From github:fluidattacks/makes-example@main
remote: Enumerating objects: 31, done.
remote: Counting objects: 100% (31/31), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 31 (delta 1), reused 20 (delta 0), pack-reused 0
Unpacking objects: 100% (31/31), 14.88 KiB | 476.00 KiB/s, done.
From https://github.com/fluidattacks/makes-example
 * [new branch]      main       -> main
Switched to branch 'main'

ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ Building project configuration ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€

/nix/store/5rdc529zr6rx9n2g56npbnif3z4xb6c7-config.json

ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ Building /api/deploy ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€

these 2 derivations will be built:
  /nix/store/q0bhl11zsikf2a381lp9hmgg4wffpna6-make-template-for-api-deploy.drv
  /nix/store/pchwjz1z7qw7d8z6nvpvi3j109k8gch9-api-deploy.drv
building '/nix/store/q0bhl11zsikf2a381lp9hmgg4wffpna6-make-template-for-api-deploy.drv'...
building '/nix/store/pchwjz1z7qw7d8z6nvpvi3j109k8gch9-api-deploy.drv'...
[INFO] Copying files
/nix/store/v5nj2z092pm0xwhn30k5x3llfqkr78wq-api-deploy

ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ Running ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€

/nix/store/dby4nqrn19p065gh7vfyi7w0cmg328sx-deploy /home/dsalazar/fluidattacks/makes-example
/tmp/tmp.xSaQBmovKs /nix/store/dby4nqrn19p065gh7vfyi7w0cmg328sx-deploy /home/dsalazar/fluidattacks/makes-example
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 10240  100 10240    0     0   8258      0  0:00:01  0:00:01 --:--:--  8264
/nix/store/dby4nqrn19p065gh7vfyi7w0cmg328sx-deploy /home/dsalazar/fluidattacks/makes-example
Killing makes-example-main ... done
Removing makes-example-main ... done
Network app is external, skipping
Creating makes-example-main ... done

ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ šŸ€ Success! ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€

After a few minutes, when we go to https://makes.fluidattacks.com/docs, we get the following:

Production environment

Deployment jobs for both development and production are supported using GitHub Actions. Every time a developer opens a pull request, a development environment for the API is created. Similarly, once that pull request is merged, a new version of the API is deployed to production.

References