convection 0.0.1 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +26 -8
- data/.rubocop_todo.yml +77 -0
- data/.ruby-version +1 -0
- data/.travis.yml +10 -0
- data/Gemfile +9 -0
- data/README.md +27 -2
- data/Rakefile +11 -1
- data/bin/convection +49 -0
- data/convection.gemspec +5 -7
- data/example/.ruby-version +1 -0
- data/example/Cloudfile +13 -0
- data/example/deprecated/elb.rb +27 -0
- data/example/deprecated/iam_access_key.rb +18 -0
- data/example/deprecated/iam_group.rb +31 -0
- data/example/{iam_role.rb → deprecated/iam_role.rb} +21 -32
- data/example/deprecated/iam_user.rb +31 -0
- data/example/deprecated/rds.rb +70 -0
- data/example/{s3.rb → deprecated/s3.rb} +0 -0
- data/example/deprecated/sqs.rb +32 -0
- data/example/deprecated/vpc.rb +85 -0
- data/example/foobar.rb +22 -0
- data/example/output/vpc.json +335 -0
- data/example/security-groups.rb +40 -0
- data/example/trust_cloudtrail.rb +24 -0
- data/example/vpc.rb +63 -81
- data/ext/resource_generator.sh +21 -0
- data/lib/convection.rb +5 -4
- data/lib/convection/control/cloud.rb +59 -0
- data/lib/convection/control/stack.rb +261 -60
- data/lib/convection/dsl/helpers.rb +63 -5
- data/lib/convection/model/attributes.rb +60 -0
- data/lib/convection/model/cloudfile.rb +58 -0
- data/lib/convection/model/diff.rb +39 -0
- data/lib/convection/model/event.rb +62 -0
- data/lib/convection/model/exceptions.rb +18 -0
- data/lib/convection/model/mixin/cidr_block.rb +4 -4
- data/lib/convection/model/mixin/colorize.rb +20 -0
- data/lib/convection/model/mixin/conditional.rb +1 -3
- data/lib/convection/model/mixin/policy.rb +89 -0
- data/lib/convection/model/mixin/protocol.rb +29 -0
- data/lib/convection/model/mixin/taggable.rb +2 -2
- data/lib/convection/model/template.rb +248 -21
- data/lib/convection/model/template/condition.rb +56 -0
- data/lib/convection/model/template/mapping.rb +4 -3
- data/lib/convection/model/template/output.rb +9 -7
- data/lib/convection/model/template/parameter.rb +19 -4
- data/lib/convection/model/template/resource.rb +317 -23
- data/lib/convection/model/template/resource/aws_auto_scaling_auto_scaling_group.rb +39 -0
- data/lib/convection/model/template/resource/aws_auto_scaling_launch_configuration.rb +30 -0
- data/lib/convection/model/template/resource/aws_auto_scaling_scaling_policy.rb +20 -0
- data/lib/convection/model/template/resource/aws_cloud_watch_alarm.rb +31 -0
- data/lib/convection/model/template/resource/aws_ec2_instance.rb +10 -46
- data/lib/convection/model/template/resource/aws_ec2_internet_gateway.rb +3 -14
- data/lib/convection/model/template/resource/aws_ec2_network_acl.rb +45 -0
- data/lib/convection/model/template/resource/aws_ec2_network_acl_entry.rb +27 -0
- data/lib/convection/model/template/resource/aws_ec2_route.rb +7 -40
- data/lib/convection/model/template/resource/aws_ec2_route_table.rb +2 -17
- data/lib/convection/model/template/resource/aws_ec2_security_group.rb +24 -30
- data/lib/convection/model/template/resource/aws_ec2_security_group_ingres.rb +25 -0
- data/lib/convection/model/template/resource/aws_ec2_subnet.rb +21 -28
- data/lib/convection/model/template/resource/aws_ec2_subnet_network_acl_association.rb +18 -0
- data/lib/convection/model/template/resource/aws_ec2_subnet_route_table_association.rb +3 -24
- data/lib/convection/model/template/resource/aws_ec2_vpc.rb +20 -22
- data/lib/convection/model/template/resource/aws_ec2_vpc_gateway_attachment.rb +4 -28
- data/lib/convection/model/template/resource/aws_elasticache_cluster.rb +24 -0
- data/lib/convection/model/template/resource/aws_elasticache_parameter_group.rb +19 -0
- data/lib/convection/model/template/resource/aws_elasticache_security_group.rb +17 -0
- data/lib/convection/model/template/resource/aws_elasticache_security_group_ingress.rb +19 -0
- data/lib/convection/model/template/resource/aws_elb.rb +39 -0
- data/lib/convection/model/template/resource/aws_iam_access_key.rb +19 -0
- data/lib/convection/model/template/resource/aws_iam_group.rb +18 -0
- data/lib/convection/model/template/resource/aws_iam_instance_profile.rb +21 -0
- data/lib/convection/model/template/resource/aws_iam_policy.rb +28 -24
- data/lib/convection/model/template/resource/aws_iam_role.rb +88 -19
- data/lib/convection/model/template/resource/aws_iam_user.rb +53 -0
- data/lib/convection/model/template/resource/aws_logs_loggroup.rb +33 -0
- data/lib/convection/model/template/resource/aws_rds_db_instance.rb +59 -0
- data/lib/convection/model/template/resource/aws_rds_db_parameter_group.rb +27 -0
- data/lib/convection/model/template/resource/aws_rds_db_security_group.rb +40 -0
- data/lib/convection/model/template/resource/aws_rds_db_subnet_group.rb +26 -0
- data/lib/convection/model/template/resource/aws_route53_health_check.rb +17 -0
- data/lib/convection/model/template/resource/aws_route53_recordset.rb +30 -0
- data/lib/convection/model/template/resource/aws_s3_bucket.rb +8 -44
- data/lib/convection/model/template/resource/aws_s3_bucket_policy.rb +14 -19
- data/lib/convection/model/template/resource/aws_sns_topic.rb +19 -0
- data/lib/convection/model/template/resource/aws_sqs_queue.rb +31 -0
- data/lib/convection/model/template/resource/aws_sqs_queue_policy.rb +18 -0
- data/test/convection/model/test_conditions.rb +121 -0
- data/test/convection/model/test_elasticache.rb +97 -0
- data/test/convection/model/test_loggroups.rb +25 -0
- data/test/convection/model/test_rds.rb +76 -0
- data/test/convection/model/test_template.rb +64 -0
- data/test/convection/model/test_validation.rb +216 -0
- data/test/test_helper.rb +17 -0
- metadata +131 -50
@@ -1,15 +1,73 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require_relative './intrinsic_functions'
|
3
|
+
|
1
4
|
module Convection
|
2
5
|
module DSL
|
3
6
|
##
|
4
|
-
#
|
7
|
+
# Methods for defining DSL/Models
|
8
|
+
##
|
9
|
+
module ClassHelpers
|
10
|
+
def attribute(attribute_name)
|
11
|
+
attr_writer attribute_name
|
12
|
+
define_method(attribute_name) do |value = nil|
|
13
|
+
instance_variable_set("@#{ attribute_name }", value) unless value.nil?
|
14
|
+
instance_variable_get("@#{ attribute_name }")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def list(attribute_name)
|
19
|
+
define_method(attribute_name) do |value = nil|
|
20
|
+
collection = instance_variable_get("@#{ attribute_name }")
|
21
|
+
collection << value unless value.nil?
|
22
|
+
collection
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Helper methods for creating templates
|
5
29
|
##
|
6
30
|
module Helpers
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
31
|
+
extend Forwardable
|
32
|
+
include DSL::IntrinsicFunctions
|
33
|
+
|
34
|
+
def_delegators :@template, :stack, :parameters, :mappings, :resources, :outputs
|
35
|
+
|
36
|
+
class << self
|
37
|
+
def included(mod)
|
38
|
+
mod.extend(DSL::ClassHelpers)
|
39
|
+
end
|
40
|
+
|
41
|
+
def method_name(cf_type)
|
42
|
+
nodes = cf_type.split('::')
|
43
|
+
nodes.shift # Remove AWS::
|
44
|
+
|
45
|
+
## Cammel-case to snake-case
|
46
|
+
nodes.map! do |n|
|
47
|
+
n.split(/([A-Z0-9])(?![A-Z0-9])(?<!$)/)
|
48
|
+
.reject(&:empty?)
|
49
|
+
.reduce('') { |a, e| (e.length == 1 && !a.empty?) ? a + "_#{e}" : a + e }
|
50
|
+
.downcase
|
51
|
+
end
|
52
|
+
|
53
|
+
nodes.join('_').downcase
|
11
54
|
end
|
12
55
|
end
|
56
|
+
|
57
|
+
## Convert various casings to CamelCase
|
58
|
+
def camel_case(str)
|
59
|
+
str.downcase.split(/[\.\-_]/).map(&:capitalize).join
|
60
|
+
end
|
61
|
+
|
62
|
+
## Convert various casings to snake_case
|
63
|
+
def snake_case(str)
|
64
|
+
str.downcase.split(/[\.\-_]/).join('_')
|
65
|
+
end
|
66
|
+
|
67
|
+
## Convert various casings to SCREAMING_SNAKE_CASE
|
68
|
+
def screaming_snake_case(str)
|
69
|
+
str.upcase.split(/[\.\-_]/).join('_')
|
70
|
+
end
|
13
71
|
end
|
14
72
|
end
|
15
73
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Convection
|
2
|
+
module Model
|
3
|
+
##
|
4
|
+
# Manage parameters and attributes across stacks
|
5
|
+
##
|
6
|
+
class Attributes
|
7
|
+
class Stack
|
8
|
+
attr_accessor :resources
|
9
|
+
attr_accessor :outputs
|
10
|
+
attr_reader :parameters
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@resources = {}
|
14
|
+
@outputs = {}
|
15
|
+
@parameters = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def include?(key)
|
19
|
+
@parameters.include?(key) || @outputs.include?(key) || @resources.include?(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](key)
|
23
|
+
@parameters[key.to_s] || @outputs[key.to_s] || @resources[key.to_s]
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(key, value)
|
27
|
+
@parameters[key.to_s] = value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :stacks
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@stacks = Hash.new do |hash, key|
|
35
|
+
hash[key] = Stack.new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def include?(stack, key)
|
40
|
+
@stacks.include?(stack) && @stacks[stack].include?(key)
|
41
|
+
end
|
42
|
+
|
43
|
+
def get(stack, key, default = nil)
|
44
|
+
include?(stack, key) ? @stacks[stack.to_s][key.to_s] : default
|
45
|
+
end
|
46
|
+
|
47
|
+
def set(stack, key, value)
|
48
|
+
@stacks[stack.to_s][key.to_s] = value
|
49
|
+
end
|
50
|
+
|
51
|
+
def load_outputs(stack)
|
52
|
+
@stacks[stack.name.to_s].outputs = stack.outputs
|
53
|
+
end
|
54
|
+
|
55
|
+
def load_resources(stack)
|
56
|
+
@stacks[stack.name.to_s].resources = stack.attribute_mapping_values
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative '../control/stack'
|
2
|
+
require_relative '../dsl/helpers'
|
3
|
+
require_relative '../model/attributes'
|
4
|
+
require_relative '../model/template'
|
5
|
+
|
6
|
+
module Convection
|
7
|
+
module DSL
|
8
|
+
##
|
9
|
+
# DSL for Cloudfile
|
10
|
+
##
|
11
|
+
module Cloudfile
|
12
|
+
include DSL::Helpers
|
13
|
+
|
14
|
+
attribute :name
|
15
|
+
attribute :region
|
16
|
+
attribute :splay
|
17
|
+
|
18
|
+
## Helper to define a template in-line
|
19
|
+
def template(*args, &block)
|
20
|
+
Model::Template.new(*args, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def attribute(stack, key, value)
|
24
|
+
@attributes.set(stack, key, value)
|
25
|
+
end
|
26
|
+
|
27
|
+
def stack(stack_name, template, options = {})
|
28
|
+
options[:region] ||= region
|
29
|
+
options[:cloud] = name
|
30
|
+
options[:attributes] = attributes
|
31
|
+
|
32
|
+
@stacks[stack_name] = Control::Stack.new(stack_name, template, options)
|
33
|
+
@deck << @stacks[stack_name]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module Model
|
39
|
+
##
|
40
|
+
# Define your Clouds
|
41
|
+
##
|
42
|
+
class Cloudfile
|
43
|
+
include DSL::Cloudfile
|
44
|
+
|
45
|
+
attr_reader :attributes
|
46
|
+
attr_reader :stacks
|
47
|
+
attr_reader :deck
|
48
|
+
|
49
|
+
def initialize(cloudfile)
|
50
|
+
@attributes = Model::Attributes.new
|
51
|
+
@stacks = {}
|
52
|
+
@deck = []
|
53
|
+
|
54
|
+
instance_eval(IO.read(cloudfile), cloudfile, 1)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative './mixin/colorize'
|
2
|
+
|
3
|
+
module Convection
|
4
|
+
module Model
|
5
|
+
##
|
6
|
+
# Difference between an item in two templates
|
7
|
+
##
|
8
|
+
class Diff
|
9
|
+
extend Mixin::Colorize
|
10
|
+
|
11
|
+
attr_reader :key
|
12
|
+
attr_reader :action
|
13
|
+
attr_reader :ours
|
14
|
+
attr_reader :theirs
|
15
|
+
colorize :action, :green => [:create], :yellow => [:update], :red => [:delete]
|
16
|
+
|
17
|
+
def initialize(key, ours, theirs)
|
18
|
+
@key = key
|
19
|
+
@ours = ours
|
20
|
+
@theirs = theirs
|
21
|
+
|
22
|
+
@action = if ours && theirs then :update
|
23
|
+
elsif ours then :create
|
24
|
+
else :delete
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_thor
|
29
|
+
message = case action
|
30
|
+
when :create then "#{ key }: #{ ours }"
|
31
|
+
when :update then "#{ key }: #{ theirs } => #{ ours }"
|
32
|
+
when :delete then key
|
33
|
+
end
|
34
|
+
|
35
|
+
[action, message, color]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require_relative './mixin/colorize'
|
2
|
+
require_relative '../control/stack'
|
3
|
+
|
4
|
+
module Convection
|
5
|
+
module Model
|
6
|
+
##
|
7
|
+
# Wrap events with some smarts
|
8
|
+
##
|
9
|
+
class Event
|
10
|
+
extend Mixin::Colorize
|
11
|
+
|
12
|
+
attr_accessor :name
|
13
|
+
attr_accessor :message
|
14
|
+
attr_accessor :level
|
15
|
+
attr_accessor :timestamp
|
16
|
+
colorize :level,
|
17
|
+
:green => [:info, :success, Control::Stack::CREATE_COMPLETE, Control::Stack::UPDATE_COMPLETE, Control::Stack::UPDATE_ROLLBACK_COMPLETE],
|
18
|
+
:red => [:error, :fail, Control::Stack::CREATE_FAILED, Control::Stack::ROLLBACK_FAILED,
|
19
|
+
Control::Stack::DELETE_FAILED, Control::Stack::UPDATE_FAILED,
|
20
|
+
Control::Stack::UPDATE_ROLLBACK_IN_PROGRESS, Control::Stack::UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS,
|
21
|
+
Control::Stack::UPDATE_ROLLBACK_FAILED],
|
22
|
+
:default => :yellow
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def from_cf(event)
|
26
|
+
Event.new(event.resource_status.downcase,
|
27
|
+
"#{ event.logical_resource_id }: (#{ event.resource_type }/"\
|
28
|
+
"#{ event.physical_resource_id}) #{ event.resource_status_reason }",
|
29
|
+
event.resource_status,
|
30
|
+
:event_id => event.event_id,
|
31
|
+
:logical_resource_id => event.logical_resource_id,
|
32
|
+
:physical_resource_id => event.physical_resource_id,
|
33
|
+
:resource_properties => event.resource_properties,
|
34
|
+
:resource_status_reason => event.resource_status_reason,
|
35
|
+
:resource_type => event.resource_type,
|
36
|
+
:stack_id => event.stack_id,
|
37
|
+
:stack_name => event.stack_name,
|
38
|
+
:timestamp => event.timestamp)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(name, message, level = :info, attributes = {})
|
43
|
+
@name = name
|
44
|
+
@message = message
|
45
|
+
@level = level
|
46
|
+
@attributes = attributes
|
47
|
+
end
|
48
|
+
|
49
|
+
def [](attr)
|
50
|
+
@attributes[attr]
|
51
|
+
end
|
52
|
+
|
53
|
+
def []=(attr, value)
|
54
|
+
@attributes[attr] = value
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_thor
|
58
|
+
[name.downcase, message, color]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class ValidationError < StandardError; end
|
2
|
+
class ExcessiveResourcesError < ValidationError; end
|
3
|
+
class ExcessiveResourceNameError < ValidationError; end
|
4
|
+
class ExcessiveMappingsError < ValidationError; end
|
5
|
+
class ExcessiveMappingAttributesError < ValidationError; end
|
6
|
+
class ExcessiveMappingNameError < ValidationError; end
|
7
|
+
class ExcessiveMappingAttributeNameError < ValidationError; end
|
8
|
+
class ExcessiveParametersError < ValidationError; end
|
9
|
+
class ExcessiveParameterNameError < ValidationError; end
|
10
|
+
class ExcessiveParameterBytesizeError < ValidationError; end
|
11
|
+
class ExcessiveOutputsError < ValidationError; end
|
12
|
+
class ExcessiveOutputNameError < ValidationError; end
|
13
|
+
class ExcessiveDescriptionError < ValidationError; end
|
14
|
+
class ExcessiveTemplateSizeError < ValidationError; end
|
15
|
+
|
16
|
+
def limit_exceeded_error(value, limit, error_class)
|
17
|
+
fail error_class, "Value #{value} exceeds Limit #{limit}"
|
18
|
+
end
|
@@ -4,12 +4,12 @@ module Convection
|
|
4
4
|
module Model
|
5
5
|
module Mixin
|
6
6
|
##
|
7
|
-
#
|
7
|
+
# Sanitized CIDR IP notation
|
8
8
|
##
|
9
9
|
module CIDRBlock
|
10
|
-
def network
|
11
|
-
|
12
|
-
|
10
|
+
def cidr_property(name = :network, property_name = 'CidrBlock')
|
11
|
+
property(name, property_name,
|
12
|
+
:transform => proc { |*args| NetAddr::CIDR.create(*args) })
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Convection
|
2
|
+
module Model
|
3
|
+
module Mixin
|
4
|
+
module Colorize
|
5
|
+
def colorize(param, options = {})
|
6
|
+
define_method(:color) do
|
7
|
+
case instance_variable_get("@#{ param }")
|
8
|
+
when *options.fetch(:white, [:status]) then :white
|
9
|
+
when *options.fetch(:cyan, [:debug, :trace]) then :cyan
|
10
|
+
when *options.fetch(:green, [:info, :success, :create]) then :green
|
11
|
+
when *options.fetch(:yellow, [:warn, :update]) then :yellow
|
12
|
+
when *options.fetch(:red, [:error, :fail, :delete]) then :red
|
13
|
+
else options.fetch(:default, :green)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Convection
|
2
|
+
module Model
|
3
|
+
module Mixin
|
4
|
+
##
|
5
|
+
# Add definition helpers for entities with policies
|
6
|
+
##
|
7
|
+
class Policy
|
8
|
+
include DSL::Helpers
|
9
|
+
|
10
|
+
DEFAULT_VERSION = '2012-10-17'
|
11
|
+
|
12
|
+
attribute :name
|
13
|
+
attribute :id
|
14
|
+
attribute :version
|
15
|
+
list :statement
|
16
|
+
|
17
|
+
def initialize(options = {})
|
18
|
+
@name = options.fetch(:name) { SecureRandom.uuid }
|
19
|
+
@version = DEFAULT_VERSION
|
20
|
+
@statement = []
|
21
|
+
|
22
|
+
@template = options[:template]
|
23
|
+
end
|
24
|
+
|
25
|
+
def allow(sid = nil, &block)
|
26
|
+
add_statement = Statement.new('Allow', @template)
|
27
|
+
add_statement.sid = sid unless sid.nil?
|
28
|
+
add_statement.instance_exec(&block) if block
|
29
|
+
|
30
|
+
statement(add_statement)
|
31
|
+
end
|
32
|
+
|
33
|
+
def document
|
34
|
+
{
|
35
|
+
'Version' => version,
|
36
|
+
'Statement' => statement.map(&:render)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def render(parent = {})
|
41
|
+
parent.tap do |resource|
|
42
|
+
resource['PolicyName'] = name unless name.is_a?(FalseClass)
|
43
|
+
resource['PolicyDocument'] = document
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# An IAM policy statement
|
49
|
+
##
|
50
|
+
class Statement
|
51
|
+
include DSL::Helpers
|
52
|
+
|
53
|
+
attribute :sid
|
54
|
+
attribute :effect
|
55
|
+
attribute :principal
|
56
|
+
attribute :condition
|
57
|
+
list :action
|
58
|
+
list :resource
|
59
|
+
|
60
|
+
def s3_resource(bucket, path = nil)
|
61
|
+
return resource "arn:aws:s3:::#{ bucket }/#{ path }" unless path.nil?
|
62
|
+
resource "arn:aws:s3:::#{ bucket }"
|
63
|
+
end
|
64
|
+
|
65
|
+
def initialize(effect = 'Allow', template = nil)
|
66
|
+
@effect = effect
|
67
|
+
|
68
|
+
@action = []
|
69
|
+
@resource = []
|
70
|
+
|
71
|
+
@template = template
|
72
|
+
end
|
73
|
+
|
74
|
+
def render
|
75
|
+
{
|
76
|
+
'Effect' => effect,
|
77
|
+
'Action' => action,
|
78
|
+
'Resource' => resource
|
79
|
+
}.tap do |statemant|
|
80
|
+
statemant['Sid'] = sid unless sid.nil?
|
81
|
+
statemant['Condition'] = condition unless condition.nil?
|
82
|
+
statemant['Principal'] = principal unless principal.nil?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|