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