open-vault/packages-oss.lock
Sam Salisbury 25137a1702
ci/packagespec (#9653)
* add packagespec build system

- The majority of changes in this commit are files generated
  by packagespec (everything in the packages-oss.lock directory).

* add .yamllint

* update to packagespec@fd54aea4

* ci: bust packagespec cache

- Change to packagespec results in package IDs that can use
  git tag refs, not just commit refs.

* update to packagepsec@5fc121d0

- This busts all caches, because of a change to the way we
  no longer traverse from tag refs to commit refs, due to
  the potential confusion this can cause.
- See fd54aea482
  for the original change to packagespec necessitating this.

* update to packagespec@5e6c87b6

- This completes the change to allowing git tag refs
  to be used for source IDs, begun in f130b940a8fbe3e9398225b08ea1d63420bef7b6

* update to packagespec@4d3c9e8b

- This busts cache, needed to apply previous change.

* remove RELEASE_BUILD_WORKFLOW_NAME

* update packagespec, add watch-ci target

* fix package names (do not refer to EDITION)

* remove EDITION input from packages-oss.yml

* bump package cache, update packagespec

* update packagespec, add 'aliases' target

* update packagespec; less output noise

* ci: give release bundle file a better name

- When performing a release build, this will include the build ID
  as part of the name, making it easier to distinguish from other
  builds.

* ci: create aliases tarball artifact

* ci: cache package metadata files

* ci: add metadata artifact

* ci: bust circleci package cache

* Revert "ci: bust circleci package cache"

This reverts commit 1320d182613466f0999d63f5742db12ac17f8e92.

* ci: remove aliases artifact

* ci: use buildID not workflowName to id artifacts

* packages: add BUNDLE_NAME metadata

* do not cache package metadata with binaries

* ci: bump package cache

* ci: debugging

* ci: fix package cache; update packagespec

* ci: update packagespec to 10e7beb2

* ci: write package metadata and aliases

* ci: switch to .zip artifacts

* switch package bundle back to tar.gz (from zip)

- Because of the way zip works, the zip archive was over 2GB rather than under 750MB as with tar.gz.

* bump packagespec, adds list-staged-builds

* update packagespec

* add publish stub + general tidy up

* bump packagespec

* bump packagespec; add make publish-config

* Makefile: tidy up packagespec targets

* pass PRODUCT_REPO_ROOT to packagespec

* bump go to 1.14.6

* packages-oss.yml: use more explicit base image

* bump packagespec to b899b7c1

* bump packagespec to f040ce8f

* packages-oss.yml: pin base image to digest

- This digest is pointed to by debian:buster-20200720
- Using a specific digest ensures that builds use the same
  base image in all contexts

* add release-repo; bump packagespec

* remove BUILD_TAGS and -tags flag

* bump packagespec to e444f742

* bump to go1.14.7

* ci: bump to go1.14.7
2020-08-11 10:00:59 +01:00
..
layers ci/packagespec (#9653) 2020-08-11 10:00:59 +01:00
.gitattributes ci/packagespec (#9653) 2020-08-11 10:00:59 +01:00
Makefile ci/packagespec (#9653) 2020-08-11 10:00:59 +01:00
README.md ci/packagespec (#9653) 2020-08-11 10:00:59 +01:00
build.mk ci/packagespec (#9653) 2020-08-11 10:00:59 +01:00
config.mk ci/packagespec (#9653) 2020-08-11 10:00:59 +01:00
layer.mk ci/packagespec (#9653) 2020-08-11 10:00:59 +01:00
pkgs.yml ci/packagespec (#9653) 2020-08-11 10:00:59 +01:00

README.md

WARNING: Do not EDIT or MERGE this file, it is generated by 'packagespec lock'.

This directory contains a build system for producing multiple different packages from the same source code (i.e. a single git commit).

The build system allows the definition of packages which are near complete descriptions of the build environment and build command used to produce each package. This set of packages is used to generate CI configuration, and drives the building of builder images (via layer.mk) and final packages (build.mk).

Packages include a set of layers which are individually cacheable stages of the build, expressed as Dockerfile templates and source definitions, and cached as Docker images.

Why?

HashiCorp ship software written in Go, which must be compiled for multiple different platforms, and with different build tags and compiler flags etc. For some of our software there are many tens of variations that must be built for a single commit. Managing these by hand is too onerous a task and one that may easily result in error. The usual solution is to write iterative programs that directly output the various binaries, however this itself can be difficult to understand, and difficult to observe clearly.

By separating the workflows of defining packages and then building them we end up with an easy to understand intermediate representation of each package (packages.lock). Not only is it easy to understand, but also to consume for other purposes, such as generating CI pipelines, or programattically editing to further automation efforts.

packages.lock essentially contains all the non-source inputs to each package along with pointers to the source itself. We cache all build layers/packages and intermediate files according to their spec + source (all the inputs). That is, each item in the cache, and the eventual packages we build are input-addressable, meaning we can fairly aggressively cache everything, with a simple invalidation rule: if any input changes, then invalidate the cache.

Workflow

The workflow is to edit packages.yml which is the human-editable description of all the packages and build layers, and then to run make packages which translates that definition into the packages.lock file tree.

Note: at present, only HashiCorp employees have access to the packagespec tool used by the Makefile to generate packages.lock, this may or may not change in future.

The convenience command make build selects the first of these packages that matches your local GOOS and GOARCH and builds that one.

Implementation

There are two separate workflows: defining packages (in packages.yml) and building packages (build.mk). For conveninience the main Makefile exposes targets that invoke those files on your behalf, which you should always use unless debugging the system. In CI there is another useful workflow: building the builder images themselves and saving them to an archive file for caching. This is implemented in layer.mk.

config.mk

config.mk is included by all the other make files, and contains global configuration as well as some utility macros and lists of required tools to install.

build.mk

build.mk produces package files for distribution by running the build command inside the relevant build container for that package.

layer.mk

layer.mk contains all the code for building and caching the build layers, it is included by build.mk so that you don't need to separately build layers locally, but in CI you can invoke it directly to pre-cache layers.

packages.yml

packages.yml is the human-editable defition of package specs.

packages.lock

packages.lock contains the fully expanded version of packages.yml, including the rendered dockerfiles in packages.lock/layers. When packages.lock changes after running 'make packages' you should also re-generate CI config by running make ci-config in the root of the repo, and commit them both.

Build internals

1. Build each builder image layer

a. Build source archives, assign SOURCE_ID

The build process uses the definitions in packages.lock to generate source archives (using the layers' source-include and source-exclude fields) from either the local filesystem, or direct from git if PRODUCT_REVISION is set. It assigns each set of source code a SOURCE_ID which is either the SHA of the latest git commit to affect any of that code, or else if the code is dirty (contains uncommitted changes), a SHA1 sum of the latest commit plus the output of 'git diff' in order to make it unique

b. Build the docker image

Using the Dockerfile from packages.lock, the source code from the source archive, and a reference to the base image (the one from the layer below) build a Docker image. Each layer in packagespec.lock is addressed (has an ID) which is a hash of its content, its source definition, and the ID of its parent layer. This layer ID is combined with the SOURCE_ID to produce the name of the Docker image (LAYER_ID:SOURCE_ID-PARENTHASH). We also append 'PARENTHASH' Which is a cumulative hash of each build layer's ID up to that point. This ensures that if any base layer changes input-wise in any way, then all subsequent layers are invalidated.

2. Build the package

Using the top-most buider image, which must by now contain all the source code, execute the build command (passing in some externally calculated paths) to compile and package the software.

Use docker cp to copy out the built artefact. We do this instead of using mounts, because that's more compatible with various remote Docker scenarios.

3. Address the package.

Each package is addressed by all its inputs (including source code), but this is not a human-readable name. We also add package aliases, in the form of symlinks in .buildcache/packages/by-alias which are human-readable names and names required by other systems involved in distribution.