cfhighlander 0.2.0.alpha.10
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/README.md +411 -0
- data/bin/cfhighlander +3 -0
- data/bin/highlander.rb +158 -0
- data/cfndsl_ext/config/managed_policies.yaml +172 -0
- data/cfndsl_ext/iam_helper.rb +55 -0
- data/cfndsl_ext/lambda_helper.rb +86 -0
- data/cfndsl_ext/sg.rb +26 -0
- data/hl_ext/aws_helper.rb +13 -0
- data/hl_ext/common_helper.rb +21 -0
- data/hl_ext/mapping_helper.rb +99 -0
- data/hl_ext/vpc.rb +12 -0
- data/lib/highlander.compiler.rb +306 -0
- data/lib/highlander.dsl.base.rb +25 -0
- data/lib/highlander.dsl.component.rb +243 -0
- data/lib/highlander.dsl.params.rb +113 -0
- data/lib/highlander.dsl.rb +428 -0
- data/lib/highlander.factory.rb +359 -0
- data/lib/highlander.helper.rb +23 -0
- data/lib/highlander.mapproviders.rb +31 -0
- data/lib/highlander.publisher.rb +71 -0
- data/lib/highlander.validator.rb +72 -0
- data/lib/util/zip.util.rb +57 -0
- data/templates/cfndsl.component.template.erb +45 -0
- metadata +248 -0
@@ -0,0 +1,359 @@
|
|
1
|
+
require_relative './highlander.dsl'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'git'
|
4
|
+
|
5
|
+
LOCAL_HIGHLANDER_CACHE_LOCATION = "#{ENV['HOME']}/.highlander/components"
|
6
|
+
|
7
|
+
module Highlander
|
8
|
+
|
9
|
+
module Factory
|
10
|
+
|
11
|
+
class Component
|
12
|
+
|
13
|
+
attr_accessor :component_dir,
|
14
|
+
:config,
|
15
|
+
:highlander_dsl_path,
|
16
|
+
:cfndsl_path,
|
17
|
+
:highlander_dsl,
|
18
|
+
:cfndsl_content,
|
19
|
+
:mappings,
|
20
|
+
:name,
|
21
|
+
:version,
|
22
|
+
:distribution_bucket,
|
23
|
+
:distribution_prefix,
|
24
|
+
:component_files,
|
25
|
+
:cfndsl_ext_files,
|
26
|
+
:lambda_src_files
|
27
|
+
|
28
|
+
def initialize(component_name, component_dir)
|
29
|
+
@name = component_name
|
30
|
+
@component_dir = component_dir
|
31
|
+
@mappings = {}
|
32
|
+
@version = nil
|
33
|
+
@distribution_bucket = nil
|
34
|
+
@distribution_prefix = nil
|
35
|
+
@component_files = []
|
36
|
+
@cfndsl_ext_files = []
|
37
|
+
@lambda_src_files = []
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_config()
|
41
|
+
@config = {} if @config.nil?
|
42
|
+
Dir["#{@component_dir}/*.config.yaml"].each do |config_file|
|
43
|
+
partial_config = YAML.load(File.read(config_file))
|
44
|
+
unless partial_config.nil?
|
45
|
+
@config.extend(partial_config)
|
46
|
+
@component_files << config_file
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def loadDepandantExt()
|
53
|
+
@highlander_dsl.dependson_components.each do |requirement|
|
54
|
+
requirement.component_loaded.cfndsl_ext_files.each do |file|
|
55
|
+
cfndsl_ext_files << file
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param [Hash] config_override
|
61
|
+
def load(config_override = nil)
|
62
|
+
if @component_dir.start_with? 'http'
|
63
|
+
raise StandardError, 'http(s) sources not supported yet'
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
@highlander_dsl_path = "#{@component_dir}/#{@name}.highlander.rb"
|
68
|
+
@cfndsl_path = "#{@component_dir}/#{@name}.cfndsl.rb"
|
69
|
+
candidate_config_path = "#{@component_dir}/#{@name}.config.yaml"
|
70
|
+
candidate_mappings_path = "#{@component_dir}/*.mappings.yaml"
|
71
|
+
candidate_dynamic_mappings_path = "#{@component_dir}/#{@name}.mappings.rb"
|
72
|
+
|
73
|
+
@cfndsl_ext_files += Dir["#{@component_dir}/ext/cfndsl/*.rb"]
|
74
|
+
@lambda_src_files += Dir["#{@component_dir}/lambdas/**/*"].find_all {|p| not File.directory? p}
|
75
|
+
@component_files += @cfndsl_ext_files
|
76
|
+
@component_files += @lambda_src_files
|
77
|
+
|
78
|
+
@config = {} if @config.nil?
|
79
|
+
|
80
|
+
@config.extend config_override unless config_override.nil?
|
81
|
+
@config['component_version'] = @version unless @version.nil?
|
82
|
+
# allow override of components
|
83
|
+
# config_override.each do |key, value|
|
84
|
+
# @config[key] = value
|
85
|
+
# end unless config_override.nil?
|
86
|
+
|
87
|
+
Dir[candidate_mappings_path].each do |mapping_file|
|
88
|
+
mappings = YAML.load(File.read(mapping_file))
|
89
|
+
@component_files << mapping_file
|
90
|
+
mappings.each do |name, map|
|
91
|
+
@mappings[name] = map
|
92
|
+
end unless mappings.nil?
|
93
|
+
end
|
94
|
+
|
95
|
+
if File.exist? candidate_dynamic_mappings_path
|
96
|
+
require candidate_dynamic_mappings_path
|
97
|
+
@component_files << candidate_dynamic_mappings_path
|
98
|
+
end
|
99
|
+
|
100
|
+
# 1st pass - parse the file
|
101
|
+
@component_files << @highlander_dsl_path
|
102
|
+
@highlander_dsl = eval(File.read(@highlander_dsl_path), binding)
|
103
|
+
|
104
|
+
# set version if not defined
|
105
|
+
@highlander_dsl.ComponentVersion(@version) unless @version.nil?
|
106
|
+
|
107
|
+
|
108
|
+
# Handle name and description defaults if they are not specified
|
109
|
+
# in template itself
|
110
|
+
if @highlander_dsl.name.nil?
|
111
|
+
@highlander_dsl.name = @name
|
112
|
+
end
|
113
|
+
|
114
|
+
if @highlander_dsl.description.nil?
|
115
|
+
@highlander_dsl.Description("#{@highlander_dsl.name} - #{@highlander_dsl.version}")
|
116
|
+
end
|
117
|
+
|
118
|
+
# set (override) distribution options
|
119
|
+
@highlander_dsl.DistributionBucket(@distribution_bucket) unless @distribution_bucket.nil?
|
120
|
+
@highlander_dsl.DistributionPrefix(@distribution_prefix) unless @distribution_prefix.nil?
|
121
|
+
|
122
|
+
if File.exist? @cfndsl_path
|
123
|
+
@component_files << @cfndsl_path
|
124
|
+
@cfndsl_content = File.read(@cfndsl_path)
|
125
|
+
@cfndsl_content.strip!
|
126
|
+
# if there is CloudFormation do [content] end extract only contents
|
127
|
+
### Regex \s is whitespace
|
128
|
+
match_data = /^CloudFormation do\s(.*)end\s?$/m.match(@cfndsl_content)
|
129
|
+
if not match_data.nil?
|
130
|
+
@cfndsl_content = match_data[1]
|
131
|
+
end
|
132
|
+
|
133
|
+
else
|
134
|
+
@cfndsl_content = ''
|
135
|
+
end
|
136
|
+
|
137
|
+
loadDepandantExt()
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class ComponentFactory
|
142
|
+
|
143
|
+
attr_accessor :component_sources
|
144
|
+
|
145
|
+
|
146
|
+
def initialize(component_sources = [])
|
147
|
+
## First look in local $PWD/components folder
|
148
|
+
## Then search for cached $HOME/.highlander/components
|
149
|
+
## Then search in sources given by dsl
|
150
|
+
default_locations = [
|
151
|
+
LOCAL_HIGHLANDER_CACHE_LOCATION,
|
152
|
+
File.expand_path('components'),
|
153
|
+
File.expand_path('.')
|
154
|
+
]
|
155
|
+
default_locations.each do |predefined_path|
|
156
|
+
component_sources.unshift(predefined_path)
|
157
|
+
end
|
158
|
+
|
159
|
+
@component_sources = component_sources
|
160
|
+
end
|
161
|
+
|
162
|
+
def findComponentDefault(component_name, component_version)
|
163
|
+
default_lookup_url = 'https://github.com/theonestack'
|
164
|
+
default_lookup_url = ENV['HIGHLANDER_DEFAULT_COMPONENT_GIT_LOOKUP'] if ENV.key? 'HIGHLANDER_DEFAULT_COMPONENT_GIT_LOOKUP'
|
165
|
+
|
166
|
+
git_url = "#{default_lookup_url}/hl-component-#{component_name}"
|
167
|
+
|
168
|
+
if component_version.nil? or component_version.empty? or component_version == 'latest'
|
169
|
+
branch = 'master'
|
170
|
+
else
|
171
|
+
branch = component_version
|
172
|
+
end
|
173
|
+
local_path = "#{LOCAL_HIGHLANDER_CACHE_LOCATION}/#{component_name}/#{component_version}"
|
174
|
+
return findComponentGit(local_path, component_name, component_version, git_url, branch)
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
def findComponentGit(local_path, component_name, component_version, git_url, branch)
|
179
|
+
begin
|
180
|
+
local_path = "#{local_path}/" unless local_path.end_with? '/'
|
181
|
+
# if this is snapshot, clean local cache
|
182
|
+
if branch.end_with? '.snapshot'
|
183
|
+
branch = branch.gsub('.snapshot', '')
|
184
|
+
FileUtils.rmtree local_path if File.exist? local_path and File.directory? local_path
|
185
|
+
end
|
186
|
+
|
187
|
+
# if local cache exists, return from cache
|
188
|
+
if not Dir.glob("#{local_path}*.highlander.rb").empty?
|
189
|
+
# if cache exists, just return from cache
|
190
|
+
component_name = Dir.glob("#{local_path}*.highlander.rb")[0].gsub(local_path, '').gsub('.highlander.rb','')
|
191
|
+
return component_name, local_path
|
192
|
+
end
|
193
|
+
|
194
|
+
# shallow clone
|
195
|
+
puts "Trying to load #{component_name}/#{component_version} from #{git_url}##{branch} ... "
|
196
|
+
clone_opts = { depth: 1 }
|
197
|
+
clone_opts[:branch] = branch if not (branch.nil? or branch.empty?)
|
198
|
+
Git.clone git_url, local_path, clone_opts
|
199
|
+
puts "\t .. cached in #{local_path}\n"
|
200
|
+
# return from cache once it's cloned
|
201
|
+
return findComponentGit(local_path, component_name, component_version, git_url, branch)
|
202
|
+
rescue Exception => e
|
203
|
+
STDERR.puts "Failed to resolve component #{component_name}@#{component_version} from #{git_url}"
|
204
|
+
STDERR.puts e
|
205
|
+
return nil
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def findComponentS3(s3_location, component_name, component_version)
|
210
|
+
parts = s3_location.split('/')
|
211
|
+
bucket = parts[2]
|
212
|
+
prefix = parts[3]
|
213
|
+
s3_key = "#{prefix}/#{component_name}/#{component_version}/#{component_name}.highlander.rb"
|
214
|
+
s3_prefix = "#{prefix}/#{component_name}/#{component_version}/"
|
215
|
+
local_destination = "#{LOCAL_HIGHLANDER_CACHE_LOCATION}/#{component_name}/#{component_version}"
|
216
|
+
begin
|
217
|
+
s3 = Aws::S3::Client.new({ region: s3_bucket_region(bucket) })
|
218
|
+
FileUtils.mkdir_p local_destination unless Dir.exist? local_destination
|
219
|
+
|
220
|
+
hl_content = s3.get_object({ bucket: bucket,
|
221
|
+
key: s3_key,
|
222
|
+
response_target: "#{local_destination}/#{component_name}.highlander.rb"
|
223
|
+
})
|
224
|
+
# if code execution got so far we consider file exists and download it locally
|
225
|
+
component_files = s3.list_objects_v2({ bucket: bucket, prefix: s3_prefix })
|
226
|
+
component_files.contents.each {|s3_object|
|
227
|
+
file_name = s3_object.key.gsub(s3_prefix, '')
|
228
|
+
destination_file = "#{local_destination}/#{file_name}"
|
229
|
+
destination_dir = File.dirname(destination_file)
|
230
|
+
print "Caching #{file_name} of #{component_name}@#{component_version} in #{destination_dir} ... "
|
231
|
+
|
232
|
+
FileUtils.mkpath(destination_dir) unless File.exists?(destination_dir)
|
233
|
+
s3.get_object({ bucket: bucket, key: s3_object.key, response_target: destination_file })
|
234
|
+
print " [OK] \n"
|
235
|
+
}
|
236
|
+
return local_destination
|
237
|
+
rescue => e
|
238
|
+
# this handles both nonexisting key and bucket
|
239
|
+
puts("#{component_name} not found in s3://#{bucket}/#{prefix}")
|
240
|
+
STDERR.puts(e.to_s) unless e.message.include? 'does not exist'
|
241
|
+
return nil
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def findComponentGitTemplate(component_name, component_version)
|
246
|
+
if component_name.include? '#'
|
247
|
+
parts = component_name.split('#')
|
248
|
+
component_name = parts[0]
|
249
|
+
component_version = parts[1]
|
250
|
+
end
|
251
|
+
|
252
|
+
# avoid any nres
|
253
|
+
component_version = '' if component_version.nil?
|
254
|
+
|
255
|
+
# if empty or latest branch is empty
|
256
|
+
if component_version.empty? or component_version == 'latest' or component_version == 'latest.snapshot'
|
257
|
+
branch = ''
|
258
|
+
else
|
259
|
+
# otherwise component version is actual branch
|
260
|
+
branch = component_version
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
git_url = nil
|
265
|
+
if component_name.start_with? 'git:'
|
266
|
+
git_url = component_name.gsub('git:', '')
|
267
|
+
elsif component_name.start_with? 'github:'
|
268
|
+
git_url = "https://github.com/#{component_name.gsub('github:', '')}"
|
269
|
+
elsif component_name.start_with? 'github.com:'
|
270
|
+
git_url = "https://github.com/#{component_name.gsub('github.com:', '')}"
|
271
|
+
end
|
272
|
+
|
273
|
+
local_path = "#{LOCAL_HIGHLANDER_CACHE_LOCATION}/#{component_name}/#{component_version}"
|
274
|
+
|
275
|
+
if not git_url.nil?
|
276
|
+
component_name, location = findComponentGit(local_path, component_name, component_version, git_url, branch)
|
277
|
+
if location.nil?
|
278
|
+
raise "Could not resolve component #{component_name}@#{component_version}"
|
279
|
+
else
|
280
|
+
return component_name, location
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
return nil
|
285
|
+
end
|
286
|
+
|
287
|
+
# Find component and given list of sources
|
288
|
+
# @return [Highlander::Factory::Component]
|
289
|
+
def findComponent(component_name, component_version = nil)
|
290
|
+
|
291
|
+
component_version_s = component_version.nil? ? 'latest' : component_version
|
292
|
+
component_version = nil if component_version == 'latest'
|
293
|
+
|
294
|
+
if component_name.include? '@' and (not component_name.start_with? 'git')
|
295
|
+
parts = component_name.split('@')
|
296
|
+
component_name = parts[0]
|
297
|
+
component_version = parts[1]
|
298
|
+
end
|
299
|
+
|
300
|
+
# if component specified as git location
|
301
|
+
new_name, candidate_git = findComponentGitTemplate(component_name, component_version_s)
|
302
|
+
return buildComponent(new_name, candidate_git) unless candidate_git.nil?
|
303
|
+
|
304
|
+
# if not git but has .snapshot lookup in default
|
305
|
+
if (not component_version.nil?) and component_version.end_with? '.snapshot'
|
306
|
+
new_name, default_candidate = findComponentDefault(component_name, component_version_s)
|
307
|
+
return buildComponent(new_name, default_candidate) unless default_candidate.nil?
|
308
|
+
end
|
309
|
+
|
310
|
+
# try in all of the component source
|
311
|
+
@component_sources.each do |source|
|
312
|
+
component_full_name = "#{component_name}@#{component_version.nil? ? 'latest' : component_version}"
|
313
|
+
# TODO handle http(s) sources and their download to local
|
314
|
+
if source.start_with?('http')
|
315
|
+
raise StandardError, 'http(s) sources not supported yet'
|
316
|
+
elsif source.start_with?('s3://')
|
317
|
+
# s3 candidate
|
318
|
+
|
319
|
+
s3_candidate = findComponentS3(source, component_name, component_version_s)
|
320
|
+
if not s3_candidate.nil?
|
321
|
+
# at this point all component files are download to local file system and consumed from there
|
322
|
+
return buildComponent(component_name, s3_candidate)
|
323
|
+
end
|
324
|
+
|
325
|
+
else
|
326
|
+
# file system candidate
|
327
|
+
candidate = "#{source}/#{component_name}"
|
328
|
+
candidate = "#{candidate}/#{component_version}" unless component_version.nil?
|
329
|
+
candidate_hl_path = "#{candidate}/#{component_name}.highlander.rb"
|
330
|
+
candidate2_hl_path = "#{source}/#{component_name}.highlander.rb"
|
331
|
+
puts "Trying to load #{component_full_name} from #{candidate} ... "
|
332
|
+
if File.exist?(candidate_hl_path)
|
333
|
+
return buildComponent(component_name, candidate)
|
334
|
+
end
|
335
|
+
puts "Trying to load #{component_full_name} from #{source} ... "
|
336
|
+
if File.exist?(candidate2_hl_path)
|
337
|
+
return buildComponent(component_name, source)
|
338
|
+
end unless component_version_s != 'latest'
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# try default component source on github
|
343
|
+
component_name, default_candidate = findComponentDefault(component_name, component_version_s)
|
344
|
+
return buildComponent(component_name, default_candidate) unless default_candidate.nil?
|
345
|
+
|
346
|
+
raise StandardError, "highlander template #{component_name}@#{component_version_s} not located" +
|
347
|
+
" in sources #{@component_sources}"
|
348
|
+
end
|
349
|
+
|
350
|
+
def buildComponent(component_name, component_dir)
|
351
|
+
component = Component.new(component_name, component_dir)
|
352
|
+
component.load_config
|
353
|
+
return component
|
354
|
+
end
|
355
|
+
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Highlander
|
2
|
+
|
3
|
+
|
4
|
+
module Helper
|
5
|
+
|
6
|
+
|
7
|
+
def self.parameter_cfndsl_value(value)
|
8
|
+
|
9
|
+
if value.class == String
|
10
|
+
return "'#{value}'"
|
11
|
+
end
|
12
|
+
|
13
|
+
if value.class == Hash
|
14
|
+
return value.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
return "'#{value}'"
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Highlander
|
2
|
+
|
3
|
+
### Map Providers provide predefined maps or list of maps
|
4
|
+
### Aside from predefined mappings from providers
|
5
|
+
### Maps can be defined on component level in component.maps.yml file
|
6
|
+
module MapProviders
|
7
|
+
|
8
|
+
class AccountId
|
9
|
+
|
10
|
+
@@maps = nil
|
11
|
+
|
12
|
+
def self.getMaps(config)
|
13
|
+
return @@maps if not @@maps.nil?
|
14
|
+
@@maps = {
|
15
|
+
}
|
16
|
+
return @@maps
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.getMapName
|
20
|
+
return 'AccountId'
|
21
|
+
end
|
22
|
+
|
23
|
+
### no getMapName, implicitly resolves to MapProvider name
|
24
|
+
def self.getDefaultKey
|
25
|
+
return "Ref('AWS::AccountId')"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative './highlander.compiler'
|
2
|
+
require 'aws-sdk-s3'
|
3
|
+
|
4
|
+
module Highlander
|
5
|
+
|
6
|
+
module Publisher
|
7
|
+
|
8
|
+
class Component
|
9
|
+
|
10
|
+
def initialize(component, cleanup)
|
11
|
+
@component = component
|
12
|
+
@cleanup_destination = cleanup
|
13
|
+
end
|
14
|
+
|
15
|
+
def publishComponent
|
16
|
+
bucket = @component.highlander_dsl.distribution_bucket
|
17
|
+
prefix = @component.highlander_dsl.distribution_prefix
|
18
|
+
version = @component.highlander_dsl.version
|
19
|
+
s3 = Aws::S3::Client.new({ region: s3_bucket_region(bucket) })
|
20
|
+
|
21
|
+
existing_objects = s3.list_objects_v2({bucket: bucket, prefix: "#{prefix}/#{@component.name}/#{version}"})
|
22
|
+
existing_objects.contents.each do |s3obj|
|
23
|
+
print "Deleting previously published #{s3obj.key} ..."
|
24
|
+
s3.delete_object(bucket: bucket, key: s3obj.key)
|
25
|
+
print " [OK] \n"
|
26
|
+
end if @cleanup_destination
|
27
|
+
|
28
|
+
@component.component_files.each do |file_path|
|
29
|
+
File.open(file_path, 'rb') do |file|
|
30
|
+
file_path = file_path.gsub(@component.component_dir, '')[1..-1]
|
31
|
+
s3_key = "#{prefix}/#{@component.name}/#{version}/#{file_path}"
|
32
|
+
print "Publish component file #{file_path} to s3://#{bucket}/#{s3_key} ... "
|
33
|
+
s3.put_object(bucket: bucket, key: s3_key, body: file)
|
34
|
+
print " [OK] \n"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
def publishFiles(file_list)
|
41
|
+
|
42
|
+
bucket = @component.highlander_dsl.distribution_bucket
|
43
|
+
prefix = @component.highlander_dsl.distribution_prefix
|
44
|
+
version = @component.highlander_dsl.version
|
45
|
+
s3 = Aws::S3::Client.new({ region: s3_bucket_region(bucket) })
|
46
|
+
|
47
|
+
s3.list_objects_v2(bucket: bucket, prefix: "#{prefix}/#{version}").contents.each do |s3obj|
|
48
|
+
print "\nDeleting previously published #{s3obj.key} ..."
|
49
|
+
s3.delete_object(bucket: bucket, key: s3obj.key)
|
50
|
+
print ' [OK]'
|
51
|
+
end if @cleanup_destination
|
52
|
+
|
53
|
+
file_list.each do |file|
|
54
|
+
file_name = File.basename file
|
55
|
+
s3_key = "#{prefix}/#{version}/#{file_name}"
|
56
|
+
print "\nPublishing #{file} to s3://#{bucket}/#{s3_key} ..."
|
57
|
+
s3.put_object({
|
58
|
+
body: File.read(file),
|
59
|
+
bucket: bucket,
|
60
|
+
key: s3_key
|
61
|
+
})
|
62
|
+
print ' [OK] '
|
63
|
+
end
|
64
|
+
print "\n"
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'aws-sdk-cloudformation'
|
2
|
+
require 'aws-sdk-s3'
|
3
|
+
require 'digest/md5'
|
4
|
+
|
5
|
+
module Highlander
|
6
|
+
|
7
|
+
module Cloudformation
|
8
|
+
|
9
|
+
|
10
|
+
class Validator
|
11
|
+
|
12
|
+
def initialize(component)
|
13
|
+
@component = component
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate(destination_template_locations, format)
|
17
|
+
destination_template_locations.each do |file|
|
18
|
+
|
19
|
+
# validate cloudformation template
|
20
|
+
file_size_bytes = File.size(file)
|
21
|
+
|
22
|
+
#:template_body (String) — Structure containing the template body with a minimum length of
|
23
|
+
# 1 byte and a maximum length of 51,200 bytes. For more information,
|
24
|
+
# go to Template Anatomy in the AWS CloudFormation User Guide.
|
25
|
+
|
26
|
+
if file_size_bytes > 51200
|
27
|
+
validate_s3 (file)
|
28
|
+
else
|
29
|
+
validate_local(file)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate_local(path)
|
37
|
+
puts "Validate template #{path} locally"
|
38
|
+
template = File.read path
|
39
|
+
awscfn = Aws::CloudFormation::Client.new
|
40
|
+
response = awscfn.validate_template({
|
41
|
+
template_body: template
|
42
|
+
})
|
43
|
+
puts 'SUCCESS'
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate_s3(path)
|
47
|
+
template = File.read path
|
48
|
+
bucket = @component.highlander_dsl.distribution_bucket
|
49
|
+
prefix = @component.highlander_dsl.distribution_prefix
|
50
|
+
md5 = Digest::MD5.hexdigest template
|
51
|
+
s3_key = "#{prefix}/highlander/validate/#{md5}"
|
52
|
+
s3 = Aws::S3::Client.new({region: s3_bucket_region(bucket)})
|
53
|
+
|
54
|
+
puts "Upload #{path} to s3://#{bucket}/#{s3_key}"
|
55
|
+
s3.put_object({ body: template, bucket: bucket, key: s3_key })
|
56
|
+
awscfn = Aws::CloudFormation::Client.new
|
57
|
+
|
58
|
+
puts "Validate s3://#{bucket}/#{s3_key}"
|
59
|
+
response = awscfn.validate_template({
|
60
|
+
template_url: "https://#{bucket}.s3.amazonaws.com/#{s3_key}"
|
61
|
+
})
|
62
|
+
puts "Delete s3://#{bucket}/#{s3_key}"
|
63
|
+
s3.delete_object({ bucket: bucket, key: s3_key })
|
64
|
+
puts 'SUCCESS'
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'zip'
|
2
|
+
|
3
|
+
|
4
|
+
module Highlander
|
5
|
+
module Util
|
6
|
+
###
|
7
|
+
### taken from https://github.com/rubyzip/rubyzip/tree/master/samples
|
8
|
+
###
|
9
|
+
class ZipFileGenerator
|
10
|
+
|
11
|
+
# Initialize with the directory to zip and the location of the output archive.
|
12
|
+
def initialize(input_dir, output_file)
|
13
|
+
@input_dir = input_dir
|
14
|
+
@output_file = output_file
|
15
|
+
end
|
16
|
+
|
17
|
+
# Zip the input directory.
|
18
|
+
def write
|
19
|
+
entries = Dir.entries(@input_dir) - %w(. ..)
|
20
|
+
|
21
|
+
::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile|
|
22
|
+
write_entries entries, '', zipfile
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# A helper method to make the recursion work.
|
29
|
+
def write_entries(entries, path, zipfile)
|
30
|
+
entries.each do |e|
|
31
|
+
zipfile_path = path == '' ? e : File.join(path, e)
|
32
|
+
disk_file_path = File.join(@input_dir, zipfile_path)
|
33
|
+
puts "Deflating #{disk_file_path}"
|
34
|
+
|
35
|
+
if File.directory? disk_file_path
|
36
|
+
recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
|
37
|
+
else
|
38
|
+
put_into_archive(disk_file_path, zipfile, zipfile_path)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
|
44
|
+
zipfile.mkdir zipfile_path
|
45
|
+
subdir = Dir.entries(disk_file_path) - %w(. ..)
|
46
|
+
write_entries subdir, zipfile_path, zipfile
|
47
|
+
end
|
48
|
+
|
49
|
+
def put_into_archive(disk_file_path, zipfile, zipfile_path)
|
50
|
+
zipfile.get_output_stream(zipfile_path) do |f|
|
51
|
+
f.write(File.open(disk_file_path, 'rb').read)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
<% for @require in component_requires %>
|
2
|
+
require('<%= @require %>')
|
3
|
+
<% end %>
|
4
|
+
CloudFormation do
|
5
|
+
|
6
|
+
<% for @mapping in dsl.mappings %>
|
7
|
+
Mapping('<%= @mapping %>', mappings['<%= @mapping %>'])
|
8
|
+
<% end %>
|
9
|
+
|
10
|
+
# render subcomponents
|
11
|
+
<% for @component in dsl.components %>
|
12
|
+
CloudFormation_Stack('<%= @component.name.gsub('-','').gsub('_','') %>') do
|
13
|
+
TemplateURL '<%= @component.distribution_url %>'
|
14
|
+
Parameters ({
|
15
|
+
<% for @param in @component.parameters %>
|
16
|
+
'<%= @param.name %>' => <%= @param.cfndsl_value %>,
|
17
|
+
<% end %>
|
18
|
+
})
|
19
|
+
end
|
20
|
+
<% end %>
|
21
|
+
|
22
|
+
<%= component_cfndsl %>
|
23
|
+
|
24
|
+
# render lambda functions
|
25
|
+
<% unless dsl.lambda_functions_keys.nil? %>
|
26
|
+
<% for @key in dsl.lambda_functions_keys %>
|
27
|
+
render_lambda_functions(self,
|
28
|
+
<%= @key %>,
|
29
|
+
lambda_metadata,
|
30
|
+
{'bucket'=>'<%= dsl.distribution_bucket %>','prefix' => '<%= dsl.distribution_prefix %>', 'version'=>'<%= dsl.version %>'})
|
31
|
+
<% end %>
|
32
|
+
<% end %>
|
33
|
+
# render parameters at the end, overriding any parameters defined in template itself
|
34
|
+
<% for @param in dsl.parameters.param_list %>
|
35
|
+
Parameter('<%= @param.name %>') do
|
36
|
+
Type '<%= @param.type %>'
|
37
|
+
Default '<%= @param.default_value %>'
|
38
|
+
<% unless @param.no_echo.nil? %>
|
39
|
+
NoEcho <%= @param.no_echo.to_s %>
|
40
|
+
<% end %>
|
41
|
+
end
|
42
|
+
<% end %>
|
43
|
+
|
44
|
+
Description '<%= dsl.description %>'
|
45
|
+
end
|