Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/update-flake.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# SPDX-FileCopyrightText: The nix-devshell Authors
# SPDX-License-Identifier: 0BSD

# Zero-manual maintenance for the from-source packages this repo's flake ships
# (arch-go, modernize, helm-schema) — the Go tools nixpkgs does not package.
# Renovate's nix manager only bumps flake inputs and can't recompute nix hashes,
# so nix-update does both (version + src hash + vendorHash) here in the one place
# they're defined. Consuming repos pick the update up by bumping their `devshell`
# flake input (Renovate lock maintenance); nixpkgs itself stays Renovate's job.
name: Update flake packages
on:
schedule:
- cron: 30 5 * * MON
workflow_dispatch:
permissions:
contents: read
jobs:
update:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write # push the update branch
pull-requests: write # open the PR
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: ./
- name: Bump the from-source flake packages (version + hashes)
run: |
for pkg in arch-go modernize helm-schema; do
echo "::group::nix-update $pkg"
nix run nixpkgs#nix-update -- --flake --version=stable "$pkg" || echo "::warning::nix-update $pkg made no change or failed"
echo "::endgroup::"
done
- uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
branch: flake-packages-update
title: "chore(deps): update the from-source flake packages"
commit-message: "chore(deps): update the from-source flake packages"
body: |
Automated `nix-update` of the flake packages nixpkgs does not ship
(arch-go, modernize, helm-schema) — version, source hash, and
vendorHash. Consuming repos pick these up by bumping their `devshell`
flake input. Merge once the Verify gate is green.
labels: dependencies
delete-branch: true
8 changes: 4 additions & 4 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,29 @@ jobs:
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: ./
- run: nix develop --command reuse lint
- run: nix develop --command ci-reuse
yaml:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: ./
- run: nix develop --command yamllint .
- run: nix develop --command ci-yaml
github-actions:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: ./
# actionlint shells out to shellcheck (in the devShell) for run: blocks.
- run: nix develop --command actionlint
- run: nix develop --command ci-actionlint
markdown:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: ./
- run: nix develop --command markdownlint-cli2 "**/*.md"
- run: nix develop --command ci-markdown
policy:
# The metio convention policies (SHA-pinned uses, job timeouts, permissions,
# …) run against this repo's action and workflows.
Expand Down
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,33 @@ SPDX-License-Identifier: 0BSD
-->
# nix-devshell

A composite GitHub Action that installs Nix and caches the `/nix` store, so a
repo whose toolchain is a nix flake runs every CI gate through the flake's
devShell and resolves the exact tool versions in `flake.lock` — identical to a
local `nix develop`.
The metio nix toolchain, in two halves:

- **A flake** (`lib.mkDevShell`) that assembles a repo's devShell from the
shared lint gate (reuse, typos, yamllint, actionlint, shellcheck,
markdownlint) and its `ci-*` command wrappers, plus the from-source Go tools
nixpkgs does not ship (arch-go, modernize, helm-schema). Defined once here so
every repo resolves the same tools from `flake.lock`.
- **A composite GitHub Action** that installs Nix and caches the `/nix` store,
so a repo whose toolchain is that flake runs every CI gate through the
devShell — identical to a local `nix develop`.

## Usage

Check out the repo first, then run each gate with `nix develop --command …`:
Build the devShell from this flake, then in CI check the repo out, set Nix up
with the action, and run each gate through the devShell:

```nix
# flake.nix
inputs.devshell.url = "github:metio/nix-devshell";
inputs.nixpkgs.follows = "devshell/nixpkgs";
outputs = { nixpkgs, devshell, ... }: {
devShells.<sys>.default = devshell.lib.mkDevShell {
pkgs = nixpkgs.legacyPackages.<sys>;
packages = [ /* repo-specific tools + gate commands */ ];
};
};
```

```yaml
jobs:
Expand All @@ -21,7 +40,7 @@ jobs:
steps:
- uses: actions/checkout@<sha>
- uses: metio/nix-devshell@<sha>
- run: nix develop --command <gate>
- run: nix develop --command ci-reuse # or any gate
```

The store is keyed on `flake.nix`/`flake.lock`, so it downloads the devShell
Expand Down
26 changes: 2 additions & 24 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

181 changes: 170 additions & 11 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
# SPDX-FileCopyrightText: The nix-devshell Authors
# SPDX-License-Identifier: 0BSD

# This repo ships a composite action, not a toolchain — but it dogfoods that
# action in its own CI: verify.yml sets up Nix through `./` (this very action)
# and runs each lint gate with `nix develop --command …`. The devShell is the
# shared metio lint gate (reuse, typos, yamllint, actionlint, shellcheck,
# markdownlint) from ci.lib.mkDevShell, so a change to the action is checked by
# the action, and local and CI resolve the same tools from flake.lock.
# The metio nix toolchain: the shared devShell (`lib.mkDevShell`) plus the from-
# source Go tools nixpkgs does not ship (arch-go, modernize, helm-schema), and —
# alongside this flake — the composite action (action.yml) that installs Nix in
# CI. Every metio repo builds its devShell from `lib.mkDevShell` and sets Nix up
# through this repo's action, so the lint gate (reuse, typos, yamllint, actionlint,
# shellcheck, markdownlint) and its `ci-*` command wrappers are defined once here
# instead of copied into each flake, and local and CI resolve the same tools from
# flake.lock. `update-flake.yml` keeps the from-source tools' versions + hashes
# current; a consuming repo picks the update up by bumping its `devshell` flake
# input (Renovate lock maintenance).
#
# A repo's flake becomes:
#
# inputs.devshell.url = "github:metio/nix-devshell";
# inputs.nixpkgs.follows = "devshell/nixpkgs"; # one nixpkgs pin, org-wide
# outputs = { nixpkgs, devshell, ... }: {
# devShells.<sys>.default = devshell.lib.mkDevShell {
# pkgs = nixpkgs.legacyPackages.<sys>;
# packages = [ … repo-specific tools + gate commands … ];
# env.KUBEBUILDER_ASSETS = "${devshell.lib.kubebuilderAssets pkgs}"; # controllers only
# };
# };
#
# This repo dogfoods both halves: verify.yml installs Nix through `./` (its own
# action) and runs each lint gate via `nix develop --command ci-*` from the
# devShell this flake defines.
{
description = "The metio nix-devshell action, dogfooded through the shared lint gate";
description = "metio nix toolchain: the shared devShell, the Go tools nixpkgs lacks, and the Nix-installer action";

inputs = {
ci.url = "github:metio/ci";
nixpkgs.follows = "ci/nixpkgs";
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-compat.url = "github:edolstra/flake-compat";
};

outputs =
{ nixpkgs, ci, ... }:
{ nixpkgs, ... }:
let
systems = [
"x86_64-linux"
Expand All @@ -25,10 +45,149 @@
"aarch64-darwin"
];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});

# dadav/helm-schema (docs chart-values reference). Tags carry no `v`.
helm-schema =
pkgs:
pkgs.buildGoModule rec {
pname = "helm-schema";
version = "0.23.4";
src = pkgs.fetchFromGitHub {
owner = "dadav";
repo = "helm-schema";
rev = version;
hash = "sha256-btkkNzye9if4lF/YdhalbwA2/dcZArU6/9Hr0bTJf1M=";
};
vendorHash = "sha256-jbK+XD5CbjMQJUJCcKbNN8LhYuhuy+Z3XcCmgiYw25Y=";
};

# arch-go (architecture rules, arch-go.yml).
arch-go =
pkgs:
pkgs.buildGoModule rec {
pname = "arch-go";
version = "2.1.2";
src = pkgs.fetchFromGitHub {
owner = "arch-go";
repo = "arch-go";
rev = "v${version}";
hash = "sha256-clwVZ/5PwUiD1LzRG6jGghQWcWZP3Pj3CzrdZiHUrIQ=";
};
vendorHash = "sha256-xIf+Ty1Pqa3oqqFLFsOv8Jz2bLOaIF+kjfGao05FhrM=";
};

# modernize (newer-Go idiom check), a subpackage of x/tools' gopls module.
modernize =
pkgs:
pkgs.buildGoModule rec {
pname = "modernize";
version = "0.47.0";
src = pkgs.fetchFromGitHub {
owner = "golang";
repo = "tools";
rev = "v${version}";
hash = "sha256-JfrmKeIAhHhxMqOfh27w+T9PaBAIzh47wOokXmr1Z5Q=";
};
modRoot = "gopls";
subPackages = [ "internal/analysis/modernize/cmd/modernize" ];
vendorHash = "sha256-GF9KSCr2aMjczVKz9H2t5Gc2kF0wqmKenO7qa8TQw4o=";
};

# controller-runtime envtest wants a dir holding etcd, kube-apiserver, and
# kubectl. Assemble it from nixpkgs so a controller's tests run offline
# against the flake-pinned Kubernetes, no setup-envtest download.
kubebuilderAssets =
pkgs:
pkgs.runCommand "kubebuilder-assets" { } ''
mkdir -p $out
ln -s ${pkgs.etcd}/bin/etcd $out/etcd
ln -s ${pkgs.kubernetes}/bin/kube-apiserver $out/kube-apiserver
ln -s ${pkgs.kubectl}/bin/kubectl $out/kubectl
'';

# The lint gate every metio repo shares, byte-for-byte.
lintTools =
pkgs: with pkgs; [
reuse
typos
yamllint
actionlint
shellcheck # actionlint shells out to it for run: blocks
markdownlint-cli2
];

# One canonical `ci-<tool>` command per shared lint tool, wrapping the EXACT
# CI invocation (e.g. `reuse lint`, `markdownlint-cli2 "**/*.md"`). Defined
# once here so every repo inherits them: a workflow runs the gate with
# `nix develop --command ci-reuse`, and a developer runs the same name bare
# inside `nix develop` — the invocation lives in one place instead of being
# copied into each workflow's YAML and each repo's flake. The `ci-` prefix
# leaves the raw tool free for its other modes (e.g. `reuse annotate`); the
# wrapper pins only the CI mode. Tools are referenced by store path so the
# wrapper resolves the flake-pinned binary regardless of PATH.
lintCommands = pkgs: [
(pkgs.writeShellScriptBin "ci-reuse" ''exec ${pkgs.reuse}/bin/reuse lint "$@"'')
(pkgs.writeShellScriptBin "ci-typos" ''exec ${pkgs.typos}/bin/typos "$@"'')
(pkgs.writeShellScriptBin "ci-yaml" ''exec ${pkgs.yamllint}/bin/yamllint . "$@"'')
# actionlint finds shellcheck on PATH, which the devShell provides.
(pkgs.writeShellScriptBin "ci-actionlint" ''exec ${pkgs.actionlint}/bin/actionlint "$@"'')
(pkgs.writeShellScriptBin "ci-markdown" ''exec ${pkgs.markdownlint-cli2}/bin/markdownlint-cli2 "**/*.md" "$@"'')
];

# Assemble a repo's devShell: the shared lint gate plus the repo's own
# tools and gate commands, any extra env vars, its command menu, and any
# always-run setup. `menu` prints only for an interactive shell — otherwise
# it lands on the stdout that `nix develop --command <tool>` captures and
# reads as tool output (e.g. golang.yml's gofumpt gate captures
# `unformatted="$(… gofumpt -l .)"`). `shellHook` always runs.
mkDevShell =
{
pkgs,
packages ? [ ],
env ? { },
menu ? "",
shellHook ? "",
}:
pkgs.mkShell (
env
// {
packages = lintTools pkgs ++ lintCommands pkgs ++ packages;
shellHook = ''
if [ -t 1 ]; then
echo "metio devshell — shared lint gate: reuse, typos, yamllint, actionlint, markdownlint-cli2"
echo " run any the CI way: ci-reuse, ci-typos, ci-yaml, ci-actionlint, ci-markdown"
${menu}
fi
''
+ shellHook;
}
);
in
{
# System-independent building blocks a repo's flake composes.
lib = {
inherit
mkDevShell
lintTools
lintCommands
helm-schema
arch-go
modernize
kubebuilderAssets
;
};

# The from-source packages, buildable for `nix build` and `nix-update`.
packages = forAllSystems (pkgs: {
helm-schema = helm-schema pkgs;
arch-go = arch-go pkgs;
modernize = modernize pkgs;
});

# This repo dogfoods its own shared devShell: the lint gate that verify.yml
# runs through `nix develop --command ci-*`.
devShells = forAllSystems (pkgs: {
default = ci.lib.mkDevShell { inherit pkgs; };
default = mkDevShell { inherit pkgs; };
});

formatter = forAllSystems (pkgs: pkgs.nixfmt-rfc-style);
Expand Down
14 changes: 14 additions & 0 deletions shell.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: The nix-devshell Authors
# SPDX-License-Identifier: 0BSD

# nix-shell compatibility: exposes the flake's devShell to plain `nix-shell`.
(import (
let
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
node = lock.nodes.flake-compat.locked;
in
fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${node.rev}.tar.gz";
sha256 = node.narHash;
}
) { src = ./.; }).shellNix