hotdog 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: 953ecab33a4f48f426c3df8b0dcca49e861e5113
4
+ data.tar.gz: 81868bd92db6fddf876fd7eae71c54a2dd841bad
5
+ SHA512:
6
+ metadata.gz: 2763f5fa563fef343b799514436bde84dd886494577b42269118c4e82d999d1c8b3c289e50e3c2a02ba4fb8806c00fbd2a2f8abe4054abc90a9457684daad7d1
7
+ data.tar.gz: bc471b4a441f275d7fbbf6b2df8d706016de004c80a949fa86499db0d09ad174b708d5864248332d4c540d00731e146e6039f97d61c090422ac7125ea229504e
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.swo
13
+ *.swp
14
+ *.o
15
+ *.a
16
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org/"
2
+
3
+ # Specify your gem's dependencies in hotdog.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Yamashita Yuu
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,85 @@
1
+ # Hotdog
2
+
3
+ Yet another command-line tools for [Datadog](https://www.datadoghq.com/).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'hotdog'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```sh
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```
22
+ $ gem install hotdog
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ Setup environment variables of `DATADOG_API_KEY` and `DATADOG_APPLICATION_KEY`.
28
+ Then, create and initialize host information. This may take several minutes.
29
+
30
+ ```sh
31
+ $ hotdog init
32
+ $ hotdot update
33
+ ```
34
+
35
+ List all registered hosts.
36
+
37
+ ```sh
38
+ $ hotdog ls
39
+ i-02605a79
40
+ i-02d78cec
41
+ i-03cb56ed
42
+ i-03dabcef
43
+ i-069e282c
44
+ ```
45
+
46
+ List all registered hosts with associated tags and headers.
47
+
48
+ ```sh
49
+ $ hotdog ls -h -l
50
+ host security-group name availability-zone instance-type image region kernel
51
+ ---------- -------------- ----------------- ----------------- ------------- ------------ --------- ------------
52
+ i-02605a79 sg-89bfe710 web-staging us-east-1a m3.medium ami-66089cdf us-east-1 aki-89ab75e1
53
+ i-02d78cec sg-89bfe710 web-production us-east-1a c3.4xlarge ami-8bb3fc92 us-east-1 aki-89ab75e1
54
+ i-03cb56ed sg-89bfe710 web-production us-east-1b c3.4xlarge ami-8bb3fc92 us-east-1 aki-89ab75e1
55
+ i-03dabcef sg-89bfe710 worker-production us-east-1a c3.xlarge ami-4032c1c8 us-east-1 aki-89ab75e1
56
+ i-069e282c sg-89bfe710 worker-staging us-east-1a t2.micro ami-384c8480 us-east-1 aki-89ab75e1
57
+ ```
58
+
59
+ Display hosts with specific attributes.
60
+
61
+ ```sh
62
+ $ hotdog ls -a host -a name
63
+ host name
64
+ ---------- -----------------
65
+ i-02605a79 web-staging
66
+ i-02d78cec web-production
67
+ i-03cb56ed web-production
68
+ i-03dabcef worker-production
69
+ i-069e282c worker-staging
70
+ ```
71
+
72
+ Search hosts matching to specified tags and values.
73
+
74
+ ```sh
75
+ $ hotdog search availability-zone:us-east-1b and 'name:web-*'
76
+ i-03cb56ed
77
+ ```
78
+
79
+ ## Contributing
80
+
81
+ 1. Fork it ( https://github.com/yyuu/hotdog/fork )
82
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
83
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
84
+ 4. Push to the branch (`git push origin my-new-feature`)
85
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/hotdog ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.expand_path("../../lib", __FILE__))
3
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
4
+ require "bundler/setup"
5
+ require "hotdog/application"
6
+ dog = Hotdog::Application.new
7
+ dog.main(ARGV.dup)
data/hotdog.gemspec ADDED
@@ -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 "hotdog/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "hotdog"
8
+ spec.version = Hotdog::VERSION
9
+ spec.authors = ["Yamashita Yuu"]
10
+ spec.email = ["peek824545201@gmail.com"]
11
+ spec.summary = %q{Yet another command-line tool for Datadog}
12
+ spec.description = %q{Yet another command-line tool for Datadog}
13
+ spec.homepage = "https://github.com/yyuu/hotdog"
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_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+
24
+ spec.add_dependency "dogapi", "~> 1.13.0"
25
+ spec.add_dependency "parslet", "~> 1.6.2"
26
+ spec.add_dependency "sqlite3", "~> 1.3.10"
27
+ end
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "logger"
4
+ require "optparse"
5
+ require "shellwords"
6
+ require "sqlite3"
7
+ require "hotdog/commands"
8
+ require "hotdog/formatters"
9
+
10
+ module Hotdog
11
+ class Application
12
+ def initialize()
13
+ @confdir = File.join(ENV["HOME"], ".hotdog")
14
+ @optparse = OptionParser.new
15
+ @options = {
16
+ environment: "default",
17
+ minimum_expiry: 28800, # 8 hours
18
+ random_expiry: 57600, # 16 hours
19
+ force: false,
20
+ formatter: get_formatter("plain").new,
21
+ headers: false,
22
+ listing: false,
23
+ logger: Logger.new(STDERR),
24
+ api_key: ENV["DATADOG_API_KEY"],
25
+ application_key: ENV["DATADOG_APPLICATION_KEY"],
26
+ print0: false,
27
+ print1: true,
28
+ tags: [],
29
+ }
30
+ @options[:logger].level = Logger::INFO
31
+ define_options
32
+ end
33
+ attr_reader :options
34
+
35
+ def main(argv=[])
36
+ config = File.join(@confdir, "config.yml")
37
+ if File.file?(config)
38
+ @options = @options.merge(YAML.load(File.read(config)))
39
+ end
40
+ args = @optparse.parse(argv)
41
+
42
+ unless options[:api_key]
43
+ raise("DATADOG_API_KEY is not set")
44
+ end
45
+
46
+ unless options[:application_key]
47
+ raise("DATADOG_APPLICATION_KEY is not set")
48
+ end
49
+
50
+ sqlite = File.expand_path(File.join(@confdir, "#{options[:environment]}.db"))
51
+ FileUtils.mkdir_p(File.dirname(sqlite))
52
+ @db = SQLite3::Database.new(sqlite)
53
+ @db.synchronous = "off"
54
+
55
+ begin
56
+ command = ( args.shift || "help" )
57
+ run_command(command, args)
58
+ rescue Errno::EPIPE
59
+ # nop
60
+ end
61
+ end
62
+
63
+ def run_command(command, args=[])
64
+ get_command(command).new(@db, options.merge(application: self)).run(args)
65
+ end
66
+
67
+ private
68
+ def define_options
69
+ @optparse.on("--api-key API_KEY") do |api_key|
70
+ options[:api_key] = api_key
71
+ end
72
+ @optparse.on("--application-key APP_KEY") do |app_key|
73
+ options[:application_key] = app_key
74
+ end
75
+ @optparse.on("-0", "--null") do
76
+ options[:print0] = true
77
+ end
78
+ @optparse.on("-1") do
79
+ options[:print1] = true
80
+ end
81
+ @optparse.on("-d", "--debug") do
82
+ options[:logger].level = Logger::DEBUG
83
+ end
84
+ @optparse.on("-E ENVIRONMENT", "--environment ENVIRONMENT") do |environment|
85
+ options[:environment] = environment
86
+ end
87
+ @optparse.on("-f", "--force") do
88
+ options[:force] = true
89
+ end
90
+ @optparse.on("-F FORMAT", "--format FORMAT") do |format|
91
+ options[:formatter] = get_formatter(format).new
92
+ end
93
+ @optparse.on("-h", "--headers") do |headers|
94
+ options[:headers] = headers
95
+ end
96
+ @optparse.on("-l") do
97
+ options[:listing] = true
98
+ end
99
+ @optparse.on("-a TAG", "-t TAG", "--tag TAG") do |tag|
100
+ options[:tags] += [tag]
101
+ end
102
+ @optparse.on("-V", "--verbose") do |tag|
103
+ options[:logger].level = Logger::DEBUG
104
+ end
105
+ end
106
+
107
+ def const_name(name)
108
+ name.to_s.split(/[^\w]+/).map { |s| s.capitalize }.join
109
+ end
110
+
111
+ def get_formatter(name)
112
+ begin
113
+ Hotdog::Formatters.const_get(const_name(name))
114
+ rescue NameError
115
+ if library = find_library("hotdog/formatters", name)
116
+ load library
117
+ Hotdog::Formatters.const_get(const_name(File.basename(library, ".rb")))
118
+ else
119
+ raise(NameError.new("unknown format: #{name}"))
120
+ end
121
+ end
122
+ end
123
+
124
+ def get_command(name)
125
+ begin
126
+ Hotdog::Commands.const_get(const_name(name))
127
+ rescue NameError
128
+ if library = find_library("hotdog/commands", name)
129
+ load library
130
+ Hotdog::Commands.const_get(const_name(File.basename(library, ".rb")))
131
+ else
132
+ raise(NameError.new("unknown command: #{name}"))
133
+ end
134
+ end
135
+ end
136
+
137
+ def find_library(dirname, name)
138
+ load_path = $LOAD_PATH.map { |path| File.join(path, dirname) }.select { |path| File.directory?(path) }
139
+ libraries = load_path.map { |path| Dir.glob(File.join(path, "*.rb")) }.reduce(:+).select { |file| File.file?(file) }
140
+ rbname = "#{name}.rb"
141
+ if library = libraries.find { |file| File.basename(file) == rbname }
142
+ library
143
+ else
144
+ candidates = libraries.map { |file| [file, File.basename(file).slice(0, name.length)] }.select { |file, s| s == name }
145
+ if candidates.length == 1
146
+ candidates.first.first
147
+ else
148
+ nil
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ # vim:set ft=ruby :
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Hotdog
4
+ module Commands
5
+ class Destroy < BaseCommand
6
+ def run(args=[])
7
+ execute(<<-EOS)
8
+ DROP TABLE IF EXISTS hosts;
9
+ EOS
10
+ execute(<<-EOS)
11
+ DROP TABLE IF EXISTS tags;
12
+ EOS
13
+ execute(<<-EOS)
14
+ DROP TABLE IF EXISTS hosts_tags;
15
+ EOS
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ # vim:set ft=ruby :
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Hotdog
4
+ module Commands
5
+ class Gc < BaseCommand
6
+ def run(args=[])
7
+ execute(<<-EOS)
8
+ DELETE FROM hosts WHERE id NOT IN ( SELECT DISTINCT host_id FROM hosts_tags );
9
+ EOS
10
+ execute(<<-EOS)
11
+ DELETE FROM tags WHERE id NOT IN ( SELECT DISTINCT tag_id FROM hosts_tags );
12
+ EOS
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ # vim:set ft=ruby :
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rbconfig"
4
+
5
+ module Hotdog
6
+ module Commands
7
+ class Help < BaseCommand
8
+ def run(args=[])
9
+ ruby = File.join(RbConfig::CONFIG["bindir"], RbConfig::CONFIG["ruby_install_name"])
10
+ exit(system(ruby, $0, "--help") ? 0 : 1)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ # vim:set ft=ruby :
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Hotdog
4
+ module Commands
5
+ class Hosts < BaseCommand
6
+ def run(args=[])
7
+ update_hosts(@options.dup)
8
+
9
+ if args.empty?
10
+ result = execute(<<-EOS).map { |row| row.first }
11
+ SELECT DISTINCT host_id FROM hosts_tags;
12
+ EOS
13
+ else
14
+ result = args.map { |host_name|
15
+ if host_name.index("*")
16
+ execute(<<-EOS, host_name).map { |row| row.first }
17
+ SELECT DISTINCT hosts_tags.host_id FROM hosts_tags
18
+ INNER JOIN hosts ON hosts_tags.host_id = hosts.id
19
+ WHERE LOWER(hosts.name) GLOB LOWER(?);
20
+ EOS
21
+ else
22
+ execute(<<-EOS, host_name).map { |row| row.first }
23
+ SELECT DISTINCT hosts_tags.host_id FROM hosts_tags
24
+ INNER JOIN hosts ON hosts_tags.host_id = hosts.id
25
+ WHERE LOWER(hosts.name) = LOWER(?);
26
+ EOS
27
+ end
28
+ }.reduce(:+)
29
+ end
30
+ if 0 < result.length
31
+ result, fields = get_hosts(result)
32
+ STDOUT.puts(format(result, fields: fields))
33
+ else
34
+ STDERR.puts("no match found: #{args.join(" ")}")
35
+ exit(1)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ # vim:set ft=ruby :
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Hotdog
4
+ module Commands
5
+ class Init < BaseCommand
6
+ def run(args=[])
7
+ execute(<<-EOS)
8
+ CREATE TABLE IF NOT EXISTS hosts (
9
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
10
+ name VARCHAR(255) NOT NULL
11
+ );
12
+ EOS
13
+ execute(<<-EOS)
14
+ CREATE UNIQUE INDEX IF NOT EXISTS hosts_name ON hosts ( name );
15
+ EOS
16
+ execute(<<-EOS)
17
+ CREATE TABLE IF NOT EXISTS tags (
18
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
19
+ name VARCHAR(200) NOT NULL,
20
+ value VARCHAR(200) NOT NULL DEFAULT ""
21
+ );
22
+ EOS
23
+ execute(<<-EOS)
24
+ CREATE UNIQUE INDEX IF NOT EXISTS tags_name_value ON tags ( name, value );
25
+ EOS
26
+ execute(<<-EOS)
27
+ CREATE TABLE IF NOT EXISTS hosts_tags (
28
+ host_id INTEGER NOT NULL,
29
+ tag_id INTEGER NOT NULL,
30
+ expires_at INTEGER NOT NULL
31
+ );
32
+ EOS
33
+ execute(<<-EOS)
34
+ CREATE UNIQUE INDEX IF NOT EXISTS hosts_tags_host_id_tag_id ON hosts_tags ( host_id, tag_id );
35
+ EOS
36
+ execute(<<-EOS)
37
+ CREATE INDEX IF NOT EXISTS hosts_tags_expires_at ON hosts_tags ( expires_at );
38
+ EOS
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ # vim:set ft=ruby :
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "hotdog/commands/hosts"
4
+
5
+ module Hotdog
6
+ module Commands
7
+ class Ls < Hosts
8
+ end
9
+ end
10
+ end
11
+
12
+ # vim:set ft=ruby :
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Hotdog
4
+ module Commands
5
+ class Rm < BaseCommand
6
+ def run(args=[])
7
+ execute(<<-EOS % args.map { "?" }.join(", "), args).map { |row| row.first }
8
+ DELETE FROM hosts_tags
9
+ WHERE host_id IN
10
+ ( SELECT hosts_tags.host_id FROM hosts_tags
11
+ INNER JOIN hosts ON hosts_tags.host_id = hosts.id
12
+ WHERE hosts.name NOT IN (%s) );
13
+ EOS
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ # vim:set ft=ruby :
@@ -0,0 +1,278 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "json"
4
+ require "parslet"
5
+
6
+ module Hotdog
7
+ module Commands
8
+ class Search < BaseCommand
9
+ def run(args=[])
10
+ expression = args.join(" ").strip
11
+ if expression.empty?
12
+ exit(1)
13
+ end
14
+
15
+ update_hosts(@options.dup)
16
+ # update_tags(@options.dup)
17
+
18
+ begin
19
+ node = parse(expression)
20
+ rescue Parslet::ParseFailed => error
21
+ STDERR.puts("syntax error: " + error.cause.ascii_tree)
22
+ exit(1)
23
+ end
24
+ result = evaluate(node, self).sort
25
+ if 0 < result.length
26
+ result, fields = get_hosts(result)
27
+ STDOUT.puts(format(result, fields: fields))
28
+ else
29
+ STDERR.puts("no match found: #{args.join(" ")}")
30
+ exit(1)
31
+ end
32
+ end
33
+
34
+ def parse(expression)
35
+ parser = ExpressionParser.new
36
+ parser.parse(expression).tap do |parsed|
37
+ logger.debug(JSON.pretty_generate(JSON.load(parsed.to_json)))
38
+ end
39
+ end
40
+
41
+ def evaluate(node, environment)
42
+ node = ExpressionTransformer.new.apply(node)
43
+ node.evaluate(environment)
44
+ end
45
+
46
+ class ExpressionParser < Parslet::Parser
47
+ root(:expression)
48
+ rule(:expression) {
49
+ ( binary_expression \
50
+ | term \
51
+ )
52
+ }
53
+ rule(:binary_expression) {
54
+ ( term.as(:left) >> spacing.maybe >> (str('&') >> str('&').maybe).as(:binary_op) >> spacing.maybe >> expression.as(:right) \
55
+ | term.as(:left) >> spacing.maybe >> (str('|') >> str('|').maybe).as(:binary_op) >> spacing.maybe >> expression.as(:right) \
56
+ | term.as(:left) >> spacing.maybe >> (match('[Aa]') >> match('[Nn]') >> match('[Dd]')).as(:binary_op) >> spacing.maybe >> expression.as(:right) \
57
+ | term.as(:left) >> spacing.maybe >> (match('[Oo]') >> match('[Rr]')).as(:binary_op) >> spacing.maybe >> expression.as(:right) \
58
+ )
59
+ }
60
+ rule(:unary_expression) {
61
+ ( spacing.maybe >> str('!').as(:unary_op) >> atom.as(:expression) \
62
+ | spacing.maybe >> str('~').as(:unary_op) >> atom.as(:expression) \
63
+ | spacing.maybe >> str('not').as(:unary_op) >> atom.as(:expression) \
64
+ )
65
+ }
66
+ rule(:term) {
67
+ ( unary_expression \
68
+ | atom \
69
+ )
70
+ }
71
+ rule(:atom) {
72
+ ( spacing.maybe >> str('(') >> expression >> str(')') >> spacing.maybe \
73
+ | spacing.maybe >> identifier_regexp.as(:identifier_regexp) >> str(':') >> attribute_regexp.as(:attribute_regexp) >> spacing.maybe \
74
+ | spacing.maybe >> identifier_regexp.as(:identifier_regexp) >> spacing.maybe \
75
+ | spacing.maybe >> identifier_glob.as(:identifier_glob) >> str(':') >> attribute_glob.as(:attribute_glob) >> spacing.maybe \
76
+ | spacing.maybe >> identifier_glob.as(:identifier_glob) >> str(':') >> attribute.as(:attribute) >> spacing.maybe \
77
+ | spacing.maybe >> identifier_glob.as(:identifier_glob) >> spacing.maybe \
78
+ | spacing.maybe >> identifier.as(:identifier)>> str(':') >> attribute_glob.as(:attribute_glob) >> spacing.maybe \
79
+ | spacing.maybe >> identifier.as(:identifier)>> str(':') >> attribute.as(:attribute) >> spacing.maybe \
80
+ | spacing.maybe >> identifier.as(:identifier) >> spacing.maybe \
81
+ )
82
+ }
83
+ rule(:identifier_regexp) {
84
+ ( str('/') >> (str('/').absent? >> any).repeat(0) >> str('/') \
85
+ )
86
+ }
87
+ rule(:identifier_glob) {
88
+ ( identifier.repeat(0) >> (glob >> identifier.maybe).repeat(1) \
89
+ )
90
+ }
91
+ rule(:identifier) {
92
+ ( match('[A-Za-z]') >> match('[-./0-9A-Z_a-z]').repeat(0) \
93
+ )
94
+ }
95
+ rule(:attribute_regexp) {
96
+ ( str('/') >> (str('/').absent? >> any).repeat(0) >> str('/') \
97
+ )
98
+ }
99
+ rule(:attribute_glob) {
100
+ ( attribute.repeat(0) >> (glob >> attribute.maybe).repeat(1) \
101
+ )
102
+ }
103
+ rule(:attribute) {
104
+ ( match('[-./0-9:A-Z_a-z]').repeat(1) \
105
+ )
106
+ }
107
+ rule(:glob) {
108
+ ( str('*') | str('?') | str('[') | str(']') )
109
+ }
110
+ rule(:spacing) {
111
+ ( match('[\t\n\r ]').repeat(1) \
112
+ )
113
+ }
114
+ end
115
+
116
+ class ExpressionTransformer < Parslet::Transform
117
+ rule(:binary_op => simple(:binary_op), :left => simple(:left), :right => simple(:right)) {
118
+ BinaryExpressionNode.new(binary_op, left, right)
119
+ }
120
+ rule(:unary_op => simple(:unary_op), :expression => simple(:expression)) {
121
+ UnaryExpressionNode.new(unary_op, expression)
122
+ }
123
+ rule(:identifier_regexp => simple(:identifier_regexp), :attribute_regexp => simple(:attribute_regexp)) {
124
+ TagRegexpExpressionNode.new(identifier_regexp.to_s, attribute_regexp.to_s)
125
+ }
126
+ rule(:identifier_regexp => simple(:identifier_regexp)) {
127
+ TagRegexpExpressionNode.new(identifier_regexp.to_s, nil)
128
+ }
129
+ rule(:identifier_glob => simple(:identifier_glob), :attribute_glob => simple(:attribute_glob)) {
130
+ TagGlobExpressionNode.new(identifier_glob.to_s, attribute_glob.to_s)
131
+ }
132
+ rule(:identifier_glob => simple(:identifier_glob), :attribute => simple(:attribute)) {
133
+ TagGlobExpressionNode.new(identifier_glob.to_s, attribute.to_s)
134
+ }
135
+ rule(:identifier_glob => simple(:identifier_glob)) {
136
+ TagGlobExpressionNode.new(identifier_glob.to_s, nil)
137
+ }
138
+ rule(:identifier => simple(:identifier), :attribute_glob => simple(:attribute_glob)) {
139
+ TagGlobExpressionNode.new(identifier.to_s, attribute_glob.to_s)
140
+ }
141
+ rule(:identifier => simple(:identifier), :attribute => simple(:attribute)) {
142
+ TagExpressionNode.new(identifier.to_s, attribute.to_s)
143
+ }
144
+ rule(:identifier => simple(:identifier)) {
145
+ TagExpressionNode.new(identifier.to_s, nil)
146
+ }
147
+ end
148
+
149
+ class ExpressionNode
150
+ def evaluate(environment)
151
+ raise(NotImplementedError)
152
+ end
153
+ end
154
+
155
+ class BinaryExpressionNode < ExpressionNode
156
+ def initialize(op, left, right)
157
+ @op = op
158
+ @left = left
159
+ @right = right
160
+ end
161
+ def evaluate(environment)
162
+ case @op
163
+ when "&&", "&", /\Aand\z/i
164
+ left_values = @left.evaluate(environment)
165
+ if left_values.empty?
166
+ []
167
+ else
168
+ (left_values & @right.evaluate(environment)).uniq
169
+ end
170
+ when "||", "|", /\Aor\z/i
171
+ left_values = @left.evaluate(environment)
172
+ (left_values | @right.evaluate(environment)).uniq
173
+ else
174
+ raise(SyntaxError.new("unknown binary operator: #{@op}"))
175
+ end
176
+ end
177
+ end
178
+
179
+ class UnaryExpressionNode < ExpressionNode
180
+ def initialize(op, expression)
181
+ @op = op
182
+ @expression = expression
183
+ end
184
+ def evaluate(environment)
185
+ case @op
186
+ when "!", "~", /\Anot\z/i
187
+ values = @expression.evaluate(environment)
188
+ if values.empty?
189
+ environment.execute(<<-EOS).map { |row| row.first }
190
+ SELECT DISTINCT host_id FROM hosts_tags;
191
+ EOS
192
+ else
193
+ environment.execute(<<-EOS % values.map { "?" }.join(", "), values).map { |row| row.first }
194
+ SELECT DISTINCT host_id FROM hosts_tags WHERE host_id NOT IN (%s);
195
+ EOS
196
+ end
197
+ else
198
+ raise(SyntaxError.new("unknown unary operator: #{@op}"))
199
+ end
200
+ end
201
+ end
202
+
203
+ class TagExpressionNode < ExpressionNode
204
+ def initialize(identifier, attribute)
205
+ @identifier = identifier
206
+ @attribute = attribute
207
+ end
208
+ attr_reader :identifier
209
+ attr_reader :attribute
210
+ def attribute?
211
+ !attribute.nil?
212
+ end
213
+ def evaluate(environment)
214
+ if attribute?
215
+ environment.execute(<<-EOS, identifier, attribute).map { |row| row.first }
216
+ SELECT DISTINCT hosts_tags.host_id FROM hosts_tags
217
+ INNER JOIN tags ON hosts_tags.tag_id = tags.id
218
+ WHERE LOWER(tags.name) = LOWER(?) AND LOWER(tags.value) = LOWER(?);
219
+ EOS
220
+ else
221
+ environment.execute(<<-EOS, identifier, identifier, identifier).map { |row| row.first }
222
+ SELECT DISTINCT hosts_tags.host_id FROM hosts_tags
223
+ INNER JOIN hosts ON hosts_tags.host_id = hosts.id
224
+ INNER JOIN tags ON hosts_tags.tag_id = tags.id
225
+ WHERE LOWER(hosts.name) = LOWER(?) OR LOWER(tags.name) = LOWER(?) OR LOWER(tags.value) = LOWER(?);
226
+ EOS
227
+ end
228
+ end
229
+ end
230
+
231
+ class TagGlobExpressionNode < TagExpressionNode
232
+ def evaluate(environment)
233
+ if attribute?
234
+ environment.execute(<<-EOS, identifier, attribute).map { |row| row.first }
235
+ SELECT DISTINCT hosts_tags.host_id FROM hosts_tags
236
+ INNER JOIN tags ON hosts_tags.tag_id = tags.id
237
+ WHERE LOWER(tags.name) GLOB LOWER(?) AND LOWER(tags.value) GLOB LOWER(?);
238
+ EOS
239
+ else
240
+ environment.execute(<<-EOS, identifier, identifier, identifier).map { |row| row.first }
241
+ SELECT DISTINCT hosts_tags.host_id FROM hosts_tags
242
+ INNER JOIN hosts ON hosts_tags.host_id = hosts.id
243
+ INNER JOIN tags ON hosts_tags.tag_id = tags.id
244
+ WHERE LOWER(hosts.name) GLOB LOWER(?) OR LOWER(tags.name) GLOB LOWER(?) OR LOWER(tags.value) GLOB LOWER(?);
245
+
246
+ EOS
247
+ end
248
+ end
249
+ end
250
+
251
+ class TagRegexpExpressionNode < TagExpressionNode
252
+ def initialize(identifier, attribute)
253
+ identifier = identifier.sub(%r{\A/(.*)/\z}) { $1 } if identifier
254
+ attribute = attribute.sub(%r{\A/(.*)/\z}) { $1 } if attribute
255
+ super(identifier, attribute)
256
+ end
257
+ def evaluate(environment)
258
+ if attribute?
259
+ environment.execute(<<-EOS, identifier, attribute).map { |row| row.first }
260
+ SELECT DISTINCT hosts_tags.host_id FROM hosts_tags
261
+ INNER JOIN tags ON hosts_tags.tag_id = tags.id
262
+ WHERE LOWER(tags.name) REGEXP LOWER(?) AND LOWER(tags.value) REGEXP LOWER(?);
263
+ EOS
264
+ else
265
+ environment.execute(<<-EOS, identifier, identifier, identifier).map { |row| row.first }
266
+ SELECT DISTINCT hosts_tags.host_id FROM hosts_tags
267
+ INNER JOIN hosts ON hosts_tags.host_id = hosts.id
268
+ INNER JOIN tags ON hosts_tags.tag_id = tags.id
269
+ WHERE LOWER(hosts.name) REGEXP LOWER(?) OR LOWER(tags.name) REGEXP LOWER(?) OR LOWER(tags.value) REGEXP LOWER(?);
270
+ EOS
271
+ end
272
+ end
273
+ end
274
+ end
275
+ end
276
+ end
277
+
278
+ # vim:set ft=ruby :
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Hotdog
4
+ module Commands
5
+ class Tags < BaseCommand
6
+ def run(args=[])
7
+ update_tags(@options.dup)
8
+ if 0 < tags.length
9
+ fields = tags.map { |tag|
10
+ tag_name, tag_value = tag.split(":", 2)
11
+ tag_name
12
+ }
13
+ result1 = fields.map { |tag_name|
14
+ execute(<<-EOS, tag_name).map { |row| row.join(",") }
15
+ SELECT DISTINCT tags.value FROM hosts_tags
16
+ INNER JOIN tags ON hosts_tags.tag_id = tags.id
17
+ WHERE tags.name = LOWER(?);
18
+ EOS
19
+ }
20
+ result = (0..result1.reduce(0) { |max, values| [max, values.length].max }).map { |field_index|
21
+ result1.map { |values| values[field_index] }
22
+ }
23
+ else
24
+ fields = ["tag"]
25
+ result = execute(<<-EOS).map { |name, value| [0 < value.length ? "#{name}:#{value}" : name] }
26
+ SELECT tags.name, tags.value FROM hosts_tags
27
+ INNER JOIN tags ON hosts_tags.tag_id = tags.id;
28
+ EOS
29
+ end
30
+ if 0 < result.length
31
+ STDOUT.puts(format(result, fields: fields))
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ # vim:set ft=ruby :
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Hotdog
4
+ module Commands
5
+ class Update < BaseCommand
6
+ def run(args=[])
7
+ application.run_command("init")
8
+ if 0 < args.length
9
+ args.each do |host_name|
10
+ update_host_tags(host_name, @options.dup)
11
+ end
12
+ else
13
+ update_tags(@options.dup)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ # vim:set ft=ruby :
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "dogapi"
4
+ require "logger"
5
+
6
+ module Hotdog
7
+ module Commands
8
+ class BaseCommand
9
+ def initialize(db, options={})
10
+ @db = db
11
+ @formatter = options[:formatter]
12
+ @logger = options[:logger]
13
+ @tags = options[:tags]
14
+ @application = options[:application]
15
+ @options = options
16
+ @dog = Dogapi::Client.new(options[:api_key], options[:application_key])
17
+ end
18
+ attr_reader :application
19
+ attr_reader :formatter
20
+ attr_reader :logger
21
+ attr_reader :tags
22
+ attr_reader :options
23
+
24
+ def run(args=[])
25
+ raise(NotImplementedError)
26
+ end
27
+
28
+ def execute(query, *args)
29
+ q = query.strip
30
+ if 0 < args.length
31
+ q += " -- VALUES (#{args.map { |arg| Array === arg ? "(#{arg.join(", ")})" : arg.inspect }.join(", ")})"
32
+ end
33
+ logger.debug(q)
34
+ @db.execute(query, args)
35
+ end
36
+
37
+ private
38
+ def format(result, options={})
39
+ @formatter.format(result, @options.merge(options))
40
+ end
41
+
42
+ def get_hosts(hosts=[])
43
+ if 0 < tags.length
44
+ result = hosts.map { |host_id|
45
+ update_host_tags(host_id, @options.merge(tags: tags))
46
+ tags.map { |tag|
47
+ tag_name, tag_value = tag.split(":", 2)
48
+ if tag_name == "host"
49
+ logger.debug("get_hosts_q1()")
50
+ @get_hosts_q1 ||= @db.prepare(<<-EOS)
51
+ SELECT name FROM hosts WHERE id = ? LIMIT 1;
52
+ EOS
53
+ @get_hosts_q1.execute(host_id).map { |row| row.first }.join(",")
54
+ else
55
+ logger.debug("get_hosts_q2()")
56
+ @get_hosts_q2 ||= @db.prepare(<<-EOS)
57
+ SELECT tags.value FROM hosts_tags
58
+ INNER JOIN tags ON hosts_tags.tag_id = tags.id
59
+ WHERE hosts_tags.host_id = ? AND tags.name = ?;
60
+ EOS
61
+ @get_hosts_q2.execute(host_id, tag_name).map { |row| row.first }.join(",")
62
+ end
63
+ }
64
+ }
65
+ fields = tags
66
+ else
67
+ if options[:listing]
68
+
69
+ fields = []
70
+ hosts = execute(<<-EOS % hosts.map { "?" }.join(", "), hosts)
71
+ SELECT id, name FROM hosts WHERE id IN (%s) ORDER BY name;
72
+ EOS
73
+ result = hosts.map { |host_id, host_name|
74
+ update_host_tags(host_name, @options.dup)
75
+ logger.debug("get_hosts_q3()")
76
+ @get_hosts_q3 ||= @db.prepare(<<-EOS)
77
+ SELECT DISTINCT tags.name FROM hosts_tags
78
+ INNER JOIN tags ON hosts_tags.tag_id = tags.id
79
+ WHERE hosts_tags.host_id = ?;
80
+ EOS
81
+ tag_names = @get_hosts_q3.execute(host_id).map { |row| row.first }
82
+ tag_names.each do |tag_name|
83
+ fields << tag_name unless fields.index(tag_name)
84
+ end
85
+ [host_name] + fields.map { |tag_name|
86
+ @get_hosts_q4 ||= @db.prepare(<<-EOS)
87
+ SELECT tags.value FROM hosts_tags
88
+ INNER JOIN tags ON hosts_tags.tag_id = tags.id
89
+ WHERE hosts_tags.host_id = ? AND tags.name = ?;
90
+ EOS
91
+ logger.debug("get_hosts_q4(%s, %s)" % [host_id.inspect, tag_name.inspect])
92
+ @get_hosts_q4.execute(host_id, tag_name).map { |row| row.first }.join(",")
93
+ }
94
+ }
95
+ fields = ["host"] + fields
96
+ else
97
+ fields = ["host"]
98
+ result = execute(<<-EOS % hosts.map { "?" }.join(", "), hosts)
99
+ SELECT name FROM hosts WHERE id IN (%s) ORDER BY name;
100
+ EOS
101
+ end
102
+ end
103
+ [result, fields]
104
+ end
105
+
106
+ def update_hosts(options={})
107
+ @db.transaction do
108
+ if not options[:force]
109
+ @update_hosts_q1 ||= @db.prepare("SELECT MIN(expires_at) FROM hosts_tags;")
110
+ logger.debug("update_hosts_q1()")
111
+ if expires_at = @update_hosts_q1.execute().map { |row| row.first }.first
112
+ if Time.new.to_i < expires_at
113
+ return
114
+ else
115
+ logger.debug("minimum expires_at was %s. start updateing." % [Time.at(expires_at)])
116
+ end
117
+ else
118
+ logger.debug("expires_at not found. start updateing.")
119
+ end
120
+ end
121
+
122
+ code, result = @dog.search("hosts:")
123
+ if code.to_i / 100 != 2
124
+ raise("HTTP #{code}: #{result.inspect}")
125
+ end
126
+
127
+ result["results"]["hosts"].each do |host_name|
128
+ @update_hosts_q2 ||= @db.prepare("INSERT OR IGNORE INTO hosts (name) VALUES (?);")
129
+ logger.debug("update_hosts_q2(%s)" % [host_name.inspect])
130
+ @update_hosts_q2.execute("INSERT OR IGNORE INTO hosts (name) VALUES (?);", host_name)
131
+ update_host_tags(host_name, options)
132
+ end
133
+
134
+ execute(<<-EOS % result["results"]["hosts"].map { "LOWER(?)" }.join(", "), result["results"]["hosts"])
135
+ DELETE FROM hosts_tags WHERE host_id NOT IN
136
+ ( SELECT id FROM hosts WHERE name IN ( %s ) );
137
+ EOS
138
+ end
139
+ end
140
+
141
+ def update_tags(options={})
142
+ @db.transaction do
143
+ if options[:force]
144
+ @update_tags_q1 ||= @db.prepare(<<-EOS)
145
+ SELECT DISTINCT hosts_tags.host_id FROM hosts_tags;
146
+ EOS
147
+ logger.debug("update_tags_q1()")
148
+ hosts = @update_tags_q1.execute().map { |row| row.first }
149
+ else
150
+ logger.debug("update_tags_q2()")
151
+ @update_tags_q2 ||= @db.prepare(<<-EOS)
152
+ SELECT DISTINCT hosts_tags.host_id FROM hosts_tags
153
+ WHERE hosts_tags.expires_at < ?;
154
+ EOS
155
+ hosts = @update_tags_q2.execute(Time.new.to_i)
156
+ end
157
+ hosts.each do |host_name|
158
+ update_host_tags(host_name, options)
159
+ end
160
+ end
161
+ end
162
+
163
+ def update_host_tags(host_name, options={})
164
+ if Integer === host_name
165
+ host_id = host_name
166
+ @update_host_tags_q1 ||= @db.prepare("SELECT name FROM hosts WHERE id = ? LIMIT 1;")
167
+ logger.debug("update_host_tags_q1(%s)" % [host_id.inspect])
168
+ host_name = @update_host_tags_q1.execute(host_id).map { |row| row.first }.first
169
+ else
170
+ @update_host_tags_q2 ||= @db.prepare("SELECT id FROM hosts WHERE LOWER(name) = LOWER(?) LIMIT 1;")
171
+ logger.debug("update_host_tags_q2(%s)" % [host_name.inspect])
172
+ host_id = @update_host_tags_q2.execute(host_name).map { |row| row.first }.first
173
+ end
174
+
175
+ if not options[:force]
176
+ @update_host_tags_q3 ||= @db.prepare("SELECT MIN(expires_at) FROM hosts_tags WHERE host_id = ?;")
177
+ logger.debug("update_host_tags_q3(%s)" % [host_id.inspect])
178
+ if expires_at = @update_host_tags_q3.execute(host_id).map { |row| row.first }.first
179
+ if Time.new.to_i < expires_at
180
+ return
181
+ else
182
+ logger.debug("%s: minimum expires_at was %s. start updating." % [host_name, Time.at(expires_at)])
183
+ end
184
+ else
185
+ logger.debug("%s: expires_at not found. start updateing." % [host_name])
186
+ end
187
+ end
188
+
189
+ code, result = @dog.host_tags(host_name)
190
+ if code.to_i / 100 != 2
191
+ raise("HTTP #{code}: #{result.inspect}")
192
+ end
193
+
194
+ expires_at = Time.new.to_i + (options[:minimum_expiry] + rand(options[:random_expiry]))
195
+ logger.debug("%s: expires_at=%s" % [host_name, Time.at(expires_at)])
196
+
197
+ result["tags"].each do |tag|
198
+ tag_name, tag_value = tag.split(":", 2)
199
+ tag_value ||= ""
200
+
201
+ if options.has_key?(:tags) and not options[:tags].empty? and not options[:tags].index(tag_name)
202
+ next
203
+ else
204
+ @update_host_tags_q4 ||= @db.prepare("INSERT OR IGNORE INTO tags (name, value) VALUES (?, ?);")
205
+ logger.debug("update_host_tags_q4(%s, %s)" % [tag_name.inspect, tag_value.inspect])
206
+ @update_host_tags_q4.execute(tag_name, tag_value)
207
+ @update_host_tags_q5 ||= @db.prepare(<<-EOS)
208
+ INSERT OR REPLACE INTO hosts_tags (host_id, tag_id, expires_at)
209
+ SELECT host.id, tag.id, ? FROM
210
+ ( SELECT id FROM hosts WHERE name = ? ) AS host,
211
+ ( SELECT id FROM tags WHERE name = ? AND value = ? ) AS tag;
212
+ EOS
213
+ logger.debug("update_host_tags_q5(%s, %s)" % [expires_at, host_name, tag_name, tag_value])
214
+ @update_host_tags_q5.execute(expires_at, host_name, tag_name, tag_value)
215
+ end
216
+ end
217
+
218
+ @update_host_tags_q6 ||= @db.prepare(<<-EOS)
219
+ DELETE FROM hosts_tags WHERE host_id = ? and expires_at <= ?;
220
+ EOS
221
+ logger.debug("update_host_tags_q6(%s, %s)" % [host_id.inspect, Time.new.to_i.inspect])
222
+ @update_host_tags_q6.execute(host_id, Time.new.to_i)
223
+ end
224
+ end
225
+ end
226
+ end
227
+
228
+ # vim:set ft=ruby :
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "json"
4
+
5
+ module Hotdog
6
+ module Formatters
7
+ class Json < BaseFormatter
8
+ def format(result, options={})
9
+ JSON.pretty_generate(result)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ # vim:set ft=ruby :
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Hotdog
4
+ module Formatters
5
+ class Plain < BaseFormatter
6
+ def format(result, options={})
7
+ if options[:print0]
8
+ sep = "\0"
9
+ elsif options[:print1]
10
+ sep = "\n"
11
+ else
12
+ sep = " "
13
+ end
14
+ if options[:print1] and options[:headers] and options[:fields]
15
+ field_length = (0...result.last.length).map { |field_index|
16
+ result.reduce(0) { |length, row|
17
+ [length, row[field_index].to_s.length, options[:fields][field_index].to_s.length].max
18
+ }
19
+ }
20
+ header_fields = options[:fields].zip(field_length).map { |field, length|
21
+ field.to_s + (" " * (length - field.length))
22
+ }
23
+ result = [
24
+ header_fields,
25
+ header_fields.map { |field|
26
+ "-" * field.length
27
+ },
28
+ ] + result.map { |row|
29
+ row.zip(field_length).map { |field, length|
30
+ field.to_s + (" " * (length - field.length))
31
+ }
32
+ }
33
+ end
34
+ _format(result, sep, options)
35
+ end
36
+
37
+ def _format(result, sep, options={})
38
+ result.map { |row|
39
+ row.join(" ")
40
+ }.join(sep)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ # vim:set ft=ruby :
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "yaml"
4
+
5
+ module Hotdog
6
+ module Formatters
7
+ class Yaml < BaseFormatter
8
+ def format(result, options={})
9
+ result.to_yaml
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ # vim:set ft=ruby :
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Hotdog
4
+ module Formatters
5
+ class BaseFormatter
6
+ def format(result, options={})
7
+ raise(NotImplementedError)
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ # vim:set ft=ruby :
@@ -0,0 +1,3 @@
1
+ module Hotdog
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hotdog
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Yamashita Yuu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dogapi
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.13.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.13.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: parslet
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.6.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.6.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.3.10
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.3.10
83
+ description: Yet another command-line tool for Datadog
84
+ email:
85
+ - peek824545201@gmail.com
86
+ executables:
87
+ - hotdog
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - bin/hotdog
97
+ - hotdog.gemspec
98
+ - lib/hotdog/application.rb
99
+ - lib/hotdog/commands.rb
100
+ - lib/hotdog/commands/destroy.rb
101
+ - lib/hotdog/commands/gc.rb
102
+ - lib/hotdog/commands/help.rb
103
+ - lib/hotdog/commands/hosts.rb
104
+ - lib/hotdog/commands/init.rb
105
+ - lib/hotdog/commands/ls.rb
106
+ - lib/hotdog/commands/rm.rb
107
+ - lib/hotdog/commands/search.rb
108
+ - lib/hotdog/commands/tags.rb
109
+ - lib/hotdog/commands/update.rb
110
+ - lib/hotdog/formatters.rb
111
+ - lib/hotdog/formatters/json.rb
112
+ - lib/hotdog/formatters/plain.rb
113
+ - lib/hotdog/formatters/yaml.rb
114
+ - lib/hotdog/version.rb
115
+ homepage: https://github.com/yyuu/hotdog
116
+ licenses:
117
+ - MIT
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 2.4.4
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: Yet another command-line tool for Datadog
139
+ test_files: []