cfhighlander 0.2.0.alpha.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|