diff --git a/examples/full-nixos/arion-compose.nix b/examples/full-nixos/arion-compose.nix index 1524e3a..47c2c28 100644 --- a/examples/full-nixos/arion-compose.nix +++ b/examples/full-nixos/arion-compose.nix @@ -1,4 +1,6 @@ { + deployment.technology = "podman"; + services.webserver = { pkgs, lib, ... }: { nixos.useSystemd = true; nixos.configuration.boot.tmpOnTmpfs = true; diff --git a/nix/overlay.nix b/nix/overlay.nix index bbacd30..8067c56 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -48,6 +48,8 @@ in haskellPkgs.ghcid haskellPkgs.haskell-language-server super.docker-compose + super.podman + super.podman-compose self.niv self.releaser ]; diff --git a/src/haskell/exe/Main.hs b/src/haskell/exe/Main.hs index eeceda1..f6ffc7c 100644 --- a/src/haskell/exe/Main.hs +++ b/src/haskell/exe/Main.hs @@ -10,7 +10,7 @@ import Arion.Aeson import Arion.Images (loadImages) import qualified Arion.DockerCompose as DockerCompose import Arion.Services (getDefaultExec) -import Arion.ExtendedInfo (loadExtendedInfoFromPath, ExtendedInfo(images, projectName)) +import Arion.ExtendedInfo (loadExtendedInfoFromPath, ExtendedInfo(images, projectName), technology, Technology(..)) import Options.Applicative import Control.Monad.Fail @@ -147,6 +147,7 @@ runDC cmd (DockerComposeArgs args) _opts = do DockerCompose.run DockerCompose.Args { files = [] , otherArgs = [cmd] ++ args + , isPodman = True -- FIXME } runBuildAndDC :: Text -> DockerComposeArgs -> CommonOptions -> IO () @@ -160,11 +161,13 @@ runEvalAndDC cmd dopts opts = do callDC :: Text -> DockerComposeArgs -> CommonOptions -> Bool -> FilePath -> IO () callDC cmd dopts opts shouldLoadImages path = do extendedInfo <- loadExtendedInfoFromPath path - when shouldLoadImages $ loadImages (images extendedInfo) + let is_podman = technology extendedInfo == Podman + when shouldLoadImages $ loadImages is_podman (images extendedInfo) let firstOpts = projectArgs extendedInfo <> commonArgs opts DockerCompose.run DockerCompose.Args { files = [path] , otherArgs = firstOpts ++ [cmd] ++ unDockerComposeArgs dopts + , isPodman = is_podman } projectArgs :: ExtendedInfo -> [Text] @@ -299,6 +302,7 @@ runExec detach privileged user noTTY index envs workDir service commandAndArgs o [] -> ["/bin/sh"] x -> x + let is_podman = technology extendedInfo == Podman let args = concat [ ["exec"] , ("--detach" <$ guard detach :: [Text]) @@ -314,6 +318,7 @@ runExec detach privileged user noTTY index envs workDir service commandAndArgs o DockerCompose.run DockerCompose.Args { files = [path] , otherArgs = projectArgs extendedInfo <> commonArgs opts <> args + , isPodman = is_podman } main :: IO () diff --git a/src/haskell/lib/Arion/DockerCompose.hs b/src/haskell/lib/Arion/DockerCompose.hs index f44d86f..365ae2c 100644 --- a/src/haskell/lib/Arion/DockerCompose.hs +++ b/src/haskell/lib/Arion/DockerCompose.hs @@ -8,6 +8,7 @@ import System.Process data Args = Args { files :: [FilePath] , otherArgs :: [Text] + , isPodman :: Bool } run :: Args -> IO () @@ -15,9 +16,9 @@ run args = do let fileArgs = files args >>= \f -> ["--file", f] allArgs = fileArgs ++ map toS (otherArgs args) - procSpec = proc "docker-compose" allArgs - - -- hPutStrLn stderr ("Running docker-compose with " <> show allArgs :: Text) + exeName = if isPodman args then "podman-compose" else "docker-compose" + procSpec = + proc exeName allArgs withCreateProcess procSpec $ \_in _out _err procHandle -> do @@ -27,4 +28,4 @@ run args = do ExitSuccess -> pass ExitFailure 1 -> exitFailure ExitFailure {} -> do - throwIO $ FatalError $ "docker-compose failed with " <> show exitCode + throwIO $ FatalError $ toS exeName <> " failed with status " <> show exitCode diff --git a/src/haskell/lib/Arion/ExtendedInfo.hs b/src/haskell/lib/Arion/ExtendedInfo.hs index 92a7668..577b5ef 100644 --- a/src/haskell/lib/Arion/ExtendedInfo.hs +++ b/src/haskell/lib/Arion/ExtendedInfo.hs @@ -22,9 +22,13 @@ data Image = Image , imageTag :: Text } deriving (Eq, Show, Generic, Aeson.ToJSON, Aeson.FromJSON) +data Technology = Docker | Podman + deriving (Eq, Show) + data ExtendedInfo = ExtendedInfo { projectName :: Maybe Text, - images :: [Image] + images :: [Image], + technology :: Technology } deriving (Eq, Show) loadExtendedInfoFromPath :: FilePath -> IO ExtendedInfo @@ -33,5 +37,10 @@ loadExtendedInfoFromPath fp = do pure ExtendedInfo { -- TODO: use aeson derived instance? projectName = v ^? key "x-arion" . key "project" . key "name" . _String, - images = (v :: Aeson.Value) ^.. key "x-arion" . key "images" . _Array . traverse . _JSON + images = (v :: Aeson.Value) ^.. key "x-arion" . key "images" . _Array . traverse . _JSON, + technology = + case v ^? key "x-arion" . key "technology" . _String of + Just "podman" -> Podman + Just "docker" -> Docker + _ -> panic "Unknown x-arion.technology" -- Shouldn't happen } diff --git a/src/haskell/lib/Arion/Images.hs b/src/haskell/lib/Arion/Images.hs index da2ad3c..7d43afa 100644 --- a/src/haskell/lib/Arion/Images.hs +++ b/src/haskell/lib/Arion/Images.hs @@ -16,10 +16,10 @@ import Arion.ExtendedInfo (Image(..)) type TaggedImage = Text -- | Subject to change -loadImages :: [Image] -> IO () -loadImages requestedImages = do +loadImages :: Bool -> [Image] -> IO () +loadImages isPodman requestedImages = do - loaded <- getDockerImages + loaded <- getDockerImages isPodman let isNew i = @@ -28,23 +28,28 @@ loadImages requestedImages = do -- -- On podman, you automatically get a localhost prefix && ("localhost/" <> imageName i <> ":" <> imageTag i) `notElem` loaded - traverse_ loadImage . filter isNew $ requestedImages + traverse_ (loadImage isPodman) . filter isNew $ requestedImages -loadImage :: Image -> IO () -loadImage Image { image = Just imgPath, imageName = name } = +exeName :: IsString p => Bool -> p +exeName _isPodman@True = "podman" +exeName _isPodman@False = "docker" + +loadImage :: Bool -> Image -> IO () +loadImage isPodman Image { image = Just imgPath, imageName = name } = withFile (toS imgPath) ReadMode $ \fileHandle -> do - let procSpec = (Process.proc "docker" [ "load" ]) { + let procSpec = (Process.proc (exeName isPodman) [ "load" ]) { Process.std_in = Process.UseHandle fileHandle } + print procSpec Process.withCreateProcess procSpec $ \_in _out _err procHandle -> do e <- Process.waitForProcess procHandle case e of ExitSuccess -> pass ExitFailure code -> - panic $ "docker load failed with exit code " <> show code <> " for image " <> name <> " from path " <> imgPath + panic $ exeName isPodman <> " load failed with exit code " <> show code <> " for image " <> name <> " from path " <> imgPath -loadImage Image { imageExe = Just imgExe, imageName = name } = do - let loadSpec = (Process.proc "docker" [ "load" ]) { Process.std_in = Process.CreatePipe } +loadImage isPodman Image { imageExe = Just imgExe, imageName = name } = do + let loadSpec = (Process.proc (exeName isPodman) [ "load" ]) { Process.std_in = Process.CreatePipe } Process.withCreateProcess loadSpec $ \(Just inHandle) _out _err loadProcHandle -> do let streamSpec = Process.proc (toS imgExe) [] Process.withCreateProcess streamSpec { Process.std_out = Process.UseHandle inHandle } $ \_ _ _ streamProcHandle -> @@ -57,15 +62,15 @@ loadImage Image { imageExe = Just imgExe, imageName = name } = do Left _ -> pass loadExit <- wait loadExitAsync case loadExit of - ExitFailure code -> panic $ "docker load failed with exit code " <> show code <> " for image " <> name <> " produced by executable " <> imgExe + ExitFailure code -> panic $ exeName isPodman <> " load failed with exit code " <> show code <> " for image " <> name <> " produced by executable " <> imgExe _ -> pass pass -loadImage Image { imageName = name } = do +loadImage _isPodman Image { imageName = name } = do panic $ "image " <> name <> " doesn't specify an image file or imageExe executable" -getDockerImages :: IO [TaggedImage] -getDockerImages = do - let procSpec = Process.proc "docker" [ "images", "--filter", "dangling=false", "--format", "{{.Repository}}:{{.Tag}}" ] +getDockerImages :: Bool -> IO [TaggedImage] +getDockerImages isPodman = do + let procSpec = Process.proc (exeName isPodman) [ "images", "--filter", "dangling=false", "--format", "{{.Repository}}:{{.Tag}}" ] map toS . T.lines . toS <$> Process.readCreateProcess procSpec "" diff --git a/src/nix/modules.nix b/src/nix/modules.nix index ecc73c1..4a8ef27 100644 --- a/src/nix/modules.nix +++ b/src/nix/modules.nix @@ -4,4 +4,7 @@ ./modules/composition/images.nix ./modules/composition/service-info.nix ./modules/composition/composition.nix -] \ No newline at end of file + ./modules/composition/deployment.nix + ./modules/composition/deployment/docker.nix + ./modules/composition/deployment/podman.nix +] diff --git a/src/nix/modules/composition/docker-compose.nix b/src/nix/modules/composition/docker-compose.nix index a74c958..c7652fe 100644 --- a/src/nix/modules/composition/docker-compose.nix +++ b/src/nix/modules/composition/docker-compose.nix @@ -29,6 +29,20 @@ let config.service.name = name; }; + json.type = with lib.types; let + valueType = nullOr (oneOf [ + bool + int + float + str + path # extra + package # extra + (attrsOf valueType) + (listOf valueType) + ]) // { + description = "JSON value"; + }; + in valueType; in { imports = [ @@ -52,11 +66,11 @@ in readOnly = true; }; docker-compose.raw = lib.mkOption { - type = lib.types.attrs; + type = json.type; description = "Attribute set that will be turned into the docker-compose.yaml file, using Nix's toJSON builtin."; }; docker-compose.extended = lib.mkOption { - type = lib.types.attrs; + type = json.type; description = "Attribute set that will be turned into the x-arion section of the docker-compose.yaml file."; }; services = lib.mkOption {