Copy directory instead of cloning an entire repository (#135)

* Copy directory instead of cloning an entire repository

* Improve haddock of types

* Rename new ADT to Source

* Add tests for new configuration

* Update changelog and readme

* Addres PR comments

* Updated changelog and hapistrano's version

Co-authored-by: Juan Paucar <jpaucar@stackbuilders.com>
This commit is contained in:
Esteban Ibarra 2020-03-29 19:54:39 -05:00 committed by GitHub
parent b321391410
commit a6183c01fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 150 additions and 32 deletions

View File

@ -1,4 +1,7 @@
## master ## 0.4.0.0
### Added
* Copy a directory's contents with `local_directory` instead of using _git_ with `repo` and `revision`.
### Changed ### Changed
* Update upper bounds for `path` and `path-io` packages. * Update upper bounds for `path` and `path-io` packages.

View File

@ -32,15 +32,18 @@ filesystem and deletes previous releases to avoid filling up the disk.
## Usage ## Usage
Hapistrano 0.3.0.0 looks for a configuration file called `hap.yaml` that Hapistrano 0.4.0.0 looks for a configuration file called `hap.yaml` that
typically looks like this: typically looks like this:
```yaml ```yaml
deploy_path: '/var/projects/my-project' deploy_path: '/var/projects/my-project'
host: myserver.com host: myserver.com
port: 2222 port: 2222
# To perform version control operations
repo: 'https://github.com/stackbuilders/hapistrano.git' repo: 'https://github.com/stackbuilders/hapistrano.git'
revision: origin/master revision: origin/master
# To copy the contents of the directory
local_directory: '/tmp/my-project'
build_script: build_script:
- stack setup - stack setup
- stack build - stack build
@ -50,10 +53,16 @@ restart_command: systemd restart my-app-service
The following parameters are required: The following parameters are required:
* `deploy_path` — the root of the deploy target on the remote host. * `deploy_path` — the root of the deploy target on the remote host.
* `repo` — the origin repository. * Related to the `source` of the repository, you have the following options:
* `revision` — the SHA1 or branch to deploy. If a branch, you will need to - _Git repository_ **default** — consists of two parameters. When these are set,
hapistrano will perform version control related operations.
**Note:** Only GitHub is supported.
* `repo` — the origin repository.
* `revision` — the SHA1 or branch to deploy. If a branch, you will need to
specify it as `origin/branch_name` due to the way that the cache repo is specify it as `origin/branch_name` due to the way that the cache repo is
configured. configured.
* `local_directory` — when this parameter is set, hapistrano will copy the
contents of the directory.
The following parameters are *optional*: The following parameters are *optional*:

View File

@ -4,7 +4,6 @@
module Main (main) where module Main (main) where
import qualified Config as C
import Control.Concurrent.Async import Control.Concurrent.Async
import Control.Concurrent.STM import Control.Concurrent.STM
import Control.Monad import Control.Monad
@ -21,6 +20,7 @@ import Paths_hapistrano (version)
import System.Exit import System.Exit
import qualified System.Hapistrano as Hap import qualified System.Hapistrano as Hap
import qualified System.Hapistrano.Commands as Hap import qualified System.Hapistrano.Commands as Hap
import qualified System.Hapistrano.Config as C
import qualified System.Hapistrano.Core as Hap import qualified System.Hapistrano.Core as Hap
import System.Hapistrano.Types import System.Hapistrano.Types
import System.IO import System.IO
@ -125,8 +125,7 @@ main = do
C.Config{..} <- Yaml.loadYamlSettings [optsConfigFile] [] Yaml.useEnv C.Config{..} <- Yaml.loadYamlSettings [optsConfigFile] [] Yaml.useEnv
chan <- newTChanIO chan <- newTChanIO
let task rf = Task { taskDeployPath = configDeployPath let task rf = Task { taskDeployPath = configDeployPath
, taskRepository = configRepo , taskSource = configSource
, taskRevision = configRevision
, taskReleaseFormat = rf } , taskReleaseFormat = rf }
let printFnc dest str = atomically $ let printFnc dest str = atomically $
writeTChan chan (PrintMsg dest str) writeTChan chan (PrintMsg dest str)
@ -141,6 +140,8 @@ main = do
then Hap.pushRelease (task releaseFormat) then Hap.pushRelease (task releaseFormat)
else Hap.pushReleaseWithoutVc (task releaseFormat) else Hap.pushReleaseWithoutVc (task releaseFormat)
rpath <- Hap.releasePath configDeployPath release rpath <- Hap.releasePath configDeployPath release
forM_ (toMaybePath configSource) $ \src ->
Hap.scpDir src rpath
forM_ configCopyFiles $ \(C.CopyThing src dest) -> do forM_ configCopyFiles $ \(C.CopyThing src dest) -> do
srcPath <- resolveFile' src srcPath <- resolveFile' src
destPath <- parseRelFile dest destPath <- parseRelFile dest

View File

@ -0,0 +1,4 @@
deploy_path: '/'
host: www.example.com
repo: 'my-repo'
revision: 'my-revision'

View File

@ -0,0 +1,3 @@
deploy_path: '/'
host: www.example.com
local_directory: '/'

View File

@ -1,5 +1,5 @@
name: hapistrano name: hapistrano
version: 0.3.10.1 version: 0.4.0.0
synopsis: A deployment library for Haskell applications synopsis: A deployment library for Haskell applications
description: description:
. .
@ -48,6 +48,7 @@ library
hs-source-dirs: src hs-source-dirs: src
exposed-modules: System.Hapistrano exposed-modules: System.Hapistrano
, System.Hapistrano.Commands , System.Hapistrano.Commands
, System.Hapistrano.Config
, System.Hapistrano.Core , System.Hapistrano.Core
, System.Hapistrano.Types , System.Hapistrano.Types
, System.Hapistrano.Commands.Internal , System.Hapistrano.Commands.Internal
@ -64,6 +65,7 @@ library
, typed-process >= 0.2 && < 0.3 , typed-process >= 0.2 && < 0.3
, time >= 1.5 && < 1.10 , time >= 1.5 && < 1.10
, transformers >= 0.4 && < 0.6 , transformers >= 0.4 && < 0.6
, yaml >= 0.8.16 && < 0.12
if flag(dev) if flag(dev)
ghc-options: -Wall -Werror ghc-options: -Wall -Werror
else else
@ -73,8 +75,7 @@ library
executable hap executable hap
hs-source-dirs: app hs-source-dirs: app
main-is: Main.hs main-is: Main.hs
other-modules: Config other-modules: Paths_hapistrano
, Paths_hapistrano
build-depends: aeson >= 0.11 && < 1.5 build-depends: aeson >= 0.11 && < 1.5
, async >= 2.0.1.6 && < 2.4 , async >= 2.0.1.6 && < 2.4
, base >= 4.8 && < 5.0 , base >= 4.8 && < 5.0
@ -100,6 +101,7 @@ test-suite test
hs-source-dirs: spec hs-source-dirs: spec
main-is: Spec.hs main-is: Spec.hs
other-modules: System.HapistranoSpec other-modules: System.HapistranoSpec
, System.HapistranoConfigSpec
, System.HapistranoPropsSpec , System.HapistranoPropsSpec
build-depends: base >= 4.8 && < 5.0 build-depends: base >= 4.8 && < 5.0
, directory >= 1.2.5 && < 1.4 , directory >= 1.2.5 && < 1.4
@ -113,6 +115,7 @@ test-suite test
, QuickCheck >= 2.5.1 && < 3.0 , QuickCheck >= 2.5.1 && < 3.0
, silently >= 1.2 && < 1.3 , silently >= 1.2 && < 1.3
, temporary >= 1.1 && < 1.4 , temporary >= 1.1 && < 1.4
, yaml >= 0.8.16 && < 0.12
build-tools: hspec-discover >= 2.0 && < 3.0 build-tools: hspec-discover >= 2.0 && < 3.0
if flag(dev) if flag(dev)

View File

@ -0,0 +1,59 @@
{-# LANGUAGE TemplateHaskell #-}
module System.HapistranoConfigSpec
( spec
) where
import System.Hapistrano.Config (Config (..), Target (..))
import System.Hapistrano.Types (Shell (..),
Source (..), TargetSystem (..))
import qualified Data.Yaml.Config as Yaml
import Path (mkAbsDir)
import Test.Hspec
spec :: Spec
spec =
describe "Hapistrano's configuration file" $ do
context "when the key 'local-repository' is present" $
it "loads LocalRepository as the configuration's source" $
Yaml.loadYamlSettings ["fixtures/local_directory_config.yaml"] [] Yaml.useEnv
>>=
(`shouldBe`
(defaultConfiguration
{ configSource = LocalDirectory { localDirectoryPath = $(mkAbsDir "/") } }
)
)
context "when the keys 'repo' and 'revision' are present" $
it "loads GitRepository as the configuration's source" $
Yaml.loadYamlSettings ["fixtures/git_repository_config.yaml"] [] Yaml.useEnv
>>= (`shouldBe` defaultConfiguration)
defaultConfiguration :: Config
defaultConfiguration =
Config
{ configDeployPath = $(mkAbsDir "/")
, configHosts =
[ Target
{ targetHost = "www.example.com"
, targetPort = 22
, targetShell = Bash
, targetSshArgs = []
}
]
, configSource = GitRepository "my-repo" "my-revision"
, configRestartCommand = Nothing
, configBuildScript = Nothing
, configCopyFiles = []
, configCopyDirs = []
, configLinkedFiles = []
, configLinkedDirs = []
, configVcAction = True
, configRunLocally = Nothing
, configTargetSystem = GNULinux
, configReleaseFormat = Nothing
, configKeepReleases = Nothing
}

View File

@ -361,8 +361,11 @@ mkTaskWithCustomRevision :: Path Abs Dir -> Path Abs Dir -> String -> Task
mkTaskWithCustomRevision deployPath repoPath revision = mkTaskWithCustomRevision deployPath repoPath revision =
Task Task
{ taskDeployPath = deployPath { taskDeployPath = deployPath
, taskRepository = fromAbsDir repoPath , taskSource =
, taskRevision = revision GitRepository
{ gitRepositoryURL = fromAbsDir repoPath
, gitRepositoryRevision = revision
}
, taskReleaseFormat = ReleaseLong , taskReleaseFormat = ReleaseLong
} }

View File

@ -55,11 +55,18 @@ import System.Hapistrano.Types
pushRelease :: Task -> Hapistrano Release pushRelease :: Task -> Hapistrano Release
pushRelease Task {..} = do pushRelease Task {..} = do
setupDirs taskDeployPath setupDirs taskDeployPath
ensureCacheInPlace taskRepository taskDeployPath pushReleaseForRepository taskSource
where
-- When the configuration is set for a local directory, it will only create
-- the release directory without any version control operations.
pushReleaseForRepository GitRepository {..} = do
ensureCacheInPlace gitRepositoryURL taskDeployPath
release <- newRelease taskReleaseFormat release <- newRelease taskReleaseFormat
cloneToRelease taskDeployPath release cloneToRelease taskDeployPath release
setReleaseRevision taskDeployPath release taskRevision setReleaseRevision taskDeployPath release gitRepositoryRevision
return release return release
pushReleaseForRepository LocalDirectory {..} =
newRelease taskReleaseFormat
-- | Same as 'pushRelease' but doesn't perform any version control -- | Same as 'pushRelease' but doesn't perform any version control
-- related operations. -- related operations.

View File

@ -4,12 +4,13 @@
{-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-orphans #-}
module Config module System.Hapistrano.Config
( Config (..) ( Config (..)
, CopyThing (..) , CopyThing (..)
, Target (..)) , Target (..))
where where
import Control.Applicative ((<|>))
import Data.Aeson import Data.Aeson
import Data.Function (on) import Data.Function (on)
import Data.List (nubBy) import Data.List (nubBy)
@ -20,6 +21,7 @@ import Path
import System.Hapistrano.Commands import System.Hapistrano.Commands
import System.Hapistrano.Types (Shell(..), import System.Hapistrano.Types (Shell(..),
ReleaseFormat (..), ReleaseFormat (..),
Source(..),
TargetSystem (..)) TargetSystem (..))
-- | Hapistrano configuration typically loaded from @hap.yaml@ file. -- | Hapistrano configuration typically loaded from @hap.yaml@ file.
@ -29,10 +31,8 @@ data Config = Config
-- ^ Top-level deploy directory on target machine -- ^ Top-level deploy directory on target machine
, configHosts :: ![Target] , configHosts :: ![Target]
-- ^ Hosts\/ports\/shell\/ssh args to deploy to. If empty, localhost will be assumed. -- ^ Hosts\/ports\/shell\/ssh args to deploy to. If empty, localhost will be assumed.
, configRepo :: !String , configSource :: !Source
-- ^ Location of repository that contains the source code to deploy -- ^ Location of the 'Source' that contains the code to deploy
, configRevision :: !String
-- ^ Revision to use
, configRestartCommand :: !(Maybe GenericCommand) , configRestartCommand :: !(Maybe GenericCommand)
-- ^ The command to execute when switching to a different release -- ^ The command to execute when switching to a different release
-- (usually after a deploy or rollback). -- (usually after a deploy or rollback).
@ -97,8 +97,10 @@ instance FromJSON Config where
let first Target{..} = host let first Target{..} = host
configHosts = nubBy ((==) `on` first) configHosts = nubBy ((==) `on` first)
(maybeToList (Target <$> host <*> pure port <*> pure shell <*> pure sshArgs) ++ hs) (maybeToList (Target <$> host <*> pure port <*> pure shell <*> pure sshArgs) ++ hs)
configRepo <- o .: "repo" source m =
configRevision <- o .: "revision" GitRepository <$> m .: "repo" <*> m .: "revision"
<|> LocalDirectory <$> m .: "local_directory"
configSource <- source o
configRestartCommand <- (o .:? "restart_command") >>= configRestartCommand <- (o .:? "restart_command") >>=
maybe (return Nothing) (fmap Just . mkCmd) maybe (return Nothing) (fmap Just . mkCmd)
configBuildScript <- o .:? "build_script" >>= configBuildScript <- o .:? "build_script" >>=

View File

@ -15,6 +15,7 @@ module System.Hapistrano.Types
( Hapistrano ( Hapistrano
, Failure(..) , Failure(..)
, Config(..) , Config(..)
, Source(..)
, Task(..) , Task(..)
, ReleaseFormat(..) , ReleaseFormat(..)
, SshOptions(..) , SshOptions(..)
@ -22,12 +23,14 @@ module System.Hapistrano.Types
, Release , Release
, TargetSystem(..) , TargetSystem(..)
, Shell(..) , Shell(..)
-- * Types helpers
, mkRelease , mkRelease
, releaseTime , releaseTime
, renderRelease , renderRelease
, parseRelease , parseRelease
, fromMaybeReleaseFormat , fromMaybeReleaseFormat
, fromMaybeKeepReleases , fromMaybeKeepReleases
, toMaybePath
) where ) where
import Control.Applicative import Control.Applicative
@ -57,15 +60,28 @@ data Config =
-- ^ How to print messages -- ^ How to print messages
} }
-- | The source of the repository. It can be from a version control provider
-- like GitHub or a local directory.
data Source
= GitRepository
{ gitRepositoryURL :: String
-- ^ The URL of remote Git repository to deploy
, gitRepositoryRevision :: String
-- ^ The SHA1 or branch to release
}
| LocalDirectory
{ localDirectoryPath :: Path Abs Dir
-- ^ The local repository to deploy
}
deriving (Eq, Ord, Show)
-- | The records describes deployment task. -- | The records describes deployment task.
data Task = data Task =
Task Task
{ taskDeployPath :: Path Abs Dir { taskDeployPath :: Path Abs Dir
-- ^ The root of the deploy target on the remote host -- ^ The root of the deploy target on the remote host
, taskRepository :: String , taskSource :: Source
-- ^ The URL of remote Git repo to deploy -- ^ The 'Source' to deploy
, taskRevision :: String
-- ^ A SHA1 or branch to release
, taskReleaseFormat :: ReleaseFormat , taskReleaseFormat :: ReleaseFormat
-- ^ The 'ReleaseFormat' to use -- ^ The 'ReleaseFormat' to use
} }
@ -140,6 +156,9 @@ renderRelease (Release rfmt time) = formatTime defaultTimeLocale fmt time
ReleaseShort -> releaseFormatShort ReleaseShort -> releaseFormatShort
ReleaseLong -> releaseFormatLong ReleaseLong -> releaseFormatLong
----------------------------------------------------------------------------
-- Types helpers
-- | Parse 'Release' identifier from a 'String'. -- | Parse 'Release' identifier from a 'String'.
parseRelease :: String -> Maybe Release parseRelease :: String -> Maybe Release
parseRelease s = parseRelease s =
@ -166,3 +185,8 @@ fromMaybeKeepReleases cliKR configKR =
defaultKeepReleases :: Natural defaultKeepReleases :: Natural
defaultKeepReleases = 5 defaultKeepReleases = 5
-- | Get the local path to copy from the 'Source' configuration value.
toMaybePath :: Source -> Maybe (Path Abs Dir)
toMaybePath (LocalDirectory path) = Just path
toMaybePath _ = Nothing