Merge pull request #104 from hercules-ci/set-project-name

Add name option for project/composition name
This commit is contained in:
Robert Hensing 2020-10-11 12:55:11 +02:00 committed by GitHub
commit 896316ce29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 143 additions and 35 deletions

View file

@ -49,6 +49,7 @@ library
exposed-modules: Arion.Nix
Arion.Aeson
Arion.DockerCompose
Arion.ExtendedInfo
Arion.Images
Arion.Services
other-modules: Paths_arion_compose

View file

@ -106,6 +106,26 @@ No Default:: {blank}
Read Only:: {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
An attribute set of service configurations. A service specifies how to run an image as a container.

View file

@ -1,5 +1,6 @@
{ pkgs, ... }:
{
config.project.name = "webapp";
config.services = {
webserver = {

View file

@ -10,6 +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 Options.Applicative
import Control.Monad.Fail
@ -27,6 +28,9 @@ data CommonOptions =
, pkgs :: Text
, nixArgs :: [Text]
, prebuiltComposeFile :: Maybe FilePath
, noAnsi :: Bool
, compatibility :: Bool
, logLevel :: Maybe Text
}
deriving (Show)
@ -63,6 +67,11 @@ parseOptions = do
( long "prebuilt-file"
<> metavar "JSONFILE"
<> 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 $
let nixArgs = userNixArgs <|> "--show-trace" <$ guard showTrace
in CommonOptions{..}
@ -142,21 +151,39 @@ runDC cmd (DockerComposeArgs args) _opts = do
runBuildAndDC :: Text -> DockerComposeArgs -> CommonOptions -> IO ()
runBuildAndDC cmd dopts opts = do
withBuiltComposeFile opts $ \path -> do
loadImages path
DockerCompose.run DockerCompose.Args
{ files = [path]
, otherArgs = [cmd] ++ unDockerComposeArgs dopts
}
withBuiltComposeFile opts $ callDC cmd dopts opts True
runEvalAndDC :: Text -> DockerComposeArgs -> CommonOptions -> IO ()
runEvalAndDC cmd dopts opts = do
withComposeFile opts $ \path ->
withComposeFile opts $ callDC cmd dopts opts False
callDC :: Text -> DockerComposeArgs -> CommonOptions -> Bool -> FilePath -> IO ()
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 = [cmd] ++ unDockerComposeArgs dopts
, 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 opts cont = case prebuiltComposeFile opts of
Just prebuilt -> do
@ -255,12 +282,18 @@ orEmpty' :: (Alternative f, Monoid a) => f a -> f a
orEmpty' m = fromMaybe mempty <$> optional m
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
putErrText $ "Service: " <> service
runExec detach privileged user noTTY index envs workDir service commandAndArgs opts =
withComposeFile opts $ \path -> do
extendedInfo <- loadExtendedInfoFromPath path
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
let commandAndArgs' = case commandAndArgs'' of
[] -> ["/bin/sh"]
@ -280,7 +313,7 @@ runExec detach privileged user noTTY index envs workDir service commandAndArgs o
]
DockerCompose.run DockerCompose.Args
{ files = [path]
, otherArgs = args
, otherArgs = projectArgs extendedInfo <> commonArgs opts <> args
}
main :: IO ()

View 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
}

View file

@ -8,38 +8,23 @@ module Arion.Images
import Prelude()
import Protolude hiding (to)
import qualified Data.Aeson as Aeson
import Arion.Aeson (decodeFile)
import qualified System.Process as Process
import qualified Data.Text as T
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 (Generic, Aeson.ToJSON, Aeson.FromJSON, Show)
import Arion.ExtendedInfo (Image(..))
type TaggedImage = Text
-- | Subject to change
loadImages :: FilePath -> IO ()
loadImages fp = do
v <- decodeFile fp
loadImages :: [Image] -> IO ()
loadImages requestedImages = do
loaded <- getDockerImages
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 . filter isNew $ images
traverse_ loadImage . filter isNew $ requestedImages
loadImage :: Image -> IO ()
loadImage (Image { image = Just imgPath, imageName = name }) =

View file

@ -37,6 +37,9 @@
"imageTag": "<HASH>"
}
],
"project": {
"name": null
},
"serviceInfo": {
"webserver": {
"defaultExec": [

View file

@ -30,6 +30,9 @@
"imageTag": "xr4ljmz3qfcwlq9rl4mr4qdrzw93rl70"
}
],
"project": {
"name": null
},
"serviceInfo": {
"webserver": {
"defaultExec": [

View file

@ -4,4 +4,5 @@
./modules/composition/images.nix
./modules/composition/service-info.nix
./modules/composition/arion-base-image.nix
./modules/composition/composition.nix
]

View 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;
};
}