giddyup-deploy 0.1.0.1.g3db36a2
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|