travis_dpl_test 2.0.3.beta.4.ror

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.
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'