cfn-guardian 0.1.0 → 0.3.3
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/.dockerignore +1 -0
- data/Dockerfile +19 -0
- data/Gemfile.lock +31 -13
- data/README.md +441 -42
- data/cfn-guardian.gemspec +6 -2
- data/lib/cfnguardian.rb +301 -27
- data/lib/cfnguardian/cloudwatch.rb +121 -0
- data/lib/cfnguardian/codecommit.rb +54 -0
- data/lib/cfnguardian/codepipeline.rb +138 -0
- data/lib/cfnguardian/compile.rb +58 -17
- data/lib/cfnguardian/config/defaults.yaml +94 -0
- data/lib/cfnguardian/display_formatter.rb +164 -0
- data/lib/cfnguardian/drift.rb +79 -0
- data/lib/cfnguardian/log.rb +0 -1
- data/lib/cfnguardian/models/alarm.rb +98 -36
- data/lib/cfnguardian/models/check.rb +103 -26
- data/lib/cfnguardian/models/composite.rb +21 -0
- data/lib/cfnguardian/models/event.rb +164 -40
- data/lib/cfnguardian/models/metric_filter.rb +28 -0
- data/lib/cfnguardian/resources/application_targetgroup.rb +2 -0
- data/lib/cfnguardian/resources/base.rb +38 -16
- data/lib/cfnguardian/resources/ecs_service.rb +2 -2
- data/lib/cfnguardian/resources/http.rb +16 -1
- data/lib/cfnguardian/resources/internal_http.rb +74 -0
- data/lib/cfnguardian/resources/internal_port.rb +33 -0
- data/lib/cfnguardian/resources/internal_sftp.rb +58 -0
- data/lib/cfnguardian/resources/log_group.rb +26 -0
- data/lib/cfnguardian/resources/network_targetgroup.rb +1 -0
- data/lib/cfnguardian/resources/port.rb +25 -0
- data/lib/cfnguardian/resources/rds_instance.rb +2 -0
- data/lib/cfnguardian/resources/sftp.rb +50 -0
- data/lib/cfnguardian/resources/sql.rb +1 -1
- data/lib/cfnguardian/resources/tls.rb +66 -0
- data/lib/cfnguardian/s3.rb +3 -2
- data/lib/cfnguardian/stacks/main.rb +86 -65
- data/lib/cfnguardian/stacks/resources.rb +81 -42
- data/lib/cfnguardian/string.rb +12 -0
- data/lib/cfnguardian/version.rb +1 -1
- metadata +102 -5
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'aws-sdk-codecommit'
|
2
|
+
require 'time'
|
3
|
+
require 'cfnguardian/log'
|
4
|
+
|
5
|
+
module CfnGuardian
|
6
|
+
class CodeCommit
|
7
|
+
include Logging
|
8
|
+
|
9
|
+
def initialize(repo_name)
|
10
|
+
@repo_name = repo_name
|
11
|
+
@client = Aws::CodeCommit::Client.new()
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_last_commit(branch='master')
|
15
|
+
resp = @client.get_branch({
|
16
|
+
repository_name: @repo_name,
|
17
|
+
branch_name: branch,
|
18
|
+
})
|
19
|
+
return resp.branch.commit_id
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_commit_history(branch='master',count=10)
|
23
|
+
history = []
|
24
|
+
commit = get_last_commit(branch)
|
25
|
+
|
26
|
+
count.times do
|
27
|
+
|
28
|
+
resp = @client.get_commit({
|
29
|
+
repository_name: @repo_name,
|
30
|
+
commit_id: commit
|
31
|
+
})
|
32
|
+
|
33
|
+
time = Time.strptime(resp.commit.committer.date,'%s')
|
34
|
+
|
35
|
+
history << {
|
36
|
+
message: resp.commit.message,
|
37
|
+
author: resp.commit.author.name,
|
38
|
+
date: time.localtime.strftime("%d/%m/%Y %I:%M %p"),
|
39
|
+
id: resp.commit.commit_id
|
40
|
+
}
|
41
|
+
|
42
|
+
if resp.commit.parents.any?
|
43
|
+
commit = resp.commit.parents.first
|
44
|
+
else
|
45
|
+
break
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
return history
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'aws-sdk-codepipeline'
|
2
|
+
require 'time'
|
3
|
+
require 'cfnguardian/log'
|
4
|
+
|
5
|
+
module CfnGuardian
|
6
|
+
class CodePipeline
|
7
|
+
include Logging
|
8
|
+
|
9
|
+
def initialize(pipeline_name)
|
10
|
+
@pipeline_name = pipeline_name
|
11
|
+
client = Aws::CodePipeline::Client.new()
|
12
|
+
@pipeline = client.get_pipeline_state({
|
13
|
+
name: @pipeline_name
|
14
|
+
})
|
15
|
+
end
|
16
|
+
|
17
|
+
def retry()
|
18
|
+
resp = client.start_pipeline_execution({
|
19
|
+
name: @pipeline_name,
|
20
|
+
client_request_token: "ClientRequestToken",
|
21
|
+
})
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_stage(stage_name)
|
25
|
+
return @pipeline.stage_states.find {|stage| stage.stage_name == stage_name}
|
26
|
+
end
|
27
|
+
|
28
|
+
def colour_rows(rows, status)
|
29
|
+
if status == 'Failed'
|
30
|
+
rows.map! {|row| row.map! {|r| r.red } }
|
31
|
+
elsif status == 'Succeeded'
|
32
|
+
rows.map! {|row| row.map! {|r| r.green } }
|
33
|
+
elsif status == 'InProgress'
|
34
|
+
rows.map! {|row| row.map! {|r| r.blue } }
|
35
|
+
elsif ["Stopped", "Stopping"].include? status
|
36
|
+
rows.map! {|row| row.map! {|r| r.yellow } }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_source()
|
41
|
+
source_stage = get_stage("Source")
|
42
|
+
action = source_stage.action_states.first
|
43
|
+
status = source_stage.latest_execution.status
|
44
|
+
state = {
|
45
|
+
stage: action.action_name,
|
46
|
+
rows: [
|
47
|
+
['Status', status],
|
48
|
+
['Commit', action.current_revision.revision_id],
|
49
|
+
['Last Status Change', action.latest_execution.last_status_change.localtime.strftime("%d/%m/%Y %I:%M %p")]
|
50
|
+
]
|
51
|
+
}
|
52
|
+
|
53
|
+
unless action.latest_execution.error_details.nil?
|
54
|
+
state[:rows].push(
|
55
|
+
['Error Message', action.latest_execution.error_details.message]
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
colour_rows(state[:rows],status)
|
60
|
+
|
61
|
+
return state
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_build()
|
65
|
+
source_stage = get_stage("Build")
|
66
|
+
action = source_stage.action_states.first
|
67
|
+
status = source_stage.latest_execution.status
|
68
|
+
state = {
|
69
|
+
stage: action.action_name,
|
70
|
+
rows: [
|
71
|
+
['Status', status],
|
72
|
+
['Build Id', action.latest_execution.external_execution_id],
|
73
|
+
['Last Status Change', action.latest_execution.last_status_change.localtime.strftime("%d/%m/%Y %I:%M %p")],
|
74
|
+
['Logs', action.latest_execution.external_execution_url]
|
75
|
+
]
|
76
|
+
}
|
77
|
+
|
78
|
+
unless action.latest_execution.error_details.nil?
|
79
|
+
state[:rows].push(
|
80
|
+
['Error Message', action.latest_execution.error_details.message]
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
colour_rows(state[:rows],status)
|
85
|
+
|
86
|
+
return state
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_create_changeset()
|
90
|
+
source_stage = get_stage("Deploy")
|
91
|
+
action = source_stage.action_states.find {|action| action.action_name == "CreateChangeSet"}
|
92
|
+
status = source_stage.latest_execution.status
|
93
|
+
state = {
|
94
|
+
stage: action.action_name,
|
95
|
+
rows: [
|
96
|
+
['Status', status],
|
97
|
+
['Summary', action.latest_execution.summary],
|
98
|
+
['Last Status Change', action.latest_execution.last_status_change.localtime.strftime("%d/%m/%Y %I:%M %p")],
|
99
|
+
]
|
100
|
+
}
|
101
|
+
|
102
|
+
unless action.latest_execution.error_details.nil?
|
103
|
+
state[:rows].push(
|
104
|
+
['Error Message', action.latest_execution.error_details.message]
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
colour_rows(state[:rows],status)
|
109
|
+
|
110
|
+
return state
|
111
|
+
end
|
112
|
+
|
113
|
+
def get_deploy_changeset()
|
114
|
+
source_stage = get_stage("Deploy")
|
115
|
+
action = source_stage.action_states.find {|action| action.action_name == "DeployChangeSet"}
|
116
|
+
status = source_stage.latest_execution.status
|
117
|
+
state = {
|
118
|
+
stage: action.action_name,
|
119
|
+
rows: [
|
120
|
+
['Status', status],
|
121
|
+
['Summary', action.latest_execution.summary],
|
122
|
+
['Last Status Change', action.latest_execution.last_status_change.localtime.strftime("%d/%m/%Y %I:%M %p")],
|
123
|
+
]
|
124
|
+
}
|
125
|
+
|
126
|
+
unless action.latest_execution.error_details.nil?
|
127
|
+
state[:rows].push(
|
128
|
+
['Error Message', action.latest_execution.error_details.message]
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
colour_rows(state[:rows],status)
|
133
|
+
|
134
|
+
return state
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
data/lib/cfnguardian/compile.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'yaml'
|
2
|
+
require 'fileutils'
|
2
3
|
require 'cfnguardian/string'
|
3
4
|
require 'cfnguardian/stacks/resources'
|
4
5
|
require 'cfnguardian/stacks/main'
|
6
|
+
require 'cfnguardian/models/composite'
|
5
7
|
require 'cfnguardian/resources/base'
|
6
8
|
require 'cfnguardian/resources/apigateway'
|
7
9
|
require 'cfnguardian/resources/application_targetgroup'
|
@@ -18,6 +20,9 @@ require 'cfnguardian/resources/elastic_file_system'
|
|
18
20
|
require 'cfnguardian/resources/elasticache_replication_group'
|
19
21
|
require 'cfnguardian/resources/elastic_loadbalancer'
|
20
22
|
require 'cfnguardian/resources/http'
|
23
|
+
require 'cfnguardian/resources/internal_http'
|
24
|
+
require 'cfnguardian/resources/port'
|
25
|
+
require 'cfnguardian/resources/internal_port'
|
21
26
|
require 'cfnguardian/resources/nrpe'
|
22
27
|
require 'cfnguardian/resources/lambda'
|
23
28
|
require 'cfnguardian/resources/network_targetgroup'
|
@@ -26,24 +31,31 @@ require 'cfnguardian/resources/rds_instance'
|
|
26
31
|
require 'cfnguardian/resources/redshift_cluster'
|
27
32
|
require 'cfnguardian/resources/sql'
|
28
33
|
require 'cfnguardian/resources/sqs_queue'
|
34
|
+
require 'cfnguardian/resources/log_group'
|
35
|
+
require 'cfnguardian/resources/sftp'
|
36
|
+
require 'cfnguardian/resources/internal_sftp'
|
37
|
+
require 'cfnguardian/resources/tls'
|
29
38
|
|
30
39
|
module CfnGuardian
|
31
40
|
class Compile
|
32
41
|
include Logging
|
33
42
|
|
34
|
-
attr_reader :cost, :resources
|
43
|
+
attr_reader :cost, :resources, :topics
|
35
44
|
|
36
|
-
def initialize(
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
config = YAML.load_file(opts.fetch(:config))
|
45
|
+
def initialize(config_file)
|
46
|
+
config = YAML.load_file(config_file)
|
47
|
+
|
41
48
|
@resource_groups = config.fetch('Resources',{})
|
49
|
+
@composites = config.fetch('Composites',{})
|
42
50
|
@templates = config.fetch('Templates',{})
|
51
|
+
@topics = config.fetch('Topics',{})
|
52
|
+
@maintenance_groups = config.fetch('MaintenaceGroups', {})
|
43
53
|
|
54
|
+
@maintenance_group_list = @maintenance_groups.keys.map {|group| "#{group}MaintenanceGroup"}
|
44
55
|
@resources = []
|
45
56
|
@stacks = []
|
46
57
|
@checks = []
|
58
|
+
@ssm_parameters = []
|
47
59
|
|
48
60
|
@cost = 0
|
49
61
|
end
|
@@ -57,7 +69,7 @@ module CfnGuardian
|
|
57
69
|
rescue NameError => e
|
58
70
|
if @templates.has_key?(group) && @templates[group].has_key?('Inherit')
|
59
71
|
begin
|
60
|
-
resource_class = Kernel.const_get("CfnGuardian::Resource::#{@templates[group]['Inherit']}").new(resource)
|
72
|
+
resource_class = Kernel.const_get("CfnGuardian::Resource::#{@templates[group]['Inherit']}").new(resource, group)
|
61
73
|
logger.debug "Inheritited resource group #{@templates[group]['Inherit']} for group #{group}"
|
62
74
|
rescue NameError => e
|
63
75
|
logger.warn "'#{@templates[group]['Inherit']}' resource group doesn't exist and is unable to be inherited from"
|
@@ -70,40 +82,69 @@ module CfnGuardian
|
|
70
82
|
end
|
71
83
|
|
72
84
|
overides = @templates.has_key?(group) ? @templates[group] : {}
|
73
|
-
@resources.concat resource_class.get_alarms(overides)
|
85
|
+
@resources.concat resource_class.get_alarms(overides,resource)
|
86
|
+
@resources.concat resource_class.get_metric_filters()
|
74
87
|
@resources.concat resource_class.get_events()
|
75
88
|
@checks.concat resource_class.get_checks()
|
76
89
|
|
77
90
|
@cost += resource_class.get_cost
|
78
91
|
end
|
79
92
|
end
|
93
|
+
|
94
|
+
@maintenance_groups.each do |maintenance_group,resource_groups|
|
95
|
+
resource_groups.each do |group, alarms|
|
96
|
+
alarms.each do |alarm, resources|
|
97
|
+
resources.each do |resource|
|
98
|
+
res = @resources.find {|r|
|
99
|
+
(r.type == 'Alarm') &&
|
100
|
+
(r.class == group && r.name == alarm) &&
|
101
|
+
(r.resource_id == resource['Id'] || r.resource_name == resource['Name'])}
|
102
|
+
unless res.nil?
|
103
|
+
res.maintenance_groups.append("#{maintenance_group}MaintenanceGroup")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
@composites.each do |name,params|
|
111
|
+
@resources.push CfnGuardian::Models::Composite.new(name,params)
|
112
|
+
@cost += 0.50
|
113
|
+
end
|
114
|
+
|
115
|
+
@ssm_parameters = @resources.select {|resource| resource.type == 'Event'}.map {|event| event.ssm_parameters}.flatten.uniq
|
116
|
+
end
|
117
|
+
|
118
|
+
def alarms
|
119
|
+
@resources.select {|resource| resource.type == 'Alarm'}
|
80
120
|
end
|
81
121
|
|
82
|
-
def split_resources
|
122
|
+
def split_resources(bucket,path)
|
83
123
|
split = @resources.each_slice(200).to_a
|
84
124
|
split.each_with_index do |resources,index|
|
85
125
|
@stacks.push({
|
86
126
|
'Name' => "GuardianStack#{index}",
|
87
|
-
'TemplateURL' => "https://#{
|
127
|
+
'TemplateURL' => "https://#{bucket}.s3.amazonaws.com/#{path}/guardian-stack-#{index}.compiled.yaml",
|
88
128
|
'Reference' => index
|
89
129
|
})
|
90
130
|
end
|
91
131
|
return split
|
92
132
|
end
|
93
133
|
|
94
|
-
def compile_templates
|
134
|
+
def compile_templates(bucket,path)
|
95
135
|
clean_out_directory()
|
96
|
-
resources = split_resources()
|
136
|
+
resources = split_resources(bucket,path)
|
97
137
|
|
98
138
|
main_stack = CfnGuardian::Stacks::Main.new()
|
99
|
-
|
100
|
-
valid = template.validate
|
139
|
+
main_stack.build_template(@stacks,@checks,@topics,@maintenance_group_list,@ssm_parameters)
|
140
|
+
valid = main_stack.template.validate
|
141
|
+
FileUtils.mkdir_p 'out'
|
101
142
|
File.write("out/guardian.compiled.yaml", JSON.parse(valid.to_json).to_yaml)
|
102
143
|
|
103
144
|
resources.each_with_index do |resources,index|
|
104
|
-
stack = CfnGuardian::Stacks::Resources.new()
|
105
|
-
|
106
|
-
valid = template.validate
|
145
|
+
stack = CfnGuardian::Stacks::Resources.new(main_stack.parameters)
|
146
|
+
stack.build_template(resources)
|
147
|
+
valid = stack.template.validate
|
107
148
|
File.write("out/guardian-stack-#{index}.compiled.yaml", JSON.parse(valid.to_json).to_yaml)
|
108
149
|
end
|
109
150
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
Resources:
|
2
|
+
AmazonMQBroker:
|
3
|
+
- Id: Default
|
4
|
+
ApiGateway:
|
5
|
+
- Id: Default
|
6
|
+
ApplicationTargetGroup:
|
7
|
+
- Id: Default
|
8
|
+
LoadBalancer: Default
|
9
|
+
AutoScalingGroup:
|
10
|
+
- Id: Default
|
11
|
+
CloudFrontDistribution:
|
12
|
+
- Id: Default
|
13
|
+
DomainExpiry:
|
14
|
+
- Id: Default
|
15
|
+
DynamoDBTable:
|
16
|
+
- Id: Default
|
17
|
+
Ec2Instance:
|
18
|
+
- Id: Default
|
19
|
+
ECSCluster:
|
20
|
+
- Id: Default
|
21
|
+
ECSService:
|
22
|
+
- Id: Default
|
23
|
+
Cluster: Default
|
24
|
+
ElasticFileSystem:
|
25
|
+
- Id: Default
|
26
|
+
ElasticLoadBalancer:
|
27
|
+
- Id: Default
|
28
|
+
ElastiCacheReplicationGroup:
|
29
|
+
- Id: Default
|
30
|
+
Http:
|
31
|
+
- Id: Default
|
32
|
+
StatusCode: 200
|
33
|
+
Ssl: true
|
34
|
+
BodyRegex: Default
|
35
|
+
InternalHttp:
|
36
|
+
- Environment: Default
|
37
|
+
VpcId: vpc-default
|
38
|
+
Subnets:
|
39
|
+
- subnet-default
|
40
|
+
Hosts:
|
41
|
+
- Id: Default
|
42
|
+
StatusCode: 200
|
43
|
+
BodyRegex: Default
|
44
|
+
Port:
|
45
|
+
- Id: Default
|
46
|
+
Port: 0
|
47
|
+
InternalPort:
|
48
|
+
- Environment: Default
|
49
|
+
VpcId: vpc-default
|
50
|
+
Subnets:
|
51
|
+
- subnet-default
|
52
|
+
Hosts:
|
53
|
+
- Id: Default
|
54
|
+
Port: 0
|
55
|
+
Lambda:
|
56
|
+
- Id: Default
|
57
|
+
LogGroup:
|
58
|
+
- Id: Default
|
59
|
+
MetricFilters:
|
60
|
+
- MetricName: Default
|
61
|
+
Pattern: Default
|
62
|
+
NetworkTargetGroup:
|
63
|
+
- Id: Default
|
64
|
+
LoadBalancer: Default
|
65
|
+
Nrpe:
|
66
|
+
- Environment: Default
|
67
|
+
VpcId: vpc-default
|
68
|
+
Subnets:
|
69
|
+
- subnet-default
|
70
|
+
Hosts:
|
71
|
+
- Id: Default
|
72
|
+
Commands:
|
73
|
+
- default
|
74
|
+
RDSClusterInstance:
|
75
|
+
- Id: Default
|
76
|
+
RDSInstance:
|
77
|
+
- Id: Default
|
78
|
+
RedshiftCluster:
|
79
|
+
- Id: Default
|
80
|
+
Sql:
|
81
|
+
- Environment: Default
|
82
|
+
VpcId: vpc-default
|
83
|
+
Subnets:
|
84
|
+
- subnet-default
|
85
|
+
Hosts:
|
86
|
+
- Id: Default
|
87
|
+
SecretId: Default
|
88
|
+
Engine: default
|
89
|
+
Queries:
|
90
|
+
- MetricName: Default
|
91
|
+
Query: Default
|
92
|
+
SQSQueue:
|
93
|
+
- Id: Default
|
94
|
+
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'cfnguardian/cloudwatch'
|
2
|
+
require 'cfnguardian/string'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module CfnGuardian
|
6
|
+
class DisplayFormatter
|
7
|
+
|
8
|
+
def initialize(alarms=[])
|
9
|
+
@alarms = alarms
|
10
|
+
end
|
11
|
+
|
12
|
+
def alarms()
|
13
|
+
resp = []
|
14
|
+
|
15
|
+
@alarms.each do |alarm|
|
16
|
+
alarm_name = CfnGuardian::CloudWatch.get_alarm_name(alarm)
|
17
|
+
puts alarm_name
|
18
|
+
rows = [
|
19
|
+
['ResourceId', alarm.resource_id],
|
20
|
+
['ResourceHash', alarm.resource_hash],
|
21
|
+
['ResourceName', alarm.resource_name],
|
22
|
+
['Enabled', alarm.enabled],
|
23
|
+
['MetricName', alarm.metric_name],
|
24
|
+
['Dimensions', alarm.dimensions],
|
25
|
+
['Threshold', alarm.threshold],
|
26
|
+
['Period', alarm.period],
|
27
|
+
['EvaluationPeriods', alarm.evaluation_periods],
|
28
|
+
['ComparisonOperator', alarm.comparison_operator],
|
29
|
+
['Statistic', alarm.statistic],
|
30
|
+
['ActionsEnabled', alarm.actions_enabled],
|
31
|
+
['DatapointsToAlarm', alarm.datapoints_to_alarm],
|
32
|
+
['ExtendedStatistic', alarm.extended_statistic],
|
33
|
+
['EvaluateLowSampleCountPercentile', alarm.evaluate_low_sample_count_percentile],
|
34
|
+
['Unit', alarm.unit],
|
35
|
+
['AlarmAction', alarm.alarm_action],
|
36
|
+
['TreatMissingData', alarm.treat_missing_data]
|
37
|
+
]
|
38
|
+
|
39
|
+
rows.select! {|row| !row[1].nil?}
|
40
|
+
|
41
|
+
resp << {
|
42
|
+
title: "#{alarm.group}::#{alarm.name}".green + "\n" + alarm_name.green,
|
43
|
+
rows: rows
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
return resp
|
48
|
+
end
|
49
|
+
|
50
|
+
def compare_alarms(metric_alarms)
|
51
|
+
resp = []
|
52
|
+
|
53
|
+
@alarms.each do |alarm|
|
54
|
+
alarm_name = CfnGuardian::CloudWatch.get_alarm_name(alarm)
|
55
|
+
metric_alarm = metric_alarms.find {|ma| ma.alarm_name == alarm_name}
|
56
|
+
dimensions = metric_alarm.dimensions.map {|dim| {dim.name.to_sym => dim.value}}.inject(:merge)
|
57
|
+
|
58
|
+
rows = [
|
59
|
+
['ResourceId', alarm.resource_id, alarm.resource_id],
|
60
|
+
['ResourceHash', alarm.resource_hash, alarm.resource_hash],
|
61
|
+
['ResourceName', alarm.resource_name, alarm.resource_name],
|
62
|
+
['Enabled', alarm.enabled, true],
|
63
|
+
['MetricName', alarm.metric_name, metric_alarm.metric_name],
|
64
|
+
['Dimensions', alarm.dimensions, dimensions],
|
65
|
+
['Threshold', alarm.threshold.to_f, metric_alarm.threshold],
|
66
|
+
['Period', alarm.period, metric_alarm.period],
|
67
|
+
['EvaluationPeriods', alarm.evaluation_periods, metric_alarm.evaluation_periods],
|
68
|
+
['ComparisonOperator', alarm.comparison_operator, metric_alarm.comparison_operator],
|
69
|
+
['Statistic', alarm.statistic, metric_alarm.statistic],
|
70
|
+
['ActionsEnabled', alarm.actions_enabled, metric_alarm.actions_enabled],
|
71
|
+
['DatapointsToAlarm', alarm.datapoints_to_alarm, metric_alarm.datapoints_to_alarm],
|
72
|
+
['ExtendedStatistic', alarm.extended_statistic, metric_alarm.extended_statistic],
|
73
|
+
['EvaluateLowSampleCountPercentile', alarm.evaluate_low_sample_count_percentile, metric_alarm.evaluate_low_sample_count_percentile],
|
74
|
+
['Unit', alarm.unit, metric_alarm.unit],
|
75
|
+
['TreatMissingData', alarm.treat_missing_data, metric_alarm.treat_missing_data],
|
76
|
+
['AlarmAction', alarm.alarm_action, alarm.alarm_action]
|
77
|
+
]
|
78
|
+
|
79
|
+
rows.select! {|row| !row[1].nil?}.each {|row| colour_compare_row(row)}
|
80
|
+
|
81
|
+
if has_config_difference?(rows)
|
82
|
+
resp << {
|
83
|
+
title: "#{alarm.group}::#{alarm.name}".green + "\n" + alarm_name.green,
|
84
|
+
rows: rows
|
85
|
+
}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
return resp
|
90
|
+
end
|
91
|
+
|
92
|
+
def alarm_state(metric_alarms)
|
93
|
+
rows = []
|
94
|
+
|
95
|
+
metric_alarms.each do |ma|
|
96
|
+
if ma.state_value == 'ALARM'
|
97
|
+
state_value = ma.state_value.to_s.red
|
98
|
+
elsif ma.state_value == 'INSUFFICIENT_DATA'
|
99
|
+
state_value = ma.state_value.to_s.yellow
|
100
|
+
else
|
101
|
+
state_value = ma.state_value.to_s.green
|
102
|
+
end
|
103
|
+
|
104
|
+
rows << [
|
105
|
+
ma.alarm_name,
|
106
|
+
state_value,
|
107
|
+
ma.state_updated_timestamp.localtime,
|
108
|
+
ma.actions_enabled ? 'ENABLED'.green : 'DISABLED'.red
|
109
|
+
]
|
110
|
+
end
|
111
|
+
# sort by state_value
|
112
|
+
return rows.sort_by {|r| r[3]}
|
113
|
+
end
|
114
|
+
|
115
|
+
def alarm_history(history,type)
|
116
|
+
rows = []
|
117
|
+
line_width = 100
|
118
|
+
|
119
|
+
history.each do |item|
|
120
|
+
data = JSON.load(item.history_data)
|
121
|
+
|
122
|
+
case type
|
123
|
+
when "StateUpdate"
|
124
|
+
rows << [
|
125
|
+
item.timestamp.localtime,
|
126
|
+
item.history_summary,
|
127
|
+
data['newState']['stateReason'].word_wrap
|
128
|
+
]
|
129
|
+
when "ConfigurationUpdate"
|
130
|
+
updated = []
|
131
|
+
if data['type'] == 'Update'
|
132
|
+
data['originalUpdatedFields'].each do |k,v|
|
133
|
+
unless k == 'alarmConfigurationUpdatedTimestamp'
|
134
|
+
updated << "#{k}: #{v} -> #{data['updatedAlarm'][k]}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
rows << [
|
139
|
+
item.timestamp.localtime,
|
140
|
+
data['type'],
|
141
|
+
updated.join("\n").word_wrap
|
142
|
+
]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
return rows
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def has_config_difference?(rows)
|
152
|
+
rows.each do |row|
|
153
|
+
unless row[1].eql?(row[2])
|
154
|
+
return true
|
155
|
+
end
|
156
|
+
end
|
157
|
+
return false
|
158
|
+
end
|
159
|
+
|
160
|
+
def colour_compare_row(row)
|
161
|
+
return row[1].eql?(row[2]) ? row.map! {|r| r.to_s.green} : row.map! {|r| r.to_s.red}
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|