travis_dpl_test 2.0.3.beta.4.ror

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +172 -0
  3. data/CODE_OF_CONDUCT.md +74 -0
  4. data/CONTRIBUTING.md +392 -0
  5. data/Gemfile +32 -0
  6. data/Gemfile.lock +611 -0
  7. data/LICENSE +19 -0
  8. data/README.md +2744 -0
  9. data/Rakefile +210 -0
  10. data/bin/dpl +11 -0
  11. data/config/transliterate.yml +733 -0
  12. data/dpl.gemspec +23 -0
  13. data/lib/dpl/assets/atlas/install +19 -0
  14. data/lib/dpl/assets/convox/install +11 -0
  15. data/lib/dpl/assets/dpl/README.erb.md +138 -0
  16. data/lib/dpl/assets/dpl/git_ssh +8 -0
  17. data/lib/dpl/assets/git/detect_private_key +8 -0
  18. data/lib/dpl/assets/hephy/filter_log +3 -0
  19. data/lib/dpl/assets/pypi/install +4 -0
  20. data/lib/dpl/assets/scalingo/install +6 -0
  21. data/lib/dpl/cli.rb +100 -0
  22. data/lib/dpl/ctx/bash.rb +549 -0
  23. data/lib/dpl/ctx/test.rb +255 -0
  24. data/lib/dpl/ctx.rb +4 -0
  25. data/lib/dpl/helper/assets.rb +38 -0
  26. data/lib/dpl/helper/cmd.rb +169 -0
  27. data/lib/dpl/helper/config_file.rb +49 -0
  28. data/lib/dpl/helper/cookbook_site_streaming_uploader.rb +249 -0
  29. data/lib/dpl/helper/env.rb +92 -0
  30. data/lib/dpl/helper/github.rb +22 -0
  31. data/lib/dpl/helper/interpolate.rb +160 -0
  32. data/lib/dpl/helper/memoize.rb +23 -0
  33. data/lib/dpl/helper/squiggle.rb +24 -0
  34. data/lib/dpl/helper/transliterate.rb +13 -0
  35. data/lib/dpl/helper/wrap.rb +11 -0
  36. data/lib/dpl/helper/zip.rb +71 -0
  37. data/lib/dpl/provider/dsl.rb +410 -0
  38. data/lib/dpl/provider/examples.rb +132 -0
  39. data/lib/dpl/provider/status.rb +61 -0
  40. data/lib/dpl/provider.rb +651 -0
  41. data/lib/dpl/providers/anynines.rb +71 -0
  42. data/lib/dpl/providers/azure_web_apps.rb +63 -0
  43. data/lib/dpl/providers/bintray.rb +324 -0
  44. data/lib/dpl/providers/bluemixcloudfoundry.rb +98 -0
  45. data/lib/dpl/providers/boxfuse.rb +52 -0
  46. data/lib/dpl/providers/cargo.rb +32 -0
  47. data/lib/dpl/providers/chef_supermarket.rb +132 -0
  48. data/lib/dpl/providers/cloud66.rb +46 -0
  49. data/lib/dpl/providers/cloudfiles.rb +62 -0
  50. data/lib/dpl/providers/cloudformation.rb +281 -0
  51. data/lib/dpl/providers/cloudfoundry.rb +89 -0
  52. data/lib/dpl/providers/codedeploy.rb +190 -0
  53. data/lib/dpl/providers/convox.rb +130 -0
  54. data/lib/dpl/providers/datica.rb +64 -0
  55. data/lib/dpl/providers/ecr.rb +129 -0
  56. data/lib/dpl/providers/elasticbeanstalk.rb +207 -0
  57. data/lib/dpl/providers/engineyard.rb +113 -0
  58. data/lib/dpl/providers/firebase.rb +45 -0
  59. data/lib/dpl/providers/flynn.rb +35 -0
  60. data/lib/dpl/providers/gae.rb +78 -0
  61. data/lib/dpl/providers/gcs.rb +132 -0
  62. data/lib/dpl/providers/git_push.rb +273 -0
  63. data/lib/dpl/providers/gleis.rb +74 -0
  64. data/lib/dpl/providers/hackage.rb +53 -0
  65. data/lib/dpl/providers/hephy.rb +107 -0
  66. data/lib/dpl/providers/heroku/api.rb +123 -0
  67. data/lib/dpl/providers/heroku/git.rb +54 -0
  68. data/lib/dpl/providers/heroku.rb +111 -0
  69. data/lib/dpl/providers/lambda.rb +211 -0
  70. data/lib/dpl/providers/launchpad.rb +80 -0
  71. data/lib/dpl/providers/netlify.rb +38 -0
  72. data/lib/dpl/providers/npm.rb +130 -0
  73. data/lib/dpl/providers/nuget.rb +41 -0
  74. data/lib/dpl/providers/openshift.rb +52 -0
  75. data/lib/dpl/providers/opsworks.rb +146 -0
  76. data/lib/dpl/providers/packagecloud.rb_ +194 -0
  77. data/lib/dpl/providers/pages/api.rb +106 -0
  78. data/lib/dpl/providers/pages/git.rb +262 -0
  79. data/lib/dpl/providers/pages.rb +18 -0
  80. data/lib/dpl/providers/puppetforge.rb +50 -0
  81. data/lib/dpl/providers/pypi.rb +125 -0
  82. data/lib/dpl/providers/releases.rb +234 -0
  83. data/lib/dpl/providers/rubygems.rb +97 -0
  84. data/lib/dpl/providers/s3.rb +251 -0
  85. data/lib/dpl/providers/scalingo.rb +69 -0
  86. data/lib/dpl/providers/script.rb +32 -0
  87. data/lib/dpl/providers/snap.rb +68 -0
  88. data/lib/dpl/providers/surge.rb +59 -0
  89. data/lib/dpl/providers/testfairy.rb +101 -0
  90. data/lib/dpl/providers/transifex.rb +72 -0
  91. data/lib/dpl/providers.rb +48 -0
  92. data/lib/dpl/string_ext.rb +23 -0
  93. data/lib/dpl/support/aws_sdk_patch.rb +26 -0
  94. data/lib/dpl/support/gems.rb +73 -0
  95. data/lib/dpl/support/gstore_patch.rb +8 -0
  96. data/lib/dpl/support/version.rb +84 -0
  97. data/lib/dpl/version.rb +5 -0
  98. data/lib/dpl.rb +23 -0
  99. data/status.json +237 -0
  100. metadata +161 -0
@@ -0,0 +1,651 @@
1
+ require 'cl'
2
+ require 'fileutils'
3
+ require 'forwardable'
4
+ require 'shellwords'
5
+ require 'dpl/helper/assets'
6
+ require 'dpl/helper/cmd'
7
+ require 'dpl/helper/config_file'
8
+ require 'dpl/helper/env'
9
+ require 'dpl/helper/interpolate'
10
+ require 'dpl/helper/memoize'
11
+ require 'dpl/helper/squiggle'
12
+ require 'dpl/provider/dsl'
13
+ require 'dpl/provider/examples'
14
+ require 'dpl/version'
15
+
16
+ module Dpl
17
+ # Base class for all concrete providers that `dpl` supports.
18
+ #
19
+ # These are subclasses of `Cl::Cmd` which means they are going to be detected
20
+ # by the first argument passed to `dpl [provider]`, instantiated, and run.
21
+ #
22
+ # Implementors are encouraged to use the provider DSL to declare various
23
+ # features, requirements, and attributes that apply to their provider, to
24
+ # implement any of the following stages (methods) according to their needs
25
+ # and semantics:
26
+ #
27
+ # * init
28
+ # * install
29
+ # * login
30
+ # * setup
31
+ # * validate
32
+ # * prepare
33
+ # * deploy
34
+ # * finish
35
+ #
36
+ # The main logic should sit in the `deploy` stage.
37
+ #
38
+ # If at any time the method `error` is called, or any exception raised the
39
+ # deploy process will be halted, and subsequent stages skipped. However, the
40
+ # stage `finish` will run even if previous stages have raised an error,
41
+ # giving the provider the opportunity to potentially clean up stage.
42
+ #
43
+ # In addition to this the following methods will be called if implemented
44
+ # by the provider:
45
+ #
46
+ # * run_cmd
47
+ # * add_key
48
+ # * remove_key
49
+ #
50
+ # Like the `finish` stage, the method `remove_key` will be called even if
51
+ # previous stages have raised an error.
52
+ #
53
+ # See the respective method's documentation for details on these.
54
+ #
55
+ # The following stages are not meant to be overwritten, but considered
56
+ # internal:
57
+ #
58
+ # * before_install
59
+ # * before_setup
60
+ # * before_prepare
61
+ # * before_finish
62
+ #
63
+ # Dependencies declared as required, such as APT, NPM, or Python are going to
64
+ # be installed as part of the `before_install` stage .
65
+ #
66
+ # Cleanup is run as part of the `before_prepare` stage if the option
67
+ # `--cleanup` was given. This will use `git stash --all` in order to reset
68
+ # the working directory to the committed state, and cleanup any left over
69
+ # artifacts from the build process. Providers can use the DSL method `keep`
70
+ # in order to declare known artifacts (such as CLI tooling installed to the
71
+ # working directory) that needs to be moved out of the way and restored after
72
+ # the cleanup process. (It is recommended to place such artifacts outside of
73
+ # the build working directory though, for example in `~/.dpl`).
74
+ #
75
+ # The method `run_cmd` is called for each command specified using the `--run`
76
+ # option. By default, these command are going to be run as local shell
77
+ # commands, but providers can choose to overwrite this method in order to run
78
+ # the command on a remote machine.
79
+ #
80
+ # @see https://github.com/svenfuchs/cl Cl's documentation for details on how
81
+ # providers (commands) are declared and run.
82
+
83
+ class Provider < Cl::Cmd
84
+ extend Dsl, Forwardable
85
+ include Assets, Env, ConfigFile, FileUtils, Interpolate, Memoize, Squiggle
86
+
87
+ class << self
88
+ def examples
89
+ @examples ||= super || Examples.new(self).cmds
90
+ end
91
+
92
+ def move_files(ctx)
93
+ ctx.move_files(move) if move.any?
94
+ end
95
+
96
+ def unmove_files(ctx)
97
+ ctx.unmove_files(move) if move.any?
98
+ end
99
+
100
+ def install_deps?
101
+ apt? || gem? || npm? || pip?
102
+ end
103
+
104
+ def install_deps(ctx)
105
+ ctx.apts_get(apt) if apt?
106
+ ctx.gems_require(gem) if gem?
107
+ npm.each { |npm| ctx.npm_install *npm } if npm?
108
+ pip.each { |pip| ctx.pip_install *pip } if pip?
109
+ end
110
+
111
+ def validate_runtimes(ctx)
112
+ ctx.validate_runtimes(runtimes) if runtimes.any?
113
+ end
114
+ end
115
+
116
+ # Fold names to display in the build log.
117
+ FOLDS = {
118
+ init: 'Initialize deployment',
119
+ setup: 'Setup deployment',
120
+ validate: 'Validate deployment',
121
+ install: 'Install deployment dependencies',
122
+ login: 'Authenticate deployment',
123
+ prepare: 'Prepare deployment',
124
+ deploy: 'Run deployment',
125
+ finish: 'Finish deployment',
126
+ }
127
+
128
+ # Deployment process stages.
129
+ #
130
+ # In addition to the stages listed here the stage `finish` will be run at
131
+ # the end of the process.
132
+ #
133
+ # Also, the methods `add_key` (called before `setup`), `remove_key` (called
134
+ # before `finish`), and `run_cmd` (called after `deploy`) may be of
135
+ # interest to implementors.
136
+ STAGES = %i(
137
+ init
138
+ install
139
+ login
140
+ setup
141
+ validate
142
+ prepare
143
+ deploy
144
+ )
145
+
146
+ abstract
147
+
148
+ opt '--cleanup', 'Clean up build artifacts from the Git working directory before the deployment', negate: %w(skip)
149
+ opt '--run CMD', 'Commands to execute after the deployment finished successfully', type: :array
150
+ opt '--stage NAME', 'Execute the given stage(s) only', type: :array, internal: true, default: STAGES
151
+ opt '--backtrace', 'Print the backtrace for exceptions', internal: true
152
+ opt '--fold', 'Wrap log output in folds', internal: true
153
+ opt '--edge', internal: true
154
+
155
+ vars *%i(
156
+ git_author_email
157
+ git_author_name
158
+ git_branch
159
+ git_commit_author
160
+ git_commit_msg
161
+ git_sha
162
+ git_tag
163
+ )
164
+
165
+ msgs before_install: 'Installing deployment dependencies',
166
+ before_setup: 'Setting the build environment up for the deployment',
167
+ setup_git_ssh: 'Setting up git-ssh',
168
+ cleanup: 'Cleaning up git repository with `git stash --all`',
169
+ ssh_keygen: 'Generating SSH key',
170
+ setup_git_ua: 'Setting up git HTTP user agent',
171
+ ssh_remote_host: 'SSH remote is %s at port %s',
172
+ ssh_try_connect: 'Waiting for SSH connection ...',
173
+ ssh_connected: 'SSH connection established.',
174
+ ssh_failed: 'Failed to establish SSH connection.'
175
+
176
+ def_delegators :'self.class', :status, :full_name, :install_deps,
177
+ :install_deps?, :keep, :move_files, :unmove_files, :needs?, :runtimes,
178
+ :validate_runtimes, :user_agent
179
+
180
+ def_delegators :ctx, :apt_get, :gem_require, :npm_install, :pip_install,
181
+ :build_dir, :build_number, :encoding, :file_size, :git_author_email,
182
+ :git_author_name, :git_branch, :git_branch, :git_commit_author,
183
+ :git_commit_msg, :git_commit_msg, :git_dirty?, :git_log, :git_log,
184
+ :git_ls_files, :git_ls_remote?, :git_remote_urls, :git_remote_urls,
185
+ :git_rev_parse, :git_rev_parse, :git_sha, :git_tag, :last_err, :last_out,
186
+ :last_out, :logger, :machine_name, :mv, :node_version, :node_version,
187
+ :npm_version, :rendezvous, :rendezvous, :repo_slug, :sleep, :sleep,
188
+ :ssh_keygen, :success?, :test?, :test?, :tmp_dir, :tty?, :which, :which,
189
+ :write_file, :write_netrc
190
+
191
+ attr_reader :repo_name, :key_name
192
+
193
+ def initialize(ctx, *args)
194
+ @repo_name = ctx.repo_name
195
+ @key_name = ctx.machine_name
196
+ super
197
+ end
198
+
199
+ # Runs all stages, all commands provided by the user, as well as the final
200
+ # stage `finish` (which will be run even if an error has been raised during
201
+ # previous stages).
202
+ def run
203
+ stages = stage.select { |stage| run_stage?(stage) }
204
+ stages.each { |stage| run_stage(stage) }
205
+ run_cmds
206
+ rescue Error
207
+ raise
208
+ rescue Exception => e
209
+ raise Error.new("#{e.message} (#{e.class})", backtrace: backtrace? ? e.backtrace : nil) unless test?
210
+ raise
211
+ ensure
212
+ run_stage(:finish, fold: false) if finish?
213
+ end
214
+
215
+ # Whether or not a stage needs to be run
216
+ def run_stage?(stage)
217
+ respond_to?(:"before_#{stage}") || respond_to?(stage)
218
+ end
219
+
220
+ def finish?
221
+ stage.size == STAGES.size
222
+ end
223
+
224
+ # Runs a single stage.
225
+ #
226
+ # For each stage the base class has the opportunity to implement a `before`
227
+ # stage method, in order to apply default behaviour. Provider implementors
228
+ # are asked to not overwrite these methods.
229
+ #
230
+ # Any log output from both the before stage and stage method is going to be
231
+ # folded in the resulting build log.
232
+ def run_stage(stage, opts = {})
233
+ fold(stage, opts) do
234
+ send(:"before_#{stage}") if respond_to?(:"before_#{stage}")
235
+ send(stage) if respond_to?(stage)
236
+ end
237
+ end
238
+
239
+ # Initialize the deployment process.
240
+ #
241
+ # This will:
242
+ #
243
+ # * Displays warning messages about the provider's maturity status, and deprecated
244
+ # options used.
245
+ # * Setup a ~/.dpl working directory
246
+ # * Move files out of the way that have been declared as such
247
+ def before_init
248
+ warn status.msg if status && status.announce?
249
+ deprecations.each { |(key, msg)| ctx.deprecate_opt(key, msg) }
250
+ setup_dpl_dir
251
+ move_files(ctx)
252
+ end
253
+
254
+ # Install APT, NPM, and Python dependencies as declared by the provider.
255
+ def before_install
256
+ validate_runtimes(ctx)
257
+ return unless install_deps?
258
+ info :before_install
259
+ install_deps(ctx)
260
+ end
261
+
262
+ # Sets the build environment up for the deployment.
263
+ #
264
+ # This will:
265
+ #
266
+ # * Setup a ~/.dpl working directory
267
+ # * Create a temporary, per build SSH key, and call `add_key` if the feature `ssh_key` has been declared as required.
268
+ # * Setup git config (email and user name) if the feature `git` has been declared as required.
269
+ # * Either set or unset the environment variable `GIT_HTTP_USER_AGENT` depending if the feature `git_http_user_agent` has been declared as required.
270
+ def before_setup
271
+ info :before_setup
272
+ setup_ssh_key if needs?(:ssh_key)
273
+ setup_git_config if needs?(:git)
274
+ setup_git_http_user_agent
275
+ end
276
+
277
+ # Prepares the deployment by cleaning up the working directory.
278
+ #
279
+ # @see Provider#cleanup
280
+ def before_prepare
281
+ cleanup if cleanup?
282
+ end
283
+
284
+ # Runs each command as given by the user using the `--run` option.
285
+ #
286
+ # For a command that matches `restart` the method `restart` will be called
287
+ # (which can be overwritten by providers, e.g. in order to restart service
288
+ # instances).
289
+ #
290
+ # All other commands will be passed to the method `run_cmd`. By default this
291
+ # will be run as a shell command locally, but providers can choose to
292
+ # overwrite this method in order to run the command on a remote machine.
293
+ def run_cmds
294
+ Array(opts[:run]).each do |cmd|
295
+ cmd.downcase == 'restart' ? restart : run_cmd(cmd)
296
+ end
297
+ end
298
+
299
+ def run_cmd(cmd)
300
+ cmd.downcase == 'restart' ? restart : shell(cmd)
301
+ end
302
+
303
+ # Finalizes the deployment process.
304
+ #
305
+ # This will:
306
+ #
307
+ # * Call the method `remove_key` if implemented by the provider, and if the
308
+ # feature `ssh_key` has been declared as required.
309
+ # * Revert the cleanup process, i.e. restore files moved out of the way
310
+ # during `cleanup`.
311
+ # * Remove the temporary directory `~/.dpl`
312
+ def before_finish
313
+ remove_key if needs?(:ssh_key) && respond_to?(:remove_key)
314
+ uncleanup if cleanup?
315
+ unmove_files(ctx)
316
+ remove_dpl_dir
317
+ end
318
+
319
+ # Resets the current working directory to the commited state.
320
+ #
321
+ # Cleanup will use `git stash --all` in order to reset the working
322
+ # directory to the committed state, and cleanup any left over artifacts
323
+ # from the build process. Providers can use the DSL method `keep` in order
324
+ # to declare known artifacts (such as CLI tooling installed to the working
325
+ # directory) that needs to be moved out of the way and restored after the
326
+ # cleanup process.
327
+ def cleanup
328
+ info :cleanup
329
+ keep.each { |path| shell "mv ./#{path} ~/#{path}", echo: false, assert: false }
330
+ shell 'git stash --all'
331
+ keep.each { |path| shell "mv ~/#{path} ./#{path}", echo: false, assert: false }
332
+ end
333
+
334
+ # Restore files that have been cleaned up.
335
+ def uncleanup
336
+ shell 'git stash pop', assert: false
337
+ end
338
+
339
+ # Creates the directory `~/.dpl` as an internal working directory.
340
+ def setup_dpl_dir
341
+ rm_rf '~/.dpl'
342
+ mkdir_p '~/.dpl'
343
+ chmod 0700, '~/.dpl'
344
+ end
345
+
346
+ # Remove the internal working directory `~/.dpl`.
347
+ def remove_dpl_dir
348
+ rm_rf '~/.dpl'
349
+ end
350
+
351
+ # Creates an SSH key, and sets up git-ssh if needed.
352
+ #
353
+ # This will:
354
+ #
355
+ # * Create a temporary, per build SSH key.
356
+ # * Setup a `git-ssh` executable to use that key.
357
+ # * Call the method `add_key` if implemented by the provider.
358
+ def setup_ssh_key
359
+ ssh_keygen(key_name, '~/.dpl/id_rsa')
360
+ setup_git_ssh('~/.dpl/id_rsa')
361
+ add_key('~/.dpl/id_rsa.pub') if respond_to?(:add_key)
362
+ end
363
+
364
+ # Setup git config
365
+ #
366
+ # This adds the current user's name and email address (as user@localhost)
367
+ # to the git config.
368
+ def setup_git_config
369
+ shell "git config user.email >/dev/null 2>/dev/null || git config user.email `whoami`@localhost", echo: false, assert: false
370
+ shell "git config user.name >/dev/null 2>/dev/null || git config user.name `whoami`", echo: false, assert: false
371
+ end
372
+
373
+ # Sets up `git-ssh` and the GIT_SSH env var
374
+ def setup_git_ssh(key)
375
+ info :setup_git_ssh
376
+ path, conf = '~/.dpl/git-ssh', asset(:dpl, :git_ssh).read % expand(key)
377
+ open(path, 'w+') { |file| file.write(conf) }
378
+ chmod(0740, path)
379
+ ENV['GIT_SSH'] = expand(path)
380
+ end
381
+
382
+ # Generates an SSH key.
383
+ def ssh_keygen(key, path)
384
+ info :ssh_keygen
385
+ ctx.ssh_keygen(key, expand(path))
386
+ end
387
+
388
+ # Sets or unsets the environment variable `GIT_HTTP_USER_AGENT`.
389
+ def setup_git_http_user_agent
390
+ return ENV.delete('GIT_HTTP_USER_AGENT') unless needs?(:git_http_user_agent)
391
+ info :setup_git_ua
392
+ ENV['GIT_HTTP_USER_AGENT'] = user_agent(git: `git --version`[/[\d\.]+/])
393
+ end
394
+
395
+ # Waits for SSH access on the given host and port.
396
+ #
397
+ # This will try to connect to the given SSH host and port, and keep
398
+ # retrying 30 times, waiting a second inbetween retries.
399
+ def wait_for_ssh_access(host, port)
400
+ info :ssh_remote_host, host, port
401
+ 1.upto(20) { try_ssh_access(host, port) && break || sleep(3) }
402
+ success? ? info(:ssh_connected) : error(:ssh_failed)
403
+ end
404
+
405
+ # Tries to connect to the given SSH host and port.
406
+ def try_ssh_access(host, port)
407
+ info :ssh_try_connect
408
+ shell "#{ENV['GIT_SSH']} #{host} -p #{port} 2>&1 | grep -c 'PTY allocation request failed' > /dev/null", echo: false, assert: false
409
+ end
410
+
411
+ # Creates a log fold.
412
+ #
413
+ # Folds any log output from the given block into a fold with the given
414
+ # name.
415
+ def fold(name, opts = {}, &block)
416
+ return yield unless fold?(name, opts)
417
+ title = FOLDS[name] || "deploy.#{name}"
418
+ ctx.fold(title, &block)
419
+ end
420
+
421
+ # Checks if the given stage needs to be folded.
422
+ #
423
+ # Depends on the option `--fold`, also omits folds for the init and finish
424
+ # stages. Can be overwritten by passing `fold: false`.
425
+ def fold?(name, opts = {})
426
+ !opts[:fold].is_a?(FalseClass) && super() && !%i(init).include?(name)
427
+ end
428
+
429
+ # Runs a script as a shell command.
430
+ #
431
+ # Scripts can be stored as separate files (assets) in the directory
432
+ # `lib/dpl/assets/[provider]`.
433
+ #
434
+ # This is meant for large shell commands that would be hard to read if
435
+ # embedded in Ruby code. Storing them as separate files helps with proper
436
+ # syntax highlighting etc in editors, and allows to execute them for
437
+ # testing purposes.
438
+ #
439
+ # Scripts can have interpolation variables. See Dpl::Interpolate for
440
+ # details on interpolating variables.
441
+ #
442
+ # See Ctx::Bash#shell for details on the options accepted.
443
+ def script(name, opts = {})
444
+ opts[:assert] = name if opts[:assert].is_a?(TrueClass)
445
+ shell(asset(name).read, opts.merge(echo: false))
446
+ end
447
+
448
+ # Runs a single shell command.
449
+ #
450
+ # Shell commands can have interpolation variables. See Dpl::Interpolate for
451
+ # details on interpolating variables.
452
+ #
453
+ # See Ctx::Bash#shell for details on the options accepted.
454
+ def shell(cmd, *args)
455
+ opts = args.last.is_a?(Hash) ? args.pop : {}
456
+ cmd = Cmd.new(self, cmd, opts)
457
+ ctx.shell(cmd)
458
+ end
459
+
460
+ # @!method print
461
+ # Prints a partial message to stdout
462
+ #
463
+ # This method does not append a newline character to the given message,
464
+ # which usually is not the desired behaviour. The method is intended to be
465
+ # used if an initial, partial message is supposed to be printed, which will
466
+ # be completed later (using the method `info`).
467
+ #
468
+ # For example:
469
+ #
470
+ # print 'Starting a long running task ...'
471
+ # run_long_running_task
472
+ # info 'done.'
473
+ #
474
+ # Messages support interpolation variables. See Dpl::Interpolate for
475
+ # details on interpolating variables.
476
+
477
+ # @!method info
478
+ # Outputs an info message to stdout
479
+ #
480
+ # This method is intended to be used for default, info level messages that
481
+ # are supposed to show up in the build log.
482
+ #
483
+ # @!method warn
484
+ # Outputs an warning message to stderr
485
+ #
486
+ # This method is intended to be used for warning messages that are supposed
487
+ # to show up in the build log, but do not qualify as errors that would
488
+ # abort the deployment process. The warning will be highlighted as red
489
+ # text. Use sparingly.
490
+ #
491
+ # Messages support interpolation variables. See Dpl::Interpolate for
492
+ # details on interpolating variables.
493
+
494
+ # @!method error
495
+ # Outputs an error message to stderr, and raises an error, halting the
496
+ # deployment process.
497
+ #
498
+ # This method is intended to be used for all error conditions that require
499
+ # the deployment process to be aborted.
500
+ #
501
+ # Messages support interpolation variables. See Dpl::Interpolate for
502
+ # details on interpolating variables.
503
+ %i(print info warn error).each do |level|
504
+ define_method(level) do |msg, *args|
505
+ msg = interpolate(self.msg(msg), args) if msg.is_a?(Symbol)
506
+ ctx.send(level, msg)
507
+ end
508
+ end
509
+
510
+ # @!method cmd
511
+ # Looks up a shell command from the commands declared by the provider
512
+ # (using the class level DSL).
513
+ #
514
+ # Not usually useful to be used by provider implementors directly. Use the
515
+ # method `shell` in order to execute shell commands.
516
+
517
+ # @!method err
518
+ # Looks up an error message from the error messages declared by the
519
+ # provider (using the class level DSL), as needed by the option `assert`
520
+ # when passed to the method `shell`.
521
+
522
+ # @!method msg
523
+ # Looks up a message from the messages declared by the provider (using the
524
+ # class level DSL).
525
+ #
526
+ # For example, a message declared on the class body like so:
527
+ #
528
+ # ```ruby
529
+ # msgs commit_msg: 'Commit build artifacts on build %{build_number}'
530
+ # ```
531
+ #
532
+ # could be used by the implementation like so:
533
+ #
534
+ # ```ruby
535
+ # def commit_msg
536
+ # interpolate(msg(:commit_msg))
537
+ # end
538
+ # ```
539
+ #
540
+ # Note that the the method `interpolate` needs to be used in order to
541
+ # interpolate variables used in a message (if any).
542
+ %i(cmd err msg str).each do |name|
543
+ define_method(name) do |*keys|
544
+ key = keys.detect { |key| key.is_a?(Symbol) }
545
+ self.class.send(:"#{name}s")[key] if key
546
+ end
547
+ end
548
+
549
+ # Escapes the given string so it can be safely used in Bash.
550
+ def escape(str)
551
+ Shellwords.escape(str)
552
+ end
553
+
554
+ # Double quotes the given string.
555
+ def quote(str)
556
+ %("#{str.to_s.gsub('"', '\"')}")
557
+ end
558
+
559
+ # Outdents the given string.
560
+ #
561
+ # @see Dpl::Squiggle
562
+ def sq(str)
563
+ self.class.sq(str)
564
+ end
565
+
566
+ # Generate shell option strings to be passed to a shell command.
567
+ #
568
+ # This generates strings like `--key="value"` for the option keys passed.
569
+ # These keys are supposed to correspond to methods on the provider
570
+ # instance, which will be called in order to determine the option value.
571
+ #
572
+ # If the returned value is an array then the option will be repeated
573
+ # multiple times. If it is a String then it will be double quoted.
574
+ # Otherwise it is assumed to be a flag that does not have a value.
575
+ #
576
+ # @option prefix [String] Use this to set a single dash as an option prefix (defaults to two dashes).
577
+ # @option dashed [Boolean] Use this to dasherize the option key (rather than underscore it, defaults to underscore).
578
+ def opts_for(keys, opts = {})
579
+ strs = Array(keys).map { |key| opt_for(key, opts) if send(:"#{key}?") }.compact
580
+ strs.join(' ') if strs.any?
581
+ end
582
+
583
+ def opt_for(key, opts = {})
584
+ case value = send(key)
585
+ when String then "#{opt_key(key, opts)}=#{value.inspect}"
586
+ when Array then value.map { |value| "#{opt_key(key, opts)}=#{value.inspect}" }
587
+ else opt_key(key, opts)
588
+ end
589
+ end
590
+
591
+ def opt_key(key, opts)
592
+ "#{opts[:prefix] || '--'}#{opts[:dashed] ? key.to_s.gsub('_', '-') : key}"
593
+ end
594
+
595
+ # Compacts the given hash by rejecting nil values.
596
+ def compact(hash)
597
+ hash.reject { |_, value| value.nil? }
598
+ end
599
+
600
+ # Returns a new hash with the given keys selected from the given hash.
601
+ def only(hash, *keys)
602
+ hash.select { |key, _| keys.include?(key) }
603
+ end
604
+
605
+ # Deep symbolizes the given hash's keys
606
+ def symbolize(obj)
607
+ case obj
608
+ when Hash
609
+ obj.map { |key, obj| [key.to_sym, symbolize(obj)] }.to_h
610
+ when Array
611
+ obj.map { |obj| symbolize(obj) }
612
+ else
613
+ obj
614
+ end
615
+ end
616
+
617
+ def file?(path)
618
+ File.file?(expand(path))
619
+ end
620
+
621
+ def mkdir_p(path)
622
+ FileUtils.mkdir_p(expand(path))
623
+ end
624
+
625
+ def chmod(perm, path)
626
+ super(perm, expand(path))
627
+ end
628
+
629
+ def mv(src, dest)
630
+ super(expand(src), expand(dest))
631
+ end
632
+
633
+ def rm_rf(path)
634
+ super(expand(path))
635
+ end
636
+
637
+ def open(path, *args, &block)
638
+ File.open(expand(path), *args, &block)
639
+ end
640
+
641
+ def read(path)
642
+ File.read(expand(path))
643
+ end
644
+
645
+ def expand(*args)
646
+ File.expand_path(*args)
647
+ end
648
+ end
649
+ end
650
+
651
+ require 'dpl/providers'