cfn_monitor 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +20 -1
- data/lib/cfn_monitor.rb +26 -7
- data/lib/cfn_monitor/deploy.rb +8 -6
- data/lib/cfn_monitor/generate.rb +75 -64
- data/lib/cfn_monitor/query.rb +11 -7
- data/lib/cfn_monitor/validate.rb +76 -0
- data/lib/cfn_monitor/version.rb +1 -1
- data/lib/config/templates.yml +61 -3
- data/lib/templates/alarms.rb +2 -2
- data/lib/templates/dns.rb +61 -0
- data/lib/templates/ecsClusters.rb +76 -0
- data/lib/templates/hosts.rb +2 -2
- data/lib/templates/master.rb +139 -4
- data/lib/templates/resources.rb +6 -2
- data/lib/templates/ssl.rb +61 -0
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 466206e0f5cedb6a5006c98a0004ce27dddf0fe0d793ed1842e1ec3d11fb8382
|
4
|
+
data.tar.gz: 72c10dbcead4deccf275f20609342474b37ae41c310643bc52d0340a0af67017
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1622b83a7cdfbf96757582b130b7c04c2ce7789f8f9de4f04c0610e2ab59795c2207361f79f157777bf7d858f42018cdaf0f430b9bb05053888b5950118058c
|
7
|
+
data.tar.gz: 9bd6dced53b3f49fa8516194a031239fe290bce680b3b59d9284a789e8509de2916c27988d830dc7b70038d561b0fa45402453e1258ebf138ce4235ee797affa
|
data/README.md
CHANGED
@@ -9,7 +9,7 @@ monitorable resources that can be placed into a config file. This config
|
|
9
9
|
can then be used to generate a cloudformation stack to create and manage
|
10
10
|
cloudwatch alarms.
|
11
11
|
|
12
|
-
It is packaged as a docker container `base2/
|
12
|
+
It is packaged as a docker container `base2/cfn-monitor` and
|
13
13
|
can be run by volume mounting in a local directory to access the config
|
14
14
|
or by using within AWS CodePipeline.
|
15
15
|
|
@@ -152,6 +152,25 @@ timeOut | A timeout value for the endpoint monitoring | 120 seconds
|
|
152
152
|
scheduleExpression | A cron expression used to schedule the endpoint monitoring | Every minute
|
153
153
|
environments | A string or array of environment names. Monitoring will only be deployed for these environments (if specified) | All environments
|
154
154
|
|
155
|
+
#### SSL certificate expiry date checking
|
156
|
+
To alert on the expiry date of an SSL certificate for a particular domain, add the following config:
|
157
|
+
|
158
|
+
```YAML
|
159
|
+
ssl:
|
160
|
+
https://www.base2services.com: Ssl
|
161
|
+
```
|
162
|
+
|
163
|
+
The `Ssl` template is scheduled to push a metric once every day.
|
164
|
+
|
165
|
+
#### DNS domain expiry date checking
|
166
|
+
To alert on the expiry date of an DNS domain, add the following config:
|
167
|
+
|
168
|
+
```YAML
|
169
|
+
dns:
|
170
|
+
base2services.com: Dns
|
171
|
+
```
|
172
|
+
|
173
|
+
The `Dns` template is scheduled to push a metric once every day.
|
155
174
|
|
156
175
|
#### Multiple templates
|
157
176
|
You can specify multiple templates for the resource by providing a list/array. You may want to do this if you want to deploy some custom alarms in addition to the default alarms for a resource.
|
data/lib/cfn_monitor.rb
CHANGED
@@ -2,6 +2,7 @@ require "thor"
|
|
2
2
|
require "cfn_monitor/version"
|
3
3
|
require "cfn_monitor/query"
|
4
4
|
require "cfn_monitor/generate"
|
5
|
+
require "cfn_monitor/validate"
|
5
6
|
require "cfn_monitor/deploy"
|
6
7
|
|
7
8
|
module CfnMonitor
|
@@ -13,6 +14,12 @@ module CfnMonitor
|
|
13
14
|
puts CfnMonitor::VERSION
|
14
15
|
end
|
15
16
|
|
17
|
+
class_option :silent,
|
18
|
+
aliases: :s,
|
19
|
+
type: :boolean,
|
20
|
+
default: false,
|
21
|
+
desc: "Don't print Cfndsl output"
|
22
|
+
|
16
23
|
# class_option :verbose,
|
17
24
|
# aliases: :V,
|
18
25
|
# type: :boolean,
|
@@ -32,35 +39,47 @@ module CfnMonitor
|
|
32
39
|
# type: :string,
|
33
40
|
# desc: "Profile name in AWS credentials file"
|
34
41
|
|
35
|
-
desc "generate", "Generate monitoring
|
42
|
+
desc "generate", "Generate monitoring CloudFormation templates"
|
36
43
|
long_desc <<-LONG
|
37
|
-
Generates
|
44
|
+
Generates CloudFormation templates from the alarm configuration and output to the output/ directory.
|
38
45
|
LONG
|
39
46
|
method_option :application, aliases: :a, type: :string, desc: "application name"
|
40
|
-
|
47
|
+
method_option :validate, aliases: :v, type: :boolean, default: false, desc: "validate cfn templates"
|
41
48
|
def generate
|
42
49
|
CfnMonitor::Generate.run(options)
|
50
|
+
if options['validate']
|
51
|
+
CfnMonitor::Validate.run(options)
|
52
|
+
end
|
43
53
|
end
|
44
54
|
|
45
|
-
desc "query", "Queries a
|
55
|
+
desc "query", "Queries a CloudFormation stack for monitorable resources"
|
46
56
|
long_desc <<-LONG
|
47
57
|
This will provide a list of resources in the correct config syntax,
|
48
58
|
including the nested stacks and the default templates for those resources.
|
49
59
|
LONG
|
50
60
|
method_option :application, aliases: :a, type: :string, desc: "application name"
|
51
|
-
method_option :stack, aliases: :s, type: :string, desc: "
|
61
|
+
method_option :stack, aliases: :s, type: :string, desc: "CloudFormation stack name"
|
52
62
|
def query
|
53
63
|
CfnMonitor::Query.run(options)
|
54
64
|
end
|
55
65
|
|
56
|
-
desc "deploy", "Deploys
|
66
|
+
desc "deploy", "Deploys generated CloudFormation templates to an S3 bucket"
|
57
67
|
long_desc <<-LONG
|
58
|
-
Deploys
|
68
|
+
Deploys generated CloudFormation templates to the specified S3 source bucket
|
59
69
|
LONG
|
60
70
|
method_option :application, aliases: :a, type: :string, desc: "application name"
|
61
71
|
def deploy
|
62
72
|
CfnMonitor::Deploy.run(options)
|
63
73
|
end
|
64
74
|
|
75
|
+
desc "validate", "Validates CloudFormation templates in an S3 bucket"
|
76
|
+
long_desc <<-LONG
|
77
|
+
Validates generated CloudFormation templates in a specified S3 source bucket
|
78
|
+
LONG
|
79
|
+
method_option :application, aliases: :a, type: :string, desc: "application name"
|
80
|
+
def validate
|
81
|
+
CfnMonitor::Validate.run(options)
|
82
|
+
end
|
83
|
+
|
65
84
|
end
|
66
85
|
end
|
data/lib/cfn_monitor/deploy.rb
CHANGED
@@ -6,14 +6,16 @@ module CfnMonitor
|
|
6
6
|
|
7
7
|
def self.run(options)
|
8
8
|
|
9
|
-
if
|
10
|
-
|
9
|
+
if options['application']
|
10
|
+
application = options['application']
|
11
|
+
custom_alarms_config_file = "#{application}/alarms.yml"
|
12
|
+
output_path = "output/#{application}"
|
13
|
+
else
|
14
|
+
application = File.basename(Dir.getwd)
|
15
|
+
custom_alarms_config_file = "alarms.yml"
|
16
|
+
output_path = "output"
|
11
17
|
end
|
12
18
|
|
13
|
-
application = options['application']
|
14
|
-
|
15
|
-
custom_alarms_config_file = "#{application}/alarms.yml"
|
16
|
-
output_path = "output/#{application}"
|
17
19
|
upload_path = "cloudformation/monitoring/#{application}"
|
18
20
|
|
19
21
|
# Load custom config files
|
data/lib/cfn_monitor/generate.rb
CHANGED
@@ -10,11 +10,13 @@ module CfnMonitor
|
|
10
10
|
|
11
11
|
def self.run(options)
|
12
12
|
|
13
|
-
if
|
14
|
-
|
13
|
+
if options['silent']
|
14
|
+
verbose_cfndsl = false
|
15
|
+
else
|
16
|
+
verbose_cfndsl = STDOUT
|
15
17
|
end
|
16
18
|
|
17
|
-
application = options['application']
|
19
|
+
application = options['application'] || '.'
|
18
20
|
|
19
21
|
template_path = File.join(File.dirname(__FILE__),'../config/templates.yml')
|
20
22
|
config_path = File.join(File.dirname(__FILE__),'../config/config.yml')
|
@@ -47,74 +49,81 @@ module CfnMonitor
|
|
47
49
|
alarms = []
|
48
50
|
resources = custom_alarms_config['resources']
|
49
51
|
metrics = custom_alarms_config['metrics']
|
50
|
-
hosts = custom_alarms_config['hosts']
|
51
|
-
|
52
|
-
|
53
|
-
services
|
54
|
-
endpoints = custom_alarms_config['endpoints']
|
55
|
-
|
56
|
-
|
52
|
+
hosts = custom_alarms_config['hosts'] || {}
|
53
|
+
ssl = custom_alarms_config['ssl'] || {}
|
54
|
+
dns = custom_alarms_config['dns'] || {}
|
55
|
+
services = custom_alarms_config['services'] || {}
|
56
|
+
endpoints = custom_alarms_config['endpoints'] || {}
|
57
|
+
ecsClusters = custom_alarms_config['ecsClusters'] || {}
|
58
|
+
|
59
|
+
alarm_parameters = { resources: resources, metrics: metrics, endpoints: endpoints, hosts: hosts, ssl: ssl, dns: dns, services: services, ecsClusters: ecsClusters }
|
57
60
|
source_bucket = custom_alarms_config['source_bucket']
|
58
61
|
|
59
|
-
|
62
|
+
alarm_parameters.each do | k,v |
|
60
63
|
if !v.nil?
|
61
|
-
v.each do | resource,
|
64
|
+
v.each do | resource,attributeList |
|
62
65
|
# set environments to 'all' by default
|
63
66
|
environments = ['all']
|
64
67
|
# Support config hashs for additional parameters
|
65
68
|
params = {}
|
66
|
-
if
|
67
|
-
|
68
|
-
environments = b if a == 'environments'
|
69
|
-
# Convert strings to arrays for consistency
|
70
|
-
if !environments.kind_of?(Array) then environments = environments.split end
|
71
|
-
params[a] = b if !['template','environments'].member? a
|
72
|
-
end
|
73
|
-
templatesEnabled = attributes['template']
|
74
|
-
else
|
75
|
-
templatesEnabled = attributes
|
69
|
+
if !attributeList.kind_of?(Array)
|
70
|
+
attributeList = [attributeList]
|
76
71
|
end
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
template_to = templates['templates'][templateEnabled].without('template')
|
85
|
-
template_merged = CfnMonitor::Utils.deep_merge(template_from, template_to)
|
86
|
-
templates['templates'][templateEnabled] = template_merged
|
72
|
+
attributeList.each do | attributes |
|
73
|
+
if attributes.kind_of?(Hash)
|
74
|
+
attributes.each do | a,b |
|
75
|
+
environments = b if a == 'environments'
|
76
|
+
# Convert strings to arrays for consistency
|
77
|
+
if !environments.kind_of?(Array) then environments = environments.split end
|
78
|
+
params[a] = b if !['template','environments'].member? a
|
87
79
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
80
|
+
templatesEnabled = attributes['template']
|
81
|
+
else
|
82
|
+
templatesEnabled = attributes
|
83
|
+
end
|
84
|
+
# Convert strings to arrays for looping
|
85
|
+
if !templatesEnabled.kind_of?(Array) then templatesEnabled = templatesEnabled.split end
|
86
|
+
templatesEnabled.each do | templateEnabled |
|
87
|
+
if !templates['templates'][templateEnabled].nil?
|
88
|
+
# If a template is provided, inherit that template
|
89
|
+
if !templates['templates'][templateEnabled]['template'].nil?
|
90
|
+
template_from = Marshal.load( Marshal.dump(templates['templates'][templates['templates'][templateEnabled]['template']]) )
|
91
|
+
template_to = templates['templates'][templateEnabled].without('template')
|
92
|
+
template_merged = CfnMonitor::Utils.deep_merge(template_from, template_to)
|
93
|
+
templates['templates'][templateEnabled] = template_merged
|
93
94
|
end
|
94
|
-
|
95
|
-
resourceParams
|
96
|
-
|
97
|
-
|
98
|
-
|
95
|
+
templates['templates'][templateEnabled].each do | alarm,parameters |
|
96
|
+
resourceParams = parameters.clone
|
97
|
+
# Override template params if overrides provided
|
98
|
+
params.each do | x,y |
|
99
|
+
resourceParams[x] = y
|
100
|
+
end
|
101
|
+
|
102
|
+
if k == :hosts
|
103
|
+
resourceParams['cmds'].each do |cmd|
|
104
|
+
hostParams = resourceParams.clone
|
105
|
+
hostParams['cmd'] = cmd
|
106
|
+
# Construct alarm object per cmd
|
107
|
+
alarms << {
|
108
|
+
resource: resource,
|
109
|
+
type: k[0...-1],
|
110
|
+
template: templateEnabled,
|
111
|
+
alarm: alarm,
|
112
|
+
parameters: hostParams,
|
113
|
+
environments: environments
|
114
|
+
}
|
115
|
+
end
|
116
|
+
else
|
117
|
+
# Construct alarm object
|
99
118
|
alarms << {
|
100
119
|
resource: resource,
|
101
120
|
type: k[0...-1],
|
102
121
|
template: templateEnabled,
|
103
122
|
alarm: alarm,
|
104
|
-
parameters:
|
123
|
+
parameters: resourceParams,
|
105
124
|
environments: environments
|
106
125
|
}
|
107
126
|
end
|
108
|
-
else
|
109
|
-
# Construct alarm object
|
110
|
-
alarms << {
|
111
|
-
resource: resource,
|
112
|
-
type: k[0...-1],
|
113
|
-
template: templateEnabled,
|
114
|
-
alarm: alarm,
|
115
|
-
parameters: resourceParams,
|
116
|
-
environments: environments
|
117
|
-
}
|
118
127
|
end
|
119
128
|
end
|
120
129
|
end
|
@@ -149,27 +158,29 @@ module CfnMonitor
|
|
149
158
|
temp_files[i].rewind
|
150
159
|
end
|
151
160
|
|
152
|
-
write_cfdndsl_template(temp_file_path, temp_file_paths, custom_alarms_config_file, source_bucket, template_envs, output_path, upload_path)
|
161
|
+
write_cfdndsl_template(alarm_parameters, temp_file_path, temp_file_paths, custom_alarms_config_file, source_bucket, template_envs, output_path, upload_path, verbose_cfndsl)
|
153
162
|
|
154
163
|
end
|
155
164
|
|
156
|
-
def self.write_cfdndsl_template(
|
165
|
+
def self.write_cfdndsl_template(alarm_parameters,alarms_config_file,configs,custom_alarms_config_file,source_bucket,template_envs,output_path,upload_path,verbose_cfndsl)
|
157
166
|
template_path = File.expand_path("../../templates", __FILE__)
|
158
167
|
FileUtils::mkdir_p output_path
|
159
168
|
configs.each_with_index do |config,index|
|
160
169
|
File.open("#{output_path}/resources#{index}.json", 'w') { |file|
|
161
|
-
file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/resources.rb",[[:yaml, config],[:raw, "template_number=#{index}"],[:raw, "source_bucket='#{source_bucket}'"],[:raw, "upload_path='#{upload_path}'"]],
|
170
|
+
file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/resources.rb",[[:yaml, config],[:raw, "template_number=#{index}"],[:raw, "source_bucket='#{source_bucket}'"],[:raw, "upload_path='#{upload_path}'"]],verbose_cfndsl)))}
|
162
171
|
File.open("#{output_path}/alarms#{index}.json", 'w') { |file|
|
163
|
-
file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/alarms.rb",[[:yaml, config],[:raw, "template_number=#{index}"],[:raw, "template_envs=#{template_envs}"]],
|
172
|
+
file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/alarms.rb",[[:yaml, config],[:raw, "template_number=#{index}"],[:raw, "template_envs=#{template_envs}"]],verbose_cfndsl)))}
|
173
|
+
end
|
174
|
+
['endpoints', 'ssl', "dns", 'hosts', 'services','ecsClusters'].each do |template|
|
175
|
+
if !alarm_parameters[template.to_sym].nil? and alarm_parameters[template.to_sym] != {}
|
176
|
+
File.open("#{output_path}/#{template}.json", 'w') { |file|
|
177
|
+
file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/#{template}.rb",[[:yaml, alarms_config_file],[:raw, "template_envs=#{template_envs}"]],verbose_cfndsl)))
|
178
|
+
}
|
179
|
+
end
|
164
180
|
end
|
165
|
-
|
166
|
-
file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/endpoints.rb",[[:yaml, alarms_config],[:raw, "template_envs=#{template_envs}"]],STDOUT)))}
|
167
|
-
File.open("#{output_path}/hosts.json", 'w') { |file|
|
168
|
-
file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/hosts.rb",[[:yaml, alarms_config],[:raw, "template_envs=#{template_envs}"]],STDOUT)))}
|
169
|
-
File.open("#{output_path}/services.json", 'w') { |file|
|
170
|
-
file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/services.rb",[[:yaml, alarms_config],[:raw, "template_envs=#{template_envs}"]],STDOUT)))}
|
181
|
+
|
171
182
|
File.open("#{output_path}/master.json", 'w') { |file|
|
172
|
-
file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/master.rb",[[:yaml, custom_alarms_config_file],[:raw, "templateCount=#{configs.count}"],[:raw, "template_envs=#{template_envs}"],[:raw, "upload_path='#{upload_path}'"]],
|
183
|
+
file.write(JSON.pretty_generate( CfnDsl.eval_file_with_extras("#{template_path}/master.rb",[[:yaml, custom_alarms_config_file],[:raw, "templateCount=#{configs.count}"],[:raw, "template_envs=#{template_envs}"],[:raw, "upload_path='#{upload_path}'"]],verbose_cfndsl)))}
|
173
184
|
end
|
174
185
|
|
175
186
|
def self.get_alarm_envs(params)
|
data/lib/cfn_monitor/query.rb
CHANGED
@@ -6,19 +6,23 @@ module CfnMonitor
|
|
6
6
|
|
7
7
|
def self.run(options)
|
8
8
|
|
9
|
-
if !options['application'] || !options['stack']
|
10
|
-
raise "No application specified"
|
11
|
-
end
|
12
|
-
|
13
9
|
if !options['stack']
|
14
10
|
raise "No stack specified"
|
15
11
|
end
|
16
12
|
|
13
|
+
if options['application']
|
14
|
+
application = options['application']
|
15
|
+
custom_alarms_config_file = "#{application}/alarms.yml"
|
16
|
+
else
|
17
|
+
application = File.basename(Dir.getwd)
|
18
|
+
custom_alarms_config_file = "alarms.yml"
|
19
|
+
end
|
20
|
+
|
17
21
|
config_path = File.join(File.dirname(__FILE__),'../config/config.yml')
|
18
22
|
# Load global config files
|
19
23
|
config = YAML.load(File.read(config_path))
|
20
24
|
|
21
|
-
custom_alarms_config_file = "#{
|
25
|
+
custom_alarms_config_file = "#{application}/alarms.yml"
|
22
26
|
|
23
27
|
# Load custom config files
|
24
28
|
custom_alarms_config = YAML.load(File.read(custom_alarms_config_file)) if File.file?(custom_alarms_config_file)
|
@@ -27,7 +31,7 @@ module CfnMonitor
|
|
27
31
|
|
28
32
|
puts "-----------------------------------------------"
|
29
33
|
puts "stack: #{options['stack']}"
|
30
|
-
puts "application: #{
|
34
|
+
puts "application: #{application}"
|
31
35
|
puts "-----------------------------------------------"
|
32
36
|
puts "Searching Stacks for Monitorable Resources"
|
33
37
|
puts "-----------------------------------------------"
|
@@ -75,7 +79,7 @@ module CfnMonitor
|
|
75
79
|
puts "-----------------------------------------------"
|
76
80
|
end
|
77
81
|
puts "Monitorable resources in #{options['stack']} stack: #{stackResourceCount}"
|
78
|
-
puts "Resources in #{
|
82
|
+
puts "Resources in #{application} alarms config: #{configResourceCount}"
|
79
83
|
if stackResourceCount > 0
|
80
84
|
puts "Coverage: #{100-(configResources.count*100/stackResourceCount)}%"
|
81
85
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'aws-sdk-s3'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module CfnMonitor
|
5
|
+
class Validate
|
6
|
+
|
7
|
+
def self.run(options)
|
8
|
+
|
9
|
+
if options['application']
|
10
|
+
application = options['application']
|
11
|
+
custom_alarms_config_file = "#{application}/alarms.yml"
|
12
|
+
output_path = "output/#{application}"
|
13
|
+
else
|
14
|
+
application = File.basename(Dir.getwd)
|
15
|
+
custom_alarms_config_file = "alarms.yml"
|
16
|
+
output_path = "output"
|
17
|
+
end
|
18
|
+
|
19
|
+
validate_path = "cloudformation/monitoring/#{application}/validate"
|
20
|
+
|
21
|
+
# Load custom config files
|
22
|
+
if File.file?(custom_alarms_config_file)
|
23
|
+
custom_alarms_config = YAML.load(File.read(custom_alarms_config_file)) if File.file?(custom_alarms_config_file)
|
24
|
+
else
|
25
|
+
puts "Failed to load #{custom_alarms_config_file}"
|
26
|
+
exit 1
|
27
|
+
end
|
28
|
+
|
29
|
+
source_region = custom_alarms_config['source_region']
|
30
|
+
source_bucket = custom_alarms_config['source_bucket']
|
31
|
+
|
32
|
+
cfn = Aws::CloudFormation::Client.new(region: source_region)
|
33
|
+
s3 = Aws::S3::Client.new(region: source_region)
|
34
|
+
validated = 0
|
35
|
+
unvalidated = 0
|
36
|
+
|
37
|
+
puts "-----------------------------------------------"
|
38
|
+
|
39
|
+
["#{output_path}/*.json"].each { |path|
|
40
|
+
Dir.glob(path) do |file|
|
41
|
+
template = File.open(file, 'rb')
|
42
|
+
filename = file.gsub("#{output_path}/", "")
|
43
|
+
begin
|
44
|
+
puts "INFO - Copying #{file} to s3://#{source_bucket}/#{validate_path}/#{filename}"
|
45
|
+
s3.put_object({
|
46
|
+
body: template,
|
47
|
+
bucket: "#{source_bucket}",
|
48
|
+
key: "#{validate_path}/#{filename}",
|
49
|
+
})
|
50
|
+
template_url = "https://#{source_bucket}.s3.amazonaws.com/#{validate_path}/#{filename}"
|
51
|
+
puts "INFO - Validating #{template_url}"
|
52
|
+
begin
|
53
|
+
resp = cfn.validate_template({ template_url: template_url })
|
54
|
+
puts "INFO - Template #{filename} validated successfully"
|
55
|
+
validated += 1
|
56
|
+
rescue => e
|
57
|
+
puts "ERROR - Template #{filename} failed to validate: #{e}"
|
58
|
+
unvalidated += 1
|
59
|
+
end
|
60
|
+
rescue => e
|
61
|
+
puts "ERROR - #{e.class}, #{e}"
|
62
|
+
exit 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
}
|
66
|
+
|
67
|
+
if unvalidated > 0
|
68
|
+
puts "ERROR - #{validated}/#{Dir["output/**/*.json"].count} templates validated successfully"
|
69
|
+
exit 1
|
70
|
+
else
|
71
|
+
puts "INFO - #{validated}/#{Dir["output/**/*.json"].count} templates validated successfully"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
data/lib/cfn_monitor/version.rb
CHANGED
data/lib/config/templates.yml
CHANGED
@@ -1,4 +1,16 @@
|
|
1
1
|
templates:
|
2
|
+
EcsCICheck:
|
3
|
+
ECSContianerInstancesDisconnected:
|
4
|
+
AlarmActions: crit
|
5
|
+
Namespace: EcsCICheck
|
6
|
+
ComparisonOperator: GreaterThanThreshold
|
7
|
+
DimensionsName: Cluster
|
8
|
+
Statistic: Maximum
|
9
|
+
Threshold: 0
|
10
|
+
Period: 60
|
11
|
+
EvaluationPeriods: 2
|
12
|
+
MetricName: ECSContianerInstancesDisconnected
|
13
|
+
TreatMissingData: notBreaching
|
2
14
|
HttpCheck:
|
3
15
|
EndpointAvailable:
|
4
16
|
AlarmActions: crit
|
@@ -66,6 +78,52 @@ templates:
|
|
66
78
|
# MetricName: Failed_SSM_Agent
|
67
79
|
# AlarmDescription: { 'Fn::Join': [ ' ', [ Ref: 'MonitoredStack', { 'Fn::Sub': ['${resource}', {'env': { Ref: 'EnvironmentName' } } ] }, '${alarmName}' ] ] }
|
68
80
|
# TreatMissingData: notBreaching
|
81
|
+
Ssl:
|
82
|
+
Crit:
|
83
|
+
AlarmActions: crit
|
84
|
+
Namespace: SSL
|
85
|
+
ComparisonOperator: LessThanThreshold
|
86
|
+
Dimensions: [ { Name: 'URL', Value: '${resource}' } ]
|
87
|
+
Statistic: Maximum
|
88
|
+
Threshold: 30 # 30 days
|
89
|
+
Period: 60
|
90
|
+
EvaluationPeriods: 1
|
91
|
+
MetricName: ExpiresInDays
|
92
|
+
AlarmDescription: { 'Fn::Join': [ ' ', [ '${resource}', '${templateName}' ] ] }
|
93
|
+
Warn:
|
94
|
+
AlarmActions: warn
|
95
|
+
Namespace: SSL
|
96
|
+
ComparisonOperator: LessThanThreshold
|
97
|
+
Dimensions: [ { Name: 'URL', Value: '${resource}' } ]
|
98
|
+
Statistic: Maximum
|
99
|
+
Threshold: 60 # 60 days
|
100
|
+
Period: 60
|
101
|
+
EvaluationPeriods: 1
|
102
|
+
MetricName: ExpiresInDays
|
103
|
+
AlarmDescription: { 'Fn::Join': [ ' ', [ '${resource}', '${templateName}' ] ] }
|
104
|
+
Dns:
|
105
|
+
Crit:
|
106
|
+
AlarmActions: crit
|
107
|
+
Namespace: DNS
|
108
|
+
ComparisonOperator: LessThanThreshold
|
109
|
+
Dimensions: [ { Name: 'Domain', Value: '${resource}' } ]
|
110
|
+
Statistic: Maximum
|
111
|
+
Threshold: 30 # 30 days
|
112
|
+
Period: 60
|
113
|
+
EvaluationPeriods: 1
|
114
|
+
MetricName: ExpiresInDays
|
115
|
+
AlarmDescription: { 'Fn::Join': [ ' ', [ '${resource}', '${templateName}' ] ] }
|
116
|
+
Warn:
|
117
|
+
AlarmActions: warn
|
118
|
+
Namespace: DNS
|
119
|
+
ComparisonOperator: LessThanThreshold
|
120
|
+
Dimensions: [ { Name: 'Domain', Value: '${resource}' } ]
|
121
|
+
Statistic: Maximum
|
122
|
+
Threshold: 60 # 60 days
|
123
|
+
Period: 60
|
124
|
+
EvaluationPeriods: 1
|
125
|
+
MetricName: ExpiresInDays
|
126
|
+
AlarmDescription: { 'Fn::Join': [ ' ', [ '${resource}', '${templateName}' ] ] }
|
69
127
|
Nrpe:
|
70
128
|
Warn:
|
71
129
|
AlarmActions: warn
|
@@ -399,14 +457,14 @@ templates:
|
|
399
457
|
EvaluationPeriods: 5
|
400
458
|
ApiGateway: #AWS:ApiGateway
|
401
459
|
ApiEndpoint5xx:
|
402
|
-
AlarmActions:
|
460
|
+
AlarmActions: warn
|
403
461
|
Namespace: AWS/ApiGateway
|
404
462
|
MetricName: 5XXError
|
405
463
|
ComparisonOperator: GreaterThanThreshold
|
406
464
|
DimensionsName: ApiName
|
407
465
|
Statistic: Sum
|
408
466
|
Threshold: 5
|
409
|
-
|
467
|
+
EvaluationPeriods: 2
|
410
468
|
DynamoDBTable: #AWS::DynamoDB::Table
|
411
469
|
DynamoDBReadUsage:
|
412
470
|
AlarmActions: warn
|
@@ -425,4 +483,4 @@ templates:
|
|
425
483
|
DimensionsName: TableName
|
426
484
|
Statistic: Sum
|
427
485
|
Threshold: 80
|
428
|
-
EvaluationPeriods: 2
|
486
|
+
EvaluationPeriods: 2
|
data/lib/templates/alarms.rb
CHANGED
@@ -53,7 +53,7 @@ CloudFormation do
|
|
53
53
|
template = alarm[:template]
|
54
54
|
name = alarm[:alarm]
|
55
55
|
params = alarm[:parameters]
|
56
|
-
cmd = params['cmd']
|
56
|
+
cmd = params['cmd'] || ''
|
57
57
|
|
58
58
|
alarmHash = Digest::MD5.hexdigest "#{resourceGroup}#{template}#{name}#{cmd}"
|
59
59
|
|
@@ -104,7 +104,7 @@ CloudFormation do
|
|
104
104
|
conditions = []
|
105
105
|
|
106
106
|
# Configure resource parameters
|
107
|
-
if
|
107
|
+
if ['resource','ecsCluster'].include? type
|
108
108
|
# Configure physical resource inputs
|
109
109
|
dimensionsNames = params['DimensionsName'].split('/')
|
110
110
|
dimensions = []
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'cfndsl'
|
2
|
+
|
3
|
+
CloudFormation do
|
4
|
+
Description("CloudWatch Endpoints")
|
5
|
+
|
6
|
+
Parameter("MonitoredStack"){
|
7
|
+
Type 'String'
|
8
|
+
}
|
9
|
+
Parameter("EnvironmentName"){
|
10
|
+
Type 'String'
|
11
|
+
}
|
12
|
+
Parameter("DnsCheckFunctionArn"){
|
13
|
+
Type 'String'
|
14
|
+
}
|
15
|
+
|
16
|
+
alarms.each do |alarm|
|
17
|
+
if alarm[:type] == 'dn'
|
18
|
+
endpointHash = Digest::MD5.hexdigest "dns-" + alarm[:resource]
|
19
|
+
|
20
|
+
# Conditionally create shedule based on environments attribute
|
21
|
+
if alarm[:environments] != ['all']
|
22
|
+
conditions = []
|
23
|
+
alarm[:environments].each do | env |
|
24
|
+
conditions << FnEquals(Ref("EnvironmentName"),env)
|
25
|
+
end
|
26
|
+
if conditions.length > 1
|
27
|
+
Condition("Condition#{endpointHash}", FnOr(conditions))
|
28
|
+
else
|
29
|
+
Condition("Condition#{endpointHash}", conditions[0])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
params = alarm[:parameters]
|
34
|
+
|
35
|
+
# Set defaults
|
36
|
+
params['scheduleExpression'] ||= "0 12 * * ? *" # 12PM every day
|
37
|
+
|
38
|
+
# Create payload
|
39
|
+
payload = {}
|
40
|
+
payload['Domain'] = alarm[:resource]
|
41
|
+
payload['Region'] = "${region}"
|
42
|
+
|
43
|
+
endpointHash = Digest::MD5.hexdigest alarm[:resource]
|
44
|
+
Resource("DnsCheckSchedule#{endpointHash}") do
|
45
|
+
Condition "Condition#{endpointHash}" if alarm[:environments] != ['all']
|
46
|
+
Type 'AWS::Events::Rule'
|
47
|
+
Property('Description', "#{payload['Domain']}")
|
48
|
+
Property('ScheduleExpression', "cron(#{params['scheduleExpression']})")
|
49
|
+
Property('State', 'ENABLED')
|
50
|
+
Property('Targets', [
|
51
|
+
{
|
52
|
+
Arn: Ref('DnsCheckFunctionArn'),
|
53
|
+
Id: endpointHash,
|
54
|
+
Input: FnSub(payload.to_json, region: Ref("AWS::Region"))
|
55
|
+
}
|
56
|
+
])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'cfndsl'
|
2
|
+
|
3
|
+
CloudFormation do
|
4
|
+
Description("CloudWatch ECS Container Instances")
|
5
|
+
|
6
|
+
Parameter("EcsCICheckFunctionArn"){
|
7
|
+
Type 'String'
|
8
|
+
}
|
9
|
+
|
10
|
+
Parameter("MonitoredStack"){
|
11
|
+
Type 'String'
|
12
|
+
}
|
13
|
+
Parameter("GetPhysicalIdFunctionArn"){
|
14
|
+
Type 'String'
|
15
|
+
}
|
16
|
+
Parameter("EnvironmentName"){
|
17
|
+
Type 'String'
|
18
|
+
}
|
19
|
+
Parameter("ConfigToggle"){
|
20
|
+
Type 'String'
|
21
|
+
}
|
22
|
+
|
23
|
+
alarms.each do |alarm|
|
24
|
+
if alarm[:type] == 'ecsCluster'
|
25
|
+
|
26
|
+
resourceHash = Digest::MD5.hexdigest alarm[:resource]
|
27
|
+
|
28
|
+
Resource("GetPhysicalId#{resourceHash}") do
|
29
|
+
Type 'Custom::GetResourcePhysicalId'
|
30
|
+
Property('ServiceToken', Ref('GetPhysicalIdFunctionArn'))
|
31
|
+
Property('StackName', Ref('MonitoredStack'))
|
32
|
+
if alarm[:resource].include? "::"
|
33
|
+
Property('LogicalResourceId', alarm[:resource].gsub('::','.') )
|
34
|
+
else
|
35
|
+
Property('LogicalResourceId', FnJoin( '.', [ Ref('MonitoredStack'), alarm[:resource] ] ))
|
36
|
+
end
|
37
|
+
Property('Region', Ref('AWS::Region'))
|
38
|
+
Property('ConfigToggle', Ref('ConfigToggle'))
|
39
|
+
end
|
40
|
+
|
41
|
+
# Conditionally create shedule based on environments attribute
|
42
|
+
if alarm[:environments] != ['all']
|
43
|
+
conditions = []
|
44
|
+
alarm[:environments].each do | env |
|
45
|
+
conditions << FnEquals(Ref("EnvironmentName"),env)
|
46
|
+
end
|
47
|
+
if conditions.length > 1
|
48
|
+
Condition("Condition#{resourceHash}", FnOr(conditions))
|
49
|
+
else
|
50
|
+
Condition("Condition#{resourceHash}", conditions[0])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Set defaults
|
55
|
+
params = alarm[:parameters]
|
56
|
+
params['scheduleExpression'] ||= "0/5 * * * ? *"
|
57
|
+
|
58
|
+
Events_Rule("EcsCICheckSchedule#{resourceHash}") {
|
59
|
+
Condition "Condition#{resourceHash}" if alarm[:environments] != ['all']
|
60
|
+
Description FnJoin("",["ECS container instance check for the ", FnGetAtt("GetPhysicalId#{resourceHash}",'PhysicalResourceId') ," ECS cluster"])
|
61
|
+
ScheduleExpression "cron(#{params['scheduleExpression']})"
|
62
|
+
State params['enabled'] ? 'ENABLED' : 'DISABLED'
|
63
|
+
Targets([
|
64
|
+
{
|
65
|
+
Arn: Ref('EcsCICheckFunctionArn'),
|
66
|
+
Id: resourceHash,
|
67
|
+
Input: FnSub({CLUSTER: '${cluster}'}.to_json, cluster: FnGetAtt("GetPhysicalId#{resourceHash}",'PhysicalResourceId'))
|
68
|
+
}
|
69
|
+
])
|
70
|
+
}
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
data/lib/templates/hosts.rb
CHANGED
@@ -73,8 +73,8 @@ CloudFormation do
|
|
73
73
|
Resource("NrpeCheckFunction#{hostHash}") do
|
74
74
|
Condition "Condition#{hostHash}" if alarm[:environments] != ['all']
|
75
75
|
Type 'AWS::Lambda::Function'
|
76
|
-
Property('Code', { S3Bucket: FnJoin('.',['base2.lambda',Ref('AWS::Region')]), S3Key: 'nrpe.zip' })
|
77
|
-
Property('Handler', '
|
76
|
+
Property('Code', { S3Bucket: FnJoin('.', ['base2.lambda', Ref('AWS::Region')]), S3Key: 'aws-lambda-nrpe-check/0.1/handler.zip' })
|
77
|
+
Property('Handler', 'main')
|
78
78
|
Property('MemorySize', 128)
|
79
79
|
Property('Runtime', 'go1.x')
|
80
80
|
Property('Timeout', 300)
|
data/lib/templates/master.rb
CHANGED
@@ -64,7 +64,41 @@ CloudFormation do
|
|
64
64
|
])
|
65
65
|
end
|
66
66
|
|
67
|
-
Resource("
|
67
|
+
Resource("EcsCICheckLambdaExecutionRole") do
|
68
|
+
Type 'AWS::IAM::Role'
|
69
|
+
Property('AssumeRolePolicyDocument', {
|
70
|
+
Version: '2012-10-17',
|
71
|
+
Statement: [{
|
72
|
+
Effect: 'Allow',
|
73
|
+
Principal: { Service: [ 'lambda.amazonaws.com' ] },
|
74
|
+
Action: [ 'sts:AssumeRole' ]
|
75
|
+
}]
|
76
|
+
})
|
77
|
+
Property('Path','/')
|
78
|
+
Property('Policies', [
|
79
|
+
PolicyName: 'CloudFormationReadOnly',
|
80
|
+
PolicyDocument: {
|
81
|
+
Version: '2012-10-17',
|
82
|
+
Statement: [{
|
83
|
+
Effect: 'Allow',
|
84
|
+
Action: [ 'logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents' ],
|
85
|
+
Resource: 'arn:aws:logs:*:*:*'
|
86
|
+
},
|
87
|
+
{
|
88
|
+
Effect: 'Allow',
|
89
|
+
Action: [ 'ecs:ListContainerInstances', 'ecs:DescribeContainerInstances' ],
|
90
|
+
Resource: '*'
|
91
|
+
},
|
92
|
+
{
|
93
|
+
Effect: 'Allow',
|
94
|
+
Action: [ 'cloudwatch:PutMetricData' ],
|
95
|
+
Resource: '*'
|
96
|
+
}]
|
97
|
+
}
|
98
|
+
])
|
99
|
+
end
|
100
|
+
|
101
|
+
Resource("LambdaExecutionRole") do
|
68
102
|
Type 'AWS::IAM::Role'
|
69
103
|
Property('AssumeRolePolicyDocument', {
|
70
104
|
Version: '2012-10-17',
|
@@ -123,12 +157,12 @@ CloudFormation do
|
|
123
157
|
|
124
158
|
Resource("HttpCheckFunction") do
|
125
159
|
Type 'AWS::Lambda::Function'
|
126
|
-
Property('Code', { S3Bucket: FnJoin('.',[Ref('AWS::Region'),'aws-lambda-http-check
|
127
|
-
Property('Handler', 'handler.
|
160
|
+
Property('Code', { S3Bucket: FnJoin('.', ['base2.lambda', Ref('AWS::Region')]), S3Key: 'aws-lambda-http-check/0.1/handler.zip' })
|
161
|
+
Property('Handler', 'handler.main')
|
128
162
|
Property('MemorySize', 128)
|
129
163
|
Property('Runtime', 'python3.6')
|
130
164
|
Property('Timeout', 300)
|
131
|
-
Property('Role', FnGetAtt('
|
165
|
+
Property('Role', FnGetAtt('LambdaExecutionRole','Arn'))
|
132
166
|
end
|
133
167
|
|
134
168
|
Resource("HttpCheckPermissions") do
|
@@ -138,6 +172,57 @@ CloudFormation do
|
|
138
172
|
Property('Principal', 'events.amazonaws.com')
|
139
173
|
end
|
140
174
|
|
175
|
+
Resource("SslCheckFunction") do
|
176
|
+
Type 'AWS::Lambda::Function'
|
177
|
+
Property('Code', { S3Bucket: FnJoin('.', ['base2.lambda', Ref('AWS::Region')]), S3Key: 'aws-lambda-ssl-check/0.1/handler.zip' })
|
178
|
+
Property('Handler', 'main')
|
179
|
+
Property('MemorySize', 128)
|
180
|
+
Property('Runtime', 'go1.x')
|
181
|
+
Property('Timeout', 300)
|
182
|
+
Property('Role', FnGetAtt('LambdaExecutionRole','Arn'))
|
183
|
+
end
|
184
|
+
|
185
|
+
Resource("SslCheckPermissions") do
|
186
|
+
Type 'AWS::Lambda::Permission'
|
187
|
+
Property('FunctionName', Ref('SslCheckFunction'))
|
188
|
+
Property('Action', 'lambda:InvokeFunction')
|
189
|
+
Property('Principal', 'events.amazonaws.com')
|
190
|
+
end
|
191
|
+
|
192
|
+
Resource("DnsCheckFunction") do
|
193
|
+
Type 'AWS::Lambda::Function'
|
194
|
+
Property('Code', { S3Bucket: FnJoin('.', ['base2.lambda', Ref('AWS::Region')]), S3Key: 'aws-lambda-dns-check/0.1/handler.zip' })
|
195
|
+
Property('Handler', 'main')
|
196
|
+
Property('MemorySize', 128)
|
197
|
+
Property('Runtime', 'go1.x')
|
198
|
+
Property('Timeout', 300)
|
199
|
+
Property('Role', FnGetAtt('LambdaExecutionRole','Arn'))
|
200
|
+
end
|
201
|
+
|
202
|
+
Resource("DnsCheckPermissions") do
|
203
|
+
Type 'AWS::Lambda::Permission'
|
204
|
+
Property('FunctionName', Ref('DnsCheckFunction'))
|
205
|
+
Property('Action', 'lambda:InvokeFunction')
|
206
|
+
Property('Principal', 'events.amazonaws.com')
|
207
|
+
end
|
208
|
+
|
209
|
+
Resource("EcsCICheckFunction") do
|
210
|
+
Type 'AWS::Lambda::Function'
|
211
|
+
Property('Code', { S3Bucket: FnJoin('.', ['base2.lambda', Ref('AWS::Region')]), S3Key: 'aws-lambda-ecs-container-instance-check/0.1/handler.zip' })
|
212
|
+
Property('Handler', 'handler.run_check')
|
213
|
+
Property('MemorySize', 128)
|
214
|
+
Property('Runtime', 'python3.6')
|
215
|
+
Property('Timeout', 300)
|
216
|
+
Property('Role', FnGetAtt('EcsCICheckLambdaExecutionRole','Arn'))
|
217
|
+
end
|
218
|
+
|
219
|
+
Resource("EcsCICheckPermissions") do
|
220
|
+
Type 'AWS::Lambda::Permission'
|
221
|
+
Property('FunctionName', Ref('EcsCICheckFunction'))
|
222
|
+
Property('Action', 'lambda:InvokeFunction')
|
223
|
+
Property('Principal', 'events.amazonaws.com')
|
224
|
+
end
|
225
|
+
|
141
226
|
params = {
|
142
227
|
MonitoredStack: Ref('MonitoredStack'),
|
143
228
|
SnsTopicCrit: Ref('SnsTopicCrit'),
|
@@ -175,6 +260,38 @@ CloudFormation do
|
|
175
260
|
end
|
176
261
|
end
|
177
262
|
|
263
|
+
sslParams = {
|
264
|
+
MonitoredStack: Ref('MonitoredStack'),
|
265
|
+
SslCheckFunctionArn: FnGetAtt('SslCheckFunction','Arn'),
|
266
|
+
EnvironmentName: FnGetAtt('GetEnvironmentName', 'EnvironmentName' )
|
267
|
+
}
|
268
|
+
|
269
|
+
ssl ||= {}
|
270
|
+
if !ssl.empty?
|
271
|
+
Resource("SslStack") do
|
272
|
+
Type 'AWS::CloudFormation::Stack'
|
273
|
+
Property('TemplateURL', "https://#{source_bucket}.s3.amazonaws.com/#{upload_path}/ssl.json")
|
274
|
+
Property('TimeoutInMinutes', 5)
|
275
|
+
Property('Parameters', sslParams)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
dnsParams = {
|
280
|
+
MonitoredStack: Ref('MonitoredStack'),
|
281
|
+
DnsCheckFunctionArn: FnGetAtt('DnsCheckFunction','Arn'),
|
282
|
+
EnvironmentName: FnGetAtt('GetEnvironmentName', 'EnvironmentName' )
|
283
|
+
}
|
284
|
+
|
285
|
+
dns ||= {}
|
286
|
+
if !dns.empty?
|
287
|
+
Resource("DnsStack") do
|
288
|
+
Type 'AWS::CloudFormation::Stack'
|
289
|
+
Property('TemplateURL', "https://#{source_bucket}.s3.amazonaws.com/#{upload_path}/dns.json")
|
290
|
+
Property('TimeoutInMinutes', 5)
|
291
|
+
Property('Parameters', dnsParams)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
178
295
|
hostParams = {
|
179
296
|
EnvironmentName: FnGetAtt('GetEnvironmentName', 'EnvironmentName' )
|
180
297
|
}
|
@@ -203,6 +320,24 @@ CloudFormation do
|
|
203
320
|
end
|
204
321
|
end
|
205
322
|
|
323
|
+
ecsClusterParams = {
|
324
|
+
MonitoredStack: Ref('MonitoredStack'),
|
325
|
+
GetPhysicalIdFunctionArn: FnGetAtt('GetPhysicalIdFunction','Arn'),
|
326
|
+
EnvironmentName: FnGetAtt('GetEnvironmentName', 'EnvironmentName' ),
|
327
|
+
ConfigToggle: Ref('ConfigToggle'),
|
328
|
+
EcsCICheckFunctionArn: FnGetAtt('EcsCICheckFunction','Arn')
|
329
|
+
}
|
330
|
+
|
331
|
+
ecsClusters ||= {}
|
332
|
+
if !ecsClusters.empty?
|
333
|
+
Resource("ServicesStack") do
|
334
|
+
Type 'AWS::CloudFormation::Stack'
|
335
|
+
Property('TemplateURL', "https://#{source_bucket}.s3.amazonaws.com/#{upload_path}/ecsClusters.json")
|
336
|
+
Property('TimeoutInMinutes', 5)
|
337
|
+
Property('Parameters', ecsClusterParams)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
206
341
|
Output("CfnMonitorVersion") { Value(CfnMonitor::VERSION) }
|
207
342
|
Output("RenderDate") { Value(Time.now.strftime("%Y-%m-%d")) }
|
208
343
|
Output("MonitoredStack") { Value(Ref("MonitoredStack")) }
|
data/lib/templates/resources.rb
CHANGED
@@ -46,13 +46,17 @@ CloudFormation do
|
|
46
46
|
Type 'Custom::GetResourcePhysicalId'
|
47
47
|
Property('ServiceToken', Ref('GetPhysicalIdFunctionArn'))
|
48
48
|
Property('StackName', Ref('MonitoredStack'))
|
49
|
-
|
49
|
+
if resource.include? "::"
|
50
|
+
Property('LogicalResourceId', resource.gsub('::','.') )
|
51
|
+
else
|
52
|
+
Property('LogicalResourceId', FnJoin( '.', [ Ref('MonitoredStack'), resource ] ))
|
53
|
+
end
|
50
54
|
Property('Region', Ref('AWS::Region'))
|
51
55
|
Property('ConfigToggle', Ref('ConfigToggle'))
|
52
56
|
end
|
53
57
|
customResources << "GetPhysicalId#{resourceHash}"
|
54
58
|
# Create outputs for user reference
|
55
|
-
Output("#{resource.delete('.')}") { Value(FnGetAtt("GetPhysicalId#{resourceHash}",'PhysicalResourceId')) }
|
59
|
+
Output("#{resource.delete('.').delete('-').delete('::')}") { Value(FnGetAtt("GetPhysicalId#{resourceHash}",'PhysicalResourceId')) }
|
56
60
|
end
|
57
61
|
end
|
58
62
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'cfndsl'
|
2
|
+
|
3
|
+
CloudFormation do
|
4
|
+
Description("CloudWatch Endpoints")
|
5
|
+
|
6
|
+
Parameter("MonitoredStack"){
|
7
|
+
Type 'String'
|
8
|
+
}
|
9
|
+
Parameter("EnvironmentName"){
|
10
|
+
Type 'String'
|
11
|
+
}
|
12
|
+
Parameter("SslCheckFunctionArn"){
|
13
|
+
Type 'String'
|
14
|
+
}
|
15
|
+
|
16
|
+
alarms.each do |alarm|
|
17
|
+
if alarm[:type] == 'ss'
|
18
|
+
endpointHash = Digest::MD5.hexdigest "ssl-" + alarm[:resource]
|
19
|
+
|
20
|
+
# Conditionally create shedule based on environments attribute
|
21
|
+
if alarm[:environments] != ['all']
|
22
|
+
conditions = []
|
23
|
+
alarm[:environments].each do | env |
|
24
|
+
conditions << FnEquals(Ref("EnvironmentName"),env)
|
25
|
+
end
|
26
|
+
if conditions.length > 1
|
27
|
+
Condition("Condition#{endpointHash}", FnOr(conditions))
|
28
|
+
else
|
29
|
+
Condition("Condition#{endpointHash}", conditions[0])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
params = alarm[:parameters]
|
34
|
+
|
35
|
+
# Set defaults
|
36
|
+
params['scheduleExpression'] ||= "0 12 * * ? *" # 12PM every day
|
37
|
+
|
38
|
+
# Create payload
|
39
|
+
payload = {}
|
40
|
+
payload['Url'] = alarm[:resource]
|
41
|
+
payload['Region'] = "${region}"
|
42
|
+
|
43
|
+
endpointHash = Digest::MD5.hexdigest alarm[:resource]
|
44
|
+
Resource("SslCheckSchedule#{endpointHash}") do
|
45
|
+
Condition "Condition#{endpointHash}" if alarm[:environments] != ['all']
|
46
|
+
Type 'AWS::Events::Rule'
|
47
|
+
Property('Description', "#{payload['Url']}")
|
48
|
+
Property('ScheduleExpression', "cron(#{params['scheduleExpression']})")
|
49
|
+
Property('State', 'ENABLED')
|
50
|
+
Property('Targets', [
|
51
|
+
{
|
52
|
+
Arn: Ref('SslCheckFunctionArn'),
|
53
|
+
Id: endpointHash,
|
54
|
+
Input: FnSub(payload.to_json, region: Ref("AWS::Region"))
|
55
|
+
}
|
56
|
+
])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cfn_monitor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Base2Services
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2019-01-21 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: thor
|
@@ -211,6 +211,7 @@ files:
|
|
211
211
|
- lib/cfn_monitor/generate.rb
|
212
212
|
- lib/cfn_monitor/query.rb
|
213
213
|
- lib/cfn_monitor/utils.rb
|
214
|
+
- lib/cfn_monitor/validate.rb
|
214
215
|
- lib/cfn_monitor/version.rb
|
215
216
|
- lib/config/config.yml
|
216
217
|
- lib/config/templates.yml
|
@@ -218,11 +219,14 @@ files:
|
|
218
219
|
- lib/lambda/getEnvironmentName.py
|
219
220
|
- lib/lambda/getPhysicalId.py
|
220
221
|
- lib/templates/alarms.rb
|
222
|
+
- lib/templates/dns.rb
|
223
|
+
- lib/templates/ecsClusters.rb
|
221
224
|
- lib/templates/endpoints.rb
|
222
225
|
- lib/templates/hosts.rb
|
223
226
|
- lib/templates/master.rb
|
224
227
|
- lib/templates/resources.rb
|
225
228
|
- lib/templates/services.rb
|
229
|
+
- lib/templates/ssl.rb
|
226
230
|
homepage: https://github.com/base2Services/cfn-monitor/blob/master/README.md
|
227
231
|
licenses:
|
228
232
|
- MIT
|
@@ -242,8 +246,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
242
246
|
- !ruby/object:Gem::Version
|
243
247
|
version: '0'
|
244
248
|
requirements: []
|
245
|
-
|
246
|
-
rubygems_version: 2.7.7
|
249
|
+
rubygems_version: 3.0.2
|
247
250
|
signing_key:
|
248
251
|
specification_version: 4
|
249
252
|
summary: Configure and generate a cloudwatch monitoring cloudformation stack
|