minestrone 0.0.1
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.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +32 -0
- data/.gitignore +5 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +35 -0
- data/Rakefile +10 -0
- data/bin/capify +89 -0
- data/bin/min +5 -0
- data/docs/lib-codebase-map.md +162 -0
- data/docs/lib-dependency-graph.svg +129 -0
- data/lib/minestrone/callback.rb +45 -0
- data/lib/minestrone/cli/help.rb +131 -0
- data/lib/minestrone/cli/help.txt +72 -0
- data/lib/minestrone/cli/options.rb +232 -0
- data/lib/minestrone/cli.rb +159 -0
- data/lib/minestrone/command.rb +177 -0
- data/lib/minestrone/configuration/actions/file_transfer.rb +53 -0
- data/lib/minestrone/configuration/actions/inspect.rb +46 -0
- data/lib/minestrone/configuration/actions/invocation.rb +202 -0
- data/lib/minestrone/configuration/alias_task.rb +29 -0
- data/lib/minestrone/configuration/callbacks.rb +129 -0
- data/lib/minestrone/configuration/connections.rb +66 -0
- data/lib/minestrone/configuration/execution.rb +139 -0
- data/lib/minestrone/configuration/loading.rb +207 -0
- data/lib/minestrone/configuration/log_formatters.rb +75 -0
- data/lib/minestrone/configuration/namespaces.rb +225 -0
- data/lib/minestrone/configuration/servers.rb +70 -0
- data/lib/minestrone/configuration/variables.rb +115 -0
- data/lib/minestrone/configuration.rb +69 -0
- data/lib/minestrone/errors.rb +17 -0
- data/lib/minestrone/ext/string.rb +7 -0
- data/lib/minestrone/extensions.rb +56 -0
- data/lib/minestrone/logger.rb +171 -0
- data/lib/minestrone/processable.rb +50 -0
- data/lib/minestrone/recipes/deploy/assets.rb +194 -0
- data/lib/minestrone/recipes/deploy/bundler.rb +81 -0
- data/lib/minestrone/recipes/deploy/dependencies.rb +44 -0
- data/lib/minestrone/recipes/deploy/local_dependency.rb +45 -0
- data/lib/minestrone/recipes/deploy/remote_dependency.rb +119 -0
- data/lib/minestrone/recipes/deploy/scm/base.rb +204 -0
- data/lib/minestrone/recipes/deploy/scm/git.rb +284 -0
- data/lib/minestrone/recipes/deploy/scm/none.rb +54 -0
- data/lib/minestrone/recipes/deploy/scm.rb +22 -0
- data/lib/minestrone/recipes/deploy/strategy/base.rb +87 -0
- data/lib/minestrone/recipes/deploy/strategy/copy.rb +353 -0
- data/lib/minestrone/recipes/deploy/strategy/remote_cache.rb +80 -0
- data/lib/minestrone/recipes/deploy/strategy.rb +22 -0
- data/lib/minestrone/recipes/deploy.rb +639 -0
- data/lib/minestrone/recipes/standard.rb +23 -0
- data/lib/minestrone/recipes/templates/maintenance.rhtml +53 -0
- data/lib/minestrone/server_definition.rb +56 -0
- data/lib/minestrone/ssh.rb +81 -0
- data/lib/minestrone/task_definition.rb +82 -0
- data/lib/minestrone/transfer.rb +205 -0
- data/lib/minestrone/version.rb +11 -0
- data/lib/minestrone.rb +3 -0
- data/minestrone.gemspec +32 -0
- data/test/cli/execute_test.rb +130 -0
- data/test/cli/help_test.rb +178 -0
- data/test/cli/options_test.rb +315 -0
- data/test/cli/ui_test.rb +26 -0
- data/test/cli_test.rb +17 -0
- data/test/command_test.rb +305 -0
- data/test/configuration/actions/file_transfer_test.rb +61 -0
- data/test/configuration/actions/inspect_test.rb +76 -0
- data/test/configuration/actions/invocation_test.rb +258 -0
- data/test/configuration/alias_task_test.rb +110 -0
- data/test/configuration/callbacks_test.rb +201 -0
- data/test/configuration/connections_test.rb +192 -0
- data/test/configuration/execution_test.rb +176 -0
- data/test/configuration/loading_test.rb +149 -0
- data/test/configuration/namespace_dsl_test.rb +325 -0
- data/test/configuration/servers_test.rb +100 -0
- data/test/configuration/variables_test.rb +191 -0
- data/test/configuration_test.rb +77 -0
- data/test/deploy/local_dependency_test.rb +61 -0
- data/test/deploy/remote_dependency_test.rb +146 -0
- data/test/deploy/scm/base_test.rb +55 -0
- data/test/deploy/scm/git_test.rb +260 -0
- data/test/deploy/scm/none_test.rb +26 -0
- data/test/deploy/strategy/copy_test.rb +360 -0
- data/test/extensions_test.rb +69 -0
- data/test/fixtures/cli_integration.rb +5 -0
- data/test/fixtures/config.rb +4 -0
- data/test/fixtures/custom.rb +3 -0
- data/test/logger_formatting_test.rb +149 -0
- data/test/logger_test.rb +134 -0
- data/test/recipes_test.rb +26 -0
- data/test/server_definition_test.rb +121 -0
- data/test/ssh_test.rb +99 -0
- data/test/task_definition_test.rb +117 -0
- data/test/transfer_test.rb +172 -0
- data/test/utils.rb +28 -0
- data/test/version_test.rb +11 -0
- metadata +258 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'minestrone/recipes/deploy/strategy/base'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'tempfile' # Dir.tmpdir
|
|
6
|
+
|
|
7
|
+
module Minestrone
|
|
8
|
+
module Deploy
|
|
9
|
+
module Strategy
|
|
10
|
+
|
|
11
|
+
# This class implements the strategy for deployments which work
|
|
12
|
+
# by preparing the source code locally, compressing it, copying the
|
|
13
|
+
# file to the target host, and uncompressing it to the deployment
|
|
14
|
+
# directory.
|
|
15
|
+
#
|
|
16
|
+
# By default, the SCM checkout command is used to obtain the local copy
|
|
17
|
+
# of the source code. If you would rather use the export operation,
|
|
18
|
+
# you can set the :copy_strategy variable to :export.
|
|
19
|
+
#
|
|
20
|
+
# set :copy_strategy, :export
|
|
21
|
+
#
|
|
22
|
+
# For even faster deployments, you can set the :copy_cache variable to
|
|
23
|
+
# true. This will cause deployments to do a new checkout of your
|
|
24
|
+
# repository to a new directory, and then copy that checkout. Subsequent
|
|
25
|
+
# deploys will just resync that copy, rather than doing an entirely new
|
|
26
|
+
# checkout. Additionally, you can specify file patterns to exclude from
|
|
27
|
+
# the copy when using :copy_cache; just set the :copy_exclude variable
|
|
28
|
+
# to a file glob (or an array of globs).
|
|
29
|
+
#
|
|
30
|
+
# set :copy_cache, true
|
|
31
|
+
# set :copy_exclude, ".git/*"
|
|
32
|
+
#
|
|
33
|
+
# Note that :copy_strategy is ignored when :copy_cache is set. Also, if
|
|
34
|
+
# you want the copy cache put somewhere specific, you can set the variable
|
|
35
|
+
# to the path you want, instead of merely 'true':
|
|
36
|
+
#
|
|
37
|
+
# set :copy_cache, "/tmp/caches/myapp"
|
|
38
|
+
#
|
|
39
|
+
# This deployment strategy also supports a special variable,
|
|
40
|
+
# :copy_compression, which must be one of :gzip, :bz2, or
|
|
41
|
+
# :zip, and which specifies how the source should be compressed for
|
|
42
|
+
# transmission to each host.
|
|
43
|
+
#
|
|
44
|
+
# By default, files will be transferred across to the remote machines via 'sftp'. If you prefer
|
|
45
|
+
# to use 'scp' you can set the :copy_via variable to :scp.
|
|
46
|
+
#
|
|
47
|
+
# set :copy_via, :scp
|
|
48
|
+
#
|
|
49
|
+
# There is a possibility to pass a build command that will get
|
|
50
|
+
# executed if your code needs to be compiled or something needs to be
|
|
51
|
+
# done before the code is ready to run.
|
|
52
|
+
#
|
|
53
|
+
# set :build_script, "make all"
|
|
54
|
+
#
|
|
55
|
+
# Note that if you use :copy_cache, the :build_script is used on the
|
|
56
|
+
# cache and thus you get faster compilation if your script does not
|
|
57
|
+
# recompile everything.
|
|
58
|
+
|
|
59
|
+
class Copy < Base
|
|
60
|
+
|
|
61
|
+
# Obtains a copy of the source code locally (via the #command method),
|
|
62
|
+
# compresses it to a single file, copies that file to the target
|
|
63
|
+
# server, and uncompresses it into the deployment directory.
|
|
64
|
+
|
|
65
|
+
def deploy!
|
|
66
|
+
copy_cache ? run_copy_cache_strategy : run_copy_strategy
|
|
67
|
+
|
|
68
|
+
create_revision_file
|
|
69
|
+
compress_repository
|
|
70
|
+
distribute!
|
|
71
|
+
ensure
|
|
72
|
+
rollback_changes
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def build(directory)
|
|
76
|
+
return unless build_script
|
|
77
|
+
|
|
78
|
+
execute "running build script on #{directory}" do
|
|
79
|
+
Dir.chdir(directory) { system(build_script) }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def check!
|
|
84
|
+
super.check do |d|
|
|
85
|
+
d.local.command(source.local.command) if source.local.command
|
|
86
|
+
d.local.command(compress(nil, nil).first)
|
|
87
|
+
d.remote.command(decompress(nil).first)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Returns the location of the local copy cache, if the strategy should
|
|
92
|
+
# use a local cache + copy instead of a new checkout/export every
|
|
93
|
+
# time. Returns +nil+ unless :copy_cache has been set. If :copy_cache
|
|
94
|
+
# is +true+, a default cache location will be returned.
|
|
95
|
+
|
|
96
|
+
def copy_cache
|
|
97
|
+
@copy_cache ||= if configuration[:copy_cache] == true
|
|
98
|
+
File.expand_path(configuration[:application], Dir.tmpdir)
|
|
99
|
+
else
|
|
100
|
+
File.expand_path(configuration[:copy_cache], Dir.pwd)
|
|
101
|
+
end
|
|
102
|
+
rescue StandardError
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def run_copy_cache_strategy
|
|
110
|
+
copy_repository_to_local_cache
|
|
111
|
+
build(copy_cache)
|
|
112
|
+
copy_cache_to_staging_area
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def run_copy_strategy
|
|
116
|
+
copy_repository_to_server
|
|
117
|
+
build(destination)
|
|
118
|
+
remove_excluded_files if copy_exclude.any?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def execute(description, &block)
|
|
122
|
+
logger.debug description
|
|
123
|
+
handle_system_errors(&block)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def handle_system_errors(&block)
|
|
127
|
+
block.call
|
|
128
|
+
raise_command_failed if last_command_failed?
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def refresh_local_cache
|
|
132
|
+
execute "refreshing local cache to revision #{revision} at #{copy_cache}" do
|
|
133
|
+
system(source.sync(revision, copy_cache))
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def create_local_cache
|
|
138
|
+
execute "preparing local cache at #{copy_cache}" do
|
|
139
|
+
system(source.checkout(revision, copy_cache))
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def raise_command_failed
|
|
144
|
+
raise Minestrone::Error, "shell command failed with return code #{$?}"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def last_command_failed?
|
|
148
|
+
$? != 0
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def copy_cache_to_staging_area
|
|
152
|
+
execute "copying cache to deployment staging area #{destination}" do
|
|
153
|
+
create_destination
|
|
154
|
+
Dir.chdir(copy_cache) { copy_files(queue_files) }
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def create_destination
|
|
159
|
+
FileUtils.mkdir_p(destination)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def copy_files(files)
|
|
163
|
+
files.each { |name| process_file(name) }
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def process_file(name)
|
|
167
|
+
send "copy_#{filetype(name)}", name
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def filetype(name)
|
|
171
|
+
filetype = File.ftype(name)
|
|
172
|
+
|
|
173
|
+
case filetype
|
|
174
|
+
when 'link', 'directory'
|
|
175
|
+
filetype
|
|
176
|
+
else
|
|
177
|
+
'file'
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def copy_link(name)
|
|
182
|
+
FileUtils.ln_s(File.readlink(name), File.join(destination, name))
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def copy_directory(name)
|
|
186
|
+
FileUtils.mkdir(File.join(destination, name))
|
|
187
|
+
copy_files(queue_files(name))
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def copy_file(name)
|
|
191
|
+
FileUtils.ln(name, File.join(destination, name))
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def queue_files(directory = nil)
|
|
195
|
+
Dir.glob(pattern_for(directory), File::FNM_DOTMATCH).reject! { |file| excluded_files_contain?(file) }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def pattern_for(directory)
|
|
199
|
+
(!directory.nil?) ? "#{escape_globs(directory)}/*" : "*"
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def escape_globs(path)
|
|
203
|
+
path.gsub(/[*?{}\[\]]/, '\\\\\\&')
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def excluded_files_contain?(file)
|
|
207
|
+
copy_exclude.any? { |p| File.fnmatch(p, file) } || ['.', '..'].include?(File.basename(file))
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def copy_repository_to_server
|
|
211
|
+
execute "getting (via #{copy_strategy}) revision #{revision} to #{destination}" do
|
|
212
|
+
copy_repository_via_strategy
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def copy_repository_via_strategy
|
|
217
|
+
system(command)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def remove_excluded_files
|
|
221
|
+
logger.debug "processing exclusions..."
|
|
222
|
+
|
|
223
|
+
copy_exclude.each do |pattern|
|
|
224
|
+
delete_list = Dir.glob(File.join(destination, pattern), File::FNM_DOTMATCH)
|
|
225
|
+
# avoid the /.. trap that deletes the parent directories
|
|
226
|
+
delete_list.delete_if { |dir| dir =~ /\/\.\.$/ }
|
|
227
|
+
FileUtils.rm_rf(delete_list.compact)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def create_revision_file
|
|
232
|
+
File.open(File.join(destination, "REVISION"), "w") { |f| f.puts(revision) }
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def compress_repository
|
|
236
|
+
execute "Compressing #{destination} to #{filename}" do
|
|
237
|
+
Dir.chdir(copy_dir) { system(compress(File.basename(destination), File.basename(filename)).join(" ")) }
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def rollback_changes
|
|
242
|
+
FileUtils.rm(filename) rescue nil
|
|
243
|
+
FileUtils.rm_rf(destination) rescue nil
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def copy_repository_to_local_cache
|
|
247
|
+
File.exist?(copy_cache) ? refresh_local_cache : create_local_cache
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def build_script
|
|
251
|
+
configuration[:build_script]
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Specify patterns to exclude from the copy. This is only valid
|
|
255
|
+
# when using a local cache.
|
|
256
|
+
def copy_exclude
|
|
257
|
+
@copy_exclude ||= Array(configuration.fetch(:copy_exclude, []))
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Returns the basename of the release_path, which will be used to
|
|
261
|
+
# name the local copy and archive file.
|
|
262
|
+
def destination
|
|
263
|
+
@destination ||= File.join(copy_dir, File.basename(configuration[:release_path]))
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Returns the value of the :copy_strategy variable, defaulting to
|
|
267
|
+
# :checkout if it has not been set.
|
|
268
|
+
def copy_strategy
|
|
269
|
+
@copy_strategy ||= configuration.fetch(:copy_strategy, :checkout)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Should return the command(s) necessary to obtain the source code
|
|
273
|
+
# locally.
|
|
274
|
+
def command
|
|
275
|
+
@command ||= case copy_strategy
|
|
276
|
+
when :checkout
|
|
277
|
+
source.checkout(revision, destination)
|
|
278
|
+
when :export
|
|
279
|
+
source.export(revision, destination)
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Returns the name of the file that the source code will be
|
|
284
|
+
# compressed to.
|
|
285
|
+
def filename
|
|
286
|
+
@filename ||= File.join(copy_dir, "#{File.basename(destination)}.#{compression.extension}")
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# The directory to which the copy should be checked out
|
|
290
|
+
def copy_dir
|
|
291
|
+
@copy_dir ||= File.expand_path(configuration[:copy_dir] || Dir.tmpdir, Dir.pwd)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# The directory on the remote server to which the archive should be
|
|
295
|
+
# copied
|
|
296
|
+
def remote_dir
|
|
297
|
+
@remote_dir ||= configuration[:copy_remote_dir] || "/tmp"
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# The location on the remote server where the file should be
|
|
301
|
+
# temporarily stored.
|
|
302
|
+
def remote_filename
|
|
303
|
+
@remote_filename ||= File.join(remote_dir, File.basename(filename))
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# A struct for representing the specifics of a compression type.
|
|
307
|
+
# Commands are arrays, where the first element is the utility to be
|
|
308
|
+
# used to perform the compression or decompression.
|
|
309
|
+
Compression = Struct.new(:extension, :compress_command, :decompress_command)
|
|
310
|
+
|
|
311
|
+
# The compression method to use, defaults to :gzip.
|
|
312
|
+
def compression
|
|
313
|
+
remote_tar = configuration[:copy_remote_tar] || 'tar'
|
|
314
|
+
local_tar = configuration[:copy_local_tar] || 'tar'
|
|
315
|
+
type = configuration[:copy_compression] || :gzip
|
|
316
|
+
|
|
317
|
+
case type
|
|
318
|
+
when :gzip, :gz then Compression.new("tar.gz", [local_tar, 'czf'], [remote_tar, 'xzf'])
|
|
319
|
+
when :bzip2, :bz2 then Compression.new("tar.bz2", [local_tar, 'cjf'], [remote_tar, 'xjf'])
|
|
320
|
+
when :zip then Compression.new("zip", %w(zip -qyr), %w(unzip -q))
|
|
321
|
+
else raise ArgumentError, "invalid compression type #{type.inspect}"
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Returns the command necessary to compress the given directory
|
|
326
|
+
# into the given file.
|
|
327
|
+
def compress(directory, file)
|
|
328
|
+
compression.compress_command + [file, directory]
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Returns the command necessary to decompress the given file,
|
|
332
|
+
# relative to the current working directory. It must also
|
|
333
|
+
# preserve the directory structure in the file.
|
|
334
|
+
def decompress(file)
|
|
335
|
+
compression.decompress_command + [file]
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def decompress_remote_file
|
|
339
|
+
run "cd #{configuration[:releases_path]} && #{decompress(remote_filename).join(" ")} && rm #{remote_filename}"
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Uploads the file to the remote server
|
|
343
|
+
def distribute!
|
|
344
|
+
args = [filename, remote_filename]
|
|
345
|
+
args << { :via => configuration[:copy_via] } if configuration[:copy_via]
|
|
346
|
+
|
|
347
|
+
upload(*args)
|
|
348
|
+
decompress_remote_file
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'minestrone/recipes/deploy/strategy/base'
|
|
4
|
+
|
|
5
|
+
module Minestrone
|
|
6
|
+
module Deploy
|
|
7
|
+
module Strategy
|
|
8
|
+
|
|
9
|
+
# Implements the deployment strategy that keeps a cached checkout of
|
|
10
|
+
# the source code on the remote server. Each deploy simply updates the
|
|
11
|
+
# cached checkout, and then does a copy from the cached copy to the
|
|
12
|
+
# final deployment location.
|
|
13
|
+
|
|
14
|
+
class RemoteCache < Base
|
|
15
|
+
|
|
16
|
+
# Executes the SCM command for this strategy and writes the REVISION mark file on the server.
|
|
17
|
+
def deploy!
|
|
18
|
+
update_repository_cache
|
|
19
|
+
copy_repository_cache
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def check!
|
|
23
|
+
super.check do |d|
|
|
24
|
+
d.remote.command(source.command)
|
|
25
|
+
d.remote.command("rsync") unless copy_exclude.empty?
|
|
26
|
+
d.remote.writable(shared_path)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def repository_cache
|
|
34
|
+
File.join(shared_path, configuration[:repository_cache] || "cached-copy")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def update_repository_cache
|
|
38
|
+
logger.trace "updating the cached checkout on the server"
|
|
39
|
+
|
|
40
|
+
command = "if [ -d #{repository_cache} ]; then " +
|
|
41
|
+
"#{source.sync(revision, repository_cache)}; " +
|
|
42
|
+
"else #{source.checkout(revision, repository_cache)}; fi"
|
|
43
|
+
|
|
44
|
+
scm_run(command)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def copy_repository_cache
|
|
48
|
+
logger.trace "copying the cached version to #{configuration[:release_path]}"
|
|
49
|
+
|
|
50
|
+
if copy_exclude.empty?
|
|
51
|
+
run "cp -RPp #{repository_cache} #{configuration[:release_path]} && #{mark}"
|
|
52
|
+
else
|
|
53
|
+
exclusions = copy_exclude.map { |e| %(--exclude="#{e}") }.join(' ')
|
|
54
|
+
run "rsync -lrpt #{exclusions} #{repository_cache}/ #{configuration[:release_path]} && #{mark}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def copy_exclude
|
|
59
|
+
@copy_exclude ||= Array(configuration.fetch(:copy_exclude, []))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Runs the given command, filtering output back through the
|
|
63
|
+
# #handle_data filter of the SCM implementation.
|
|
64
|
+
def scm_run(command)
|
|
65
|
+
run(command) do |ch, stream, text|
|
|
66
|
+
ch[:state] ||= { :channel => ch }
|
|
67
|
+
output = source.handle_data(ch[:state], stream, text)
|
|
68
|
+
ch.send_data(output) if output
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Returns the command which will write the identifier of the
|
|
73
|
+
# revision being deployed to the REVISION file on the server.
|
|
74
|
+
def mark
|
|
75
|
+
"(echo #{revision} > #{configuration[:release_path]}/REVISION)"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Minestrone
|
|
4
|
+
module Deploy
|
|
5
|
+
module Strategy
|
|
6
|
+
def self.new(strategy, config = {})
|
|
7
|
+
strategy_file = "minestrone/recipes/deploy/strategy/#{strategy}"
|
|
8
|
+
strategy_const = strategy.to_s.capitalize.gsub(/_(.)/) { $1.upcase }
|
|
9
|
+
|
|
10
|
+
require(strategy_file) unless const_defined?(strategy_const)
|
|
11
|
+
|
|
12
|
+
if const_defined?(strategy_const)
|
|
13
|
+
const_get(strategy_const).new(config)
|
|
14
|
+
else
|
|
15
|
+
raise Minestrone::Error, "could not find `#{name}::#{strategy_const}' in `#{strategy_file}'"
|
|
16
|
+
end
|
|
17
|
+
rescue LoadError
|
|
18
|
+
raise Minestrone::Error, "could not find any strategy named `#{strategy}'"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|