Class: DogTrainer::API
- Inherits:
-
Object
- Object
- DogTrainer::API
- Includes:
- Logging
- Defined in:
- lib/dogtrainer/api.rb
Overview
Helper methods to upsert/ensure existence and configuration of DataDog Monitors, TimeBoards and ScreenBoards.
Instance Method Summary collapse
-
#check_dog_result(r, accepted_codes = ['200']) ⇒ Object
Check the result of a Dogapi::Client call.
-
#create_monitor(_mon_name, mon_params) ⇒ Object
Create a monitor that doesn’t already exist; return its id.
-
#generate_messages(metric_desc, comparison, mon_type) ⇒ Object
Given the name of a metric we’re monitoring and the comparison method, generate alert messages for the monitor.
-
#get_existing_monitor_by_name(mon_name) ⇒ Object
Get all monitors from DataDog; return the one named “mon_name“ or nil.
-
#get_existing_screenboard_by_name(dash_name) ⇒ Object
get all screenboards from DataDog; return the one named “dash_name“ or nil returns the screenboard definition hash from the DataDog API.
-
#get_existing_timeboard_by_name(dash_name) ⇒ Object
get all timeboards from DataDog; return the one named “dash_name“ or nil returns the timeboard definition hash from the DataDog API.
-
#get_git_url_for_directory(dir_path) ⇒ Object
Given the path to a directory on disk that may be a git repository, return the URL to its first remote, or nil otherwise.
-
#get_monitors ⇒ Object
Get all monitors from DataDog, caching them in an instance variable.
-
#get_repo_path ⇒ Object
Return a human-usable string identifying where to make changes to the resources created by this class.
-
#graphdef(title, queries, markers = {}) ⇒ Object
Create a graph definition (graphdef) to use with Boards APIs.
-
#initialize(api_key, app_key, notify_to, repo_path = nil) ⇒ API
constructor
Initialize class; set instance configuration.
-
#mute_monitor_by_id(mon_id, options = { end_timestamp: nil }) ⇒ Object
Mute the monitor identified by the specified unique ID, with an optional duration.
-
#mute_monitor_by_name(mon_name, options = { end_timestamp: nil }) ⇒ Object
Mute the monitor identified by the specified name, with an optional duration.
-
#mute_monitors_by_regex(mon_name_regex, options = { end_timestamp: nil }) ⇒ Object
Mute all monitors with names matching the specified regex, with an optional duration.
-
#params_for_monitor(name, message, query, threshold, options = { escalation_message: nil, alert_no_data: true, mon_type: 'metric alert', renotify_interval: 60, no_data_timeframe: 20, evaluation_delay: nil }) ⇒ Object
Return a hash of parameters for a monitor with the specified configuration.
-
#unmute_monitor_by_id(mon_id) ⇒ Object
Unute the monitor identified by the specified unique ID.
-
#unmute_monitor_by_name(mon_name) ⇒ Object
Unmute the monitor identified by the specified name.
-
#unmute_monitors_by_regex(mon_name_regex) ⇒ Object
Unmute all monitors with names matching the specified regex.
-
#upsert_monitor(mon_name, query, threshold, comparator, options = { alert_no_data: true, mon_type: 'metric alert', renotify_interval: 60, no_data_timeframe: 20, evaluation_delay: nil, message: nil }) ⇒ Object
Create or update a monitor in DataDog with the given name and data/params.
-
#upsert_screenboard(dash_name, widgets) ⇒ Object
Create or update a screenboard in DataDog with the given name and data/params.
-
#upsert_timeboard(dash_name, graphs) ⇒ Object
Create or update a timeboard in DataDog with the given name and data/params.
Methods included from Logging
debug_formatter, default_formatter, default_outputter, #logger, #logger_name
Constructor Details
#initialize(api_key, app_key, notify_to, repo_path = nil) ⇒ API
Initialize class; set instance configuration.
25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/dogtrainer/api.rb', line 25 def initialize(api_key, app_key, notify_to, repo_path = nil) logger.debug 'initializing DataDog API client' @dog = Dogapi::Client.new(api_key, app_key) @monitors = nil @timeboards = nil @screenboards = nil @notify_to = notify_to if repo_path.nil? @repo_path = get_repo_path logger.debug "using repo_path: #{@repo_path}" else @repo_path = repo_path end end |
Instance Method Details
#check_dog_result(r, accepted_codes = ['200']) ⇒ Object
Check the result of a Dogapi::Client call.
Dogapi::Client returns responses as arrays, with the first element being the HTTP response code and the second element being the actual response.
Check the specified
50 51 52 |
# File 'lib/dogtrainer/api.rb', line 50 def check_dog_result(r, accepted_codes = ['200']) raise DogApiException, r unless accepted_codes.include?(r[0]) end |
#create_monitor(_mon_name, mon_params) ⇒ Object
Create a monitor that doesn’t already exist; return its id
361 362 363 364 365 366 367 368 369 |
# File 'lib/dogtrainer/api.rb', line 361 def create_monitor(_mon_name, mon_params) res = @dog.monitor(mon_params['type'], mon_params['query'], mon_params) if res[0] == '200' logger.info "\tMonitor #{res[1]['id']} created successfully" return res[1]['id'] else logger.error "\tError creating monitor: #{res}" end end |
#generate_messages(metric_desc, comparison, mon_type) ⇒ Object
Given the name of a metric we’re monitoring and the comparison method, generate alert messages for the monitor.
This method is intended for internal use by the class, but can be overridden if the implementation is not desired.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/dogtrainer/api.rb', line 117 def (metric_desc, comparison, mon_type) if mon_type == 'service check' = [ "{{#is_alert}}'#{metric_desc}' is FAILING: {{check_message}}", "{{/is_alert}}\n", "{{#is_warning}}'#{metric_desc}' is WARNING: {{check_message}}", "{{/is_warning}}\n", "{{#is_recovery}}'#{metric_desc}' recovered: {{check_message}}", "{{/is_recovery}}\n", "{{#is_no_data}}'#{metric_desc}' is not reporting data", "{{/is_no_data}}\n", # repo path and notify to '(monitor and threshold configuration for this alert is managed by ', "#{@repo_path}) #{@notify_to}" ].join('') escalation = "'#{metric_desc}' is still in error state: " \ '{{check_message}}' return [, escalation] end = [ "{{#is_alert}}'#{metric_desc}' should be #{comparison} {{threshold}}, ", "but is {{value}}.{{/is_alert}}\n", "{{#is_recovery}}'#{metric_desc}' recovered (current value {{value}} ", "is #{comparison} threshold of {{threshold}}).{{/is_recovery}}\n", '(monitor and threshold configuration for this alert is managed by ', "#{@repo_path}) #{@notify_to}" ].join('') escalation = "'#{metric_desc}' is still in error state (current value " \ "{{value}} is #{comparison} threshold of {{threshold}})" [, escalation] end |
#get_existing_monitor_by_name(mon_name) ⇒ Object
Get all monitors from DataDog; return the one named “mon_name“ or nil
This caches all monitors from DataDog in an instance variable.
376 377 378 379 380 381 |
# File 'lib/dogtrainer/api.rb', line 376 def get_existing_monitor_by_name(mon_name) get_monitors.each do |mon| return mon if mon['name'] == mon_name end nil end |
#get_existing_screenboard_by_name(dash_name) ⇒ Object
get all screenboards from DataDog; return the one named “dash_name“ or nil returns the screenboard definition hash from the DataDog API
688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 |
# File 'lib/dogtrainer/api.rb', line 688 def get_existing_screenboard_by_name(dash_name) if @screenboards.nil? @screenboards = @dog.get_all_screenboards puts "Found #{@screenboards[1]['screenboards'].length} existing " \ 'screenboards in DataDog' if @screenboards[1]['screenboards'].empty? puts 'ERROR: Docker API call returned no existing screenboards. ' \ 'Something is wrong.' exit 1 end end @screenboards[1]['screenboards'].each do |dash| return @dog.get_screenboard(dash['id'])[1] if dash['title'] == dash_name end nil end |
#get_existing_timeboard_by_name(dash_name) ⇒ Object
get all timeboards from DataDog; return the one named “dash_name“ or nil returns the timeboard definition hash from the DataDog API
669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 |
# File 'lib/dogtrainer/api.rb', line 669 def get_existing_timeboard_by_name(dash_name) if @timeboards.nil? @timeboards = @dog.get_dashboards puts "Found #{@timeboards[1]['dashes'].length} existing timeboards " \ 'in DataDog' if @timeboards[1]['dashes'].empty? puts 'ERROR: Docker API call returned no existing timeboards. ' \ 'Something is wrong.' exit 1 end end @timeboards[1]['dashes'].each do |dash| return @dog.get_dashboard(dash['id'])[1] if dash['title'] == dash_name end nil end |
#get_git_url_for_directory(dir_path) ⇒ Object
Given the path to a directory on disk that may be a git repository, return the URL to its first remote, or nil otherwise.
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/dogtrainer/api.rb', line 84 def get_git_url_for_directory(dir_path) logger.debug "trying to find git remote for: #{dir_path}" conf = nil Dir.chdir(dir_path) do begin conf = `git config --local -l` rescue conf = nil end end return nil if conf.nil? conf.split("\n").each do |line| return Regexp.last_match(1) if line =~ /^remote\.[^\.]+\.url=(.+)/ end nil end |
#get_monitors ⇒ Object
Get all monitors from DataDog, caching them in an instance variable.
384 385 386 387 388 389 390 391 392 393 394 |
# File 'lib/dogtrainer/api.rb', line 384 def get_monitors if @monitors.nil? @monitors = @dog.get_all_monitors(group_states: 'all') logger.info "Found #{@monitors[1].length} existing monitors in DataDog" if @monitors[1].empty? raise 'ERROR: DataDog API call returned no existing monitors. ' \ 'Something is wrong.' end end @monitors[1] end |
#get_repo_path ⇒ Object
Return a human-usable string identifying where to make changes to the resources created by this class. Returns the first of:
-
“GIT_URL“ environment variable, if set and not empty
-
“CIRCLE_REPOSITORY_URL“ environment variable, if set and not empty
-
If the code calling this class is part of a git repository on disk and “git“ is present on the system and in PATH, the URL of the first remote for the repository.
If none of these are found, an error will be raised.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/dogtrainer/api.rb', line 64 def get_repo_path %w[GIT_URL CIRCLE_REPOSITORY_URL].each do |vname| return ENV[vname] if ENV.has_key?(vname) && !ENV[vname].empty? end # try to find git repository # get the path to the calling code; # caller[0] is #initialize, caller[1] is what instantiated the class path, = caller[1].partition(':') repo_path = get_git_url_for_directory(File.dirname(path)) if repo_path.nil? raise 'Unable to determine source code path; please ' \ 'specify repo_path option to DogTrainer::API' end repo_path end |
#graphdef(title, queries, markers = {}) ⇒ Object
Create a graph definition (graphdef) to use with Boards APIs. For further information, see: docs.datadoghq.com/graphingjson/
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 |
# File 'lib/dogtrainer/api.rb', line 544 def graphdef(title, queries, markers = {}) queries = [queries] unless queries.is_a?(Array) d = { 'definition' => { 'viz' => 'timeseries', 'requests' => [] }, 'title' => title } queries.each do |q| d['definition']['requests'] << { 'q' => q, 'conditional_formats' => [], 'type' => 'line' } end unless markers.empty? d['definition']['markers'] = [] markers.each do |name, val| d['definition']['markers'] << { 'type' => 'error dashed', 'val' => val.to_s, 'value' => "y = #{val}", 'label' => "#{name}==#{val}" } end end d end |
#mute_monitor_by_id(mon_id, options = { end_timestamp: nil }) ⇒ Object
Mute the monitor identified by the specified unique ID, with an optional duration.
412 413 414 415 416 417 418 419 420 421 |
# File 'lib/dogtrainer/api.rb', line 412 def mute_monitor_by_id(mon_id, = { end_timestamp: nil }) if .fetch(:end_timestamp, nil).nil? logger.info "Muting monitor by ID #{mon_id}" check_dog_result(@dog.mute_monitor(mon_id)) else end_ts = [:end_timestamp] logger.info "Muting monitor by ID #{mon_id} until #{end_ts}" check_dog_result(@dog.mute_monitor(mon_id, end: end_ts)) end end |
#mute_monitor_by_name(mon_name, options = { end_timestamp: nil }) ⇒ Object
Mute the monitor identified by the specified name, with an optional duration.
440 441 442 443 444 445 446 447 448 449 450 451 452 |
# File 'lib/dogtrainer/api.rb', line 440 def mute_monitor_by_name(mon_name, = { end_timestamp: nil }) mon = get_existing_monitor_by_name(mon_name) raise "ERROR: Could not find monitor with name #{mon_name}" if mon.nil? if .fetch(:end_timestamp, nil).nil? logger.info "Muting monitor by name #{mon_name} (#{mon['id']})" check_dog_result(@dog.mute_monitor(mon['id'])) else end_ts = [:end_timestamp] logger.info "Muting monitor by name #{mon_name} (#{mon['id']}) " \ "until #{end_ts}" check_dog_result(@dog.mute_monitor(mon['id'], end: end_ts)) end end |
#mute_monitors_by_regex(mon_name_regex, options = { end_timestamp: nil }) ⇒ Object
Mute all monitors with names matching the specified regex, with an optional duration.
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 |
# File 'lib/dogtrainer/api.rb', line 475 def mute_monitors_by_regex(mon_name_regex, = { end_timestamp: nil }) if mon_name_regex.class != Regexp mon_name_regex = Regexp.new(mon_name_regex) end if .fetch(:end_timestamp, nil).nil? logger.info "Muting monitors by regex #{mon_name_regex.source}" end_ts = nil else logger.info "Muting monitors by regex #{mon_name_regex.source} " \ "until #{end_ts}" end_ts = [:end_timestamp] end logger.debug "Searching for monitors matching: #{mon_name_regex.source}" get_monitors.each do |mon| if mon['name'] =~ mon_name_regex logger.info "Muting monitor '#{mon['name']}' (#{mon['id']})" mute_monitor_by_id(mon['id'], end_timestamp: end_ts) end end end |
#params_for_monitor(name, message, query, threshold, options = { escalation_message: nil, alert_no_data: true, mon_type: 'metric alert', renotify_interval: 60, no_data_timeframe: 20, evaluation_delay: nil }) ⇒ Object
Return a hash of parameters for a monitor with the specified configuration. For further information, see: docs.datadoghq.com/api/#monitors
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/dogtrainer/api.rb', line 182 def params_for_monitor( name, , query, threshold, = { escalation_message: nil, alert_no_data: true, mon_type: 'metric alert', renotify_interval: 60, no_data_timeframe: 20, evaluation_delay: nil } ) [:alert_no_data] = true unless .key?(:alert_no_data) [:mon_type] = 'metric alert' unless .key?(:mon_type) [:renotify_interval] = 60 unless .key?(:renotify_interval) [:no_data_timeframe] = 20 unless .key?(:no_data_timeframe) [:evaluation_delay] = nil unless .key?(:evaluation_delay) # handle threshold hash thresh = if threshold.is_a?(Hash) threshold else { 'critical' => threshold } end monitor_data = { 'name' => name, 'type' => [:mon_type], 'query' => query, 'message' => , 'tags' => [], 'options' => { 'notify_audit' => false, 'locked' => false, 'timeout_h' => 0, 'silenced' => {}, 'thresholds' => thresh, 'require_full_window' => false, 'notify_no_data' => [:alert_no_data], 'renotify_interval' => [:renotify_interval], 'no_data_timeframe' => [:no_data_timeframe] } } unless [:escalation_message].nil? monitor_data['options']['escalation_message'] = \ [:escalation_message] end unless [:evaluation_delay].nil? monitor_data['options']['evaluation_delay'] = [:evaluation_delay] end monitor_data end |
#unmute_monitor_by_id(mon_id) ⇒ Object
Unute the monitor identified by the specified unique ID.
500 501 502 503 |
# File 'lib/dogtrainer/api.rb', line 500 def unmute_monitor_by_id(mon_id) logger.info "Unmuting monitor by ID #{mon_id}" check_dog_result(@dog.unmute_monitor(mon_id, all_scopes: true)) end |
#unmute_monitor_by_name(mon_name) ⇒ Object
Unmute the monitor identified by the specified name.
509 510 511 512 513 514 |
# File 'lib/dogtrainer/api.rb', line 509 def unmute_monitor_by_name(mon_name) mon = get_existing_monitor_by_name(mon_name) logger.info "Unmuting monitor by name #{mon_name}" raise "ERROR: Could not find monitor with name #{mon_name}" if mon.nil? unmute_monitor_by_id(mon['id']) end |
#unmute_monitors_by_regex(mon_name_regex) ⇒ Object
Unmute all monitors with names matching the specified regex.
519 520 521 522 523 524 525 526 527 528 529 530 |
# File 'lib/dogtrainer/api.rb', line 519 def unmute_monitors_by_regex(mon_name_regex) if mon_name_regex.class != Regexp mon_name_regex = Regexp.new(mon_name_regex) end logger.info "Unmuting monitors by regex #{mon_name_regex.source}" get_monitors.each do |mon| if mon['name'] =~ mon_name_regex logger.info "Unmuting monitor '#{mon['name']}' (#{mon['id']})" unmute_monitor_by_id(mon['id']) end end end |
#upsert_monitor(mon_name, query, threshold, comparator, options = { alert_no_data: true, mon_type: 'metric alert', renotify_interval: 60, no_data_timeframe: 20, evaluation_delay: nil, message: nil }) ⇒ Object
Create or update a monitor in DataDog with the given name and data/params. This method handles either creating the monitor if one with the same name doesn’t already exist in the specified DataDog account, or else updating an existing monitor with the same name if one exists but the parameters differ.
For further information on parameters and options, see: docs.datadoghq.com/api/#monitors
This method calls #generate_messages to build the notification messages and #params_for_monitor to generate the parameters.
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 |
# File 'lib/dogtrainer/api.rb', line 284 def upsert_monitor( mon_name, query, threshold, comparator, = { alert_no_data: true, mon_type: 'metric alert', renotify_interval: 60, no_data_timeframe: 20, evaluation_delay: nil, message: nil } ) [:alert_no_data] = true unless .key?(:alert_no_data) [:mon_type] = 'metric alert' unless .key?(:mon_type) [:renotify_interval] = 60 unless .key?(:renotify_interval) [:no_data_timeframe] = 20 unless .key?(:no_data_timeframe) [:evaluation_delay] = nil unless .key?(:evaluation_delay) msg, esc = (mon_name, comparator, [:mon_type]) = if [:message].nil? msg else [:message] end escalation = if .key?(:escalation_message) [:escalation_message] else esc end rno = [:renotify_interval] mon_params = params_for_monitor( mon_name, , query, threshold, escalation_message: escalation, alert_no_data: [:alert_no_data], mon_type: [:mon_type], renotify_interval: rno, no_data_timeframe: [:no_data_timeframe], evaluation_delay: [:evaluation_delay] ) logger.info "Upserting monitor: #{mon_name}" monitor = get_existing_monitor_by_name(mon_name) return create_monitor(mon_name, mon_params) if monitor.nil? logger.debug "\tfound existing monitor id=#{monitor['id']}" do_update = false mon_params.each do |k, _v| unless monitor.include?(k) logger.debug "\tneeds update based on missing key: #{k}" do_update = true break end next unless monitor[k] != mon_params[k] logger.debug "\tneeds update based on difference in key #{k}; " \ "current='#{monitor[k]}' desired='#{mon_params[k]}'" do_update = true break end unless do_update logger.debug "\tmonitor is correct in DataDog." return monitor['id'] end res = @dog.update_monitor(monitor['id'], mon_params['query'], mon_params) if res[0] == '200' logger.info "\tMonitor #{monitor['id']} updated successfully" return monitor['id'] else logger.error "\tError updating monitor #{monitor['id']}: #{res}" end end |
#upsert_screenboard(dash_name, widgets) ⇒ Object
Create or update a screenboard in DataDog with the given name and data/params. For further information, see: docs.datadoghq.com/api/screenboards/ and docs.datadoghq.com/api/?lang=ruby#screenboards
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 |
# File 'lib/dogtrainer/api.rb', line 628 def upsert_screenboard(dash_name, ) logger.info "Upserting screenboard: #{dash_name}" desc = "created by DogTrainer RubyGem via #{@repo_path}" dash = get_existing_screenboard_by_name(dash_name) if dash.nil? d = @dog.create_screenboard(board_title: dash_name, description: desc, widgets: ) check_dog_result(d) logger.info "Created screenboard #{d[1]['id']}" return end logger.debug "\tfound existing screenboard id=#{dash['id']}" needs_update = false if dash['description'] != desc logger.debug "\tneeds update of description" needs_update = true end if dash['board_title'] != dash_name logger.debug "\tneeds update of title" needs_update = true end if dash['widgets'] != logger.debug "\tneeds update of widgets" needs_update = true end if needs_update logger.info "\tUpdating screenboard #{dash['id']}" d = @dog.update_screenboard(dash['id'], board_title: dash_name, description: desc, widgets: ) check_dog_result(d) logger.info "\tScreenboard updated." else logger.info "\tScreenboard is up-to-date" end end |
#upsert_timeboard(dash_name, graphs) ⇒ Object
Create or update a timeboard in DataDog with the given name and data/params. For further information, see: docs.datadoghq.com/api/#timeboards
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 |
# File 'lib/dogtrainer/api.rb', line 581 def upsert_timeboard(dash_name, graphs) logger.info "Upserting timeboard: #{dash_name}" desc = "created by DogTrainer RubyGem via #{@repo_path}" dash = get_existing_timeboard_by_name(dash_name) if dash.nil? d = @dog.create_dashboard(dash_name, desc, graphs) check_dog_result(d) logger.info "Created timeboard #{d[1]['dash']['id']}" return end logger.debug "\tfound existing timeboard id=#{dash['dash']['id']}" needs_update = false if dash['dash']['description'] != desc logger.debug "\tneeds update of description" needs_update = true end if dash['dash']['title'] != dash_name logger.debug "\tneeds update of title" needs_update = true end if dash['dash']['graphs'] != graphs logger.debug "\tneeds update of graphs" needs_update = true end if needs_update logger.info "\tUpdating timeboard #{dash['dash']['id']}" d = @dog.update_dashboard( dash['dash']['id'], dash_name, desc, graphs ) check_dog_result(d) logger.info "\tTimeboard updated." else logger.info "\tTimeboard is up-to-date" end end |