packer: add withPlugins

This commit is contained in:
José Luis Lafuente
2026-06-23 13:29:29 +02:00
parent 2ca7882b70
commit cd4f09c079
9 changed files with 354 additions and 4 deletions
+1
View File
@@ -25,6 +25,7 @@ etc-files.section.md
nginx.section.md
nrfutil.section.md
opengl.section.md
packer.section.md
shell-helpers.section.md
python-tree-sitter.section.md
treefmt.section.md
+70
View File
@@ -0,0 +1,70 @@
# Packer {#sec-packer}
[Packer](https://www.packer.io) is a tool for creating identical machine images
for multiple platforms from a single source configuration.
## Using Packer with plugins {#sec-packer-with-plugins}
Packer's functionality is extended through
[plugins](https://developer.hashicorp.com/packer/docs/plugins). Rather than
letting Packer download plugins at runtime, you can build a Packer wrapper that
bundles the plugins you need with `packer.withPlugins`.
`packer.withPlugins` takes a function that receives the set of available plugins
and returns the list of plugins to include:
```nix
packer.withPlugins (ps: [ ps.docker ])
```
This produces a `packer` executable wrapped with the `PACKER_PLUGIN_PATH`
environment variable set, so the selected plugins are available without a
separate `packer plugins install` step.
For example, to get a development shell with Packer and the Docker plugin:
```nix
{
pkgs ? import <nixpkgs> { },
}:
pkgs.mkShell {
packages = [
(pkgs.packer.withPlugins (ps: [ ps.docker ]))
];
}
```
Multiple plugins can be selected at once:
```nix
packer.withPlugins (ps: [
ps.docker
ps.qemu
])
```
## Listing available plugins {#sec-packer-list-plugins}
The packaged plugins are exposed as the `packer.plugins` attribute set. To list
every plugin available in your version of Nixpkgs, query its attribute names:
```ShellSession
$ nix eval nixpkgs#packer.plugins --apply builtins.attrNames
[ "docker" "qemu" ]
```
Without flakes:
```ShellSession
$ nix-env -f '<nixpkgs>' -qaP -A packer.plugins
packer.plugins.docker packer-plugin-docker-1.1.2
packer.plugins.qemu packer-plugin-qemu-1.1.4
```
The attribute name (for example `docker` or `qemu`) is what you pass to
`packer.withPlugins`.
Notes:
- `mkPackerPlugin` currently only supports `fetchFromGitHub` as the fetcher.
+9
View File
@@ -620,6 +620,15 @@
"chap-overrides": [
"index.html#chap-overrides"
],
"sec-packer": [
"index.html#sec-packer"
],
"sec-packer-list-plugins": [
"index.html#sec-packer-list-plugins"
],
"sec-packer-with-plugins": [
"index.html#sec-packer-with-plugins"
],
"sec-pkg-override": [
"index.html#sec-pkg-override"
],
@@ -0,0 +1,100 @@
let
platformSuffix = {
x86_64-linux = "linux_amd64";
x86_64-darwin = "darwin_amd64";
aarch64-linux = "linux_arm64";
aarch64-darwin = "darwin_arm64";
};
in
{
stdenv,
lib,
buildGoModule,
versionCheckHook,
nix-update-script,
}:
lib.extendMkDerivation {
constructDrv = buildGoModule;
excludeDrvArgNames = [
"apiVersion"
];
extendDrvArgs =
finalAttrs:
{
pname,
version,
src,
ldflags ? [ ],
nativeInstallCheckInputs ? [ ],
postFixup ? "",
subPackages ? [ "." ],
apiVersion ? "x5.0",
versionCheckProgramArg ? "describe",
doInstallCheck ? true,
...
}@prevAttrs:
# Packer expects plugins to be hosted on GitHub and follow a specific release structure
# (see: https://developer.hashicorp.com/packer/docs/plugins/creation#creating-a-github-release).
# This introduces two requirements:
#
# 1. Directory Structure: Packer expects the plugin binary to reside in:
# $PACKER_PLUGIN_PATH/github.com/$OWNER/$TYPE/
# where $TYPE is the repo name without the "packer-plugin-" prefix.
#
# 2. Configuration Source: When declaring the plugin in a Packer template, the `source`
# attribute must match this GitHub address format (e.g.: "github.com/$OWNER/$TYPE")
let
_ = lib.assertMsg (
src ? repo && src ? owner && src ? githubBase
) "mk-packer-plugin: fetchFromGitHub is currently the only supported fetcher";
suffix =
platformSuffix."${stdenv.hostPlatform.system}"
or (throw "Unsupported system: ${stdenv.hostPlatform.system}");
binName = "${finalAttrs.src.repo}_v${finalAttrs.version}_${apiVersion}_${suffix}";
in
{
inherit
subPackages
doInstallCheck
versionCheckProgramArg
;
__structuredAttrs = true;
strictDeps = true;
ldflags =
ldflags
++ (
let
versionFlag = "${finalAttrs.src.githubBase}/${finalAttrs.src.owner}/${finalAttrs.src.repo}/version";
in
[
"-s"
"-w"
"-X ${versionFlag}.Version=${finalAttrs.version}"
"-X ${versionFlag}.VersionPrerelease="
]
);
versionCheckProgram = prevAttrs.versionCheckProgram or "${placeholder "out"}/bin/${binName}";
nativeInstallCheckInputs = nativeInstallCheckInputs ++ [ versionCheckHook ];
# Generate checksums AFTER fixup phase when binary is finalized
postFixup = postFixup + ''
mv "$out/bin/${finalAttrs.src.repo}" "$out/bin/${binName}"
sha256sum "$out/bin/${binName}" | cut -d' ' -f1 > "$out/bin/${binName}_SHA256SUM"
'';
meta.mainProgram = binName;
passthru = {
pluginPath = "${finalAttrs.src.githubBase}/${finalAttrs.src.owner}/${lib.removePrefix "packer-plugin-" finalAttrs.src.repo}/${binName}";
updateScript = prevAttrs.passthru.updateScript or (nix-update-script { });
};
};
}
+47 -4
View File
@@ -3,16 +3,18 @@
buildGoModule,
fetchFromGitHub,
installShellFiles,
callPackage,
runCommand,
}:
buildGoModule rec {
buildGoModule (finalAttrs: {
pname = "packer";
version = "1.15.4";
src = fetchFromGitHub {
owner = "hashicorp";
repo = "packer";
rev = "v${version}";
tag = "v${finalAttrs.version}";
hash = "sha256-mhHES+/FCvVBBQm1qDQeH6WY2c9hIV7N3iFBCqJqJLw=";
};
@@ -31,6 +33,46 @@ buildGoModule rec {
installShellCompletion --zsh contrib/zsh-completion/_packer
'';
passthru =
let
pluginScope = callPackage ./plugins.nix { };
in
{
plugins = lib.filterAttrs (_: lib.isDerivation) pluginScope;
withPlugins =
f:
(callPackage ./with-plugins.nix {
packer = finalAttrs.finalPackage;
packerPlugins = pluginScope;
})
{ selector = f; };
tests.withPlugins =
let
packer-with-docker = finalAttrs.passthru.withPlugins (ps: [ ps.docker ]);
in
runCommand "packer-test-with-plugins"
{
nativeBuildInputs = [ packer-with-docker ];
}
''
packer plugins installed > output.txt
cat output.txt
expected_path="${finalAttrs.passthru.plugins.docker.pluginPath}"
if ! grep -q "$expected_path" output.txt; then
echo "ERROR: Expected plugin path not found in 'packer plugins installed' output"
echo "Expected: $expected_path"
echo "Got:"
cat output.txt
exit 1
fi
touch $out
'';
};
meta = {
description = "Tool for creating identical machine images for multiple platforms from a single source configuration";
homepage = "https://www.packer.io";
@@ -40,7 +82,8 @@ buildGoModule rec {
ma27
techknowlogick
qjoly
jlesquembre
];
changelog = "https://github.com/hashicorp/packer/blob/v${version}/CHANGELOG.md";
changelog = "https://github.com/hashicorp/packer/blob/v${finalAttrs.version}/CHANGELOG.md";
};
}
})
+19
View File
@@ -0,0 +1,19 @@
{
lib,
newScope,
}:
lib.makeScope newScope (
self:
let
packages = lib.packagesFromDirectoryRecursive {
inherit (self) callPackage;
directory = ./plugins;
};
in
{
mkPackerPlugin = self.callPackage ./extra/mk-packer-plugin.nix { };
}
// lib.mapAttrs' (
name: pkg: lib.nameValuePair (lib.removePrefix "packer-plugin-" name) pkg
) packages
)
@@ -0,0 +1,27 @@
{
lib,
mkPackerPlugin,
fetchFromGitHub,
}:
mkPackerPlugin (finalAttrs: {
pname = "packer-plugin-docker";
version = "1.1.2";
src = fetchFromGitHub {
owner = "hashicorp";
repo = "packer-plugin-docker";
tag = "v${finalAttrs.version}";
hash = "sha256-h89FGwUQjTk81CYUe1xmKxSHr1t3wyBg9lHUTqmVym8=";
};
vendorHash = "sha256-mvyafYSLi/q7lWorfKc4Gc4oM7yti3v/bLcVnNkH7ZY=";
meta = {
description = "Packer plugin for Docker";
homepage = "https://github.com/hashicorp/packer-plugin-docker";
license = lib.licenses.mpl20;
maintainers = with lib.maintainers; [ jlesquembre ];
};
})
@@ -0,0 +1,27 @@
{
lib,
mkPackerPlugin,
fetchFromGitHub,
}:
mkPackerPlugin (finalAttrs: {
pname = "packer-plugin-qemu";
version = "1.1.4";
src = fetchFromGitHub {
owner = "hashicorp";
repo = "packer-plugin-qemu";
tag = "v${finalAttrs.version}";
hash = "sha256-Ose7ueo9V2zJ5K5yvew9ErTD9lR+rkp1UB/yDi+U+fY=";
};
vendorHash = "sha256-pgfI/ntG6Fesimw3jk70GP+lBUEUMfq6wbqXx8xCTf0=";
meta = {
description = "Packer plugin for QEMU";
homepage = "https://github.com/hashicorp/packer-plugin-qemu";
license = lib.licenses.mpl20;
maintainers = with lib.maintainers; [ jlesquembre ];
};
})
+54
View File
@@ -0,0 +1,54 @@
{
lib,
packer,
packerPlugins,
makeBinaryWrapper,
linkFarm,
stdenvNoCC,
}:
lib.extendMkDerivation {
constructDrv = stdenvNoCC.mkDerivation;
excludeDrvArgNames = [
"derivationArgs"
"selector"
];
extendDrvArgs =
finalAttrs:
{
selector ? (_: [ ]),
name ? "packer-with-plugins",
nativeBuildInputs ? [ makeBinaryWrapper ],
...
}:
let
plugins = selector packerPlugins;
pluginFarm = linkFarm "packer-plugins" (
builtins.concatMap (p: [
{
name = p.pluginPath;
path = "${p}/bin/${p.meta.mainProgram}";
}
{
name = "${p.pluginPath}_SHA256SUM";
path = "${p}/bin/${p.meta.mainProgram}_SHA256SUM";
}
]) plugins
);
in
{
inherit name nativeBuildInputs;
__structuredAttrs = true;
strictDeps = true;
meta.mainProgram = "packer";
dontUnpack = true;
buildCommand = ''
makeWrapper "${packer}/bin/packer" "$out/bin/packer" \
--set PACKER_PLUGIN_PATH "${pluginFarm}"
'';
};
}