dpl-connect 1.8.43
Sign up to get free protection for your applications and to get access to all the features.
- 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
|