Skip to content
Open
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
8 changes: 4 additions & 4 deletions src/libcmd/builtin-flake-schemas.nix
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,12 @@
inventory = self.lib.derivationsInventory "package" false;
};

dockerImagesSchema = {
ociImagesSchema = {
version = 1;
doc = ''
The `dockerImages` flake output contains derivations that build valid Docker images.
The `ociImages` flake output contains derivations that build valid Open Container Initiative images.
'';
inventory = self.lib.derivationsInventory "Docker image" false;
inventory = self.lib.derivationsInventory "OCI image" false;
};

legacyPackagesSchema = {
Expand Down Expand Up @@ -432,7 +432,7 @@
schemas.homeModules = homeModulesSchema;
schemas.darwinConfigurations = darwinConfigurationsSchema;
schemas.darwinModules = darwinModulesSchema;
schemas.dockerImages = dockerImagesSchema;
schemas.ociImages = ociImagesSchema;
schemas.bundlers = bundlersSchema;
};
}
7 changes: 7 additions & 0 deletions src/libcmd/flake-schemas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "nix/fetchers/fetch-to-store.hh"
#include "nix/util/memory-source-accessor.hh"
#include "nix/util/mounted-source-accessor.hh"
#include "nix/flake/provenance.hh"

namespace nix::flake_schemas {

Expand Down Expand Up @@ -162,12 +163,18 @@ void forEachOutput(
void visit(
std::optional<std::string> system,
ref<AttrCursor> node,
std::shared_ptr<const Provenance> provenance,
std::function<void(const Leaf & leaf)> visitLeaf,
std::function<void(std::function<void(ForEachChild)>)> visitNonLeaf,
std::function<void(ref<AttrCursor> node, const std::vector<std::string> & systems)> visitFiltered)
{
Activity act(*logger, lvlInfo, actUnknown, fmt("evaluating '%s'", node->getAttrPathStr()));

PushProvenance pushedProvenance(
node->root->state,
provenance ? std::make_shared<const FlakeProvenance>(provenance, node->getAttrPathStr(), evalSettings.pureEval)
: nullptr);

/* Apply the system type filter. */
if (system) {
if (auto forSystems = Node(node).forSystems()) {
Expand Down
1 change: 1 addition & 0 deletions src/libcmd/include/nix/cmd/flake-schemas.hh
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ typedef std::function<void(Symbol attrName, ref<AttrCursor> attr, bool isLast)>
void visit(
std::optional<std::string> system,
ref<AttrCursor> node,
std::shared_ptr<const Provenance> provenance,
std::function<void(const Leaf & leaf)> visitLeaf,
std::function<void(std::function<void(ForEachChild)>)> visitNonLeaf,
std::function<void(ref<AttrCursor> node, const std::vector<std::string> & systems)> visitFiltered);
Expand Down
2 changes: 0 additions & 2 deletions src/libcmd/include/nix/cmd/installable-attr-path.hh
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
#include <regex>
#include <queue>

#include <nlohmann/json.hpp>

namespace nix {

class InstallableAttrPath : public InstallableValue
Expand Down
16 changes: 16 additions & 0 deletions src/libstore/globals.cc
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,22 @@ std::string BaseSetting<Settings::ExternalBuilders>::to_string() const
return nlohmann::json(value).dump();
}

template<>
std::map<std::string, std::string> BaseSetting<std::map<std::string, std::string>>::parse(const std::string & str) const
{
try {
return nlohmann::json::parse(str).template get<std::map<std::string, std::string>>();
} catch (std::exception & e) {
throw UsageError("parsing setting '%s': %s", name, e.what());
}
}

template<>
std::string BaseSetting<std::map<std::string, std::string>>::to_string() const
{
return nlohmann::json(value).dump();
}

template<>
void BaseSetting<PathsInChroot>::appendOrSet(PathsInChroot newValue, bool append)
{
Expand Down
9 changes: 9 additions & 0 deletions src/libstore/include/nix/store/globals.hh
Original file line number Diff line number Diff line change
Expand Up @@ -1462,6 +1462,15 @@ public:
)"};

std::optional<std::string> getHostName();

Setting<std::map<std::string, std::string>> buildProvenanceTags{
this,
{},
"build-provenance-tags",
R"(
Arbitrary name/value pairs that are recorded in the build provenance of store paths built by this machine.
This can be used to tag builds with metadata such as the CI job URL, build cluster name, etc.
)"};
};

// FIXME: don't use a global variable.
Expand Down
15 changes: 7 additions & 8 deletions src/libstore/include/nix/store/provenance.hh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ struct BuildProvenance : Provenance
*/
std::optional<std::string> buildHost;

/**
* User-defined tags from the build host.
*/
std::map<std::string, std::string> tags;

/**
* The system type of the derivation.
*/
Expand All @@ -39,15 +44,9 @@ struct BuildProvenance : Provenance
const StorePath & drvPath,
const OutputName & output,
std::optional<std::string> buildHost,
std::map<std::string, std::string> tags,
std::string system,
std::shared_ptr<const Provenance> next)
: drvPath(drvPath)
, output(output)
, buildHost(std::move(buildHost))
, system(std::move(system))
, next(std::move(next))
{
}
std::shared_ptr<const Provenance> next);

nlohmann::json to_json() const override;
};
Expand Down
34 changes: 33 additions & 1 deletion src/libstore/provenance.cc
Original file line number Diff line number Diff line change
@@ -1,8 +1,35 @@
#include "nix/store/provenance.hh"
#include "nix/util/json-utils.hh"

#include <regex>

namespace nix {

static void checkProvenanceTagName(std::string_view name)
{
static const std::regex tagNameRegex("^[A-Za-z_][A-Za-z0-9_+\\-]*$");
if (!std::regex_match(name.begin(), name.end(), tagNameRegex))
throw Error("tag name '%s' is invalid", name);
}

BuildProvenance::BuildProvenance(
const StorePath & drvPath,
const OutputName & output,
std::optional<std::string> buildHost,
std::map<std::string, std::string> tags,
std::string system,
std::shared_ptr<const Provenance> next)
: drvPath(drvPath)
, output(output)
, buildHost(std::move(buildHost))
, tags(std::move(tags))
, system(std::move(system))
, next(std::move(next))
{
for (const auto & [name, value] : this->tags)
checkProvenanceTagName(name);
}

nlohmann::json BuildProvenance::to_json() const
{
return {
Expand All @@ -12,6 +39,7 @@ nlohmann::json BuildProvenance::to_json() const
{"buildHost", buildHost},
{"system", system},
{"next", next ? next->to_json() : nlohmann::json(nullptr)},
{"tags", tags},
};
}

Expand All @@ -23,10 +51,14 @@ Provenance::Register registerBuildProvenance("build", [](nlohmann::json json) {
std::optional<std::string> buildHost;
if (auto p = optionalValueAt(obj, "buildHost"))
buildHost = p->get<std::optional<std::string>>();
std::map<std::string, std::string> tags;
if (auto p = optionalValueAt(obj, "tags"); p && !p->is_null())
tags = p->get<std::map<std::string, std::string>>();
auto buildProv = make_ref<BuildProvenance>(
StorePath(getString(valueAt(obj, "drv"))),
getString(valueAt(obj, "output")),
buildHost,
std::move(buildHost),
std::move(tags),
getString(valueAt(obj, "system")),
next);
return buildProv;
Expand Down
7 changes: 6 additions & 1 deletion src/libstore/unix/build/derivation-builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1873,7 +1873,12 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
newInfo.ultimate = true;
if (experimentalFeatureSettings.isEnabled(Xp::Provenance))
newInfo.provenance = std::make_shared<const BuildProvenance>(
drvPath, outputName, settings.getHostName(), drv.platform, drvProvenance);
drvPath,
outputName,
settings.getHostName(),
settings.buildProvenanceTags.get(),
drv.platform,
drvProvenance);
store.signPathInfo(newInfo);

finish(newInfo.path);
Expand Down
2 changes: 2 additions & 0 deletions src/nix/flake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ struct CmdFlakeCheck : FlakeCommand, MixFlakeSchemas
flake_schemas::visit(
checkAllSystems ? std::optional<std::string>() : localSystem,
node,
flake->flake.provenance,

[&](const flake_schemas::Leaf & leaf) {
try {
Expand Down Expand Up @@ -899,6 +900,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON, MixFlakeSchemas
flake_schemas::visit(
showAllSystems ? std::optional<std::string>() : localSystem,
node,
flake->flake.provenance,

[&](const flake_schemas::Leaf & leaf) {
if (auto what = leaf.what())
Expand Down
2 changes: 2 additions & 0 deletions src/nix/provenance.cc
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ struct CmdProvenanceShow : StorePathsCommand
build->output,
build->buildHost.value_or("unknown host").c_str(),
build->system);
for (auto & [tagName, tagValue] : build->tags)
logger->cout(" tag " ANSI_BOLD "%s" ANSI_NORMAL ": %s", tagName, tagValue);
provenance = build->next;
}

Expand Down
1 change: 1 addition & 0 deletions tests/functional/common/init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ substituters =
flake-registry = $TEST_ROOT/registry.json
show-trace = true
host-name = test-host
build-provenance-tags = {"pr": "1234", "branch": "main"}
include nix.conf.extra
trusted-users = $(whoami)
${_NIX_TEST_EXTRA_CONFIG:-}
Expand Down
17 changes: 17 additions & 0 deletions tests/functional/flakes/provenance.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ builder=$(nix eval --raw "$flake1Dir#packages.$system.default._builder")
},
"output": "out",
"system": "$system",
"tags": {
"branch": "main",
"pr": "1234"
},
"type": "build"
}
EOF
Expand Down Expand Up @@ -172,6 +176,10 @@ nix copy --from "file://$binaryCache" "$outPath" --no-check-sigs
},
"output": "out",
"system": "$system",
"tags": {
"branch": "main",
"pr": "1234"
},
"type": "build"
},
"type": "copied"
Expand All @@ -186,6 +194,8 @@ unset _NIX_FORCE_HTTP
$outPath
← copied from file://$binaryCache
← built from derivation $drvPath (output out) on test-host for $system
tag branch: main
tag pr: 1234
← with derivation metadata
{
"license": [
Expand Down Expand Up @@ -258,6 +268,8 @@ nix build --impure --print-out-paths --no-link "$flake1Dir#packages.$system.defa
[[ "$(nix provenance show "$outPath")" = "$(cat <<EOF
$outPath
← built from derivation $drvPath (output out) on test-host for $system
tag branch: main
tag pr: 1234
← with derivation metadata
{
"license": [
Expand Down Expand Up @@ -353,3 +365,8 @@ nix provenance verify "$path"
echo barf > "$TEST_ROOT/hello.txt"

expectStderr 1 nix provenance verify "$path" | grepQuiet "hash mismatch for URL"

# Test invalid tag names
for name in "123-invalid" "invalid tag" "invalid@tag" "-invalid" " foo"; do
expectStderr 1 nix build --build-provenance-tags "{\"$name\": \"value\"}" --no-link "$flake1Dir#packages.$system.default" 2>&1 | grepQuiet "tag name '$name' is invalid"
done
Loading