{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
module System.Hapistrano
( pushRelease
, pushReleaseWithoutVc
, registerReleaseAsComplete
, activateRelease
, linkToShared
, rollback
, dropOldReleases
, playScript
, playScriptLocally
, releasePath
, sharedPath
, currentSymlinkPath
, tempSymlinkPath
, ctokenPath )
where
import Control.Monad
import Control.Monad.Except
import Control.Monad.Reader (local)
import Data.List (dropWhileEnd, genericDrop, sortOn)
import Data.Maybe (mapMaybe)
import Data.Ord (Down (..))
import Data.Time
import Numeric.Natural
import Path
import System.Hapistrano.Commands
import System.Hapistrano.Core
import System.Hapistrano.Types
pushRelease :: Task -> Hapistrano Release
pushRelease Task {..} = do
setupDirs taskDeployPath
pushReleaseForRepository taskSource
where
pushReleaseForRepository GitRepository {..} = do
ensureCacheInPlace gitRepositoryURL taskDeployPath
release <- newRelease taskReleaseFormat
cloneToRelease taskDeployPath release
setReleaseRevision taskDeployPath release gitRepositoryRevision
return release
pushReleaseForRepository LocalDirectory {..} =
newRelease taskReleaseFormat
pushReleaseWithoutVc :: Task -> Hapistrano Release
pushReleaseWithoutVc Task {..} = do
setupDirs taskDeployPath
newRelease taskReleaseFormat
registerReleaseAsComplete
:: Path Abs Dir
-> Release
-> Hapistrano ()
registerReleaseAsComplete deployPath release = do
cpath <- ctokenPath deployPath release
exec (Touch cpath)
activateRelease
:: TargetSystem
-> Path Abs Dir
-> Release
-> Hapistrano ()
activateRelease ts deployPath release = do
rpath <- releasePath deployPath release
let tpath = tempSymlinkPath deployPath
cpath = currentSymlinkPath deployPath
exec (Ln ts rpath tpath)
exec (Mv ts tpath cpath)
rollback
:: TargetSystem
-> Path Abs Dir
-> Natural
-> Hapistrano ()
rollback ts deployPath n = do
crs <- completedReleases deployPath
drs <- deployedReleases deployPath
case genericDrop n (if null crs then drs else crs) of
[] -> failWith 1 (Just "Could not find the requested release to rollback to.")
(x:_) -> activateRelease ts deployPath x
dropOldReleases
:: Path Abs Dir
-> Natural
-> Hapistrano ()
dropOldReleases deployPath n = do
dreleases <- deployedReleases deployPath
forM_ (genericDrop n dreleases) $ \release -> do
rpath <- releasePath deployPath release
exec (Rm rpath)
creleases <- completedReleases deployPath
forM_ (genericDrop n creleases) $ \release -> do
cpath <- ctokenPath deployPath release
exec (Rm cpath)
playScript
:: Path Abs Dir
-> Release
-> [GenericCommand]
-> Hapistrano ()
playScript deployDir release cmds = do
rpath <- releasePath deployDir release
forM_ cmds (execWithInheritStdout . Cd rpath)
playScriptLocally :: [GenericCommand] -> Hapistrano ()
playScriptLocally cmds =
local
(\c ->
c
{ configSshOptions = Nothing
}) $
forM_ cmds execWithInheritStdout
setupDirs
:: Path Abs Dir
-> Hapistrano ()
setupDirs deployPath = do
(exec . MkDir . releasesPath) deployPath
(exec . MkDir . cacheRepoPath) deployPath
(exec . MkDir . ctokensPath) deployPath
(exec . MkDir . sharedPath) deployPath
ensureCacheInPlace
:: String
-> Path Abs Dir
-> Hapistrano ()
ensureCacheInPlace repo deployPath = do
let cpath = cacheRepoPath deployPath
refs = cpath </> $(mkRelDir "refs")
exists <- (exec (Ls refs) >> return True)
`catchError` const (return False)
unless exists $
exec (GitClone True (Left repo) cpath)
exec (Cd cpath (GitFetch "origin"))
newRelease :: ReleaseFormat -> Hapistrano Release
newRelease releaseFormat =
mkRelease releaseFormat <$> liftIO getCurrentTime
cloneToRelease
:: Path Abs Dir
-> Release
-> Hapistrano ()
cloneToRelease deployPath release = do
rpath <- releasePath deployPath release
let cpath = cacheRepoPath deployPath
exec (GitClone False (Right cpath) rpath)
setReleaseRevision
:: Path Abs Dir
-> Release
-> String
-> Hapistrano ()
setReleaseRevision deployPath release revision = do
rpath <- releasePath deployPath release
exec (Cd rpath (GitCheckout revision))
deployedReleases
:: Path Abs Dir
-> Hapistrano [Release]
deployedReleases deployPath = do
let rpath = releasesPath deployPath
xs <- exec (Find 1 rpath :: Find Dir)
ps <- stripDirs rpath (filter (/= rpath) xs)
(return . sortOn Down . mapMaybe parseRelease)
(dropWhileEnd (== '/') . fromRelDir <$> ps)
completedReleases
:: Path Abs Dir
-> Hapistrano [Release]
completedReleases deployPath = do
let cpath = ctokensPath deployPath
xs <- exec (Find 1 cpath :: Find File)
ps <- stripDirs cpath xs
(return . sortOn Down . mapMaybe parseRelease)
(dropWhileEnd (== '/') . fromRelFile <$> ps)
releasesPath
:: Path Abs Dir
-> Path Abs Dir
releasesPath deployPath = deployPath </> $(mkRelDir "releases")
sharedPath
:: Path Abs Dir
-> Path Abs Dir
sharedPath deployPath = deployPath </> $(mkRelDir "shared")
linkToShared
:: TargetSystem
-> Path Abs Dir
-> Path Abs Dir
-> FilePath
-> Hapistrano ()
linkToShared configTargetSystem rpath configDeployPath thingToLink = do
destPath <- parseRelFile thingToLink
let dpath = rpath </> destPath
sharedPath' = sharedPath configDeployPath </> destPath
exec $ Ln configTargetSystem sharedPath' dpath
releasePath
:: Path Abs Dir
-> Release
-> Hapistrano (Path Abs Dir)
releasePath deployPath release = do
let rendered = renderRelease release
case parseRelDir rendered of
Nothing -> failWith 1 (Just $ "Could not append path: " ++ rendered)
Just rpath -> return (releasesPath deployPath </> rpath)
cacheRepoPath
:: Path Abs Dir
-> Path Abs Dir
cacheRepoPath deployPath = deployPath </> $(mkRelDir "repo")
currentSymlinkPath
:: Path Abs Dir
-> Path Abs File
currentSymlinkPath deployPath = deployPath </> $(mkRelFile "current")
tempSymlinkPath
:: Path Abs Dir
-> Path Abs File
tempSymlinkPath deployPath = deployPath </> $(mkRelFile "current_tmp")
ctokensPath
:: Path Abs Dir
-> Path Abs Dir
ctokensPath deployPath = deployPath </> $(mkRelDir "ctokens")
ctokenPath
:: Path Abs Dir
-> Release
-> Hapistrano (Path Abs File)
ctokenPath deployPath release = do
let rendered = renderRelease release
case parseRelFile rendered of
Nothing -> failWith 1 (Just $ "Could not append path: " ++ rendered)
Just rpath -> return (ctokensPath deployPath </> rpath)
stripDirs :: Path Abs Dir -> [Path Abs t] -> Hapistrano [Path Rel t]
stripDirs path =
#if MIN_VERSION_path(0,6,0)
mapM (stripProperPrefix path)
#else
mapM (stripDir path)
#endif