doggy 0.1.3 → 0.2.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: 4a08db9823ee6c5ed48f47766bfd0efbea67b6ed
4
- data.tar.gz: dbd6aa580574f14850b44d1b9ceea5038171127b
3
+ metadata.gz: ae47149af8df1d2a4f5e977b85ec4659b396c524
4
+ data.tar.gz: 4ba1dffaf222c19e4c6a6f6f64c75643d9bb4924
5
5
  SHA512:
6
- metadata.gz: 2a14f340fe533e1169ea5ad4612fcaca875c6a903a4a9d8c7b3c82ef59cbfefa5d500426e59c1767553aef7de07249a8cdf742d3841abdc0fb91b998df0b29ca
7
- data.tar.gz: 9a9917fd410dd1d4648a574d6b8f2f1371e9b5d43b8834af9ab33e1b49c3be9d0637dff54e0a4a928089b43460aa99f2208609a62f9b385fea1bf1c7fc54e124
6
+ metadata.gz: 256336173e7b014f436b936a57eca85b9d73f9eae3dbab1a8cd73cb41b6617dc89ff4b0ee6499329270b862213816b5019be9857b57f6d2f1f056262ecaadd3f
7
+ data.tar.gz: 16d53a9d77766dcc441f49cf2ee8b12ee9636fed3cf155f54eb4576eb1cc624385fa20072deeff91fa8ff23a2eb26763c6e8a9d1be148f30bb4e177350cf6dc4
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- doggy (0.1.3)
4
+ doggy (0.2.0)
5
5
  dogapi (~> 1.17)
6
6
  ejson (~> 1.0)
7
7
  thor (~> 0.19)
data/README.md CHANGED
@@ -18,13 +18,32 @@ Or install it yourself as:
18
18
 
19
19
  $ gem install doggy
20
20
 
21
- ## Usage
21
+ ## Authentication
22
+
23
+ To authenticate, you need to set API and APP keys for your DataDog account.
24
+
25
+ #### Environment variables
26
+
27
+ Export `DATADOG_API_KEY` and `DATADOG_APP_KEY` environment variables and `doggy` will catch them up automatically.
28
+
29
+ #### ejson
22
30
 
31
+ Set up `ejson` by putting `secrets.ejson` in your root object store.
32
+
33
+ #### json (plaintext)
34
+
35
+ If you're feeling adventurous, just put plaintext `secrets.json` in your root object store like this:
36
+
37
+ ```json
38
+ {
39
+ "datadog_api_key": "key1",
40
+ "datadog_app_key": "key2"
41
+ }
23
42
  ```
24
- # Export your DataDog credentials or use ejson
25
- $ export DATADOG_API_KEY=api_key_goes_here
26
- $ export DATADOG_APP_KEY=app_key_goes_here
27
43
 
44
+ ## Usage
45
+
46
+ ```bash
28
47
  # Download selected items from DataDog
29
48
  $ doggy pull ID ID
30
49
 
@@ -37,14 +56,41 @@ $ doggy push ID ID ID
37
56
  # Upload all items to DataDog
38
57
  $ doggy push
39
58
 
40
- # Create a new dashboard
41
- $ doggy create dash 'My New Dash'
59
+ # Edit a dashboard in WYSIWYG
60
+ $ doggy edit ID
42
61
 
43
62
  # Delete selected items from both DataDog and local storage
44
63
  $ doggy delete ID ID ID
64
+
65
+ # Mute monitor(s) forever
66
+ $ doggy mute ID ID ID
67
+
68
+ # Unmute monitor(s)
69
+ $ doggy unmute ID ID ID
70
+ ```
71
+
72
+ ## Example object definition
73
+
74
+ #### Ruby DSL
75
+
76
+ A DataDog object will be populated from `obj()` hash as shown below.
77
+
78
+ ```ruby
79
+ created_at = Time.parse('2015-01-01 14:00:01').to_i * 1000
80
+
81
+ query = "sum(last_1m):sum:Engine.current_thrust.status{status:error}.as_count() < 50"
82
+
83
+ obj({
84
+ created_at: created_at,
85
+ id: 100500,
86
+ message: "Houston, we have a problem @pagerduty-Houston",
87
+ name: "Engine thrust",
88
+ query: query,
89
+ type: "query alert"
90
+ })
45
91
  ```
46
92
 
47
- Note that we currently don't support global upload due to high risk of overwriting things. We'll turn this feature on after initial testing period.
93
+ For more examples, check the `examples` directory.
48
94
 
49
95
  ## Development
50
96
 
@@ -0,0 +1,14 @@
1
+ created_at = Time.parse('2015-01-01 14:00:01').to_i * 1000
2
+
3
+ query <<-QUERY
4
+ sum(last_1m):sum:Engine.current_thrust.status{status:error}.as_count() < 50
5
+ QUERY
6
+
7
+ obj({
8
+ created_at: created_at,
9
+ id: 100500,
10
+ message: "Houston, we have a problem @pagerduty-Houston",
11
+ name: "Engine thrust",
12
+ query: query,
13
+ type: "query alert"
14
+ })
data/exe/doggy CHANGED
@@ -4,6 +4,9 @@
4
4
  Signal.trap('INT') { exit 1 }
5
5
 
6
6
  require 'bundler/setup'
7
- require 'doggy/cli'
7
+ require 'doggy/friendly_errors'
8
8
 
9
- Doggy::CLI.start(ARGV, :debug => true)
9
+ Doggy.with_friendly_errors do
10
+ require 'doggy/cli'
11
+ Doggy::CLI.start(ARGV, :debug => true)
12
+ end
data/lib/doggy.rb CHANGED
@@ -4,9 +4,15 @@ require 'json'
4
4
  require 'yaml'
5
5
  require 'dogapi'
6
6
 
7
+ require 'doggy/friendly_errors'
8
+
7
9
  require 'doggy/version'
10
+ require 'doggy/errors'
11
+ require 'doggy/shared_helpers'
8
12
  require 'doggy/client'
9
13
  require 'doggy/worker'
14
+ require 'doggy/definition'
15
+ require 'doggy/dsl'
10
16
  require 'doggy/serializer/json'
11
17
  require 'doggy/serializer/yaml'
12
18
  require 'doggy/model/dash'
@@ -14,18 +20,9 @@ require 'doggy/model/monitor'
14
20
  require 'doggy/model/screen'
15
21
 
16
22
  module Doggy
17
- DOG_SKIP_REGEX = /\[dog\s+skip\]/i.freeze
23
+ DOG_SKIP_REGEX = /😱|:scream:/i.freeze
24
+ MANAGED_BY_DOGGY_REGEX = /🐶|\:dog\:/i.freeze
18
25
  DEFAULT_SERIALIZER_CLASS = Doggy::Serializer::Json
19
- MAX_TRIES = 5
20
-
21
- class DoggyError < StandardError
22
- def self.status_code(code)
23
- define_method(:status_code) { code }
24
- end
25
- end
26
-
27
- class InvalidOption < DoggyError; status_code(15); end
28
- class InvalidItemType < DoggyError; status_code(10); end
29
26
 
30
27
  class << self
31
28
  # @option arguments [Constant] :serializer A specific serializer class to use, will be initialized by doggy and passed the object instance
@@ -37,50 +34,42 @@ module Doggy
37
34
  Doggy::Client.new
38
35
  end
39
36
 
40
- # Absolute path of where alerts are stored on the filesystem.
41
- #
42
- # @return [Pathname]
43
- def alerts_path
44
- @alerts_path ||= Pathname.new('alerts').expand_path(Dir.pwd).expand_path.tap { |path| FileUtils.mkdir_p(path) }
45
- end
46
-
47
- # Absolute path of where dashes are stored on the filesystem.
48
- #
49
- # @return [Pathname]
50
- def dashes_path
51
- @dashes_path ||= Pathname.new('dashes').expand_path(Dir.pwd).expand_path.tap { |path| FileUtils.mkdir_p(path) }
52
- end
53
-
54
- # Absolute path of where screens are stored on the filesystem.
55
- #
56
- # @return [Pathname]
57
- def screens_path
58
- @screens_path ||= Pathname.new('screens').expand_path(Dir.pwd).expand_path.tap { |path| FileUtils.mkdir_p(path) }
59
- end
60
-
61
- # Cleans up directory
62
- def clean_dir(dir)
63
- Dir.foreach(dir) { |f| fn = File.join(dir, f); File.delete(fn) if f != '.' && f != '..'}
64
- end
65
-
66
- def all_local_items
67
- @all_local_items ||= Dir[Doggy.dashes_path.join('**/*'), Doggy.alerts_path.join('**/*'), Doggy.screens_path.join('**/*')].inject({}) { |memo, file| memo.merge load_item(f) }
37
+ def objects_path
38
+ @objects_path ||= Pathname.new('objects').expand_path(Dir.pwd).expand_path.tap { |path| FileUtils.mkdir_p(path) }
68
39
  end
69
40
 
70
41
  def load_item(f)
71
- filetype = File.extname(f)
72
-
73
- item = case filetype
42
+ item = case File.extname(f)
74
43
  when '.yaml', '.yml' then Doggy::Serializer::Yaml.load(File.read(f))
75
44
  when '.json' then Doggy::Serializer::Json.load(File.read(f))
45
+ when '.rb' then Doggy::Dsl.evaluate(f).obj
76
46
  else raise InvalidItemType
77
47
  end
78
48
 
79
- { [ determine_type(item), item['id'] ] => item }
49
+ # Hackery to support legacy dash format
50
+ {
51
+ [
52
+ determine_type(item), item['id'] || item['dash']['id']
53
+ ] => item['dash'] ? item['dash'] : item
54
+ }
55
+ end
56
+
57
+ def edit(id_or_filename)
58
+ if id_or_filename =~ /json|yml|yaml/
59
+ item_from_filename = Doggy.load_item(Doggy.objects_path.join(id_or_filename))
60
+ id = item_from_filename.keys[0][1]
61
+ else
62
+ id = id_or_filename
63
+ end
64
+
65
+ object = (item_from_filename || all_local_items).detect { |(type, object_id), object| object_id.to_s == id.to_s }
66
+ if object && object[0] && object[0][0] && type = object[0][0].sub(/^[a-z\d]*/) { $&.capitalize }
67
+ Object.const_get("Doggy::#{type}").edit(id)
68
+ end
80
69
  end
81
70
 
82
71
  def determine_type(item)
83
- return 'dash' if item['graphs']
72
+ return 'dash' if item['graphs'] || item['dash']
84
73
  return 'monitor' if item['message']
85
74
  return 'screen' if item['board_title']
86
75
  raise InvalidItemType
@@ -94,24 +83,7 @@ module Doggy
94
83
  puts "Exception: #{e.message}"
95
84
  end
96
85
 
97
- def with_retry(times: MAX_TRIES, reraise: false)
98
- tries = 0
99
- while tries < times
100
- begin
101
- yield
102
- break
103
- rescue => e
104
- error "Caught error in create_record! attempt #{tries}..."
105
- error "#{e.class.name}: #{e.message}"
106
- error "#{e.backtrace.join("\n")}"
107
- tries += 1
108
-
109
- raise e if tries >= times && reraise
110
- end
111
- end
112
- end
113
-
114
- def current_version
86
+ def current_sha
115
87
  now = Time.now.to_i
116
88
  month_ago = now - 3600 * 24 * 30
117
89
  events = Doggy.client.dog.stream(month_ago, now, tags: %w(audit shipit))[1]['events']
@@ -121,15 +93,10 @@ module Doggy
121
93
  puts "Exception: #{e.message}"
122
94
  end
123
95
 
124
- def all_remote_dashes
125
- @all_remote_dashes ||= Doggy.client.dog.get_dashboards[1]['dashes'].inject({}) do |memo, dash|
126
- memo.merge([ 'dash', dash['id'] ] => dash)
127
- end
128
- end
129
-
130
- def all_remote_monitors
131
- @all_remote_monitors ||= Doggy.client.dog.get_all_monitors[1].inject({}) do |memo, monitor|
132
- memo.merge([ 'monitor', monitor['id'] ] => monitor)
96
+ def all_local_items
97
+ @all_local_items ||= Dir[Doggy.objects_path.join('**/*')].inject({}) do |memo, file|
98
+ next if File.directory?(file)
99
+ memo.merge!(load_item(file))
133
100
  end
134
101
  end
135
102
  end
data/lib/doggy/cli.rb CHANGED
@@ -23,7 +23,7 @@ module Doggy
23
23
  check_unknown_options!(:except => [:config, :exec])
24
24
  stop_on_unknown_option! :exec
25
25
 
26
- desc "pull [SPACE SEPARATED IDs]", "Pulls objects from DataDog"
26
+ desc "pull OBJECT_ID OBJECT_ID OBJECT_ID", "Pulls objects from DataDog"
27
27
  long_desc <<-D
28
28
  Pull objects from DataDog. If pull is successful, Doggy exits with a status of 0.
29
29
  If not, the error is displayed and Doggy exits status 1.
@@ -33,7 +33,7 @@ module Doggy
33
33
  Pull.new(options.dup, ids).run
34
34
  end
35
35
 
36
- desc "push [SPACE SEPARATED IDs]", "Pushes objects to DataDog"
36
+ desc "push [OBJECT_ID OBJECT_ID OBJECT_ID]", "Pushes objects to DataDog"
37
37
  long_desc <<-D
38
38
  Pushes objects to DataDog. If push is successful, Doggy exits with a status of 0.
39
39
  If not, the error is displayed and Doggy exits status 1.
@@ -43,17 +43,17 @@ module Doggy
43
43
  Push.new(options.dup, ids).run
44
44
  end
45
45
 
46
- desc "create OBJECT_TYPE OBJECT_NAME", "Creates a new object on DataDog"
46
+ desc "edit OBJECT_ID", "Edit an existing object on DataDog"
47
47
  long_desc <<-D
48
- Creates a new object on DataDog. If create is successful, Doggy exits with a status of 0.
49
- If not, the error is displayed and Doggy exits status 1.
48
+ Opens default browser pointing to an object to edit it visually. After you finish, it will
49
+ display edit result.
50
50
  D
51
- def create(kind, name)
52
- require 'doggy/cli/create'
53
- Create.new(options.dup, kind, name).run
51
+ def edit(id)
52
+ require 'doggy/cli/edit'
53
+ Edit.new(options.dup, id).run
54
54
  end
55
55
 
56
- desc "delete SPACE SEPARATED IDs", "Deletes objects from DataDog"
56
+ desc "delete OBJECT_ID OBJECT_ID OBJECT_ID", "Deletes objects from DataDog"
57
57
  long_desc <<-D
58
58
  Deletes objects from DataDog. If delete is successful, Doggy exits with a status of 0.
59
59
  If not, the error is displayed and Doggy exits status 1.
@@ -63,7 +63,7 @@ module Doggy
63
63
  Delete.new(options.dup, ids).run
64
64
  end
65
65
 
66
- desc "mute [SPACE SEPARATED IDs]", "Mutes monitor on DataDog"
66
+ desc "mute OBJECT_ID OBJECT_ID OBJECT_ID", "Mutes monitor on DataDog"
67
67
  long_desc <<-D
68
68
  Mutes monitor on DataDog. If mute is successful, Doggy exits with a status of 0.
69
69
  If not, the error is displayed and Doggy exits status 1.
@@ -73,7 +73,7 @@ module Doggy
73
73
  Mute.new(options.dup, ids).run
74
74
  end
75
75
 
76
- desc "unmute [SPACE SEPARATED IDs]", "Unmutes monitor on DataDog"
76
+ desc "unmute OBJECT_ID OBJECT_ID OBJECT_ID", "Unmutes monitor on DataDog"
77
77
  long_desc <<-D
78
78
  Deletes objects from DataDog. If delete is successful, Doggy exits with a status of 0.
79
79
  If not, the error is displayed and Doggy exits status 1.
@@ -83,12 +83,21 @@ module Doggy
83
83
  Unmute.new(options.dup, ids).run
84
84
  end
85
85
 
86
- desc "version", "Detects the most recent SHA deployed by ShipIt"
86
+ desc "sha", "Detects the most recent SHA deployed by ShipIt"
87
87
  long_desc <<-D
88
88
  Scans DataDog event stream for shipit events what contain most recently deployed version
89
89
  of DataDog properties.
90
90
  If not, the error is displayed and Doggy exits status 1.
91
91
  D
92
+ def sha
93
+ require 'doggy/cli/sha'
94
+ Sha.new.run
95
+ end
96
+
97
+ desc "version", "Prints Doggy version"
98
+ long_desc <<-D
99
+ Prints Doggy version
100
+ D
92
101
  def version
93
102
  require 'doggy/cli/version'
94
103
  Version.new.run
@@ -13,7 +13,7 @@ module Doggy
13
13
  Doggy::Monitor.delete(ids)
14
14
  Doggy::Screen.delete(ids)
15
15
  rescue DoggyError
16
- puts "Create failed."
16
+ puts "Delete failed."
17
17
  exit 1
18
18
  end
19
19
  end
@@ -0,0 +1,14 @@
1
+ module Doggy
2
+ class CLI::Edit
3
+ attr_reader :options, :id
4
+
5
+ def initialize(options, id)
6
+ @options = options
7
+ @id = id
8
+ end
9
+
10
+ def run
11
+ Doggy.edit(id)
12
+ end
13
+ end
14
+ end
@@ -9,15 +9,9 @@ module Doggy
9
9
 
10
10
  def run
11
11
  begin
12
- if ids.any?
13
- Doggy::Dash.download(ids)
14
- Doggy::Monitor.download(ids)
15
- Doggy::Screen.download(ids)
16
- else
17
- Doggy::Dash.download_all
18
- Doggy::Monitor.download_all
19
- Doggy::Screen.download_all
20
- end
12
+ Doggy::Dash.download(ids)
13
+ Doggy::Monitor.download(ids)
14
+ Doggy::Screen.download(ids)
21
15
  rescue DoggyError
22
16
  puts "Pull failed."
23
17
  exit 1
@@ -0,0 +1,12 @@
1
+ module Doggy
2
+ class CLI::Sha
3
+ def run
4
+ begin
5
+ print Doggy.current_sha
6
+ rescue DoggyError
7
+ puts "Could not fetch latest SHA from DataDog."
8
+ exit 1
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,12 +1,7 @@
1
1
  module Doggy
2
2
  class CLI::Version
3
3
  def run
4
- begin
5
- print Doggy.current_version
6
- rescue DoggyError
7
- puts "Could not fetch latest SHA from DataDog."
8
- exit 1
9
- end
4
+ print Doggy::VERSION
10
5
  end
11
6
  end
12
7
  end
@@ -0,0 +1,9 @@
1
+ module Doggy
2
+ class Definition
3
+ attr_reader :obj
4
+
5
+ def initialize(obj)
6
+ @obj = obj
7
+ end
8
+ end
9
+ end
data/lib/doggy/dsl.rb ADDED
@@ -0,0 +1,73 @@
1
+ module Doggy
2
+ class Dsl
3
+ def self.evaluate(object_file)
4
+ builder = new
5
+ builder.eval_file(object_file)
6
+ builder.to_definition
7
+ end
8
+
9
+ def initialize
10
+ @obj = {}
11
+ end
12
+
13
+ def eval_file(object_file, contents = nil)
14
+ contents ||= File.open(object_file.to_s, "rb") { |f| f.read }
15
+ instance_eval(contents, object_file.to_s, 1)
16
+ rescue Exception => e
17
+ message = "There was an error " \
18
+ "#{e.is_a?(ObjectFileEvalError) ? "evaluating" : "parsing"} " \
19
+ "`#{File.basename object_file.to_s}`: #{e.message}"
20
+
21
+ raise DSLError.new(message, object_file, e.backtrace, contents)
22
+ end
23
+
24
+ def obj(structure)
25
+ @obj = structure
26
+ end
27
+
28
+ # @return [Definition] the parsed object definition.
29
+ def to_definition
30
+ Definition.new(@obj)
31
+ end
32
+
33
+ def method_missing(name, *args)
34
+ raise Doggy::ObjectFileError, "Undefined local variable or method `#{name}' for object file"
35
+ end
36
+
37
+ private
38
+
39
+ class DSLError < Doggy::ObjectFileError
40
+ # @return [String] the message that should be presented to the user.
41
+ attr_reader :message
42
+
43
+ # @return [String] the path of the dsl file that raised the exception.
44
+ attr_reader :dsl_path
45
+
46
+ # @return [Exception] the backtrace of the exception raised by the evaluation of the dsl file.
47
+ attr_reader :backtrace
48
+
49
+ # @param [Exception] backtrace @see backtrace
50
+ # @param [String] dsl_path @see dsl_path
51
+ def initialize(message, dsl_path, backtrace, contents = nil)
52
+ @status_code = $!.respond_to?(:status_code) && $!.status_code
53
+
54
+ @message = message
55
+ @dsl_path = dsl_path
56
+ @backtrace = backtrace
57
+ @contents = contents
58
+ end
59
+
60
+ def status_code
61
+ @status_code || super
62
+ end
63
+
64
+ # @return [String] the contents of the DSL that cause the exception to
65
+ # be raised.
66
+ def contents
67
+ @contents ||= begin
68
+ dsl_path && File.exist?(dsl_path) && File.read(dsl_path)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,12 @@
1
+ module Doggy
2
+ class DoggyError < StandardError
3
+ def self.status_code(code)
4
+ define_method(:status_code) { code }
5
+ end
6
+ end
7
+
8
+ class ObjectFileError < DoggyError; status_code(12); end
9
+ class ObjectFileEvalError < DoggyError; status_code(11); end
10
+ class InvalidOption < DoggyError; status_code(15); end
11
+ class InvalidItemType < DoggyError; status_code(10); end
12
+ end
@@ -0,0 +1,76 @@
1
+ require 'cgi'
2
+ require 'thor'
3
+ require 'doggy'
4
+
5
+ module Doggy
6
+ def self.with_friendly_errors
7
+ yield
8
+ rescue Doggy::Dsl::DSLError => e
9
+ puts e.message
10
+ exit e.status_code
11
+ rescue Doggy::DoggyError => e
12
+ puts e.message
13
+ puts e
14
+ exit e.status_code
15
+ rescue Thor::AmbiguousTaskError => e
16
+ puts e.message
17
+ exit 15
18
+ rescue Thor::UndefinedTaskError => e
19
+ puts e.message
20
+ exit 15
21
+ rescue Thor::Error => e
22
+ puts e.message
23
+ exit 1
24
+ rescue Interrupt => e
25
+ puts "\nQuitting..."
26
+ puts e
27
+ exit 1
28
+ rescue SystemExit => e
29
+ exit e.status
30
+ rescue Exception => e
31
+ request_issue_report_for(e)
32
+ exit 1
33
+ end
34
+
35
+ def self.request_issue_report_for(e)
36
+ puts <<-EOS.gsub(/^ {6}/, "")
37
+ --- ERROR REPORT TEMPLATE -------------------------------------------------------
38
+ - What did you do?
39
+
40
+ I ran the command `#{$PROGRAM_NAME} #{ARGV.join(" ")}`
41
+
42
+ - What did you expect to happen?
43
+
44
+ I expected Doggy to...
45
+
46
+ - What happened instead?
47
+
48
+ Instead, what actually happened was...
49
+
50
+
51
+ Error details
52
+
53
+ #{e.class}: #{e.message}
54
+ #{e.backtrace.join("\n ")}
55
+
56
+ --- TEMPLATE END ----------------------------------------------------------------
57
+
58
+ EOS
59
+
60
+ puts "Unfortunately, an unexpected error occurred, and Doggy cannot continue."
61
+
62
+ puts <<-EOS.gsub(/^ {6}/, "")
63
+
64
+ First, try this link to see if there are any existing issue reports for this error:
65
+ #{issues_url(e)}
66
+
67
+ If there aren't any reports for this error yet, please create copy and paste the report template above into a new issue. Don't forget to anonymize any private data! The new issue form is located at:
68
+ https://github.com/bai/doggy/issues/new
69
+ EOS
70
+ end
71
+
72
+ def self.issues_url(exception)
73
+ "https://github.com/bai/doggy/search?q=" \
74
+ "#{CGI.escape(exception.message.lines.first.chomp)}&type=Issues"
75
+ end
76
+ end
@@ -1,26 +1,9 @@
1
1
  module Doggy
2
2
  class Dash
3
- def initialize(**options)
4
- @id = options[:id]
5
- @title = options[:title] || raw_local['title']
6
- @description = options[:description] || raw_local['description']
7
- @graphs = options[:graphs] || raw_local['graphs']
8
- @template_variables = options[:template_variables] || raw_local['template_variables']
9
- end
10
-
11
- def self.download_all
12
- ids = Doggy.client.dog.get_dashboards[1]['dashes'].map { |d| d['id'] }
13
- puts "Downloading #{ids.size} dashboards..."
14
- Doggy.clean_dir(Doggy.dashes_path)
15
- download(ids)
16
- rescue => e
17
- puts "Exception: #{e.message}"
18
- end
19
-
20
3
  def self.upload_all
21
- ids = Dir[Doggy.dashes_path.join('*')].map { |f| File.basename(f, '.*') }
22
- puts "Uploading #{ids.size} dashboards from #{Doggy.dashes_path}: #{ids.join(', ')}"
23
- upload(ids)
4
+ objects = all_local_items.find_all { |(type, id), object| type == 'dash' }
5
+ puts "Uploading #{objects.size} dashboards"
6
+ upload(objects.map { |(type, id), object| id })
24
7
  rescue => e
25
8
  puts "Exception: #{e.message}"
26
9
  end
@@ -33,20 +16,23 @@ module Doggy
33
16
  Doggy::Worker.new(threads: Doggy::Worker::CONCURRENT_STREAMS) { |id| new(id: id).push }.call([*ids])
34
17
  end
35
18
 
36
- def self.create(name)
37
- # This graphs placeholder is required as you cannot create an empty dashboard via API
38
- dash = new(title: name, description: '', graphs: [{
39
- "definition" => {
40
- "events" => [],
41
- "requests" => [
42
- {"q" => "avg:system.mem.free{*}"}
43
- ],
44
- "viz" => "timeseries"
45
- },
46
- "title" => "Average Memory Free"
47
- }])
48
- dash.push
49
- dash.save
19
+ def self.edit(id)
20
+ system %{open "https://app.datadoghq.com/dash/#{id}"}
21
+ if SharedHelpers.agree("Are you done?")
22
+ puts 'Here is the output of your edit:'
23
+ puts Doggy::Serializer::Json.dump(new(id: id).raw)
24
+ else
25
+ puts "run, rabbit run / dig that hole, forget the sun / and when at last the work is done / don't sit down / it's time to dig another one"
26
+ edit(id)
27
+ end
28
+ end
29
+
30
+ def initialize(**options)
31
+ @id = options[:id]
32
+ @title = options[:title] || raw_local['title']
33
+ @description = options[:description] || raw_local['description']
34
+ @graphs = options[:graphs] || raw_local['graphs']
35
+ @template_variables = options[:template_variables] || raw_local['template_variables']
50
36
  end
51
37
 
52
38
  def raw
@@ -58,7 +44,10 @@ module Doggy
58
44
 
59
45
  def raw_local
60
46
  return {} unless File.exists?(path)
61
- @raw_local ||= Doggy.serializer.load(File.read(path))
47
+ @raw_local ||= begin
48
+ object = Doggy.serializer.load(File.read(path))
49
+ object['dash'] ? object['dash'] : object
50
+ end
62
51
  end
63
52
 
64
53
  def save
@@ -72,12 +61,17 @@ module Doggy
72
61
  def push
73
62
  return unless File.exists?(path)
74
63
  return if @title =~ Doggy::DOG_SKIP_REGEX
64
+ return unless Doggy.determine_type(raw_local) == 'dash'
65
+
66
+ # Managed by doggy (TM)
67
+ @title = @title =~ MANAGED_BY_DOGGY_REGEX ? @title : @title + " 🐶"
68
+
75
69
  if @id
76
- Doggy.with_retry do
70
+ SharedHelpers.with_retry do
77
71
  Doggy.client.dog.update_dashboard(@id, @title, @description, @graphs, @template_variables)
78
72
  end
79
73
  else
80
- Doggy.with_retry do
74
+ SharedHelpers.with_retry do
81
75
  dash = Doggy.client.dog.create_dashboard(@title, @description, @graphs)
82
76
  end
83
77
  # FIXME: Remove duplication
@@ -88,13 +82,12 @@ module Doggy
88
82
 
89
83
  def delete
90
84
  Doggy.client.dog.delete_dashboard(@id)
91
- File.unlink(path)
92
85
  end
93
86
 
94
87
  private
95
88
 
96
89
  def path
97
- "#{Doggy.dashes_path}/#{@id}.json"
90
+ "#{Doggy.objects_path}/#{@id}.json"
98
91
  end
99
92
  end
100
93
  end
@@ -1,33 +1,9 @@
1
1
  module Doggy
2
2
  class Monitor
3
- def initialize(**options)
4
- @id = options[:id]
5
- @query = options[:query]
6
- @silenced = options[:silenced]
7
- @name = options[:name]
8
- @timeout_h = options[:timeout_h]
9
- @message = options[:message]
10
- @notify_audit = options[:notify_audit]
11
- @notify_no_data = options[:notify_no_data]
12
- @renotify_interval = options[:renotify_interval]
13
- @escalation_message = options[:escalation_message]
14
- @no_data_timeframe = options[:no_data_timeframe]
15
- @silenced_timeout_ts = options[:silenced_timeout_ts]
16
- end
17
-
18
- def self.download_all
19
- ids = Doggy.client.dog.get_all_alerts[1]['alerts'].map { |d| d['id'] }
20
- puts "Downloading #{ids.size} alerts..."
21
- Doggy.clean_dir(Doggy.alerts_path)
22
- download(ids)
23
- rescue => e
24
- puts "Exception: #{e.message}"
25
- end
26
-
27
3
  def self.upload_all
28
- ids = Dir[Doggy.alerts_path.join('*')].map { |f| File.basename(f, '.*') }
29
- puts "Uploading #{ids.size} alerts from #{Doggy.alerts_path}: #{ids.join(', ')}"
30
- upload(ids)
4
+ objects = all_local_items.find_all { |(type, id), object| type == 'monitor' }
5
+ puts "Uploading #{objects.size} monitors"
6
+ upload(objects.map { |(type, id), object| id })
31
7
  rescue => e
32
8
  puts "Exception: #{e.message}"
33
9
  end
@@ -48,11 +24,30 @@ module Doggy
48
24
  Doggy::Worker.new(threads: Doggy::Worker::CONCURRENT_STREAMS) { |id| new(id: id).unmute }.call([*ids])
49
25
  end
50
26
 
51
- def self.create(name)
52
- # Adding a placeholder query as it's a mandatory parameter
53
- item = new(name: name, query: 'avg(last_1m):avg:system.load.1{*} > 100')
54
- item.push
55
- item.save
27
+ def self.edit(id)
28
+ system %{open "https://app.datadoghq.com/monitors##{id}"}
29
+ if SharedHelpers.agree("Are you done?")
30
+ puts 'Here is the output of your edit:'
31
+ puts Doggy::Serializer::Json.dump(new(id: id).raw)
32
+ else
33
+ puts "run, rabbit run / dig that hole, forget the sun / and when at last the work is done / don't sit down / it's time to dig another one"
34
+ edit(id)
35
+ end
36
+ end
37
+
38
+ def initialize(**options)
39
+ @id = options[:id]
40
+ @query = options[:query]
41
+ @silenced = options[:silenced]
42
+ @name = options[:name]
43
+ @timeout_h = options[:timeout_h]
44
+ @message = options[:message]
45
+ @notify_audit = options[:notify_audit]
46
+ @notify_no_data = options[:notify_no_data]
47
+ @renotify_interval = options[:renotify_interval]
48
+ @escalation_message = options[:escalation_message]
49
+ @no_data_timeframe = options[:no_data_timeframe]
50
+ @silenced_timeout_ts = options[:silenced_timeout_ts]
56
51
  end
57
52
 
58
53
  def raw
@@ -94,10 +89,15 @@ module Doggy
94
89
 
95
90
  def push
96
91
  return if @name =~ Doggy::DOG_SKIP_REGEX
92
+ return unless Doggy.determine_type(raw_local) == 'monitor'
93
+
94
+ # Managed by doggy (TM)
95
+ @name = @name =~ MANAGED_BY_DOGGY_REGEX ? @name : @name + " 🐶"
96
+
97
97
  if @id
98
98
  return unless File.exists?(path)
99
99
 
100
- Doggy.with_retry do
100
+ SharedHelpers.with_retry do
101
101
  Doggy.client.dog.update_monitor(@id, @query || raw_local['query'], {
102
102
  name: @name || raw_local['name'],
103
103
  timeout_h: @timeout_h || raw_local['timeout_h'],
@@ -114,7 +114,7 @@ module Doggy
114
114
  })
115
115
  end
116
116
  else
117
- Doggy.with_retry do
117
+ SharedHelpers.with_retry do
118
118
  result = Doggy.client.dog.monitor('metric alert', @query, name: @name)
119
119
  end
120
120
  @id = result[1]['id']
@@ -123,13 +123,12 @@ module Doggy
123
123
 
124
124
  def delete
125
125
  Doggy.client.dog.delete_alert(@id)
126
- File.unlink(path)
127
126
  end
128
127
 
129
128
  private
130
129
 
131
130
  def path
132
- "#{Doggy.alerts_path}/#{@id}.json"
131
+ "#{Doggy.objects_path}/#{@id}.json"
133
132
  end
134
133
 
135
134
  def mute_state_for(id)
@@ -1,23 +1,9 @@
1
1
  module Doggy
2
2
  class Screen
3
- def initialize(**options)
4
- @id = options[:id]
5
- @description = options[:description] || raw_local
6
- end
7
-
8
- def self.download_all
9
- ids = Doggy.client.dog.get_all_screenboards[1]['screenboards'].map { |d| d['id'] }
10
- puts "Downloading #{ids.size} screenboards..."
11
- Doggy.clean_dir(Doggy.screens_path)
12
- download(ids)
13
- rescue => e
14
- puts "Exception: #{e.message}"
15
- end
16
-
17
3
  def self.upload_all
18
- ids = Dir[Doggy.screens_path.join('*')].map { |f| File.basename(f, '.*') }
19
- puts "Uploading #{ids.size} screenboards from #{Doggy.screens_path}: #{ids.join(', ')}"
20
- upload(ids)
4
+ objects = all_local_items.find_all { |(type, id), object| type == 'screen' }
5
+ puts "Uploading #{objects.size} screens"
6
+ upload(objects.map { |(type, id), object| id })
21
7
  rescue => e
22
8
  puts "Exception: #{e.message}"
23
9
  end
@@ -30,10 +16,20 @@ module Doggy
30
16
  Doggy::Worker.new(threads: Doggy::Worker::CONCURRENT_STREAMS) { |id| new(id: id).push }.call([*ids])
31
17
  end
32
18
 
33
- def self.create(name)
34
- item = new(description: { 'board_title' => name, 'widgets' => [] })
35
- item.push
36
- item.save
19
+ def self.edit(id)
20
+ system %{open "https://app.datadoghq.com/screen/#{id}"}
21
+ if SharedHelpers.agree("Are you done?")
22
+ puts 'Here is the output of your edit:'
23
+ puts Doggy::Serializer::Json.dump(new(id: id).raw)
24
+ else
25
+ puts "run, rabbit run / dig that hole, forget the sun / and when at last the work is done / don't sit down / it's time to dig another one"
26
+ edit(id)
27
+ end
28
+ end
29
+
30
+ def initialize(**options)
31
+ @id = options[:id]
32
+ @description = options[:description] || raw_local
37
33
  end
38
34
 
39
35
  def raw
@@ -57,12 +53,13 @@ module Doggy
57
53
 
58
54
  def push
59
55
  return if @description =~ Doggy::DOG_SKIP_REGEX
56
+ return unless Doggy.determine_type(raw_local) == 'screen'
60
57
  if @id
61
- Doggy.with_retry do
58
+ SharedHelpers.with_retry do
62
59
  Doggy.client.dog.update_screenboard(@id, @description)
63
60
  end
64
61
  else
65
- Doggy.with_retry do
62
+ SharedHelpers.with_retry do
66
63
  result = Doggy.client.dog.create_screenboard(@description)
67
64
  end
68
65
  @id = result[1]['id']
@@ -72,13 +69,12 @@ module Doggy
72
69
 
73
70
  def delete
74
71
  Doggy.client.dog.delete_screenboard(@id)
75
- File.unlink(path)
76
72
  end
77
73
 
78
74
  private
79
75
 
80
76
  def path
81
- "#{Doggy.screens_path}/#{@id}.json"
77
+ "#{Doggy.objects_path}/#{@id}.json"
82
78
  end
83
79
  end
84
80
  end
@@ -0,0 +1,36 @@
1
+ module Doggy
2
+ module SharedHelpers
3
+ MAX_TRIES = 5
4
+
5
+ def self.strip_heredoc(string)
6
+ indent = string.scan(/^[ \t]*(?=\S)/).min.try(:size) || 0
7
+ string.gsub(/^[ \t]{#{indent}}/, '')
8
+ end
9
+
10
+ def self.with_retry(times: MAX_TRIES, reraise: false)
11
+ tries = 0
12
+ while tries < times
13
+ begin
14
+ yield
15
+ break
16
+ rescue => e
17
+ error "Caught error! Attempt #{tries}..."
18
+ error "#{e.class.name}: #{e.message}"
19
+ error "#{e.backtrace.join("\n")}"
20
+ tries += 1
21
+
22
+ raise e if tries >= times && reraise
23
+ end
24
+ end
25
+ end
26
+
27
+ def self.agree(prompt)
28
+ raise Error, "Not a tty" unless $stdin.tty?
29
+
30
+ puts prompt + " (Y/N)"
31
+ line = $stdin.readline.chomp.upcase
32
+ puts
33
+ line == "Y"
34
+ end
35
+ end
36
+ end
data/lib/doggy/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Doggy
2
- VERSION = '0.1.3'
2
+ VERSION = '0.2.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doggy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vlad Gorodetsky
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-09-14 00:00:00.000000000 Z
11
+ date: 2015-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -111,22 +111,29 @@ files:
111
111
  - bin/console
112
112
  - bin/setup
113
113
  - doggy.gemspec
114
+ - examples/my-monitor.rb
114
115
  - exe/doggy
115
116
  - lib/doggy.rb
116
117
  - lib/doggy/cli.rb
117
- - lib/doggy/cli/create.rb
118
118
  - lib/doggy/cli/delete.rb
119
+ - lib/doggy/cli/edit.rb
119
120
  - lib/doggy/cli/mute.rb
120
121
  - lib/doggy/cli/pull.rb
121
122
  - lib/doggy/cli/push.rb
123
+ - lib/doggy/cli/sha.rb
122
124
  - lib/doggy/cli/unmute.rb
123
125
  - lib/doggy/cli/version.rb
124
126
  - lib/doggy/client.rb
127
+ - lib/doggy/definition.rb
128
+ - lib/doggy/dsl.rb
129
+ - lib/doggy/errors.rb
130
+ - lib/doggy/friendly_errors.rb
125
131
  - lib/doggy/model/dash.rb
126
132
  - lib/doggy/model/monitor.rb
127
133
  - lib/doggy/model/screen.rb
128
134
  - lib/doggy/serializer/json.rb
129
135
  - lib/doggy/serializer/yaml.rb
136
+ - lib/doggy/shared_helpers.rb
130
137
  - lib/doggy/version.rb
131
138
  - lib/doggy/worker.rb
132
139
  homepage: http://github.com/bai/doggy
@@ -1,25 +0,0 @@
1
- module Doggy
2
- class CLI::Create
3
- attr_reader :options, :kind, :name
4
-
5
- def initialize(options, kind, name)
6
- @options = options
7
- @kind = kind
8
- @name = name
9
- end
10
-
11
- def run
12
- begin
13
- case kind
14
- when 'dash', 'dashboard' then Doggy::Dash.create(name)
15
- when 'alert', 'monitor' then Doggy::Monitor.create(name)
16
- when 'screen', 'screenboard' then Doggy::Screen.create(name)
17
- else puts 'Unknown item type'
18
- end
19
- rescue DoggyError
20
- puts "Create failed."
21
- exit 1
22
- end
23
- end
24
- end
25
- end