Implement image loading, use it instead of arion-base
This commit is contained in:
parent
fcf270c80c
commit
1fe10c076d
8 changed files with 110 additions and 13 deletions
|
@ -31,6 +31,8 @@ common deps
|
||||||
, async
|
, async
|
||||||
, bytestring
|
, bytestring
|
||||||
, directory
|
, directory
|
||||||
|
, lens
|
||||||
|
, lens-aeson
|
||||||
, process
|
, process
|
||||||
, process-extras
|
, process-extras
|
||||||
, temporary
|
, temporary
|
||||||
|
@ -46,6 +48,7 @@ library
|
||||||
exposed-modules: Arion.Nix
|
exposed-modules: Arion.Nix
|
||||||
Arion.Aeson
|
Arion.Aeson
|
||||||
Arion.DockerCompose
|
Arion.DockerCompose
|
||||||
|
Arion.Images
|
||||||
other-modules: Paths_arion_compose
|
other-modules: Paths_arion_compose
|
||||||
-- other-extensions:
|
-- other-extensions:
|
||||||
hs-source-dirs: src/haskell/lib
|
hs-source-dirs: src/haskell/lib
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# to update: $ nix-prefetch-url --unpack url
|
# to update: $ nix-prefetch-url --unpack url
|
||||||
builtins.fetchTarball {
|
builtins.fetchTarball {
|
||||||
url = "https://github.com/NixOS/nixpkgs/archive/be445a9074f139d63e704fa82610d25456562c3d.tar.gz";
|
url = "https://github.com/NixOS/nixpkgs/archive/bd5e8f35c2e9d1ddc9cd2fea7a23563336d54acb.tar.gz";
|
||||||
sha256 = "15dc7gdspimavcwyw9nif4s59v79gk18rwsafylffs9m1ld2dxwa";
|
sha256 = "1wnzqqijrwf797nb234q10zb1h7086njradkkrx3a15b303grsw4";
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import Protolude hiding (Down)
|
||||||
|
|
||||||
import Arion.Nix
|
import Arion.Nix
|
||||||
import Arion.Aeson
|
import Arion.Aeson
|
||||||
|
import Arion.Images (loadImages)
|
||||||
import qualified Arion.DockerCompose as DockerCompose
|
import qualified Arion.DockerCompose as DockerCompose
|
||||||
|
|
||||||
import Options.Applicative
|
import Options.Applicative
|
||||||
|
@ -141,7 +142,8 @@ runDC cmd (DockerComposeArgs args) opts = do
|
||||||
runBuildAndDC :: Text -> DockerComposeArgs -> CommonOptions -> IO ()
|
runBuildAndDC :: Text -> DockerComposeArgs -> CommonOptions -> IO ()
|
||||||
runBuildAndDC cmd dopts opts = do
|
runBuildAndDC cmd dopts opts = do
|
||||||
ea <- defaultEvaluationArgs opts
|
ea <- defaultEvaluationArgs opts
|
||||||
Arion.Nix.withBuiltComposition ea $ \path ->
|
Arion.Nix.withBuiltComposition ea $ \path -> do
|
||||||
|
loadImages path
|
||||||
DockerCompose.run DockerCompose.Args
|
DockerCompose.run DockerCompose.Args
|
||||||
{ files = [path]
|
{ files = [path]
|
||||||
, otherArgs = [cmd] ++ unDockerComposeArgs dopts
|
, otherArgs = [cmd] ++ unDockerComposeArgs dopts
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
module Arion.Aeson where
|
module Arion.Aeson where
|
||||||
|
|
||||||
|
import Prelude ()
|
||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
|
import qualified Data.ByteString.Lazy as BL
|
||||||
import qualified Data.Text.Lazy as TL
|
import qualified Data.Text.Lazy as TL
|
||||||
import qualified Data.Text.Lazy.IO as TL
|
import qualified Data.Text.Lazy.IO as TL
|
||||||
import qualified Data.Text.Lazy.Builder as TB
|
import qualified Data.Text.Lazy.Builder as TB
|
||||||
|
@ -18,3 +20,10 @@ pretty =
|
||||||
. TB.toLazyText
|
. TB.toLazyText
|
||||||
. Data.Aeson.Encode.Pretty.encodePrettyToTextBuilder' config
|
. Data.Aeson.Encode.Pretty.encodePrettyToTextBuilder' config
|
||||||
where config = defConfig { confCompare = compare, confTrailingNewline = True }
|
where config = defConfig { confCompare = compare, confTrailingNewline = True }
|
||||||
|
|
||||||
|
decodeFile :: FromJSON a => FilePath -> IO a
|
||||||
|
decodeFile fp = do
|
||||||
|
b <- BL.readFile fp
|
||||||
|
case eitherDecode b of
|
||||||
|
Left e -> panic (toS e)
|
||||||
|
Right v -> pure v
|
||||||
|
|
60
src/haskell/lib/Arion/Images.hs
Normal file
60
src/haskell/lib/Arion/Images.hs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
{-# LANGUAGE DeriveAnyClass #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Arion.Images
|
||||||
|
( loadImages
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Prelude()
|
||||||
|
import Protolude hiding (to)
|
||||||
|
|
||||||
|
import qualified Data.Aeson as Aeson
|
||||||
|
import Arion.Aeson (decodeFile)
|
||||||
|
import qualified Data.ByteString as BS
|
||||||
|
import qualified System.Process as Process
|
||||||
|
|
||||||
|
import Control.Lens
|
||||||
|
import Data.Aeson.Lens
|
||||||
|
import Data.String
|
||||||
|
import System.IO (withFile, IOMode(ReadMode))
|
||||||
|
|
||||||
|
|
||||||
|
data Image = Image
|
||||||
|
{ image :: Text -- ^ file path
|
||||||
|
, imageName :: Text
|
||||||
|
, imageTag :: Text
|
||||||
|
} deriving (Generic, Aeson.ToJSON, Aeson.FromJSON, Show)
|
||||||
|
|
||||||
|
type TaggedImage = Text
|
||||||
|
|
||||||
|
loadImages :: FilePath -> IO ()
|
||||||
|
loadImages fp = do
|
||||||
|
|
||||||
|
v <- decodeFile fp
|
||||||
|
|
||||||
|
loaded <- dockerImages
|
||||||
|
|
||||||
|
let
|
||||||
|
images :: [Image]
|
||||||
|
images = (v :: Aeson.Value) ^.. key "x-arion" . key "images" . _Array . traverse . _JSON
|
||||||
|
|
||||||
|
isNew i = (imageName i <> ":" <> imageTag i) `notElem` loaded
|
||||||
|
|
||||||
|
traverse_ loadImage . map (toS . image) . filter isNew $ images
|
||||||
|
|
||||||
|
loadImage :: FilePath -> IO ()
|
||||||
|
loadImage imgPath = withFile (imgPath) ReadMode $ \fileHandle -> do
|
||||||
|
let procSpec = (Process.proc "docker" [ "load" ]) {
|
||||||
|
Process.std_in = Process.UseHandle fileHandle
|
||||||
|
}
|
||||||
|
Process.withCreateProcess procSpec $ \_in _out _err procHandle -> do
|
||||||
|
e <- Process.waitForProcess procHandle
|
||||||
|
case e of
|
||||||
|
ExitSuccess -> pass
|
||||||
|
ExitFailure code -> panic $ "docker load (" <> show code <> ") failed for " <> toS imgPath
|
||||||
|
|
||||||
|
|
||||||
|
dockerImages :: IO [TaggedImage]
|
||||||
|
dockerImages = do
|
||||||
|
let procSpec = Process.proc "docker" [ "images", "--filter", "dangling=false", "--format", "{{.Repository}}:{{.Tag}}" ]
|
||||||
|
(map toS . lines) <$> Process.readCreateProcess procSpec ""
|
|
@ -32,12 +32,27 @@ spec = describe "evaluateComposition" $ it "matches an example" $ do
|
||||||
expected <- T.readFile "src/haskell/testdata/Arion/NixSpec/arion-compose.json"
|
expected <- T.readFile "src/haskell/testdata/Arion/NixSpec/arion-compose.json"
|
||||||
censorPaths actual `shouldBe` censorPaths expected
|
censorPaths actual `shouldBe` censorPaths expected
|
||||||
|
|
||||||
censorPaths :: Text -> Text
|
censorPaths = censorImages . censorStorePaths
|
||||||
censorPaths x = case T.breakOn "/nix/store/" x of
|
--censorPaths = censorStorePaths
|
||||||
|
|
||||||
|
censorStorePaths :: Text -> Text
|
||||||
|
censorStorePaths x = case T.breakOn "/nix/store/" x of
|
||||||
(prefix, tl) | (tl :: Text) == "" -> prefix
|
(prefix, tl) | (tl :: Text) == "" -> prefix
|
||||||
(prefix, tl) -> prefix <> "<STOREPATH>" <> censorPaths
|
(prefix, tl) -> prefix <> "<STOREPATH>" <> censorPaths
|
||||||
(T.dropWhile isNixNameChar $ T.drop (T.length "/nix/store/") tl)
|
(T.dropWhile isNixNameChar $ T.drop (T.length "/nix/store/") tl)
|
||||||
|
|
||||||
|
-- Probably slow, due to not O(1) <>
|
||||||
|
censorImages :: Text -> Text
|
||||||
|
censorImages x = case T.break (\c -> c == ':' || c == '"') x of
|
||||||
|
(prefix, tl) | tl == "" -> prefix
|
||||||
|
(prefix, tl) | let imageId = T.take 33 (T.drop 1 tl)
|
||||||
|
, T.last imageId == '\"'
|
||||||
|
-- Approximation of nix hash validation
|
||||||
|
, T.all (\c -> (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')) (T.take 32 imageId)
|
||||||
|
-> prefix <> T.take 1 tl <> "<HASH>" <> censorImages (T.drop 33 tl)
|
||||||
|
(prefix, tl) -> prefix <> T.take 1 tl <> censorImages (T.drop 1 tl)
|
||||||
|
|
||||||
|
|
||||||
-- | WARNING: THIS IS LIKELY WRONG: DON'T REUSE
|
-- | WARNING: THIS IS LIKELY WRONG: DON'T REUSE
|
||||||
isNixNameChar :: Char -> Bool
|
isNixNameChar :: Char -> Bool
|
||||||
isNixNameChar c | c >= '0' && c <= '9' = True
|
isNixNameChar c | c >= '0' && c <= '9' = True
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
{
|
{
|
||||||
"services": {
|
"services": {
|
||||||
"webserver": {
|
"webserver": {
|
||||||
"build": {
|
|
||||||
"context": "/nix/store/l6jwin74n93d66ralxzb001c22yjii9x-arion-image"
|
|
||||||
},
|
|
||||||
"command": [
|
"command": [
|
||||||
"/nix/store/b9w61w4g8sqgrm3rid6ca22krslqghb3-nixos-system-unnamed-19.03.173100.e726e8291b2/init"
|
"/nix/store/b9w61w4g8sqgrm3rid6ca22krslqghb3-nixos-system-unnamed-19.03.173100.e726e8291b2/init"
|
||||||
],
|
],
|
||||||
|
@ -12,7 +9,7 @@
|
||||||
"PATH": "/usr/bin:/run/current-system/sw/bin/",
|
"PATH": "/usr/bin:/run/current-system/sw/bin/",
|
||||||
"container": "docker"
|
"container": "docker"
|
||||||
},
|
},
|
||||||
"image": "arion-base",
|
"image": "webserver:<HASH>",
|
||||||
"ports": [
|
"ports": [
|
||||||
"8000:80"
|
"8000:80"
|
||||||
],
|
],
|
||||||
|
@ -33,7 +30,13 @@
|
||||||
},
|
},
|
||||||
"version": "3.4",
|
"version": "3.4",
|
||||||
"x-arion": {
|
"x-arion": {
|
||||||
"images": [],
|
"images": [
|
||||||
|
{
|
||||||
|
"image": "<STOREPATH>",
|
||||||
|
"imageName": "webserver",
|
||||||
|
"imageTag": "<HASH>"
|
||||||
|
}
|
||||||
|
],
|
||||||
"serviceInfo": {
|
"serviceInfo": {
|
||||||
"webserver": {
|
"webserver": {
|
||||||
"defaultExec": [
|
"defaultExec": [
|
||||||
|
|
|
@ -29,9 +29,14 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config = mkIf config.service.useHostStore {
|
config = mkIf config.service.useHostStore {
|
||||||
image.nixBuild = false; # no need to build and load
|
image.nixBuild = true;
|
||||||
service.image = "arion-base";
|
image.contents = [
|
||||||
service.build.context = "${../../../arion-image}";
|
(pkgs.runCommand "minimal-contents" {} ''
|
||||||
|
mkdir -p $out/bin $out/usr/bin
|
||||||
|
ln -s /run/system/bin/sh $out/bin/sh
|
||||||
|
ln -s /run/system/usr/bin/env $out/usr/bin/env
|
||||||
|
'')
|
||||||
|
];
|
||||||
service.environment.NIX_REMOTE = lib.optionalString config.service.useHostNixDaemon "daemon";
|
service.environment.NIX_REMOTE = lib.optionalString config.service.useHostNixDaemon "daemon";
|
||||||
service.volumes = [
|
service.volumes = [
|
||||||
"${config.host.nixStorePrefix}/nix/store:/nix/store${lib.optionalString config.service.hostStoreAsReadOnly ":ro"}"
|
"${config.host.nixStorePrefix}/nix/store:/nix/store${lib.optionalString config.service.hostStoreAsReadOnly ":ro"}"
|
||||||
|
|
Loading…
Reference in a new issue