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
* 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
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:
```yaml
deploy_path: '/var/projects/my-project'
host: myserver.com
port: 2222
# To perform version control operations
repo: 'https://github.com/stackbuilders/hapistrano.git'
revision: origin/master
# To copy the contents of the directory
local_directory: '/tmp/my-project'
build_script:
- stack setup
- stack build
@ -50,10 +53,16 @@ restart_command: systemd restart my-app-service
The following parameters are required:
* `deploy_path` — the root of the deploy target on the remote host.
* `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.
* Related to the `source` of the repository, you have the following options:
- _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
configured.
* `local_directory` — when this parameter is set, hapistrano will copy the
contents of the directory.
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.
For installing the hap binary in your local path:
```bash
For installing the hap binary in your local path:
```bash
nix-env -i hapistrano -f release.nix
```
For developing Hapistrano with Nix, you can create a development environment using:

View File

@ -4,7 +4,6 @@
module Main (main) where
import qualified Config as C
import Control.Concurrent.Async
import Control.Concurrent.STM
import Control.Monad
@ -21,6 +20,7 @@ import Paths_hapistrano (version)
import System.Exit
import qualified System.Hapistrano as Hap
import qualified System.Hapistrano.Commands as Hap
import qualified System.Hapistrano.Config as C
import qualified System.Hapistrano.Core as Hap
import System.Hapistrano.Types
import System.IO
@ -125,8 +125,7 @@ main = do
C.Config{..} <- Yaml.loadYamlSettings [optsConfigFile] [] Yaml.useEnv
chan <- newTChanIO
let task rf = Task { taskDeployPath = configDeployPath
, taskRepository = configRepo
, taskRevision = configRevision
, taskSource = configSource
, taskReleaseFormat = rf }
let printFnc dest str = atomically $
writeTChan chan (PrintMsg dest str)
@ -141,6 +140,8 @@ main = do
then Hap.pushRelease (task releaseFormat)
else Hap.pushReleaseWithoutVc (task releaseFormat)
rpath <- Hap.releasePath configDeployPath release
forM_ (toMaybePath configSource) $ \src ->
Hap.scpDir src rpath
forM_ configCopyFiles $ \(C.CopyThing src dest) -> do
srcPath <- resolveFile' src
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
version: 0.3.10.1
version: 0.4.0.0
synopsis: A deployment library for Haskell applications
description:
.
@ -48,6 +48,7 @@ library
hs-source-dirs: src
exposed-modules: System.Hapistrano
, System.Hapistrano.Commands
, System.Hapistrano.Config
, System.Hapistrano.Core
, System.Hapistrano.Types
, System.Hapistrano.Commands.Internal
@ -64,6 +65,7 @@ library
, typed-process >= 0.2 && < 0.3
, time >= 1.5 && < 1.10
, transformers >= 0.4 && < 0.6
, yaml >= 0.8.16 && < 0.12
if flag(dev)
ghc-options: -Wall -Werror
else
@ -73,8 +75,7 @@ library
executable hap
hs-source-dirs: app
main-is: Main.hs
other-modules: Config
, Paths_hapistrano
other-modules: Paths_hapistrano
build-depends: aeson >= 0.11 && < 1.5
, async >= 2.0.1.6 && < 2.4
, base >= 4.8 && < 5.0
@ -100,6 +101,7 @@ test-suite test
hs-source-dirs: spec
main-is: Spec.hs
other-modules: System.HapistranoSpec
, System.HapistranoConfigSpec
, System.HapistranoPropsSpec
build-depends: base >= 4.8 && < 5.0
, directory >= 1.2.5 && < 1.4
@ -113,6 +115,7 @@ test-suite test
, QuickCheck >= 2.5.1 && < 3.0
, silently >= 1.2 && < 1.3
, temporary >= 1.1 && < 1.4
, yaml >= 0.8.16 && < 0.12
build-tools: hspec-discover >= 2.0 && < 3.0
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 =
Task
{ taskDeployPath = deployPath
, taskRepository = fromAbsDir repoPath
, taskRevision = revision
, taskSource =
GitRepository
{ gitRepositoryURL = fromAbsDir repoPath
, gitRepositoryRevision = revision
}
, taskReleaseFormat = ReleaseLong
}

View File

@ -55,11 +55,18 @@ import System.Hapistrano.Types
pushRelease :: Task -> Hapistrano Release
pushRelease Task {..} = do
setupDirs taskDeployPath
ensureCacheInPlace taskRepository taskDeployPath
release <- newRelease taskReleaseFormat
cloneToRelease taskDeployPath release
setReleaseRevision taskDeployPath release taskRevision
return release
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
cloneToRelease taskDeployPath release
setReleaseRevision taskDeployPath release gitRepositoryRevision
return release
pushReleaseForRepository LocalDirectory {..} =
newRelease taskReleaseFormat
-- | Same as 'pushRelease' but doesn't perform any version control
-- related operations.

View File

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

View File

@ -15,6 +15,7 @@ module System.Hapistrano.Types
( Hapistrano
, Failure(..)
, Config(..)
, Source(..)
, Task(..)
, ReleaseFormat(..)
, SshOptions(..)
@ -22,12 +23,14 @@ module System.Hapistrano.Types
, Release
, TargetSystem(..)
, Shell(..)
-- * Types helpers
, mkRelease
, releaseTime
, renderRelease
, parseRelease
, fromMaybeReleaseFormat
, fromMaybeKeepReleases
, toMaybePath
) where
import Control.Applicative
@ -57,15 +60,28 @@ data Config =
-- ^ 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.
data Task =
Task
{ taskDeployPath :: Path Abs Dir
-- ^ The root of the deploy target on the remote host
, taskRepository :: String
-- ^ The URL of remote Git repo to deploy
, taskRevision :: String
-- ^ A SHA1 or branch to release
, taskSource :: Source
-- ^ The 'Source' to deploy
, taskReleaseFormat :: ReleaseFormat
-- ^ The 'ReleaseFormat' to use
}
@ -140,6 +156,9 @@ renderRelease (Release rfmt time) = formatTime defaultTimeLocale fmt time
ReleaseShort -> releaseFormatShort
ReleaseLong -> releaseFormatLong
----------------------------------------------------------------------------
-- Types helpers
-- | Parse 'Release' identifier from a 'String'.
parseRelease :: String -> Maybe Release
parseRelease s =
@ -166,3 +185,8 @@ fromMaybeKeepReleases cliKR configKR =
defaultKeepReleases :: Natural
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