travis_dpl_test 2.0.3.beta.4.ror
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +172 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +392 -0
- data/Gemfile +32 -0
- data/Gemfile.lock +611 -0
- data/LICENSE +19 -0
- data/README.md +2744 -0
- data/Rakefile +210 -0
- data/bin/dpl +11 -0
- data/config/transliterate.yml +733 -0
- data/dpl.gemspec +23 -0
- data/lib/dpl/assets/atlas/install +19 -0
- data/lib/dpl/assets/convox/install +11 -0
- data/lib/dpl/assets/dpl/README.erb.md +138 -0
- data/lib/dpl/assets/dpl/git_ssh +8 -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 +100 -0
- data/lib/dpl/ctx/bash.rb +549 -0
- data/lib/dpl/ctx/test.rb +255 -0
- data/lib/dpl/ctx.rb +4 -0
- data/lib/dpl/helper/assets.rb +38 -0
- data/lib/dpl/helper/cmd.rb +169 -0
- data/lib/dpl/helper/config_file.rb +49 -0
- data/lib/dpl/helper/cookbook_site_streaming_uploader.rb +249 -0
- data/lib/dpl/helper/env.rb +92 -0
- data/lib/dpl/helper/github.rb +22 -0
- data/lib/dpl/helper/interpolate.rb +160 -0
- data/lib/dpl/helper/memoize.rb +23 -0
- data/lib/dpl/helper/squiggle.rb +24 -0
- data/lib/dpl/helper/transliterate.rb +13 -0
- data/lib/dpl/helper/wrap.rb +11 -0
- data/lib/dpl/helper/zip.rb +71 -0
- data/lib/dpl/provider/dsl.rb +410 -0
- data/lib/dpl/provider/examples.rb +132 -0
- data/lib/dpl/provider/status.rb +61 -0
- data/lib/dpl/provider.rb +651 -0
- data/lib/dpl/providers/anynines.rb +71 -0
- data/lib/dpl/providers/azure_web_apps.rb +63 -0
- data/lib/dpl/providers/bintray.rb +324 -0
- data/lib/dpl/providers/bluemixcloudfoundry.rb +98 -0
- data/lib/dpl/providers/boxfuse.rb +52 -0
- data/lib/dpl/providers/cargo.rb +32 -0
- data/lib/dpl/providers/chef_supermarket.rb +132 -0
- data/lib/dpl/providers/cloud66.rb +46 -0
- data/lib/dpl/providers/cloudfiles.rb +62 -0
- data/lib/dpl/providers/cloudformation.rb +281 -0
- data/lib/dpl/providers/cloudfoundry.rb +89 -0
- data/lib/dpl/providers/codedeploy.rb +190 -0
- data/lib/dpl/providers/convox.rb +130 -0
- data/lib/dpl/providers/datica.rb +64 -0
- data/lib/dpl/providers/ecr.rb +129 -0
- data/lib/dpl/providers/elasticbeanstalk.rb +207 -0
- data/lib/dpl/providers/engineyard.rb +113 -0
- data/lib/dpl/providers/firebase.rb +45 -0
- data/lib/dpl/providers/flynn.rb +35 -0
- data/lib/dpl/providers/gae.rb +78 -0
- data/lib/dpl/providers/gcs.rb +132 -0
- data/lib/dpl/providers/git_push.rb +273 -0
- data/lib/dpl/providers/gleis.rb +74 -0
- data/lib/dpl/providers/hackage.rb +53 -0
- data/lib/dpl/providers/hephy.rb +107 -0
- data/lib/dpl/providers/heroku/api.rb +123 -0
- data/lib/dpl/providers/heroku/git.rb +54 -0
- data/lib/dpl/providers/heroku.rb +111 -0
- data/lib/dpl/providers/lambda.rb +211 -0
- data/lib/dpl/providers/launchpad.rb +80 -0
- data/lib/dpl/providers/netlify.rb +38 -0
- data/lib/dpl/providers/npm.rb +130 -0
- data/lib/dpl/providers/nuget.rb +41 -0
- data/lib/dpl/providers/openshift.rb +52 -0
- data/lib/dpl/providers/opsworks.rb +146 -0
- data/lib/dpl/providers/packagecloud.rb_ +194 -0
- data/lib/dpl/providers/pages/api.rb +106 -0
- data/lib/dpl/providers/pages/git.rb +262 -0
- data/lib/dpl/providers/pages.rb +18 -0
- data/lib/dpl/providers/puppetforge.rb +50 -0
- data/lib/dpl/providers/pypi.rb +125 -0
- data/lib/dpl/providers/releases.rb +234 -0
- data/lib/dpl/providers/rubygems.rb +97 -0
- data/lib/dpl/providers/s3.rb +251 -0
- data/lib/dpl/providers/scalingo.rb +69 -0
- data/lib/dpl/providers/script.rb +32 -0
- data/lib/dpl/providers/snap.rb +68 -0
- data/lib/dpl/providers/surge.rb +59 -0
- data/lib/dpl/providers/testfairy.rb +101 -0
- data/lib/dpl/providers/transifex.rb +72 -0
- data/lib/dpl/providers.rb +48 -0
- data/lib/dpl/string_ext.rb +23 -0
- data/lib/dpl/support/aws_sdk_patch.rb +26 -0
- data/lib/dpl/support/gems.rb +73 -0
- data/lib/dpl/support/gstore_patch.rb +8 -0
- data/lib/dpl/support/version.rb +84 -0
- data/lib/dpl/version.rb +5 -0
- data/lib/dpl.rb +23 -0
- data/status.json +237 -0
- metadata +161 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dpl
|
4
|
+
module Providers
|
5
|
+
class ChefSupermarket < Provider
|
6
|
+
register :'chef-supermarket'
|
7
|
+
register :chef_supermarket
|
8
|
+
|
9
|
+
status :alpha
|
10
|
+
|
11
|
+
full_name 'Chef Supermarket'
|
12
|
+
|
13
|
+
description sq(<<-STR)
|
14
|
+
tbd
|
15
|
+
STR
|
16
|
+
|
17
|
+
gem 'chef', '~> 18', require: %w[
|
18
|
+
chef/cookbook/cookbook_version_loader
|
19
|
+
chef/cookbook_uploader
|
20
|
+
]
|
21
|
+
|
22
|
+
gem 'json'
|
23
|
+
gem 'mime-types', '~> 3.4.1'
|
24
|
+
gem 'net-telnet', '~> 0.1.0' if ruby_pre?('2.3')
|
25
|
+
gem 'rack'
|
26
|
+
|
27
|
+
env :chef
|
28
|
+
|
29
|
+
opt '--user_id ID', 'Chef Supermarket user name', required: true
|
30
|
+
opt '--name NAME', 'Cookbook name', note: 'defaults to the name given in metadata.json or metadata.rb', alias: :cookbook_name, deprecated: :cookbook_name
|
31
|
+
opt '--category CAT', 'Cookbook category in Supermarket', required: true, see: 'https://docs.getchef.com/knife_cookbook_site.html#id12', alias: :cookbook_category, deprecated: :cookbook_category
|
32
|
+
opt '--client_key KEY', 'Client API key file name', default: 'client.pem'
|
33
|
+
opt '--dir DIR', 'Directory containing the cookbook', default: '.'
|
34
|
+
|
35
|
+
URL = 'https://supermarket.chef.io/api/v1/cookbooks'
|
36
|
+
|
37
|
+
msgs validate: 'Validating cookbook',
|
38
|
+
upload: 'Uploading cookbook %{name} to %{url}',
|
39
|
+
missing_file: 'Missing file: %s',
|
40
|
+
unknown_error: 'Unknown error while sharing cookbook: %s',
|
41
|
+
version_exists: 'The same version of this cookbook already exists on the Opscode Cookbook Site.'
|
42
|
+
|
43
|
+
def setup
|
44
|
+
Chef::Config[:client_key] = client_key
|
45
|
+
chdir dir
|
46
|
+
end
|
47
|
+
|
48
|
+
def validate
|
49
|
+
info :validate
|
50
|
+
validate_file client_key
|
51
|
+
uploader.validate_cookbooks
|
52
|
+
end
|
53
|
+
|
54
|
+
def deploy
|
55
|
+
info :upload
|
56
|
+
upload
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def upload
|
62
|
+
res = Chef::Knife::Core::CookbookSiteStreamingUploader.post(URL, user_id, client_key, params)
|
63
|
+
handle_error(res.body) if res.code.to_i != 201
|
64
|
+
end
|
65
|
+
|
66
|
+
def params
|
67
|
+
{ cookbook: json(category: category), tarball: tarball}
|
68
|
+
end
|
69
|
+
|
70
|
+
def tarball
|
71
|
+
shell "tar -czf /tmp/#{name}.tgz -C #{build_dir} ."
|
72
|
+
shell "tar -tvf /tmp/#{name}.tgz"
|
73
|
+
open "/tmp/#{name}.tgz"
|
74
|
+
end
|
75
|
+
|
76
|
+
def name
|
77
|
+
@name ||= name_from_json || name_from_rb || error(:missing_file, 'metadata.json or metadata.rb')
|
78
|
+
end
|
79
|
+
|
80
|
+
def name_from_json
|
81
|
+
JSON.parse(read('metadata.json'))['name'] if file?('metadata.json')
|
82
|
+
end
|
83
|
+
|
84
|
+
def name_from_rb
|
85
|
+
Chef::Cookbook::Metadata.new.from_file('metadata.rb') if file?('metadata.rb')
|
86
|
+
end
|
87
|
+
|
88
|
+
def cookbook
|
89
|
+
@cookbook ||= loader.cookbook_version
|
90
|
+
end
|
91
|
+
|
92
|
+
def loader
|
93
|
+
Chef::Cookbook::CookbookVersionLoader.new('.').tap(&:load!)
|
94
|
+
end
|
95
|
+
|
96
|
+
def uploader
|
97
|
+
Chef::CookbookUploader.new(cookbook)
|
98
|
+
end
|
99
|
+
|
100
|
+
def build_dir
|
101
|
+
@build_dir ||= Chef::Knife::Core::CookbookSiteStreamingUploader.create_build_dir(cookbook)
|
102
|
+
end
|
103
|
+
|
104
|
+
def validate_file(path)
|
105
|
+
error :missing_file, path unless file?(path)
|
106
|
+
end
|
107
|
+
|
108
|
+
def url
|
109
|
+
URL
|
110
|
+
end
|
111
|
+
|
112
|
+
def handle_error(res)
|
113
|
+
res = JSON.parse(res)
|
114
|
+
unknown_error(res) unless res['error_messages']
|
115
|
+
version_exists if res['error_messages'][0].include?('Version already exists')
|
116
|
+
error (res['error_messages'][0]).to_s
|
117
|
+
end
|
118
|
+
|
119
|
+
def unknown_error(msg)
|
120
|
+
error :unknown_error, msg
|
121
|
+
end
|
122
|
+
|
123
|
+
def version_exists
|
124
|
+
error :version_exists
|
125
|
+
end
|
126
|
+
|
127
|
+
def json(obj)
|
128
|
+
JSON.dump(obj)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dpl
|
4
|
+
module Providers
|
5
|
+
class Cloud66 < Provider
|
6
|
+
register :cloud66
|
7
|
+
|
8
|
+
status :alpha
|
9
|
+
|
10
|
+
description sq(<<-STR)
|
11
|
+
tbd
|
12
|
+
STR
|
13
|
+
|
14
|
+
env :cloud66
|
15
|
+
|
16
|
+
opt '--redeployment_hook URL', 'The redeployment hook URL', required: true, secret: true
|
17
|
+
|
18
|
+
msgs failed: 'Redeployment failed (%s)'
|
19
|
+
|
20
|
+
def deploy
|
21
|
+
response = client.request(request)
|
22
|
+
error :failed, response.code if response.code != '200'
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def client
|
28
|
+
Net::HTTP.new(uri.host, uri.port).tap do |client|
|
29
|
+
client.use_ssl = use_ssl?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def request
|
34
|
+
Net::HTTP::Post.new(uri.path)
|
35
|
+
end
|
36
|
+
|
37
|
+
def uri
|
38
|
+
@uri ||= URI.parse(redeployment_hook)
|
39
|
+
end
|
40
|
+
|
41
|
+
def use_ssl?
|
42
|
+
uri.scheme.downcase == 'https'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dpl
|
4
|
+
module Providers
|
5
|
+
class Cloudfiles < Provider
|
6
|
+
register :cloudfiles
|
7
|
+
|
8
|
+
status :alpha
|
9
|
+
|
10
|
+
full_name 'Cloud Files'
|
11
|
+
|
12
|
+
description sq(<<-STR)
|
13
|
+
tbd
|
14
|
+
STR
|
15
|
+
|
16
|
+
gem 'nokogiri', '~> 1.15'
|
17
|
+
gem 'fog-core', '~> 2.3', require: 'fog/core'
|
18
|
+
gem 'fog-rackspace', '~> 0.1.6', git: 'https://github.com/travis-oss/fog-rackspace', require: 'fog/rackspace'
|
19
|
+
|
20
|
+
env :cloudfiles
|
21
|
+
|
22
|
+
opt '--username USER', 'Rackspace username', required: true
|
23
|
+
opt '--api_key KEY', 'Rackspace API key', required: true, secret: true
|
24
|
+
opt '--region REGION', 'Cloudfiles region', required: true, enum: %w[ord dfw syd iad hkg]
|
25
|
+
opt '--container NAME', 'Name of the container that files will be uploaded to', required: true
|
26
|
+
opt '--glob GLOB', 'Paths to upload', default: '**/*'
|
27
|
+
opt '--dot_match', 'Upload hidden files starting a dot'
|
28
|
+
|
29
|
+
msgs missing_container: 'The specified container does not exist.'
|
30
|
+
|
31
|
+
def deploy
|
32
|
+
paths.each do |path|
|
33
|
+
container.files.create(key: path, body: File.open(path))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def paths
|
38
|
+
paths = Dir.glob(*glob)
|
39
|
+
paths.reject { |path| File.directory?(path) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def glob
|
43
|
+
glob = [super]
|
44
|
+
glob << File::FNM_DOTMATCH if dot_match?
|
45
|
+
glob
|
46
|
+
end
|
47
|
+
|
48
|
+
def container
|
49
|
+
@container ||= api.directories.get(super) || error(:missing_container)
|
50
|
+
end
|
51
|
+
|
52
|
+
def api
|
53
|
+
@api ||= Fog::Storage.new(
|
54
|
+
provider: 'Rackspace',
|
55
|
+
rackspace_username: username,
|
56
|
+
rackspace_api_key: api_key,
|
57
|
+
rackspace_region: region
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,281 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dpl
|
4
|
+
module Providers
|
5
|
+
class Cloudformation < Provider
|
6
|
+
register :cloudformation
|
7
|
+
status :stable
|
8
|
+
|
9
|
+
full_name 'AWS CloudFormation'
|
10
|
+
|
11
|
+
description sq(<<-STR)
|
12
|
+
tbd
|
13
|
+
STR
|
14
|
+
|
15
|
+
gem 'aws-sdk-cloudformation', '~> 1.0'
|
16
|
+
|
17
|
+
env :aws, :cloudformation
|
18
|
+
config '~/.aws/credentials', prefix: 'aws'
|
19
|
+
|
20
|
+
opt '--access_key_id ID', 'AWS Access Key ID', required: true, secret: true
|
21
|
+
opt '--secret_access_key KEY', 'AWS Secret Key', required: true, secret: true
|
22
|
+
opt '--region REGION', 'AWS Region to deploy to', default: 'us-east-1'
|
23
|
+
opt '--template STR', 'CloudFormation template file', required: true, note: 'can be either a local path or an S3 URL'
|
24
|
+
opt '--stack_name NAME', 'CloudFormation Stack Name.', required: true
|
25
|
+
opt '--stack_name_prefix STR', 'CloudFormation Stack Name Prefix.'
|
26
|
+
opt '--promote', 'Deploy changes', default: true, note: 'otherwise a change set is created'
|
27
|
+
opt '--role_arn ARN', 'AWS Role ARN'
|
28
|
+
opt '--sts_assume_role ARN', 'AWS Role ARN for cross account deployments (assumed by travis using given AWS credentials).'
|
29
|
+
opt '--capabilities STR', 'CloudFormation allowed capabilities', type: :array, enum: %w[CAPABILITY_IAM CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND], sep: ',', see: 'https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_CreateStack.html'
|
30
|
+
opt '--wait', 'Wait for CloutFormation to finish the stack creation and update', default: true
|
31
|
+
opt '--wait_timeout SEC', 'How many seconds to wait for stack creation and update.', type: :integer, default: 3600
|
32
|
+
opt '--create_timeout SEC', 'How many seconds to wait before the stack status becomes CREATE_FAILED', type: :integer, default: 3600, note: 'valid only when creating a stack'
|
33
|
+
opt '--parameters STR', 'key=value pairs or ENV var names', type: :array, eg: 'one=1 or ENV_VAR_TWO'
|
34
|
+
opt '--output_file PATH', 'Path to output file to store CloudFormation outputs to'
|
35
|
+
|
36
|
+
msgs login: 'Using Access Key: %{access_key_id}',
|
37
|
+
create_stack: 'Creating stack ...',
|
38
|
+
promote_stack: 'Promoting stack ...',
|
39
|
+
create_change_set: 'Creating change set ...',
|
40
|
+
stack_up_to_date: 'Stack already up to date.',
|
41
|
+
delete_change_set: 'No changes in stack. Removing changeset.',
|
42
|
+
done: 'Done.',
|
43
|
+
missing_template: 'File does not exist: %{template}',
|
44
|
+
invalid_creds: 'Invalid credentials'
|
45
|
+
|
46
|
+
strs change_set_name: 'travis-ci-build-%{build_number}-%{now}',
|
47
|
+
change_set_desc: 'Changeset created by Travis CI job for build #%{build_number} (%{git_sha})'
|
48
|
+
|
49
|
+
def login
|
50
|
+
info :login
|
51
|
+
end
|
52
|
+
|
53
|
+
def deploy
|
54
|
+
stack_exists? ? update : create
|
55
|
+
store_events if output_file?
|
56
|
+
rescue Aws::CloudFormation::Errors::InvalidAccessKeyId
|
57
|
+
error :invalid_creds
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def update
|
63
|
+
promote? ? promote : create_change_set(:update)
|
64
|
+
rescue Aws::CloudFormation::Errors::ValidationError => e
|
65
|
+
raise e unless e.message.start_with?('No updates are to be performed')
|
66
|
+
|
67
|
+
info :stack_up_to_date
|
68
|
+
end
|
69
|
+
|
70
|
+
def promote
|
71
|
+
info :promote_stack
|
72
|
+
client.update_stack(common_params)
|
73
|
+
stream_events(stack_name, :stack_update_complete) if wait?
|
74
|
+
info :done
|
75
|
+
end
|
76
|
+
|
77
|
+
def create
|
78
|
+
promote? ? create_stack : create_change_set(:create)
|
79
|
+
end
|
80
|
+
|
81
|
+
def create_stack
|
82
|
+
info :create_stack
|
83
|
+
params = { timeout_in_minutes: create_timeout, on_failure: 'ROLLBACK' }
|
84
|
+
client.create_stack(common_params.merge(params))
|
85
|
+
stream_events(stack_name, :stack_create_complete) if wait?
|
86
|
+
info :done
|
87
|
+
end
|
88
|
+
|
89
|
+
def create_change_set(type)
|
90
|
+
info :create_change_set
|
91
|
+
set = client.create_change_set(common_params.merge(change_set_params(type)))
|
92
|
+
wait_for(:change_set_create_complete, change_set_name: set.id) if wait? && !test?
|
93
|
+
info :done
|
94
|
+
rescue Aws::Waiters::Errors::FailureStateError => e
|
95
|
+
raise e unless change_set_contains_changes?(set)
|
96
|
+
|
97
|
+
info :delete_change_set
|
98
|
+
client.delete_change_set(change_set_name: set.id)
|
99
|
+
end
|
100
|
+
|
101
|
+
def change_set_params(type)
|
102
|
+
{
|
103
|
+
change_set_type: type.to_s.upcase,
|
104
|
+
change_set_name: interpolate(str(:change_set_name)),
|
105
|
+
description: interpolate(str(:change_set_desc))
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
def change_set_contains_changes?(change_set)
|
110
|
+
data = client.describe_change_set(change_set_name: change_set.id)
|
111
|
+
data.status_reason.start_with?(%(The submitted information didn't contain changes))
|
112
|
+
end
|
113
|
+
|
114
|
+
def stack_exists?
|
115
|
+
stack = last_stack
|
116
|
+
stack && stack.stack_status != 'REVIEW_IN_PROGRESS'
|
117
|
+
rescue Aws::CloudFormation::Errors::ValidationError => e
|
118
|
+
raise e unless e.message.include?('does not exist')
|
119
|
+
|
120
|
+
false
|
121
|
+
end
|
122
|
+
|
123
|
+
def stream_events(stack_name, condition)
|
124
|
+
stream = EventStream.new(client, stack_name, method(:info))
|
125
|
+
wait_for(condition, stack_name:) unless test? # hmm.
|
126
|
+
ensure
|
127
|
+
stream&.stop
|
128
|
+
end
|
129
|
+
|
130
|
+
def wait_for(cond, params)
|
131
|
+
started_at = Time.now
|
132
|
+
timeout = ->(*) { throw :failure if Time.now - started_at > wait_timeout }
|
133
|
+
# params = params.merge(max_attempts: nil, delay: 5, before_wait: timeout)
|
134
|
+
client.wait_until(cond, params) { |w| w.before_wait(&timeout) }
|
135
|
+
end
|
136
|
+
|
137
|
+
def store_events
|
138
|
+
logs = last_stack.outputs || {}
|
139
|
+
logs = logs.map { |log| "#{log[:output_key]}=#{log[:output_value]}" }
|
140
|
+
File.write(output_file, logs.join("\n"))
|
141
|
+
end
|
142
|
+
|
143
|
+
def last_stack
|
144
|
+
client.describe_stacks(stack_name:)[:stacks].first
|
145
|
+
end
|
146
|
+
|
147
|
+
def common_params
|
148
|
+
params = {
|
149
|
+
stack_name:,
|
150
|
+
role_arn:,
|
151
|
+
capabilities:,
|
152
|
+
parameters:
|
153
|
+
}
|
154
|
+
params.merge!(template_param)
|
155
|
+
@common_params ||= compact(params)
|
156
|
+
end
|
157
|
+
|
158
|
+
def parameters
|
159
|
+
@parameters ||= Array(super).map do |str|
|
160
|
+
key, value = str.split('=', 2)
|
161
|
+
{ parameter_key: key, parameter_value: value || ENV[key] }
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def create_timeout
|
166
|
+
super / 60
|
167
|
+
end
|
168
|
+
|
169
|
+
def stack_name
|
170
|
+
@stack_name ||= "#{stack_name_prefix}#{super}"
|
171
|
+
end
|
172
|
+
|
173
|
+
def template_param
|
174
|
+
str = template
|
175
|
+
return { template_url: str } if url?(str)
|
176
|
+
return { template_body: read(str) } if file?(str)
|
177
|
+
|
178
|
+
error(:missing_template)
|
179
|
+
end
|
180
|
+
|
181
|
+
def client
|
182
|
+
@client ||= Aws::CloudFormation::Client.new(client_options)
|
183
|
+
end
|
184
|
+
|
185
|
+
def client_options
|
186
|
+
params = { region:, credentials: }
|
187
|
+
params = params.merge(credentials: assume_role(params)) if sts_assume_role?
|
188
|
+
params
|
189
|
+
end
|
190
|
+
|
191
|
+
def credentials
|
192
|
+
Aws::Credentials.new(access_key_id, secret_access_key)
|
193
|
+
end
|
194
|
+
|
195
|
+
def assume_role(params)
|
196
|
+
assumed_role = Aws::STS::Client.new(params).assume_role(
|
197
|
+
role_arn: sts_assume_role,
|
198
|
+
role_session_name: "travis-build-#{build_number}"
|
199
|
+
)
|
200
|
+
Aws::Credentials.new(
|
201
|
+
assumed_role.credentials.access_key_id,
|
202
|
+
assumed_role.credentials.secret_access_key,
|
203
|
+
assumed_role.credentials.session_token
|
204
|
+
)
|
205
|
+
end
|
206
|
+
|
207
|
+
def now
|
208
|
+
Time.now.strftime('%Y%m%d%H%M%S')
|
209
|
+
end
|
210
|
+
|
211
|
+
def url?(str)
|
212
|
+
str =~ %r{^https?://}
|
213
|
+
end
|
214
|
+
|
215
|
+
class EventStream < Struct.new(:client, :stack_name, :handler)
|
216
|
+
attr_reader :thread
|
217
|
+
|
218
|
+
def initialize(*)
|
219
|
+
super
|
220
|
+
@event = describe_stack_events.stack_events.first
|
221
|
+
@thread = Thread.new(&method(:process))
|
222
|
+
end
|
223
|
+
|
224
|
+
def stop
|
225
|
+
mutex.synchronize { @stop = true }
|
226
|
+
thread.join
|
227
|
+
end
|
228
|
+
|
229
|
+
private
|
230
|
+
|
231
|
+
def process
|
232
|
+
until mutex.synchronize { @stop }
|
233
|
+
@event, events = events_since(@event)
|
234
|
+
events.each { |e| handler.call(format_event(e)) }
|
235
|
+
sleep 5 unless ENV['ENV'] == 'test'
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# source: https://github.com/rvedotrc/cfn-events/blob/master/lib/cfn-events/runner.rb
|
240
|
+
def events_since(event)
|
241
|
+
described_stack = describe_stack_events
|
242
|
+
stack_events = described_stack.stack_events
|
243
|
+
return [event, []] if stack_events.first.event_id == event.event_id
|
244
|
+
|
245
|
+
events = []
|
246
|
+
described_stack.each_page do |page|
|
247
|
+
if (oldest_new = page.stack_events.index { |e| e.event_id == event.event_id })
|
248
|
+
events.concat(page.stack_events[0..oldest_new - 1])
|
249
|
+
return [events.first, events.reverse]
|
250
|
+
end
|
251
|
+
events.concat(page.stack_events)
|
252
|
+
end
|
253
|
+
|
254
|
+
warn %(Last-seen stack event is no longer returned by AWS. Please raise this as a provider's bug.)
|
255
|
+
[events.first, events.reverse]
|
256
|
+
end
|
257
|
+
|
258
|
+
def describe_stack_events
|
259
|
+
client.describe_stack_events(stack_name:)
|
260
|
+
end
|
261
|
+
|
262
|
+
def mutex
|
263
|
+
@mutex ||= Mutex.new
|
264
|
+
end
|
265
|
+
|
266
|
+
EVENT_KEYS = %i[timestamp resource_type resource_status logical_resource_id
|
267
|
+
physical_resource_id resource_status_reason].freeze
|
268
|
+
|
269
|
+
def format_event(event)
|
270
|
+
parts = EVENT_KEYS.map { |key| event.send(key) }
|
271
|
+
parts[0] = format_timestamp(parts[0])
|
272
|
+
parts.join(' ')
|
273
|
+
end
|
274
|
+
|
275
|
+
def format_timestamp(timestamp)
|
276
|
+
timestamp.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dpl
|
4
|
+
module Providers
|
5
|
+
class Cloudfoundry < Provider
|
6
|
+
register :cloudfoundry
|
7
|
+
|
8
|
+
status :stable
|
9
|
+
|
10
|
+
full_name 'Cloud Foundry'
|
11
|
+
|
12
|
+
description sq(<<-STR)
|
13
|
+
tbd
|
14
|
+
STR
|
15
|
+
|
16
|
+
env :cloudfoundry
|
17
|
+
|
18
|
+
opt '--username USER', 'Cloud Foundry username', required: true
|
19
|
+
opt '--password PASS', 'Cloud Foundry password', required: true, secret: true
|
20
|
+
opt '--organization ORG', 'Cloud Foundry organization', required: true
|
21
|
+
opt '--space SPACE', 'Cloud Foundry space', required: true
|
22
|
+
opt '--api URL', 'Cloud Foundry api URL', default: 'https://api.run.pivotal.io'
|
23
|
+
opt '--app_name APP', 'Application name'
|
24
|
+
opt '--buildpack PACK', 'Buildpack name or Git URL'
|
25
|
+
opt '--manifest FILE', 'Path to the manifest'
|
26
|
+
opt '--skip_ssl_validation', 'Skip SSL validation'
|
27
|
+
opt '--deployment_strategy STRATEGY', 'Deployment strategy, either rolling or null'
|
28
|
+
opt '--v3', 'Use the v3 API version to push the application'
|
29
|
+
opt '--logout', default: true, internal: true
|
30
|
+
|
31
|
+
cmds install: 'test $(uname) = "Linux" && rel="linux64-binary" || rel="macosx64"; wget "https://cli.run.pivotal.io/stable?release=${rel}&version=v7&source=github" -qO cf.tgz && tar -zxvf cf.tgz && rm cf.tgz',
|
32
|
+
api: './cf api %{api} %{skip_ssl_validation_opt}',
|
33
|
+
login: './cf login -u %{username} -p %{password} -o %{organization} -s %{space}',
|
34
|
+
push: './cf %{push_cmd} %{push_args}',
|
35
|
+
logout: './cf logout'
|
36
|
+
|
37
|
+
errs install: 'Failed to install CLI tools',
|
38
|
+
api: 'Failed to set api %{api}',
|
39
|
+
login: 'Failed to login',
|
40
|
+
push: 'Failed to push app',
|
41
|
+
logout: 'Failed to logout'
|
42
|
+
|
43
|
+
msgs manifest_missing: 'Application must have a manifest.yml for unattended deployment'
|
44
|
+
|
45
|
+
def install
|
46
|
+
shell :install
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate
|
50
|
+
error :manifest_missing if manifest? && manifest_missing?
|
51
|
+
end
|
52
|
+
|
53
|
+
def login
|
54
|
+
shell :api
|
55
|
+
shell :login
|
56
|
+
end
|
57
|
+
|
58
|
+
def deploy
|
59
|
+
shell :push
|
60
|
+
end
|
61
|
+
|
62
|
+
def finish
|
63
|
+
shell :logout if logout?
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def push_cmd
|
69
|
+
v3? ? 'v3-push' : 'push'
|
70
|
+
end
|
71
|
+
|
72
|
+
def push_args
|
73
|
+
args = []
|
74
|
+
args << quote(app_name) if app_name?
|
75
|
+
args << "-f #{manifest}" if manifest?
|
76
|
+
args << "--strategy #{deployment_strategy}" if deployment_strategy?
|
77
|
+
args.join(' ')
|
78
|
+
end
|
79
|
+
|
80
|
+
def skip_ssl_validation_opt
|
81
|
+
'--skip-ssl-validation' if skip_ssl_validation?
|
82
|
+
end
|
83
|
+
|
84
|
+
def manifest_missing?
|
85
|
+
!File.exist?(manifest)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|