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,549 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require 'cl'
5
+ require 'fileutils'
6
+ require 'logger'
7
+ require 'open3'
8
+ require 'tmpdir'
9
+ require 'securerandom'
10
+ require 'dpl/support/version'
11
+
12
+ module Dpl
13
+ module Ctx
14
+ class Bash < Cl::Ctx
15
+ include FileUtils
16
+
17
+ attr_accessor :folds, :stdout, :stderr, :last_out, :last_err
18
+
19
+ def initialize(stdout = $stdout, stderr = $stderr)
20
+ @stdout = stdout
21
+ @stderr = stderr
22
+ @folds = 0
23
+ super('dpl', abort: false)
24
+ end
25
+
26
+ # Folds any log output from the given block
27
+ #
28
+ # Starts a log fold with the given fold message, calls the block, and
29
+ # closes the fold.
30
+ #
31
+ # @param msg [String] the message that will appear on the log fold
32
+ def fold(msg)
33
+ self.folds += 1
34
+ print "travis_fold:start:dpl.#{folds}\r\e[K"
35
+ time do
36
+ info "\e[33m#{msg}\e[0m"
37
+ yield
38
+ end
39
+ ensure
40
+ print "\ntravis_fold:end:dpl.#{folds}\r\e[K"
41
+ end
42
+
43
+ # Times the given block
44
+ #
45
+ # Starts a travis time log tag, calls the block, and closes the tag,
46
+ # including timing information. This makes a timing badge appear on
47
+ # the surrounding log fold.
48
+ def time
49
+ id = SecureRandom.hex[0, 8]
50
+ start = Time.now.to_i * (10**9)
51
+ print "travis_time:start:#{id}\r\e[K"
52
+ yield
53
+ ensure
54
+ finish = Time.now.to_i * (10**9)
55
+ duration = finish - start
56
+ print "\ntravis_time:end:#{id}:start=#{start},finish=#{finish},duration=#{duration}\r\e[K"
57
+ end
58
+
59
+ # Outputs a deprecation warning for a given deprecated option key to stderr.
60
+ #
61
+ # @param key [Symbol] the deprecated option key
62
+ # @param msg [String or Symbol] the deprecation message. if given a Symbol this will be wrapped into the string "Please use #{symbol}".
63
+ def deprecate_opt(key, msg)
64
+ msg = "please use #{msg}" if msg.is_a?(Symbol)
65
+ warn "Deprecated option #{key} used (#{msg})."
66
+ end
67
+
68
+ # Outputs an info level message to stdout.
69
+ def info(*msgs)
70
+ stdout.puts(*msgs)
71
+ end
72
+
73
+ # Prints an info level message to stdout.
74
+ #
75
+ # This method does not append a newline character to the given message,
76
+ # which usually is not the desired behaviour. The method is intended to
77
+ # be used if an initial, partial message is supposed to be printed, which
78
+ # will be completed later (using the method `info`).
79
+ #
80
+ # For example:
81
+ #
82
+ # print 'Starting a long running task ...'
83
+ # run_long_running_task
84
+ # info 'done.'
85
+ def print(chars)
86
+ stdout.print(chars)
87
+ end
88
+
89
+ # Outputs an warning message to stderr
90
+ #
91
+ # This method is intended to be used for warning messages that are
92
+ # supposed to show up in the build log, but do not qualify as errors that
93
+ # would abort the deployment process. The warning will be highlighted as
94
+ # yellow text. Use sparingly.
95
+ def warn(*msgs)
96
+ msgs = msgs.join("\n").lines
97
+ msgs.each { |msg| stderr.puts("\e[33;1m#{msg}\e[0m") }
98
+ end
99
+
100
+ # Raises an exception, halting the deployment process.
101
+ #
102
+ # The calling executable `bin/dpl` will catch the exception, and abort
103
+ # the ruby process with the given error message.
104
+ #
105
+ # This method is intended to be used for all error conditions that
106
+ # require the deployment process to be aborted.
107
+ def error(message)
108
+ raise Error, message
109
+ end
110
+
111
+ # Returns a logger
112
+ #
113
+ # Returns a logger instance, with the given log level set. This can be
114
+ # used to pass to clients that accept a Ruby logger, such as Faraday,
115
+ # for debugging purposes.
116
+ #
117
+ # Use with care.
118
+ #
119
+ # @param level [Symbol] the Ruby logger log level
120
+ def logger(level = :info)
121
+ logger = Logger.new(stderr)
122
+ logger.level = Logger.const_get(level.to_s.upcase)
123
+ logger
124
+ end
125
+
126
+ def validate_runtimes(runtimes)
127
+ failed = runtimes.reject(&method(:validate_runtime))
128
+ failed = failed.map { |name, versions| "#{name} (#{versions.join(', ')})" }
129
+ error "Failed validating runtimes: #{failed.join(', ')}" if failed.any?
130
+ end
131
+
132
+ def validate_runtime(args)
133
+ name, required = *args
134
+ info "Validating required runtime version: #{name} (#{required.join(', ')})"
135
+ version = name == :node_js ? node_version : python_version
136
+ required.all? { |required| Version.new(version).satisfies?(required) }
137
+ end
138
+
139
+ def apts_get(packages)
140
+ packages = packages.reject { |name, cmd = name| which(cmd || name) }
141
+ return unless packages.any?
142
+
143
+ apt_update
144
+ packages.each { |package, cmd| apt_get(package, cmd || package, update: false) }
145
+ end
146
+
147
+ # Installs an APT package
148
+ #
149
+ # Installs the APT package with the given name, unless the command is already
150
+ # available (as determined by `which [cmd]`.
151
+ #
152
+ # @param package [String] the package name
153
+ # @param cmd [String] an executable installed by the package, defaults to the package name
154
+ def apt_get(package, cmd = package, opts = {})
155
+ return if which(cmd)
156
+
157
+ apt_update unless opts[:update].is_a?(FalseClass)
158
+ shell "sudo apt-get -qq install #{package}", retry: true
159
+ end
160
+
161
+ def apt_update
162
+ shell 'sudo apt-get update', retry: true
163
+ end
164
+
165
+ # Requires source files from Ruby gems, installing them on demand if required
166
+ #
167
+ # Installs the Ruby gems with the given version, if not already installed, and
168
+ # requires the specified source files from that gem.
169
+ #
170
+ # This happens using the bundler/inline API.
171
+ #
172
+ # @param gems [Array<String, String, Hash>] Array of gem requirements: gem name, version, and options (`require`: A single path or a list of paths to source files to require from this Ruby gem)
173
+ #
174
+ # @see https://bundler.io/v2.0/guides/bundler_in_a_single_file_ruby_script.html
175
+ def gems_require(gems)
176
+ # A local Gemfile.lock might interfer with bundler/inline, even though
177
+ # it should not. Switching to a temporary dir fixes this.
178
+ Dir.chdir(tmp_dir) do
179
+ require 'bundler/inline'
180
+ info "Installing gem dependencies: #{gems.map { |name, version, _| "#{name} #{"(#{version})" if version}".strip }.join(', ')}"
181
+ env = ENV.to_h
182
+ # Bundler.reset!
183
+ # Gem.loaded_specs.clear
184
+ gemfile do
185
+ source 'https://rubygems.org'
186
+ gems.each { |g| gem(*g) }
187
+ end
188
+ # https://github.com/bundler/bundler/issues/7181
189
+ ENV.replace(env)
190
+ end
191
+ end
192
+
193
+ # Installs an NPM package
194
+ #
195
+ # Installs the NPM package with the given name, unless the command is already
196
+ # available (as determined by `which [cmd]`.
197
+ #
198
+ # @param package [String] the package name
199
+ # @param cmd [String] an executable installed by the package, defaults to the package name
200
+ def npm_install(package, cmd = package)
201
+ shell "npm install -g #{package}", retry: true unless which(cmd)
202
+ end
203
+
204
+ # Installs a Python package
205
+ #
206
+ # Installs the Python package with the given name. A previously installed
207
+ # package is uninstalled before that, but only if `version` was given.
208
+ #
209
+ # @param package [String] Package name (required).
210
+ # @param cmd [String] Executable command installed by that package (optional, defaults to the package name).
211
+ # @param version [String] Package version (optional).
212
+ def pip_install(package, cmd = package, version = nil)
213
+ ENV['VIRTUAL_ENV'] = File.expand_path('~/dpl_venv')
214
+ ENV['PATH'] = File.expand_path("~/dpl_venv/bin:#{ENV['PATH']}")
215
+ shell 'virtualenv --no-site-packages ~/dpl_venv', echo: true
216
+ shell 'pip install urllib3[secure]'
217
+ cmd = "pip install #{package}"
218
+ cmd << pip_version(version) if version
219
+ shell cmd, retry: true
220
+ end
221
+
222
+ def pip_version(version)
223
+ version =~ /^\d+/ ? "==#{version}" : version
224
+ end
225
+
226
+ # Generates an SSH key
227
+ #
228
+ # @param name [String] the key name
229
+ # @param file [String] path to the key file
230
+ def ssh_keygen(name, file)
231
+ shell %(ssh-keygen -t rsa -N "" -C #{name} -f #{file})
232
+ end
233
+
234
+ # Runs a single shell command
235
+ #
236
+ # This the is the central point of executing any shell commands. It allows two
237
+ # strategies for running commands in subprocesses:
238
+ #
239
+ # * Using [Kernel#system](https://ruby-doc.org/core-2.6.3/Kernel.html#method-i-system)
240
+ # which is the default strategy, and should be used when possible. The stdout
241
+ # and stderr streams will not be captured, but streamed directly to the parent
242
+ # process (so any output on these streams appears in the build log as soon as
243
+ # possible).
244
+ #
245
+ # * Using [Open3.capture3](https://ruby-doc.org/stdlib-2.6.3/libdoc/open3/rdoc/Open3.html#method-c-capture3)
246
+ # which captures both stdout and stderr, and does not automatically output it
247
+ # to the build log. Implementors can choose to display it after the shell command
248
+ # has completed, using the `%{out}` and `%{err}` interpolation variables. Use
249
+ # sparingly.
250
+ #
251
+ # The method accepts the following options:
252
+ #
253
+ # @param cmd [String] the shell command to execute
254
+ # @param opts [Hash] options
255
+ #
256
+ # @option opts [Boolean] :echo output the command to stdout before running it
257
+ # @option opts [Boolean] :silence silence all log output by redirecting stdout and stderr to `/dev/null`
258
+ # @option opts [Boolean] :capture use `Open3.capture3` to capture stdout and stderr
259
+ # @option opts [String] :python wrap the command into Bash code that enforces the given Python version to be used
260
+ # @option opts [String] :retry retries the command 2 more times if it fails
261
+ # @option opts [String] :info message to output to stdout if the command has exited with the exit code 0 (supports the interpolation variable `${out}` for stdout in case it was captured.
262
+ # @option opts [String] :assert error message to be raised if the command has exited with a non-zero exit code (supports the interpolation variable `${out}` for stdout in case it was captured.
263
+ #
264
+ # @return [Boolean] whether or not the command was successful (has exited with the exit code 0)
265
+ def shell(cmd, opts = {})
266
+ cmd = Cmd.new(nil, cmd, opts) if cmd.is_a?(String)
267
+ info cmd.msg if cmd.msg?
268
+ info cmd.echo if cmd.echo?
269
+
270
+ @last_out, @last_err, @last_status = retrying(cmd.retry ? 2 : 0) do
271
+ send(cmd.capture? ? :open3 : :system, cmd.cmd, cmd.opts)
272
+ end
273
+
274
+ info format(cmd.success, out: last_out) if success? && cmd.success?
275
+ error format(cmd.error, err: last_err) if failed? && cmd.assert?
276
+
277
+ success? && cmd.capture? ? last_out.chomp : @last_status
278
+ end
279
+
280
+ def retrying(max, tries = 0, status = false)
281
+ loop do
282
+ tries += 1
283
+ out, err, status = yield
284
+ return [out, err, status] if status || tries > max
285
+ end
286
+ end
287
+
288
+ # Runs a shell command and captures stdout, stderr, and the exit status
289
+ #
290
+ # Runs the given command using `Open3.capture3`, which will capture the
291
+ # stdout and stderr streams, as well as the exit status. I.e. this will
292
+ # *not* stream log output in real time, but capture the output, and allow
293
+ # implementors to display it later (using the `%{out}` and `%{err}`
294
+ # interpolation variables.
295
+ #
296
+ # Use sparingly.
297
+ #
298
+ # @option chdir [String] directory temporarily to change to before running the command
299
+ def open3(cmd, opts)
300
+ opts = [opts[:chdir] ? only(opts, :chdir) : nil].compact
301
+ out, err, status = Open3.capture3(cmd, *opts)
302
+ [out, err, status.success?]
303
+ end
304
+
305
+ # Runs a shell command, streaming any stdout or stderr output, and
306
+ # returning the exit status
307
+ #
308
+ # This is the default method for executing shell commands. The stdout and
309
+ # stderr will not be captured, but streamed directly to the parent process.
310
+ #
311
+ # @option chdir [String] directory temporarily to change to before running the command
312
+ def system(cmd, opts = {})
313
+ opts = [opts[:chdir] ? only(opts, :chdir) : nil].compact
314
+ Kernel.system(cmd, *opts)
315
+ ['', '', last_process_status]
316
+ end
317
+
318
+ # Whether or not the last executed shell command was successful.
319
+ def success?
320
+ !!@last_status
321
+ end
322
+
323
+ # Whether or not the last executed shell command has failed.
324
+ def failed?
325
+ !success?
326
+ end
327
+
328
+ # Returns the last child process' exit status
329
+ #
330
+ # Internal, and not to be used by implementors. $? is a read-only
331
+ # variable, so we use a method that we can stub during tests.
332
+ def last_process_status
333
+ $CHILD_STATUS.success?
334
+ end
335
+
336
+ # Whether or not the current Ruby process runs with superuser priviledges.
337
+ def sudo?
338
+ Process::UID.eid.zero?
339
+ end
340
+
341
+ # Returns current repository name
342
+ #
343
+ # Uses the environment variable `TRAVIS_REPO_SLUG` if present, or the
344
+ # current directory's base name.
345
+ #
346
+ # Note that this might return an unexpected string outside of the context
347
+ # of Travis CI build environments if the method is called at a time when
348
+ # the current working directory has changed.
349
+ def repo_name
350
+ ENV['TRAVIS_REPO_SLUG'] ? ENV['TRAVIS_REPO_SLUG'].split('/').last : File.basename(Dir.pwd)
351
+ end
352
+
353
+ # Returns current repository slug
354
+ #
355
+ # Uses the environment variable `TRAVIS_REPO_SLUG` if present, or the
356
+ # last two segmens of the current working directory's path.
357
+ #
358
+ # Note that this might return an unexpected string outside of the context
359
+ # of Travis CI build environments if the method is called at a time when
360
+ # the current working directory has changed.
361
+ def repo_slug
362
+ ENV['TRAVIS_REPO_SLUG'] || Dir.pwd.split('/')[-2, 2].join('/')
363
+ end
364
+
365
+ # Returns the current build directory
366
+ #
367
+ # Uses the environment variable `TRAVIS_REPO_SLUG` if present, and
368
+ # defaults to `.` otherwise.
369
+ #
370
+ # Note that this might return an unexpected string outside of the context
371
+ # of Travis CI build environments if the method is called at a time when
372
+ # the current working directory has changed.
373
+ def build_dir
374
+ ENV['TRAVIS_BUILD_DIR'] || '.'
375
+ end
376
+
377
+ # Returns the current build number
378
+ #
379
+ # Returns the value of the environment variable `TRAVIS_BUILD_NUMBER` if
380
+ # present.
381
+ def build_number
382
+ ENV['TRAVIS_BUILD_NUMBER'] || raise('TRAVIS_BUILD_NUMBER not set')
383
+ end
384
+
385
+ # Returns the encoding of the given file, as determined by `file`.
386
+ def encoding(path)
387
+ case `file '#{path}'`
388
+ when /gzip compressed/
389
+ 'gzip'
390
+ when /compress'd/
391
+ 'compress'
392
+ when /text/
393
+ 'text'
394
+ when /data/
395
+ # shrugs?
396
+ end
397
+ end
398
+
399
+ # Returns the current branch name
400
+ def git_branch
401
+ ENV['TRAVIS_BRANCH'] || git_rev_parse('HEAD')
402
+ end
403
+
404
+ # Returns the message of the commit `git_sha`.
405
+ def git_commit_msg
406
+ `git log #{git_sha} -n 1 --pretty=%B`.chomp
407
+ end
408
+
409
+ # Returns the committer name of the commit `git_sha`.
410
+ def git_author_name
411
+ `git log #{git_sha} -n 1 --pretty=%an`.chomp
412
+ end
413
+
414
+ # Returns the comitter email of the commit `git_sha`.
415
+ def git_author_email
416
+ `git log #{git_sha} -n 1 --pretty=%ae`.chomp
417
+ end
418
+
419
+ # Whether or not the git working directory is dirty or has new or deleted files
420
+ def git_dirty?
421
+ !`git status --short`.chomp.empty?
422
+ end
423
+
424
+ # Returns the output of `git log`, using the given args.
425
+ def git_log(args)
426
+ `git log #{args}`.chomp
427
+ end
428
+
429
+ # Returns the Git log, separated by NULs
430
+ #
431
+ # Returns the output of `git ls-files -z`, which separates log entries by
432
+ # NULs, rather than newline characters.
433
+ def git_ls_files
434
+ `git ls-files -z`.split("\x0")
435
+ end
436
+
437
+ # Returns true if the given ref exists remotely
438
+ def git_ls_remote?(url, ref)
439
+ Kernel.system("git ls-remote --exit-code #{url} #{ref} > /dev/null 2>&1")
440
+ end
441
+
442
+ # Returns known Git remote URLs
443
+ def git_remote_urls
444
+ `git remote -v`.scan(/\t[^\s]+\s/).map(&:strip).uniq
445
+ end
446
+
447
+ # Returns the sha for the given Git ref
448
+ def git_rev_parse(ref)
449
+ `git rev-parse #{ref}`.strip
450
+ end
451
+
452
+ # Returns the latest tag name, if any
453
+ def git_tag
454
+ `git describe --tags --exact-match 2>/dev/null`.chomp
455
+ end
456
+
457
+ # Returns the current commit sha
458
+ def git_sha
459
+ ENV['TRAVIS_COMMIT'] || `git rev-parse HEAD`.chomp
460
+ end
461
+
462
+ # Returns the local machine's hostname
463
+ def machine_name
464
+ `hostname`.strip
465
+ end
466
+
467
+ # Returns the current Node.js version
468
+ def node_version
469
+ `node -v`.sub(/^v/, '').chomp
470
+ end
471
+
472
+ # Returns the current NPM version
473
+ def npm_version
474
+ `npm --version`
475
+ end
476
+
477
+ # Returns the current Node.js version
478
+ def python_version
479
+ `python --version 2>&1`.sub(/^Python /, '').chomp
480
+ end
481
+
482
+ # Returns true or false depending if the given command can be found
483
+ def which(cmd)
484
+ !`which #{cmd}`.chomp.empty? if cmd
485
+ end
486
+
487
+ # Returns a unique temporary directory name
488
+ def tmp_dir
489
+ @tmp_dir ||= Dir.mktmpdir
490
+ end
491
+
492
+ # Returns the size of the given file path
493
+ def file_size(path)
494
+ File.size(path)
495
+ end
496
+
497
+ def move_files(paths)
498
+ paths.each do |path|
499
+ target = "#{tmp_dir}/#{File.basename(path)}"
500
+ mv(path, target) if File.exist?(path)
501
+ end
502
+ end
503
+
504
+ def unmove_files(paths)
505
+ paths.each do |path|
506
+ source = "#{tmp_dir}/#{File.basename(path)}"
507
+ mv(source, path) if File.exist?(source)
508
+ end
509
+ end
510
+
511
+ def mv(src, dest)
512
+ Kernel.system("sudo mv #{src} #{dest} 2> /dev/null")
513
+ end
514
+
515
+ # Writes the given content to the given file path
516
+ def write_file(path, content, chmod = nil)
517
+ path = File.expand_path(path)
518
+ FileUtils.mkdir_p(File.dirname(path))
519
+ File.open(path, 'w+') { |f| f.write(content) }
520
+ FileUtils.chmod(chmod, path) if chmod
521
+ end
522
+
523
+ # Writes the given machine, login, and password to ~/.netrc
524
+ def write_netrc(machine, login, password)
525
+ require 'netrc'
526
+ netrc = Netrc.read
527
+ netrc[machine] = [login, password]
528
+ netrc.save
529
+ end
530
+
531
+ def sleep(sec)
532
+ Kernel.sleep(sec)
533
+ end
534
+
535
+ def tty?
536
+ $stdout.isatty
537
+ end
538
+
539
+ # Returns a copy of the given hash, reduced to the given keys
540
+ def only(hash, *keys)
541
+ hash.select { |key, _| keys.include?(key) }.to_h
542
+ end
543
+
544
+ def test?
545
+ false
546
+ end
547
+ end
548
+ end
549
+ end