hotdog 0.33.0 → 0.35.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: 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