dpl 1.10.17.travis.6637.6 → 2.0.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +74 -0
- data/CONTRIBUTING.md +392 -0
- data/Gemfile +17 -3
- data/Gemfile.lock +373 -0
- data/LICENSE +16 -19
- data/NOTES.md +275 -0
- data/README.md +1977 -707
- data/Rakefile +2 -2
- data/bin/dpl +7 -3
- data/lib/dpl.rb +20 -0
- data/lib/dpl/assets/atlas/install +19 -0
- data/lib/dpl/assets/dpl/README.erb.md +133 -0
- data/lib/dpl/assets/dpl/git_ssh +2 -0
- data/lib/dpl/assets/git/detect_private_key +8 -0
- data/lib/dpl/assets/hephy/filter_log +3 -0
- data/lib/dpl/assets/pypi/install +4 -0
- data/lib/dpl/assets/scalingo/install +6 -0
- data/lib/dpl/cli.rb +36 -48
- data/lib/dpl/ctx.rb +2 -0
- data/lib/dpl/ctx/bash.rb +543 -0
- data/lib/dpl/ctx/test.rb +242 -0
- data/lib/dpl/helper/assets.rb +36 -0
- data/lib/dpl/helper/cmd.rb +167 -0
- data/lib/dpl/helper/config_file.rb +47 -0
- data/lib/dpl/helper/env.rb +39 -0
- data/lib/dpl/helper/interpolate.rb +126 -0
- data/lib/dpl/helper/memoize.rb +20 -0
- data/lib/dpl/helper/squiggle.rb +22 -0
- data/lib/dpl/helper/zip.rb +69 -0
- data/lib/dpl/provider.rb +562 -234
- data/lib/dpl/provider/dsl.rb +369 -0
- data/lib/dpl/provider/examples.rb +128 -0
- data/lib/dpl/provider/status.rb +59 -0
- data/lib/dpl/providers.rb +40 -0
- data/lib/dpl/providers/anynines.rb +65 -0
- data/lib/dpl/providers/atlas.rb +49 -0
- data/lib/dpl/providers/azure_web_apps.rb +59 -0
- data/lib/dpl/providers/bintray.rb +313 -0
- data/lib/dpl/providers/bluemixcloudfoundry.rb +92 -0
- data/lib/dpl/providers/boxfuse.rb +48 -0
- data/lib/dpl/providers/cargo.rb +19 -0
- data/lib/dpl/providers/chef_supermarket.rb +128 -0
- data/lib/dpl/providers/cloud66.rb +40 -0
- data/lib/dpl/providers/cloudfiles.rb +56 -0
- data/lib/dpl/providers/cloudfoundry.rb +81 -0
- data/lib/dpl/providers/codedeploy.rb +179 -0
- data/lib/dpl/providers/datica.rb +60 -0
- data/lib/dpl/providers/elasticbeanstalk.rb +195 -0
- data/lib/dpl/providers/engineyard.rb +107 -0
- data/lib/dpl/providers/firebase.rb +41 -0
- data/lib/dpl/providers/gae.rb +74 -0
- data/lib/dpl/providers/gcs.rb +105 -0
- data/lib/dpl/providers/hackage.rb +47 -0
- data/lib/dpl/providers/hephy.rb +101 -0
- data/lib/dpl/providers/heroku.rb +111 -0
- data/lib/dpl/providers/heroku/api.rb +119 -0
- data/lib/dpl/providers/heroku/git.rb +50 -0
- data/lib/dpl/providers/lambda.rb +202 -0
- data/lib/dpl/providers/launchpad.rb +74 -0
- data/lib/dpl/providers/netlify.rb +30 -0
- data/lib/dpl/providers/npm.rb +88 -0
- data/lib/dpl/providers/openshift.rb +46 -0
- data/lib/dpl/providers/opsworks.rb +142 -0
- data/lib/dpl/providers/packagecloud.rb +190 -0
- data/lib/dpl/providers/pages.rb +17 -0
- data/lib/dpl/providers/pages/api.rb +102 -0
- data/lib/dpl/providers/pages/git.rb +251 -0
- data/lib/dpl/providers/puppetforge.rb +44 -0
- data/lib/dpl/providers/pypi.rb +120 -0
- data/lib/dpl/providers/releases.rb +214 -0
- data/lib/dpl/providers/rubygems.rb +89 -0
- data/lib/dpl/providers/s3.rb +243 -0
- data/lib/dpl/providers/scalingo.rb +63 -0
- data/lib/dpl/providers/script.rb +28 -0
- data/lib/dpl/providers/snap.rb +59 -0
- data/lib/dpl/providers/surge.rb +55 -0
- data/lib/dpl/providers/testfairy.rb +93 -0
- data/lib/dpl/providers/transifex.rb +66 -0
- data/lib/dpl/support/aws_sdk_patch.rb +23 -0
- data/lib/dpl/support/gems.rb +69 -0
- data/lib/dpl/support/gstore_patch.rb +6 -0
- data/lib/dpl/support/version.rb +83 -0
- data/lib/dpl/version.rb +2 -2
- metadata +98 -169
- data/.coveralls.yml +0 -1
- data/.github/CONTRIBUTING.md +0 -173
- data/.github/stale.yml +0 -53
- data/.gitignore +0 -13
- data/.rspec +0 -2
- data/.travis.yml +0 -56
- data/dpl-anynines.gemspec +0 -3
- data/dpl-atlas.gemspec +0 -3
- data/dpl-azure_webapps.gemspec +0 -3
- data/dpl-bintray.gemspec +0 -3
- data/dpl-bitballoon.gemspec +0 -3
- data/dpl-bluemix_cloud_foundry.gemspec +0 -3
- data/dpl-boxfuse.gemspec +0 -3
- data/dpl-cargo.gemspec +0 -3
- data/dpl-catalyze.gemspec +0 -3
- data/dpl-chef_supermarket.gemspec +0 -20
- data/dpl-cloud66.gemspec +0 -3
- data/dpl-cloud_files.gemspec +0 -3
- data/dpl-cloud_foundry.gemspec +0 -3
- data/dpl-code_deploy.gemspec +0 -3
- data/dpl-deis.gemspec +0 -3
- data/dpl-elastic_beanstalk.gemspec +0 -3
- data/dpl-engine_yard.gemspec +0 -3
- data/dpl-firebase.gemspec +0 -3
- data/dpl-gae.gemspec +0 -3
- data/dpl-gcs.gemspec +0 -3
- data/dpl-hackage.gemspec +0 -3
- data/dpl-hephy.gemspec +0 -3
- data/dpl-heroku.gemspec +0 -3
- data/dpl-lambda.gemspec +0 -3
- data/dpl-launchpad.gemspec +0 -3
- data/dpl-npm.gemspec +0 -3
- data/dpl-openshift.gemspec +0 -3
- data/dpl-ops_works.gemspec +0 -3
- data/dpl-packagecloud.gemspec +0 -3
- data/dpl-pages.gemspec +0 -3
- data/dpl-puppet_forge.gemspec +0 -3
- data/dpl-pypi.gemspec +0 -3
- data/dpl-releases.gemspec +0 -8
- data/dpl-rubygems.gemspec +0 -3
- data/dpl-s3.gemspec +0 -3
- data/dpl-scalingo.gemspec +0 -3
- data/dpl-script.gemspec +0 -3
- data/dpl-snap.gemspec +0 -3
- data/dpl-surge.gemspec +0 -3
- data/dpl-testfairy.gemspec +0 -3
- data/dpl-transifex.gemspec +0 -3
- data/dpl.gemspec +0 -3
- data/gemspec_helper.rb +0 -51
- data/lib/dpl/error.rb +0 -3
- data/notes/engine_yard.md +0 -1
- data/notes/heroku.md +0 -3
- data/spec/cli_spec.rb +0 -36
- data/spec/provider_spec.rb +0 -191
- data/spec/spec_helper.rb +0 -20
@@ -0,0 +1,39 @@
|
|
1
|
+
module Dpl
|
2
|
+
module Env
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
# should this sit in Cl?
|
8
|
+
module ClassMethods
|
9
|
+
attr_reader :env_prefixes
|
10
|
+
|
11
|
+
def env(*strs)
|
12
|
+
opts = strs.last.is_a?(Hash) ? strs.pop : {}
|
13
|
+
if strs.any?
|
14
|
+
strs = strs.map(&:to_s).map(&:upcase)
|
15
|
+
@env_prefixes = strs.map { |str| "#{str.to_s.upcase}_" }
|
16
|
+
# allow unconventional ENV vars such as GOOGLECLOUDKEYFILE
|
17
|
+
@env_prefixes += strs if opts[:allow_skip_underscore]
|
18
|
+
elsif env_prefixes
|
19
|
+
opts = ENV.select { |key, _| prefixed?(key) }
|
20
|
+
opts.map { |key, value| [unprefix(key).downcase.to_sym, value] }.to_h
|
21
|
+
else
|
22
|
+
{}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def prefixed?(key)
|
27
|
+
env_prefixes.any? { |prefix| key.to_s.start_with?(prefix) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def unprefix(key)
|
31
|
+
env_prefixes.inject(key) { |key, prefix| key.sub(prefix, '') }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def opts
|
36
|
+
@opts ||= self.class.env.merge(super)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Dpl
|
4
|
+
module Interpolate
|
5
|
+
# Interpolates variables in the given string.
|
6
|
+
#
|
7
|
+
# Variables can be contained in scripts, shell commands, and messages.
|
8
|
+
# They have the syntax `%{name}` or `%s` (or any other identifier supported
|
9
|
+
# by [Kernel#sprintf](https://ruby-doc.org/core-2.6.3/Kernel.html#method-i-format)).
|
10
|
+
#
|
11
|
+
# This supports two styles of interpolation:
|
12
|
+
#
|
13
|
+
# * Named variables `%{name}` and
|
14
|
+
# * Positional variables.
|
15
|
+
#
|
16
|
+
# Named variable names need to match constants on the provider class, or
|
17
|
+
# methods on the provider instance, which will be called in order to
|
18
|
+
# evaluate the value to be interpolated.
|
19
|
+
#
|
20
|
+
# Positional variables can be used if no corresponding method exists, e.g.
|
21
|
+
# if the value that needs to be interpolated is an argument passed to a
|
22
|
+
# local method.
|
23
|
+
#
|
24
|
+
# For example, using named variables:
|
25
|
+
#
|
26
|
+
# ```ruby
|
27
|
+
# def upload_file
|
28
|
+
# interpolate('Uploading file %{file} to %{target}')
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# def file
|
32
|
+
# './file_name'
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# def target
|
36
|
+
# 'target host'
|
37
|
+
# end
|
38
|
+
# ```
|
39
|
+
#
|
40
|
+
# Using positional variables:
|
41
|
+
#
|
42
|
+
# ```ruby
|
43
|
+
# def upload_file(file, target)
|
44
|
+
# interpolate('Uploading file %s to %s', file, target)
|
45
|
+
# end
|
46
|
+
# ```
|
47
|
+
#
|
48
|
+
# Implementors are encouraged to use named variables when possible, but
|
49
|
+
# are free to choose according to their needs.
|
50
|
+
def interpolate(str, args = [], opts = {})
|
51
|
+
args = args.shift if args.is_a?(Array) && args.first.is_a?(Hash)
|
52
|
+
Interpolator.new(str, self, args || {}, opts).apply
|
53
|
+
end
|
54
|
+
|
55
|
+
# Obfuscates the given string.
|
56
|
+
#
|
57
|
+
# Replaces all but the first N characters with asterisks, and paddes
|
58
|
+
# the string to a standard length of 20 characters. N depends on the
|
59
|
+
# length of the original string.
|
60
|
+
def obfuscate(str, opts = {})
|
61
|
+
return str if opts[:secure] || !str.tainted?
|
62
|
+
keep = (str.length / (4.0 + str.length / 5).round).round
|
63
|
+
keep = 1 if keep == 0
|
64
|
+
str[0, keep] + '*' * (20 - keep)
|
65
|
+
end
|
66
|
+
|
67
|
+
class Interpolator < Struct.new(:str, :obj, :args, :opts)
|
68
|
+
include Interpolate
|
69
|
+
|
70
|
+
MODIFIER = %i(obfuscate escape quote)
|
71
|
+
PATTERN = /%\{(\$?[\w]+)\}/
|
72
|
+
ENV_VAR = /^\$[A-Z_]+$/
|
73
|
+
UPCASE = /^[A-Z_]+$/
|
74
|
+
|
75
|
+
def apply
|
76
|
+
str = interpolate(self.str.to_s)
|
77
|
+
str = obfuscate(str) unless opts[:secure]
|
78
|
+
str = str.gsub(' ', ' ') if str.lines.size == 1
|
79
|
+
str
|
80
|
+
end
|
81
|
+
|
82
|
+
def interpolate(str)
|
83
|
+
str = str % args if args.is_a?(Array) && args.any?
|
84
|
+
str.to_s.gsub(PATTERN) { normalize(lookup($1.to_sym)) }
|
85
|
+
end
|
86
|
+
|
87
|
+
def obfuscate(str)
|
88
|
+
secrets(str).inject(str) do |str, secret|
|
89
|
+
str.gsub(secret, super(secret))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def secrets(str)
|
94
|
+
return [] unless str.is_a?(String) && str.tainted?
|
95
|
+
opts = obj.class.opts.select(&:secret?)
|
96
|
+
secrets = opts.map { |opt| obj.opts[opt.name] }.compact
|
97
|
+
secrets.select { |secret| str.include?(secret) }
|
98
|
+
end
|
99
|
+
|
100
|
+
def normalize(obj)
|
101
|
+
obj.is_a?(Array) ? obj.join(' ') : obj.to_s
|
102
|
+
end
|
103
|
+
|
104
|
+
def lookup(key)
|
105
|
+
if mod = modifier(key)
|
106
|
+
key = key.to_s.sub("#{mod}d_", '')
|
107
|
+
obj.send(mod, lookup(key))
|
108
|
+
elsif key.to_s =~ ENV_VAR
|
109
|
+
ENV[key.to_s.sub('$', '')]
|
110
|
+
elsif key.to_s =~ UPCASE && obj.class.const_defined?(key)
|
111
|
+
obj.class.const_get(key)
|
112
|
+
elsif args.is_a?(Hash) && args.key?(key)
|
113
|
+
args[key]
|
114
|
+
elsif obj.respond_to?(key, true)
|
115
|
+
obj.send(key)
|
116
|
+
else
|
117
|
+
raise KeyError, key
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def modifier(key)
|
122
|
+
MODIFIER.detect { |mod| key.to_s.start_with?("#{mod}d_") }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Memoize
|
2
|
+
class ArgsError < StandardError; end
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
def memoize(name)
|
6
|
+
ivar = :"@#{name.to_s.sub('?', '_predicate')}"
|
7
|
+
prepend Module.new {
|
8
|
+
define_method(name) do |*args|
|
9
|
+
raise ArgsError.new('cannot pass arguments to memoized method %p' % name) unless args.empty?
|
10
|
+
return instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
11
|
+
instance_variable_set(ivar, super())
|
12
|
+
end
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.included(base)
|
18
|
+
base.extend(ClassMethods)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Beloved squiggly heredocs did not exist in Ruby 2.1, which we still want to
|
2
|
+
# support, so let's give kudos with a method `sq`.
|
3
|
+
module Squiggle
|
4
|
+
# Outdents each line on a multiline string by the number of leading
|
5
|
+
# whitespace characters on the first line.
|
6
|
+
#
|
7
|
+
# This method exists so we can unindet heredoc strings the same way that
|
8
|
+
# Ruby 2.2's squiggly heredocs work, but still support Ruby 2.1 for the
|
9
|
+
# time being.
|
10
|
+
#
|
11
|
+
# For example:
|
12
|
+
#
|
13
|
+
# str = sq(<<-str)
|
14
|
+
# This multiline string will be outdented by two characters,
|
15
|
+
# so the extra indentation on this line will be kept,
|
16
|
+
# while this line sits on the same level as the first line.
|
17
|
+
# str
|
18
|
+
def sq(str)
|
19
|
+
width = str =~ /( *)\S/ && $1.size
|
20
|
+
str.lines.map { |line| line.gsub(/^ {#{width}}/, '') }.join
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Dpl
|
4
|
+
class Zip < Struct.new(:src, :dest, :opts)
|
5
|
+
ZIP_EXT = %w(.zip .jar)
|
6
|
+
|
7
|
+
def initialize(*)
|
8
|
+
require 'zip'
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def zip
|
13
|
+
if zip_file?
|
14
|
+
File.new(src)
|
15
|
+
elsif dir?
|
16
|
+
zip_dir
|
17
|
+
else
|
18
|
+
zip_file
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def zip_dir
|
23
|
+
create(Dir.glob(*glob).reject { |path| dir?(path) })
|
24
|
+
end
|
25
|
+
|
26
|
+
def zip_file
|
27
|
+
create([src])
|
28
|
+
end
|
29
|
+
|
30
|
+
def create(files)
|
31
|
+
::Zip::File.open(dest, ::Zip::File::CREATE) do |zip|
|
32
|
+
files.each do |file|
|
33
|
+
zip.add(file.sub("#{src}/", ''), file)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
File.new(dest)
|
37
|
+
end
|
38
|
+
|
39
|
+
def zip_file?
|
40
|
+
exts.include?(File.extname(src))
|
41
|
+
end
|
42
|
+
|
43
|
+
def dir?(path = src)
|
44
|
+
File.directory?(path)
|
45
|
+
end
|
46
|
+
|
47
|
+
def copy
|
48
|
+
FileUtils.cp(src, dest)
|
49
|
+
end
|
50
|
+
|
51
|
+
def glob
|
52
|
+
glob = ["#{src}/**/*"]
|
53
|
+
glob << File::FNM_DOTMATCH if dot_match?
|
54
|
+
glob
|
55
|
+
end
|
56
|
+
|
57
|
+
def dot_match?
|
58
|
+
opts[:dot_match]
|
59
|
+
end
|
60
|
+
|
61
|
+
def exts
|
62
|
+
opts[:exts] ||= ZIP_EXT
|
63
|
+
end
|
64
|
+
|
65
|
+
def opts
|
66
|
+
super || {}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/dpl/provider.rb
CHANGED
@@ -1,312 +1,640 @@
|
|
1
|
-
require '
|
2
|
-
require 'dpl/version'
|
1
|
+
require 'cl'
|
3
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'
|
4
15
|
|
5
|
-
module
|
6
|
-
class
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
58
91
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
# when requiring the file corresponding to the provider name
|
63
|
-
# given in the options, the general strategy is to normalize
|
64
|
-
# the option to lower-case alphanumeric, then
|
65
|
-
# use that key to find the file name using the GEM_NAME_OF map.
|
66
|
-
|
67
|
-
context.fold("Installing deploy dependencies") do
|
68
|
-
begin
|
69
|
-
opt_lower = super.option(:provider).to_s.downcase
|
70
|
-
opt = opt_lower.gsub(/[^a-z0-9]/, '')
|
71
|
-
class_name = class_of(opt)
|
72
|
-
raise Error, "could not find provider %p" % opt unless class_name
|
73
|
-
require "dpl/provider/#{GEM_NAME_OF[class_name]}"
|
74
|
-
provider = const_get(class_name).new(context, options)
|
75
|
-
rescue NameError, LoadError => e
|
76
|
-
if /uninitialized constant DPL::Provider::(?<provider_wanted>\S+)/ =~ e.message
|
77
|
-
provider_gem_name = GEM_NAME_OF[provider_wanted]
|
78
|
-
elsif %r(cannot load such file -- dpl/provider/(?<provider_file_name>\S+)) =~ e.message
|
79
|
-
provider_gem_name = GEM_NAME_OF[class_name]
|
80
|
-
else
|
81
|
-
# don't know what to do with this error
|
82
|
-
raise e
|
83
|
-
end
|
84
|
-
install_cmd = "gem install dpl-#{provider_gem_name || opt} -v #{ENV['DPL_VERSION'] || DPL::VERSION}"
|
85
|
-
|
86
|
-
if File.exist?(local_gem = File.join(Dir.pwd, "dpl-#{GEM_NAME_OF[provider_gem_name] || opt_lower}-#{ENV['DPL_VERSION'] || DPL::VERSION}.gem"))
|
87
|
-
install_cmd = "gem install #{local_gem}"
|
88
|
-
end
|
89
|
-
|
90
|
-
context.shell(install_cmd)
|
91
|
-
Gem.clear_paths
|
92
|
-
|
93
|
-
require "dpl/provider/#{GEM_NAME_OF[class_name]}"
|
94
|
-
provider = const_get(class_name).new(context, options)
|
95
|
-
rescue DPL::Error
|
96
|
-
if opt_lower
|
97
|
-
provider = const_get(opt.capitalize).new(context, options)
|
98
|
-
else
|
99
|
-
raise Error, 'missing provider'
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
if options[:no_deploy]
|
104
|
-
def provider.deploy; end
|
105
|
-
else
|
106
|
-
provider.install_deploy_dependencies if provider.respond_to? :install_deploy_dependencies
|
107
|
-
end
|
108
|
-
|
109
|
-
provider
|
92
|
+
def move_files(ctx)
|
93
|
+
ctx.move_files(move) if move.any?
|
110
94
|
end
|
111
|
-
end
|
112
95
|
|
113
|
-
|
114
|
-
|
115
|
-
|
96
|
+
def unmove_files(ctx)
|
97
|
+
ctx.unmove_files(move) if move.any?
|
98
|
+
end
|
116
99
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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?
|
121
113
|
end
|
122
|
-
puts ''
|
123
114
|
end
|
124
115
|
|
125
|
-
|
126
|
-
|
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
|
+
arg :provider, 'The provider name', required: true
|
149
|
+
|
150
|
+
opt '--run CMD', 'Command to execute after the deployment finished successfully', type: :array
|
151
|
+
opt '--cleanup', 'Skip cleaning up build artifacts before the deployment', negate: %w(skip)
|
152
|
+
opt '--stage NAME', 'Execute the given stage(s) only', type: :array, internal: true, default: STAGES
|
153
|
+
opt '--backtrace', 'Print the backtrace for exceptions', internal: true
|
154
|
+
opt '--fold', 'Wrap log output in folds', internal: true
|
155
|
+
|
156
|
+
msgs before_install: 'Installing deployment dependencies',
|
157
|
+
before_setup: 'Setting the build environment up for the deployment',
|
158
|
+
setup_git_ssh: 'Setting up git-ssh',
|
159
|
+
cleanup: 'Cleaning up git repository with `git stash --all`',
|
160
|
+
ssh_keygen: 'Generating SSH key',
|
161
|
+
setup_git_ua: 'Setting up git HTTP user agent',
|
162
|
+
ssh_remote_host: 'SSH remote is %s at port %s',
|
163
|
+
ssh_try_connect: 'Waiting for SSH connection ...',
|
164
|
+
ssh_connected: 'SSH connection established.',
|
165
|
+
ssh_failed: 'Failed to establish SSH connection.'
|
166
|
+
|
167
|
+
def_delegators :'self.class', :status, :full_name, :install_deps,
|
168
|
+
:install_deps?, :keep, :move_files, :unmove_files, :needs?, :runtimes,
|
169
|
+
:validate_runtimes, :user_agent
|
170
|
+
|
171
|
+
def_delegators :ctx, :apt_get, :gem_require, :npm_install, :pip_install,
|
172
|
+
:build_dir, :build_number, :repo_slug, :encoding, :git_author_email,
|
173
|
+
:git_author_name, :git_branch, :git_commit_msg, :git_dirty?, :git_log,
|
174
|
+
:git_ls_files, :git_ls_remote?, :git_remote_urls, :git_rev_parse,
|
175
|
+
:git_sha, :git_tag, :machine_name, :node_version, :npm_version, :sleep,
|
176
|
+
:ssh_keygen, :success?, :tmp_dir, :which, :logger, :rendezvous,
|
177
|
+
:file_size, :write_file, :write_netrc, :last_out, :last_err, :test?,
|
178
|
+
:tty?
|
179
|
+
|
180
|
+
attr_reader :repo_name, :key_name
|
181
|
+
|
182
|
+
def initialize(ctx, *args)
|
183
|
+
@repo_name = ctx.repo_name
|
184
|
+
@key_name = ctx.machine_name
|
185
|
+
super
|
127
186
|
end
|
128
187
|
|
129
|
-
|
130
|
-
|
188
|
+
# Runs all stages, all commands provided by the user, as well as the final
|
189
|
+
# stage `finish` (which will be run even if an error has been raised during
|
190
|
+
# previous stages).
|
191
|
+
def run
|
192
|
+
stages = stage.select { |stage| run_stage?(stage) }
|
193
|
+
stages.each { |stage| run_stage(stage) }
|
194
|
+
run_cmds
|
195
|
+
rescue Error
|
196
|
+
raise
|
197
|
+
rescue Exception => e
|
198
|
+
raise Error.new("#{e.message} (#{e.class})", backtrace: backtrace? ? e.backtrace : nil) unless test?
|
199
|
+
raise
|
200
|
+
ensure
|
201
|
+
run_stage(:finish, fold: false) if finish?
|
131
202
|
end
|
132
203
|
|
133
|
-
|
134
|
-
|
204
|
+
# Whether or not a stage needs to be run
|
205
|
+
def run_stage?(stage)
|
206
|
+
respond_to?(:"before_#{stage}") || respond_to?(stage)
|
135
207
|
end
|
136
208
|
|
137
|
-
def
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
209
|
+
def finish?
|
210
|
+
stage.size == STAGES.size
|
211
|
+
end
|
212
|
+
|
213
|
+
# Runs a single stage.
|
214
|
+
#
|
215
|
+
# For each stage the base class has the opportunity to implement a `before`
|
216
|
+
# stage method, in order to apply default behaviour. Provider implementors
|
217
|
+
# are asked to not overwrite these methods.
|
218
|
+
#
|
219
|
+
# Any log output from both the before stage and stage method is going to be
|
220
|
+
# folded in the resulting build log.
|
221
|
+
def run_stage(stage, opts = {})
|
222
|
+
fold(stage, opts) do
|
223
|
+
send(:"before_#{stage}") if respond_to?(:"before_#{stage}")
|
224
|
+
send(stage) if respond_to?(stage)
|
145
225
|
end
|
146
|
-
context.shell("export PATH=$PATH:$HOME/.local/bin")
|
147
226
|
end
|
148
227
|
|
149
|
-
|
150
|
-
|
228
|
+
# Initialize the deployment process.
|
229
|
+
#
|
230
|
+
# This will:
|
231
|
+
#
|
232
|
+
# * Displays warning messages about the provider's maturity status, and deprecated
|
233
|
+
# options used.
|
234
|
+
# * Setup a ~/.dpl working directory
|
235
|
+
# * Move files out of the way that have been declared as such
|
236
|
+
def before_init
|
237
|
+
warn status.msg if status && status.announce?
|
238
|
+
deprecations.each { |(key, msg)| ctx.deprecate_opt(key, msg) }
|
239
|
+
setup_dpl_dir
|
240
|
+
move_files(ctx)
|
151
241
|
end
|
152
242
|
|
153
|
-
|
154
|
-
|
243
|
+
# Install APT, NPM, and Python dependencies as declared by the provider.
|
244
|
+
def before_install
|
245
|
+
validate_runtimes(ctx)
|
246
|
+
return unless install_deps?
|
247
|
+
info :before_install
|
248
|
+
install_deps(ctx)
|
155
249
|
end
|
156
250
|
|
157
|
-
|
251
|
+
# Sets the build environment up for the deployment.
|
252
|
+
#
|
253
|
+
# This will:
|
254
|
+
#
|
255
|
+
# * Setup a ~/.dpl working directory
|
256
|
+
# * Create a temporary, per build SSH key, and call `add_key` if the feature `ssh_key` has been declared as required.
|
257
|
+
# * Setup git config (email and user name) if the feature `git` has been declared as required.
|
258
|
+
# * Either set or unset the environment variable `GIT_HTTP_USER_AGENT` depending if the feature `git_http_user_agent` has been declared as required.
|
259
|
+
def before_setup
|
260
|
+
info :before_setup
|
261
|
+
setup_ssh_key if needs?(:ssh_key)
|
262
|
+
setup_git_config if needs?(:git)
|
263
|
+
setup_git_http_user_agent
|
264
|
+
end
|
158
265
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
266
|
+
# Prepares the deployment by cleaning up the working directory.
|
267
|
+
#
|
268
|
+
# @see Provider#cleanup
|
269
|
+
def before_prepare
|
270
|
+
cleanup if cleanup?
|
271
|
+
end
|
272
|
+
|
273
|
+
# Runs each command as given by the user using the `--run` option.
|
274
|
+
#
|
275
|
+
# For a command that matches `restart` the method `restart` will be called
|
276
|
+
# (which can be overwritten by providers, e.g. in order to restart service
|
277
|
+
# instances).
|
278
|
+
#
|
279
|
+
# All other commands will be passed to the method `run_cmd`. By default this
|
280
|
+
# will be run as a shell command locally, but providers can choose to
|
281
|
+
# overwrite this method in order to run the command on a remote machine.
|
282
|
+
def run_cmds
|
283
|
+
Array(opts[:run]).each do |cmd|
|
284
|
+
cmd.downcase == 'restart' ? restart : run_cmd(cmd)
|
165
285
|
end
|
166
286
|
end
|
167
287
|
|
168
|
-
def
|
169
|
-
|
170
|
-
strings.unshift "travis/0.1.0" if context.env['TRAVIS']
|
171
|
-
strings = strings.flat_map { |e| Hash === e ? e.map { |k,v| "#{k}/#{v}" } : e }
|
172
|
-
strings.join(" ").gsub(/\s+/, " ").strip
|
288
|
+
def run_cmd(cmd)
|
289
|
+
cmd.downcase == 'restart' ? restart : shell(cmd)
|
173
290
|
end
|
174
291
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
292
|
+
# Finalizes the deployment process.
|
293
|
+
#
|
294
|
+
# This will:
|
295
|
+
#
|
296
|
+
# * Call the method `remove_key` if implemented by the provider, and if the
|
297
|
+
# feature `ssh_key` has been declared as required.
|
298
|
+
# * Revert the cleanup process, i.e. restore files moved out of the way
|
299
|
+
# during `cleanup`.
|
300
|
+
# * Remove the temporary directory `~/.dpl`
|
301
|
+
def before_finish
|
302
|
+
remove_key if needs?(:ssh_key) && respond_to?(:remove_key)
|
303
|
+
uncleanup if cleanup?
|
304
|
+
unmove_files(ctx)
|
305
|
+
remove_dpl_dir
|
179
306
|
end
|
180
307
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
308
|
+
# Resets the current working directory to the commited state.
|
309
|
+
#
|
310
|
+
# Cleanup will use `git stash --all` in order to reset the working
|
311
|
+
# directory to the committed state, and cleanup any left over artifacts
|
312
|
+
# from the build process. Providers can use the DSL method `keep` in order
|
313
|
+
# to declare known artifacts (such as CLI tooling installed to the working
|
314
|
+
# directory) that needs to be moved out of the way and restored after the
|
315
|
+
# cleanup process.
|
316
|
+
def cleanup
|
317
|
+
info :cleanup
|
318
|
+
keep.each { |path| shell "mv ./#{path} ~/#{path}", echo: false, assert: false }
|
319
|
+
shell 'git stash --all'
|
320
|
+
keep.each { |path| shell "mv ~/#{path} ./#{path}", echo: false, assert: false }
|
321
|
+
end
|
185
322
|
|
186
|
-
|
187
|
-
|
188
|
-
|
323
|
+
# Restore files that have been cleaned up.
|
324
|
+
def uncleanup
|
325
|
+
shell 'git stash pop', assert: false
|
326
|
+
end
|
189
327
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
328
|
+
# Creates the directory `~/.dpl` as an internal working directory.
|
329
|
+
def setup_dpl_dir
|
330
|
+
rm_rf '~/.dpl'
|
331
|
+
mkdir_p '~/.dpl'
|
332
|
+
chmod 0700, '~/.dpl'
|
333
|
+
end
|
195
334
|
|
196
|
-
|
197
|
-
|
335
|
+
# Remove the internal working directory `~/.dpl`.
|
336
|
+
def remove_dpl_dir
|
337
|
+
rm_rf '~/.dpl'
|
338
|
+
end
|
198
339
|
|
199
|
-
|
340
|
+
# Creates an SSH key, and sets up git-ssh if needed.
|
341
|
+
#
|
342
|
+
# This will:
|
343
|
+
#
|
344
|
+
# * Create a temporary, per build SSH key.
|
345
|
+
# * Setup a `git-ssh` executable to use that key.
|
346
|
+
# * Call the method `add_key` if implemented by the provider.
|
347
|
+
def setup_ssh_key
|
348
|
+
ssh_keygen(key_name, '~/.dpl/id_rsa')
|
349
|
+
setup_git_ssh('~/.dpl/id_rsa')
|
350
|
+
add_key('~/.dpl/id_rsa.pub') if respond_to?(:add_key)
|
351
|
+
end
|
200
352
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
ensure
|
209
|
-
if needs_key?
|
210
|
-
remove_key rescue nil
|
211
|
-
end
|
212
|
-
uncleanup
|
353
|
+
# Setup git config
|
354
|
+
#
|
355
|
+
# This adds the current user's name and email address (as user@localhost)
|
356
|
+
# to the git config.
|
357
|
+
def setup_git_config
|
358
|
+
shell "git config user.email >/dev/null 2>/dev/null || git config user.email `whoami`@localhost", echo: false, assert: false
|
359
|
+
shell "git config user.name >/dev/null 2>/dev/null || git config user.name `whoami`", echo: false, assert: false
|
213
360
|
end
|
214
361
|
|
215
|
-
|
216
|
-
|
362
|
+
# Sets up `git-ssh` and the GIT_SSH env var
|
363
|
+
def setup_git_ssh(key)
|
364
|
+
info :setup_git_ssh
|
365
|
+
path, conf = '~/.dpl/git-ssh', asset(:dpl, :git_ssh).read % expand(key)
|
366
|
+
open(path, 'w+') { |file| file.write(conf) }
|
367
|
+
chmod(0740, path)
|
368
|
+
ENV['GIT_SSH'] = expand(path)
|
217
369
|
end
|
218
370
|
|
219
|
-
|
220
|
-
|
371
|
+
# Generates an SSH key.
|
372
|
+
def ssh_keygen(key, path)
|
373
|
+
info :ssh_keygen
|
374
|
+
ctx.ssh_keygen(key, expand(path))
|
221
375
|
end
|
222
376
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
"See https://docs.travis-ci.com/user/deployment#Uploading-Files-and-skip_cleanup."
|
229
|
-
context.shell "git stash --all"
|
230
|
-
context.shell "mv ~/dpl .dpl"
|
377
|
+
# Sets or unsets the environment variable `GIT_HTTP_USER_AGENT`.
|
378
|
+
def setup_git_http_user_agent
|
379
|
+
return ENV.delete('GIT_HTTP_USER_AGENT') unless needs?(:git_http_user_agent)
|
380
|
+
info :setup_git_ua
|
381
|
+
ENV['GIT_HTTP_USER_AGENT'] = user_agent(git: `git --version`[/[\d\.]+/])
|
231
382
|
end
|
232
383
|
|
233
|
-
|
234
|
-
|
235
|
-
|
384
|
+
# Waits for SSH access on the given host and port.
|
385
|
+
#
|
386
|
+
# This will try to connect to the given SSH host and port, and keep
|
387
|
+
# retrying 30 times, waiting a second inbetween retries.
|
388
|
+
def wait_for_ssh_access(host, port)
|
389
|
+
info :ssh_remote_host, host, port
|
390
|
+
1.upto(20) { try_ssh_access(host, port) && break || sleep(3) }
|
391
|
+
success? ? info(:ssh_connected) : error(:ssh_failed)
|
236
392
|
end
|
237
393
|
|
238
|
-
|
239
|
-
|
394
|
+
# Tries to connect to the given SSH host and port.
|
395
|
+
def try_ssh_access(host, port)
|
396
|
+
info :ssh_try_connect
|
397
|
+
shell "#{ENV['GIT_SSH']} #{host} -p #{port} 2>&1 | grep -c 'PTY allocation request failed' > /dev/null", echo: false, assert: false
|
240
398
|
end
|
241
399
|
|
242
|
-
|
400
|
+
# Creates a log fold.
|
401
|
+
#
|
402
|
+
# Folds any log output from the given block into a fold with the given
|
403
|
+
# name.
|
404
|
+
def fold(name, opts = {}, &block)
|
405
|
+
return yield unless fold?(name, opts)
|
406
|
+
title = FOLDS[name] || "deploy.#{name}"
|
407
|
+
ctx.fold(title, &block)
|
243
408
|
end
|
244
409
|
|
245
|
-
|
246
|
-
|
410
|
+
# Checks if the given stage needs to be folded.
|
411
|
+
#
|
412
|
+
# Depends on the option `--fold`, also omits folds for the init and finish
|
413
|
+
# stages. Can be overwritten by passing `fold: false`.
|
414
|
+
def fold?(name, opts = {})
|
415
|
+
!opts[:fold].is_a?(FalseClass) && super() && !%i(init).include?(name)
|
247
416
|
end
|
248
417
|
|
249
|
-
|
250
|
-
|
251
|
-
|
418
|
+
# Runs a script as a shell command.
|
419
|
+
#
|
420
|
+
# Scripts can be stored as separate files (assets) in the directory
|
421
|
+
# `lib/dpl/assets/[provider]`.
|
422
|
+
#
|
423
|
+
# This is meant for large shell commands that would be hard to read if
|
424
|
+
# embedded in Ruby code. Storing them as separate files helps with proper
|
425
|
+
# syntax highlighting etc in editors, and allows to execute them for
|
426
|
+
# testing purposes.
|
427
|
+
#
|
428
|
+
# Scripts can have interpolation variables. See Dpl::Interpolate for
|
429
|
+
# details on interpolating variables.
|
430
|
+
#
|
431
|
+
# See Ctx::Bash#shell for details on the options accepted.
|
432
|
+
def script(name, opts = {})
|
433
|
+
opts[:assert] = name if opts[:assert].is_a?(TrueClass)
|
434
|
+
shell(asset(name).read, opts.merge(echo: false))
|
252
435
|
end
|
253
436
|
|
254
|
-
|
255
|
-
|
256
|
-
|
437
|
+
# Runs a single shell command.
|
438
|
+
#
|
439
|
+
# Shell commands 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 shell(cmd, *args)
|
444
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
445
|
+
cmd = Cmd.new(self, cmd, opts)
|
446
|
+
ctx.shell(cmd)
|
447
|
+
end
|
257
448
|
|
258
|
-
|
259
|
-
|
260
|
-
|
449
|
+
# @!method print
|
450
|
+
# Prints a partial message to stdout
|
451
|
+
#
|
452
|
+
# This method does not append a newline character to the given message,
|
453
|
+
# which usually is not the desired behaviour. The method is intended to be
|
454
|
+
# used if an initial, partial message is supposed to be printed, which will
|
455
|
+
# be completed later (using the method `info`).
|
456
|
+
#
|
457
|
+
# For example:
|
458
|
+
#
|
459
|
+
# print 'Starting a long running task ...'
|
460
|
+
# run_long_running_task
|
461
|
+
# info 'done.'
|
462
|
+
#
|
463
|
+
# Messages support interpolation variables. See Dpl::Interpolate for
|
464
|
+
# details on interpolating variables.
|
465
|
+
|
466
|
+
# @!method info
|
467
|
+
# Outputs an info message to stdout
|
468
|
+
#
|
469
|
+
# This method is intended to be used for default, info level messages that
|
470
|
+
# are supposed to show up in the build log.
|
471
|
+
#
|
472
|
+
# @!method warn
|
473
|
+
# Outputs an warning message to stderr
|
474
|
+
#
|
475
|
+
# This method is intended to be used for warning messages that are supposed
|
476
|
+
# to show up in the build log, but do not qualify as errors that would
|
477
|
+
# abort the deployment process. The warning will be highlighted as red
|
478
|
+
# text. Use sparingly.
|
479
|
+
#
|
480
|
+
# Messages support interpolation variables. See Dpl::Interpolate for
|
481
|
+
# details on interpolating variables.
|
482
|
+
|
483
|
+
# @!method error
|
484
|
+
# Outputs an error message to stderr, and raises an error, halting the
|
485
|
+
# deployment process.
|
486
|
+
#
|
487
|
+
# This method is intended to be used for all error conditions that require
|
488
|
+
# the deployment process to be aborted.
|
489
|
+
#
|
490
|
+
# Messages support interpolation variables. See Dpl::Interpolate for
|
491
|
+
# details on interpolating variables.
|
492
|
+
%i(print info warn error).each do |level|
|
493
|
+
define_method(level) do |msg, *args|
|
494
|
+
msg = interpolate(self.msg(msg), args) if msg.is_a?(Symbol)
|
495
|
+
ctx.send(level, msg)
|
261
496
|
end
|
497
|
+
end
|
262
498
|
|
263
|
-
|
264
|
-
|
499
|
+
# @!method cmd
|
500
|
+
# Looks up a shell command from the commands declared by the provider
|
501
|
+
# (using the class level DSL).
|
502
|
+
#
|
503
|
+
# Not usually useful to be used by provider implementors directly. Use the
|
504
|
+
# method `shell` in order to execute shell commands.
|
505
|
+
|
506
|
+
# @!method err
|
507
|
+
# Looks up an error message from the error messages declared by the
|
508
|
+
# provider (using the class level DSL), as needed by the option `assert`
|
509
|
+
# when passed to the method `shell`.
|
510
|
+
|
511
|
+
# @!method msg
|
512
|
+
# Looks up a message from the messages declared by the provider (using the
|
513
|
+
# class level DSL).
|
514
|
+
#
|
515
|
+
# For example, a message declared on the class body like so:
|
516
|
+
#
|
517
|
+
# ```ruby
|
518
|
+
# msgs commit_msg: 'Commit build artifacts on build %{build_number}'
|
519
|
+
# ```
|
520
|
+
#
|
521
|
+
# could be used by the implementation like so:
|
522
|
+
#
|
523
|
+
# ```ruby
|
524
|
+
# def commit_msg
|
525
|
+
# interpolate(msg(:commit_msg))
|
526
|
+
# end
|
527
|
+
# ```
|
528
|
+
#
|
529
|
+
# Note that the the method `interpolate` needs to be used in order to
|
530
|
+
# interpolate variables used in a message (if any).
|
531
|
+
%i(cmd err msg str).each do |name|
|
532
|
+
define_method(name) do |*keys|
|
533
|
+
key = keys.detect { |key| key.is_a?(Symbol) }
|
534
|
+
self.class.send(:"#{name}s")[key] if key
|
535
|
+
end
|
265
536
|
end
|
266
537
|
|
267
|
-
|
268
|
-
|
538
|
+
# Escapes the given string so it can be safely used in Bash.
|
539
|
+
def escape(str)
|
540
|
+
Shellwords.escape(str)
|
269
541
|
end
|
270
542
|
|
271
|
-
|
272
|
-
|
543
|
+
# Double quotes the given string.
|
544
|
+
def quote(str)
|
545
|
+
%("#{str.gsub('"', '\"')}")
|
273
546
|
end
|
274
547
|
|
275
|
-
|
276
|
-
|
548
|
+
# Outdents the given string.
|
549
|
+
#
|
550
|
+
# @see Dpl::Squiggle
|
551
|
+
def sq(str)
|
552
|
+
self.class.sq(str)
|
277
553
|
end
|
278
554
|
|
279
|
-
|
555
|
+
# Generate shell option strings to be passed to a shell command.
|
556
|
+
#
|
557
|
+
# This generates strings like `--key="value"` for the option keys passed.
|
558
|
+
# These keys are supposed to correspond to methods on the provider
|
559
|
+
# instance, which will be called in order to determine the option value.
|
560
|
+
#
|
561
|
+
# If the returned value is an array then the option will be repeated
|
562
|
+
# multiple times. If it is a String then it will be double quoted.
|
563
|
+
# Otherwise it is assumed to be a flag that does not have a value.
|
564
|
+
#
|
565
|
+
# @option prefix [String] Use this to set a single dash as an option prefix (defaults to two dashes).
|
566
|
+
# @option dashed [Boolean] Use this to dasherize the option key (rather than underscore it, defaults to underscore).
|
567
|
+
def opts_for(keys, opts = {})
|
568
|
+
strs = Array(keys).map { |key| opt_for(key, opts) if send(:"#{key}?") }.compact
|
569
|
+
strs.join(' ') if strs.any?
|
280
570
|
end
|
281
571
|
|
282
|
-
def
|
283
|
-
|
284
|
-
|
285
|
-
when
|
286
|
-
|
287
|
-
when /compress'd/
|
288
|
-
'compress'
|
289
|
-
when /text/
|
290
|
-
'text'
|
291
|
-
when /data/
|
292
|
-
# Shrugs?
|
572
|
+
def opt_for(key, opts = {})
|
573
|
+
case value = send(key)
|
574
|
+
when String then "#{opt_key(key, opts)}=#{value.inspect}"
|
575
|
+
when Array then value.map { |value| "#{opt_key(key, opts)}=#{value.inspect}" }
|
576
|
+
else opt_key(key, opts)
|
293
577
|
end
|
294
578
|
end
|
295
579
|
|
296
|
-
def
|
297
|
-
|
580
|
+
def opt_key(key, opts)
|
581
|
+
"#{opts[:prefix] || '--'}#{opts[:dashed] ? key.to_s.gsub('_', '-') : key}"
|
298
582
|
end
|
299
583
|
|
300
|
-
|
301
|
-
|
584
|
+
# Compacts the given hash by rejecting nil values.
|
585
|
+
def compact(hash)
|
586
|
+
hash.reject { |_, value| value.nil? }
|
302
587
|
end
|
303
588
|
|
304
|
-
|
305
|
-
|
589
|
+
# Returns a new hash with the given keys selected from the given hash.
|
590
|
+
def only(hash, *keys)
|
591
|
+
hash.select { |key, _| keys.include?(key) }
|
306
592
|
end
|
307
593
|
|
308
|
-
|
309
|
-
|
594
|
+
# Deep symbolizes the given hash's keys
|
595
|
+
def symbolize(obj)
|
596
|
+
case obj
|
597
|
+
when Hash
|
598
|
+
obj.map { |key, obj| [key.to_sym, symbolize(obj)] }.to_h
|
599
|
+
when Array
|
600
|
+
obj.map { |obj| symbolize(obj) }
|
601
|
+
else
|
602
|
+
obj
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
def file?(path)
|
607
|
+
File.file?(expand(path))
|
608
|
+
end
|
609
|
+
|
610
|
+
def mkdir_p(path)
|
611
|
+
FileUtils.mkdir_p(expand(path))
|
612
|
+
end
|
613
|
+
|
614
|
+
def chmod(perm, path)
|
615
|
+
super(perm, expand(path))
|
616
|
+
end
|
617
|
+
|
618
|
+
def mv(src, dest)
|
619
|
+
super(expand(src), expand(dest))
|
620
|
+
end
|
621
|
+
|
622
|
+
def rm_rf(path)
|
623
|
+
super(expand(path))
|
624
|
+
end
|
625
|
+
|
626
|
+
def open(path, *args, &block)
|
627
|
+
File.open(expand(path), *args, &block)
|
628
|
+
end
|
629
|
+
|
630
|
+
def read(path)
|
631
|
+
File.read(expand(path))
|
632
|
+
end
|
633
|
+
|
634
|
+
def expand(*args)
|
635
|
+
File.expand_path(*args)
|
310
636
|
end
|
311
637
|
end
|
312
638
|
end
|
639
|
+
|
640
|
+
require 'dpl/providers'
|