radiosonde 0.0.1

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 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