cfhighlander 0.2.0.alpha.10

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