dpl-connect 1.8.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +8 -0
- data/.rspec +2 -0
- data/.travis.yml +36 -0
- data/Gemfile +100 -0
- data/LICENSE +22 -0
- data/README.md +934 -0
- data/Rakefile +1 -0
- data/TESTING.md +29 -0
- data/bin/dpl +5 -0
- data/dpl.gemspec +32 -0
- data/lib/dpl/cli.rb +66 -0
- data/lib/dpl/error.rb +3 -0
- data/lib/dpl/provider.rb +264 -0
- data/lib/dpl/provider/anynines.rb +13 -0
- data/lib/dpl/provider/appfog.rb +21 -0
- data/lib/dpl/provider/atlas.rb +108 -0
- data/lib/dpl/provider/azure_webapps.rb +48 -0
- data/lib/dpl/provider/bintray.rb +509 -0
- data/lib/dpl/provider/bitballoon.rb +22 -0
- data/lib/dpl/provider/bluemix_cloud_foundry.rb +23 -0
- data/lib/dpl/provider/boxfuse.rb +57 -0
- data/lib/dpl/provider/catalyze.rb +49 -0
- data/lib/dpl/provider/chef_supermarket.rb +85 -0
- data/lib/dpl/provider/cloud66.rb +38 -0
- data/lib/dpl/provider/cloud_files.rb +38 -0
- data/lib/dpl/provider/cloud_foundry.rb +43 -0
- data/lib/dpl/provider/code_deploy.rb +123 -0
- data/lib/dpl/provider/deis.rb +119 -0
- data/lib/dpl/provider/divshot.rb +23 -0
- data/lib/dpl/provider/elastic_beanstalk.rb +195 -0
- data/lib/dpl/provider/engine_yard.rb +90 -0
- data/lib/dpl/provider/firebase.rb +27 -0
- data/lib/dpl/provider/gae.rb +97 -0
- data/lib/dpl/provider/gcs.rb +59 -0
- data/lib/dpl/provider/hackage.rb +29 -0
- data/lib/dpl/provider/heroku.rb +18 -0
- data/lib/dpl/provider/heroku/api.rb +98 -0
- data/lib/dpl/provider/heroku/generic.rb +94 -0
- data/lib/dpl/provider/heroku/git.rb +28 -0
- data/lib/dpl/provider/lambda.rb +236 -0
- data/lib/dpl/provider/launchpad.rb +48 -0
- data/lib/dpl/provider/modulus.rb +23 -0
- data/lib/dpl/provider/npm.rb +64 -0
- data/lib/dpl/provider/openshift.rb +59 -0
- data/lib/dpl/provider/ops_works.rb +132 -0
- data/lib/dpl/provider/packagecloud.rb +144 -0
- data/lib/dpl/provider/pages.rb +79 -0
- data/lib/dpl/provider/puppet_forge.rb +43 -0
- data/lib/dpl/provider/pypi.rb +111 -0
- data/lib/dpl/provider/releases.rb +139 -0
- data/lib/dpl/provider/rubygems.rb +51 -0
- data/lib/dpl/provider/s3.rb +123 -0
- data/lib/dpl/provider/scalingo.rb +97 -0
- data/lib/dpl/provider/script.rb +29 -0
- data/lib/dpl/provider/surge.rb +33 -0
- data/lib/dpl/provider/testfairy.rb +190 -0
- data/lib/dpl/provider/transifex.rb +45 -0
- data/lib/dpl/version.rb +3 -0
- data/notes/engine_yard.md +1 -0
- data/notes/heroku.md +3 -0
- data/spec/cli_spec.rb +36 -0
- data/spec/provider/anynines_spec.rb +20 -0
- data/spec/provider/appfog_spec.rb +35 -0
- data/spec/provider/atlas_spec.rb +99 -0
- data/spec/provider/azure_webapps_spec.rb +95 -0
- data/spec/provider/bintray_spec.rb +259 -0
- data/spec/provider/bitballoon_spec.rb +32 -0
- data/spec/provider/bluemixcloudfoundry_spec.rb +23 -0
- data/spec/provider/boxfuse_spec.rb +16 -0
- data/spec/provider/catalyze_spec.rb +39 -0
- data/spec/provider/chef_supermarket_spec.rb +51 -0
- data/spec/provider/cloud66_spec.rb +44 -0
- data/spec/provider/cloud_files_spec.rb +88 -0
- data/spec/provider/cloudfoundry_spec.rb +71 -0
- data/spec/provider/code_deploy_spec.rb +360 -0
- data/spec/provider/deis_spec.rb +116 -0
- data/spec/provider/divshot_spec.rb +28 -0
- data/spec/provider/elastic_beanstalk_spec.rb +209 -0
- data/spec/provider/firebase_spec.rb +40 -0
- data/spec/provider/gae_spec.rb +26 -0
- data/spec/provider/gcs_spec.rb +115 -0
- data/spec/provider/hackage_spec.rb +47 -0
- data/spec/provider/heroku_spec.rb +357 -0
- data/spec/provider/lambda_spec.rb +432 -0
- data/spec/provider/launchpad_spec.rb +33 -0
- data/spec/provider/modulus_spec.rb +29 -0
- data/spec/provider/npm_spec.rb +95 -0
- data/spec/provider/openshift_spec.rb +91 -0
- data/spec/provider/ops_works_spec.rb +127 -0
- data/spec/provider/packagecloud_spec.rb +56 -0
- data/spec/provider/puppet_forge_spec.rb +60 -0
- data/spec/provider/pypi_spec.rb +103 -0
- data/spec/provider/releases_spec.rb +303 -0
- data/spec/provider/rubygems_spec.rb +106 -0
- data/spec/provider/s3_spec.rb +174 -0
- data/spec/provider/scalingo_spec.rb +64 -0
- data/spec/provider/script_spec.rb +26 -0
- data/spec/provider/surge_spec.rb +15 -0
- data/spec/provider/testfairy_spec.rb +86 -0
- data/spec/provider/transifex_spec.rb +110 -0
- data/spec/provider_spec.rb +210 -0
- data/spec/spec_helper.rb +20 -0
- metadata +279 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module DPL
|
|
2
|
+
class Provider
|
|
3
|
+
class GAE < Provider
|
|
4
|
+
experimental 'Google App Engine'
|
|
5
|
+
|
|
6
|
+
BASE='https://dl.google.com/dl/cloudsdk/channels/rapid/'
|
|
7
|
+
NAME='google-cloud-sdk'
|
|
8
|
+
EXT='.tar.gz'
|
|
9
|
+
INSTALL='~'
|
|
10
|
+
BOOTSTRAP="#{INSTALL}/#{NAME}/bin/bootstrapping/install.py"
|
|
11
|
+
GCLOUD="#{INSTALL}/#{NAME}/bin/gcloud"
|
|
12
|
+
|
|
13
|
+
def with_python_2_7(cmd)
|
|
14
|
+
cmd.gsub!(/'/, "'\\\\''")
|
|
15
|
+
context.shell("bash -c 'source #{context.env['HOME']}/virtualenv/python2.7/bin/activate; #{cmd}'")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def install_deploy_dependencies
|
|
19
|
+
if File.exists? GCLOUD
|
|
20
|
+
return
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
$stderr.puts 'Python 2.7 Version'
|
|
24
|
+
|
|
25
|
+
unless with_python_2_7("python -c 'import sys; print(sys.version)'")
|
|
26
|
+
error 'Could not use python2.7'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
$stderr.puts 'Downloading Google Cloud SDK ...'
|
|
30
|
+
|
|
31
|
+
unless context.shell("curl -L #{BASE + NAME + EXT} | gzip -d | tar -x -C #{INSTALL}")
|
|
32
|
+
error 'Could not download Google Cloud SDK.'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
$stderr.puts 'Bootstrapping Google Cloud SDK ...'
|
|
36
|
+
|
|
37
|
+
unless with_python_2_7("#{BOOTSTRAP} --usage-reporting=false --command-completion=false --path-update=false")
|
|
38
|
+
error 'Could not bootstrap Google Cloud SDK.'
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def needs_key?
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def check_auth
|
|
47
|
+
unless with_python_2_7("#{GCLOUD} -q auth activate-service-account --key-file #{keyfile}")
|
|
48
|
+
error 'Authentication failed.'
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def keyfile
|
|
53
|
+
options[:keyfile] || context.env['GOOGLECLOUDKEYFILE'] || 'service-account.json'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def project
|
|
57
|
+
options[:project] || context.env['GOOGLECLOUDPROJECT'] || context.env['CLOUDSDK_CORE_PROJECT'] || File.dirname(context.env['TRAVIS_REPO_SLUG'] || '')
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def version
|
|
61
|
+
options[:version]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def config
|
|
65
|
+
options[:config] || 'app.yaml'
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def no_promote
|
|
69
|
+
options[:no_promote]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def verbosity
|
|
73
|
+
options[:verbosity] || 'warning'
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def no_stop_previous_version
|
|
77
|
+
options[:no_stop_previous_version]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def push_app
|
|
81
|
+
command = GCLOUD
|
|
82
|
+
command << ' --quiet'
|
|
83
|
+
command << " --verbosity \"#{verbosity}\""
|
|
84
|
+
command << " --project \"#{project}\""
|
|
85
|
+
command << " app deploy \"#{config}\""
|
|
86
|
+
command << " --version \"#{version}\"" unless version.to_s.empty?
|
|
87
|
+
command << " --#{no_promote ? 'no-' : ''}promote"
|
|
88
|
+
command << ' --no-stop-previous-version' unless no_stop_previous_version.to_s.empty?
|
|
89
|
+
unless with_python_2_7(command)
|
|
90
|
+
log 'Deployment failed.'
|
|
91
|
+
context.shell('find $HOME/.config/gcloud/logs -type f -print -exec cat {} \;')
|
|
92
|
+
error ''
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require 'kconv'
|
|
2
|
+
|
|
3
|
+
module DPL
|
|
4
|
+
class Provider
|
|
5
|
+
class GCS < Provider
|
|
6
|
+
requires 'gstore'
|
|
7
|
+
requires 'mime-types', version: '~> 2.0'
|
|
8
|
+
|
|
9
|
+
def needs_key?
|
|
10
|
+
false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def client
|
|
14
|
+
@client ||= GStore::Client.new(
|
|
15
|
+
:access_key => option(:access_key_id),
|
|
16
|
+
:secret_key => option(:secret_access_key)
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def check_auth
|
|
21
|
+
log "Logging in with Access Key: #{option(:access_key_id)[-4..-1].rjust(20, '*')}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def upload_path(filename)
|
|
25
|
+
[options[:upload_dir], filename].compact.join("/")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def push_app
|
|
29
|
+
glob_args = ["**/*"]
|
|
30
|
+
glob_args << File::FNM_DOTMATCH if options[:dot_match]
|
|
31
|
+
Dir.chdir(options.fetch(:local_dir, Dir.pwd)) do
|
|
32
|
+
Dir.glob(*glob_args) do |filename|
|
|
33
|
+
next if File.directory?(filename)
|
|
34
|
+
content_type = MIME::Types.type_for(filename).first.to_s
|
|
35
|
+
opts = { :"Content-Type" => content_type }.merge(encoding_option_for(filename))
|
|
36
|
+
opts["Cache-Control"] = options[:cache_control] if options[:cache_control]
|
|
37
|
+
opts["x-goog-acl"] = options[:acl] if options[:acl]
|
|
38
|
+
|
|
39
|
+
client.put_object(
|
|
40
|
+
option(:bucket),
|
|
41
|
+
upload_path(filename),
|
|
42
|
+
{ :data => File.read(filename), :headers => opts }
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
def encoding_option_for(path)
|
|
50
|
+
if detect_encoding? && encoding_for(path)
|
|
51
|
+
{"Content-Encoding" => encoding_for(path)}
|
|
52
|
+
else
|
|
53
|
+
{}
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module DPL
|
|
2
|
+
class Provider
|
|
3
|
+
class Hackage < Provider
|
|
4
|
+
apt_get 'cabal', 'cabal-install'
|
|
5
|
+
|
|
6
|
+
def check_auth
|
|
7
|
+
unless option(:username) and option(:password)
|
|
8
|
+
raise Error, "must supply username and password"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def check_app
|
|
13
|
+
context.shell "cabal check" or raise Error, "cabal check failed"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def needs_key?
|
|
17
|
+
false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def push_app
|
|
21
|
+
context.shell "cabal sdist" or raise Error, "cabal sdist failed"
|
|
22
|
+
Dir.glob("dist/*.tar.gz") do |tar|
|
|
23
|
+
context.shell "cabal upload --username=#{option(:username)} --password=#{option(:password)} #{tar}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module DPL
|
|
2
|
+
class Provider
|
|
3
|
+
module Heroku
|
|
4
|
+
autoload :API, 'dpl/provider/heroku/api'
|
|
5
|
+
autoload :Generic, 'dpl/provider/heroku/generic'
|
|
6
|
+
autoload :Git, 'dpl/provider/heroku/git'
|
|
7
|
+
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
def new(context, options)
|
|
11
|
+
strategy = options[:strategy] || 'api'
|
|
12
|
+
constant = constants.detect { |c| c.to_s.downcase == strategy.downcase.gsub(/\W/, '') }
|
|
13
|
+
raise Error, 'unknown strategy %p' % strategy unless constant and constant != Generic
|
|
14
|
+
const_get(constant).new(context, options)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'shellwords'
|
|
3
|
+
require 'logger'
|
|
4
|
+
|
|
5
|
+
module DPL
|
|
6
|
+
class Provider
|
|
7
|
+
module Heroku
|
|
8
|
+
class API < Generic
|
|
9
|
+
attr_reader :build_id
|
|
10
|
+
requires 'faraday'
|
|
11
|
+
requires 'rendezvous'
|
|
12
|
+
|
|
13
|
+
def push_app
|
|
14
|
+
pack_archive
|
|
15
|
+
upload_archive
|
|
16
|
+
trigger_build
|
|
17
|
+
verify_build
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def archive_file
|
|
21
|
+
Shellwords.escape("#{context.env['HOME']}/.dpl.#{option(:app)}.tgz")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def pack_archive
|
|
25
|
+
log "creating application archive"
|
|
26
|
+
context.shell "tar -zcf #{archive_file} --exclude .git ."
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def upload_archive
|
|
30
|
+
log "uploading application archive"
|
|
31
|
+
context.shell "curl #{Shellwords.escape(put_url)} -X PUT -H 'Content-Type:' -H 'Accept: application/vnd.heroku+json; version=3' --data-binary @#{archive_file}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def trigger_build
|
|
35
|
+
log "triggering new deployment"
|
|
36
|
+
response = faraday.post("/apps/#{option(:app)}/builds") do |req|
|
|
37
|
+
req.headers['Content-Type'] = 'application/json'
|
|
38
|
+
req.body = {
|
|
39
|
+
"source_blob" => {
|
|
40
|
+
"url" => get_url,
|
|
41
|
+
"version" => version
|
|
42
|
+
}
|
|
43
|
+
}.to_json
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if response.success?
|
|
47
|
+
@build_id = JSON.parse(response.body)['id']
|
|
48
|
+
output_stream_url = JSON.parse(response.body)['output_stream_url']
|
|
49
|
+
context.shell "curl #{Shellwords.escape(output_stream_url)} -H 'Accept: application/vnd.heroku+json; version=3'"
|
|
50
|
+
else
|
|
51
|
+
handle_error_response(response)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def verify_build
|
|
56
|
+
loop do
|
|
57
|
+
response = faraday.get("/apps/#{option(:app)}/builds/#{build_id}/result")
|
|
58
|
+
exit_code = JSON.parse(response.body)['exit_code']
|
|
59
|
+
if exit_code.nil?
|
|
60
|
+
log "heroku build still pending"
|
|
61
|
+
sleep 5
|
|
62
|
+
next
|
|
63
|
+
elsif exit_code == 0
|
|
64
|
+
break
|
|
65
|
+
else
|
|
66
|
+
error "deploy failed, build exited with code #{exit_code}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def get_url
|
|
72
|
+
source_blob.fetch("get_url")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def put_url
|
|
76
|
+
source_blob.fetch("put_url")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def source_blob
|
|
80
|
+
return @source_blob if @source_blob
|
|
81
|
+
|
|
82
|
+
response = faraday.post('/sources')
|
|
83
|
+
|
|
84
|
+
if response.success?
|
|
85
|
+
@source_blob = JSON.parse(response.body)["source_blob"]
|
|
86
|
+
else
|
|
87
|
+
handle_error_response(response)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def version
|
|
92
|
+
@version ||= options[:version] || context.env['TRAVIS_COMMIT'] || `git rev-parse HEAD`.strip
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module DPL
|
|
4
|
+
class Provider
|
|
5
|
+
module Heroku
|
|
6
|
+
class Generic < Provider
|
|
7
|
+
requires 'rendezvous'
|
|
8
|
+
requires 'faraday'
|
|
9
|
+
|
|
10
|
+
attr_reader :app, :user
|
|
11
|
+
|
|
12
|
+
def needs_key?
|
|
13
|
+
false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def faraday
|
|
17
|
+
return @conn if @conn
|
|
18
|
+
headers = { "Accept" => "application/vnd.heroku+json; version=3" }
|
|
19
|
+
|
|
20
|
+
if options[:user] and options[:password]
|
|
21
|
+
# no-op
|
|
22
|
+
else
|
|
23
|
+
headers.merge!({ "Authorization" => "Bearer #{option(:api_key)}" })
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@conn = Faraday.new( url: 'https://api.heroku.com', headers: headers ) do |faraday|
|
|
27
|
+
if options[:user] and options[:password]
|
|
28
|
+
faraday.basic_auth(options[:user], options[:password])
|
|
29
|
+
end
|
|
30
|
+
if log_level = options[:log_level]
|
|
31
|
+
logger = Logger.new($stderr)
|
|
32
|
+
logger.level = Logger.const_get(log_level.upcase)
|
|
33
|
+
|
|
34
|
+
faraday.response :logger, logger do | logger |
|
|
35
|
+
logger.filter(/(.*Authorization: ).*/,'\1[REDACTED]')
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
faraday.adapter Faraday.default_adapter
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def check_auth
|
|
43
|
+
response = faraday.get('/account')
|
|
44
|
+
|
|
45
|
+
if response.success?
|
|
46
|
+
email = JSON.parse(response.body)["email"]
|
|
47
|
+
@user = email
|
|
48
|
+
log "authentication succeeded"
|
|
49
|
+
else
|
|
50
|
+
handle_error_response(response)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def handle_error_response(response)
|
|
55
|
+
error_response = JSON.parse(response.body)
|
|
56
|
+
error "API request failed.\nMessage: #{error_response["message"]}\nReference: #{error_response["url"]}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def check_app
|
|
60
|
+
log "checking for app #{option(:app)}"
|
|
61
|
+
response = faraday.get("/apps/#{option(:app)}")
|
|
62
|
+
if response.success?
|
|
63
|
+
@app = JSON.parse(response.body)
|
|
64
|
+
log "found app #{@app["name"]}"
|
|
65
|
+
else
|
|
66
|
+
handle_error_response(response)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def restart
|
|
71
|
+
response = faraday.delete "/apps/#{option(:app)}/dynos" do |req|
|
|
72
|
+
req.headers['Content-Type'] = 'application/json'
|
|
73
|
+
end
|
|
74
|
+
unless response.success?
|
|
75
|
+
handle_error_response(response)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def run(command)
|
|
80
|
+
response = faraday.post "/apps/#{option(:app)}/dynos" do |req|
|
|
81
|
+
req.headers['Content-Type'] = 'application/json'
|
|
82
|
+
req.body = {"command" => command, "attach" => true}.to_json
|
|
83
|
+
end
|
|
84
|
+
if response.success?
|
|
85
|
+
rendezvous_url = JSON.parse(response.body)["attach_url"]
|
|
86
|
+
Rendezvous.start(url: rendezvous_url)
|
|
87
|
+
else
|
|
88
|
+
handle_error_response(response)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module DPL
|
|
2
|
+
class Provider
|
|
3
|
+
module Heroku
|
|
4
|
+
class Git < Generic
|
|
5
|
+
requires 'netrc'
|
|
6
|
+
|
|
7
|
+
def git_url
|
|
8
|
+
"https://git.heroku.com/#{option(:app)}.git"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def push_app
|
|
12
|
+
git_remote = options[:git] || git_url
|
|
13
|
+
write_netrc if git_remote.start_with?("https://")
|
|
14
|
+
log "$ git fetch origin $TRAVIS_BRANCH --unshallow"
|
|
15
|
+
context.shell "git fetch origin $TRAVIS_BRANCH --unshallow"
|
|
16
|
+
log "$ git push #{git_remote} HEAD:refs/heads/master -f"
|
|
17
|
+
context.shell "git push #{git_remote} HEAD:refs/heads/master -f"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def write_netrc
|
|
21
|
+
n = Netrc.read
|
|
22
|
+
n['git.heroku.com'] = [user, option(:api_key)]
|
|
23
|
+
n.save
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'tempfile'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module DPL
|
|
6
|
+
class Provider
|
|
7
|
+
class Lambda < Provider
|
|
8
|
+
requires 'aws-sdk', version: '~> 2.0'
|
|
9
|
+
requires 'rubyzip', load: 'zip'
|
|
10
|
+
|
|
11
|
+
def lambda
|
|
12
|
+
@lambda ||= ::Aws::Lambda::Client.new(lambda_options)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def lambda_options
|
|
16
|
+
{
|
|
17
|
+
region: options[:region] || 'us-east-1',
|
|
18
|
+
credentials: ::Aws::Credentials.new(option(:access_key_id), option(:secret_access_key))
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def push_app
|
|
23
|
+
|
|
24
|
+
# The original LambdaPreview client supported create/update in one call
|
|
25
|
+
# To keep compatibility we try to fetch the function and then decide
|
|
26
|
+
# whether to update the code or create a new function
|
|
27
|
+
|
|
28
|
+
function_name = options[:name] || option(:function_name)
|
|
29
|
+
|
|
30
|
+
begin
|
|
31
|
+
response = lambda.get_function({function_name: function_name})
|
|
32
|
+
|
|
33
|
+
log "Function #{function_name} already exists, updating."
|
|
34
|
+
|
|
35
|
+
# Options defined at
|
|
36
|
+
# https://docs.aws.amazon.com/sdkforruby/api/Aws/Lambda/Client.html#update_function_configuration-instance_method
|
|
37
|
+
response = lambda.update_function_configuration({
|
|
38
|
+
function_name: function_name,
|
|
39
|
+
description: options[:description] || default_description,
|
|
40
|
+
timeout: options[:timeout] || default_timeout,
|
|
41
|
+
memory_size: options[:memory_size] || default_memory_size,
|
|
42
|
+
role: option(:role),
|
|
43
|
+
handler: handler,
|
|
44
|
+
runtime: options[:runtime] || default_runtime,
|
|
45
|
+
vpc_config: vpc_config,
|
|
46
|
+
environment: environment_variables,
|
|
47
|
+
dead_letter_config: dead_letter_arn,
|
|
48
|
+
kms_key_arn: options[:kms_key_arn] || default_kms_key_arn,
|
|
49
|
+
tracing_config: tracing_mode
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
log "Updated configuration of function: #{response.function_name}."
|
|
53
|
+
|
|
54
|
+
if function_tags
|
|
55
|
+
log "Add tags to function #{response.function_name}."
|
|
56
|
+
response = lambda.tag_resource({
|
|
57
|
+
resource: response.function_arn,
|
|
58
|
+
tags: function_tags
|
|
59
|
+
})
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Options defined at
|
|
63
|
+
# https://docs.aws.amazon.com/sdkforruby/api/Aws/Lambda/Client.html#update_function_code-instance_method
|
|
64
|
+
response = lambda.update_function_code({
|
|
65
|
+
function_name: options[:name] || option(:function_name),
|
|
66
|
+
zip_file: function_zip,
|
|
67
|
+
publish: publish
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
log "Updated code of function: #{response.function_name}."
|
|
71
|
+
rescue ::Aws::Lambda::Errors::ResourceNotFoundException
|
|
72
|
+
log "Function #{function_name} does not exist, creating."
|
|
73
|
+
# Options defined at
|
|
74
|
+
# https://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html
|
|
75
|
+
response = lambda.create_function({
|
|
76
|
+
function_name: options[:name] || option(:function_name),
|
|
77
|
+
description: options[:description] || default_description,
|
|
78
|
+
timeout: options[:timeout] || default_timeout,
|
|
79
|
+
memory_size: options[:memory_size] || default_memory_size,
|
|
80
|
+
role: option(:role),
|
|
81
|
+
handler: handler,
|
|
82
|
+
code: {
|
|
83
|
+
zip_file: function_zip,
|
|
84
|
+
},
|
|
85
|
+
runtime: options[:runtime] || default_runtime,
|
|
86
|
+
publish: publish,
|
|
87
|
+
vpc_config: vpc_config,
|
|
88
|
+
environment: environment_variables,
|
|
89
|
+
dead_letter_config: dead_letter_arn,
|
|
90
|
+
kms_key_arn: options[:kms_key_arn] || default_kms_key_arn,
|
|
91
|
+
tracing_config: tracing_mode,
|
|
92
|
+
tags: function_tags
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
log "Created lambda: #{response.function_name}."
|
|
96
|
+
end
|
|
97
|
+
rescue ::Aws::Lambda::Errors::ServiceException => exception
|
|
98
|
+
error(exception.message)
|
|
99
|
+
rescue ::Aws::Lambda::Errors::InvalidParameterValueException => exception
|
|
100
|
+
error(exception.message)
|
|
101
|
+
rescue ::Aws::Lambda::Errors::ResourceNotFoundException => exception
|
|
102
|
+
error(exception.message)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def handler
|
|
106
|
+
module_name = options[:module_name] || default_module_name
|
|
107
|
+
handler_name = option(:handler_name)
|
|
108
|
+
|
|
109
|
+
"#{module_name}.#{handler_name}"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def function_zip
|
|
113
|
+
target_zip_path = File.absolute_path(options[:zip] || Dir.pwd)
|
|
114
|
+
dest_file_path = output_file_path
|
|
115
|
+
|
|
116
|
+
if File.directory?(target_zip_path)
|
|
117
|
+
zip_directory(dest_file_path, target_zip_path)
|
|
118
|
+
elsif File.file?(target_zip_path)
|
|
119
|
+
zip_file(dest_file_path, target_zip_path)
|
|
120
|
+
else
|
|
121
|
+
error('Invalid zip option. If set, must be path to directory, js file, or a zip file.')
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
File.new(dest_file_path)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def zip_file(dest_file_path, target_file_path)
|
|
128
|
+
if File.extname(target_file_path) == '.zip'
|
|
129
|
+
# Just copy it to the destination right away, since it is already a zip.
|
|
130
|
+
FileUtils.cp(target_file_path, dest_file_path)
|
|
131
|
+
dest_file_path
|
|
132
|
+
else
|
|
133
|
+
# Zip up the file.
|
|
134
|
+
src_directory_path = File.dirname(target_file_path)
|
|
135
|
+
files = [ target_file_path ]
|
|
136
|
+
|
|
137
|
+
create_zip(dest_file_path, src_directory_path, files)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def zip_directory(dest_file_path, target_directory_path)
|
|
142
|
+
files = Dir[File.join(target_directory_path, '**', '**')]
|
|
143
|
+
create_zip(dest_file_path, target_directory_path, files)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def create_zip(dest_file_path, src_directory_path, files)
|
|
147
|
+
Zip::File.open(dest_file_path, Zip::File::CREATE) do |zipfile|
|
|
148
|
+
files.each do |file|
|
|
149
|
+
zipfile.add(file.sub(src_directory_path + File::SEPARATOR, ''), file)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
dest_file_path
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def needs_key?
|
|
157
|
+
false
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def check_auth
|
|
161
|
+
log "Using Access Key: #{option(:access_key_id)[-4..-1].rjust(20, '*')}"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def output_file_path
|
|
165
|
+
@output_file_path ||= '/tmp/' + random_chars(8) + '-lambda.zip'
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def vpc_config
|
|
169
|
+
options[:subnet_ids] && options[:security_group_ids] ? { :subnet_ids => Array(options[:subnet_ids]), :security_group_ids => Array(options[:security_group_ids]) } : nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def environment_variables
|
|
173
|
+
options[:environment_variables] ? { :variables => split_string_array_to_hash(options[:environment_variables]) } : nil
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def dead_letter_arn
|
|
177
|
+
options[:dead_letter_arn] ? { :target_arn => options[:dead_letter_arn]} : nil
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def tracing_mode
|
|
181
|
+
options[:tracing_mode] ? { :mode => options[:tracing_mode]} : nil
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def default_kms_key_arn
|
|
185
|
+
nil
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def function_tags
|
|
189
|
+
options[:function_tags] ? split_string_array_to_hash(options[:function_tags]) : nil
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def default_runtime
|
|
193
|
+
'nodejs'
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def default_timeout
|
|
197
|
+
3 # seconds
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def default_description
|
|
201
|
+
"Deploy build #{context.env['TRAVIS_BUILD_NUMBER']} to AWS Lambda via Travis CI"
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def default_memory_size
|
|
205
|
+
128
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def default_module_name
|
|
209
|
+
'index'
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def publish
|
|
213
|
+
!!options[:publish]
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def split_string_array_to_hash(arr, delimiter="=")
|
|
217
|
+
variables = {}
|
|
218
|
+
Array(arr).map do |val|
|
|
219
|
+
keyval = val.split(delimiter)
|
|
220
|
+
variables[keyval[0]] = keyval[1]
|
|
221
|
+
end
|
|
222
|
+
variables
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def random_chars(count=8)
|
|
226
|
+
(36**(count-1) + rand(36**count - 36**(count-1))).to_s(36)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def cleanup
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def uncleanup
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|