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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +53 -7
- data/examples/my-monitor.rb +14 -0
- data/exe/doggy +5 -2
- data/lib/doggy.rb +38 -71
- data/lib/doggy/cli.rb +21 -12
- data/lib/doggy/cli/delete.rb +1 -1
- data/lib/doggy/cli/edit.rb +14 -0
- data/lib/doggy/cli/pull.rb +3 -9
- data/lib/doggy/cli/sha.rb +12 -0
- data/lib/doggy/cli/version.rb +1 -6
- data/lib/doggy/definition.rb +9 -0
- data/lib/doggy/dsl.rb +73 -0
- data/lib/doggy/errors.rb +12 -0
- data/lib/doggy/friendly_errors.rb +76 -0
- data/lib/doggy/model/dash.rb +32 -39
- data/lib/doggy/model/monitor.rb +35 -36
- data/lib/doggy/model/screen.rb +21 -25
- data/lib/doggy/shared_helpers.rb +36 -0
- data/lib/doggy/version.rb +1 -1
- metadata +10 -3
- data/lib/doggy/cli/create.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae47149af8df1d2a4f5e977b85ec4659b396c524
|
4
|
+
data.tar.gz: 4ba1dffaf222c19e4c6a6f6f64c75643d9bb4924
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 256336173e7b014f436b936a57eca85b9d73f9eae3dbab1a8cd73cb41b6617dc89ff4b0ee6499329270b862213816b5019be9857b57f6d2f1f056262ecaadd3f
|
7
|
+
data.tar.gz: 16d53a9d77766dcc441f49cf2ee8b12ee9636fed3cf155f54eb4576eb1cc624385fa20072deeff91fa8ff23a2eb26763c6e8a9d1be148f30bb4e177350cf6dc4
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -18,13 +18,32 @@ Or install it yourself as:
|
|
18
18
|
|
19
19
|
$ gem install doggy
|
20
20
|
|
21
|
-
##
|
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
|
-
#
|
41
|
-
$ doggy
|
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
|
-
|
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/
|
7
|
+
require 'doggy/friendly_errors'
|
8
8
|
|
9
|
-
Doggy
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
125
|
-
@
|
126
|
-
|
127
|
-
|
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
|
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 [
|
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 "
|
46
|
+
desc "edit OBJECT_ID", "Edit an existing object on DataDog"
|
47
47
|
long_desc <<-D
|
48
|
-
|
49
|
-
|
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
|
52
|
-
require 'doggy/cli/
|
53
|
-
|
51
|
+
def edit(id)
|
52
|
+
require 'doggy/cli/edit'
|
53
|
+
Edit.new(options.dup, id).run
|
54
54
|
end
|
55
55
|
|
56
|
-
desc "delete
|
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
|
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
|
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 "
|
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
|
data/lib/doggy/cli/delete.rb
CHANGED
data/lib/doggy/cli/pull.rb
CHANGED
@@ -9,15 +9,9 @@ module Doggy
|
|
9
9
|
|
10
10
|
def run
|
11
11
|
begin
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
data/lib/doggy/cli/version.rb
CHANGED
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
|
data/lib/doggy/errors.rb
ADDED
@@ -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
|
data/lib/doggy/model/dash.rb
CHANGED
@@ -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
|
-
|
22
|
-
puts "Uploading #{
|
23
|
-
upload(
|
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.
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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 ||=
|
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
|
-
|
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
|
-
|
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.
|
90
|
+
"#{Doggy.objects_path}/#{@id}.json"
|
98
91
|
end
|
99
92
|
end
|
100
93
|
end
|
data/lib/doggy/model/monitor.rb
CHANGED
@@ -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
|
-
|
29
|
-
puts "Uploading #{
|
30
|
-
upload(
|
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.
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
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
|
-
|
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.
|
131
|
+
"#{Doggy.objects_path}/#{@id}.json"
|
133
132
|
end
|
134
133
|
|
135
134
|
def mute_state_for(id)
|
data/lib/doggy/model/screen.rb
CHANGED
@@ -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
|
-
|
19
|
-
puts "Uploading #{
|
20
|
-
upload(
|
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.
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
58
|
+
SharedHelpers.with_retry do
|
62
59
|
Doggy.client.dog.update_screenboard(@id, @description)
|
63
60
|
end
|
64
61
|
else
|
65
|
-
|
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.
|
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
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.
|
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-
|
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
|
data/lib/doggy/cli/create.rb
DELETED
@@ -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
|