hotdog 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 +7 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +85 -0
- data/Rakefile +2 -0
- data/bin/hotdog +7 -0
- data/hotdog.gemspec +27 -0
- data/lib/hotdog/application.rb +155 -0
- data/lib/hotdog/commands/destroy.rb +21 -0
- data/lib/hotdog/commands/gc.rb +18 -0
- data/lib/hotdog/commands/help.rb +16 -0
- data/lib/hotdog/commands/hosts.rb +42 -0
- data/lib/hotdog/commands/init.rb +44 -0
- data/lib/hotdog/commands/ls.rb +12 -0
- data/lib/hotdog/commands/rm.rb +19 -0
- data/lib/hotdog/commands/search.rb +278 -0
- data/lib/hotdog/commands/tags.rb +38 -0
- data/lib/hotdog/commands/update.rb +20 -0
- data/lib/hotdog/commands.rb +228 -0
- data/lib/hotdog/formatters/json.rb +15 -0
- data/lib/hotdog/formatters/plain.rb +46 -0
- data/lib/hotdog/formatters/yaml.rb +15 -0
- data/lib/hotdog/formatters.rb +13 -0
- data/lib/hotdog/version.rb +3 -0
- metadata +139 -0
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
data/Gemfile
ADDED
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
data/bin/hotdog
ADDED
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,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,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 :
|
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: []
|