radiosonde 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 75143e75e9fad051bdae8993ad6d67d583b3873f
4
+ data.tar.gz: d0c7c4e7fee046cd4d644702aab4b406200f126c
5
+ SHA512:
6
+ metadata.gz: b1bf53f56c20e10b82bed5cbf3926335acaaa1603a5ddf0de7cf2dfbcfb2fa82f048bc3050663054f5c68c776e42cfbf193502246a1e0f7ceb70316b72238f5c
7
+ data.tar.gz: a0fc622cfc46c04b57b63771d07010ea98e50bbb005e2387482b6dcaf1d4ad46090a02cb6ec9b8435a947350736b6c55f2afdbbe5e74069f2c351c9739ae26be
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ test.rb
19
+ Alarmfile
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in radiosonde.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Genki Sugawara
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # Radiosonde
2
+
3
+ Radiosonde is a tool to manage CloudWatch Alarm.
4
+
5
+ It defines the state of CloudWatch Alarm using DSL, and updates CloudWatch Alarm according to DSL.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'radiosonde'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install radiosonde
20
+
21
+ ## Usage
22
+
23
+ ```sh
24
+ export AWS_ACCESS_KEY_ID='...'
25
+ export AWS_SECRET_ACCESS_KEY='...'
26
+ export AWS_REGION='us-east-1'
27
+ radiosonde -e -o Alarmfile # export CloudWatch Alarm
28
+ vi Alarmfile
29
+ radiosonde -a --dry-run
30
+ radiosonde -a # apply `Alarmfile` to CloudWatch
31
+ ```
32
+
33
+ ## Help
34
+
35
+ ```
36
+ Usage: radiosonde [options]
37
+ -p, --profile PROFILE_NAME
38
+ -k, --access-key ACCESS_KEY
39
+ -s, --secret-key SECRET_KEY
40
+ -r, --region REGION
41
+ -a, --apply
42
+ -f, --file FILE
43
+ --dry-run
44
+ -e, --export
45
+ -o, --output FILE
46
+ --no-color
47
+ --debug
48
+ ```
49
+
50
+ ## Alarmfile example
51
+
52
+ ```ruby
53
+ require 'other/alarmfile'
54
+
55
+ alarm "alarm1" do
56
+ namespace "AWS/EC2"
57
+ metric_name "CPUUtilization"
58
+ dimensions "InstanceId"=>"i-XXXXXXXX"
59
+ period 300
60
+ statistic :average
61
+ threshold ">=", 50.0
62
+ evaluation_periods 1
63
+ actions_enabled true
64
+ alarm_actions []
65
+ ok_actions []
66
+ insufficient_data_actions ["arn:aws:sns:us-east-1:123456789012:my_topic"]
67
+ end
68
+
69
+ alarm "alarm2" do
70
+ ...
71
+ end
72
+ ```
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+ task :default => :spec
data/bin/radiosonde ADDED
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path("#{File.dirname __FILE__}/../lib")
3
+ require 'rubygems'
4
+ require 'radiosonde'
5
+ require 'optparse'
6
+
7
+ Version = Radiosonde::VERSION
8
+ DEFAULT_FILENAME = 'Alarmfile'
9
+
10
+ mode = nil
11
+ file = DEFAULT_FILENAME
12
+ output_file = '-'
13
+
14
+ options = {
15
+ :dry_run => false,
16
+ :color => true,
17
+ :debug => false,
18
+ }
19
+
20
+ ARGV.options do |opt|
21
+ begin
22
+ access_key = nil
23
+ secret_key = nil
24
+ region = nil
25
+ profile_name = nil
26
+
27
+ opt.on('-p', '--profile PROFILE_NAME') {|v| profile_name = v }
28
+ opt.on('-k', '--access-key ACCESS_KEY') {|v| access_key = v }
29
+ opt.on('-s', '--secret-key SECRET_KEY') {|v| secret_key = v }
30
+ opt.on('-r', '--region REGION') {|v| region = v }
31
+ opt.on('-a', '--apply') { mode = :apply }
32
+ opt.on('-f', '--file FILE') {|v| file = v }
33
+ opt.on('', '--dry-run') { options[:dry_run] = true }
34
+ opt.on('-e', '--export') { mode = :export }
35
+ opt.on('-o', '--output FILE') {|v| output_file = v }
36
+ opt.on('' , '--no-color') { options[:color] = false }
37
+ opt.on('' , '--debug') { options[:debug] = true }
38
+ opt.parse!
39
+
40
+ aws_opts = {}
41
+
42
+ if access_key and secret_key
43
+ aws_opts = {
44
+ :access_key_id => access_key,
45
+ :secret_access_key => secret_key,
46
+ }
47
+ elsif profile_name
48
+ provider = AWS::Core::CredentialProviders::SharedCredentialFileProvider.new(
49
+ :profile_name => profile_name
50
+ )
51
+ aws_opts[:credential_provider] = provider
52
+ elsif (access_key and !secret_key) or (!access_key and secret_key) or mode.nil?
53
+ puts opt.help
54
+ exit 1
55
+ end
56
+
57
+ aws_opts[:region] = region if region
58
+ AWS.config(aws_opts)
59
+ rescue => e
60
+ $stderr.puts("[ERROR] #{e.message}")
61
+ exit 1
62
+ end
63
+ end
64
+
65
+ String.colorize = options[:color]
66
+
67
+ if options[:debug]
68
+ AWS.config({
69
+ :http_wire_trace => true,
70
+ :logger => Radiosonde::Logger.instance,
71
+ })
72
+ end
73
+
74
+ begin
75
+ logger = Radiosonde::Logger.instance
76
+ logger.set_debug(options[:debug])
77
+ client = Radiosonde::Client.new(options)
78
+
79
+ case mode
80
+ when :export
81
+ if output_file == '-'
82
+ logger.info('# Export Alarm')
83
+ puts client.export(options)
84
+ else
85
+ logger.info("Export Alarm to `#{output_file}`")
86
+ open(output_file, 'wb') {|f| f.puts client.export(options) }
87
+ end
88
+ when :apply
89
+ unless File.exist?(file)
90
+ raise "No Alarmfile found (looking for: #{file})"
91
+ end
92
+
93
+ msg = "Apply `#{file}` to CloudWatch"
94
+ msg << ' (dry-run)' if options[:dry_run]
95
+ logger.info(msg)
96
+
97
+ updated = client.apply(file)
98
+
99
+ logger.info('No change'.intense_blue) unless updated
100
+ else
101
+ raise 'must not happen'
102
+ end
103
+ rescue => e
104
+ if options[:debug]
105
+ raise e
106
+ else
107
+ $stderr.puts("[ERROR] #{e.message}".red)
108
+ exit 1
109
+ end
110
+ end
@@ -0,0 +1,66 @@
1
+ class Radiosonde::Client
2
+ include Radiosonde::Logger::Helper
3
+ include Radiosonde::Utils
4
+
5
+ def initialize(options = {})
6
+ @options = options
7
+ @cloud_watch = AWS::CloudWatch.new
8
+ end
9
+
10
+ def export(opts = {})
11
+ exported = nil
12
+
13
+ AWS.memoize do
14
+ exported = Radiosonde::Exporter.export(@cloud_watch, @options.merge(opts))
15
+ end
16
+
17
+ Radiosonde::DSL.convert(exported, @options.merge(opts))
18
+ end
19
+
20
+ def apply(file)
21
+ AWS.memoize { walk(file) }
22
+ end
23
+
24
+ private
25
+
26
+ def walk(file)
27
+ dsl = load_file(file)
28
+ dsl_alarms = collect_to_hash(dsl.alarms, :alarm_name)
29
+ aws = Radiosonde::Wrapper.wrap(@cloud_watch, @options)
30
+ aws_alarms = collect_to_hash(aws.alarms, :alarm_name)
31
+
32
+ dsl_alarms.each do |alarm_name, dsl_alarm|
33
+ aws_alarm = aws_alarms.delete(alarm_name)
34
+
35
+ if aws_alarm
36
+ walk_alarm(dsl_alarm, aws_alarm)
37
+ else
38
+ aws.alarms.create(alarm_name, dsl_alarm)
39
+ end
40
+ end
41
+
42
+ aws_alarms.each do |alarm_name, aws_alarm|
43
+ aws_alarm.delete
44
+ end
45
+
46
+ @cloud_watch.modified?
47
+ end
48
+
49
+ def walk_alarm(dsl_alarm, aws_alarm)
50
+ unless aws_alarm.eql?(dsl_alarm)
51
+ aws_alarm.update(dsl_alarm)
52
+ end
53
+ end
54
+
55
+ def load_file(file)
56
+ if file.kind_of?(String)
57
+ open(file) do |f|
58
+ Radiosonde::DSL.parse(f.read, file)
59
+ end
60
+ elsif file.respond_to?(:read)
61
+ Radiosonde::DSL.parse(file.read, file.path)
62
+ else
63
+ raise TypeError, "can't convert #{file} into File"
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,22 @@
1
+ class Radiosonde::DSL::ComparisonOperator
2
+ ALIASES = {
3
+ 'GreaterThanOrEqualToThreshold' => '>=',
4
+ 'GreaterThanThreshold' => '>',
5
+ 'LessThanThreshold' => '<',
6
+ 'LessThanOrEqualToThreshold' => '<=',
7
+ }
8
+
9
+ class << self
10
+ def conv_to_alias(operator)
11
+ ALIASES[operator] || operator
12
+ end
13
+
14
+ def valid?(operator)
15
+ ALIASES.keys.include?(operator) or ALIASES.values.include?(operator)
16
+ end
17
+
18
+ def normalize(operator)
19
+ (ALIASES.respond_to?(:key) ? ALIASES.key(operator) : ALIASES.index(operator)) || operator
20
+ end
21
+ end # of class methods
22
+ end
@@ -0,0 +1,124 @@
1
+ class Radiosonde::DSL::Context::Alarm
2
+ include Radiosonde::DSL::Validator
3
+
4
+ def initialize(name, &block)
5
+ @error_identifier = "Alarm `#{name}`"
6
+ @result = OpenStruct.new(
7
+ :alarm_name => name,
8
+ :alarm_actions => [],
9
+ :ok_actions => [],
10
+ :insufficient_data_actions => []
11
+ )
12
+ instance_eval(&block)
13
+ end
14
+
15
+ def result
16
+ [
17
+ :metric_name,
18
+ :period,
19
+ :statistic,
20
+ :threshold,
21
+ :comparison_operator,
22
+ :actions_enabled,
23
+ ].each do |name|
24
+ _required(name, @result[name])
25
+ end
26
+
27
+ @result
28
+ end
29
+
30
+ private
31
+
32
+ def description(value)
33
+ _call_once(:alarm_description)
34
+ @result.alarm_description = value.nil? ? nil : value.to_s
35
+ end
36
+
37
+ def namespace(value)
38
+ _call_once(:namespace)
39
+ @result.namespace = value.nil? ? nil : value.to_s
40
+ end
41
+
42
+ def metric_name(value)
43
+ _call_once(:metric_name)
44
+ _required(:metric_name, value)
45
+ @result.metric_name = value.to_s
46
+ end
47
+
48
+ def dimensions(value)
49
+ _call_once(:dimensions)
50
+ _expected_type(value, Hash, Array)
51
+
52
+ if value.kind_of?(Hash)
53
+ value = value.map do |name, value|
54
+ {:name => name, :value => value}
55
+ end
56
+ end
57
+
58
+ @result.dimensions = value
59
+ end
60
+
61
+ def period(value)
62
+ _call_once(:period)
63
+ @result.period = value.to_i
64
+ end
65
+
66
+ def statistic(value)
67
+ _call_once(:statistic)
68
+ _validate("Invalid value: #{value}") do
69
+ Radiosonde::DSL::Statistic.valid?(value)
70
+ end
71
+
72
+ @result.statistic = Radiosonde::DSL::Statistic.normalize(value)
73
+ end
74
+
75
+ def threshold(operator, value)
76
+ _call_once(:threshold)
77
+ _required(:threshold, value)
78
+ operator = operator.to_s
79
+ _validate("Invalid operator: #{operator}") do
80
+ Radiosonde::DSL::ComparisonOperator.valid?(operator)
81
+ end
82
+
83
+ @result.threshold = value.to_f
84
+ @result.comparison_operator = Radiosonde::DSL::ComparisonOperator.normalize(operator)
85
+ end
86
+
87
+ def evaluation_periods(value)
88
+ _call_once(:evaluation_periods)
89
+ @result.evaluation_periods = value.to_i
90
+ end
91
+
92
+ def unit(value)
93
+ _call_once(:unit)
94
+ _validate("Invalid value: #{value}") do
95
+ Radiosonde::DSL::Unit.valid?(value)
96
+ end
97
+
98
+ @result.unit = Radiosonde::DSL::Unit.normalize(value)
99
+ end
100
+
101
+ def actions_enabled(value)
102
+ _call_once(:actions_enabled)
103
+ _expected_type(value, TrueClass, FalseClass)
104
+ @result.actions_enabled = value
105
+ end
106
+
107
+ def alarm_actions(*actions)
108
+ _call_once(:alarm_actions)
109
+ _expected_type(actions, Array)
110
+ @result.alarm_actions = [(actions || [])].flatten
111
+ end
112
+
113
+ def ok_actions(*actions)
114
+ _call_once(:ok_actions)
115
+ _expected_type(actions, Array)
116
+ @result.ok_actions = [(actions || [])].flatten
117
+ end
118
+
119
+ def insufficient_data_actions(*actions)
120
+ _call_once(:insufficient_data_actions)
121
+ _expected_type(actions, Array)
122
+ @result.insufficient_data_actions = [(actions || [])].flatten
123
+ end
124
+ end
@@ -0,0 +1,45 @@
1
+ class Radiosonde::DSL::Context
2
+ include Radiosonde::DSL::Validator
3
+
4
+ class << self
5
+ def eval(dsl, path, opts = {})
6
+ self.new(path, opts) {
7
+ eval(dsl, binding, path)
8
+ }
9
+ end
10
+ end # of class methods
11
+
12
+ attr_reader :result
13
+
14
+ def initialize(path, options = {}, &block)
15
+ @path = path
16
+ @options = options
17
+ @result = OpenStruct.new(:alarms => [])
18
+ @alarm_names = []
19
+ instance_eval(&block)
20
+ end
21
+
22
+ private
23
+
24
+ def require(file)
25
+ alarmfile = File.expand_path(File.join(File.dirname(@path), file))
26
+
27
+ if File.exist?(alarmfile)
28
+ instance_eval(File.read(alarmfile), alarmfile)
29
+ elsif File.exist?(alarmfile + '.rb')
30
+ instance_eval(File.read(alarmfile + '.rb'), alarmfile + '.rb')
31
+ else
32
+ Kernel.require(file)
33
+ end
34
+ end
35
+
36
+ def alarm(name, &block)
37
+ _required(:alarm_name, name)
38
+ _validate("Alarm `#{name}` is already defined") do
39
+ not @alarm_names.include?(name)
40
+ end
41
+
42
+ @result.alarms << Radiosonde::DSL::Context::Alarm.new(name, &block).result
43
+ @alarm_names << name
44
+ end
45
+ end
@@ -0,0 +1,119 @@
1
+ class Radiosonde::DSL::Converter
2
+ class << self
3
+ def convert(exported, opts = {})
4
+ self.new(exported, opts).convert
5
+ end
6
+ end # of class methods
7
+
8
+ def initialize(exported, options = {})
9
+ @exported = exported
10
+ @options = options
11
+ end
12
+
13
+ def convert
14
+ @exported.each.map {|alarm_name, alarm_attrs|
15
+ output_alarm(alarm_name, alarm_attrs)
16
+ }.join("\n")
17
+ end
18
+
19
+ private
20
+
21
+ def output_alarm(name, attrs)
22
+ name = name.inspect
23
+ description = attrs[:description]
24
+ description = "description #{description.inspect}\n " if description
25
+ namespace = attrs[:namespace].inspect
26
+ metric_name = attrs[:metric_name].inspect
27
+ dimensions = format_dimensions(attrs)
28
+ dimensions = "dimensions #{dimensions}\n " if dimensions
29
+ period = attrs[:period].inspect
30
+ statistic = Radiosonde::DSL::Statistic.conv_to_alias(attrs[:statistic]).inspect
31
+ threshold = format_threshold(attrs)
32
+ evaluation_periods = attrs[:evaluation_periods].inspect
33
+ actions_enabled = attrs[:actions_enabled].inspect
34
+ alarm_actions = attrs[:alarm_actions].inspect
35
+ ok_actions = attrs[:ok_actions].inspect
36
+ insufficient_data_actions = attrs[:insufficient_data_actions].inspect
37
+
38
+ if unit = attrs[:unit]
39
+ unit = Radiosonde::DSL::Unit.conv_to_alias(unit).inspect
40
+ unit = "unit #{unit}"
41
+ end
42
+
43
+ <<-EOS
44
+ alarm #{name} do
45
+ #{description
46
+ }namespace #{namespace}
47
+ metric_name #{metric_name}
48
+ #{dimensions
49
+ }period #{period}
50
+ statistic #{statistic}
51
+ threshold #{threshold}
52
+ evaluation_periods #{evaluation_periods}
53
+ #{unit
54
+ }actions_enabled #{actions_enabled}
55
+ alarm_actions #{alarm_actions}
56
+ ok_actions #{ok_actions}
57
+ insufficient_data_actions #{insufficient_data_actions}
58
+ end
59
+ EOS
60
+ end
61
+
62
+ def format_dimensions(attrs)
63
+ dimensions = attrs[:dimensions] || []
64
+ return nil if dimensions.empty?
65
+ names = dimensions.map {|i| i[:name] }
66
+
67
+ if duplicated?(names)
68
+ dimensions.inspect
69
+ else
70
+ dimension_hash = {}
71
+
72
+ dimensions.each do |dimension|
73
+ name = dimension[:name]
74
+ value = dimension[:value]
75
+ dimension_hash[name] = value
76
+ end
77
+
78
+ unbrace(dimension_hash.inspect)
79
+ end
80
+ end
81
+
82
+ def format_threshold(attrs)
83
+ threshold = attrs[:threshold]
84
+ operator = attrs[:comparison_operator]
85
+ operator = Radiosonde::DSL::ComparisonOperator.conv_to_alias(operator)
86
+
87
+ [
88
+ operator.inspect,
89
+ threshold.inspect,
90
+ ].join(', ')
91
+ end
92
+
93
+ def output_actions(attrs, opts = {})
94
+ prefix = opts[:prefix]
95
+ enabled = attrs[:actions_enabled].inspect
96
+ alarm_actions = attrs[:alarm_actions].inspect
97
+ ok_actions = attrs[:ok_actions].inspect
98
+ insufficient_data_actions = attrs[:insufficient_data_actions].inspect
99
+
100
+ [
101
+ 'actions {',
102
+ " :enabled => #{enabled},",
103
+ " :alarm => #{alarm_actions},",
104
+ " :ok => #{ok_actions},",
105
+ " :insufficient => #{insufficient_data_actions},",
106
+ '}',
107
+ ].join("\n#{prefix}")
108
+ end
109
+
110
+ private
111
+
112
+ def unbrace(str)
113
+ str.sub(/\A\s*\{/, '').sub(/\}\s*\z/, '')
114
+ end
115
+
116
+ def duplicated?(list)
117
+ list.length != list.uniq.length
118
+ end
119
+ end
@@ -0,0 +1,23 @@
1
+ class Radiosonde::DSL::Statistic
2
+ ALIASES = {
3
+ 'SampleCount' => :sample_count,
4
+ 'Average' => :average,
5
+ 'Sum' => :sum,
6
+ 'Minimum' => :minimum,
7
+ 'Maximum' => :maximum,
8
+ }
9
+
10
+ class << self
11
+ def conv_to_alias(statistic)
12
+ ALIASES[statistic] || statistic
13
+ end
14
+
15
+ def valid?(statistic)
16
+ ALIASES.keys.include?(statistic) or ALIASES.values.include?(statistic)
17
+ end
18
+
19
+ def normalize(statistic)
20
+ (ALIASES.respond_to?(:key) ? ALIASES.key(statistic) : ALIASES.index(statistic)) || statistic
21
+ end
22
+ end # of class methods
23
+ end
@@ -0,0 +1,44 @@
1
+ class Radiosonde::DSL::Unit
2
+ ALIASES = {
3
+ 'Seconds' => :seconds,
4
+ 'Microseconds' => :microseconds,
5
+ 'Milliseconds' => :milliseconds,
6
+ 'Bytes' => :bytes,
7
+ 'Kilobytes' => :kilobytes,
8
+ 'Megabytes' => :megabytes,
9
+ 'Gigabytes' => :gigabytes,
10
+ 'Terabytes' => :terabytes,
11
+ 'Bits' => :bits,
12
+ 'Kilobits' => :kilobits,
13
+ 'Megabits' => :megabits,
14
+ 'Gigabits' => :gigabits,
15
+ 'Terabits' => :terabits,
16
+ 'Percent' => :percent,
17
+ 'Count' => :count,
18
+ 'Bytes/Second' => :bytes_per_second,
19
+ 'Kilobytes/Second' => :kilobytes_per_second,
20
+ 'Megabytes/Second' => :megabytes_per_second,
21
+ 'Gigabytes/Second' => :gigabytes_per_second,
22
+ 'Terabytes/Second' => :terabytes_per_second,
23
+ 'Bits/Second' => :bits_per_second,
24
+ 'Kilobits/Second' => :kilobits_per_second,
25
+ 'Megabits/Second' => :megabits_per_second,
26
+ 'Gigabits/Second' => :gigabits_per_second,
27
+ 'Terabits/Second' => :terabits_per_second,
28
+ 'Count/Second' => :count_per_second,
29
+ 'None' => :none,
30
+ }
31
+ class << self
32
+ def conv_to_alias(unit)
33
+ ALIASES[unit] || unit
34
+ end
35
+
36
+ def valid?(unit)
37
+ ALIASES.keys.include?(unit) or ALIASES.values.include?(unit)
38
+ end
39
+
40
+ def normalize(unit)
41
+ (ALIASES.respond_to?(:key) ? ALIASES.key(unit) : ALIASES.index(unit)) || unit
42
+ end
43
+ end # of class methods
44
+ end
@@ -0,0 +1,46 @@
1
+ module Radiosonde::DSL::Validator
2
+ def _required(name, value)
3
+ invalid = false
4
+
5
+ if value
6
+ case value
7
+ when String
8
+ invalid = value.strip.empty?
9
+ when Array, Hash
10
+ invalid = value.empty?
11
+ end
12
+ elsif value.nil?
13
+ invalid = true
14
+ end
15
+
16
+ raise _identify("`#{name}` is required") if invalid
17
+ end
18
+
19
+ def _call_once(method_name)
20
+ @called ||= []
21
+
22
+ if @called.include?(method_name)
23
+ raise _identify("`#{method_name}` is already defined")
24
+ end
25
+
26
+ @called << method_name
27
+ end
28
+
29
+ def _expected_type(value, *types)
30
+ unless types.any? {|t| value.kind_of?(t) }
31
+ raise _identify("Invalid type: #{value}")
32
+ end
33
+ end
34
+
35
+ def _validate(errmsg)
36
+ raise _identify(errmsg) unless yield
37
+ end
38
+
39
+ def _identify(errmsg)
40
+ if @error_identifier
41
+ errmsg = "#{@error_identifier}: #{errmsg}"
42
+ end
43
+
44
+ return errmsg
45
+ end
46
+ end
@@ -0,0 +1,11 @@
1
+ class Radiosonde::DSL
2
+ class << self
3
+ def convert(exported, opts = {})
4
+ Radiosonde::DSL::Converter.convert(exported, opts)
5
+ end
6
+
7
+ def parse(dsl, path, opts = {})
8
+ Radiosonde::DSL::Context.eval(dsl, path, opts).result
9
+ end
10
+ end # of class methods
11
+ end
@@ -0,0 +1,60 @@
1
+ class Radiosonde::Exporter
2
+ class << self
3
+ def export(clowd_watch, opts = {})
4
+ self.new(clowd_watch, opts).export
5
+ end
6
+ end # of class methods
7
+
8
+ def initialize(clowd_watch, options = {})
9
+ @clowd_watch = clowd_watch
10
+ @options = options
11
+ end
12
+
13
+ def export
14
+ result = {}
15
+
16
+ @clowd_watch.alarms.each do |alarm|
17
+ export_alarm(alarm, result)
18
+ end
19
+
20
+ return result
21
+ end
22
+
23
+ private
24
+
25
+ def export_alarm(alarm, result)
26
+ alarm_attrs = {
27
+ :description => alarm.alarm_description,
28
+ :metric_name => alarm.metric_name,
29
+ :namespace => alarm.namespace,
30
+ :dimensions => alarm.dimensions,
31
+ :period => alarm.period,
32
+ :statistic => alarm.statistic,
33
+ :threshold => alarm.threshold,
34
+ :comparison_operator => alarm.comparison_operator,
35
+ :evaluation_periods => alarm.evaluation_periods,
36
+ :actions_enabled => alarm.actions_enabled,
37
+ :alarm_actions => alarm.alarm_actions,
38
+ :ok_actions => alarm.ok_actions,
39
+ :insufficient_data_actions => alarm.insufficient_data_actions,
40
+ :unit => alarm.unit,
41
+ }
42
+
43
+ if @options[:with_status]
44
+ alarm_attrs[:status] = export_alarm_status(alarm)
45
+ end
46
+
47
+ result[alarm.alarm_name] = alarm_attrs
48
+ end
49
+
50
+ def export_alarm_status(alarm)
51
+ {
52
+ :alarm_arn => alarm.alarm_arn,
53
+ :state_value => alarm.state_value,
54
+ :state_reason => alarm.state_reason,
55
+ :state_reason_data => alarm.state_reason_data ? JSON.parse(alarm.state_reason_data) : nil,
56
+ :state_updated_timestamp => alarm.state_updated_timestamp,
57
+ :alarm_configuration_updated_timestamp => alarm.alarm_configuration_updated_timestamp,
58
+ }
59
+ end
60
+ end
@@ -0,0 +1,9 @@
1
+ class AWS::CloudWatch
2
+ def modify!
3
+ @modified = true
4
+ end
5
+
6
+ def modified?
7
+ !!@modified
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ class String
2
+ @@colorize = false
3
+
4
+ class << self
5
+ def colorize=(value)
6
+ @@colorize = value
7
+ end
8
+
9
+ def colorize
10
+ @@colorize
11
+ end
12
+ end # of class methods
13
+
14
+ Term::ANSIColor::Attribute.named_attributes.map do |attribute|
15
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
16
+ def #{attribute.name}
17
+ if @@colorize
18
+ Term::ANSIColor.send(#{attribute.name.inspect}, self)
19
+ else
20
+ self
21
+ end
22
+ end
23
+ EOS
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ class Radiosonde::Logger < ::Logger
2
+ include Singleton
3
+
4
+ def initialize
5
+ super($stdout)
6
+
7
+ self.formatter = proc do |severity, datetime, progname, msg|
8
+ "#{msg}\n"
9
+ end
10
+
11
+ self.level = Logger::INFO
12
+ end
13
+
14
+ def set_debug(value)
15
+ self.level = value ? Logger::DEBUG : Logger::INFO
16
+ end
17
+
18
+ module Helper
19
+ def log(level, message, color, log_id = nil)
20
+ message = "[#{level.to_s.upcase}] #{message}" unless level == :info
21
+ message << ": #{log_id}" if log_id
22
+ message << ' (dry-run)' if @options && @options[:dry_run]
23
+ logger = (@options && @options[:logger]) || Radiosonde::Logger.instance
24
+ logger.send(level, message.send(color))
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ module Radiosonde::Utils
2
+ def collect_to_hash(collection, *key_attrs)
3
+ opts = key_attrs.last.kind_of?(Hash) ? key_attrs.pop : {}
4
+ hash = {}
5
+
6
+ collection.each do |item|
7
+ if block_given?
8
+ key = yield(item)
9
+ else
10
+ key = key_attrs.map {|k| item.send(k) }
11
+ key = key.first if key_attrs.length == 1
12
+ end
13
+
14
+ if opts[:has_many]
15
+ hash[key] ||= []
16
+ hash[key] << item
17
+ else
18
+ hash[key] = item
19
+ end
20
+ end
21
+
22
+ return hash
23
+ end
24
+ module_function :collect_to_hash
25
+ end
@@ -0,0 +1,3 @@
1
+ module Radiosonde
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,109 @@
1
+ class Radiosonde::Wrapper::Alarm
2
+ extend Forwardable
3
+ include Radiosonde::Logger::Helper
4
+
5
+ ATTRIBUTES_WITHOUT_NAME = [
6
+ :alarm_description,
7
+ :namespace,
8
+ :metric_name,
9
+ :dimensions,
10
+ :period,
11
+ :statistic,
12
+ :threshold,
13
+ :comparison_operator,
14
+ :evaluation_periods,
15
+ :actions_enabled,
16
+ :unit,
17
+ :alarm_actions,
18
+ :ok_actions,
19
+ :insufficient_data_actions,
20
+ :actions_enabled,
21
+ ]
22
+
23
+ ATTRIBUTES = [:alarm_name] + ATTRIBUTES_WITHOUT_NAME
24
+
25
+ DEFAULT_VALUES = {
26
+ :dimensions => []
27
+ }
28
+
29
+ ATTRIBUTES.each do |name|
30
+ def_delegator :@alarm, name
31
+ end
32
+
33
+ class << self
34
+ def normalize_attrs(attrs)
35
+ normalized = {}
36
+
37
+ Radiosonde::Wrapper::Alarm::ATTRIBUTES_WITHOUT_NAME.each do |name|
38
+ unless (value = attrs[name]).nil?
39
+ normalized[name] = value
40
+ end
41
+ end
42
+
43
+ return normalized
44
+ end
45
+ end # of class methods
46
+
47
+ def initialize(clowd_watch, alarm, options = {})
48
+ @clowd_watch = clowd_watch
49
+ @alarm = alarm
50
+ @options = options
51
+ end
52
+
53
+ def eql?(dsl)
54
+ diff(dsl).empty?
55
+ end
56
+
57
+ def update(dsl)
58
+ delta = diff(dsl)
59
+ log(:info, 'Update Alarm', :green, "#{self.alarm_name} > #{format_delta(delta)}")
60
+
61
+ unless @options[:dry_run]
62
+ opts = self.class.normalize_attrs(dsl)
63
+ @alarm.update(opts)
64
+ @clowd_watch.modify!
65
+ end
66
+ end
67
+
68
+ def delete
69
+ log(:info, 'Delete Alarm', :red, self.alarm_name)
70
+
71
+ unless @options[:dry_run]
72
+ @alarm.delete
73
+ @clowd_watch.modify!
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def diff(dsl)
80
+ delta = {}
81
+
82
+ ATTRIBUTES.each do |name|
83
+ self_value = self.send(name)
84
+ dsl_value = dsl[name]
85
+
86
+ if normalize(name, self_value) != normalize(name, dsl_value)
87
+ delta[name] = {:old => self_value, :new => dsl_value}
88
+ end
89
+ end
90
+
91
+ return delta
92
+ end
93
+
94
+ def format_delta(delta)
95
+ delta.map {|name, values|
96
+ "#{name}(#{values[:old]}=>#{values[:new]})"
97
+ }.join(', ')
98
+ end
99
+
100
+ def normalize(name, value)
101
+ if [Array, Hash].any? {|c| value.kind_of?(c) }
102
+ value.sort
103
+ elsif DEFAULT_VALUES.has_key?(name) and value.nil?
104
+ DEFAULT_VALUES[name]
105
+ else
106
+ value
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,29 @@
1
+ class Radiosonde::Wrapper::AlarmCollection
2
+ include Radiosonde::Logger::Helper
3
+
4
+ def initialize(clowd_watch, alarms, options = {})
5
+ @clowd_watch = clowd_watch
6
+ @alarms = alarms
7
+ @options = options
8
+ end
9
+
10
+ def each
11
+ @alarms.each do |alarm|
12
+ yield(Radiosonde::Wrapper::Alarm.new(@clowd_watch, alarm, @options))
13
+ end
14
+ end
15
+
16
+ def create(name, dsl)
17
+ log(:info, 'Create Alarm', :cyan, name)
18
+ opts = Radiosonde::Wrapper::Alarm.normalize_attrs(dsl)
19
+
20
+ if @options[:dry_run]
21
+ alarm = OpenStruct.new(opts.merge(:alarm_name => name))
22
+ else
23
+ alarm = @alarms.create(name, opts)
24
+ @clowd_watch.modify!
25
+ end
26
+
27
+ Radiosonde::Wrapper::Alarm.new(@clowd_watch, alarm, @options)
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ class Radiosonde::Wrapper::CloudWatch
2
+ def initialize(clowd_watch, options = {})
3
+ @clowd_watch = clowd_watch
4
+ @options = options
5
+ end
6
+
7
+ def alarms
8
+ Radiosonde::Wrapper::AlarmCollection.new(@clowd_watch, @clowd_watch.alarms, @options)
9
+ end
10
+
11
+ def modified?
12
+ @clowd_watch.modified?
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ class Radiosonde::Wrapper
2
+ class << self
3
+ def wrap(cw, opts = {})
4
+ Radiosonde::Wrapper::CloudWatch.new(cw, opts)
5
+ end
6
+ end # of class methods
7
+ end
data/lib/radiosonde.rb ADDED
@@ -0,0 +1,29 @@
1
+ module Radiosonde; end
2
+
3
+ require 'forwardable'
4
+ require 'json'
5
+ require 'logger'
6
+ require 'ostruct'
7
+ require 'singleton'
8
+
9
+ require 'aws-sdk'
10
+ require 'term/ansicolor'
11
+
12
+ require 'radiosonde/logger'
13
+ require 'radiosonde/utils'
14
+ require 'radiosonde/client'
15
+ require 'radiosonde/dsl'
16
+ require 'radiosonde/dsl/validator'
17
+ require 'radiosonde/dsl/comparison_operator'
18
+ require 'radiosonde/dsl/context'
19
+ require 'radiosonde/dsl/context/alarm'
20
+ require 'radiosonde/dsl/converter'
21
+ require 'radiosonde/dsl/statistic'
22
+ require 'radiosonde/exporter'
23
+ require 'radiosonde/ext/cloud_watch_ext'
24
+ require 'radiosonde/ext/string_ext'
25
+ require 'radiosonde/version'
26
+ require 'radiosonde/wrapper'
27
+ require 'radiosonde/wrapper/alarm'
28
+ require 'radiosonde/wrapper/alarm_collection'
29
+ require 'radiosonde/wrapper/cloud_watch'
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'radiosonde/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'radiosonde'
8
+ spec.version = Radiosonde::VERSION
9
+ spec.authors = ['Genki Sugawara']
10
+ spec.email = ['sgwr_dts@yahoo.co.jp']
11
+ spec.description = "Radiosonde is a tool to manage CloudWatch Alarm. It defines the state of CloudWatch Alarm using DSL, and updates CloudWatch Alarm according to DSL."
12
+ spec.summary = "Radiosonde is a tool to manage CloudWatch Alarm."
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'aws-sdk', '>= 1.50.0'
22
+ spec.add_dependency "json"
23
+ spec.add_dependency "term-ansicolor"
24
+ spec.add_development_dependency 'bundler', '~> 1.5'
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_development_dependency 'rspec', '>= 3.0.0'
27
+ end
@@ -0,0 +1,5 @@
1
+ describe Radiosonde do
2
+ it do
3
+ expect(1).to eq(1)
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ require 'radiosonde'
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: radiosonde
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Genki Sugawara
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.50.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.50.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: term-ansicolor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: 3.0.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: 3.0.0
97
+ description: Radiosonde is a tool to manage CloudWatch Alarm. It defines the state
98
+ of CloudWatch Alarm using DSL, and updates CloudWatch Alarm according to DSL.
99
+ email:
100
+ - sgwr_dts@yahoo.co.jp
101
+ executables:
102
+ - radiosonde
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - .gitignore
107
+ - .rspec
108
+ - Gemfile
109
+ - LICENSE.txt
110
+ - README.md
111
+ - Rakefile
112
+ - bin/radiosonde
113
+ - lib/radiosonde.rb
114
+ - lib/radiosonde/client.rb
115
+ - lib/radiosonde/dsl.rb
116
+ - lib/radiosonde/dsl/comparison_operator.rb
117
+ - lib/radiosonde/dsl/context.rb
118
+ - lib/radiosonde/dsl/context/alarm.rb
119
+ - lib/radiosonde/dsl/converter.rb
120
+ - lib/radiosonde/dsl/statistic.rb
121
+ - lib/radiosonde/dsl/unit.rb
122
+ - lib/radiosonde/dsl/validator.rb
123
+ - lib/radiosonde/exporter.rb
124
+ - lib/radiosonde/ext/cloud_watch_ext.rb
125
+ - lib/radiosonde/ext/string_ext.rb
126
+ - lib/radiosonde/logger.rb
127
+ - lib/radiosonde/utils.rb
128
+ - lib/radiosonde/version.rb
129
+ - lib/radiosonde/wrapper.rb
130
+ - lib/radiosonde/wrapper/alarm.rb
131
+ - lib/radiosonde/wrapper/alarm_collection.rb
132
+ - lib/radiosonde/wrapper/cloud_watch.rb
133
+ - radiosonde.gemspec
134
+ - spec/radiosonde_spec.rb
135
+ - spec/spec_helper.rb
136
+ homepage: ''
137
+ licenses:
138
+ - MIT
139
+ metadata: {}
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - '>='
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - '>='
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 2.0.14
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: Radiosonde is a tool to manage CloudWatch Alarm.
160
+ test_files:
161
+ - spec/radiosonde_spec.rb
162
+ - spec/spec_helper.rb