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,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
|