hotdog 0.17.1 → 0.18.0
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 +4 -4
- data/lib/hotdog/application.rb +1 -1
- data/lib/hotdog/commands/down.rb +15 -8
- data/lib/hotdog/commands/pssh.rb +17 -8
- data/lib/hotdog/commands/scp.rb +0 -26
- data/lib/hotdog/commands/search.rb +10 -5
- data/lib/hotdog/commands/sftp.rb +0 -26
- data/lib/hotdog/commands/ssh.rb +38 -35
- data/lib/hotdog/commands/tag.rb +10 -9
- data/lib/hotdog/commands/untag.rb +16 -9
- data/lib/hotdog/commands/up.rb +6 -5
- data/lib/hotdog/commands.rb +155 -110
- data/lib/hotdog/expression/semantics.rb +9 -0
- data/lib/hotdog/expression/syntax.rb +1 -1
- data/lib/hotdog/version.rb +1 -1
- data/spec/core/commands_spec.rb +2 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a32689688c7824af117417f98913a377e5f8e273
|
|
4
|
+
data.tar.gz: e814b609418012c7192cd8ada949a12392651288
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b02bd4a8dc956cbcb74cf6854d6a540c197b2d4e96215b201aa4a06348d5ff6c9f3747edf4751ddda299f15b9400ebced4dd7ab11a498c7017d9a81d31a49ff
|
|
7
|
+
data.tar.gz: 062f80089aaa87daf0310578a728cceee13d9ad94d475fbb4be604553414831d42f0a8065a4a97896ed8a17d3f6d000f135948237cc8677d6a870d7ea6d7c5cf
|
data/lib/hotdog/application.rb
CHANGED
data/lib/hotdog/commands/down.rb
CHANGED
|
@@ -24,22 +24,29 @@ module Hotdog
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def run(args=[], options={})
|
|
27
|
-
args.
|
|
27
|
+
scopes = args.map { |arg|
|
|
28
28
|
if arg.index(":").nil?
|
|
29
|
-
|
|
29
|
+
"host:#{arg}"
|
|
30
30
|
else
|
|
31
|
-
|
|
31
|
+
arg
|
|
32
32
|
end
|
|
33
|
+
}
|
|
34
|
+
scopes.each do |scope|
|
|
33
35
|
with_retry(options) do
|
|
34
36
|
schedule_downtime(scope, options)
|
|
35
37
|
end
|
|
36
38
|
end
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
hosts = scopes.select { |scope| scope.start_with?("host:") }.map { |scope|
|
|
40
|
+
scope.slice("host:".length, scope.length)
|
|
41
|
+
}
|
|
42
|
+
if 0 < hosts.length
|
|
43
|
+
if open_db
|
|
44
|
+
hosts.each_slice(SQLITE_LIMIT_COMPOUND_SELECT) do |hosts|
|
|
45
|
+
execute_db(@db, "DELETE FROM hosts_tags WHERE host_id IN ( SELECT id FROM hosts WHERE name IN (%s) )" % hosts.map { "?" }.join(", "), hosts)
|
|
46
|
+
execute_db(@db, "DELETE FROM hosts WHERE name IN (%s)" % hosts.map { "?" }.join(", "), hosts)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
41
49
|
end
|
|
42
|
-
FileUtils.rm_f(File.join(options[:confdir], PERSISTENT_DB))
|
|
43
50
|
end
|
|
44
51
|
|
|
45
52
|
private
|
data/lib/hotdog/commands/pssh.rb
CHANGED
|
@@ -34,15 +34,24 @@ module Hotdog
|
|
|
34
34
|
infile.seek(0)
|
|
35
35
|
end
|
|
36
36
|
begin
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
identifier = options[:show_identifier] ? host : nil
|
|
40
|
-
success = exec_command(identifier, cmdline, index: i, output: true, infile: (infile ? infile.path : nil))
|
|
41
|
-
if !success && options[:stop_on_error]
|
|
42
|
-
raise StopException.new
|
|
43
|
-
end
|
|
44
|
-
success
|
|
37
|
+
hosts_cmdlines = hosts.map { |host|
|
|
38
|
+
[host, build_command_string(host, @remote_command, options)]
|
|
45
39
|
}
|
|
40
|
+
if options[:dry_run]
|
|
41
|
+
stats = hosts_cmdlines.map { |host, cmdline|
|
|
42
|
+
STDOUT.puts(cmdline)
|
|
43
|
+
true
|
|
44
|
+
}
|
|
45
|
+
else
|
|
46
|
+
stats = Parallel.map(hosts_cmdlines.each_with_index.to_a, in_threads: parallelism(hosts)) { |(host, cmdline), i|
|
|
47
|
+
identifier = options[:show_identifier] ? host : nil
|
|
48
|
+
success = exec_command(identifier, cmdline, index: i, output: true, infile: (infile ? infile.path : nil))
|
|
49
|
+
if !success && options[:stop_on_error]
|
|
50
|
+
raise StopException.new
|
|
51
|
+
end
|
|
52
|
+
success
|
|
53
|
+
}
|
|
54
|
+
end
|
|
46
55
|
if stats.all?
|
|
47
56
|
exit(0)
|
|
48
57
|
else
|
data/lib/hotdog/commands/scp.rb
CHANGED
|
@@ -13,32 +13,6 @@ module Hotdog
|
|
|
13
13
|
cmdline = ["scp"] + build_command_options(options) + Shellwords.split(command).map { |token| token.gsub(/@(?=:)/, host) }
|
|
14
14
|
Shellwords.join(cmdline)
|
|
15
15
|
end
|
|
16
|
-
|
|
17
|
-
def build_command_options(options={})
|
|
18
|
-
arguments = []
|
|
19
|
-
if options[:forward_agent]
|
|
20
|
-
# nop
|
|
21
|
-
end
|
|
22
|
-
if options[:ssh_config]
|
|
23
|
-
cmdline << "-F" << File.expand_path(options[:ssh_config])
|
|
24
|
-
end
|
|
25
|
-
if options[:identity_file]
|
|
26
|
-
arguments << "-i" << options[:identity_file]
|
|
27
|
-
end
|
|
28
|
-
if options[:user]
|
|
29
|
-
arguments << "-o" << "User=#{options[:user]}"
|
|
30
|
-
end
|
|
31
|
-
if options[:options]
|
|
32
|
-
arguments += options[:options].flat_map { |option| ["-o", option] }
|
|
33
|
-
end
|
|
34
|
-
if options[:port]
|
|
35
|
-
arguments << "-P" << options[:port]
|
|
36
|
-
end
|
|
37
|
-
if options[:verbose]
|
|
38
|
-
arguments << "-v"
|
|
39
|
-
end
|
|
40
|
-
arguments
|
|
41
|
-
end
|
|
42
16
|
end
|
|
43
17
|
end
|
|
44
18
|
end
|
|
@@ -36,11 +36,7 @@ module Hotdog
|
|
|
36
36
|
if @remote_command
|
|
37
37
|
logger.warn("ignore remote command: #{@remote_command}")
|
|
38
38
|
end
|
|
39
|
-
expression = args.join(" ").strip
|
|
40
|
-
if expression.empty?
|
|
41
|
-
# return everything if given expression is empty
|
|
42
|
-
expression = "*"
|
|
43
|
-
end
|
|
39
|
+
expression = rewrite_expression(args.join(" ").strip)
|
|
44
40
|
|
|
45
41
|
begin
|
|
46
42
|
node = parse(expression)
|
|
@@ -114,6 +110,15 @@ module Hotdog
|
|
|
114
110
|
raise("parser error: unknown expression: #{node.inspect}")
|
|
115
111
|
end
|
|
116
112
|
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
def rewrite_expression(expression)
|
|
116
|
+
if expression.empty?
|
|
117
|
+
# return everything if given expression is empty
|
|
118
|
+
expression = "*"
|
|
119
|
+
end
|
|
120
|
+
expression
|
|
121
|
+
end
|
|
117
122
|
end
|
|
118
123
|
end
|
|
119
124
|
end
|
data/lib/hotdog/commands/sftp.rb
CHANGED
|
@@ -15,32 +15,6 @@ module Hotdog
|
|
|
15
15
|
end
|
|
16
16
|
Shellwords.join(cmdline)
|
|
17
17
|
end
|
|
18
|
-
|
|
19
|
-
def build_command_options(options={})
|
|
20
|
-
arguments = []
|
|
21
|
-
if options[:forward_agent]
|
|
22
|
-
# nop
|
|
23
|
-
end
|
|
24
|
-
if options[:ssh_config]
|
|
25
|
-
cmdline << "-F" << File.expand_path(options[:ssh_config])
|
|
26
|
-
end
|
|
27
|
-
if options[:identity_file]
|
|
28
|
-
arguments << "-i" << options[:identity_file]
|
|
29
|
-
end
|
|
30
|
-
if options[:user]
|
|
31
|
-
arguments << "-o" << "User=#{options[:user]}"
|
|
32
|
-
end
|
|
33
|
-
if options[:options]
|
|
34
|
-
arguments += options[:options].flat_map { |option| ["-o", option] }
|
|
35
|
-
end
|
|
36
|
-
if options[:port]
|
|
37
|
-
arguments << "-P" << options[:port]
|
|
38
|
-
end
|
|
39
|
-
if options[:verbose]
|
|
40
|
-
arguments << "-v"
|
|
41
|
-
end
|
|
42
|
-
arguments
|
|
43
|
-
end
|
|
44
18
|
end
|
|
45
19
|
end
|
|
46
20
|
end
|
data/lib/hotdog/commands/ssh.rb
CHANGED
|
@@ -9,32 +9,35 @@ module Hotdog
|
|
|
9
9
|
module Commands
|
|
10
10
|
class SshAlike < Search
|
|
11
11
|
def define_options(optparse, options={})
|
|
12
|
-
default_option(options, :
|
|
13
|
-
default_option(options, :user, nil)
|
|
14
|
-
default_option(options, :port, nil)
|
|
15
|
-
default_option(options, :identity_file, nil)
|
|
16
|
-
default_option(options, :forward_agent, false)
|
|
12
|
+
default_option(options, :ssh_options, {})
|
|
17
13
|
default_option(options, :color, :auto)
|
|
18
14
|
default_option(options, :max_parallelism, Parallel.processor_count)
|
|
19
15
|
default_option(options, :shuffle, false)
|
|
20
16
|
default_option(options, :ssh_config, nil)
|
|
17
|
+
optparse.on("-C", "Enable compression.") do |v|
|
|
18
|
+
options[:ssh_options]["Compression"] = "yes"
|
|
19
|
+
end
|
|
21
20
|
optparse.on("-F SSH_CONFIG", "Specifies an alternative per-user SSH configuration file.") do |configfile|
|
|
22
21
|
options[:ssh_config] = configfile
|
|
23
22
|
end
|
|
24
|
-
optparse.on("-
|
|
25
|
-
options[:
|
|
23
|
+
optparse.on("--dry-run", "Dry run.") do |v|
|
|
24
|
+
options[:dry_run] = v
|
|
25
|
+
end
|
|
26
|
+
optparse.on("-o SSH_OPTION", "Passes this string to ssh command through shell. This option may be given multiple times") do |ssh_option|
|
|
27
|
+
ssh_option_key, ssh_option_value = ssh_option.split("=", 2)
|
|
28
|
+
options[:ssh_options][ssh_option_key] = ssh_option_value
|
|
26
29
|
end
|
|
27
30
|
optparse.on("-i SSH_IDENTITY_FILE", "SSH identity file path") do |path|
|
|
28
|
-
options[:
|
|
31
|
+
options[:ssh_options]["IdentityFile"] = path
|
|
29
32
|
end
|
|
30
33
|
optparse.on("-A", "Enable agent forwarding", TrueClass) do |b|
|
|
31
|
-
options[:
|
|
34
|
+
options[:ssh_options]["ForwardAgent"] = "yes"
|
|
32
35
|
end
|
|
33
36
|
optparse.on("-p PORT", "Port of the remote host", Integer) do |port|
|
|
34
|
-
options[:
|
|
37
|
+
options[:ssh_options]["Port"] = port
|
|
35
38
|
end
|
|
36
39
|
optparse.on("-u SSH_USER", "SSH login user name") do |user|
|
|
37
|
-
options[:
|
|
40
|
+
options[:ssh_options]["User"] = user
|
|
38
41
|
end
|
|
39
42
|
optparse.on("-v", "--verbose", "Enable verbose ode") do |v|
|
|
40
43
|
options[:verbose] = v
|
|
@@ -54,11 +57,7 @@ module Hotdog
|
|
|
54
57
|
end
|
|
55
58
|
|
|
56
59
|
def run(args=[], options={})
|
|
57
|
-
expression = args.join(" ").strip
|
|
58
|
-
if expression.empty?
|
|
59
|
-
# return everything if given expression is empty
|
|
60
|
-
expression = "*"
|
|
61
|
-
end
|
|
60
|
+
expression = rewrite_expression(args.join(" ").strip)
|
|
62
61
|
|
|
63
62
|
begin
|
|
64
63
|
node = parse(expression)
|
|
@@ -69,15 +68,20 @@ module Hotdog
|
|
|
69
68
|
|
|
70
69
|
result0 = evaluate(node, self)
|
|
71
70
|
tuples, fields = get_hosts_with_search_tags(result0, node)
|
|
72
|
-
if options[:shuffle]
|
|
73
|
-
tuples = tuples.shuffle()
|
|
74
|
-
end
|
|
75
71
|
tuples = filter_hosts(tuples)
|
|
76
72
|
validate_hosts!(tuples, fields)
|
|
77
73
|
run_main(tuples.map {|tuple| tuple.first }, options)
|
|
78
74
|
end
|
|
79
75
|
|
|
80
76
|
private
|
|
77
|
+
def rewrite_expression(expression)
|
|
78
|
+
expression = super(expression)
|
|
79
|
+
if options[:shuffle]
|
|
80
|
+
expression = "SHUFFLE((#{expression}))"
|
|
81
|
+
end
|
|
82
|
+
expression
|
|
83
|
+
end
|
|
84
|
+
|
|
81
85
|
def parallelism(hosts)
|
|
82
86
|
options[:max_parallelism] || hosts.size
|
|
83
87
|
end
|
|
@@ -116,24 +120,10 @@ module Hotdog
|
|
|
116
120
|
|
|
117
121
|
def build_command_options(options={})
|
|
118
122
|
cmdline = []
|
|
119
|
-
if options[:forward_agent]
|
|
120
|
-
cmdline << "-A"
|
|
121
|
-
end
|
|
122
123
|
if options[:ssh_config]
|
|
123
124
|
cmdline << "-F" << File.expand_path(options[:ssh_config])
|
|
124
125
|
end
|
|
125
|
-
|
|
126
|
-
cmdline << "-i" << options[:identity_file]
|
|
127
|
-
end
|
|
128
|
-
if options[:user]
|
|
129
|
-
cmdline << "-l" << options[:user]
|
|
130
|
-
end
|
|
131
|
-
if options[:options]
|
|
132
|
-
cmdline += options[:options].flat_map { |option| ["-o", option] }
|
|
133
|
-
end
|
|
134
|
-
if options[:port]
|
|
135
|
-
cmdline << "-p" << options[:port].to_s
|
|
136
|
-
end
|
|
126
|
+
cmdline += options[:ssh_options].flat_map { |k, v| ["-o", "#{k}=#{v}"] }
|
|
137
127
|
if options[:verbose]
|
|
138
128
|
cmdline << "-v"
|
|
139
129
|
end
|
|
@@ -210,6 +200,14 @@ module Hotdog
|
|
|
210
200
|
end
|
|
211
201
|
|
|
212
202
|
private
|
|
203
|
+
def rewrite_expression(expression)
|
|
204
|
+
expression = super(expression)
|
|
205
|
+
if options[:index]
|
|
206
|
+
expression = "SLICE((#{expression}), #{options[:index]}, 1)"
|
|
207
|
+
end
|
|
208
|
+
expression
|
|
209
|
+
end
|
|
210
|
+
|
|
213
211
|
def filter_hosts(tuples)
|
|
214
212
|
tuples = super
|
|
215
213
|
if options[:index] and options[:index] < tuples.length
|
|
@@ -232,7 +230,12 @@ module Hotdog
|
|
|
232
230
|
def run_main(hosts, options={})
|
|
233
231
|
cmdline = build_command_string(hosts.first, @remote_command, options)
|
|
234
232
|
logger.debug("execute: #{cmdline}")
|
|
235
|
-
|
|
233
|
+
if options[:dry_run]
|
|
234
|
+
STDOUT.puts(cmdline)
|
|
235
|
+
exit(0)
|
|
236
|
+
else
|
|
237
|
+
exec(cmdline)
|
|
238
|
+
end
|
|
236
239
|
exit(127)
|
|
237
240
|
end
|
|
238
241
|
end
|
data/lib/hotdog/commands/tag.rb
CHANGED
|
@@ -24,24 +24,25 @@ module Hotdog
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def run(args=[], options={})
|
|
27
|
-
args.
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
hosts = args.map { |arg|
|
|
28
|
+
arg.sub(/\Ahost:/, "")
|
|
29
|
+
}
|
|
30
|
+
hosts.each do |host|
|
|
30
31
|
if options[:tags].empty?
|
|
31
32
|
# nop
|
|
32
33
|
else
|
|
33
34
|
# add all as user tags
|
|
34
35
|
with_retry(options) do
|
|
35
|
-
add_tags(
|
|
36
|
+
add_tags(host, options[:tags], source=options[:tag_source])
|
|
36
37
|
end
|
|
37
38
|
end
|
|
38
39
|
end
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
if open_db
|
|
41
|
+
create_tags(@db, options[:tags])
|
|
42
|
+
options[:tags].each do |tag|
|
|
43
|
+
associae_tag_hosts(@db, tag, hosts)
|
|
44
|
+
end
|
|
43
45
|
end
|
|
44
|
-
FileUtils.rm_f(File.join(options[:confdir], PERSISTENT_DB))
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
private
|
|
@@ -24,33 +24,40 @@ module Hotdog
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def run(args=[], options={})
|
|
27
|
-
args.
|
|
28
|
-
|
|
27
|
+
hosts = args.map { |arg|
|
|
28
|
+
arg.sub(/\Ahost:/, "")
|
|
29
|
+
}
|
|
29
30
|
|
|
31
|
+
hosts.each do |host|
|
|
30
32
|
if options[:tags].empty?
|
|
31
33
|
# delete all user tags
|
|
32
34
|
with_retry do
|
|
33
|
-
detach_tags(
|
|
35
|
+
detach_tags(host, source=options[:tag_source])
|
|
34
36
|
end
|
|
35
37
|
else
|
|
36
|
-
host_tags = with_retry { host_tags(
|
|
38
|
+
host_tags = with_retry { host_tags(host, source=options[:tag_source]) }
|
|
37
39
|
old_tags = host_tags["tags"]
|
|
38
40
|
new_tags = old_tags - options[:tags]
|
|
39
41
|
if old_tags == new_tags
|
|
40
42
|
# nop
|
|
41
43
|
else
|
|
42
44
|
with_retry do
|
|
43
|
-
update_tags(
|
|
45
|
+
update_tags(host, new_tags, source=options[:tag_source])
|
|
44
46
|
end
|
|
45
47
|
end
|
|
46
48
|
end
|
|
47
49
|
end
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
if options[:tags].empty?
|
|
52
|
+
# refresh all persistent.db since there is no way to identify user tags
|
|
53
|
+
remove_db
|
|
54
|
+
else
|
|
55
|
+
if open_db
|
|
56
|
+
options[:tags].each do |tag|
|
|
57
|
+
disassociate_tag_hosts(tag, hosts)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
52
60
|
end
|
|
53
|
-
FileUtils.rm_f(File.join(options[:confdir], PERSISTENT_DB))
|
|
54
61
|
end
|
|
55
62
|
|
|
56
63
|
private
|
data/lib/hotdog/commands/up.rb
CHANGED
|
@@ -37,12 +37,13 @@ module Hotdog
|
|
|
37
37
|
cancel_downtime(downtime["id"], options)
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
hosts = scopes.select { |scope| scope.start_with?("host:") }.map { |scope|
|
|
41
|
+
scope.slice("host:".length, scope.length)
|
|
42
|
+
}
|
|
43
|
+
if 0 < hosts.length
|
|
44
|
+
# refresh all persistent.db to retrieve information about up'd host
|
|
45
|
+
remove_db
|
|
44
46
|
end
|
|
45
|
-
FileUtils.rm_f(File.join(options[:confdir], PERSISTENT_DB))
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
private
|
data/lib/hotdog/commands.rb
CHANGED
|
@@ -92,25 +92,16 @@ module Hotdog
|
|
|
92
92
|
get_hosts_fields(host_ids, fields)
|
|
93
93
|
else
|
|
94
94
|
if @options[:listing]
|
|
95
|
-
q1 = "SELECT DISTINCT tags.name FROM hosts_tags " \
|
|
96
|
-
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
|
97
|
-
"WHERE hosts_tags.host_id IN (%s);"
|
|
98
95
|
if @options[:primary_tag]
|
|
99
96
|
fields = [
|
|
100
97
|
@options[:primary_tag],
|
|
101
98
|
"host",
|
|
102
|
-
] + host_ids
|
|
103
|
-
execute(q1 % host_ids.map { "?" }.join(", "), host_ids).map { |row| row.first }.reject { |tag_name|
|
|
104
|
-
tag_name == @options[:primary_tag]
|
|
105
|
-
}
|
|
106
|
-
}
|
|
99
|
+
] + get_fields(host_ids).reject { |tag_name| tag_name == @options[:primary_tag] }
|
|
107
100
|
get_hosts_fields(host_ids, fields)
|
|
108
101
|
else
|
|
109
102
|
fields = [
|
|
110
103
|
"host",
|
|
111
|
-
] + host_ids
|
|
112
|
-
execute(q1 % host_ids.map { "?" }.join(", "), host_ids).map { |row| row.first }
|
|
113
|
-
}
|
|
104
|
+
] + get_fields(host_ids)
|
|
114
105
|
get_hosts_fields(host_ids, fields)
|
|
115
106
|
end
|
|
116
107
|
else
|
|
@@ -124,48 +115,85 @@ module Hotdog
|
|
|
124
115
|
end
|
|
125
116
|
end
|
|
126
117
|
|
|
127
|
-
def
|
|
128
|
-
|
|
118
|
+
def get_fields(host_ids)
|
|
119
|
+
host_ids.each_slice(SQLITE_LIMIT_COMPOUND_SELECT).flat_map { |host_ids|
|
|
120
|
+
q = "SELECT DISTINCT tags.name FROM hosts_tags " \
|
|
121
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
|
122
|
+
"WHERE hosts_tags.host_id IN (%s);" % host_ids.map { "?" }.join(", ")
|
|
123
|
+
execute(q, host_ids).map { |row| row.first }
|
|
124
|
+
}.uniq
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def get_hosts_fields(host_ids, fields, options={})
|
|
128
|
+
case fields.length
|
|
129
|
+
when 0
|
|
129
130
|
[[], fields]
|
|
131
|
+
when 1
|
|
132
|
+
get_hosts_field(host_ids, fields.first, options)
|
|
130
133
|
else
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
host_names = {}
|
|
134
|
+
if fields.find { |field| /\Ahost\z/i =~ field }
|
|
135
|
+
host_names = Hash[execute("SELECT id, name FROM hosts WHERE id IN (%s);" % host_ids.map { "?" }.join(", "), host_ids).map { |row| row.to_a }]
|
|
134
136
|
else
|
|
135
|
-
host_names =
|
|
136
|
-
host_ids.each_slice(SQLITE_LIMIT_COMPOUND_SELECT).flat_map { |host_ids|
|
|
137
|
-
execute("SELECT id, name FROM hosts WHERE id IN (%s)" % host_ids.map { "?" }.join(", "), host_ids).map { |row| row.to_a }
|
|
138
|
-
}
|
|
139
|
-
]
|
|
137
|
+
host_names = {}
|
|
140
138
|
end
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
139
|
+
|
|
140
|
+
[host_ids.map { |host_id| get_host_fields(host_id, fields, options.merge(host_names: host_names)) }.map { |result, fields| result }, fields]
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def get_host_fields(host_id, fields, options={})
|
|
145
|
+
field_values = {"host" => options.fetch(:host_names, {}).fetch(host_id, nil)}
|
|
146
|
+
|
|
147
|
+
fields.reject { |field| /\Ahost\z/i =~ field }.uniq.each_slice(SQLITE_LIMIT_COMPOUND_SELECT - 1).each do |fields|
|
|
148
|
+
q = "SELECT LOWER(tags.name), GROUP_CONCAT(tags.value, ',') FROM hosts_tags " \
|
|
149
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
|
150
|
+
"WHERE hosts_tags.host_id = ? AND tags.name IN (%s) " \
|
|
151
|
+
"GROUP BY tags.name;" % fields.map { "?" }.join(", ")
|
|
152
|
+
execute(q, [host_id] + fields).each do |row|
|
|
153
|
+
field_values[row[0]] = row[1]
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
result = fields.map { |tag_name|
|
|
158
|
+
tag_value = field_values.fetch(tag_name.downcase, nil)
|
|
159
|
+
display_tag(tag_name, tag_value)
|
|
160
|
+
}
|
|
161
|
+
[result, fields]
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def get_hosts_field(host_ids, field, options={})
|
|
165
|
+
if /\Ahost\z/i =~ field
|
|
166
|
+
result = host_ids.each_slice(SQLITE_LIMIT_COMPOUND_SELECT).flat_map { |host_ids|
|
|
167
|
+
execute("SELECT name FROM hosts WHERE id IN (%s)" % host_ids.map { "?" }.join(", "), host_ids).map { |row| row.to_a }
|
|
168
|
+
}
|
|
169
|
+
else
|
|
170
|
+
result = host_ids.each_slice(SQLITE_LIMIT_COMPOUND_SELECT - 1).flat_map { |host_ids|
|
|
171
|
+
q = "SELECT LOWER(tags.name), GROUP_CONCAT(tags.value, ',') FROM hosts_tags " \
|
|
172
|
+
"INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
|
|
173
|
+
"WHERE hosts_tags.host_id IN (%s) AND tags.name = ? " \
|
|
174
|
+
"GROUP BY hosts_tags.host_id, tags.name;" % host_ids.map { "?" }.join(", ")
|
|
175
|
+
r = execute(q, host_ids + [field]).map { |tag_name, tag_value|
|
|
176
|
+
[display_tag(tag_name, tag_value)]
|
|
166
177
|
}
|
|
178
|
+
if r.empty?
|
|
179
|
+
host_ids.map { [nil] }
|
|
180
|
+
else
|
|
181
|
+
r
|
|
182
|
+
end
|
|
167
183
|
}
|
|
168
|
-
|
|
184
|
+
end
|
|
185
|
+
[result, [field]]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def display_tag(tag_name, tag_value)
|
|
189
|
+
if tag_value
|
|
190
|
+
if tag_value.empty?
|
|
191
|
+
tag_name # use `tag_name` as `tag_value` for the tags without any values
|
|
192
|
+
else
|
|
193
|
+
tag_value
|
|
194
|
+
end
|
|
195
|
+
else
|
|
196
|
+
nil
|
|
169
197
|
end
|
|
170
198
|
end
|
|
171
199
|
|
|
@@ -178,23 +206,19 @@ module Hotdog
|
|
|
178
206
|
db.close()
|
|
179
207
|
end
|
|
180
208
|
|
|
181
|
-
def
|
|
209
|
+
def open_db(options={})
|
|
182
210
|
options = @options.merge(options)
|
|
183
|
-
if @db
|
|
211
|
+
if @db
|
|
212
|
+
@db
|
|
213
|
+
else
|
|
184
214
|
FileUtils.mkdir_p(options[:confdir])
|
|
185
215
|
persistent = File.join(options[:confdir], PERSISTENT_DB)
|
|
186
216
|
|
|
187
217
|
if (not options[:force] and File.exist?(persistent) and Time.new < File.mtime(persistent) + options[:expiry]) or options[:offline]
|
|
188
218
|
begin
|
|
189
219
|
persistent_db = SQLite3::Database.new(persistent)
|
|
190
|
-
persistent_db.execute(
|
|
191
|
-
SELECT hosts_tags.host_id FROM hosts_tags
|
|
192
|
-
INNER JOIN hosts ON hosts_tags.host_id = hosts.id
|
|
193
|
-
INNER JOIN tags ON hosts_tags.tag_id = tags.id
|
|
194
|
-
LIMIT 1;
|
|
195
|
-
EOS
|
|
220
|
+
persistent_db.execute("SELECT hosts_tags.host_id FROM hosts_tags INNER JOIN hosts ON hosts_tags.host_id = hosts.id INNER JOIN tags ON hosts_tags.tag_id = tags.id LIMIT 1;")
|
|
196
221
|
@db = persistent_db
|
|
197
|
-
return
|
|
198
222
|
rescue SQLite3::SQLException
|
|
199
223
|
if options[:offline]
|
|
200
224
|
raise(RuntimeError.new("no database available on offline mode"))
|
|
@@ -203,81 +227,56 @@ module Hotdog
|
|
|
203
227
|
end
|
|
204
228
|
end
|
|
205
229
|
end
|
|
230
|
+
@db
|
|
231
|
+
end
|
|
232
|
+
end
|
|
206
233
|
|
|
234
|
+
def update_db(options={})
|
|
235
|
+
options = @options.merge(options)
|
|
236
|
+
if open_db(options)
|
|
237
|
+
@db
|
|
238
|
+
else
|
|
207
239
|
memory_db = SQLite3::Database.new(":memory:")
|
|
208
|
-
execute_db(memory_db,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
execute_db(memory_db, "CREATE UNIQUE INDEX IF NOT EXISTS hosts_name ON hosts ( name );")
|
|
215
|
-
execute_db(memory_db, <<-EOS)
|
|
216
|
-
CREATE TABLE IF NOT EXISTS tags (
|
|
217
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
218
|
-
name VARCHAR(200) NOT NULL COLLATE NOCASE,
|
|
219
|
-
value VARCHAR(200) NOT NULL COLLATE NOCASE
|
|
220
|
-
);
|
|
221
|
-
EOS
|
|
222
|
-
execute_db(memory_db, "CREATE UNIQUE INDEX IF NOT EXISTS tags_name_value ON tags ( name, value );")
|
|
223
|
-
execute_db(memory_db, <<-EOS)
|
|
224
|
-
CREATE TABLE IF NOT EXISTS hosts_tags (
|
|
225
|
-
host_id INTEGER NOT NULL,
|
|
226
|
-
tag_id INTEGER NOT NULL
|
|
227
|
-
);
|
|
228
|
-
EOS
|
|
229
|
-
execute_db(memory_db, "CREATE UNIQUE INDEX IF NOT EXISTS hosts_tags_host_id_tag_id ON hosts_tags ( host_id, tag_id );")
|
|
240
|
+
execute_db(memory_db, "CREATE TABLE IF NOT EXISTS hosts (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(255) NOT NULL COLLATE NOCASE);")
|
|
241
|
+
execute_db(memory_db, "CREATE UNIQUE INDEX IF NOT EXISTS hosts_name ON hosts (name);")
|
|
242
|
+
execute_db(memory_db, "CREATE TABLE IF NOT EXISTS tags (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(200) NOT NULL COLLATE NOCASE, value VARCHAR(200) NOT NULL COLLATE NOCASE);")
|
|
243
|
+
execute_db(memory_db, "CREATE UNIQUE INDEX IF NOT EXISTS tags_name_value ON tags (name, value);")
|
|
244
|
+
execute_db(memory_db, "CREATE TABLE IF NOT EXISTS hosts_tags (host_id INTEGER NOT NULL, tag_id INTEGER NOT NULL);")
|
|
245
|
+
execute_db(memory_db, "CREATE UNIQUE INDEX IF NOT EXISTS hosts_tags_host_id_tag_id ON hosts_tags (host_id, tag_id);")
|
|
230
246
|
|
|
231
247
|
all_tags = get_all_tags()
|
|
232
248
|
|
|
233
249
|
memory_db.transaction do
|
|
234
250
|
known_tags = all_tags.keys.map { |tag| split_tag(tag) }.uniq
|
|
235
|
-
|
|
236
|
-
q = "INSERT OR IGNORE INTO tags (name, value) VALUES %s" % known_tags.map { "(?, ?)" }.join(", ")
|
|
237
|
-
execute_db(memory_db, q, known_tags)
|
|
238
|
-
end
|
|
251
|
+
create_tags(memory_db, known_tags)
|
|
239
252
|
|
|
240
253
|
known_hosts = all_tags.values.reduce(:+).uniq
|
|
241
|
-
|
|
242
|
-
q = "INSERT OR IGNORE INTO hosts (name) VALUES %s" % known_hosts.map { "(?)" }.join(", ")
|
|
243
|
-
execute_db(memory_db, q, known_hosts)
|
|
244
|
-
end
|
|
254
|
+
create_hosts(memory_db, known_hosts)
|
|
245
255
|
|
|
246
256
|
all_tags.each do |tag, hosts|
|
|
247
|
-
|
|
248
|
-
q = "INSERT OR REPLACE INTO hosts_tags (host_id, tag_id) " \
|
|
249
|
-
"SELECT host.id, tag.id FROM " \
|
|
250
|
-
"( SELECT id FROM hosts WHERE name IN (%s) ) AS host, " \
|
|
251
|
-
"( SELECT id FROM tags WHERE name = ? AND value = ? LIMIT 1 ) AS tag;" % hosts.map { "?" }.join(", ")
|
|
252
|
-
begin
|
|
253
|
-
execute_db(memory_db, q, (hosts + split_tag(tag)))
|
|
254
|
-
rescue SQLite3::RangeException => error
|
|
255
|
-
# FIXME: bulk insert occationally fails even if there are no errors in bind parameters
|
|
256
|
-
# `bind_param': bind or column index out of range (SQLite3::RangeException)
|
|
257
|
-
logger.warn("bulk insert failed due to #{error.message}. fallback to normal insert.")
|
|
258
|
-
hosts.each do |host|
|
|
259
|
-
q = "INSERT OR REPLACE INTO hosts_tags (host_id, tag_id) " \
|
|
260
|
-
"SELECT host.id, tag.id FROM " \
|
|
261
|
-
"( SELECT id FROM hosts WHERE name = ? ) AS host, " \
|
|
262
|
-
"( SELECT id FROM tags WHERE name = ? AND value = ? LIMIT 1 ) AS tag;"
|
|
263
|
-
execute_db(memory_db, q, [host] + split_tag(tag))
|
|
264
|
-
end
|
|
265
|
-
end
|
|
266
|
-
end
|
|
257
|
+
associate_tag_hosts(memory_db, tag, hosts)
|
|
267
258
|
end
|
|
268
259
|
end
|
|
269
260
|
|
|
270
261
|
# backup in-memory db to file
|
|
262
|
+
FileUtils.mkdir_p(options[:confdir])
|
|
263
|
+
persistent = File.join(options[:confdir], PERSISTENT_DB)
|
|
271
264
|
FileUtils.rm_f(persistent)
|
|
272
265
|
persistent_db = SQLite3::Database.new(persistent)
|
|
273
266
|
copy_db(memory_db, persistent_db)
|
|
274
|
-
close_db(
|
|
275
|
-
@db =
|
|
276
|
-
else
|
|
277
|
-
@db
|
|
267
|
+
close_db(memory_db)
|
|
268
|
+
@db = persistent_db
|
|
278
269
|
end
|
|
279
270
|
end
|
|
280
271
|
|
|
272
|
+
def remove_db(options={})
|
|
273
|
+
options = @options.merge(options)
|
|
274
|
+
if @db
|
|
275
|
+
close_db(@db)
|
|
276
|
+
end
|
|
277
|
+
FileUtils.rm_f(File.join(options[:confdir], PERSISTENT_DB))
|
|
278
|
+
end
|
|
279
|
+
|
|
281
280
|
def execute_db(db, q, args=[])
|
|
282
281
|
begin
|
|
283
282
|
logger.debug("execute: #{q} -- #{args.inspect}")
|
|
@@ -322,6 +321,52 @@ module Hotdog
|
|
|
322
321
|
Hash[responses.fetch(:all_tags, {}).fetch("tags", []).map { |tag, hosts| [tag, hosts.reject { |host| downtimes.include?(host) }] }]
|
|
323
322
|
end
|
|
324
323
|
|
|
324
|
+
def create_hosts(db, hosts)
|
|
325
|
+
hosts.each_slice(SQLITE_LIMIT_COMPOUND_SELECT) do |hosts|
|
|
326
|
+
q = "INSERT OR IGNORE INTO hosts (name) VALUES %s" % hosts.map { "(?)" }.join(", ")
|
|
327
|
+
execute_db(db, q, hosts)
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def create_tags(db, tags)
|
|
332
|
+
tags.each_slice(SQLITE_LIMIT_COMPOUND_SELECT / 2) do |tags|
|
|
333
|
+
q = "INSERT OR IGNORE INTO tags (name, value) VALUES %s" % tags.map { "(?, ?)" }.join(", ")
|
|
334
|
+
execute_db(db, q, tags)
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def associate_tag_hosts(db, tag, hosts)
|
|
339
|
+
hosts.each_slice(SQLITE_LIMIT_COMPOUND_SELECT - 2) do |hosts|
|
|
340
|
+
q = "INSERT OR REPLACE INTO hosts_tags (host_id, tag_id) " \
|
|
341
|
+
"SELECT host.id, tag.id FROM " \
|
|
342
|
+
"( SELECT id FROM hosts WHERE name IN (%s) ) AS host, " \
|
|
343
|
+
"( SELECT id FROM tags WHERE name = ? AND value = ? LIMIT 1 ) AS tag;" % hosts.map { "?" }.join(", ")
|
|
344
|
+
begin
|
|
345
|
+
execute_db(db, q, (hosts + split_tag(tag)))
|
|
346
|
+
rescue SQLite3::RangeException => error
|
|
347
|
+
# FIXME: bulk insert occationally fails even if there are no errors in bind parameters
|
|
348
|
+
# `bind_param': bind or column index out of range (SQLite3::RangeException)
|
|
349
|
+
logger.warn("bulk insert failed due to #{error.message}. fallback to normal insert.")
|
|
350
|
+
hosts.each do |host|
|
|
351
|
+
q = "INSERT OR REPLACE INTO hosts_tags (host_id, tag_id) " \
|
|
352
|
+
"SELECT host.id, tag.id FROM " \
|
|
353
|
+
"( SELECT id FROM hosts WHERE name = ? ) AS host, " \
|
|
354
|
+
"( SELECT id FROM tags WHERE name = ? AND value = ? LIMIT 1 ) AS tag;"
|
|
355
|
+
execute_db(db, q, [host] + split_tag(tag))
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def disassociate_tag_hosts(db, tag, hosts)
|
|
362
|
+
hosts.each_slice(SQLITE_LIMIT_COMPOUND_SELECT - 2) do |hosts|
|
|
363
|
+
q = "DELETE FROM hosts_tags " \
|
|
364
|
+
"WHERE tag_id IN ( SELECT id FROM tags WHERE name = ? AND value = ? LIMIT 1 );" \
|
|
365
|
+
"AND host_id IN ( SELECT id FROM hosts WHERE name IN (%s) ) " % hosts.map { "?" }.join(", ")
|
|
366
|
+
execute_db(db, q, split_tag(tag) + hosts)
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
325
370
|
def dog()
|
|
326
371
|
@dog ||= Dogapi::Client.new(application.api_key, application.application_key)
|
|
327
372
|
end
|
|
@@ -425,8 +425,13 @@ module Hotdog
|
|
|
425
425
|
@function = :ORDER_BY
|
|
426
426
|
when "REVERSE", "reverse"
|
|
427
427
|
@function = :REVERSE
|
|
428
|
+
when "SAMPLE", "sample"
|
|
429
|
+
@function = :HEAD
|
|
430
|
+
args[0] = FuncallNode.new("SHUFFLE", [args[0]])
|
|
428
431
|
when "SHUFFLE", "shuffle"
|
|
429
432
|
@function = :SHUFFLE
|
|
433
|
+
when "SLICE", "slice"
|
|
434
|
+
@function = :SLICE
|
|
430
435
|
when "SORT", "sort"
|
|
431
436
|
@function = :ORDER_BY
|
|
432
437
|
when "TAIL", "tail"
|
|
@@ -474,6 +479,8 @@ module Hotdog
|
|
|
474
479
|
@args[0] = @args[0].optimize(options)
|
|
475
480
|
when :SHUFFLE
|
|
476
481
|
@args[0] = @args[0].optimize(options)
|
|
482
|
+
when :SLICE
|
|
483
|
+
@args[0] = @args[0].optimize(options)
|
|
477
484
|
when :TAIL
|
|
478
485
|
@args[0] = @args[0].optimize(options)
|
|
479
486
|
end
|
|
@@ -510,6 +517,8 @@ module Hotdog
|
|
|
510
517
|
args[0].evaluate(environment, options).reverse()
|
|
511
518
|
when :SHUFFLE
|
|
512
519
|
args[0].evaluate(environment, options).shuffle()
|
|
520
|
+
when :SLICE
|
|
521
|
+
args[0].evaluate(environment, options).slice(args[1], args[2] || 1)
|
|
513
522
|
when :TAIL
|
|
514
523
|
args[0].evaluate(environment, options).last(args[1] || 1)
|
|
515
524
|
else
|
data/lib/hotdog/version.rb
CHANGED
data/spec/core/commands_spec.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "spec_helper"
|
|
2
2
|
require "hotdog/application"
|
|
3
3
|
require "hotdog/commands"
|
|
4
|
+
require "hotdog/commands/search"
|
|
4
5
|
|
|
5
6
|
describe "commands" do
|
|
6
7
|
let(:cmd) {
|
|
@@ -99,7 +100,7 @@ describe "commands" do
|
|
|
99
100
|
end
|
|
100
101
|
|
|
101
102
|
it "get host fields with host" do
|
|
102
|
-
allow(cmd).to receive(:execute).with("SELECT id, name FROM hosts WHERE id IN (?, ?, ?)", [1, 2, 3]) {
|
|
103
|
+
allow(cmd).to receive(:execute).with("SELECT id, name FROM hosts WHERE id IN (?, ?, ?);", [1, 2, 3]) {
|
|
103
104
|
[[1, "host1"], [2, "host2"], [3, "host3"]]
|
|
104
105
|
}
|
|
105
106
|
q1 = [
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hotdog
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.18.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yamashita Yuu
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2016-
|
|
11
|
+
date: 2016-12-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|