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,172 @@
|
|
1
|
+
ec2-describe:
|
2
|
+
action:
|
3
|
+
- ec2:Describe*
|
4
|
+
- autoscaling:Describe*
|
5
|
+
- elasticloadbalancing:Describe*
|
6
|
+
|
7
|
+
ec2-cleanup-eni:
|
8
|
+
action:
|
9
|
+
- ec2:DeleteNetworkInterface
|
10
|
+
- ec2:DetachNetworkInterface
|
11
|
+
|
12
|
+
ec2-create-tag:
|
13
|
+
action:
|
14
|
+
- ec2:CreateTags
|
15
|
+
|
16
|
+
cloudwatch-logs:
|
17
|
+
action:
|
18
|
+
- logs:CreateLogGroup
|
19
|
+
- logs:CreateLogStream
|
20
|
+
- logs:PutLogEvents
|
21
|
+
- logs:DescribeLogStreams
|
22
|
+
- logs:DescribeLogGroups
|
23
|
+
resource:
|
24
|
+
- arn:aws:logs:*:*:*
|
25
|
+
|
26
|
+
cloudwatch-monitoring-writer:
|
27
|
+
action:
|
28
|
+
- cloudwatch:PutMetricData
|
29
|
+
|
30
|
+
cloudwatch-monitoring:
|
31
|
+
action:
|
32
|
+
- cloudwatch:PutMetricData
|
33
|
+
- cloudwatch:GetMetricStatistics
|
34
|
+
- cloudwatch:ListMetrics
|
35
|
+
|
36
|
+
s3-list-buckets:
|
37
|
+
action:
|
38
|
+
- s3:ListAllMyBuckets
|
39
|
+
- s3:ListBucket
|
40
|
+
|
41
|
+
s3-chef-ro:
|
42
|
+
action:
|
43
|
+
- s3:Get*
|
44
|
+
- s3:List*
|
45
|
+
resource:
|
46
|
+
- arn:aws:s3:::%{source_bucket}/chef
|
47
|
+
- arn:aws:s3:::%{source_bucket}/chef/*
|
48
|
+
|
49
|
+
s3-codedeploy-ro:
|
50
|
+
action:
|
51
|
+
- s3:Get*
|
52
|
+
- s3:List*
|
53
|
+
resource:
|
54
|
+
- arn:aws:s3:::%{source_bucket}/codedeploy
|
55
|
+
- arn:aws:s3:::%{source_bucket}/codedeploy/*
|
56
|
+
|
57
|
+
ecs-container-instance:
|
58
|
+
action:
|
59
|
+
- ecs:CreateCluster
|
60
|
+
- ecs:DeregisterContainerInstance
|
61
|
+
- ecs:DiscoverPollEndpoint
|
62
|
+
- ecs:Poll
|
63
|
+
- ecs:RegisterContainerInstance
|
64
|
+
- ecs:StartTelemetrySession
|
65
|
+
- ecs:Submit*
|
66
|
+
- ecr:GetAuthorizationToken
|
67
|
+
- ecr:BatchCheckLayerAvailability
|
68
|
+
- ecr:GetDownloadUrlForLayer
|
69
|
+
- ecr:BatchGetImage
|
70
|
+
- logs:CreateLogStream
|
71
|
+
- logs:PutLogEvents
|
72
|
+
|
73
|
+
ecs-service-role:
|
74
|
+
action:
|
75
|
+
- ecs:CreateCluster
|
76
|
+
- ecs:DeregisterContainerInstance
|
77
|
+
- ecs:DiscoverPollEndpoint
|
78
|
+
- ecs:Poll
|
79
|
+
- ecs:RegisterContainerInstance
|
80
|
+
- ecs:StartTelemetrySession
|
81
|
+
- ecs:Submit*
|
82
|
+
- ec2:AuthorizeSecurityGroupIngress
|
83
|
+
- ec2:Describe*
|
84
|
+
- elasticloadbalancing:DeregisterInstancesFromLoadBalancer
|
85
|
+
- elasticloadbalancing:Describe*
|
86
|
+
- elasticloadbalancing:RegisterInstancesWithLoadBalancer
|
87
|
+
- elasticloadbalancing:RegisterTargets
|
88
|
+
- elasticloadbalancing:DeregisterTargets
|
89
|
+
- autoscaling:Describe*
|
90
|
+
|
91
|
+
ecr-pull-images:
|
92
|
+
action:
|
93
|
+
- ecr:BatchCheckLayerAvailability
|
94
|
+
- ecr:BatchGetImage
|
95
|
+
- ecr:Get*
|
96
|
+
- ecr:List*
|
97
|
+
|
98
|
+
attach-ebs-volume:
|
99
|
+
action:
|
100
|
+
- ec2:DescribeVolumes
|
101
|
+
- ec2:AttachVolume
|
102
|
+
- ec2:DetachVolume
|
103
|
+
|
104
|
+
attach-network-interface:
|
105
|
+
action:
|
106
|
+
- ec2:DescribeNetworkInterfaces
|
107
|
+
- ec2:AttachNetworkInterface
|
108
|
+
- ec2:DetachNetworkInterface
|
109
|
+
|
110
|
+
associate-address:
|
111
|
+
action:
|
112
|
+
- ec2:AssociateAddress
|
113
|
+
|
114
|
+
ssm:
|
115
|
+
action:
|
116
|
+
- ssm:DescribeAssociation
|
117
|
+
- ssm:GetDeployablePatchSnapshotForInstance
|
118
|
+
- ssm:GetDocument
|
119
|
+
- ssm:GetParameters
|
120
|
+
- ssm:DescribeParameters
|
121
|
+
- ssm:ListAssociations
|
122
|
+
- ssm:ListInstanceAssociations
|
123
|
+
- ssm:PutInventory
|
124
|
+
- ssm:UpdateAssociationStatus
|
125
|
+
- ssm:UpdateInstanceAssociationStatus
|
126
|
+
- ssm:UpdateInstanceInformation
|
127
|
+
|
128
|
+
ec2messages:
|
129
|
+
action:
|
130
|
+
- ec2messages:AcknowledgeMessage
|
131
|
+
- ec2messages:DeleteMessage
|
132
|
+
- ec2messages:FailMessage
|
133
|
+
- ec2messages:GetEndpoint
|
134
|
+
- ec2messages:GetMessages
|
135
|
+
- ec2messages:SendReply
|
136
|
+
|
137
|
+
rds-backup-restore:
|
138
|
+
action:
|
139
|
+
- s3:ListBucket
|
140
|
+
- s3:GetBucketLocation
|
141
|
+
- s3:GetObjectMetaData
|
142
|
+
- s3:GetObject
|
143
|
+
- s3:PutObject
|
144
|
+
- s3:ListMultipartUploadParts
|
145
|
+
- s3:AbortMultipartUpload
|
146
|
+
resource:
|
147
|
+
- Fn::Join:
|
148
|
+
- ""
|
149
|
+
-
|
150
|
+
- 'arn:aws:s3:::'
|
151
|
+
- Fn::FindInMap:
|
152
|
+
- 'AccountId'
|
153
|
+
- Ref: 'AWS::AccountId'
|
154
|
+
- 'DatabaseBucket'
|
155
|
+
- Fn::Join:
|
156
|
+
- ""
|
157
|
+
-
|
158
|
+
- 'arn:aws:s3:::'
|
159
|
+
- Fn::FindInMap:
|
160
|
+
- 'AccountId'
|
161
|
+
- Ref: 'AWS::AccountId'
|
162
|
+
- 'DatabaseBucket'
|
163
|
+
- '/*'
|
164
|
+
|
165
|
+
spot-role:
|
166
|
+
action:
|
167
|
+
- ec2:DescribeImages
|
168
|
+
- ec2:DescribeSubnets
|
169
|
+
- ec2:RequestSpotInstances
|
170
|
+
- ec2:TerminateInstances
|
171
|
+
- ec2:DescribeInstanceStatus
|
172
|
+
- iam:PassRole
|
@@ -0,0 +1,55 @@
|
|
1
|
+
def service_role_assume_policy(service)
|
2
|
+
unless service.end_with? '.amazonaws.com'
|
3
|
+
service = "#{service}.amazonaws.com"
|
4
|
+
end
|
5
|
+
return {
|
6
|
+
Version: '2012-10-17',
|
7
|
+
Statement: [{ Effect: 'Allow', Principal: { Service: "#{service}" }, Action: 'sts:AssumeRole' }]
|
8
|
+
}
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
def iam_policy_allow(name, actions, resource='*')
|
13
|
+
|
14
|
+
return {
|
15
|
+
PolicyName: name,
|
16
|
+
PolicyDocument: {
|
17
|
+
Statement: [{
|
18
|
+
Sid: name.gsub('_', '').gsub('-', '').downcase,
|
19
|
+
Action: actions,
|
20
|
+
Resource: resource,
|
21
|
+
Effect: 'Allow'
|
22
|
+
}]
|
23
|
+
}
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
class IAMPolicies
|
28
|
+
|
29
|
+
def initialize(custom_policies = nil)
|
30
|
+
@managed_policies = YAML.load(File.read("#{File.dirname(__FILE__)}/config/managed_policies.yaml"))
|
31
|
+
@policy_array = Array.new
|
32
|
+
@policies = (not custom_policies.nil?) ? @managed_policies.merge(custom_policies) : @managed_policies
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_policies(group = nil)
|
36
|
+
if group.kind_of?(Array)
|
37
|
+
puts group
|
38
|
+
create_policies(group)
|
39
|
+
else
|
40
|
+
create_policies(@config['default_policies']) if @config.key?('default_policies')
|
41
|
+
create_policies(@config['group_policies'][group]) unless group.nil?
|
42
|
+
end
|
43
|
+
return @policy_array
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_policies(policies)
|
47
|
+
policies.each do |policy|
|
48
|
+
raise "ERROR: #{policy} policy doesn't exist in the managed policies or as a custom policy" if !@policies.key?(policy)
|
49
|
+
resource = (@policies[policy].key?('resource') ? (@policies[policy]['resource']) : ["*"])
|
50
|
+
@policy_array << { PolicyName: policy, PolicyDocument: { Statement: [{ Effect: 'Allow', Action: @policies[policy]['action'], Resource: resource }] } }
|
51
|
+
end
|
52
|
+
return @policy_array
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require_relative './iam_helper'
|
2
|
+
|
3
|
+
def render_lambda_functions(cfndsl, lambdas, lambda_metadata, distribution)
|
4
|
+
|
5
|
+
custom_policies = (lambdas.key? 'custom_policies') ? lambdas['custom_policies'] : {}
|
6
|
+
lambdas['roles'].each do |lambda_role, role_config|
|
7
|
+
cfndsl.IAM_Role("LambdaRole#{lambda_role}") do
|
8
|
+
AssumeRolePolicyDocument service_role_assume_policy('lambda')
|
9
|
+
Path '/'
|
10
|
+
unless role_config['policies_inline'].nil?
|
11
|
+
Policies(
|
12
|
+
IAMPolicies.new(custom_policies).create_policies(role_config['policies_inline'])
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
unless role_config['policies_managed'].nil?
|
17
|
+
ManagedPolicyArns(role_config['policies_managed'])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
lambdas['functions'].each do |key, lambda_config|
|
23
|
+
name = key
|
24
|
+
environment = lambda_config['environment'] || {}
|
25
|
+
|
26
|
+
# Create Lambda function
|
27
|
+
function_name = name
|
28
|
+
Lambda_Function(function_name) do
|
29
|
+
Code({
|
30
|
+
S3Bucket: distribution['bucket'],
|
31
|
+
S3Key: "#{distribution['prefix']}/#{distribution['version']}/#{lambda_metadata['path'][key]}"
|
32
|
+
})
|
33
|
+
|
34
|
+
Environment(Variables: Hash[environment.collect { |k, v| [k, v] }])
|
35
|
+
|
36
|
+
Handler(lambda_config['handler'] || 'index.handler')
|
37
|
+
MemorySize(lambda_config['memory'] || 128)
|
38
|
+
Role(FnGetAtt("LambdaRole#{lambda_config['role']}", 'Arn'))
|
39
|
+
Runtime(lambda_config['runtime'])
|
40
|
+
Timeout(lambda_config['timeout'] || 10)
|
41
|
+
if !lambda_config['vpc'].nil? && lambda_config['vpc']
|
42
|
+
# TODO implement VPC config
|
43
|
+
end
|
44
|
+
|
45
|
+
if !lambda_config['named'].nil? && lambda_config['named']
|
46
|
+
FunctionName(name)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
Lambda_Version("#{name}Version#{lambda_metadata['version'][key]}") do
|
51
|
+
DeletionPolicy('Retain')
|
52
|
+
FunctionName(Ref(name))
|
53
|
+
CodeSha256(lambda_metadata['sha256'][key])
|
54
|
+
end
|
55
|
+
|
56
|
+
# Generate lambda function Policy
|
57
|
+
unless lambda_config['allowed_sources'].nil?
|
58
|
+
i = 1
|
59
|
+
lambda_config['allowed_sources'].each do |source|
|
60
|
+
Lambda_Permission("#{name}Permissions#{i}") do
|
61
|
+
FunctionName(Ref(name))
|
62
|
+
Action('lambda:InvokeFunction')
|
63
|
+
Principal(source['principal'])
|
64
|
+
end
|
65
|
+
i += 1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Scheduled triggering of lambda function
|
70
|
+
if lambda_config.key?('schedules')
|
71
|
+
lambda_config['schedules'].each_with_index do |schedule, index|
|
72
|
+
Events_Rule("Lambda#{name}Schedule#{index}") do
|
73
|
+
ScheduleExpression("cron(#{schedule['cronExpression']})")
|
74
|
+
State('ENABLED')
|
75
|
+
target = {
|
76
|
+
'Arn' => FnGetAtt(name, 'Arn'), 'Id' => "lambda#{name}",
|
77
|
+
}
|
78
|
+
target['Input'] = schedule['payload'] if schedule.key?('payload')
|
79
|
+
Targets([target])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
data/cfndsl_ext/sg.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
def sg_create_rules (x, ip_blocks={})
|
3
|
+
rules = []
|
4
|
+
x.each do | group |
|
5
|
+
group['ips'].each do |ip|
|
6
|
+
group['rules'].each do |rule|
|
7
|
+
lookup_ips_for_sg(ip_blocks, ip).each do |cidr|
|
8
|
+
rules << { IpProtocol: "#{rule['IpProtocol']}", FromPort: "#{rule['FromPort']}", ToPort: "#{rule['ToPort']}", CidrIp: cidr }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
return rules
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def lookup_ips_for_sg (ips, ip_block_name={})
|
18
|
+
if ip_block_name == 'stack'
|
19
|
+
cidr = [FnJoin( "", [ "10.", Ref('StackOctet'), ".", "0.0/16" ] )]
|
20
|
+
elsif ips.has_key? ip_block_name
|
21
|
+
cidr = ips[ip_block_name]
|
22
|
+
else
|
23
|
+
cidr = [ip_block_name]
|
24
|
+
end
|
25
|
+
cidr
|
26
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'aws-sdk-s3'
|
2
|
+
|
3
|
+
def aws_credentials
|
4
|
+
# TODO implement credentials e.g. for environment create/update/delete
|
5
|
+
end
|
6
|
+
|
7
|
+
def s3_bucket_region(bucket)
|
8
|
+
s3 = Aws::S3::Client.new
|
9
|
+
location = s3.get_bucket_location({ bucket: bucket }).location_constraint
|
10
|
+
location = 'us-east-1' if location == ''
|
11
|
+
location = 'eu-west-1' if location == 'EU'
|
12
|
+
location
|
13
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class ::Hash
|
2
|
+
def deep_merge(second)
|
3
|
+
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : Array === v1 && Array === v2 ? v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }
|
4
|
+
self.merge(second.to_h, &merger)
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
def extend(second)
|
9
|
+
second.each { |k, v|
|
10
|
+
|
11
|
+
if ((self.key? k) and (v.is_a? Hash and self[k].is_a? Hash))
|
12
|
+
self[k].extend(v)
|
13
|
+
else
|
14
|
+
self[k] = v
|
15
|
+
end
|
16
|
+
|
17
|
+
} if second.is_a? Hash
|
18
|
+
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require_relative '../lib/highlander.mapproviders'
|
2
|
+
|
3
|
+
# Return mapping provider as class
|
4
|
+
def mappings_provider(provider_name)
|
5
|
+
return nil if provider_name.nil?
|
6
|
+
provider = nil
|
7
|
+
providers = Object.const_get('Highlander').const_get('MapProviders')
|
8
|
+
begin
|
9
|
+
providers.const_get(provider_name)
|
10
|
+
rescue NameError => e
|
11
|
+
if e.to_s.include? 'uninitialized constant Highlander::MapProviders::'
|
12
|
+
return nil
|
13
|
+
end
|
14
|
+
STDERR.puts(e.to_s)
|
15
|
+
raise e
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return all of the maps from mapping provider
|
20
|
+
def mappings_provider_maps(provider_name, config)
|
21
|
+
provider = mappings_provider(provider_name)
|
22
|
+
|
23
|
+
if provider.nil?
|
24
|
+
STDERR.puts("Can't find mapping provider #{provider_name} and it's maps")
|
25
|
+
return nil
|
26
|
+
else
|
27
|
+
return provider.getMaps(config)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Renders CloudFormation function for retrieving Mapping value. If key name and map name are not given,
|
32
|
+
# extraction from mapping provider will be tried
|
33
|
+
def mapping_value(component:, provider_name:, value_name:, key_name: nil)
|
34
|
+
|
35
|
+
provider = nil
|
36
|
+
|
37
|
+
# if key name is nil, provider must provide key
|
38
|
+
if key_name.nil?
|
39
|
+
provider = mappings_provider(provider_name)
|
40
|
+
if provider.nil?
|
41
|
+
STDERR.puts("Error: mapping provider #{provider_name} not found, can't render value of #{value_name} attribute")
|
42
|
+
return nil
|
43
|
+
end
|
44
|
+
unless provider.respond_to? 'getDefaultKey'
|
45
|
+
STDERR.puts("Error: #{provider} does not implement getDefaultKey. Can't render value of #{value_name} attribtue")
|
46
|
+
return nil
|
47
|
+
end
|
48
|
+
|
49
|
+
key_name = provider.getDefaultKey
|
50
|
+
end
|
51
|
+
|
52
|
+
# Map name defaults to provider name. If provider exists and implements getMapName
|
53
|
+
# map name will be dynamically resolved via this method
|
54
|
+
map_name = provider_name
|
55
|
+
|
56
|
+
provider = mappings_provider(provider_name) if provider.nil?
|
57
|
+
unless provider.nil?
|
58
|
+
if provider.respond_to? 'getMapName'
|
59
|
+
map_name = provider.getMapName
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# there is no provider
|
64
|
+
# and map name is not a reference
|
65
|
+
if((provider.nil?) and (not map_name.start_with? 'Ref('))
|
66
|
+
# check if mapping exists on component
|
67
|
+
unless ((component.config['mappings'].key? map_name))
|
68
|
+
STDERR.puts("Could not resolve mapping value: MapName=#{map_name},Key=#{key_name},Attribute=#{value_name}")
|
69
|
+
return nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# both map name and key name could be predefined via intrinsic functions
|
74
|
+
if map_name.class == Hash
|
75
|
+
map_name = map_name.to_s
|
76
|
+
elsif( not map_name.include? 'Ref(')
|
77
|
+
map_name = "'#{map_name}'"
|
78
|
+
end
|
79
|
+
|
80
|
+
if key_name.class == Hash
|
81
|
+
key_name = key_name.to_s
|
82
|
+
elsif( not key_name.include? 'Ref(')
|
83
|
+
key_name = "'#{key_name}'"
|
84
|
+
end
|
85
|
+
|
86
|
+
if value_name.nil?
|
87
|
+
STDERR.puts("No value defined for mapping parameter. MapName=#{map_name},Key=#{key_name},Attribute=#{value_name}")
|
88
|
+
return nil
|
89
|
+
end
|
90
|
+
if value_name.class == Hash
|
91
|
+
value_name = value_name.to_s
|
92
|
+
elsif( not value_name.include? 'Ref(')
|
93
|
+
value_name = "'#{value_name}'"
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
return "FnFindInMap(#{map_name},#{key_name},#{value_name})"
|
98
|
+
|
99
|
+
end
|
data/hl_ext/vpc.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
def subnet_parameters(subnets, maximum_availability_zones)
|
2
|
+
subnets.each { |subnet_name, subnet_config|
|
3
|
+
# Account mappings for AZs
|
4
|
+
maximum_availability_zones.times do |x|
|
5
|
+
|
6
|
+
# Request output from other component as input
|
7
|
+
# to this component
|
8
|
+
OutputParam component: 'vpc', name: "Subnet#{subnet_config['name']}#{x}"
|
9
|
+
|
10
|
+
end
|
11
|
+
}
|
12
|
+
end
|