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

View file

@ -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.

View file

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

View file

@ -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,21 +151,39 @@ 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
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 DockerCompose.run DockerCompose.Args
{ files = [path] { 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 :: CommonOptions -> (FilePath -> IO r) -> IO r
withBuiltComposeFile opts cont = case prebuiltComposeFile opts of withBuiltComposeFile opts cont = case prebuiltComposeFile opts of
Just prebuilt -> do Just prebuilt -> do
@ -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 ()

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 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 }) =

View file

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

View file

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

View file

@ -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
] ]

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