Merge pull request #104 from hercules-ci/set-project-name
Add name option for project/composition name
This commit is contained in:
commit
896316ce29
10 changed files with 143 additions and 35 deletions
|
@ -49,6 +49,7 @@ library
|
||||||
exposed-modules: Arion.Nix
|
exposed-modules: Arion.Nix
|
||||||
Arion.Aeson
|
Arion.Aeson
|
||||||
Arion.DockerCompose
|
Arion.DockerCompose
|
||||||
|
Arion.ExtendedInfo
|
||||||
Arion.Images
|
Arion.Images
|
||||||
Arion.Services
|
Arion.Services
|
||||||
other-modules: Paths_arion_compose
|
other-modules: Paths_arion_compose
|
||||||
|
|
|
@ -106,6 +106,26 @@ No Default:: {blank}
|
||||||
Read Only:: {blank}
|
Read Only:: {blank}
|
||||||
No Example:: {blank}
|
No Example:: {blank}
|
||||||
|
|
||||||
|
== project.name
|
||||||
|
|
||||||
|
Name of the project.
|
||||||
|
|
||||||
|
See link:https://docs.docker.com/compose/reference/envvars/#compose_project_name[COMPOSE_PROJECT_NAME]
|
||||||
|
|
||||||
|
|
||||||
|
[discrete]
|
||||||
|
=== details
|
||||||
|
|
||||||
|
Type:: null or string
|
||||||
|
Default::
|
||||||
|
+
|
||||||
|
----
|
||||||
|
null
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
No Example:: {blank}
|
||||||
|
|
||||||
== services
|
== services
|
||||||
|
|
||||||
An attribute set of service configurations. A service specifies how to run an image as a container.
|
An attribute set of service configurations. A service specifies how to run an image as a container.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{ pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
{
|
{
|
||||||
|
config.project.name = "webapp";
|
||||||
config.services = {
|
config.services = {
|
||||||
|
|
||||||
webserver = {
|
webserver = {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import Arion.Aeson
|
||||||
import Arion.Images (loadImages)
|
import Arion.Images (loadImages)
|
||||||
import qualified Arion.DockerCompose as DockerCompose
|
import qualified Arion.DockerCompose as DockerCompose
|
||||||
import Arion.Services (getDefaultExec)
|
import Arion.Services (getDefaultExec)
|
||||||
|
import Arion.ExtendedInfo (loadExtendedInfoFromPath, ExtendedInfo(images, projectName))
|
||||||
|
|
||||||
import Options.Applicative
|
import Options.Applicative
|
||||||
import Control.Monad.Fail
|
import Control.Monad.Fail
|
||||||
|
@ -27,6 +28,9 @@ data CommonOptions =
|
||||||
, pkgs :: Text
|
, pkgs :: Text
|
||||||
, nixArgs :: [Text]
|
, nixArgs :: [Text]
|
||||||
, prebuiltComposeFile :: Maybe FilePath
|
, prebuiltComposeFile :: Maybe FilePath
|
||||||
|
, noAnsi :: Bool
|
||||||
|
, compatibility :: Bool
|
||||||
|
, logLevel :: Maybe Text
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
|
@ -63,6 +67,11 @@ parseOptions = do
|
||||||
( long "prebuilt-file"
|
( long "prebuilt-file"
|
||||||
<> metavar "JSONFILE"
|
<> metavar "JSONFILE"
|
||||||
<> help "Do not evaluate and use the prebuilt JSONFILE instead. Causes other evaluation-related options to be ignored." )
|
<> help "Do not evaluate and use the prebuilt JSONFILE instead. Causes other evaluation-related options to be ignored." )
|
||||||
|
noAnsi <- flag False True (long "no-ansi"
|
||||||
|
<> help "Avoid ANSI control sequences")
|
||||||
|
compatibility <- flag False True (long "no-ansi"
|
||||||
|
<> help "If set, Docker Compose will attempt to convert deploy keys in v3 files to their non-Swarm equivalent")
|
||||||
|
logLevel <- optional $ fmap T.pack $ strOption (long "log-level" <> metavar "LEVEL" <> help "Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)")
|
||||||
pure $
|
pure $
|
||||||
let nixArgs = userNixArgs <|> "--show-trace" <$ guard showTrace
|
let nixArgs = userNixArgs <|> "--show-trace" <$ guard showTrace
|
||||||
in CommonOptions{..}
|
in CommonOptions{..}
|
||||||
|
@ -142,20 +151,38 @@ 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
|
||||||
withBuiltComposeFile opts $ \path -> do
|
withBuiltComposeFile opts $ callDC cmd dopts opts True
|
||||||
loadImages path
|
|
||||||
DockerCompose.run DockerCompose.Args
|
|
||||||
{ files = [path]
|
|
||||||
, otherArgs = [cmd] ++ unDockerComposeArgs dopts
|
|
||||||
}
|
|
||||||
|
|
||||||
runEvalAndDC :: Text -> DockerComposeArgs -> CommonOptions -> IO ()
|
runEvalAndDC :: Text -> DockerComposeArgs -> CommonOptions -> IO ()
|
||||||
runEvalAndDC cmd dopts opts = do
|
runEvalAndDC cmd dopts opts = do
|
||||||
withComposeFile opts $ \path ->
|
withComposeFile opts $ callDC cmd dopts opts False
|
||||||
DockerCompose.run DockerCompose.Args
|
|
||||||
{ files = [path]
|
callDC :: Text -> DockerComposeArgs -> CommonOptions -> Bool -> FilePath -> IO ()
|
||||||
, otherArgs = [cmd] ++ unDockerComposeArgs dopts
|
callDC cmd dopts opts shouldLoadImages path = do
|
||||||
}
|
extendedInfo <- loadExtendedInfoFromPath path
|
||||||
|
when shouldLoadImages $ loadImages (images extendedInfo)
|
||||||
|
let firstOpts = projectArgs extendedInfo <> commonArgs opts
|
||||||
|
DockerCompose.run DockerCompose.Args
|
||||||
|
{ files = [path]
|
||||||
|
, otherArgs = firstOpts ++ [cmd] ++ unDockerComposeArgs dopts
|
||||||
|
}
|
||||||
|
|
||||||
|
projectArgs :: ExtendedInfo -> [Text]
|
||||||
|
projectArgs extendedInfo =
|
||||||
|
do
|
||||||
|
n <- toList (projectName extendedInfo)
|
||||||
|
["--project-name", n]
|
||||||
|
|
||||||
|
commonArgs :: CommonOptions -> [Text]
|
||||||
|
commonArgs opts = do
|
||||||
|
guard (noAnsi opts)
|
||||||
|
["--no-ansi"]
|
||||||
|
<> do
|
||||||
|
guard (compatibility opts)
|
||||||
|
["--compatibility"]
|
||||||
|
<> do
|
||||||
|
l <- toList (logLevel opts)
|
||||||
|
["--log-level", l]
|
||||||
|
|
||||||
withBuiltComposeFile :: CommonOptions -> (FilePath -> IO r) -> IO r
|
withBuiltComposeFile :: CommonOptions -> (FilePath -> IO r) -> IO r
|
||||||
withBuiltComposeFile opts cont = case prebuiltComposeFile opts of
|
withBuiltComposeFile opts cont = case prebuiltComposeFile opts of
|
||||||
|
@ -255,12 +282,18 @@ orEmpty' :: (Alternative f, Monoid a) => f a -> f a
|
||||||
orEmpty' m = fromMaybe mempty <$> optional m
|
orEmpty' m = fromMaybe mempty <$> optional m
|
||||||
|
|
||||||
runExec :: Bool -> Bool -> Maybe Text -> Bool -> Int -> [(Text, Text)] -> Maybe Text -> Text -> [Text] -> CommonOptions -> IO ()
|
runExec :: Bool -> Bool -> Maybe Text -> Bool -> Int -> [(Text, Text)] -> Maybe Text -> Text -> [Text] -> CommonOptions -> IO ()
|
||||||
runExec detach privileged user noTTY index envs workDir service commandAndArgs opts = do
|
runExec detach privileged user noTTY index envs workDir service commandAndArgs opts =
|
||||||
putErrText $ "Service: " <> service
|
|
||||||
|
|
||||||
withComposeFile opts $ \path -> do
|
withComposeFile opts $ \path -> do
|
||||||
|
extendedInfo <- loadExtendedInfoFromPath path
|
||||||
commandAndArgs'' <- case commandAndArgs of
|
commandAndArgs'' <- case commandAndArgs of
|
||||||
[] -> getDefaultExec path service
|
[] -> do
|
||||||
|
cmd <- getDefaultExec path service
|
||||||
|
case cmd of
|
||||||
|
[] -> do
|
||||||
|
putErrText "You must provide a command via service.defaultExec or on the command line."
|
||||||
|
exitFailure
|
||||||
|
_ ->
|
||||||
|
pure cmd
|
||||||
x -> pure x
|
x -> pure x
|
||||||
let commandAndArgs' = case commandAndArgs'' of
|
let commandAndArgs' = case commandAndArgs'' of
|
||||||
[] -> ["/bin/sh"]
|
[] -> ["/bin/sh"]
|
||||||
|
@ -280,7 +313,7 @@ runExec detach privileged user noTTY index envs workDir service commandAndArgs o
|
||||||
]
|
]
|
||||||
DockerCompose.run DockerCompose.Args
|
DockerCompose.run DockerCompose.Args
|
||||||
{ files = [path]
|
{ files = [path]
|
||||||
, otherArgs = args
|
, otherArgs = projectArgs extendedInfo <> commonArgs opts <> args
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
|
|
37
src/haskell/lib/Arion/ExtendedInfo.hs
Normal file
37
src/haskell/lib/Arion/ExtendedInfo.hs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{-# LANGUAGE DeriveAnyClass #-}
|
||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-
|
||||||
|
|
||||||
|
Parses the x-arion field in the generated compose file.
|
||||||
|
|
||||||
|
-}
|
||||||
|
module Arion.ExtendedInfo where
|
||||||
|
|
||||||
|
import Prelude()
|
||||||
|
import Protolude
|
||||||
|
import Data.Aeson as Aeson
|
||||||
|
import Arion.Aeson
|
||||||
|
import Control.Lens
|
||||||
|
import Data.Aeson.Lens
|
||||||
|
|
||||||
|
data Image = Image
|
||||||
|
{ image :: Maybe Text -- ^ image tar.gz file path
|
||||||
|
, imageExe :: Maybe Text -- ^ path to exe producing image tar
|
||||||
|
, imageName :: Text
|
||||||
|
, imageTag :: Text
|
||||||
|
} deriving (Eq, Show, Generic, Aeson.ToJSON, Aeson.FromJSON)
|
||||||
|
|
||||||
|
data ExtendedInfo = ExtendedInfo {
|
||||||
|
projectName :: Maybe Text,
|
||||||
|
images :: [Image]
|
||||||
|
} deriving (Eq, Show)
|
||||||
|
|
||||||
|
loadExtendedInfoFromPath :: FilePath -> IO ExtendedInfo
|
||||||
|
loadExtendedInfoFromPath fp = do
|
||||||
|
v <- decodeFile fp
|
||||||
|
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
|
||||||
|
}
|
|
@ -8,38 +8,23 @@ module Arion.Images
|
||||||
import Prelude()
|
import Prelude()
|
||||||
import Protolude hiding (to)
|
import Protolude hiding (to)
|
||||||
|
|
||||||
import qualified Data.Aeson as Aeson
|
|
||||||
import Arion.Aeson (decodeFile)
|
|
||||||
import qualified System.Process as Process
|
import qualified System.Process as Process
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
|
|
||||||
import Control.Lens
|
import Arion.ExtendedInfo (Image(..))
|
||||||
import Data.Aeson.Lens
|
|
||||||
|
|
||||||
data Image = Image
|
|
||||||
{ image :: Maybe Text -- ^ image tar.gz file path
|
|
||||||
, imageExe :: Maybe Text -- ^ path to exe producing image tar
|
|
||||||
, imageName :: Text
|
|
||||||
, imageTag :: Text
|
|
||||||
} deriving (Generic, Aeson.ToJSON, Aeson.FromJSON, Show)
|
|
||||||
|
|
||||||
type TaggedImage = Text
|
type TaggedImage = Text
|
||||||
|
|
||||||
-- | Subject to change
|
-- | Subject to change
|
||||||
loadImages :: FilePath -> IO ()
|
loadImages :: [Image] -> IO ()
|
||||||
loadImages fp = do
|
loadImages requestedImages = do
|
||||||
|
|
||||||
v <- decodeFile fp
|
|
||||||
|
|
||||||
loaded <- getDockerImages
|
loaded <- getDockerImages
|
||||||
|
|
||||||
let
|
let
|
||||||
images :: [Image]
|
|
||||||
images = (v :: Aeson.Value) ^.. key "x-arion" . key "images" . _Array . traverse . _JSON
|
|
||||||
|
|
||||||
isNew i = (imageName i <> ":" <> imageTag i) `notElem` loaded
|
isNew i = (imageName i <> ":" <> imageTag i) `notElem` loaded
|
||||||
|
|
||||||
traverse_ loadImage . filter isNew $ images
|
traverse_ loadImage . filter isNew $ requestedImages
|
||||||
|
|
||||||
loadImage :: Image -> IO ()
|
loadImage :: Image -> IO ()
|
||||||
loadImage (Image { image = Just imgPath, imageName = name }) =
|
loadImage (Image { image = Just imgPath, imageName = name }) =
|
||||||
|
|
|
@ -37,6 +37,9 @@
|
||||||
"imageTag": "<HASH>"
|
"imageTag": "<HASH>"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"project": {
|
||||||
|
"name": null
|
||||||
|
},
|
||||||
"serviceInfo": {
|
"serviceInfo": {
|
||||||
"webserver": {
|
"webserver": {
|
||||||
"defaultExec": [
|
"defaultExec": [
|
||||||
|
|
|
@ -30,6 +30,9 @@
|
||||||
"imageTag": "xr4ljmz3qfcwlq9rl4mr4qdrzw93rl70"
|
"imageTag": "xr4ljmz3qfcwlq9rl4mr4qdrzw93rl70"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"project": {
|
||||||
|
"name": null
|
||||||
|
},
|
||||||
"serviceInfo": {
|
"serviceInfo": {
|
||||||
"webserver": {
|
"webserver": {
|
||||||
"defaultExec": [
|
"defaultExec": [
|
||||||
|
|
|
@ -4,4 +4,5 @@
|
||||||
./modules/composition/images.nix
|
./modules/composition/images.nix
|
||||||
./modules/composition/service-info.nix
|
./modules/composition/service-info.nix
|
||||||
./modules/composition/arion-base-image.nix
|
./modules/composition/arion-base-image.nix
|
||||||
|
./modules/composition/composition.nix
|
||||||
]
|
]
|
24
src/nix/modules/composition/composition.nix
Normal file
24
src/nix/modules/composition/composition.nix
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{ config, lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) types mkOption;
|
||||||
|
|
||||||
|
link = url: text:
|
||||||
|
''link:${url}[${text}]'';
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
project.name = mkOption {
|
||||||
|
description = ''
|
||||||
|
Name of the project.
|
||||||
|
|
||||||
|
See ${link "https://docs.docker.com/compose/reference/envvars/#compose_project_name" "COMPOSE_PROJECT_NAME"}
|
||||||
|
'';
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
docker-compose.extended.project.name = config.project.name;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue