hotdog 0.33.0 → 0.35.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d1245bbeae50ce94b1bcee1d3f8e25ae31cb2a7c
4
- data.tar.gz: e211f3e852912e103754077f51e1dc5e39118c8f
3
+ metadata.gz: 5c34bd3f352760468e86aecb50152087c9534938
4
+ data.tar.gz: 105739578293caaaf5b2264fc8c5614b1cccddda
5
5
  SHA512:
6
- metadata.gz: 78f9e8dde8de80e7d3bb24178e395e37314b6daf4c6375cb80cb456dc62647ded6a379d0ef6982cacd6581e7ac43f0d1447757cd05a4e1f551c36d9331c353a9
7
- data.tar.gz: aabd50ca10d6c384da7237cce23466231f007bc9fd224339f1153aec0cdc91539713d3f8d9b90e1ba80d67f819fa516119a5ea19bdf62d63efca20e4f5604fc6
6
+ metadata.gz: 7d095e791a616d81e24cc2510c6fcceb8ed1461075a622a08b90ab6c6b3e865ac343c28ce2dbc7fb22239ac8e143e744bb5f009278f34ed6d46b7660b84b8994
7
+ data.tar.gz: 6ffd19158c137ad8eea99942a38c3fda059f9fcdc668df8f4cb8442cac3f8f916addea202e635a01b14d02a26e7bab4fae53e77f33ac7a4b9c59956f306e83aa
@@ -6,6 +6,7 @@ require "optparse"
6
6
  require "yaml"
7
7
  require "hotdog/commands"
8
8
  require "hotdog/formatters"
9
+ require "hotdog/sources"
9
10
  require "hotdog/version"
10
11
 
11
12
  module Hotdog
@@ -40,9 +41,9 @@ module Hotdog
40
41
  @optparse = OptionParser.new
41
42
  @optparse.version = Hotdog::VERSION
42
43
  @options = {
43
- endpoint: ENV.fetch("DATADOG_HOST", "https://app.datadoghq.com"),
44
- api_key: ENV["DATADOG_API_KEY"],
45
- application_key: ENV["DATADOG_APPLICATION_KEY"],
44
+ endpoint: nil,
45
+ api_key: nil,
46
+ application_key: nil,
46
47
  application: self,
47
48
  confdir: find_confdir(File.expand_path(".")),
48
49
  debug: false,
@@ -51,7 +52,7 @@ module Hotdog
51
52
  force: false,
52
53
  format: "text",
53
54
  headers: false,
54
- source: nil,
55
+ source: "datadog",
55
56
  status: nil,
56
57
  listing: false,
57
58
  logger: @logger,
@@ -69,23 +70,36 @@ module Hotdog
69
70
  # reject nil values to declare sensible default later in subcommand
70
71
  val.nil?
71
72
  }
72
-
73
+ @source_provider = nil # will be initialized later in `main()`
73
74
  define_options
74
75
  end
75
76
  attr_reader :logger
76
77
  attr_reader :options
77
78
  attr_reader :optparse
79
+ attr_reader :source_provider
78
80
 
79
81
  def main(argv=[])
80
82
  config = File.join(options[:confdir], "config.yml")
81
83
  if File.file?(config)
82
- loaded = YAML.load(ERB.new(File.read(config)).result)
84
+ begin
85
+ loaded = YAML.load(ERB.new(File.read(config)).result)
86
+ rescue => error
87
+ STDERR.puts("hotdog: failed to load configuration file at #{config.inspect}: #{error}")
88
+ exit(1)
89
+ end
83
90
  if Hash === loaded
84
91
  @options = @options.merge(Hash[loaded.map { |key, value| [Symbol === key ? key : key.to_s.to_sym, value] }])
85
92
  end
86
93
  end
87
94
  args = @optparse.order(argv)
88
95
 
96
+ begin
97
+ @source_provider = get_source(@options[:source])
98
+ rescue NameError
99
+ STDERR.puts("hotdog: '#{@options[:source]}' is not a valid hotdog source.")
100
+ exit(1)
101
+ end
102
+
89
103
  begin
90
104
  command_name = ( args.shift || "help" )
91
105
  begin
@@ -137,42 +151,6 @@ module Hotdog
137
151
  end
138
152
  end
139
153
 
140
- def api_key()
141
- if options[:api_key]
142
- options[:api_key]
143
- else
144
- update_api_key!
145
- if options[:api_key]
146
- options[:api_key]
147
- else
148
- raise("DATADOG_API_KEY is not set")
149
- end
150
- end
151
- end
152
-
153
- def application_key()
154
- if options[:application_key]
155
- options[:application_key]
156
- else
157
- update_application_key!
158
- if options[:application_key]
159
- options[:application_key]
160
- else
161
- raise("DATADOG_APPLICATION_KEY is not set")
162
- end
163
- end
164
- end
165
-
166
- def source()
167
- options.fetch(:source, SOURCE_DATADOG)
168
- end
169
-
170
- def source_name(source=self.source)
171
- {
172
- SOURCE_DATADOG => "datadog",
173
- }.fetch(source, "unknown")
174
- end
175
-
176
154
  def status()
177
155
  options.fetch(:status, STATUS_RUNNING)
178
156
  end
@@ -230,16 +208,7 @@ module Hotdog
230
208
  options[:headers] = v
231
209
  end
232
210
  @optparse.on("--source=SOURCE", "Specify custom host source") do |v|
233
- case v
234
- when /\A\d\z/i
235
- options[:source] = v.to_i
236
- when /\A(?:all|any)\z/i
237
- options[:source] = nil
238
- when /\A(?:datadog)\z/i
239
- options[:source] = SOURCE_DATADOG
240
- else
241
- raise(OptionParser::InvalidArgument.new("unknown source: #{v}"))
242
- end
211
+ @options[:source] = v
243
212
  end
244
213
  @optparse.on("--status=STATUS", "Specify custom host status") do |v|
245
214
  case v
@@ -320,6 +289,21 @@ module Hotdog
320
289
  klass.new(self)
321
290
  end
322
291
 
292
+ def get_source(name)
293
+ begin
294
+ klass = Hotdog::Sources.const_get(const_name(name))
295
+ rescue NameError
296
+ library = find_library("hotdog/sources", name)
297
+ if library
298
+ load library
299
+ klass = Hotdog::Sources.const_get(const_name(File.basename(library, ".rb")))
300
+ else
301
+ raise(NameError.new("unknown source: #{name}"))
302
+ end
303
+ end
304
+ klass.new(self)
305
+ end
306
+
323
307
  def find_library(dirname, name)
324
308
  load_path = $LOAD_PATH.map { |path| File.join(path, dirname) }.select { |path| File.directory?(path) }
325
309
  libraries = load_path.flat_map { |path| Dir.glob(File.join(path, "*.rb")) }.select { |file| File.file?(file) }
@@ -354,46 +338,6 @@ module Hotdog
354
338
  end
355
339
  end
356
340
  end
357
-
358
- def update_api_key!()
359
- if options[:api_key_command]
360
- logger.info("api_key_command> #{options[:api_key_command]}")
361
- options[:api_key] = IO.popen(options[:api_key_command]) do |io|
362
- io.read.strip
363
- end
364
- unless $?.success?
365
- raise("failed: #{options[:api_key_command]}")
366
- end
367
- else
368
- update_keys!
369
- end
370
- end
371
-
372
- def update_application_key!()
373
- if options[:application_key_command]
374
- logger.info("application_key_command> #{options[:application_key_command]}")
375
- options[:application_key] = IO.popen(options[:application_key_command]) do |io|
376
- io.read.strip
377
- end
378
- unless $?.success?
379
- raise("failed: #{options[:application_key_command]}")
380
- end
381
- else
382
- update_keys!
383
- end
384
- end
385
-
386
- def update_keys!()
387
- if options[:key_command]
388
- logger.info("key_command> #{options[:key_command]}")
389
- options[:api_key], options[:application_key] = IO.popen(options[:key_command]) do |io|
390
- io.read.strip.split(":", 2)
391
- end
392
- unless $?.success?
393
- raise("failed: #{options[:key_command]}")
394
- end
395
- end
396
- end
397
341
  end
398
342
  end
399
343
 
@@ -1,22 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "fileutils"
4
- require "dogapi"
5
- require "multi_json"
6
- require "oj"
7
- require "open-uri"
8
- require "parallel"
9
4
  require "sqlite3"
10
- require "uri"
11
5
 
12
6
  module Hotdog
13
7
  module Commands
14
8
  class BaseCommand
15
9
  def initialize(application)
16
10
  @application = application
11
+ @source_provider = application.source_provider
17
12
  @logger = application.logger
18
13
  @options = application.options
19
- @dog = nil # lazy initialization
20
14
  @prepared_statements = {}
21
15
  @persistent_db_path = File.join(@options.fetch(:confdir, "."), "hotdog.sqlite3")
22
16
  end
@@ -262,25 +256,21 @@ module Hotdog
262
256
 
263
257
  def create_db(db, options={})
264
258
  options = @options.merge(options)
265
- requests = {all_downtimes: "/api/v1/downtime", all_tags: "/api/v1/tags/hosts"}
266
259
  begin
267
- parallelism = Parallel.processor_count
268
- # generate payload before forking threads to avoid fetching keys multiple times
269
- query = URI.encode_www_form(api_key: application.api_key, application_key: application.application_key)
270
- responses = Hash[Parallel.map(requests, in_threads: parallelism) { |name, request_path|
271
- [name, datadog_get(request_path, query)]
272
- }]
260
+ all_tags = @source_provider.get_all_tags()
261
+ all_downtimes = @source_provider.get_all_downtimes().flat_map { |downtime|
262
+ # find host scopes
263
+ Array(downtime["scope"]).select { |scope| scope.start_with?("host:") }.map { |scope| scope.sub(/\Ahost:/, "") }
264
+ }
273
265
  rescue => error
274
266
  STDERR.puts(error.message)
275
267
  exit(1)
276
268
  end
277
- all_tags = prepare_tags(responses.fetch(:all_tags, {}))
278
- all_downtimes = prepare_downtimes(responses.fetch(:all_downtimes, {}))
279
269
  if not all_downtimes.empty?
280
270
  logger.info("ignore host(s) with scheduled downtimes: #{all_downtimes.inspect}")
281
271
  end
282
272
  db.transaction do
283
- execute_db(db, "CREATE TABLE IF NOT EXISTS hosts (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(255) NOT NULL COLLATE NOCASE, source INTEGER NOT NULL DEFAULT #{SOURCE_DATADOG}, status INTEGER NOT NULL DEFAULT #{STATUS_PENDING});")
273
+ execute_db(db, "CREATE TABLE IF NOT EXISTS hosts (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(255) NOT NULL COLLATE NOCASE, source INTEGER NOT NULL DEFAULT #{@source_provider.id}, status INTEGER NOT NULL DEFAULT #{STATUS_PENDING});")
284
274
  execute_db(db, "CREATE UNIQUE INDEX IF NOT EXISTS hosts_name ON hosts (name);")
285
275
  execute_db(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);")
286
276
  execute_db(db, "CREATE UNIQUE INDEX IF NOT EXISTS tags_name_value ON tags (name, value);")
@@ -288,11 +278,7 @@ module Hotdog
288
278
  execute_db(db, "CREATE UNIQUE INDEX IF NOT EXISTS hosts_tags_host_id_tag_id ON hosts_tags (host_id, tag_id);")
289
279
 
290
280
  execute_db(db, "CREATE TABLE IF NOT EXISTS source_names (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(200) NOT NULL COLLATE NOCASE);")
291
- {
292
- SOURCE_DATADOG => application.source_name(SOURCE_DATADOG),
293
- }.each do |source_id, source_name|
294
- execute_db(db, "INSERT OR IGNORE INTO source_names (id, name) VALUES (?, ?);", [source_id, source_name])
295
- end
281
+ execute_db(db, "INSERT OR IGNORE INTO source_names (id, name) VALUES (?, ?);", [@source_provider.id, @source_provider.name])
296
282
 
297
283
  execute_db(db, "CREATE TABLE IF NOT EXISTS status_names (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(200) NOT NULL COLLATE NOCASE);")
298
284
  {
@@ -343,42 +329,12 @@ module Hotdog
343
329
  end
344
330
  end
345
331
 
346
- def datadog_get(request_path, query=nil)
347
- # TODO: make this pluggable
348
- endpoint = options[:endpoint]
349
- query ||= URI.encode_www_form(api_key: application.api_key, application_key: application.application_key)
350
- uri = URI.join(endpoint, "#{request_path}?#{query}")
351
- begin
352
- response = uri.open("User-Agent" => "hotdog/#{Hotdog::VERSION}") { |fp| fp.read }
353
- MultiJson.load(response)
354
- rescue OpenURI::HTTPError => error
355
- code, _body = error.io.status
356
- raise(RuntimeError.new("datadog: GET #{request_path} returns [#{code.inspect}, ...]"))
357
- end
358
- end
359
-
360
- def prepare_tags(tags)
361
- Hash(tags).fetch("tags", {})
362
- end
363
-
364
- def prepare_downtimes(downtimes)
365
- now = Time.new.to_i
366
- Array(downtimes).select { |downtime|
367
- # active downtimes
368
- downtime["active"] and ( downtime["start"].nil? or downtime["start"] < now ) and ( downtime["end"].nil? or now <= downtime["end"] ) and downtime["monitor_id"].nil?
369
- }.flat_map { |downtime|
370
- # find host scopes
371
- downtime["scope"].select { |scope| scope.start_with?("host:") }.map { |scope| scope.sub(/\Ahost:/, "") }
372
- }
373
- end
374
-
375
332
  def create_hosts(db, hosts, downtimes)
376
333
  hosts.each_slice(SQLITE_LIMIT_COMPOUND_SELECT / 3) do |hosts|
377
334
  q = "INSERT OR IGNORE INTO hosts (name, source, status) VALUES %s;" % hosts.map { "(?, ?, ?)" }.join(", ")
378
335
  execute_db(db, q, hosts.map { |host|
379
- source = SOURCE_DATADOG
380
336
  status = downtimes.include?(host) ? STATUS_STOPPED : STATUS_RUNNING
381
- [host, source, status]
337
+ [host, @source_provider.id, status]
382
338
  })
383
339
  end
384
340
 
@@ -447,10 +403,6 @@ module Hotdog
447
403
  end
448
404
  end
449
405
 
450
- def dog()
451
- @dog ||= Dogapi::Client.new(application.api_key, application.application_key)
452
- end
453
-
454
406
  def split_tag(tag)
455
407
  tagname, tagvalue = tag.split(":", 2)
456
408
  [rewrite_legacy_tagname(tagname), tagvalue || ""]
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "fileutils"
4
-
5
3
  module Hotdog
6
4
  module Commands
7
5
  class Down < BaseCommand
@@ -70,20 +68,10 @@ module Hotdog
70
68
  end
71
69
  scopes.each do |scope|
72
70
  with_retry(options) do
73
- schedule_downtime(scope, options)
71
+ @source_provider.schedule_downtime(scope, options)
74
72
  end
75
73
  end
76
74
  end
77
-
78
- private
79
- def schedule_downtime(scope, options={})
80
- code, schedule = dog.schedule_downtime(scope, :start => options[:start].to_i, :end => (options[:start]+options[:downtime]).to_i)
81
- logger.debug("dog.schedule_donwtime(%s, :start => %s, :end => %s) #==> [%s, %s]" % [scope.inspect, options[:start].to_i, (options[:start]+options[:downtime]).to_i, code.inspect, schedule.inspect])
82
- if code.to_i / 100 != 2
83
- raise("dog.schedule_downtime(%s, ...) returns [%s, %s]" % [scope.inspect, code.inspect, schedule.inspect])
84
- end
85
- schedule
86
- end
87
75
  end
88
76
  end
89
77
  end
@@ -120,10 +120,6 @@ module Hotdog
120
120
  # return everything if given expression is empty
121
121
  expression = "*"
122
122
  end
123
- if options[:source]
124
- source_name = application.source_name(options[:source])
125
- expression = "@source:#{source_name} AND (#{expression})"
126
- end
127
123
  if options[:status]
128
124
  status_name = application.status_name(options[:status])
129
125
  expression = "@status:#{status_name} AND (#{expression})"
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "json"
4
+ require "parallel"
4
5
  require "parslet"
5
6
  require "shellwords"
6
7
  require "thread"
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "fileutils"
4
-
5
3
  module Hotdog
6
4
  module Commands
7
5
  class Tag < BaseCommand
@@ -15,7 +13,7 @@ module Hotdog
15
13
  optparse.on("--retry-delay SECONDS") do |v|
16
14
  options[:retry_delay] = v.to_i
17
15
  end
18
- optparse.on("--source SOURCE") do |v|
16
+ optparse.on("--tag-source SOURCE") do |v|
19
17
  options[:tag_source] = v
20
18
  end
21
19
  optparse.on("-a TAG", "-t TAG", "--tag TAG", "Use specified tag name/value") do |v|
@@ -41,33 +39,16 @@ module Hotdog
41
39
  hosts.each do |host|
42
40
  if options[:tags].empty?
43
41
  # nop; just show current tags
44
- host_tags = with_retry { host_tags(host, source=options[:tag_source]) }
42
+ host_tags = with_retry { @source_provider.host_tags(host, source=options[:tag_source]) }
45
43
  STDOUT.puts host_tags['tags'].inspect
46
44
  else
47
45
  # add all as user tags
48
46
  with_retry(options) do
49
- add_tags(host, options[:tags], source=options[:tag_source])
47
+ @source_provider.add_tags(host, options[:tags], source=options[:tag_source])
50
48
  end
51
49
  end
52
50
  end
53
51
  end
54
-
55
- private
56
- def add_tags(host_name, tags, options={})
57
- code, resp = dog.add_tags(host_name, tags, options)
58
- if code.to_i / 100 != 2
59
- raise("dog.add_tags(#{host_name.inspect}, #{tags.inspect}, #{options.inspect}) returns [#{code.inspect}, #{resp.inspect}]")
60
- end
61
- resp
62
- end
63
-
64
- def host_tags(host_name, options={})
65
- code, host_tags = dog.host_tags(host_name, options)
66
- if code.to_i / 100 != 2
67
- raise("dog.host_tags(#{host_name.inspect}, #{options.inspect}) returns [#{code.inspect}, #{host_tags.inspect}]")
68
- end
69
- host_tags
70
- end
71
52
  end
72
53
  end
73
54
  end
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "fileutils"
4
-
5
3
  module Hotdog
6
4
  module Commands
7
5
  class Untag < BaseCommand
@@ -15,7 +13,7 @@ module Hotdog
15
13
  optparse.on("--retry-delay SECONDS") do |v|
16
14
  options[:retry_delay] = v.to_i
17
15
  end
18
- optparse.on("--source SOURCE") do |v|
16
+ optparse.on("--tag-source SOURCE") do |v|
19
17
  options[:tag_source] = v
20
18
  end
21
19
  optparse.on("-a TAG", "-t TAG", "--tag TAG", "Use specified tag name/value") do |v|
@@ -48,47 +46,22 @@ module Hotdog
48
46
  if options[:tags].empty?
49
47
  # delete all user tags
50
48
  with_retry do
51
- detach_tags(host, source=options[:tag_source])
49
+ @source_provider.detach_tags(host, source=options[:tag_source])
52
50
  end
53
51
  else
54
- host_tags = with_retry { host_tags(host, source=options[:tag_source]) }
52
+ host_tags = with_retry { @source_provider.host_tags(host, source=options[:tag_source]) }
55
53
  old_tags = host_tags["tags"]
56
54
  new_tags = old_tags - options[:tags]
57
55
  if old_tags == new_tags
58
56
  # nop
59
57
  else
60
58
  with_retry do
61
- update_tags(host, new_tags, source=options[:tag_source])
59
+ @source_provider.update_tags(host, new_tags, source=options[:tag_source])
62
60
  end
63
61
  end
64
62
  end
65
63
  end
66
64
  end
67
-
68
- private
69
- def detach_tags(host_name, options={})
70
- code, detach_tags = dog.detach_tags(host_name, options)
71
- if code.to_i / 100 != 2
72
- raise("dog.detach_tags(#{host_name.inspect}, #{options.inspect}) returns [#{code.inspect}, #{detach_tags.inspect}]")
73
- end
74
- detach_tags
75
- end
76
-
77
- def host_tags(host_name, options={})
78
- code, host_tags = dog.host_tags(host_name, options)
79
- if code.to_i / 100 != 2
80
- raise("dog.host_tags(#{host_name.inspect}, #{options.inspect}) returns [#{code.inspect}, #{host_tags.inspect}]")
81
- end
82
- host_tags
83
- end
84
-
85
- def update_tags(host_name, tags, options={})
86
- code, update_tags = dog.update_tags(host_name, tags, options)
87
- if code.to_i / 100 != 2
88
- raise("dog.update_tags(#{host_name.inspect}, #{tags.inspect}, #{options.inspect}) returns [#{code.inspect}, #{update_tags.inspect}]")
89
- end
90
- update_tags
91
- end
92
65
  end
93
66
  end
94
67
  end
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "fileutils"
4
-
5
3
  module Hotdog
6
4
  module Commands
7
5
  class Up < BaseCommand
@@ -25,7 +23,7 @@ module Hotdog
25
23
  }
26
24
  all_downtimes = nil
27
25
  with_retry(options) do
28
- all_downtimes = get_all_downtimes(options)
26
+ all_downtimes = @source_provider.get_all_downtimes(options)
29
27
  end
30
28
 
31
29
  cancel_downtimes = all_downtimes.select { |downtime|
@@ -34,7 +32,7 @@ module Hotdog
34
32
 
35
33
  cancel_downtimes.each do |downtime|
36
34
  with_retry(options) do
37
- cancel_downtime(downtime["id"], options)
35
+ @source_provider.cancel_downtime(downtime["id"], options)
38
36
  end
39
37
  end
40
38
 
@@ -57,23 +55,6 @@ module Hotdog
57
55
  end
58
56
  end
59
57
  end
60
-
61
- private
62
- def get_all_downtimes(options={})
63
- code, all_downtimes = dog.get_all_downtimes()
64
- if code.to_i / 100 != 2
65
- raise("dog.get_all_downtimes() returns [%s, %s]" % [code.inspect, all_downtimes.inspect])
66
- end
67
- all_downtimes
68
- end
69
-
70
- def cancel_downtime(id, options={})
71
- code, cancel = dog.cancel_downtime(id)
72
- if code.to_i / 100 != 2
73
- raise("dog.cancel_downtime(%s) returns [%s, %s]" % [id.inspect, code.inspect, cancel.inspect])
74
- end
75
- cancel
76
- end
77
58
  end
78
59
  end
79
60
  end
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module Hotdog
4
+ module Sources
5
+ class BaseSource
6
+ def initialize(application)
7
+ @application = application
8
+ @logger = application.logger
9
+ @options = application.options
10
+ end
11
+ attr_reader :application
12
+ attr_reader :logger
13
+ attr_reader :options
14
+
15
+ def id() #=> Integer
16
+ raise(NotImplementedError)
17
+ end
18
+
19
+ def name() #=> String
20
+ raise(NotImplementedError)
21
+ end
22
+
23
+ def endpoint() #=> String
24
+ options[:endpoint]
25
+ end
26
+
27
+ def api_key() #=> String
28
+ options[:api_key]
29
+ end
30
+
31
+ def application_key() #=> String
32
+ options[:application_key]
33
+ end
34
+
35
+ def schedule_downtime(scope, options={})
36
+ raise(NotImplementedError)
37
+ end
38
+
39
+ def cancel_downtime(id, options={})
40
+ raise(NotImplementedError)
41
+ end
42
+
43
+ def get_all_downtimes(options={})
44
+ #
45
+ # This should return some `Array<Hash<String,String>>` like follows
46
+ #
47
+ # ```json
48
+ # [
49
+ # {
50
+ # "recurrence": null,
51
+ # "end": 1533593208,
52
+ # "monitor_tags": [
53
+ # "*"
54
+ # ],
55
+ # "canceled": null,
56
+ # "monitor_id": null,
57
+ # "org_id": 12345,
58
+ # "disabled": false,
59
+ # "start": 1533592608,
60
+ # "creator_id": 78913,
61
+ # "parent_id": null,
62
+ # "timezone": "UTC",
63
+ # "active": false,
64
+ # "scope": [
65
+ # "host:i-abcdef01234567890"
66
+ # ],
67
+ # "message": null,
68
+ # "downtime_type": null,
69
+ # "id": 278432422,
70
+ # "updater_id": null
71
+ # }
72
+ # ]
73
+ # ```
74
+ #
75
+ raise(NotImplementedError)
76
+ end
77
+
78
+ def get_all_tags(options={})
79
+ #
80
+ # This should return some `Hash<String,Array<String>>` like follows
81
+ #
82
+ # ```json
83
+ # {
84
+ # "tagname:tagvalue": [
85
+ # "foo",
86
+ # "bar",
87
+ # "baz"
88
+ # ]
89
+ # }
90
+ # ```
91
+ #
92
+ raise(NotImplementedError)
93
+ end
94
+
95
+ def get_host_tags(host_name, options={})
96
+ raise(NotImplementedError)
97
+ end
98
+
99
+ def add_tags(host_name, tags, options={})
100
+ raise(NotImplementedError)
101
+ end
102
+
103
+ def detach_tags(host_name, options={})
104
+ raise(NotImplementedError)
105
+ end
106
+
107
+ def update_tags(host_name, tags, options={})
108
+ raise(NotImplementedError)
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "dogapi"
4
+ require "multi_json"
5
+ require "oj"
6
+ require "open-uri"
7
+ require "uri"
8
+
9
+ module Hotdog
10
+ module Sources
11
+ class Datadog < BaseSource
12
+ def initialize(application)
13
+ super(application)
14
+ options[:endpoint] = ENV.fetch("DATADOG_HOST", "https://app.datadoghq.com")
15
+ options[:api_key] = ENV["DATADOG_API_KEY"]
16
+ options[:application_key] = ENV["DATADOG_APPLICATION_KEY"]
17
+ @dog = nil # lazy initialization
18
+ end
19
+
20
+ def id()
21
+ Hotdog::SOURCE_DATADOG
22
+ end
23
+
24
+ def name()
25
+ "datadog"
26
+ end
27
+
28
+ def endpoint()
29
+ options[:endpoint]
30
+ end
31
+
32
+ def api_key()
33
+ if options[:api_key]
34
+ options[:api_key]
35
+ else
36
+ update_api_key!
37
+ if options[:api_key]
38
+ options[:api_key]
39
+ else
40
+ raise("DATADOG_API_KEY is not set")
41
+ end
42
+ end
43
+ end
44
+
45
+ def application_key()
46
+ if options[:application_key]
47
+ options[:application_key]
48
+ else
49
+ update_application_key!
50
+ if options[:application_key]
51
+ options[:application_key]
52
+ else
53
+ raise("DATADOG_APPLICATION_KEY is not set")
54
+ end
55
+ end
56
+ end
57
+
58
+ def schedule_downtime(scope, options={})
59
+ code, schedule = dog.schedule_downtime(scope, :start => options[:start].to_i, :end => (options[:start]+options[:downtime]).to_i)
60
+ logger.debug("dog.schedule_donwtime(%s, :start => %s, :end => %s) #==> [%s, %s]" % [scope.inspect, options[:start].to_i, (options[:start]+options[:downtime]).to_i, code.inspect, schedule.inspect])
61
+ if code.to_i / 100 != 2
62
+ raise("dog.schedule_downtime(%s, ...) returns [%s, %s]" % [scope.inspect, code.inspect, schedule.inspect])
63
+ end
64
+ schedule
65
+ end
66
+
67
+ def cancel_downtime(id, options={})
68
+ code, cancel = dog.cancel_downtime(id)
69
+ if code.to_i / 100 != 2
70
+ raise("dog.cancel_downtime(%s) returns [%s, %s]" % [id.inspect, code.inspect, cancel.inspect])
71
+ end
72
+ cancel
73
+ end
74
+
75
+ def get_all_downtimes(options={})
76
+ now = Time.new.to_i
77
+ Array(datadog_get("/api/v1/downtime")).select { |downtime|
78
+ # active downtimes
79
+ downtime["active"] and ( downtime["start"].nil? or downtime["start"] < now ) and ( downtime["end"].nil? or now <= downtime["end"] ) and downtime["monitor_id"].nil?
80
+ }
81
+ end
82
+
83
+ def get_all_tags(options={})
84
+ Hash(datadog_get("/api/v1/tags/hosts")).fetch("tags", {})
85
+ end
86
+
87
+ def get_host_tags(host_name, options={})
88
+ code, host_tags = dog.host_tags(host_name, options)
89
+ if code.to_i / 100 != 2
90
+ raise("dog.host_tags(#{host_name.inspect}, #{options.inspect}) returns [#{code.inspect}, #{host_tags.inspect}]")
91
+ end
92
+ host_tags
93
+ end
94
+
95
+ def add_tags(host_name, tags, options={})
96
+ code, resp = dog.add_tags(host_name, tags, options)
97
+ if code.to_i / 100 != 2
98
+ raise("dog.add_tags(#{host_name.inspect}, #{tags.inspect}, #{options.inspect}) returns [#{code.inspect}, #{resp.inspect}]")
99
+ end
100
+ resp
101
+ end
102
+
103
+ def detach_tags(host_name, options={})
104
+ code, detach_tags = dog.detach_tags(host_name, options)
105
+ if code.to_i / 100 != 2
106
+ raise("dog.detach_tags(#{host_name.inspect}, #{options.inspect}) returns [#{code.inspect}, #{detach_tags.inspect}]")
107
+ end
108
+ detach_tags
109
+ end
110
+
111
+ def update_tags(host_name, tags, options={})
112
+ code, update_tags = dog.update_tags(host_name, tags, options)
113
+ if code.to_i / 100 != 2
114
+ raise("dog.update_tags(#{host_name.inspect}, #{tags.inspect}, #{options.inspect}) returns [#{code.inspect}, #{update_tags.inspect}]")
115
+ end
116
+ update_tags
117
+ end
118
+
119
+ private
120
+ def dog()
121
+ @dog ||= Dogapi::Client.new(self.api_key, self.application_key)
122
+ end
123
+
124
+ def datadog_get(request_path, query=nil)
125
+ query ||= URI.encode_www_form(api_key: self.api_key, application_key: self.application_key)
126
+ uri = URI.join(self.endpoint, "#{request_path}?#{query}")
127
+ begin
128
+ response = uri.open("User-Agent" => "hotdog/#{Hotdog::VERSION}") { |fp| fp.read }
129
+ MultiJson.load(response)
130
+ rescue OpenURI::HTTPError => error
131
+ code, _body = error.io.status
132
+ raise(RuntimeError.new("datadog: GET #{request_path} returns [#{code.inspect}, ...]"))
133
+ end
134
+ end
135
+
136
+ def update_api_key!()
137
+ if options[:api_key_command]
138
+ logger.info("api_key_command> #{options[:api_key_command]}")
139
+ options[:api_key] = IO.popen(options[:api_key_command]) do |io|
140
+ io.read.strip
141
+ end
142
+ unless $?.success?
143
+ raise("failed: #{options[:api_key_command]}")
144
+ end
145
+ else
146
+ update_keys!
147
+ end
148
+ end
149
+
150
+ def update_application_key!()
151
+ if options[:application_key_command]
152
+ logger.info("application_key_command> #{options[:application_key_command]}")
153
+ options[:application_key] = IO.popen(options[:application_key_command]) do |io|
154
+ io.read.strip
155
+ end
156
+ unless $?.success?
157
+ raise("failed: #{options[:application_key_command]}")
158
+ end
159
+ else
160
+ update_keys!
161
+ end
162
+ end
163
+
164
+ def update_keys!()
165
+ if options[:key_command]
166
+ logger.info("key_command> #{options[:key_command]}")
167
+ options[:api_key], options[:application_key] = IO.popen(options[:key_command]) do |io|
168
+ io.read.strip.split(":", 2)
169
+ end
170
+ unless $?.success?
171
+ raise("failed: #{options[:key_command]}")
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ # vim:set ft=ruby :
@@ -1,3 +1,3 @@
1
1
  module Hotdog
2
- VERSION = "0.33.0"
2
+ VERSION = "0.35.0"
3
3
  end
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.33.0
4
+ version: 0.35.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: 2018-05-22 00:00:00.000000000 Z
11
+ date: 2018-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -179,6 +179,8 @@ files:
179
179
  - lib/hotdog/formatters/text.rb
180
180
  - lib/hotdog/formatters/tsv.rb
181
181
  - lib/hotdog/formatters/yaml.rb
182
+ - lib/hotdog/sources.rb
183
+ - lib/hotdog/sources/datadog.rb
182
184
  - lib/hotdog/version.rb
183
185
  - spec/core/application_spec.rb
184
186
  - spec/core/commands_spec.rb