diff --git a/src/nix/modules/composition/images.nix b/src/nix/modules/composition/images.nix index c3c09a3..278716d 100644 --- a/src/nix/modules/composition/images.nix +++ b/src/nix/modules/composition/images.nix @@ -14,22 +14,11 @@ let addDetails = serviceName: service: builtins.addErrorContext "while evaluating the image for service ${serviceName}" (let - inherit (service) build; + imageAttrName = "image${lib.optionalString (service.image.tarball.isExe or false) "Exe"}"; in { - imageName = build.imageName or service.image.name; - imageTag = - if build.image.imageTag != "" - then build.image.imageTag - 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; - } - ) - ); + inherit (service.image.tarball) imageName imageTag; + ${imageAttrName} = service.image.tarball.outPath; + }); in { options = { @@ -40,6 +29,21 @@ in }; }; config = { + assertions = let + assertionsForRepoTagComponent = component: attrName: + lib.mapAttrsToList (name: value: { + assertion = value.${attrName} != null; + 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; docker-compose.extended.images = config.build.imagesToLoad; }; diff --git a/src/nix/modules/service/image.nix b/src/nix/modules/service/image.nix index cc29578..6a9622d 100644 --- a/src/nix/modules/service/image.nix +++ b/src/nix/modules/service/image.nix @@ -35,13 +35,36 @@ let (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 (component != null && 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 # arbitrary paths and not `dockerTools`-produced derivations. - dummyImagePackage = outPath: { + dummyImagePackage = outPath: let + baseNameNoExtension = lib.strings.removeSuffix ".tar.gz" (baseNameOf outPath); + baseNameComponents = lib.strings.splitString "-" baseNameNoExtension; + fallbacks = + if lib.isStorePath outPath + then { + imageName = lib.concatStringsSep "-" (lib.tail baseNameComponents); + imageTag = lib.head baseNameComponents; + } + else { + imageName = null; + imageTag = null; + }; + in { inherit outPath; type = "derivation"; - imageName = config.image.name; - imageTag = if config.image.tag == null then "" else config.image.tag; + imageName = fallbackImageName fallbacks.imageName; + imageTag = fallbackImageTag fallbacks.imageTag; }; # Type matching the essential attributes of derivations produced by @@ -99,11 +122,11 @@ let builtImage = buildOrStreamLayeredImage { inherit (config.image) - name tag contents includeStorePaths ; + name = fallbackImageName ("localhost/" + config.service.name); config = config.image.rawConfig; maxLayers = 100; @@ -129,33 +152,6 @@ let in { 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 { type = bool; description = '' @@ -169,9 +165,8 @@ in ''; }; image.name = mkOption { - type = str; - default = "localhost/" + config.service.name; - defaultText = lib.literalExpression or lib.literalExample ''"localhost/" + config.service.name''; + type = nullOr str; + default = null; description = '' A human readable name for the Docker image. @@ -258,6 +253,11 @@ in builder functions, or a Docker image tarball at some arbitrary 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** that using this option causes Arion to ignore most other options in the `services.<name>.image` namespace. The exceptions are `services.<name>.image.name` and `services.<name>.image.tag`, which are @@ -266,34 +266,19 @@ in ''; default = builtImage; example = lib.literalExample or lib.literalExpression '' - let - myimage = pkgs.dockerTools.buildImage { - name = "my-image"; - contents = [ pkgs.coreutils ]; - }; - in - config.services = { - myservice = { - image.tarball = myimage; - # ... - }; - } + pkgs.dockerTools.buildLayeredImage { + # ... + }; ''; }; }; 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.nixBuild = lib.mkDefault (priorityIsDefault options.service.image); } ( 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}"; }) ]; }