cfn_monitor 0.1.1 → 0.2.0
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 +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
|