feat: support using arbitrary image tarballs

Via the `services.<name>.image.tarball` option.

This permits users to supply any valid image archive, potentially (but
not necessarily) generated by a builder function from
`pkgs.dockerTools`.
This commit is contained in:
Matt Schreiber 2020-01-15 15:12:03 -05:00 committed by Matt Schreiber
parent add0e67d2b
commit fc14ff7abf
No known key found for this signature in database

View file

@ -1,7 +1,11 @@
{ pkgs, lib, config, options, ... }:
let
inherit (lib)
all
flip
functionArgs
hasAttr
isDerivation
mkOption
optionalAttrs
types
@ -10,7 +14,20 @@ let
inherit (pkgs)
dockerTools
;
inherit (types) attrsOf listOf nullOr package str unspecified bool;
inherit (types)
addCheck
attrs
attrsOf
bool
coercedTo
listOf
nullOr
oneOf
package
path
str
unspecified
;
# TODO: dummy-config is a useless layer. Nix 2.3 will let us inspect
# the string context instead, so we can avoid this.
@ -18,6 +35,41 @@ let
(pkgs.writeText "dummy-config.json" (builtins.toJSON config.image.rawConfig))
];
# Shim for `services.<name>.image.tarball` definitions that refer to
# arbitrary paths and not `dockerTools`-produced derivations.
dummyImagePackage = outPath: {
inherit outPath;
type = "derivation";
imageName = config.image.name;
imageTag = if config.image.tag == null then "" else config.image.tag;
};
# Type matching the essential attributes of derivations produced by
# `dockerTools` builder functions.
imagePackageType = addCheck attrs (x: isDerivation x && (all (flip hasAttr x) [ "imageName" "imageTag" ]));
# `coercedTo path dummyImagePackage package` looks sufficient, but it is not.
# `coercedTo` defines this `check` function:
#
# x: (coercedType.check x && finalType.check (coerceFunc x)) || finalType.check x;
#
# and this `merge` function:
#
# loc: defs:
# let
# coerceVal = val:
# if coercedType.check val then coerceFunc val
# else val;
# in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
#
# Meaning that values that satisfy `finalType.check` may still be subject to
# coercion. In this case, derivations satisfy `path.check`, so will be
# coerced using the `dummyImagePackage` function. To avoid this unnecessary
# coercion, we instead force checking whether the value satisfies
# `imagePackageType.check` *first* via placing `imagePackageType` at the head
# of the list provided to `oneOf`.
imageTarballType = oneOf [ imagePackageType (coercedTo path dummyImagePackage imagePackageType) ];
includeStorePathsWarningAndDefault = lib.warn ''
You're using a version of Nixpkgs that doesn't support the includeStorePaths
parameter in dockerTools.streamLayeredImage. Without this, Arion's
@ -48,6 +100,7 @@ let
builtImage = buildOrStreamLayeredImage {
inherit (config.image)
name
tag
contents
includeStorePaths
;
@ -80,6 +133,16 @@ in
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;
};
@ -110,10 +173,39 @@ in
default = "localhost/" + config.service.name;
defaultText = lib.literalExpression or lib.literalExample ''"localhost/" + config.service.name'';
description = ''
A human readable name for the docker image.
A human readable name for the Docker image.
Shows up in the `docker ps` output in the
`IMAGE` column, among other places.
::: {.important}
If you set {option}`services.<name>.image.tarball` to an arbitrary
Docker image tarball (and not, say, a derivation produced by one of the
[`dockerTools`](https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-dockerTools)
builder functions), then you **must** set {option}`services.<name>.image.name`
to the name of the image in the tarball. Otherwise, Arion will not be
able to properly identify the image in the generated Docker Compose
configuration file.
:::
'';
};
image.tag = mkOption {
type = nullOr str;
default = null;
description = ''
A tag to assign to the built image, or (if you specified an image archive
with `image.tarball`) the tag that arion should use when referring to
the loaded image.
::: {.important}
If you set {option}`services.<name>.image.tarball` to an arbitrary
Docker image tarball (and not, say, a derivation produced by one of the
[`dockerTools`](https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-dockerTools)
builder functions), then you **must** set {option}`services.<name>.image.tag`
to one of the tags associated with `services.<name>.image.name` in the
image tarball. Otherwise, Arion will not be able to properly identify
the image in the generated Docker Compose configuration file.
:::
'';
};
image.contents = mkOption {
@ -162,9 +254,42 @@ in
description = ''
'';
};
image.tarball = mkOption {
type = nullOr imageTarballType;
default = builtImage;
defaultText = "${builtins.storeDir}/image-built-from-service-configuration.tar.gz";
description = ''
Docker image tarball to be `docker load`-ed. This can be a derivation
produced with one of the [`dockerTools`](https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-dockerTools)
builder functions, or a Docker image tarball at some arbitrary
location.
::: {.note}
Using this option causes Arion to ignore most other options in the
{option}`services.<name>.image` namespace. The exceptions are
{option}`services.<name>.image.name` and {option}`services.<name>.image.tag`,
which are used when the provided {option}`services.<name>.image.tarball`
is not a derivation with the attributes `imageName` and `imageTag`.
:::
'';
example = lib.literalExample or lib.literalExpression ''
let
myimage = pkgs.dockerTools.buildImage {
name = "my-image";
contents = [ pkgs.coreutils ];
};
in
config.services = {
myservice = {
image.tarball = myimage;
# ...
};
}
'';
};
};
config = lib.mkMerge [{
build.image = builtImage;
build.image = config.image.tarball;
build.imageName = config.build.image.imageName;
build.imageTag =
if config.build.image.imageTag != ""