chore: remove build.image{,Name,Tag}

and replace its uses with `image.tarball{,.imageName,.imageTag}`.

This changeset is a hard deprecation of `build.image{,Name,Tag}` and
does not do any option renaming, etc., as the removed options were all
marked as internal.

This changeset also includes code that ensures the newly-relied-upon
options are properly defined, raising user-visible assertion errors if
no sane default image name or tag could be inferred.
This commit is contained in:
Matt Schreiber 2023-10-13 15:43:48 -04:00
parent d2fb84c6e0
commit 491afaa97c
No known key found for this signature in database
2 changed files with 76 additions and 73 deletions

View file

@ -13,23 +13,15 @@ let
addDetails = serviceName: service: addDetails = serviceName: service:
builtins.addErrorContext "while evaluating the image for service ${serviceName}" builtins.addErrorContext "while evaluating the image for service ${serviceName}"
(let (
inherit (service) build; let
in { imageAttrName = "image${lib.optionalString (service.image.tarball.isExe or false) "Exe"}";
imageName = build.imageName or service.image.name; in
imageTag = {
if build.image.imageTag != "" inherit (service.image.tarball) imageName imageTag;
then build.image.imageTag ${imageAttrName} = service.image.tarball.outPath;
else lib.head (lib.strings.splitString "-" (baseNameOf build.image.outPath));
} // (if build.image.isExe or false
then {
imageExe = build.image.outPath;
} }
else { );
image = build.image.outPath;
}
)
);
in in
{ {
options = { options = {
@ -40,6 +32,26 @@ in
}; };
}; };
config = { config = {
assertions =
let
assertionsForRepoTagComponent = component: attrName:
lib.mapAttrsToList
(name: value: {
assertion = lib.types.nonEmptyStr.check value.${attrName};
message = lib.replaceStrings [ "\n" ] [ " " ] ''
Unable to infer the ${component} of the image associated with
config.services.${name}. Please set
config.services.${name}.image.${attrName} to a non-empty
string.
'';
})
serviceImages;
nameAssertions = assertionsForRepoTagComponent "name" "imageName";
tagAssertions = assertionsForRepoTagComponent "tag" "imageTag";
in
nameAssertions ++ tagAssertions;
build.imagesToLoad = lib.attrValues serviceImages; build.imagesToLoad = lib.attrValues serviceImages;
docker-compose.extended.images = config.build.imagesToLoad; docker-compose.extended.images = config.build.imagesToLoad;
}; };

View file

@ -21,6 +21,7 @@ let
bool bool
coercedTo coercedTo
listOf listOf
nonEmptyStr
nullOr nullOr
oneOf oneOf
package package
@ -35,14 +36,42 @@ let
(pkgs.writeText "dummy-config.json" (builtins.toJSON config.image.rawConfig)) (pkgs.writeText "dummy-config.json" (builtins.toJSON config.image.rawConfig))
]; ];
# Neither image names nor tags can can be empty strings; setting either to an
# empty string will cause `docker load` to croak with the error message
# "invalid reference format".
fallbackImageRepoTagComponent = component: fallback:
if nonEmptyStr.check component
then component
else fallback;
fallbackImageName = fallbackImageRepoTagComponent config.image.name;
fallbackImageTag = fallbackImageRepoTagComponent config.image.tag;
# Shim for `services.<name>.image.tarball` definitions that refer to # Shim for `services.<name>.image.tarball` definitions that refer to
# arbitrary paths and not `dockerTools`-produced derivations. # arbitrary paths and not `dockerTools`-produced derivations.
dummyImagePackage = outPath: { dummyImagePackage = outPath:
inherit outPath; let
type = "derivation"; tarballSuffix = ".tar.gz";
imageName = config.image.name; repoTagSeparator = "-";
imageTag = if config.image.tag == null then "" else config.image.tag; baseName = baseNameOf outPath;
}; baseNameNoExtension = lib.strings.removeSuffix tarballSuffix baseName;
baseNameComponents = lib.strings.splitString repoTagSeparator baseNameNoExtension;
fallbacks =
if ((lib.isStorePath outPath) && (lib.hasSuffix tarballSuffix baseName) && (lib.hasInfix repoTagSeparator baseName))
then {
imageName = lib.concatStringsSep repoTagSeparator (lib.tail baseNameComponents);
imageTag = lib.head baseNameComponents;
}
else {
imageName = null;
imageTag = null;
};
in
{
inherit outPath;
type = "derivation";
imageName = fallbackImageName fallbacks.imageName;
imageTag = fallbackImageTag fallbacks.imageTag;
};
# Type matching the essential attributes of derivations produced by # Type matching the essential attributes of derivations produced by
# `dockerTools` builder functions. # `dockerTools` builder functions.
@ -99,11 +128,11 @@ let
builtImage = buildOrStreamLayeredImage { builtImage = buildOrStreamLayeredImage {
inherit (config.image) inherit (config.image)
name
tag tag
contents contents
includeStorePaths includeStorePaths
; ;
name = fallbackImageName ("localhost/" + config.service.name);
config = config.image.rawConfig; config = config.image.rawConfig;
maxLayers = 100; maxLayers = 100;
@ -129,33 +158,6 @@ let
in in
{ {
options = { options = {
build.image = mkOption {
type = nullOr package;
description = ''
Docker image derivation to be `docker load`-ed.
By default, when `services.<name>.image.nixBuild` is enabled, this is
the image produced using `services.<name>.image.command`,
`services.<name>.image.contents`, and
`services.<name>.image.rawConfig`.
'';
defaultText = lib.literalExample ''
pkgs.dockerTools.buildLayeredImage {
# ...
};
'';
internal = true;
};
build.imageName = mkOption {
type = str;
description = "Derived from `build.image`";
internal = true;
};
build.imageTag = mkOption {
type = str;
description = "Derived from `build.image`";
internal = true;
};
image.nixBuild = mkOption { image.nixBuild = mkOption {
type = bool; type = bool;
description = '' description = ''
@ -169,9 +171,8 @@ in
''; '';
}; };
image.name = mkOption { image.name = mkOption {
type = str; type = nullOr str;
default = "localhost/" + config.service.name; default = null;
defaultText = lib.literalExpression or lib.literalExample ''"localhost/" + config.service.name'';
description = '' description = ''
A human readable name for the Docker image. A human readable name for the Docker image.
@ -264,6 +265,11 @@ in
builder functions, or a Docker image tarball at some arbitrary builder functions, or a Docker image tarball at some arbitrary
location. location.
By default, when `services.<name>.image.nixBuild` is enabled, this is
the image produced using `services.<name>.image.command`,
`services.<name>.image.contents`, and
`services.<name>.image.rawConfig`.
::: {.note} ::: {.note}
Using this option causes Arion to ignore most other options in the Using this option causes Arion to ignore most other options in the
{option}`services.<name>.image` namespace. The exceptions are {option}`services.<name>.image` namespace. The exceptions are
@ -272,35 +278,20 @@ in
is not a derivation with the attributes `imageName` and `imageTag`. is not a derivation with the attributes `imageName` and `imageTag`.
::: :::
''; '';
example = lib.literalExample or lib.literalExpression '' example = lib.literalExpression ''
let pkgs.dockerTools.buildLayeredImage {
myimage = pkgs.dockerTools.buildImage { # ...
name = "my-image"; };
contents = [ pkgs.coreutils ];
};
in
config.services = {
myservice = {
image.tarball = myimage;
# ...
};
}
''; '';
}; };
}; };
config = lib.mkMerge [{ config = lib.mkMerge [{
build.image = config.image.tarball;
build.imageName = config.build.image.imageName;
build.imageTag =
if config.build.image.imageTag != ""
then config.build.image.imageTag
else lib.head (lib.strings.splitString "-" (baseNameOf config.build.image.outPath));
image.rawConfig.Cmd = config.image.command; image.rawConfig.Cmd = config.image.command;
image.nixBuild = lib.mkDefault (priorityIsDefault options.service.image); image.nixBuild = lib.mkDefault (priorityIsDefault options.service.image);
} }
( lib.mkIf (config.service.build.context == null) ( lib.mkIf (config.service.build.context == null)
{ {
service.image = lib.mkDefault "${config.build.imageName}:${config.build.imageTag}"; service.image = lib.mkDefault "${config.image.tarball.imageName}:${config.image.tarball.imageTag}";
}) })
]; ];
} }