Skip to content

Commit

Permalink
Merge pull request #65 from base2Services/feature/alarm-tags
Browse files Browse the repository at this point in the history
feature/alarm tags
  • Loading branch information
Guslington authored May 26, 2022
2 parents e448e19 + fb428a8 commit eefc62b
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 6 deletions.
63 changes: 63 additions & 0 deletions docs/alarm_tags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Guardian Alarm Tags

AWS tags can be applied to Cloudwatch alarms created by guardian. This is available as a separate guardian command [`cfn-guardian tag-alarms`] because Cloudformation doesn't support creating tags on Cloudwatch alarms.

## Default Tags

Guardian will add the following default tags to each alarm

```
guardian:resource:id
guardian:resource:group
guardian:alarm:name
guardian:alarm:metric
guardian:alarm:severity
```

## Adding Tags

Additional tags can added through the alarms yaml configuration file. They can be applied globally to all alarms, to all alarms in a resource group or a specific alarm.

### Global Tags

Global tags are applied to every alarm created by guardian. Add the `GlobalTags` key at the top level of the alarms yaml config with key:value pairs.

```yml
GlobalTags:
key: value
env: production
```
### Resource Group Tags
Resource group tags are applied to every alarm in a guardian resource group using the `Templates` section to add the tags.

```yaml
Templates:
Ec2Instance:
GroupOverrides:
Tags:
key: value
env: production
```

### Specific Alarm Tags

To add tags to a specific guardian alarm you can apply the tags in the `Templates` section of the alarms yaml config.

```yaml
Templates:
Ec2Instance:
CPUUtilizationHigh:
Tags:
key: value
alarm-action: restart ec2 instance
```

## Applying tags

To apply the tags run the `tag-alarms` command passing the alarms yaml config.

```sh
cfn-guardian tag-alarms --config alarms.yaml
```
3 changes: 2 additions & 1 deletion docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@
7. [Maintenance Mode](maintenance_mode.md)
8. [Composite Alarms](composite_alarms.md)
9. [Alarms for Custom Metrics](custom_metrics.md)
10. [Dimension Variables](variables.md)
10. [Dimension Variables](variables.md)
11. [Alarm Tags](alarm_tags.md)
28 changes: 26 additions & 2 deletions lib/cfnguardian.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
require "cfnguardian/drift"
require "cfnguardian/codecommit"
require "cfnguardian/codepipeline"
require "cfnguardian/tagger"

module CfnGuardian
class Cli < Thor
Expand Down Expand Up @@ -117,14 +118,37 @@ def deploy
deployer.execute_change_set(change_set.id)
deployer.wait_for_execute(change_set_type)
end


desc "tag-alarms", "apply tags to the cloudwatch alarms deployed"
long_desc <<-LONG
Because Cloudformation desn't support tagging cloudwatch alarms this command
applies tags to each cloudwatch alarm created by guardian.
Guardian defines default tags and this can be added to through the alarms.yaml config.
LONG
method_option :config, aliases: :c, type: :string, desc: "yaml config file", required: true
method_option :region, aliases: :r, type: :string, desc: "set the AWS region"

def tag_alarms
set_log_level(options[:debug])
set_region(options[:region],true)

compiler = CfnGuardian::Compile.new(options[:config])
compiler.get_resources
alarms = compiler.alarms

tagger = CfnGuardian::Tagger.new()
alarms.each do |alarm|
tagger.tag_alarm(alarm, compiler.global_tags)
end
end

desc "show-drift", "Cloudformation drift detection"
long_desc <<-LONG
Displays any cloudformation drift detection in the cloudwatch alarms from the deployed stacks
LONG
method_option :stack_name, aliases: :s, type: :string, default: 'guardian', desc: "set the Cloudformation stack name"
method_option :region, aliases: :r, type: :string, desc: "set the AWS region"

def show_drift
set_region(options[:region],true)

Expand Down
4 changes: 4 additions & 0 deletions lib/cfnguardian/cloudwatch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ def self.get_alarm_name(alarm)
alarm_id = alarm.resource_name.nil? ? alarm.resource_id : alarm.resource_name
return "guardian-#{alarm.group}-#{alarm_id}-#{alarm.name}"
end

def self.get_alarm_arn(alarm)
return "arn:aws:cloudwatch:#{Aws.config[:region]}:#{aws_account_id()}:alarm:#{self.get_alarm_name(alarm)}"
end

def self.get_alarms_by_prefix(prefix:, state: nil, action_prefix: nil)
client = Aws::CloudWatch::Client.new()
Expand Down
3 changes: 2 additions & 1 deletion lib/cfnguardian/compile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ module CfnGuardian
class Compile
include Logging

attr_reader :cost, :resources, :topics
attr_reader :cost, :resources, :topics, :global_tags

def initialize(config_file)
config = YAML.load_file(config_file)
Expand All @@ -68,6 +68,7 @@ def initialize(config_file)
@topics = config.fetch('Topics',{})
@maintenance_groups = config.fetch('MaintenanceGroups', {})
@event_subscriptions = config.fetch('EventSubscriptions', {})
@global_tags = config.fetch('GlobalTags', {})

# Make sure the default topics exist if they aren't supplied in the alarms.yaml
%w(Critical Warning Task Informational Events).each do |topic|
Expand Down
5 changes: 3 additions & 2 deletions lib/cfnguardian/models/alarm.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class BaseAlarm
:evaluate_low_sample_count_percentile,
:unit,
:maintenance_groups,
:additional_notifiers
:additional_notifiers,
:tags

def initialize(resource)
@type = 'Alarm'
Expand All @@ -56,6 +57,7 @@ def initialize(resource)
@treat_missing_data = nil
@maintenance_groups = []
@additional_notifiers = []
@tags = {}
end

def metric_name=(metric_name)
Expand All @@ -64,7 +66,6 @@ def metric_name=(metric_name)
end
end


class ApiGatewayAlarm < BaseAlarm
def initialize(resource)
super(resource)
Expand Down
69 changes: 69 additions & 0 deletions lib/cfnguardian/tagger.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
require 'aws-sdk-cloudwatch'
require 'cfnguardian/cloudwatch'
require 'cfnguardian/log'

module CfnGuardian
class Tagger
include Logging

def initialize()
@client = Aws::CloudWatch::Client.new(max_attempts: 5)
end

def tag_alarm(alarm, global_tags={})
alarm_arn = CfnGuardian::CloudWatch.get_alarm_arn(alarm)

new_tags = get_tags(alarm, global_tags)
current_tags = get_alarm_tags(alarm_arn)
tags_to_delete = get_tags_to_delete(current_tags, new_tags)

if tags_to_delete.any?
logger.debug "Removing tags #{tags_to_delete} from alarm #{alarm_arn}"
@client.untag_resource({
resource_arn: alarm_arn,
tag_keys: tags_to_delete
})
end

if tags_changed?(current_tags, new_tags)
logger.debug "Updating tags on alarm #{alarm_arn}"
@client.tag_resource({
resource_arn: alarm_arn,
tags: new_tags.map {|key,value| {key: key, value: value}}
})
end
end

def get_tags(alarm, global_tags)
defaults = {
'guardian:resource:id' => alarm.resource_id,
'guardian:resource:group' => alarm.group,
'guardian:alarm:name' => alarm.name,
'guardian:alarm:metric' => alarm.metric_name,
'guardian:alarm:severity' => alarm.alarm_action
}
tags = global_tags.merge(defaults)
return alarm.tags.merge(tags)
end

def get_alarm_tags(alarm_arn)
resp = @client.list_tags_for_resource({
resource_arn: alarm_arn
})
return resp.tags
end

def get_tags_to_delete(current_tags, new_tags)
return current_tags.select {|tag| !new_tags.has_key?(tag.key)}.map {|tag| tag.key}
end

def tags_changed?(current_tags, new_tags)
return tags_to_hash(current_tags) != new_tags
end

def tags_to_hash(tags)
return tags.map {|tag| {tag.key => tag.value} }.reduce(Hash.new, :merge)
end

end
end

0 comments on commit eefc62b

Please sign in to comment.