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,
specify it as `origin/branch_name` due to the way that the cache repo is hapistrano will perform version control related operations.
configured. **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
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*:
@ -204,8 +213,8 @@ available on [Docker Hub](https://hub.docker.com/r/stackbuilders/hapistrano/).
If you want to use Nix for building Hapistrano, the required release.nix and default.nix are available. If you want to use Nix for building Hapistrano, the required release.nix and default.nix are available.
For installing the hap binary in your local path: For installing the hap binary in your local path:
```bash ```bash
nix-env -i hapistrano -f release.nix nix-env -i hapistrano -f release.nix
``` ```
For developing Hapistrano with Nix, you can create a development environment using: For developing Hapistrano with Nix, you can create a development environment using:

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
release <- newRelease taskReleaseFormat where
cloneToRelease taskDeployPath release -- When the configuration is set for a local directory, it will only create
setReleaseRevision taskDeployPath release taskRevision -- the release directory without any version control operations.
return release pushReleaseForRepository GitRepository {..} = do
ensureCacheInPlace gitRepositoryURL taskDeployPath
release <- newRelease taskReleaseFormat
cloneToRelease taskDeployPath release
setReleaseRevision taskDeployPath release gitRepositoryRevision
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