travis_dpl_test 2.0.3.beta.4.ror
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/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,249 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Author:: Stanislav Vitvitskiy
|
|
3
|
+
# Author:: Nuo Yan (nuo@chef.io)
|
|
4
|
+
# Author:: Christopher Walters (<cw@chef.io>)
|
|
5
|
+
# Copyright:: Copyright (c) Chef Software Inc.
|
|
6
|
+
# License:: Apache License, Version 2.0
|
|
7
|
+
#
|
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
# you may not use this file except in compliance with the License.
|
|
10
|
+
# You may obtain a copy of the License at
|
|
11
|
+
#
|
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
#
|
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
# See the License for the specific language governing permissions and
|
|
18
|
+
# limitations under the License.
|
|
19
|
+
#
|
|
20
|
+
|
|
21
|
+
autoload :URI, "uri"
|
|
22
|
+
module Net
|
|
23
|
+
autoload :HTTP, "net/http"
|
|
24
|
+
end
|
|
25
|
+
autoload :OpenSSL, "openssl"
|
|
26
|
+
module Mixlib
|
|
27
|
+
module Authentication
|
|
28
|
+
autoload :SignedHeaderAuth, "mixlib/authentication/signedheaderauth"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
require 'chef'
|
|
32
|
+
require "chef-utils/dist" unless defined?(ChefUtils::Dist)
|
|
33
|
+
class Chef
|
|
34
|
+
class Knife
|
|
35
|
+
module Core
|
|
36
|
+
# == Chef::Knife::Core::CookbookSiteStreamingUploader
|
|
37
|
+
# A streaming multipart HTTP upload implementation. Used to upload cookbooks
|
|
38
|
+
# (in tarball form) to https://supermarket.chef.io
|
|
39
|
+
#
|
|
40
|
+
# inspired by http://stanislavvitvitskiy.blogspot.com/2008/12/multipart-post-in-ruby.html
|
|
41
|
+
class CookbookSiteStreamingUploader
|
|
42
|
+
|
|
43
|
+
DefaultHeaders = { "accept" => "application/json", "x-chef-version" => ::Chef::VERSION }.freeze # rubocop:disable Naming/ConstantName
|
|
44
|
+
|
|
45
|
+
class << self
|
|
46
|
+
|
|
47
|
+
def create_build_dir(cookbook)
|
|
48
|
+
tmp_cookbook_path = Tempfile.new("#{ChefUtils::Dist::Infra::SHORT}-#{cookbook.name}-build")
|
|
49
|
+
tmp_cookbook_path.close
|
|
50
|
+
tmp_cookbook_dir = tmp_cookbook_path.path
|
|
51
|
+
File.unlink(tmp_cookbook_dir)
|
|
52
|
+
FileUtils.mkdir_p(tmp_cookbook_dir)
|
|
53
|
+
Chef::Log.trace("Staging at #{tmp_cookbook_dir}")
|
|
54
|
+
checksums_to_on_disk_paths = cookbook.checksums
|
|
55
|
+
cookbook.each_file do |manifest_record|
|
|
56
|
+
path_in_cookbook = manifest_record[:path]
|
|
57
|
+
on_disk_path = checksums_to_on_disk_paths[manifest_record[:checksum]]
|
|
58
|
+
dest = File.join(tmp_cookbook_dir, cookbook.name.to_s, path_in_cookbook)
|
|
59
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
60
|
+
Chef::Log.trace("Staging #{on_disk_path} to #{dest}")
|
|
61
|
+
FileUtils.cp(on_disk_path, dest)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# First, generate metadata
|
|
65
|
+
Chef::Log.trace("Generating metadata")
|
|
66
|
+
kcm = Chef::Knife::CookbookMetadata.new
|
|
67
|
+
kcm.config[:cookbook_path] = [ tmp_cookbook_dir ]
|
|
68
|
+
kcm.name_args = [ cookbook.name.to_s ]
|
|
69
|
+
kcm.run
|
|
70
|
+
|
|
71
|
+
tmp_cookbook_dir
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def post(to_url, user_id, secret_key_filename, params = {}, headers = {})
|
|
75
|
+
make_request(:post, to_url, user_id, secret_key_filename, params, headers)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def put(to_url, user_id, secret_key_filename, params = {}, headers = {})
|
|
79
|
+
make_request(:put, to_url, user_id, secret_key_filename, params, headers)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def make_request(http_verb, to_url, user_id, secret_key_filename, params = {}, headers = {})
|
|
83
|
+
boundary = "----RubyMultipartClient" + rand(1000000).to_s + "ZZZZZ"
|
|
84
|
+
parts = []
|
|
85
|
+
content_file = nil
|
|
86
|
+
|
|
87
|
+
secret_key = OpenSSL::PKey::RSA.new(File.read(secret_key_filename))
|
|
88
|
+
|
|
89
|
+
unless params.nil? || params.empty?
|
|
90
|
+
params.each do |key, value|
|
|
91
|
+
if value.is_a?(File)
|
|
92
|
+
content_file = value
|
|
93
|
+
filepath = value.path
|
|
94
|
+
filename = File.basename(filepath)
|
|
95
|
+
parts << StringPart.new( "--" + boundary + "\r\n" +
|
|
96
|
+
"Content-Disposition: form-data; name=\"" + key.to_s + "\"; filename=\"" + filename + "\"\r\n" +
|
|
97
|
+
"Content-Type: application/octet-stream\r\n\r\n")
|
|
98
|
+
parts << StreamPart.new(value, File.size(filepath))
|
|
99
|
+
parts << StringPart.new("\r\n")
|
|
100
|
+
else
|
|
101
|
+
parts << StringPart.new( "--" + boundary + "\r\n" +
|
|
102
|
+
"Content-Disposition: form-data; name=\"" + key.to_s + "\"\r\n\r\n")
|
|
103
|
+
parts << StringPart.new(value.to_s + "\r\n")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
parts << StringPart.new("--" + boundary + "--\r\n")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
body_stream = MultipartStream.new(parts)
|
|
110
|
+
|
|
111
|
+
timestamp = Time.now.utc.iso8601
|
|
112
|
+
|
|
113
|
+
url = URI.parse(to_url)
|
|
114
|
+
|
|
115
|
+
Chef::Log.logger.debug("Signing: method: #{http_verb}, url: #{url}, file: #{content_file}, User-id: #{user_id}, Timestamp: #{timestamp}")
|
|
116
|
+
|
|
117
|
+
# We use the body for signing the request if the file parameter
|
|
118
|
+
# wasn't a valid file or wasn't included. Extract the body (with
|
|
119
|
+
# multi-part delimiters intact) to sign the request.
|
|
120
|
+
# TODO: tim: 2009-12-28: It'd be nice to remove this special case, and
|
|
121
|
+
# always hash the entire request body. In the file case it would just be
|
|
122
|
+
# expanded multipart text - the entire body of the POST.
|
|
123
|
+
content_body = parts.inject("") { |result, part| result + part.read(0, part.size) }
|
|
124
|
+
content_file.rewind if content_file # we consumed the file for the above operation, so rewind it.
|
|
125
|
+
|
|
126
|
+
signing_options = {
|
|
127
|
+
http_method: http_verb,
|
|
128
|
+
path: url.path,
|
|
129
|
+
user_id: user_id,
|
|
130
|
+
timestamp: timestamp }
|
|
131
|
+
(content_file && signing_options[:file] = content_file) || (signing_options[:body] = (content_body || ""))
|
|
132
|
+
|
|
133
|
+
headers.merge!(Mixlib::Authentication::SignedHeaderAuth.signing_object(signing_options).sign(secret_key))
|
|
134
|
+
|
|
135
|
+
content_file.rewind if content_file
|
|
136
|
+
|
|
137
|
+
# net/http doesn't like symbols for header keys, so we'll to_s each one just in case
|
|
138
|
+
headers = DefaultHeaders.merge(Hash[*headers.map { |k, v| [k.to_s, v] }.flatten])
|
|
139
|
+
|
|
140
|
+
req = case http_verb
|
|
141
|
+
when :put
|
|
142
|
+
Net::HTTP::Put.new(url.path, headers)
|
|
143
|
+
when :post
|
|
144
|
+
Net::HTTP::Post.new(url.path, headers)
|
|
145
|
+
end
|
|
146
|
+
req.content_length = body_stream.size
|
|
147
|
+
req.content_type = "multipart/form-data; boundary=" + boundary unless parts.empty?
|
|
148
|
+
req.body_stream = body_stream
|
|
149
|
+
|
|
150
|
+
http = Chef::HTTP::BasicClient.new(url).http_client
|
|
151
|
+
res = http.request(req)
|
|
152
|
+
|
|
153
|
+
# alias status to code and to_s to body for test purposes
|
|
154
|
+
# TODO: stop the following madness!
|
|
155
|
+
class << res
|
|
156
|
+
alias :to_s :body
|
|
157
|
+
|
|
158
|
+
# BUG this makes the response compatible with what response_steps expects to test headers (response.headers[] -> response[])
|
|
159
|
+
def headers # rubocop:disable Lint/NestedMethodDefinition
|
|
160
|
+
self
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def status # rubocop:disable Lint/NestedMethodDefinition
|
|
164
|
+
code.to_i
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
res
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
class StreamPart
|
|
173
|
+
def initialize(stream, size)
|
|
174
|
+
@stream, @size = stream, size
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def size
|
|
178
|
+
@size
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# read the specified amount from the stream
|
|
182
|
+
def read(offset, how_much)
|
|
183
|
+
@stream.read(how_much)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
class StringPart
|
|
188
|
+
def initialize(str)
|
|
189
|
+
@str = str
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def size
|
|
193
|
+
@str.length
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# read the specified amount from the string starting at the offset
|
|
197
|
+
def read(offset, how_much)
|
|
198
|
+
@str[offset, how_much]
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
class MultipartStream
|
|
203
|
+
def initialize(parts)
|
|
204
|
+
@parts = parts
|
|
205
|
+
@part_no = 0
|
|
206
|
+
@part_offset = 0
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def size
|
|
210
|
+
@parts.inject(0) { |size, part| size + part.size }
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def read(how_much, dst_buf = nil)
|
|
214
|
+
if @part_no >= @parts.size
|
|
215
|
+
dst_buf.replace("") if dst_buf
|
|
216
|
+
return dst_buf
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
how_much_current_part = @parts[@part_no].size - @part_offset
|
|
220
|
+
|
|
221
|
+
how_much_current_part = if how_much_current_part > how_much
|
|
222
|
+
how_much
|
|
223
|
+
else
|
|
224
|
+
how_much_current_part
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
how_much_next_part = how_much - how_much_current_part
|
|
228
|
+
|
|
229
|
+
current_part = @parts[@part_no].read(@part_offset, how_much_current_part)
|
|
230
|
+
|
|
231
|
+
# recurse into the next part if the current one was not large enough
|
|
232
|
+
if how_much_next_part > 0
|
|
233
|
+
@part_no += 1
|
|
234
|
+
@part_offset = 0
|
|
235
|
+
next_part = read(how_much_next_part)
|
|
236
|
+
result = current_part + (next_part || "")
|
|
237
|
+
else
|
|
238
|
+
@part_offset += how_much_current_part
|
|
239
|
+
result = current_part
|
|
240
|
+
end
|
|
241
|
+
dst_buf ? dst_buf.replace(result || "") : result
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dpl/helper/memoize'
|
|
4
|
+
|
|
5
|
+
module Dpl
|
|
6
|
+
module Env
|
|
7
|
+
def self.included(base)
|
|
8
|
+
base.extend(ClassMethods)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class Env
|
|
12
|
+
include Memoize
|
|
13
|
+
# opts[:allow_skip_underscore] allows unconventional ENV vars such as GOOGLECLOUDKEYFILE
|
|
14
|
+
|
|
15
|
+
attr_reader :cmd, :env, :strs, :keys, :opts
|
|
16
|
+
|
|
17
|
+
def initialize(env, args)
|
|
18
|
+
@env = env
|
|
19
|
+
@opts = args.last.is_a?(Hash) ? args.pop : {}
|
|
20
|
+
@strs = args.map(&:to_s).map(&:upcase)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def env(cmd)
|
|
24
|
+
@cmd = cmd
|
|
25
|
+
env = @env.select { |key, _| keys.include?(key) }
|
|
26
|
+
env = env.transform_keys { |key| unprefix(key).downcase.to_sym }
|
|
27
|
+
env.transform_keys { |key| dealias(key) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def description(cmd)
|
|
31
|
+
strs = self.strs.map { |str| "#{str}_" }
|
|
32
|
+
strs += self.strs if opts[:allow_skip_underscore]
|
|
33
|
+
strs = strs.size > 1 ? "[#{strs.sort.join('|')}]" : strs.join
|
|
34
|
+
"Options can be given via env vars if prefixed with `#{strs}`. #{example(cmd)}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def example(cmd)
|
|
38
|
+
return unless opt = cmd.opts.detect { |option| option.secret? }
|
|
39
|
+
|
|
40
|
+
env = strs.map { |str| "`#{str}_#{opt.name.upcase}=<#{opt.name}>`" }
|
|
41
|
+
env += strs.map { |str| "`#{str}#{opt.name.upcase}=<#{opt.name}>`" } if opts[:allow_skip_underscore]
|
|
42
|
+
"E.g. the option `--#{opt.name}` can be given as #{sentence(env)}."
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def sentence(strs)
|
|
46
|
+
return strs.join if strs.size == 1
|
|
47
|
+
|
|
48
|
+
[strs[0..-2].join(', '), strs[-1]].join(' or ')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def dealias(key)
|
|
54
|
+
opt = cmd.opts.detect { |option| option.aliases.include?(key) }
|
|
55
|
+
opt ? opt.name : key
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def unprefix(key)
|
|
59
|
+
strs.inject(key) { |key, str| key.sub(/^#{str}_?/, '') }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def keys
|
|
63
|
+
keys = cmd.opts.map(&:name) + cmd.opts.map(&:aliases).flatten
|
|
64
|
+
strs.map { |str| keys.map { |key| keys_for(str, key) } }.flatten
|
|
65
|
+
end
|
|
66
|
+
memoize :keys
|
|
67
|
+
|
|
68
|
+
def keys_for(str, key)
|
|
69
|
+
keys = [["#{str}_", key.upcase].join]
|
|
70
|
+
keys << [str, key.upcase].join if opts[:allow_skip_underscore]
|
|
71
|
+
keys
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# should this sit in Cl?
|
|
76
|
+
module ClassMethods
|
|
77
|
+
def env(*strs)
|
|
78
|
+
if strs.any?
|
|
79
|
+
@env = Env.new(ENV, strs)
|
|
80
|
+
elsif env = @env || superclass.instance_variable_get(:@env)
|
|
81
|
+
env.env(self)
|
|
82
|
+
else
|
|
83
|
+
{}
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def opts
|
|
89
|
+
@opts ||= self.class.env.merge(super)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dpl/helper/transliterate'
|
|
4
|
+
|
|
5
|
+
# I18n.load_path << File.expand_path('config/transliterate.yml')
|
|
6
|
+
# I18n.eager_load!
|
|
7
|
+
# I18n.config.available_locales_set << :en # seems really wrong, but ¯\_(ツ)_/¯
|
|
8
|
+
|
|
9
|
+
module Dpl
|
|
10
|
+
module Github
|
|
11
|
+
include Transliterate
|
|
12
|
+
|
|
13
|
+
def normalize_filename(str)
|
|
14
|
+
str = File.basename(str)
|
|
15
|
+
str = str.split(' ').first
|
|
16
|
+
str = transliterate(str)
|
|
17
|
+
str.gsub(/[^\w@+\-_]/, '.')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
extend self
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
module Dpl
|
|
6
|
+
module Interpolate
|
|
7
|
+
# Interpolates variables in the given string.
|
|
8
|
+
#
|
|
9
|
+
# Variables can be contained in scripts, shell commands, and messages.
|
|
10
|
+
# They have the syntax `%{name}` or `%s` (or any other identifier supported
|
|
11
|
+
# by [Kernel#sprintf](https://ruby-doc.org/core-2.6.3/Kernel.html#method-i-format)).
|
|
12
|
+
#
|
|
13
|
+
# This supports two styles of interpolation:
|
|
14
|
+
#
|
|
15
|
+
# * Named variables `%{name}` and
|
|
16
|
+
# * Positional variables.
|
|
17
|
+
#
|
|
18
|
+
# Named variable names need to match constants on the provider class, or
|
|
19
|
+
# methods on the provider instance, which will be called in order to
|
|
20
|
+
# evaluate the value to be interpolated.
|
|
21
|
+
#
|
|
22
|
+
# Positional variables can be used if no corresponding method exists, e.g.
|
|
23
|
+
# if the value that needs to be interpolated is an argument passed to a
|
|
24
|
+
# local method.
|
|
25
|
+
#
|
|
26
|
+
# For example, using named variables:
|
|
27
|
+
#
|
|
28
|
+
# ```ruby
|
|
29
|
+
# def upload_file
|
|
30
|
+
# interpolate('Uploading file %{file} to %{target}')
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# def file
|
|
34
|
+
# './file_name'
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# def target
|
|
38
|
+
# 'target host'
|
|
39
|
+
# end
|
|
40
|
+
# ```
|
|
41
|
+
#
|
|
42
|
+
# Using positional variables:
|
|
43
|
+
#
|
|
44
|
+
# ```ruby
|
|
45
|
+
# def upload_file(file, target)
|
|
46
|
+
# interpolate('Uploading file %s to %s', file, target)
|
|
47
|
+
# end
|
|
48
|
+
# ```
|
|
49
|
+
#
|
|
50
|
+
# Implementors are encouraged to use named variables when possible, but
|
|
51
|
+
# are free to choose according to their needs.
|
|
52
|
+
def interpolate(str, args = [], opts = {})
|
|
53
|
+
args = args.shift if args.is_a?(Array) && args.first.is_a?(Hash)
|
|
54
|
+
Interpolator.new(str, self, args || {}, opts).apply
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Interpolation variables as declared by the provider.
|
|
58
|
+
#
|
|
59
|
+
# By default this contains string option names, but additional
|
|
60
|
+
# methods can be added using Provider::Dsl#vars.
|
|
61
|
+
def vars
|
|
62
|
+
self.class.vars
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Obfuscates the given string.
|
|
66
|
+
#
|
|
67
|
+
# Replaces all but the first N characters with asterisks, and paddes
|
|
68
|
+
# the string to a standard length of 20 characters. N depends on the
|
|
69
|
+
# length of the original string.
|
|
70
|
+
def obfuscate(str, opts = {})
|
|
71
|
+
return str if opts[:secure] || !str.blacklisted?
|
|
72
|
+
|
|
73
|
+
keep = (str.length / (4.0 + str.length / 5).round).round
|
|
74
|
+
keep = 1 if keep.zero?
|
|
75
|
+
str[0, keep] + '*' * (20 - keep)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class Interpolator < Struct.new(:str, :obj, :args, :opts)
|
|
79
|
+
include Interpolate
|
|
80
|
+
|
|
81
|
+
MODIFIER = %i[obfuscate escape quote].freeze
|
|
82
|
+
PATTERN = /%\{(\$?[\w]+)\}/
|
|
83
|
+
ENV_VAR = /^\$[A-Z_]+$/
|
|
84
|
+
UPCASE = /^[A-Z_]+$/
|
|
85
|
+
UNKNOWN = '[unknown variable: %s]'
|
|
86
|
+
|
|
87
|
+
def apply
|
|
88
|
+
str = interpolate(self.str.to_s)
|
|
89
|
+
str = obfuscate(str) unless opts[:secure]
|
|
90
|
+
str = str.gsub(' ', ' ') if str.lines.size == 1
|
|
91
|
+
str
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def interpolate(str)
|
|
95
|
+
str = str % args if args.is_a?(Array) && args.any?
|
|
96
|
+
@blacklist_result = false
|
|
97
|
+
str = str.to_s.gsub(PATTERN) do
|
|
98
|
+
@blacklist_result = true
|
|
99
|
+
normalize(lookup(::Regexp.last_match(1).to_sym))
|
|
100
|
+
end
|
|
101
|
+
@blacklist_result || (args.is_a?(Array) && args.any? { |arg| arg.is_a?(String) && arg.blacklisted? }) ? str.blacklist : str
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def obfuscate(str)
|
|
105
|
+
secrets(str).inject(str) do |str, secret|
|
|
106
|
+
secret = secret.dup if secret.frozen?
|
|
107
|
+
secret.blacklist if str.blacklisted?
|
|
108
|
+
str.gsub(secret, super(secret))
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def secrets(str)
|
|
113
|
+
return [] unless str.is_a?(String) && str.blacklisted?
|
|
114
|
+
|
|
115
|
+
opts = obj.class.opts.select(&:secret?)
|
|
116
|
+
secrets = opts.map { |opt| obj.opts[opt.name] }.compact
|
|
117
|
+
secrets.select { |secret| str.include?(secret) }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def normalize(obj)
|
|
121
|
+
obj.is_a?(Array) ? obj.join(' ') : obj.to_s
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def lookup(key)
|
|
125
|
+
if vars? && !var?(key)
|
|
126
|
+
UNKNOWN % key
|
|
127
|
+
elsif mod = modifier(key)
|
|
128
|
+
key = key.to_s.sub("#{mod}d_", '')
|
|
129
|
+
obj.send(mod, lookup(key))
|
|
130
|
+
elsif key.to_s =~ ENV_VAR
|
|
131
|
+
ENV[key.to_s.sub('$', '')]
|
|
132
|
+
elsif key.to_s =~ UPCASE && obj.class.const_defined?(key)
|
|
133
|
+
obj.class.const_get(key)
|
|
134
|
+
elsif args.is_a?(Hash) && args.key?(key)
|
|
135
|
+
args[key]
|
|
136
|
+
elsif obj.respond_to?(key, true)
|
|
137
|
+
obj.send(key)
|
|
138
|
+
else
|
|
139
|
+
raise KeyError, key
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def modifier(key)
|
|
144
|
+
MODIFIER.detect { |mod| key.to_s.start_with?("#{mod}d_") }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def var?(key)
|
|
148
|
+
vars.include?(key)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def vars
|
|
152
|
+
opts[:vars]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def vars?
|
|
156
|
+
!!vars
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Memoize
|
|
4
|
+
class ArgsError < StandardError; end
|
|
5
|
+
|
|
6
|
+
module ClassMethods
|
|
7
|
+
def memoize(name)
|
|
8
|
+
ivar = :"@#{name.to_s.sub('?', '_predicate')}"
|
|
9
|
+
prepend Module.new {
|
|
10
|
+
define_method(name) do |*args|
|
|
11
|
+
raise ArgsError, 'cannot pass arguments to memoized method %p' % name unless args.empty?
|
|
12
|
+
return instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
|
13
|
+
|
|
14
|
+
instance_variable_set(ivar, super())
|
|
15
|
+
end
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.included(base)
|
|
21
|
+
base.extend(ClassMethods)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Beloved squiggly heredocs did not exist in Ruby 2.1, which we still want to
|
|
4
|
+
# support, so let's give kudos with a method `sq`.
|
|
5
|
+
module Squiggle
|
|
6
|
+
# Outdents each line on a multiline string by the number of leading
|
|
7
|
+
# whitespace characters on the first line.
|
|
8
|
+
#
|
|
9
|
+
# This method exists so we can unindet heredoc strings the same way that
|
|
10
|
+
# Ruby 2.2's squiggly heredocs work, but still support Ruby 2.1 for the
|
|
11
|
+
# time being.
|
|
12
|
+
#
|
|
13
|
+
# For example:
|
|
14
|
+
#
|
|
15
|
+
# str = sq(<<-str)
|
|
16
|
+
# This multiline string will be outdented by two characters,
|
|
17
|
+
# so the extra indentation on this line will be kept,
|
|
18
|
+
# while this line sits on the same level as the first line.
|
|
19
|
+
# str
|
|
20
|
+
def sq(str)
|
|
21
|
+
width = str =~ /( *)\S/ && ::Regexp.last_match(1).size
|
|
22
|
+
str.lines.map { |line| line.gsub(/^ {#{width}}/, '') }.join
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dpl
|
|
4
|
+
module Transliterate
|
|
5
|
+
APPROXIMATIONS = YAML.load(File.read(File.expand_path('../../../config/transliterate.yml', __dir__)))
|
|
6
|
+
|
|
7
|
+
def transliterate(string, replacement = '.')
|
|
8
|
+
string.gsub(/[^\x00-\x7f]/u) do |char|
|
|
9
|
+
APPROXIMATIONS[char] || replacement
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'tempfile'
|
|
4
|
+
|
|
5
|
+
module Dpl
|
|
6
|
+
class Zip < Struct.new(:src, :dest, :opts)
|
|
7
|
+
ZIP_EXT = %w[.zip .jar].freeze
|
|
8
|
+
|
|
9
|
+
def initialize(*)
|
|
10
|
+
require 'zip'
|
|
11
|
+
super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def zip
|
|
15
|
+
if zip_file?
|
|
16
|
+
File.new(src)
|
|
17
|
+
elsif dir?
|
|
18
|
+
zip_dir
|
|
19
|
+
else
|
|
20
|
+
zip_file
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def zip_dir
|
|
25
|
+
create(Dir.glob(*glob).reject { |path| dir?(path) })
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def zip_file
|
|
29
|
+
create([src])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def create(files)
|
|
33
|
+
::Zip::File.open(dest, ::Zip::File::CREATE) do |zip|
|
|
34
|
+
files.each do |file|
|
|
35
|
+
zip.add(file.sub("#{src}/", ''), file)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
File.new(dest)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def zip_file?
|
|
42
|
+
exts.include?(File.extname(src))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def dir?(path = src)
|
|
46
|
+
File.directory?(path)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def copy
|
|
50
|
+
FileUtils.cp(src, dest)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def glob
|
|
54
|
+
glob = ["#{src}/**/*"]
|
|
55
|
+
glob << File::FNM_DOTMATCH if dot_match?
|
|
56
|
+
glob
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def dot_match?
|
|
60
|
+
opts[:dot_match]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def exts
|
|
64
|
+
opts[:exts] ||= ZIP_EXT
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def opts
|
|
68
|
+
super || {}
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|