diff options
Diffstat (limited to 'ops/pipelines')
-rw-r--r-- | ops/pipelines/README.md | 5 | ||||
-rw-r--r-- | ops/pipelines/depot.nix | 80 | ||||
-rw-r--r-- | ops/pipelines/static-pipeline.yaml | 79 |
3 files changed, 164 insertions, 0 deletions
diff --git a/ops/pipelines/README.md b/ops/pipelines/README.md new file mode 100644 index 000000000000..a3f94fd23143 --- /dev/null +++ b/ops/pipelines/README.md @@ -0,0 +1,5 @@ +This folder contains the dynamic configuration for our [Buildkite CI +setup](https://tvl.fyi/builds). + +The configuration is built and dynamically loaded by Buildkite at the start of +each CI pipeline. diff --git a/ops/pipelines/depot.nix b/ops/pipelines/depot.nix new file mode 100644 index 000000000000..878526e37484 --- /dev/null +++ b/ops/pipelines/depot.nix @@ -0,0 +1,80 @@ +# This file configures the primary build pipeline used for the +# top-level list of depot targets. +# +# It outputs a "YAML" (actually JSON) file which is evaluated and +# submitted to Buildkite at the start of each build. This means we can +# dynamically configure the pipeline execution here. +{ depot, lib, pkgs, ... }: + +let + inherit (builtins) concatStringsSep foldl' map toJSON; + inherit (pkgs) symlinkJoin writeText; + + # Create an expression that builds the target at the specified + # location. + mkBuildExpr = target: + let + descend = expr: attr: "builtins.getAttr \"${attr}\" (${expr})"; + targetExpr = foldl' descend "import ./. {}" target.__readTree; + subtargetExpr = descend targetExpr target.__subtarget; + in if target ? __subtarget then subtargetExpr else targetExpr; + + # Create a pipeline label from the targets tree location. + mkLabel = target: + let label = concatStringsSep "/" target.__readTree; + in if target ? __subtarget + then "${label}:${target.__subtarget}" + else label; + + # Create a pipeline step from a single target. + mkStep = target: { + command = let + drvPath = builtins.unsafeDiscardStringContext target.drvPath; + in lib.concatStringsSep " " [ + # First try to realise the drvPath of the target so we don't evaluate twice. + # Nix has no concept of depending on a derivation file without depending on + # at least one of its `outPath`s, so we need to discard the string context + # if we don't want to build everything during pipeline construction. + "nix-store --realise '${drvPath}'" + # However, Nix doesn't track references of store paths to derivations, so + # there's no guarantee that the derivation file is not garbage collected. + # To handle this case we fall back to an ordinary build if the derivation + # file is missing. + "|| (test ! -f '${drvPath}' && nix-build -E '${mkBuildExpr target}' --show-trace)" + ]; + label = ":nix: ${mkLabel target}"; + + # Skip build steps if their out path has already been built. + skip = let + shouldSkip = with builtins; + # Only skip in real Buildkite builds + (getEnv "BUILDKITE_BUILD_ID" != "") && + # Always build everything for the canon branch. + (getEnv "BUILDKITE_BRANCH" != "refs/heads/canon") && + # Discard string context to avoid realising the store path during + # pipeline construction. + (pathExists (unsafeDiscardStringContext target.outPath)); + in if shouldSkip then "Target was already built." else false; + }; + + # Protobuf check step which validates that changes to .proto files + # between revisions don't cause backwards-incompatible or otherwise + # flawed changes. + protoCheck = { + command = "${depot.nix.bufCheck}/bin/ci-buf-check"; + label = ":water_buffalo:"; + }; + + # This defines the build pipeline, using the pipeline format + # documented on https://buildkite.com/docs/pipelines/defining-steps + # + # Pipeline steps need to stay in order. + pipeline.steps = + # Create build steps for each CI target + (map mkStep depot.ci.targets) + + ++ [ + # Simultaneously run protobuf checks + protoCheck + ]; +in (writeText "depot.yaml" (toJSON pipeline)) diff --git a/ops/pipelines/static-pipeline.yaml b/ops/pipelines/static-pipeline.yaml new file mode 100644 index 000000000000..f366afe24cca --- /dev/null +++ b/ops/pipelines/static-pipeline.yaml @@ -0,0 +1,79 @@ +# This file defines the static Buildkite pipeline which attempts to +# create the dynamic pipeline of all depot targets. +# +# If something fails during the creation of the pipeline, the fallback +# is executed instead which will simply report an error to Gerrit. +--- +steps: + - label: ":llama:" + command: | + set -ue + nix-build -A ops.pipelines.depot -o depot.yaml --show-trace && \ + buildkite-agent pipeline upload depot.yaml + + # Wait for all previous steps to complete. + - wait: null + continue_on_failure: true + + # Exit with success or failure depending on whether any other steps + # failed. + # + # This information is checked by querying the Buildkite GraphQL API + # and fetching the count of failed steps. + # + # This step must be :duck: (yes, really!) because the post-command + # hook will inspect this name. + # + # Note that this step has requirements for the agent environment, which + # are enforced in our NixOS configuration: + # + # * curl and jq must be on the $PATH of build agents + # * besadii configuration must be readable to the build agents + - label: ":duck:" + key: ":duck:" + command: | + set -ueo pipefail + + readonly FAILED_JOBS=$(curl 'https://graphql.buildkite.com/v1' \ + --silent \ + -H "Authorization: Bearer $(cat /run/agenix/buildkite-graphql-token)" \ + -d "{\"query\": \"query BuildStatusQuery { build(uuid: \\\"$BUILDKITE_BUILD_ID\\\") { jobs(passed: false) { count } } }\"}" | \ + jq -r '.data.build.jobs.count') + + echo "$$FAILED_JOBS build jobs failed." + + if (( $$FAILED_JOBS > 0 )); then + exit 1 + fi + + # After duck, on success, create a gcroot if the build branch is + # canon. + # + # We care that this anchors *most* of the depot, in practice it's + # unimportant if there is a build race and we get +-1 of the + # targets. + # + # Unfortunately this requires a third evaluation of the graph, but + # since it happens after :duck: it should not affect the timing of + # status reporting back to Gerrit. + - label: ":anchor:" + if: "build.branch == 'refs/heads/canon'" + command: | + nix-instantiate -A ci.gcroot --add-root /nix/var/nix/gcroots/depot/canon + depends_on: + - step: ":duck:" + allow_failure: false + + # Create a revision number for the current commit for builds on + # canon. + # + # This writes data back to Gerrit using the Buildkite agent + # credentials injected through a git credentials helper. + # + # Revision numbers are defined as the number of commits in the + # lineage of HEAD, following only the first parent of merges. + - label: ":git:" + if: "build.branch == 'refs/heads/canon'" + command: | + git -c 'credential.helper=gerrit-creds' \ + push origin "HEAD:refs/r/$(git rev-list --count --first-parent HEAD)" |