diff --git a/CHANGELOG.md b/CHANGELOG.md index 405dedc..6c8d48d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,3 +3,4 @@ ## 0.1.0.0 -- YYYY-mm-dd * First version. Released on an unsuspecting world. +* *BREAKING:* useHostStore now uses a proper empty base image, like `scratch`. diff --git a/arion-compose.cabal b/arion-compose.cabal index c7fcef4..b0e56e8 100644 --- a/arion-compose.cabal +++ b/arion-compose.cabal @@ -19,6 +19,7 @@ data-files: nix/*.nix , nix/modules/composition/*.nix , nix/modules/nixos/*.nix , nix/modules/service/*.nix + , arion-image/Dockerfile -- all data is verbatim from some sources data-dir: src @@ -26,16 +27,19 @@ data-dir: src common deps build-depends: base ^>=4.12.0.0 , aeson + , async + , bytestring + , process + , process-extras , text , protolude library import: deps - -- exposed-modules: - -- other-modules: + exposed-modules: Arion.Nix + other-modules: Paths_arion_compose -- other-extensions: - build-depends: process hs-source-dirs: src/haskell/lib default-language: Haskell2010 @@ -45,7 +49,8 @@ executable arion -- other-modules: -- other-extensions: build-depends: optparse-applicative - , process + , aeson-pretty + , arion-compose hs-source-dirs: src/haskell/exe default-language: Haskell2010 diff --git a/arion.nix b/arion.nix index 9a23bde..2b4f81a 100644 --- a/arion.nix +++ b/arion.nix @@ -3,6 +3,7 @@ }: let + # TODO: Replace by a new expression for the new Haskell main arion = stdenv.mkDerivation { name = "arion"; src = ./src; diff --git a/nix/overlay.nix b/nix/overlay.nix index 97c319f..3ddc2f6 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -1,10 +1,22 @@ self: super: let inherit (self.arion-project) haskellPkgs; + + srcDir = ../src; # TODO gitignoreSource + whitelist nix and arion-image + eval = import (srcDir + "/nix/eval-composition.nix"); + build = args@{...}: + let composition = eval args; + in composition.config.build.dockerComposeYaml; + hlib = super.haskell.lib; in { + arion-v0 = super.callPackage ../arion.nix {}; - arion = super.haskell.lib.justStaticExecutables haskellPkgs.arion-compose; + arion = hlib.justStaticExecutables (hlib.overrideCabal haskellPkgs.arion-compose (o: { + passthru = o.passthru // { + inherit eval build; + }; + })); tests = super.callPackage ../tests {}; doc = super.callPackage ../doc {}; diff --git a/src/arion-image/Dockerfile b/src/arion-image/Dockerfile index 4aa6448..b4a070a 100644 --- a/src/arion-image/Dockerfile +++ b/src/arion-image/Dockerfile @@ -1,3 +1,5 @@ FROM scratch -COPY passwd /etc/passwd -ADD tarball.tar.gz / +# scratch itself can't be run. + +# This seems like a no-op: +CMD [] diff --git a/src/arion-image/passwd b/src/arion-image/passwd deleted file mode 100644 index 321840c..0000000 --- a/src/arion-image/passwd +++ /dev/null @@ -1,2 +0,0 @@ -root:x:0:0:System administrator:/root:/bin/sh -nobody:x:65534:65534:Unprivileged account (don't use!):/var/empty:/run/current-system/sw/bin/nologin diff --git a/src/arion-image/tarball/bin/sh b/src/arion-image/tarball/bin/sh deleted file mode 120000 index 261b8af..0000000 --- a/src/arion-image/tarball/bin/sh +++ /dev/null @@ -1 +0,0 @@ -/run/system/bin/sh \ No newline at end of file diff --git a/src/arion-image/tarball/usr/bin/env b/src/arion-image/tarball/usr/bin/env deleted file mode 120000 index 5871f63..0000000 --- a/src/arion-image/tarball/usr/bin/env +++ /dev/null @@ -1 +0,0 @@ -/run/system/usr/bin/env \ No newline at end of file diff --git a/src/haskell/exe/Main.hs b/src/haskell/exe/Main.hs index fe4f608..a4a0ec6 100644 --- a/src/haskell/exe/Main.hs +++ b/src/haskell/exe/Main.hs @@ -5,15 +5,21 @@ import Protolude hiding (Down) +import Arion.Nix + import Options.Applicative import Control.Applicative +import qualified Data.Aeson.Encode.Pretty import qualified Data.Text as T import qualified Data.Text.IO as T +import qualified Data.Text.Lazy as TL +import qualified Data.Text.Lazy.Builder as TB import qualified Data.List.NonEmpty as NE import Data.List.NonEmpty (NonEmpty(..)) + import Control.Arrow ((>>>)) data CommonOptions = @@ -46,7 +52,7 @@ parseOptions = do <> metavar "EXPR" <> showDefault <> value "./arion-pkgs.nix" - <> help "Use EXPR to get the Nixpkgs attrset used for bootstrapping \ + <> help "Use Nix expression EXPR to get the Nixpkgs attrset used for bootstrapping \ \and evaluating the configuration." ) pure CommonOptions{..} @@ -111,28 +117,12 @@ commandDC run cmdStr help = (run cmdStr <$> parseDockerComposeArgs) (progDesc (T.unpack help) <> fullDesc <> forwardOptions)) --------------------------------------------------------------------------------- - -modulesNixExpr :: NonEmpty FilePath -> Text -modulesNixExpr = - NE.toList - >>> fmap pathExpr - >>> T.unwords - >>> wrapList - where - pathExpr path | isAbsolute path = "(/. + \"" <> T.pack path <> "\")" - | otherwise = "(./. + \"" <> T.pack path <> "\")" - - isAbsolute ('/':_) = True - isAbsolute _ = False - - wrapList s = "[ " <> s <> " ]" -------------------------------------------------------------------------------- runDC :: Text -> DockerComposeArgs -> CommonOptions -> IO () runDC cmd (DockerComposeArgs args) opts = - T.putStrLn $ "TODO: docker-compose " <> cmd <> " " <> T.unwords args + panic $ "TODO: docker-compose " <> cmd <> " " <> T.unwords args runBuildAndDC :: Text -> DockerComposeArgs -> CommonOptions -> IO () runBuildAndDC cmd dopts opts = do @@ -146,8 +136,13 @@ runEvalAndDC cmd dopts opts = do runCat :: CommonOptions -> IO () runCat (CommonOptions files pkgs) = do - T.putStrLn "Running cat ... TODO" - T.putStrLn (modulesNixExpr files) + v <- Arion.Nix.evaluate EvaluationArgs + { evalUid = 0 -- TODO + , evalModules = files + , evalPkgs = pkgs + , evalWorkDir = Nothing + } + T.hPutStrLn stderr (TL.toStrict $ TB.toLazyText $ Data.Aeson.Encode.Pretty.encodePrettyToTextBuilder v) runRepl :: CommonOptions -> IO () runRepl opts = diff --git a/src/haskell/lib/Arion/Nix.hs b/src/haskell/lib/Arion/Nix.hs new file mode 100644 index 0000000..f985a90 --- /dev/null +++ b/src/haskell/lib/Arion/Nix.hs @@ -0,0 +1,91 @@ +{-# LANGUAGE OverloadedStrings #-} +module Arion.Nix where + +import Prelude ( ) +import Protolude +import Data.Aeson +import qualified Data.String +import System.Process +import qualified Data.ByteString as BS +import qualified Data.ByteString.Lazy as BL +import qualified System.Process.ByteString.Lazy + as PBL +import Paths_arion_compose +import Control.Applicative + +import qualified Data.Text as T +import qualified Data.Text.IO as T + +import qualified Data.List.NonEmpty as NE +import Data.List.NonEmpty ( NonEmpty(..) ) + +import Control.Arrow ( (>>>) ) + +data EvaluationArgs = EvaluationArgs + { evalUid :: Int + , evalModules :: NonEmpty FilePath + , evalPkgs :: Text + , evalWorkDir :: Maybe FilePath + } + +evaluate :: EvaluationArgs -> IO Value +evaluate ea = do + evalComposition <- getDataFileName "nix/eval-composition.nix" + let args = + [ evalComposition + , "--eval" + , "--strict" + , "--read-write-mode" + , "--json" + , "--show-trace" + , "--argstr" + , "uid" + , show $ evalUid ea + , "--arg" + , "modules" + , modulesNixExpr $ evalModules ea + , "--arg" + , "pkgs" + , toS $ evalPkgs ea + , "--attr" + , "config.build.dockerComposeYamlAttrs" + ] + stdin = mempty + procSpec = (proc "nix-instantiate" args) { cwd = evalWorkDir ea } + + -- TODO: lazy IO is tricky. Let's use conduit/pipes instead? + (exitCode, out, err) <- PBL.readCreateProcessWithExitCode procSpec stdin + + -- Stream 'err' + errDone <- async (BL.hPutStr stderr err) + + -- Force 'out' + v <- Protolude.evaluate (eitherDecode out) + + -- Wait for process exit and 'err' printout + wait errDone + + case exitCode of + ExitSuccess -> pass + ExitFailure e -> throwIO $ FatalError "Evaluation failed" -- TODO: don't print this exception in main + + case v of + Right r -> pure r + Left e -> throwIO $ FatalError "Couldn't parse nix-instantiate output" + + +modulesNixExpr :: NonEmpty FilePath -> [Char] +modulesNixExpr = + NE.toList >>> fmap pathExpr >>> Data.String.unwords >>> wrapList + where + pathExpr :: FilePath -> [Char] + pathExpr path | isAbsolute path = "(/. + \"/${" <> toNixStringLiteral path <> "}\")" + | otherwise = "(./. + \"/${" <> toNixStringLiteral path <> "}\")" + + isAbsolute ('/' : _) = True + isAbsolute _ = False + + wrapList s = "[ " <> s <> " ]" + +toNixStringLiteral :: [Char] -> [Char] +toNixStringLiteral = show -- FIXME: custom escaping including '$' diff --git a/src/nix/modules/composition/docker-compose.nix b/src/nix/modules/composition/docker-compose.nix index 95855ff..6734041 100644 --- a/src/nix/modules/composition/docker-compose.nix +++ b/src/nix/modules/composition/docker-compose.nix @@ -19,10 +19,17 @@ in build.dockerComposeYaml = lib.mkOption { type = lib.types.package; description = "A derivation that produces a docker-compose.yaml file for this composition."; + readOnly = true; }; build.dockerComposeYamlText = lib.mkOption { type = lib.types.string; description = "The text of build.dockerComposeYaml."; + readOnly = true; + }; + build.dockerComposeYamlAttrs = lib.mkOption { + type = lib.types.attrsOf lib.types.unspecified; + description = "The text of build.dockerComposeYaml."; + readOnly = true; }; docker-compose.raw = lib.mkOption { type = lib.types.attrs; @@ -45,7 +52,8 @@ in }; config = { build.dockerComposeYaml = pkgs.writeText "docker-compose.yaml" config.build.dockerComposeYamlText; - build.dockerComposeYamlText = builtins.toJSON (config.docker-compose.raw); + build.dockerComposeYamlText = builtins.toJSON (config.build.dockerComposeYamlAttrs); + build.dockerComposeYamlAttrs = config.docker-compose.raw; docker-compose.evaluatedServices = lib.mapAttrs evalService config.docker-compose.services; docker-compose.raw = {