cloudformation-tool 0.9.1 → 1.0.1
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 +5 -5
- data/README.md +4 -2
- data/lib/cloud_formation_tool/cli/compile.rb +1 -1
- data/lib/cloud_formation_tool/cloud_formation/lambda_code.rb +80 -31
- data/lib/cloud_formation_tool/cloud_formation.rb +29 -32
- data/lib/cloud_formation_tool/storable.rb +5 -1
- data/lib/cloud_formation_tool/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ef4e953cd8194f197b68c1e110dae6f3a81f5c4e2730fe63950d5ac1392f1b02
|
4
|
+
data.tar.gz: 9d5ea95468d689707ec79534281ea14ae51521d3491787e523a1ef0c11c295a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d43ffe6a9b74696f7a96c36a4f25bcc51322bd61f6d72000133ba6ef13720bd9412132c693597d40018b9bfd771038e576a1739e3c0b2e1cf895ecbdfaaaeee1
|
7
|
+
data.tar.gz: 1b6226952862d335bfa9970b8c394e9201548284af96363a39c54c00aac1a6ba946207c2076676c983fbe17c07fef566c23047aec842cc4dd71db6a3f4b7c31d
|
data/README.md
CHANGED
@@ -192,8 +192,10 @@ specifying the S3 bucket and object key, either of the following fields may be u
|
|
192
192
|
- The field `URL` may be used to specify an HTTP URL from which the code is to be uploaded to AWS Lambda. The tool
|
193
193
|
will download the code file from the specified URL, upload it to S3 and specify the correct S3 location for
|
194
194
|
CloudFormation.
|
195
|
-
- The field `Path` may be used to specify a local file or directory containing the code to be uploaded.
|
196
|
-
If the path specifies a directory, it will be compressed as a Zip file.
|
195
|
+
- The field `Path` may be used to specify a local file or directory containing the code to be uploaded.
|
196
|
+
If the path specifies a directory, it will be compressed and uploaded to S3 as a Zip file. If the path is a
|
197
|
+
single file, it will be converted to a `ZipFile`, allowing implicit use of the CloudFormation `cfn-response` module
|
198
|
+
and the AWS SDK, but the file is also subject to all `ZipFile` restrictions - such as limited to 4KB size.
|
197
199
|
|
198
200
|
#### Example:
|
199
201
|
|
@@ -7,47 +7,92 @@ module CloudFormationTool
|
|
7
7
|
class LambdaCode
|
8
8
|
include Storable
|
9
9
|
|
10
|
-
def initialize(
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
else
|
10
|
+
def initialize(code, tpl)
|
11
|
+
@data = code
|
12
|
+
@data['Url'] = @data.delete 'URL' if @data.key? 'URL' # normalize to CF convention if seeing old key
|
13
|
+
if @data.key? 'Url'
|
14
|
+
log "Trying Lambda code from #{@data['Url']}"
|
15
|
+
@data['Url'] = url = tpl.resolveVal(@data['Url'])
|
16
|
+
return unless url.is_a? String
|
17
|
+
log "Downloading Lambda code from #{url}"
|
18
|
+
unless already_in_cache(url)
|
20
19
|
res = fetch_from_url(url)
|
21
20
|
@s3_url = URI(upload(make_filename(url.split('.').last), res.body, mime_type: res['content-type'], gzip: false))
|
21
|
+
log "uploaded Lambda function to #{@s3_url}"
|
22
22
|
end
|
23
|
-
|
23
|
+
elsif @data.key? 'Path'
|
24
|
+
@data['Path'] = path = tpl.resolveVal(@data['Path'])
|
25
|
+
return unless path.is_a? String
|
26
|
+
log "Reading Lambda code from #{path}"
|
27
|
+
path = if path.start_with? "/" then path else "#{tpl.basedir}/#{path}" end
|
28
|
+
if File.directory?(path)
|
29
|
+
@s3_url = URI(upload(make_filename('zip'), zip_path(path), mime_type: 'application/zip', gzip: false))
|
30
|
+
log "uploaded Lambda function to #{@s3_url}"
|
31
|
+
else # Convert files to ZipFile
|
32
|
+
@data.delete 'Path'
|
33
|
+
@data['ZipFile'] = File.read(path)
|
34
|
+
end
|
35
|
+
end
|
24
36
|
end
|
25
37
|
|
26
|
-
def
|
38
|
+
def zip_path(path)
|
39
|
+
temp_file = Tempfile.new
|
40
|
+
temp_path = temp_file.path + '.zip'
|
27
41
|
begin
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
Dir[File.join(path_str, '*')].each do |file|
|
32
|
-
zipfile.add(file.sub("#{path_str}/", ''), file)
|
42
|
+
Zip::ZipFile.open(temp_path, true) do |zipfile|
|
43
|
+
Dir[File.join(path, '**','*')].each do |file|
|
44
|
+
zipfile.add(file.sub("#{path}/", ''), file)
|
33
45
|
end
|
34
46
|
end
|
35
|
-
|
47
|
+
File.read(temp_path)
|
36
48
|
ensure
|
37
|
-
temp_file.close
|
38
|
-
|
49
|
+
temp_file.close!
|
50
|
+
File.unlink temp_path
|
39
51
|
end
|
40
|
-
zip_data
|
41
52
|
end
|
42
53
|
|
43
|
-
|
44
|
-
|
45
|
-
|
54
|
+
def already_in_cache(uri_str, limit = 10)
|
55
|
+
raise ArgumentError, 'too many HTTP redirects' if limit == 0
|
56
|
+
url = URI(uri_str)
|
57
|
+
begin
|
58
|
+
Net::HTTP.start(url.host, url.port) do |http|
|
59
|
+
request = Net::HTTP::Get.new(url)
|
60
|
+
http.request(request) do |response|
|
61
|
+
# handle redirects like Github likes to do
|
62
|
+
case response
|
63
|
+
when Net::HTTPSuccess then
|
64
|
+
check_cached(response['ETag'])
|
65
|
+
when Net::HTTPRedirection then
|
66
|
+
location = response['location']
|
67
|
+
log "redirected to #{location}"
|
68
|
+
already_in_cache(location, limit - 1)
|
69
|
+
else
|
70
|
+
raise ArgumentError, "Error getting response: #{response}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
rescue EOFError
|
75
|
+
end
|
76
|
+
!@s3_url.nil?
|
77
|
+
end
|
78
|
+
|
79
|
+
def check_cached(etag)
|
80
|
+
etag.gsub!(/"/,'') unless etag.nil?
|
81
|
+
o = cached_object(etag)
|
82
|
+
unless o.nil?
|
83
|
+
log 'reusing cached object'
|
84
|
+
@s3_url = o.public_url
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def fetch_from_url(uri_str)
|
89
|
+
$__fetch_cache ||= Hash.new do |h, url|
|
90
|
+
h[url] = fetch_from_url_real(url)
|
91
|
+
end
|
92
|
+
$__fetch_cache[uri_str]
|
46
93
|
end
|
47
|
-
$__fetch_cache[uri_str]
|
48
|
-
end
|
49
94
|
|
50
|
-
|
95
|
+
def fetch_from_url_real(uri_str, limit = 10)
|
51
96
|
raise ArgumentError, 'too many HTTP redirects' if limit == 0
|
52
97
|
response = Net::HTTP.get_response(URI(uri_str))
|
53
98
|
case response
|
@@ -63,10 +108,14 @@ module CloudFormationTool
|
|
63
108
|
end
|
64
109
|
|
65
110
|
def to_cloudformation
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
111
|
+
if @s3_url.nil?
|
112
|
+
@data
|
113
|
+
else
|
114
|
+
{
|
115
|
+
'S3Bucket' => @s3_url.hostname.split('.').first,
|
116
|
+
'S3Key' => @s3_url.path[1..-1]
|
117
|
+
}
|
118
|
+
end
|
70
119
|
end
|
71
120
|
end
|
72
121
|
|
@@ -10,6 +10,8 @@ module CloudFormationTool
|
|
10
10
|
CloudFormation.new(path)
|
11
11
|
end
|
12
12
|
|
13
|
+
attr_reader :basedir
|
14
|
+
|
13
15
|
def initialize(path)
|
14
16
|
log "Loading #{path}"
|
15
17
|
@path = path
|
@@ -35,9 +37,8 @@ module CloudFormationTool
|
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
38
|
-
def compile
|
39
|
-
|
40
|
-
@compiled = true
|
40
|
+
def compile(parameters = nil)
|
41
|
+
@params = parameters unless parameters.nil?
|
41
42
|
embed_includes
|
42
43
|
@data = load_files(@data)
|
43
44
|
end
|
@@ -127,7 +128,7 @@ module CloudFormationTool
|
|
127
128
|
def resolveVal(value)
|
128
129
|
case value
|
129
130
|
when Hash
|
130
|
-
if value
|
131
|
+
if value.key? 'Ref'
|
131
132
|
if @params.nil?
|
132
133
|
# no parameters, we are probably in a sub template, just return the ref and hope
|
133
134
|
# a parent template has what it takes to resolve the ref
|
@@ -143,41 +144,37 @@ module CloudFormationTool
|
|
143
144
|
raise CloudFormationTool::Errors::AppError, "Value #{value} is not a valid value or reference"
|
144
145
|
end
|
145
146
|
else
|
146
|
-
value
|
147
|
+
value
|
147
148
|
end
|
148
149
|
end
|
149
150
|
|
150
|
-
def load_files(data)
|
151
|
+
def load_files(data, restype = nil)
|
151
152
|
case data
|
152
153
|
when Array
|
153
|
-
data.collect { |data| load_files(data) }
|
154
|
+
data.collect { |data| load_files(data, restype) }
|
154
155
|
when Hash
|
156
|
+
# remember the current resource type
|
157
|
+
restype = data['Type'] if restype.nil? and data.key?('Type')
|
155
158
|
data.inject({}) do |dict, (key, val)|
|
156
|
-
dict[key] =
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
LambdaCode.new(path: if path.start_with? "/" then path else "#{@basedir}/#{path}" end).to_cloudformation
|
176
|
-
else # resolving didn't work - we probably don't have parameters
|
177
|
-
val
|
178
|
-
end
|
179
|
-
else
|
180
|
-
load_files(val)
|
159
|
+
dict[key] = case restype
|
160
|
+
when 'AWS::AutoScaling::LaunchConfiguration'
|
161
|
+
if (key == "UserData") and (val["File"])
|
162
|
+
# Support LaunchConfiguration UserData from file
|
163
|
+
CloudInit.new("#{@basedir}/#{val["File"]}").to_base64
|
164
|
+
elsif (key == "UserData") and (val["FileTemplate"])
|
165
|
+
# Support LaunchConfiguration UserData from file with substitutions
|
166
|
+
{ "Fn::Base64" => { "Fn::Sub" => CloudInit.new("#{@basedir}/#{val["FileTemplate"]}").compile } }
|
167
|
+
else
|
168
|
+
load_files(val, restype)
|
169
|
+
end
|
170
|
+
when 'AWS::Lambda::Function'
|
171
|
+
if key == 'Code'
|
172
|
+
LambdaCode.new(val, self).to_cloudformation
|
173
|
+
else
|
174
|
+
load_files(val, restype)
|
175
|
+
end
|
176
|
+
else
|
177
|
+
load_files(val, restype)
|
181
178
|
end
|
182
179
|
dict
|
183
180
|
end
|
@@ -14,6 +14,10 @@ module CloudFormationTool
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
def cached_object(md5)
|
18
|
+
Aws::S3::Bucket.new(s3_bucket_name(region), client: awss3(region)).objects(prefix: "cf-compiled/#{md5[0]}/#{md5[1..2]}/#{md5}/").first
|
19
|
+
end
|
20
|
+
|
17
21
|
def upload(path, content, mime_type: 'text/yaml', gzip: true)
|
18
22
|
md5 = Digest::MD5.hexdigest content
|
19
23
|
prefix = "#{md5[0]}/#{md5[1..2]}/#{md5}"
|
@@ -23,7 +27,7 @@ module CloudFormationTool
|
|
23
27
|
# do a local copy to the requested path) because this way cloudformation can see
|
24
28
|
# that the updated template is exactly the same as the old one and will not force
|
25
29
|
# an unneeded update.
|
26
|
-
o =
|
30
|
+
o = cached_object(md5)
|
27
31
|
if o.nil?
|
28
32
|
# no such luck, we need to actually upload the file
|
29
33
|
o = b.object("cf-compiled/#{prefix}/#{path}")
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cloudformation-tool
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Oded Arbel
|
@@ -176,7 +176,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
176
176
|
version: '0'
|
177
177
|
requirements: []
|
178
178
|
rubyforge_project:
|
179
|
-
rubygems_version: 2.6
|
179
|
+
rubygems_version: 2.7.6
|
180
180
|
signing_key:
|
181
181
|
specification_version: 4
|
182
182
|
summary: A pre-compiler tool for CloudFormation YAML templates
|