{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns #-}
module Main where
import Data.Maybe ( fromMaybe )
import qualified Data.Text as Text
import Data.Text ( Text )
import Language.Docker
import Prelude hiding ( FilePath )
import Text.ParserCombinators.ReadP
import Text.Read
import Turtle
import qualified Docker.Cacher
import qualified Docker.Cacher.Internal
-- | This script has 2 modes. One for building the Dockerfile, and another for caching its stages
data Mode
= Build
| Cache
deriving (Show)
instance Read Mode where
readPrec =
Text.Read.choice $
strValMap
[ ("Build", Build) -- Accept both casings
, ("build", Build)
, ("Cache", Cache)
, ("cache", Cache)
]
where
strValMap = map (\(flag, result) -> lift $ string flag >> return result)
data Args = Args
{ mode :: Mode
, noCacheStages :: Bool
, noBuildCache :: Bool
}
-- | Describes the arguments this script takes from the command line
parser :: Parser Args
parser =
Args -- Pass the parsed arguments into the Args data container
<$> argRead "mode" "Whether to build or to cache (options: build | cache)"
<*> switch
"no-cache-stages"
's'
"Each of the FROM instruction will be cached in separate images if this flag is not set"
<*> switch "no-cache-build"
'n'
"Skip the internal docker cache when building the image"
main :: IO ()
main = do
Args { mode, noCacheStages, noBuildCache } <- options -- Parse the CLI arguments as a Mode
"Builds a docker file and caches its stages"
parser
app <- Docker.Cacher.App <$> needEnv "APP_NAME" -- Get the APP environment variable and then wrap it in App
branch <- Docker.Cacher.Branch <$> needEnv "GIT_BRANCH"
fallbackBranch <- fmap Docker.Cacher.Branch <$> need "FALLBACK_BRANCH"
maybeFile <- do
file <- need "DOCKERFILE" -- Get the dockerfile path if any
return (fmap fromText file) -- And transform it to a FilePath
-- if DOCKERFILE is not present, we assume is in the current directory
currentDirectory <- pwd
let Just file = maybeFile <|> Just (currentDirectory </> "Dockerfile")
-- Now we try to parse the dockefile
dockerFile <- parseFile (Text.unpack (format fp file)) -- Convert the dockerfile to an AST
case dockerFile of
Left message ->
error ("There was an error parsing the docker file: " <> show message)
Right ast -> case mode of
Cache -> if noCacheStages
then echo "Skipping... I was told not to cache any stages separetely"
else sh (Docker.Cacher.cacheBuild app branch fallbackBranch ast)
Build -> do
name <- Docker.Cacher.Internal.ImageName <$> needEnv "DOCKER_TAG"
buildOPtions <- do
opts <- need "DOCKER_BUILD_OPTIONS"
if noBuildCache
then
return
$ Just
( opts
& fromMaybe ""
& (<> " --no-cache") -- Append the docker --no-cache option
& Docker.Cacher.BuildOptions
)
else return (fmap Docker.Cacher.BuildOptions opts)
if noCacheStages
then sh (Docker.Cacher.build app name buildOPtions ast)
else sh
(Docker.Cacher.buildFromCache app
branch
fallbackBranch
name
buildOPtions
ast
)
needEnv :: MonadIO m => Text -> m Text
needEnv varName = do
value <- need varName
case value of
Nothing -> error
("I was expecting the " <> show varName <> " env var to be present.")
Just val -> return val