giddyup-deploy 0.1.0.1.g3db36a2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +319 -0
- data/bin/git-deploy +5 -0
- data/lib/giddyup/command_wrapper.rb +104 -0
- data/lib/giddyup/git_deploy.rb +202 -0
- metadata +197 -0
data/README.md
ADDED
@@ -0,0 +1,319 @@
|
|
1
|
+
# GIDDYUP!
|
2
|
+
|
3
|
+
Does deploying your application seem like a bit too much of a chore? Do you
|
4
|
+
wish it wasn't so hard? Well, with giddyup it can become just a tiny bit
|
5
|
+
easier.
|
6
|
+
|
7
|
+
If you've ever used, or seen, Heroku's git-based deployment model, you know
|
8
|
+
how simple app deployment *can* be. While we don't try to emulate all of
|
9
|
+
Heroku's excellent infrastructure, giddyup does handle a small corner of
|
10
|
+
that -- the ability to deploy your app with a simple 'git push'.
|
11
|
+
|
12
|
+
|
13
|
+
# Installation
|
14
|
+
|
15
|
+
At this stage, giddyup is only available directly as a git repo. There are
|
16
|
+
no versioned releases, nor are there distribution packages available. My
|
17
|
+
recommendation is to create a clone of the [main giddyup
|
18
|
+
repo](https://github.com/mpalmer/giddyup) somewhere (I suggest
|
19
|
+
`/usr/local/lib/giddyup` if you've got root, and probably `~/giddyup`
|
20
|
+
otherwise) and then to update to newer giddyup functionality, just `git
|
21
|
+
pull` in that clone. You then have a fixed location to point your hook
|
22
|
+
symlinks and inclusions of `functions.sh`.
|
23
|
+
|
24
|
+
Eventually, when giddyup stabilises there will be distribution packages;
|
25
|
+
they'll likely put everything into `/usr/lib/giddyup`, at which time you can
|
26
|
+
just delete your git repo at `/usr/local/lib/giddyup` and symlink that
|
27
|
+
location `/usr/lib/giddyup`, and nothing else will need to change.
|
28
|
+
|
29
|
+
Symlink the `giddyup` script to somewhere in your `PATH`; this will allow
|
30
|
+
you to trivially setup new deployment destinations. Do **not** *copy* the
|
31
|
+
`giddyup` script; it relies on being in the same directory as `update-hook`
|
32
|
+
in order to be able to find the hook script.
|
33
|
+
|
34
|
+
|
35
|
+
# Setting up new deployments
|
36
|
+
|
37
|
+
1. Run the `giddyup` script, passing it the location of the "root" of your
|
38
|
+
new deployment. The script will create the basic directory structure and
|
39
|
+
symlink the update hook into the right place.
|
40
|
+
|
41
|
+
2. Set the git config variables in the config for the repo you
|
42
|
+
created in step 1 (see the section "Configuration" for the
|
43
|
+
available variables)
|
44
|
+
|
45
|
+
> e.g. `git config -f /home/appuser/appname/repo/config giddyup.environment
|
46
|
+
> production`
|
47
|
+
|
48
|
+
3. Add the necessary hooks to your application's local git repo to effect
|
49
|
+
proper deployment.
|
50
|
+
|
51
|
+
4. Add the newly created git repo as a remote in your local working copy,
|
52
|
+
then push to that remote to make your initial deploy:
|
53
|
+
|
54
|
+
> e.g. `git remote add deploy appuser@example.com:appname/repo;
|
55
|
+
> git push deploy master:master`
|
56
|
+
|
57
|
+
5. Configure your webserver to pass requests to the appserver, and test that
|
58
|
+
everything is working properly.
|
59
|
+
|
60
|
+
|
61
|
+
# Deployment tree structure
|
62
|
+
|
63
|
+
Giddyup creates a tree of directories underneath the specified "root"
|
64
|
+
directory, that contains (hopefully) everything related to your application.
|
65
|
+
The structure is as follows:
|
66
|
+
|
67
|
+
* `repo`: This is the repository to which git pushes are made, and within
|
68
|
+
which the Giddyup hook script is placed. It must be a "bare" git repo
|
69
|
+
(because otherwise Git gets ridiculously confused and annoyed) and it is
|
70
|
+
the parent of this directory which is considered to be the "root" of the
|
71
|
+
entire application. (Note: this doesn't *actually* have to be named
|
72
|
+
`repo`; but it's best to keep a consistent convention, for the sake of
|
73
|
+
sanity).
|
74
|
+
|
75
|
+
* `shared`: All of your application's data which should persist between
|
76
|
+
releases should be kept in here -- log files (if you want to keep the same
|
77
|
+
log files between releases), **definitely** your customers' uploaded assets
|
78
|
+
-- all that sort of thing goes in here, and should be symlinked from your
|
79
|
+
release. See the section "Shared data", below.
|
80
|
+
|
81
|
+
* `releases`: This is where each individual release goes. Each deployment
|
82
|
+
of your application gets a directory of it's own in here, named after the
|
83
|
+
current date/time at the time of deployment, in the format
|
84
|
+
`YYYYMMDD-HHMMSS`.
|
85
|
+
|
86
|
+
* `current`: A symlink to the currently running version of your application.
|
87
|
+
|
88
|
+
|
89
|
+
## Shared data
|
90
|
+
|
91
|
+
Anything you need to be shared across deployments should live under the
|
92
|
+
`shared` directory, and symlinks placed in your releases to point to the
|
93
|
+
relevant data in `shared`. You share things in your deployments by calling
|
94
|
+
the `share` helper function within your hook scripts. This function will
|
95
|
+
take a relative path (relative to the root of your repo) and create a
|
96
|
+
symlink into the same path within `shared`.
|
97
|
+
|
98
|
+
For example, you might have a hook script that uses Bundler to setup your
|
99
|
+
local gems. Since you don't want to have to dick around reinstalling all
|
100
|
+
your gems with every release, you want to share that bundle between
|
101
|
+
releases. Your hook script might look something like this:
|
102
|
+
|
103
|
+
. /path/to/giddyup/functions.sh
|
104
|
+
share vendor/bundle
|
105
|
+
cd "${RELEASE}"
|
106
|
+
bundle install --deployment
|
107
|
+
|
108
|
+
This will create a symlink from `$ROOT/releases/<timestamp>/vendor/bundle`
|
109
|
+
to `$ROOT/shared/vendor/bundle`, then run bundle in the new release to
|
110
|
+
ensure that all your required gems are available.
|
111
|
+
|
112
|
+
If a file or directory already exists in `releases/<timestamp>` that is
|
113
|
+
supposed to be symlinked, we'll remove it before creating the symlink. The
|
114
|
+
directory structure in `shared` will always mirror that of your releases
|
115
|
+
when you use `share`; this improves simplicity and comprehensibility.
|
116
|
+
|
117
|
+
If a symlink destination doesn't exist already within `shared`, then we'll
|
118
|
+
copy the source content in your release (if it exists) into `shared`.
|
119
|
+
Leading directory components will be created in `shared` automatically, as
|
120
|
+
well.
|
121
|
+
|
122
|
+
|
123
|
+
# Hooks
|
124
|
+
|
125
|
+
Because everyone's application is slightly different, Giddyup itself doesn't
|
126
|
+
actually do very much itself -- instead, it delegates a lot of things back
|
127
|
+
to code that you write, as hooks. To understand hooks, it's best to
|
128
|
+
understand how Giddyup works:
|
129
|
+
|
130
|
+
1. You push your updated code to the repo controlled by giddyup
|
131
|
+
2. The `update` hook, provided by Giddyup, starts to run
|
132
|
+
3. Giddyup makes a copy of the code in your deployment repo
|
133
|
+
4. Giddyup runs the `stop` hook
|
134
|
+
5. Giddyup changes the "current" symlink for the deployment to point at the
|
135
|
+
newly configured code
|
136
|
+
6. Giddyup runs the `start` hook
|
137
|
+
7. Old releases are tidied up, if required (see the `giddyup.keepreleases`
|
138
|
+
config variable)
|
139
|
+
|
140
|
+
To put it another way, the following hooks are available:
|
141
|
+
|
142
|
+
* **stop**: Run after the new release is in place, but while the system's
|
143
|
+
idea of the "current" release still points to the currently running code.
|
144
|
+
You'll probably want to do whatever's required to stop your appserver from
|
145
|
+
running in this hook, run bundler, and perhaps put up a maintenance page.
|
146
|
+
|
147
|
+
* **start**: Run after the "current" symlink has been changed to point to
|
148
|
+
the new code. In here you'd probably want to do database migrations,
|
149
|
+
start your appserver, and take down your maintenance page.
|
150
|
+
|
151
|
+
|
152
|
+
## Running hooks
|
153
|
+
|
154
|
+
Giddyup always runs hooks from the **newly deployed** version of your
|
155
|
+
codebase (so that if you happened to push a deploy with a dodgy `stop` hook,
|
156
|
+
you can recover from it by pushing a fixed version). It looks for hooks in
|
157
|
+
the location specified by the `giddyup.hookdir` git config variable (see
|
158
|
+
"Configuration", below).
|
159
|
+
|
160
|
+
It is looking for files or directories that match the name of the hook
|
161
|
+
(`start`, `stop`, etc). If there is a file named after the hook, and it is
|
162
|
+
executable, then that file is executed. If, on the other hand, there is a
|
163
|
+
directory named after the hook, then all executable files in that directory
|
164
|
+
are executed, in the lexical order of the names of the files.
|
165
|
+
|
166
|
+
If you really, *really* need to be able to run hook files that aren't
|
167
|
+
already executable (I don't know why; git stores executable bits just fine)
|
168
|
+
then you can set the `autochmodhooks` config variable; this will have the
|
169
|
+
effect of making *every* file in your hook directories (if you're using
|
170
|
+
them) a hook script. So don't combine this config option with a `README`...
|
171
|
+
|
172
|
+
Each hook script is run as a separate process, and as such cannot effect the
|
173
|
+
environment or working directory of giddyup itself or any other hook script.
|
174
|
+
|
175
|
+
|
176
|
+
## Hook environment
|
177
|
+
|
178
|
+
The environment of the hook is very minimal; only the following environment
|
179
|
+
variables will be set:
|
180
|
+
|
181
|
+
* `PATH` -- the same as the path of the Giddyup script itself.
|
182
|
+
* `APP_ENV` -- the environment specified by `giddyup.environment` (see
|
183
|
+
"Configuration", below).
|
184
|
+
* `ROOT` -- the directory that is the "root" of the entire deployment; the
|
185
|
+
directory which the deployment git repository (and everything else) lives
|
186
|
+
* `RELEASE` -- the canonical directory that contains the data in this
|
187
|
+
release of the application.
|
188
|
+
* `NEWREV` -- The SHA of the commit being deployed.
|
189
|
+
* `OLDREV` -- The SHA of the commit being replaced.
|
190
|
+
|
191
|
+
The working directory of all hooks is the root of the deployment tree.
|
192
|
+
During the 'stop' hook, the `current` symlink will point to the previous
|
193
|
+
running release of the application, while during the `start` hook the
|
194
|
+
`current` symlink will point to the new release you're currently deploying.
|
195
|
+
|
196
|
+
|
197
|
+
## Hook script helpers
|
198
|
+
|
199
|
+
To help you make your hook scripts easier to write, there are some shell
|
200
|
+
functions available to help you on your way. To use them, merely add:
|
201
|
+
|
202
|
+
. /path/to/giddyup/functions.sh
|
203
|
+
|
204
|
+
at the top of your hook script, and then call away to your heart's content.
|
205
|
+
|
206
|
+
|
207
|
+
|
208
|
+
# Error handling
|
209
|
+
|
210
|
+
At present, the error handling in giddyup is a bit primitive. In general,
|
211
|
+
if any part of the giddyup script fails, the whole process will abort. This
|
212
|
+
*mostly* won't hurt your running app, because giddyup does as much as it can
|
213
|
+
before starting or stopping anything. However, if your hook scripts bomb
|
214
|
+
out, or one of the things that runs between stopping and starting the app
|
215
|
+
fails, then things could be left in a bit of a limbo state.
|
216
|
+
|
217
|
+
Improvements to this part of the system, particularly around intelligent
|
218
|
+
rollback strategies, is planned. Patches welcome.
|
219
|
+
|
220
|
+
|
221
|
+
# Configuration
|
222
|
+
|
223
|
+
Gidduyp is controlled by git repository configuration variables; these can
|
224
|
+
either be set using `git config` in the deployment repository, or by
|
225
|
+
directly editing the `config` file in the deployment repository.
|
226
|
+
|
227
|
+
Available configuration variables are given below.
|
228
|
+
|
229
|
+
|
230
|
+
## `giddyup.environment`
|
231
|
+
|
232
|
+
(**OPTIONAL**; default: none)
|
233
|
+
|
234
|
+
Many web application frameworks have a concept of "environments"; that is,
|
235
|
+
different sets of configuration parameters (like database credentials) that
|
236
|
+
vary between instances of the application running with the same source code.
|
237
|
+
|
238
|
+
Giddyup supports this paradigm with the use of the `giddyup.environment`
|
239
|
+
variable. If set, the value will be passed through to the `APP_ENV`
|
240
|
+
environment variable in your hook scripts; this allows you to do slightly
|
241
|
+
different things (like reload a different appserver, or assist in running
|
242
|
+
your database migrations against a different database). Since this
|
243
|
+
environment is specified per-repository, you can run several environments
|
244
|
+
(say, uat, staging, and production) with the same in-repo deployment hooks,
|
245
|
+
just by pushing to different remote repositories (which have different
|
246
|
+
settings for `giddyup.environment`, of course).
|
247
|
+
|
248
|
+
|
249
|
+
## `giddyup.hookdir`
|
250
|
+
|
251
|
+
(**OPTIONAL**; default `config/hooks`)
|
252
|
+
|
253
|
+
The directory within which Giddyup will look to find hook scripts, relative
|
254
|
+
to the root of your working copy. The intent is that the hooks relating to
|
255
|
+
the deployment of your application are best kept *with* your application.
|
256
|
+
|
257
|
+
|
258
|
+
## `giddyup.keepreleases`
|
259
|
+
|
260
|
+
(**OPTIONAL**; default: 5)
|
261
|
+
|
262
|
+
Giddyup will keep a few older deployed releases available, in case you need
|
263
|
+
to make an emergency rollback, or examine exactly what was recently
|
264
|
+
deployed. This configuration parameter determines how many releases to
|
265
|
+
keep. Set this to 1 to not keep any previous releases, and set it to 0 if
|
266
|
+
you want to keep every release ever (not a good idea unless you've got one
|
267
|
+
of those fancy new infinite-capacity hard drives).
|
268
|
+
|
269
|
+
|
270
|
+
## `giddyup.debug`
|
271
|
+
|
272
|
+
(**OPTIONAL**; default: false)
|
273
|
+
|
274
|
+
If you want to have far, far too much gory detail about what giddyup is
|
275
|
+
doing, you can set this to true.
|
276
|
+
|
277
|
+
|
278
|
+
## `giddyup.autochmodhooks`
|
279
|
+
|
280
|
+
(**OPTIONAL**; default: false)
|
281
|
+
|
282
|
+
Sometimes the hook files found in `giddyup.hookdir` (e.g. in
|
283
|
+
`config/hooks/`) are not marked as executable in the repository. Setting
|
284
|
+
`giddyup.autochmodhooks` to `true` will cause giddyup to set the executable
|
285
|
+
bit itself before running the hook script.
|
286
|
+
|
287
|
+
|
288
|
+
# Frequently Answered Questions
|
289
|
+
|
290
|
+
## What? Is that all?
|
291
|
+
|
292
|
+
Yes, the main giddyup update hook script is quite minimal. The aim here is
|
293
|
+
to provide the necessary scaffolding upon which customised deployment
|
294
|
+
processes can be implemented.
|
295
|
+
|
296
|
+
To put it another way: yes, Giddyup is small and dumb. You're supposed to
|
297
|
+
provide the smarts in hooks. If you have a general-purpose hook you'd like
|
298
|
+
to share with others, please feel free to [send it to
|
299
|
+
me](mailto:theshed+giddyup@hezmatt.org) and I'll include it in the examples.
|
300
|
+
|
301
|
+
|
302
|
+
## I need moah hooks!
|
303
|
+
|
304
|
+
Weeeeell... maybe you do, maybe you don't. Remember that you can execute as
|
305
|
+
many different hook scripts as you like, and in the order you specify. For
|
306
|
+
example, you might think you need a "pre-stop" hook, to do things that might
|
307
|
+
take a while, before the application is stopped (to minimise downtime). You
|
308
|
+
don't actually need a separate hook in giddyup; all you need to do is move
|
309
|
+
the hook script that actually stops your appservers to after the hook script
|
310
|
+
that does whatever time-consuming task you have in mind.
|
311
|
+
|
312
|
+
|
313
|
+
## What's with the name?!?
|
314
|
+
|
315
|
+
Take the phrase "git deployment", chop off most of "deployment" to produce
|
316
|
+
something like "gitde", say it as a single word a few times until the 't'
|
317
|
+
kinda disappears and the 'e' lengthens so it sounds like "gidee", then shout
|
318
|
+
"yeehaw!" like an old time cowboy. Waving of Stetsons is optional, but
|
319
|
+
strongly encouraged.
|
data/bin/git-deploy
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# Wrap all calls to external commands in appropriate class methods.
|
2
|
+
#
|
3
|
+
# Honestly, this is primarily for ease of testing, but I suppose if someone
|
4
|
+
# wanted to go hog-wild and replace this for some crazy use of GitDeploy, it
|
5
|
+
# might come in handy.
|
6
|
+
#
|
7
|
+
require 'open3'
|
8
|
+
|
9
|
+
module Giddyup; end
|
10
|
+
|
11
|
+
class Giddyup::CommandWrapper
|
12
|
+
class CommandFailed < StandardError
|
13
|
+
attr_reader :status, :output, :command
|
14
|
+
|
15
|
+
def initialize(status, output, command)
|
16
|
+
@status = status
|
17
|
+
@output = output
|
18
|
+
@command = command
|
19
|
+
super("Command execution failed")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Run a command and return the output of the command as an array
|
24
|
+
# containing elements of the following form:
|
25
|
+
#
|
26
|
+
# [<stream>, <line>]
|
27
|
+
#
|
28
|
+
# Where <stream> is either :stdout or :stderr (to indicate which output
|
29
|
+
# stream the line came from), and <line> is the line of text (sans
|
30
|
+
# trailing newline) that was output.
|
31
|
+
#
|
32
|
+
# These lines of text are interspersed in the order they were received,
|
33
|
+
# to ensure the best possible match-up of errors to regular output.
|
34
|
+
#
|
35
|
+
# If the command returns a non-zero exit code, a
|
36
|
+
# Giddyup::CommandWrapper::CommandFailed exception is raised containing
|
37
|
+
# the status of the failed command and all output.
|
38
|
+
#
|
39
|
+
def self.run_command(cmd)
|
40
|
+
output = []
|
41
|
+
rv = nil
|
42
|
+
|
43
|
+
Open3.popen3(cmd) do |stdin, stdout, stderr, thr|
|
44
|
+
# We don't want to talk *to* you
|
45
|
+
stdin.close
|
46
|
+
|
47
|
+
fds = [stdout, stderr]
|
48
|
+
|
49
|
+
until fds.empty?
|
50
|
+
a = IO.select(fds, [], [], 0.1)
|
51
|
+
|
52
|
+
if a.nil?
|
53
|
+
if fds == [stderr]
|
54
|
+
# SSH is an annoying sack of crap. When using ControlMaster
|
55
|
+
# (multiplexing), the background mux process keeps stderr open,
|
56
|
+
# which means that this loop never ends (because stderr.eof?
|
57
|
+
# is never true). So, we're using the heuristic that if stdout
|
58
|
+
# is closed (ie it hsa been removed from `fds`) and we've timed
|
59
|
+
# out rather than seeing any further activity on stderr) then
|
60
|
+
# the command execution is done.
|
61
|
+
break
|
62
|
+
else
|
63
|
+
# We had a timeout, but we're still running!
|
64
|
+
next
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
if a[0].include? stdout
|
69
|
+
if stdout.eof?
|
70
|
+
stdout.close
|
71
|
+
fds.delete(stdout)
|
72
|
+
else
|
73
|
+
output << [:stdout, stdout.readline.chomp]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
if a[0].include? stderr
|
78
|
+
if stderr.eof?
|
79
|
+
stderr.close
|
80
|
+
fds.delete(stderr)
|
81
|
+
else
|
82
|
+
output << [:stderr, stderr.readline.chomp]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
rv = thr.value
|
88
|
+
end
|
89
|
+
|
90
|
+
if rv.exitstatus != 0
|
91
|
+
raise CommandFailed.new(rv, output, cmd)
|
92
|
+
end
|
93
|
+
|
94
|
+
output
|
95
|
+
end
|
96
|
+
|
97
|
+
# Run a command, but only return stdout as a string
|
98
|
+
def self.run_command_stdout(cmd)
|
99
|
+
run_command(cmd).
|
100
|
+
select { |l| l[0] == :stdout }.
|
101
|
+
map { |l| l[1] }.
|
102
|
+
join("\n")
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# Class implementing the git-deploy command line.
|
2
|
+
#
|
3
|
+
# Create an instance with an array of command line parameters, and call `#run`.
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'open3'
|
7
|
+
require 'terminal-display-colors'
|
8
|
+
require 'giddyup/command_wrapper'
|
9
|
+
|
10
|
+
module Giddyup; end
|
11
|
+
|
12
|
+
class Giddyup::GitDeploy
|
13
|
+
attr_reader :env, :verbose
|
14
|
+
|
15
|
+
def initialize(argv, opts = {})
|
16
|
+
argv = argv.dup
|
17
|
+
@verbose = false
|
18
|
+
|
19
|
+
if argv.index('-v')
|
20
|
+
@verbose = true
|
21
|
+
argv.delete('-v')
|
22
|
+
end
|
23
|
+
|
24
|
+
@stdout = opts[:stdout] || $stdout
|
25
|
+
@stderr = opts[:stderr] || $stderr
|
26
|
+
@command = opts[:command_wrapper] || Giddyup::CommandWrapper
|
27
|
+
|
28
|
+
begin
|
29
|
+
@base_dir = @command.run_command_stdout("git rev-parse --show-toplevel")
|
30
|
+
rescue Giddyup::CommandWrapper::CommandFailed => e
|
31
|
+
raise RuntimeError,
|
32
|
+
"ERROR: Not in a git tree"
|
33
|
+
end
|
34
|
+
|
35
|
+
@config_file = File.join(@base_dir, '.git-deploy')
|
36
|
+
|
37
|
+
@env = argv.shift
|
38
|
+
|
39
|
+
unless argv.empty?
|
40
|
+
raise RuntimeError,
|
41
|
+
"Unknown config parameter(s): #{argv.inspect}"
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def run
|
47
|
+
start_time = Time.now
|
48
|
+
|
49
|
+
if @env.nil? or @env.empty?
|
50
|
+
raise RuntimeError,
|
51
|
+
"No environment given to deploy to"
|
52
|
+
end
|
53
|
+
|
54
|
+
@stdout.puts "Deploying to environment '#{@env}'"
|
55
|
+
|
56
|
+
do_push
|
57
|
+
do_trigger
|
58
|
+
|
59
|
+
@stdout.printf "Completed deployment in %.2f seconds.\n", (Time.now - start_time).to_f
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def do_push
|
64
|
+
config_item("pushTo", true).each do |t|
|
65
|
+
run_command "Pushing HEAD to remote '#{t}'",
|
66
|
+
"git push '#{t}' HEAD:master"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def do_trigger
|
71
|
+
targets.each do |t|
|
72
|
+
ssh, dir = t.split(':', 2)
|
73
|
+
run_command "Triggering deployment for '#{t}'",
|
74
|
+
"ssh #{ssh} /usr/local/lib/giddyup/trigger-deploy " +
|
75
|
+
"#{@verbose ? '-v ' : ''}#{dir}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# From a list of `target` / `target-enumerator` configuration parameters
|
80
|
+
# (in the form returned from `config_list`), return a one-dimensional
|
81
|
+
# array of strings containing the names of all machines to deploy to.
|
82
|
+
#
|
83
|
+
def targets
|
84
|
+
@targets ||= enumerate_targets
|
85
|
+
end
|
86
|
+
|
87
|
+
def enumerate_targets
|
88
|
+
config_list('target(Enumerator)?$').map do |li|
|
89
|
+
if li[0].downcase == 'target'
|
90
|
+
li[1]
|
91
|
+
elsif li[0].downcase == 'targetenumerator'
|
92
|
+
rv = run_command("Enumerating targets using '#{li[1]}'", li[1]).split(/\s+/)
|
93
|
+
if $?.exitstatus != 0
|
94
|
+
raise RuntimeError,
|
95
|
+
"Target enumeration failed. Aborting."
|
96
|
+
end
|
97
|
+
rv
|
98
|
+
else
|
99
|
+
raise RuntimeError,
|
100
|
+
"Unknown target expansion option: #{li[0]}"
|
101
|
+
end
|
102
|
+
end.flatten
|
103
|
+
end
|
104
|
+
|
105
|
+
# Get the value(s) of a configuration item from the config file.
|
106
|
+
#
|
107
|
+
# Reads the `.git-deploy` config file, and retrieves the value of the
|
108
|
+
# config `item`. If you pass `true` to `multi`, then the configuration
|
109
|
+
# parameter is read as a "multi-value" parameter, and the list of
|
110
|
+
# configuration parameters is returned as an array; otherwise, we will
|
111
|
+
# get a single value, returned as a string, and if multiple values are
|
112
|
+
# returned, a RuntimeError will be raised.
|
113
|
+
#
|
114
|
+
def config_item(item, multi = false)
|
115
|
+
cmd = "git config -f #{@config_file} " +
|
116
|
+
"#{multi ? '--get-all' : '--get'} " +
|
117
|
+
"'environment.#{@env}.#{item}'"
|
118
|
+
|
119
|
+
begin
|
120
|
+
s = @command.run_command_stdout(cmd)
|
121
|
+
rescue Giddyup::CommandWrapper::CommandFailed => e
|
122
|
+
if e.status.exitstatus == 2 and !multi
|
123
|
+
raise RuntimeError,
|
124
|
+
"Multiple values found for a single-value parameter environment.#{@env}.#{item}"
|
125
|
+
elsif e.status.exitstatus == 1
|
126
|
+
# This means "nothing found", which we're cool with
|
127
|
+
return multi ? [] : ""
|
128
|
+
else
|
129
|
+
raise RuntimeError,
|
130
|
+
"Failed to get config parameter environment.#{@env}.#{item}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
if multi
|
135
|
+
s.split(/\s+/)
|
136
|
+
else
|
137
|
+
s
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Retrieve the in-order list of configuration parameters whose
|
142
|
+
# item name matches the anchored `regex`.
|
143
|
+
#
|
144
|
+
# This function returns an array of the form `[[name, value], [name,
|
145
|
+
# value], ...]`.
|
146
|
+
#
|
147
|
+
def config_list(regex)
|
148
|
+
cmd = "git config -f #{@config_file} " +
|
149
|
+
"--get-regexp 'environment.#{@env}.#{regex}'"
|
150
|
+
|
151
|
+
begin
|
152
|
+
@command.
|
153
|
+
run_command_stdout(cmd).
|
154
|
+
split("\n").
|
155
|
+
map { |l| l.split(/\s+/, 2) }.
|
156
|
+
map { |item| [item[0].gsub("environment.#{@env}.", ''), item[1]] }
|
157
|
+
rescue Giddyup::CommandWrapper::CommandFailed => e
|
158
|
+
if e.status.exitstatus == 1
|
159
|
+
# "Nothing found", OK then
|
160
|
+
return []
|
161
|
+
else
|
162
|
+
raise RuntimeError,
|
163
|
+
"Failed to get config list environment.#{@env}.#{regex}"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Run a command, with all sorts of prettyness.
|
169
|
+
#
|
170
|
+
# This function will run the specified command, telling the user what is
|
171
|
+
# happening. If the command succeeds (that is, exits with a status of
|
172
|
+
# 0), then a green "OK" will be printed, otherwise a red "FAILED" will be
|
173
|
+
# printed. If the command fails, or @verbose is set, then the output of
|
174
|
+
# the command will be displayed after the status line, with `stdout: ` or
|
175
|
+
# `stderr: ` prefixed to each line, as appropriate.
|
176
|
+
#
|
177
|
+
# Regardless of what happens, the stdout of the command will be returned
|
178
|
+
# as a string.
|
179
|
+
#
|
180
|
+
def run_command(desc, cmdline)
|
181
|
+
@stdout.print "[....] #{desc}"
|
182
|
+
|
183
|
+
output = nil
|
184
|
+
failed = false
|
185
|
+
begin
|
186
|
+
output = @command.run_command(cmdline)
|
187
|
+
rescue Giddyup::CommandWrapper::CommandFailed => e
|
188
|
+
@stdout.puts "\x0d["+"FAIL".red+"] #{desc}"
|
189
|
+
output = e.output
|
190
|
+
failed = true
|
191
|
+
else
|
192
|
+
@stdout.puts "\x0d["+" OK ".green+"] #{desc}"
|
193
|
+
end
|
194
|
+
|
195
|
+
if @verbose or failed
|
196
|
+
@stdout.puts cmdline
|
197
|
+
@stdout.puts output.map { |l| "#{l[0].to_s.send(l[0] == :stderr ? :cyan : :purple)}: #{l[1]}" }.join("\n")
|
198
|
+
end
|
199
|
+
|
200
|
+
return output.select { |l| l[0] == :stdout }.map { |l| l[1] }.join("\n")
|
201
|
+
end
|
202
|
+
end
|
metadata
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: giddyup-deploy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.1.g3db36a2
|
5
|
+
prerelease: 8
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matt Palmer
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-09-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: git-version-bump
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: terminal-display-colors
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bundler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: plymouth
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: pry
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: rake
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rdoc
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rspec
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: spork
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
description:
|
159
|
+
email:
|
160
|
+
executables:
|
161
|
+
- git-deploy
|
162
|
+
extensions: []
|
163
|
+
extra_rdoc_files:
|
164
|
+
- README.md
|
165
|
+
files:
|
166
|
+
- bin/git-deploy
|
167
|
+
- lib/giddyup/git_deploy.rb
|
168
|
+
- lib/giddyup/command_wrapper.rb
|
169
|
+
- README.md
|
170
|
+
homepage: http://theshed.hezmatt.org/giddyup
|
171
|
+
licenses: []
|
172
|
+
post_install_message:
|
173
|
+
rdoc_options: []
|
174
|
+
require_paths:
|
175
|
+
- lib
|
176
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
177
|
+
none: false
|
178
|
+
requirements:
|
179
|
+
- - ! '>='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
segments:
|
183
|
+
- 0
|
184
|
+
hash: -3314317262415006446
|
185
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
186
|
+
none: false
|
187
|
+
requirements:
|
188
|
+
- - ! '>'
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
version: 1.3.1
|
191
|
+
requirements: []
|
192
|
+
rubyforge_project:
|
193
|
+
rubygems_version: 1.8.23
|
194
|
+
signing_key:
|
195
|
+
specification_version: 3
|
196
|
+
summary: ! '''git-deploy'' command to interact with giddyup-managed deployments'
|
197
|
+
test_files: []
|