hotdog 0.17.1 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|