kashi 0.1.0.beta1

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: '055328ffe3f5ea256bb7b5a93ff93b955a87f502'
4
+ data.tar.gz: 772ae8d3b42689c100d474a375676790f3366377
5
+ SHA512:
6
+ metadata.gz: af45c16beb5e36f27cfde874292f3f1c659cbc3639376f4aad54b15609f1035c937c17a09a3ea4cd629a8df35c00100b8a23d7e930a64f0e4f182a37268d8acd
7
+ data.tar.gz: '0928ab0904b4f029cae919d0981216fbad95041e06b952923dd34ecddd20031e80d286ac6d42a936add1075b50cd4819513af5deb807bc6d6d1458d68ed97a94'
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kashi.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 shinya-watanabe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # Kashi
2
+
3
+ Kashi is a tool to manage StatusCake. It defines the state of StatusCake using DSL, and updates StatusCake according to DSL.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'kashi'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install kashi
20
+
21
+ ## Usage
22
+
23
+ ```
24
+ export KASHI_SC_USER='...'
25
+ export KASHI_SC_API_KEY='...'
26
+ kashi -e # export StatusCake
27
+ vi SCfile
28
+ kashi -a --dry-run
29
+ kashi -a # apply `SCfile` to StatusCake
30
+ ```
31
+
32
+ ## Help
33
+
34
+ ```
35
+ Usage: kashi [options]
36
+ -h, --help Show help
37
+ -a, --apply Apply DSL
38
+ -e, --export Export to DSL
39
+ -n, --dry-run Dry run
40
+ --no-color
41
+ No color
42
+ -s, --split Split export DLS file contact group and tests
43
+ --split-more
44
+ Split export DLS file to 1 per object
45
+ -v, --debug Show debug log
46
+ ```
47
+
48
+ ## SCfile
49
+
50
+ See Accepted values at documents below.
51
+
52
+ - Contact Groups
53
+ - https://www.statuscake.com/api/Contact%20Groups/Add%20or%20Update%20Contact%20Group.md
54
+ - Tests
55
+ - https://www.statuscake.com/api/Tests/Updating%20Inserting%20and%20Deleting%20Tests.md
56
+
57
+ ```ruby
58
+ cake do
59
+ contact_group do
60
+ group_name "Alarm"
61
+ desktop_alert 0
62
+ email ["wata.gm@gmail.com"]
63
+ boxcar ""
64
+ pushover ""
65
+ ping_url ""
66
+ mobile nil
67
+ end
68
+
69
+ test do
70
+ website_name "your awesome site"
71
+ website_url "https://example.com/healthcheck"
72
+
73
+ paused 0
74
+ # HTTP,TCP,PING
75
+ test_type "HTTP"
76
+ contact_group ["Alarm"]
77
+ check_rate 300
78
+ timeout 40
79
+ website_host ""
80
+ node_locations ["freeserver2"]
81
+ find_string ""
82
+ do_not_find 0
83
+ logo_image ""
84
+
85
+ custom_header(
86
+ {"Host"=>"example.com"}
87
+ )
88
+ confirmation "2"
89
+
90
+ basic_user nil
91
+ basic_pass nil
92
+
93
+ use_jar nil
94
+
95
+ dns_ip ""
96
+ dns_server ""
97
+ trigger_rate "0"
98
+ test_tags ["Web", "Internal"]
99
+ status_codes ["204", "205", "206", "303", "400", "401", "403", "404", "405", "406", "408", "410", "413", "444", "429", "494", "495", "496", "499", "500", "501", "502", "503", "504", "505", "506", "507", "508", "509", "510", "511", "521", "522", "523", "524", "520", "598", "599", "302"]
100
+ enable_ssl_warning 0
101
+ follow_redirect 1
102
+ end
103
+ end
104
+ ```
105
+
106
+ ## Similar tools
107
+
108
+ * [Codenize.tools](http://codenize.tools/)
109
+
110
+ ## Contributing
111
+
112
+ Bug reports and pull requests are welcome on GitHub at https://github.com/wata-gh/kashi.
113
+
114
+
115
+ ## License
116
+
117
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "kashi"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/kashi ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'kashi/cli'
3
+
4
+ Kashi::CLI.start(ARGV)
data/kashi.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kashi/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'kashi'
8
+ spec.version = Kashi::VERSION
9
+ spec.authors = ['wata']
10
+ spec.email = ['wata.gm@gmail.com']
11
+
12
+ spec.summary = %q{Codenize StatusCake}
13
+ spec.description = %q{Manage StatusCake by DSL}
14
+ spec.homepage = 'https://github.com/wata-gh/kashi'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_dependency 'statuscake', '~> 0.1.2'
25
+ spec.add_dependency 'hashie', '~> 3.5', '>= 3.5.5'
26
+ spec.add_dependency 'diffy', '~> 3.2', '>= 3.2.0'
27
+
28
+ spec.add_development_dependency 'bundler', '~> 1.14'
29
+ spec.add_development_dependency 'rake', '~> 10.0'
30
+ spec.add_development_dependency 'pry-byebug', '~> 3.4'
31
+ end
data/lib/kashi/cli.rb ADDED
@@ -0,0 +1,70 @@
1
+ require 'optparse'
2
+ require 'kashi'
3
+
4
+ module Kashi
5
+ class CLI
6
+ def self.start(argv)
7
+ new(argv).run
8
+ end
9
+
10
+ def initialize(argv)
11
+ @argv = argv.dup
12
+ @help = argv.empty?
13
+ @filepath = 'SCfile'
14
+ @options = {
15
+ color: true,
16
+ }
17
+ parser.order!(@argv)
18
+ end
19
+
20
+ def run
21
+ if @help
22
+ puts parser.help
23
+ elsif @apply
24
+ Apply.new(@filepath, @options).run
25
+ elsif @export
26
+ Export.new(@filepath, @options).run
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def parser
33
+ @parser ||= OptionParser.new do |opts|
34
+ opts.version = VERSION
35
+ opts.on('-h', '--help', 'Show help') { @help = true }
36
+ opts.on('-a', '--apply', 'Apply DSL') { @apply = true }
37
+ opts.on('-e', '--export', 'Export to DSL') { @export = true }
38
+ opts.on('-n', '--dry-run', 'Dry run') { @options[:dry_run] = true }
39
+ opts.on('', '--no-color', 'No color') { @options[:color] = false }
40
+ opts.on('-s', '--split', 'Split export DLS file contact group and tests') { @options[:split] = true }
41
+ opts.on('', '--split-more', 'Split export DLS file to 1 per object') { @options[:split_more] = true }
42
+ opts.on('-v', '--debug', 'Show debug log') { Kashi.logger.level = Logger::DEBUG }
43
+ end
44
+ end
45
+
46
+ class Apply
47
+ def initialize(filepath, options)
48
+ @filepath = filepath
49
+ @options = options
50
+ end
51
+
52
+ def run
53
+ require 'kashi/client'
54
+ result = Client.new(@filepath, @options).apply
55
+ end
56
+ end
57
+
58
+ class Export
59
+ def initialize(filepath, options)
60
+ @filepath = filepath
61
+ @options = options
62
+ end
63
+
64
+ def run
65
+ require 'kashi/client'
66
+ result = Client.new(@filepath, @options).export
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,229 @@
1
+ require 'json'
2
+ require 'kashi'
3
+ require 'kashi/converter'
4
+ require 'kashi/client_wrapper'
5
+ require 'kashi/dsl'
6
+
7
+ module Kashi
8
+ class Client
9
+ MAGIC_COMMENT = <<-EOS
10
+ # -*- mode: ruby -*-
11
+ # vi: set ft=ruby :
12
+ EOS
13
+
14
+ def initialize(filepath, options = {})
15
+ @filepath = filepath
16
+ @options = options
17
+ end
18
+
19
+ def traverse_contact_groups(dsl_contact_groups, sc_contact_groups_by_id, sc_contact_groups_by_name)
20
+ dsl_contact_groups_by_name = dsl_contact_groups.group_by(&:group_name)
21
+ dsl_contact_groups_by_id = dsl_contact_groups.group_by(&:contact_id)
22
+
23
+ # create
24
+ dsl_contact_groups_by_name.reject { |n| sc_contact_groups_by_name[n] }.each do |name, dsl_contact_group|
25
+ sc_contact_groups_by_name[name] = dsl_contact_group.map(&:create)
26
+ end
27
+
28
+ # modify
29
+ dsl_contact_groups_by_name.each do |name, dsl_contact_groups|
30
+ next unless sc_contact_groups = sc_contact_groups_by_name.delete(name)
31
+
32
+ if dsl_contact_groups.length == 1 && sc_contact_groups.length == 1
33
+ next if sc_contact_groups[0]['Success'] # created contact group
34
+ dsl_contact_groups[0].cake(sc_contact_groups[0]).modify
35
+ else
36
+ dsl_contact_groups.each do |dsl_contact_group|
37
+ sc_contact_group = sc_contact_groups.find { |sc_cg| sc_cg['ContactID'] == dsl_contact_group.contact_id }
38
+ raise "contact_id must be set if same name contact group exist. `#{name}`" unless sc_contact_group
39
+ dsl_contact_group.cake(sc_contact_group).modify
40
+ end
41
+ end
42
+ end
43
+
44
+ # delete
45
+ sc_contact_groups_by_name.each do |name, sc_contact_groups|
46
+ sc_contact_groups.each do |sc_contact_group|
47
+ Kashi.logger.info("Delete ContactGroup `#{name}` #{sc_contact_group['ContactID']}")
48
+ next if @options[:dry_run]
49
+ client.contactgroups_update(method: :delete, ContactID: sc_contact_group['ContactID'])
50
+ end
51
+ end
52
+ end
53
+
54
+ def traverse_tests(dsl_tests, sc_tests_by_id, sc_tests_by_name)
55
+ dsl_tests_by_name = dsl_tests.group_by(&:website_name)
56
+ dsl_tests_by_id = dsl_tests.group_by(&:test_id)
57
+
58
+ # create
59
+ dsl_tests_by_name.reject { |n, _| sc_tests_by_name[n] }.each do |name, dsl_tests|
60
+ sc_tests_by_name[name] = dsl_tests.map do |dsl_test|
61
+ # if test_id exist, its name might been changed
62
+ if dsl_test.test_id
63
+ sc_test = sc_tests_by_id[dsl_test.test_id]
64
+ if sc_test
65
+ sc_tests_by_name[sc_test['WebsiteName']].delete_if { |sc_test| sc_test['TestID'] == dsl_test.test_id }
66
+ next sc_test
67
+ end
68
+ next
69
+ end
70
+ dsl_test.create
71
+ end
72
+ end
73
+
74
+ # modify
75
+ dsl_tests_by_name.each do |name, dsl_tests|
76
+ next unless sc_tests = sc_tests_by_name.delete(name)
77
+
78
+ if dsl_tests.length == 1 && sc_tests.length == 1
79
+ next if sc_tests[0]['Success'] # created test
80
+ sc_test = client.tests_details(TestID: sc_tests[0]['TestID'])
81
+ dsl_tests[0].cake(sc_test).modify
82
+ else
83
+ dsl_tests.each do |dsl_test|
84
+ sc_test = sc_tests.find { |sc_t| sc_t['TestID'] == dsl_test.test_id }
85
+ raise "test_id must be set if same name test exist. `#{name}`" unless sc_test
86
+ sc_test = client.tests_details(TestID: sc_test['TestID'])
87
+ dsl_test.cake(sc_test).modify
88
+ end
89
+ end
90
+ end
91
+
92
+ # delete
93
+ sc_tests_by_name.each do |name, sc_tests|
94
+ sc_tests.each do |sc_test|
95
+ Kashi.logger.info("Delete Test `#{name}` #{sc_test['TestID']}")
96
+ next if @options[:dry_run]
97
+ client.tests_details(method: :delete, TestID: sc_test['TestID'])
98
+ end
99
+ end
100
+ end
101
+
102
+ def apply
103
+ Kashi.logger.info("Applying...")
104
+ dsl = load_file(@filepath)
105
+
106
+ sc_contact_groups = client.contactgroups
107
+ sc_contact_groups_by_id = sc_contact_groups.each_with_object({}) do |contact_group, h|
108
+ h[contact_group['ContactID']] = contact_group
109
+ end
110
+ sc_contact_groups_by_name = sc_contact_groups.group_by do |contact_group|
111
+ contact_group['GroupName']
112
+ end
113
+
114
+ sc_tests = client.tests
115
+ sc_tests_by_id = sc_tests.each_with_object({}) do |test, h|
116
+ h[test['TestID']] = test
117
+ end
118
+ sc_tests_by_name = sc_tests.group_by do |sc_tests|
119
+ sc_tests['WebsiteName']
120
+ end
121
+
122
+ traverse_contact_groups(dsl.cake.contact_groups, sc_contact_groups_by_id, sc_contact_groups_by_name)
123
+
124
+ traverse_tests(dsl.cake.tests, sc_tests_by_id, sc_tests_by_name)
125
+ end
126
+
127
+ def export
128
+ Kashi.logger.info("Exporting...")
129
+
130
+ # API access
131
+ contact_groups = client.contactgroups(method: :get)
132
+ contact_groups_by_id = contact_groups.each_with_object({}) do |contact_group, hash|
133
+ hash[contact_group['ContactID']] = contact_group
134
+ end
135
+
136
+ # API access
137
+ tests = client.tests(method: :get)
138
+ tests_by_id = tests.each_with_object({}) do |test, hash|
139
+ # API access
140
+ hash[test['TestID']] = client.tests_details(TestID: test['TestID'])
141
+ end
142
+
143
+ path = Pathname.new(@filepath)
144
+ base_dir = path.parent
145
+
146
+ if @options[:split_more]
147
+ # contact_groups
148
+ contact_groups_by_id.each do |id, contact_group|
149
+ Converter.new({}, { id => contact_group }).convert do |dsl|
150
+ kashi_base_dir = base_dir.join('contact_groups')
151
+ FileUtils.mkdir_p(kashi_base_dir)
152
+ sc_file = kashi_base_dir.join("#{contact_group['GroupName'].gsub(/[\/ ]/, '_')}_#{contact_group['ContactID']}.cake")
153
+ Kashi.logger.info("Export #{sc_file}")
154
+ open(sc_file, 'wb') do |f|
155
+ f.puts MAGIC_COMMENT
156
+ f.puts dsl
157
+ end
158
+ end
159
+ end
160
+
161
+ # tests
162
+ tests_by_id.each do |id, test|
163
+ Converter.new({ id => test }, {}).convert do |dsl|
164
+ kashi_base_dir = base_dir.join('tests')
165
+ FileUtils.mkdir_p(kashi_base_dir)
166
+ sc_file = kashi_base_dir.join("#{test['WebsiteName'].gsub(/[\/ ]/, '_')}_#{test['TestID']}.cake")
167
+ Kashi.logger.info("Export #{sc_file}")
168
+ open(sc_file, 'wb') do |f|
169
+ f.puts MAGIC_COMMENT
170
+ f.puts dsl
171
+ end
172
+ end
173
+ end
174
+ elsif @options[:split]
175
+ # contact_groups
176
+ Converter.new({}, contact_groups_by_id).convert do |dsl|
177
+ sc_file = base_dir.join('contact_groups.cake')
178
+ Kashi.logger.info("Export #{sc_file}")
179
+ open(sc_file, 'wb') do |f|
180
+ f.puts MAGIC_COMMENT
181
+ f.puts dsl
182
+ end
183
+ end
184
+
185
+ # test
186
+ Converter.new(tests_by_id, {}).convert do |dsl|
187
+ sc_file = base_dir.join('tests.cake')
188
+ Kashi.logger.info("Export #{sc_file}")
189
+ open(sc_file, 'wb') do |f|
190
+ f.puts MAGIC_COMMENT
191
+ f.puts dsl
192
+ end
193
+ end
194
+ else
195
+ Converter.new(tests_by_id, contact_groups_by_id).convert do |dsl|
196
+ FileUtils.mkdir_p(base_dir)
197
+ Kashi.logger.info("Export #{path}")
198
+ open(path, 'wb') do |f|
199
+ f.puts MAGIC_COMMENT
200
+ f.puts dsl
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ # for develop
207
+ CACHE_DIR = Pathname.new("./tmp")
208
+ def cache(key)
209
+ cache_file = CACHE_DIR.join("#{key}.json")
210
+ if cache_file.exist?
211
+ return JSON.parse(cache_file.read)
212
+ end
213
+
214
+ yield.tap do |res|
215
+ cache_file.write(res.to_json)
216
+ end
217
+ end
218
+
219
+ def load_file(file)
220
+ open(file) do |f|
221
+ DSL.define(f.read, file, @options).result
222
+ end
223
+ end
224
+
225
+ def client
226
+ @client ||= ClientWrapper.new(@options)
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,17 @@
1
+ require 'forwardable'
2
+ require 'statuscake'
3
+
4
+ module Kashi
5
+ class ClientWrapper
6
+ extend Forwardable
7
+
8
+ def_delegators :@client, *%i/
9
+ contactgroups contactgroups_update
10
+ tests tests_details tests_update
11
+ /
12
+
13
+ def initialize(options)
14
+ @client = StatusCake::Client.new(API: ENV['KASHI_SC_API_KEY'], Username: ENV['KASHI_SC_USER'])
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ require 'erb'
2
+
3
+ module Kashi
4
+ class Converter
5
+ def initialize(tests_by_id, contact_groups_by_id)
6
+ @tests_by_id = tests_by_id
7
+ @contact_groups_by_id = contact_groups_by_id
8
+ end
9
+
10
+ def convert
11
+ yield output_test(@tests_by_id, @contact_groups_by_id)
12
+ end
13
+
14
+ private
15
+
16
+ def output_test(tests_by_id, contact_groups_by_id)
17
+ path = Pathname.new(File.expand_path('../', __FILE__)).join('output_test.erb')
18
+ ERB.new(path.read, nil, '-').result(binding)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ require 'ostruct'
2
+ require 'kashi/dsl/test'
3
+ require 'kashi/dsl/contact_group'
4
+
5
+ module Kashi
6
+ class DSL
7
+ class Cake
8
+ attr_reader :result
9
+
10
+ def initialize(context, tests = [], contacts = [], &block)
11
+ @context = context
12
+
13
+ @result = OpenStruct.new(tests: tests, contact_groups: contacts)
14
+
15
+ @tests = []
16
+ @contacts = []
17
+ instance_eval(&block)
18
+ end
19
+
20
+ private
21
+
22
+ def test(*args, &block)
23
+ test_id = nil
24
+ unless args.empty?
25
+ if @tests.include?(test_id)
26
+ raise "#{test_id} is already defined"
27
+ end
28
+ test_id = args.first
29
+ end
30
+
31
+ @result.tests << Test.new(@context, test_id, &block).result
32
+ @tests << test_id
33
+ end
34
+
35
+ def contact_group(*args, &block)
36
+ contact_id = nil
37
+ unless args.empty?
38
+ if @contacts.include?(contact_id)
39
+ raise "#{contact_id} is already defined"
40
+ end
41
+ contact_id = args.first
42
+ end
43
+
44
+ @result.contact_groups << ContactGroup.new(@context, contact_id, &block).result
45
+ @contacts << contact_id
46
+ end
47
+ end
48
+ end
49
+ end