hotdog 0.2.2 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 12b1b97f8c77f181a665ba52db0725c613d638a7
4
- data.tar.gz: 73be8bb8526df6c4bb95811e6e56de196fae0a73
3
+ metadata.gz: fccd372d49246c2c85768c9244a29746fafcfb40
4
+ data.tar.gz: 9b4d490218a3d93af4806b12d47dfca28f8d90f8
5
5
  SHA512:
6
- metadata.gz: 602156cf61f52c8f8c14937dcf324b93516ac16b6f0e61c971bcccb124a7827902e313c400fdcb98eb54d83f8ae2cf7a21783d4f1937df18899c20782380224a
7
- data.tar.gz: eb9ab65fad8b6b25e59eec751cc02b4a99cc0d69fc5f2fc61a7c6df2c9fbce5055928ed2ab8f2c85b7af2a8580f9db51e5534c277fac89a100ab7e9b7064eb27
6
+ metadata.gz: 8e21eb61e7ccfc2b533e005eb1f494ead1c16c12cd21e0384d46228e88f3a5d6771a62f9006fca59bc538e680e4a7370f1a1905c3e1aa314ea368e4b97146a92
7
+ data.tar.gz: 654ccf67781c3c1a7ebc1f59fac07a5dd6319231474e6ffadcd08309d32b062efe7e543c1db83f7d821bb054a6ea31c111b7c717f63f0692b3929a6fc79df971
data/README.md CHANGED
@@ -100,24 +100,33 @@ $ hotdog ssh availability-zone:us-east-1b and 'name:web-*' -t public_ipv4 -u use
100
100
  Acceptable expressions in pseudo BNF.
101
101
 
102
102
  ```
103
- expression: binary_expression
104
- | term
103
+ expression: expression0
105
104
  ;
106
105
 
107
- binary_expression: term "&&" term
108
- | term "||" term
109
- | term "and" term
110
- | term "or" term
111
- ;
112
-
113
- unary_expression: '!' expression
114
- | '~' expression
115
- | "not" expression
116
- ;
117
-
118
- term: unary_expression
119
- | atom
120
- ;
106
+ expression0: expression1 "and" expression
107
+ | expression1 "or" expression
108
+ | expression1
109
+ ;
110
+
111
+ expression1: "not" expression
112
+ | expression2
113
+ ;
114
+
115
+ expression2: expression3 expression
116
+ | expression3
117
+ ;
118
+
119
+ expression3: expression4 "&&" expression
120
+ | expression4 "||" expression
121
+ | expression4
122
+ ;
123
+
124
+ expression4: '!' atom
125
+ | '~' atom
126
+ | '!' expression
127
+ | '~' expression
128
+ | atom
129
+ ;
121
130
 
122
131
  atom: '(' expression ')'
123
132
  | IDENTIFIER separator ATTRIBUTE
@@ -79,38 +79,49 @@ module Hotdog
79
79
  class ExpressionParser < Parslet::Parser
80
80
  root(:expression)
81
81
  rule(:expression) {
82
- ( binary_expression \
83
- | term \
82
+ ( expression0 \
84
83
  )
85
84
  }
86
- rule(:binary_op) {
87
- ( str('&') >> str('&').maybe \
88
- | str('|') >> str('|').maybe \
89
- | match('[Aa]') >> match('[Nn]') >> match('[Dd]') \
90
- | match('[Oo]') >> match('[Rr]') \
85
+ rule(:expression0) {
86
+ ( expression1.as(:left) >> spacing.maybe >> binary_op.as(:binary_op) >> spacing.maybe >> expression.as(:right) \
87
+ | expression1 \
91
88
  )
92
89
  }
93
- rule(:binary_expression) {
94
- ( term.as(:left) >> spacing.maybe >> binary_op.as(:binary_op) >> spacing.maybe >> expression.as(:right) \
95
- | term.as(:left) >> spacing.maybe.as(:binary_op) >> expression.as(:right) \
90
+ rule(:expression1) {
91
+ ( unary_op.as(:unary_op) >> spacing.maybe >> expression.as(:expression) \
92
+ | expression2 \
96
93
  )
97
94
  }
98
- rule(:unary_op) {
99
- ( str('!') \
100
- | str('~') \
101
- | match('[Nn]') >> match('[Oo]') >> match('[Tt]') \
95
+ rule(:expression2) {
96
+ ( expression3.as(:left) >> spacing.maybe.as(:binary_op) >> expression.as(:right) \
97
+ | expression3 \
102
98
  )
103
99
  }
104
- rule(:unary_expression) {
105
- ( spacing.maybe >> unary_op.as(:unary_op) >> term.as(:expression) \
106
- | spacing.maybe >> unary_op.as(:unary_op) >> expression.as(:expression) \
100
+ rule(:expression3) {
101
+ ( expression4.as(:left) >> spacing.maybe >> str('&&').as(:binary_op) >> spacing.maybe >> expression.as(:right) \
102
+ | expression4.as(:left) >> spacing.maybe >> str('||').as(:binary_op) >> spacing.maybe >> expression.as(:right) \
103
+ | expression4.as(:left) >> spacing.maybe >> str('&').as(:binary_op) >> spacing.maybe >> expression.as(:right) \
104
+ | expression4.as(:left) >> spacing.maybe >> str('|').as(:binary_op) >> spacing.maybe >> expression.as(:right) \
105
+ | expression4 \
107
106
  )
108
107
  }
109
- rule(:term) {
110
- ( unary_expression \
108
+ rule(:expression4) {
109
+ ( str('!').as(:unary_op) >> spacing.maybe >> atom.as(:expression) \
110
+ | str('~').as(:unary_op) >> spacing.maybe >> atom.as(:expression) \
111
+ | str('!').as(:unary_op) >> spacing.maybe >> expression.as(:expression) \
112
+ | str('~').as(:unary_op) >> spacing.maybe >> expression.as(:expression) \
111
113
  | atom \
112
114
  )
113
115
  }
116
+ rule(:binary_op) {
117
+ ( str('and') \
118
+ | str('or') \
119
+ )
120
+ }
121
+ rule(:unary_op) {
122
+ ( str('not') \
123
+ )
124
+ }
114
125
  rule(:atom) {
115
126
  ( spacing.maybe >> str('(') >> expression >> str(')') >> spacing.maybe \
116
127
  | spacing.maybe >> identifier_regexp.as(:identifier_regexp) >> separator >> attribute_regexp.as(:attribute_regexp) >> spacing.maybe \
@@ -137,11 +148,11 @@ module Hotdog
137
148
  )
138
149
  }
139
150
  rule(:identifier_glob) {
140
- ( unary_op.absent? >> binary_op.absent? >> identifier.repeat(0) >> (glob >> identifier.maybe).repeat(1) \
151
+ ( binary_op.absent? >> unary_op.absent? >> identifier.repeat(0) >> (glob >> identifier.maybe).repeat(1) \
141
152
  )
142
153
  }
143
154
  rule(:identifier) {
144
- ( unary_op.absent? >> binary_op.absent? >> match('[A-Za-z]') >> match('[-./0-9A-Z_a-z]').repeat(0) \
155
+ ( binary_op.absent? >> unary_op.absent? >> match('[A-Za-z]') >> match('[-./0-9A-Z_a-z]').repeat(0) \
145
156
  )
146
157
  }
147
158
  rule(:separator) {
@@ -154,11 +165,11 @@ module Hotdog
154
165
  )
155
166
  }
156
167
  rule(:attribute_glob) {
157
- ( unary_op.absent? >> binary_op.absent? >> attribute.repeat(0) >> (glob >> attribute.maybe).repeat(1) \
168
+ ( binary_op.absent? >> unary_op.absent? >> attribute.repeat(0) >> (glob >> attribute.maybe).repeat(1) \
158
169
  )
159
170
  }
160
171
  rule(:attribute) {
161
- ( unary_op.absent? >> binary_op.absent? >> match('[-./0-9:A-Z_a-z]').repeat(1) \
172
+ ( binary_op.absent? >> unary_op.absent? >> match('[-./0-9:A-Z_a-z]').repeat(1) \
162
173
  )
163
174
  }
164
175
  rule(:glob) {
@@ -80,36 +80,7 @@ module Hotdog
80
80
  tag_name, tag_value = split_tag(tag)
81
81
  tag_name
82
82
  }
83
- fields_without_host = fields.reject { |tag_name| tag_name == "host" }
84
- if fields == fields_without_host
85
- host_names = {}
86
- else
87
- host_names = Hash[
88
- host_ids.each_slice(SQLITE_LIMIT_COMPOUND_SELECT).flat_map { |host_ids|
89
- execute("SELECT id, name FROM hosts WHERE id IN (%s)" % host_ids.map { "?" }.join(", "), host_ids).map { |row| row.to_a }
90
- }
91
- ]
92
- end
93
- q1 = "SELECT tags.name, GROUP_CONCAT(tags.value, ',') FROM hosts_tags " \
94
- "INNER JOIN hosts ON hosts_tags.host_id = hosts.id " \
95
- "INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
96
- "WHERE hosts_tags.host_id = ? AND tags.name IN (%s) " \
97
- "GROUP BY tags.name;"
98
- result = host_ids.map { |host_id|
99
- tag_values = Hash[
100
- fields_without_host.each_slice(SQLITE_LIMIT_COMPOUND_SELECT - 1).flat_map { |fields_without_host|
101
- execute(q1 % fields_without_host.map { "?" }.join(", "), [host_id] + fields_without_host).map { |row| row.to_a }
102
- }
103
- ]
104
- fields.map { |tag_name|
105
- if tag_name == "host"
106
- host_names.fetch(host_id, "")
107
- else
108
- tag_values.fetch(tag_name, "")
109
- end
110
- }
111
- }
112
- [result, fields]
83
+ get_hosts_fields(host_ids, fields)
113
84
  else
114
85
  if @options[:listing]
115
86
  q1 = "SELECT DISTINCT tags.name FROM hosts_tags " \
@@ -124,61 +95,62 @@ module Hotdog
124
95
  tag_name == @options[:primary_tag]
125
96
  }
126
97
  }
98
+ get_hosts_fields(host_ids, fields)
127
99
  else
128
100
  fields = [
129
101
  "host",
130
102
  ] + host_ids.each_slice(SQLITE_LIMIT_COMPOUND_SELECT).flat_map { |host_ids|
131
103
  execute(q1 % host_ids.map { "?" }.join(", "), host_ids).map { |row| row.first }
132
104
  }
105
+ get_hosts_fields(host_ids, fields)
133
106
  end
134
- host_names = Hash[
135
- host_ids.each_slice(SQLITE_LIMIT_COMPOUND_SELECT).flat_map { |host_ids|
136
- execute("SELECT id, name FROM hosts WHERE id IN (%s)" % host_ids.map { "?" }.join(", "), host_ids).map { |row| row.to_a }
137
- }
138
- ]
139
- q2 = "SELECT tags.name, GROUP_CONCAT(tags.value, ',') FROM hosts_tags " \
140
- "INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
141
- "WHERE hosts_tags.host_id = ? AND tags.name IN (%s) " \
142
- "GROUP BY tags.name;"
143
- fields_without_host = fields.reject { |tag_name| tag_name == "host" }
144
- result = host_ids.map { |host_id|
145
- tag_values = Hash[
146
- fields_without_host.each_slice(SQLITE_LIMIT_COMPOUND_SELECT - 1).flat_map { |fields_without_host|
147
- execute(q2 % fields_without_host.map { "?" }.join(", "), [host_id] + fields_without_host).map { |row| row.to_a }
148
- }
149
- ]
150
- fields.map { |tag_name|
151
- if tag_name == "host"
152
- host_names.fetch(host_id, "")
153
- else
154
- tag_values.fetch(tag_name, "")
155
- end
156
- }
157
- }
158
- [result, fields]
159
107
  else
160
108
  if @options[:primary_tag]
161
- fields = [@options[:primary_tag]]
162
- q1 = "SELECT tags.value FROM hosts_tags " \
163
- "INNER JOIN hosts ON hosts_tags.host_id = hosts.id " \
164
- "INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
165
- "WHERE hosts_tags.host_id IN (%s) AND tags.name = ?;"
166
- result = host_ids.each_slice(SQLITE_LIMIT_COMPOUND_SELECT - 1).flat_map { |host_ids|
167
- execute(q1 % host_ids.map { "?" }.join(", "), host_ids + [@options[:primary_tag]]).map { |row| row.to_a }
168
- }
169
- [result, fields]
109
+ get_hosts_fields(host_ids, [@options[:primary_tag]])
170
110
  else
171
- fields = ["host"]
172
- result = host_ids.each_slice(SQLITE_LIMIT_COMPOUND_SELECT).flat_map { |host_ids|
173
- execute("SELECT name FROM hosts WHERE id IN (%s)" % host_ids.map { "?" }.join(", "), host_ids).map { |row| row.to_a }
174
- }
175
- [result, fields]
111
+ get_hosts_fields(host_ids, ["host"])
176
112
  end
177
113
  end
178
114
  end
179
115
  end
180
116
  end
181
117
 
118
+ def get_hosts_fields(host_ids, fields)
119
+ if fields.empty?
120
+ [[], fields]
121
+ else
122
+ fields_without_host = fields.reject { |tag_name| tag_name == "host" }
123
+ if fields == fields_without_host
124
+ host_names = {}
125
+ else
126
+ host_names = Hash[
127
+ host_ids.each_slice(SQLITE_LIMIT_COMPOUND_SELECT).flat_map { |host_ids|
128
+ execute("SELECT id, name FROM hosts WHERE id IN (%s)" % host_ids.map { "?" }.join(", "), host_ids).map { |row| row.to_a }
129
+ }
130
+ ]
131
+ end
132
+ q1 = "SELECT tags.name, GROUP_CONCAT(tags.value, ',') FROM hosts_tags " \
133
+ "INNER JOIN tags ON hosts_tags.tag_id = tags.id " \
134
+ "WHERE hosts_tags.host_id = ? AND tags.name IN (%s) " \
135
+ "GROUP BY tags.name;"
136
+ result = host_ids.map { |host_id|
137
+ tag_values = Hash[
138
+ fields_without_host.each_slice(SQLITE_LIMIT_COMPOUND_SELECT - 1).flat_map { |fields_without_host|
139
+ execute(q1 % fields_without_host.map { "?" }.join(", "), [host_id] + fields_without_host).map { |row| row.to_a }
140
+ }
141
+ ]
142
+ fields.map { |tag_name|
143
+ if tag_name == "host"
144
+ host_names.fetch(host_id, "")
145
+ else
146
+ tag_values.fetch(tag_name, "")
147
+ end
148
+ }
149
+ }
150
+ [result, fields]
151
+ end
152
+ end
153
+
182
154
  def close_db(db, options={})
183
155
  @prepared_statements = @prepared_statements.reject { |k, statement|
184
156
  (db.hash & MASK_DATABASE == k & MASK_DATABASE).tap do |delete_p|
@@ -1,3 +1,3 @@
1
1
  module Hotdog
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -0,0 +1,122 @@
1
+ require "spec_helper"
2
+ require "hotdog/application"
3
+ require "hotdog/commands"
4
+
5
+ describe "commands" do
6
+ let(:cmd) {
7
+ Hotdog::Commands::Search.new(Hotdog::Application.new)
8
+ }
9
+
10
+ before(:each) do
11
+ ENV["DATADOG_API_KEY"] = "DATADOG_API_KEY"
12
+ ENV["DATADOG_APPLICATION_KEY"] = "DATADOG_APPLICATION_KEY"
13
+ end
14
+
15
+ it "get empty hosts" do
16
+ cmd.options[:listing] = false
17
+ cmd.options[:primary_tag] = nil
18
+ allow(cmd).to receive(:update_db)
19
+ expect(cmd.__send__(:get_hosts, [], [])).to eq([[], []])
20
+ end
21
+
22
+ it "get hosts" do
23
+ cmd.options[:listing] = false
24
+ cmd.options[:primary_tag] = nil
25
+ allow(cmd).to receive(:update_db)
26
+ allow(cmd).to receive(:get_hosts_fields).with([1, 2, 3], ["host"])
27
+ expect(cmd.__send__(:get_hosts, [1, 2, 3], []))
28
+ end
29
+
30
+ it "get hosts with primary tag" do
31
+ cmd.options[:listing] = false
32
+ cmd.options[:primary_tag] = "foo"
33
+ allow(cmd).to receive(:update_db)
34
+ allow(cmd).to receive(:get_hosts_fields).with([1, 2, 3], ["foo"])
35
+ expect(cmd.__send__(:get_hosts, [1, 2, 3], []))
36
+ end
37
+
38
+ it "get hosts with tags" do
39
+ cmd.options[:listing] = false
40
+ cmd.options[:primary_tag] = nil
41
+ allow(cmd).to receive(:update_db)
42
+ allow(cmd).to receive(:get_hosts_fields).with([1, 2, 3], ["foo", "bar", "baz"])
43
+ expect(cmd.__send__(:get_hosts, [1, 2, 3], ["foo", "bar", "baz"]))
44
+ end
45
+
46
+ it "get hosts with all tags" do
47
+ cmd.options[:listing] = true
48
+ cmd.options[:primary_tag] = nil
49
+ allow(cmd).to receive(:update_db)
50
+ q1 = [
51
+ "SELECT DISTINCT tags.name FROM hosts_tags",
52
+ "INNER JOIN tags ON hosts_tags.tag_id = tags.id",
53
+ "WHERE hosts_tags.host_id IN (?, ?, ?);",
54
+ ]
55
+ allow(cmd).to receive(:execute).with(q1.join(" "), [1, 2, 3]) {
56
+ [["foo"], ["bar"], ["baz"]]
57
+ }
58
+ allow(cmd).to receive(:get_hosts_fields).with([1, 2, 3], ["host", "foo", "bar", "baz"])
59
+ expect(cmd.__send__(:get_hosts, [1, 2, 3], []))
60
+ end
61
+
62
+ it "get hosts with all tags with primary tag" do
63
+ cmd.options[:listing] = true
64
+ cmd.options[:primary_tag] = "bar"
65
+ allow(cmd).to receive(:update_db)
66
+ q1 = [
67
+ "SELECT DISTINCT tags.name FROM hosts_tags",
68
+ "INNER JOIN tags ON hosts_tags.tag_id = tags.id",
69
+ "WHERE hosts_tags.host_id IN (?, ?, ?);",
70
+ ]
71
+ allow(cmd).to receive(:execute).with(q1.join(" "), [1, 2, 3]) {
72
+ [["foo"], ["bar"], ["baz"]]
73
+ }
74
+ allow(cmd).to receive(:get_hosts_fields).with([1, 2, 3], ["bar", "host", "foo", "baz"])
75
+ expect(cmd.__send__(:get_hosts, [1, 2, 3], []))
76
+ end
77
+
78
+ it "get empty host fields" do
79
+ expect(cmd.__send__(:get_hosts_fields, [1, 2, 3], [])).to eq([[], []])
80
+ end
81
+
82
+ it "get host fields without host" do
83
+ q1 = [
84
+ "SELECT tags.name, GROUP_CONCAT(tags.value, ',') FROM hosts_tags",
85
+ "INNER JOIN tags ON hosts_tags.tag_id = tags.id",
86
+ "WHERE hosts_tags.host_id = ? AND tags.name IN (?, ?, ?)",
87
+ "GROUP BY tags.name;",
88
+ ]
89
+ allow(cmd).to receive(:execute).with(q1.join(" "), [1, "foo", "bar", "baz"]) {
90
+ [["foo", "foo1"], ["bar", "bar1"], ["baz", "baz1"]]
91
+ }
92
+ allow(cmd).to receive(:execute).with(q1.join(" "), [2, "foo", "bar", "baz"]) {
93
+ [["foo", "foo2"], ["bar", "bar2"], ["baz", "baz2"]]
94
+ }
95
+ allow(cmd).to receive(:execute).with(q1.join(" "), [3, "foo", "bar", "baz"]) {
96
+ [["foo", "foo3"], ["bar", "bar3"], ["baz", "baz3"]]
97
+ }
98
+ expect(cmd.__send__(:get_hosts_fields, [1, 2, 3], ["foo", "bar", "baz"])).to eq([[["foo1", "bar1", "baz1"], ["foo2", "bar2", "baz2"], ["foo3", "bar3", "baz3"]], ["foo", "bar", "baz"]])
99
+ end
100
+
101
+ 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
+ [[1, "host1"], [2, "host2"], [3, "host3"]]
104
+ }
105
+ q1 = [
106
+ "SELECT tags.name, GROUP_CONCAT(tags.value, ',') FROM hosts_tags",
107
+ "INNER JOIN tags ON hosts_tags.tag_id = tags.id",
108
+ "WHERE hosts_tags.host_id = ? AND tags.name IN (?, ?)",
109
+ "GROUP BY tags.name;",
110
+ ]
111
+ allow(cmd).to receive(:execute).with(q1.join(" "), [1, "foo", "bar"]) {
112
+ [["foo", "foo1"], ["bar", "bar1"]]
113
+ }
114
+ allow(cmd).to receive(:execute).with(q1.join(" "), [2, "foo", "bar"]) {
115
+ [["foo", "foo2"], ["bar", "bar2"]]
116
+ }
117
+ allow(cmd).to receive(:execute).with(q1.join(" "), [3, "foo", "bar"]) {
118
+ [["foo", "foo3"], ["bar", "bar3"]]
119
+ }
120
+ expect(cmd.__send__(:get_hosts_fields, [1, 2, 3], ["foo", "bar", "host"])).to eq([[["foo1", "bar1", "host1"], ["foo2", "bar2", "host2"], ["foo3", "bar3", "host3"]], ["foo", "bar", "host"]])
121
+ end
122
+ end
@@ -235,7 +235,7 @@ describe "parser" do
235
235
  end
236
236
 
237
237
  it "parses 'not foo and bar'" do
238
- expect(cmd.parse("not foo and bar")).to eq({left: {unary_op: "not", expression: {identifier: "foo"}}, binary_op: "and", right: {identifier: "bar"}})
238
+ expect(cmd.parse("not foo and bar")).to eq({unary_op: "not", expression: {left: {identifier: "foo"}, binary_op: "and", right: {identifier: "bar"}}})
239
239
  end
240
240
 
241
241
  it "parses '! foo and bar'" do
@@ -243,7 +243,7 @@ describe "parser" do
243
243
  end
244
244
 
245
245
  it "parses 'not foo && bar'" do
246
- expect(cmd.parse("not foo && bar")).to eq({left: {unary_op: "not", expression: {identifier: "foo"}}, binary_op: "&&", right: {identifier: "bar"}})
246
+ expect(cmd.parse("not foo && bar")).to eq({unary_op: "not", expression: {left: {identifier: "foo"}, binary_op: "&&", right: {identifier: "bar"}}})
247
247
  end
248
248
 
249
249
  it "parses '! foo && bar'" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hotdog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yamashita Yuu
@@ -145,6 +145,7 @@ files:
145
145
  - lib/hotdog/formatters/yaml.rb
146
146
  - lib/hotdog/version.rb
147
147
  - spec/core/application_spec.rb
148
+ - spec/core/commands_spec.rb
148
149
  - spec/formatter/csv_spec.rb
149
150
  - spec/formatter/json_spec.rb
150
151
  - spec/formatter/ltsv_spec.rb
@@ -183,6 +184,7 @@ specification_version: 4
183
184
  summary: Yet another command-line tool for Datadog
184
185
  test_files:
185
186
  - spec/core/application_spec.rb
187
+ - spec/core/commands_spec.rb
186
188
  - spec/formatter/csv_spec.rb
187
189
  - spec/formatter/json_spec.rb
188
190
  - spec/formatter/ltsv_spec.rb