doggy 0.2.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +26 -11
- data/README.md +0 -23
- data/Rakefile +4 -3
- data/bin/console +8 -0
- data/bin/setup +2 -0
- data/doggy.gemspec +9 -7
- data/exe/doggy +2 -10
- data/lib/doggy.rb +47 -87
- data/lib/doggy/cli.rb +31 -73
- data/lib/doggy/cli/edit.rb +32 -5
- data/lib/doggy/cli/mute.rb +4 -9
- data/lib/doggy/cli/pull.rb +20 -12
- data/lib/doggy/cli/push.rb +17 -19
- data/lib/doggy/cli/unmute.rb +5 -9
- data/lib/doggy/model.rb +155 -0
- data/lib/doggy/models/dashboard.rb +37 -0
- data/lib/doggy/models/monitor.rb +82 -0
- data/lib/doggy/models/screen.rb +37 -0
- data/lib/doggy/version.rb +1 -1
- metadata +45 -39
- data/examples/my-monitor.rb +0 -14
- data/lib/doggy/cli/delete.rb +0 -21
- data/lib/doggy/cli/sha.rb +0 -12
- data/lib/doggy/cli/version.rb +0 -7
- data/lib/doggy/client.rb +0 -46
- data/lib/doggy/definition.rb +0 -9
- data/lib/doggy/dsl.rb +0 -73
- data/lib/doggy/errors.rb +0 -12
- data/lib/doggy/friendly_errors.rb +0 -76
- data/lib/doggy/model/dash.rb +0 -93
- data/lib/doggy/model/monitor.rb +0 -142
- data/lib/doggy/model/screen.rb +0 -80
- data/lib/doggy/serializer/json.rb +0 -15
- data/lib/doggy/serializer/yaml.rb +0 -15
- data/lib/doggy/shared_helpers.rb +0 -63
- data/lib/doggy/worker.rb +0 -31
data/lib/doggy/cli/edit.rb
CHANGED
@@ -1,14 +1,41 @@
|
|
1
1
|
module Doggy
|
2
2
|
class CLI::Edit
|
3
|
-
|
4
|
-
|
5
|
-
def initialize(options, id)
|
3
|
+
def initialize(options, param)
|
6
4
|
@options = options
|
7
|
-
@
|
5
|
+
@param = param
|
8
6
|
end
|
9
7
|
|
10
8
|
def run
|
11
|
-
|
9
|
+
resource = resource_by_param
|
10
|
+
return Doggy.ui.error("Could not find resource with #{ @param }") unless resource
|
11
|
+
|
12
|
+
Dir.chdir(File.dirname(resource.path)) do
|
13
|
+
system("open '#{ resource.human_edit_url }'")
|
14
|
+
while !Doggy.ui.yes?('Are you done editing?') do
|
15
|
+
Doggy.ui.say "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"
|
16
|
+
end
|
17
|
+
|
18
|
+
new_resource = resource.class.find(resource.id)
|
19
|
+
new_resource.path = resource.path
|
20
|
+
new_resource.save_local
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def resource_by_param
|
27
|
+
resources = Doggy::Models::Dashboard.all_local
|
28
|
+
resources += Doggy::Models::Monitor.all_local
|
29
|
+
resources += Doggy::Models::Screen.all_local
|
30
|
+
|
31
|
+
if @param =~ /^[0-9]+$/ then
|
32
|
+
id = @param.to_i
|
33
|
+
return resources.find { |res| res.id == id }
|
34
|
+
else
|
35
|
+
full_path = File.expand_path(@param, Dir.pwd)
|
36
|
+
return resources.find { |res| res.path == full_path }
|
37
|
+
end
|
12
38
|
end
|
13
39
|
end
|
14
40
|
end
|
41
|
+
|
data/lib/doggy/cli/mute.rb
CHANGED
@@ -1,19 +1,14 @@
|
|
1
1
|
module Doggy
|
2
2
|
class CLI::Mute
|
3
|
-
attr_reader :options, :ids
|
4
|
-
|
5
3
|
def initialize(options, ids)
|
6
4
|
@options = options
|
7
|
-
@ids
|
5
|
+
@ids = ids
|
8
6
|
end
|
9
7
|
|
10
8
|
def run
|
11
|
-
|
12
|
-
|
13
|
-
rescue DoggyError
|
14
|
-
puts "Mute failed."
|
15
|
-
exit 1
|
16
|
-
end
|
9
|
+
monitors = @ids.map { |id| Doggy::Models::Monitor.find(id) }
|
10
|
+
monitors.each(&:mute)
|
17
11
|
end
|
18
12
|
end
|
19
13
|
end
|
14
|
+
|
data/lib/doggy/cli/pull.rb
CHANGED
@@ -1,21 +1,29 @@
|
|
1
1
|
module Doggy
|
2
2
|
class CLI::Pull
|
3
|
-
|
4
|
-
|
5
|
-
def initialize(options, ids)
|
3
|
+
def initialize(options)
|
6
4
|
@options = options
|
7
|
-
@ids = ids
|
8
5
|
end
|
9
6
|
|
10
7
|
def run
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
8
|
+
pull_resources('dashboards', Models::Dashboard) if should_pull?('dashboards')
|
9
|
+
pull_resources('monitors', Models::Monitor) if should_pull?('monitors')
|
10
|
+
pull_resources('screens', Models::Screen) if should_pull?('screens')
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def should_pull?(resource)
|
16
|
+
@options.empty? || @options[resource]
|
17
|
+
end
|
18
|
+
|
19
|
+
def pull_resources(name, klass)
|
20
|
+
Doggy.ui.say "Pulling #{ name }"
|
21
|
+
local_resources = klass.all_local
|
22
|
+
remote_resources = klass.all
|
23
|
+
|
24
|
+
klass.assign_paths(remote_resources, local_resources)
|
25
|
+
remote_resources.each(&:save_local)
|
19
26
|
end
|
20
27
|
end
|
21
28
|
end
|
29
|
+
|
data/lib/doggy/cli/push.rb
CHANGED
@@ -1,28 +1,26 @@
|
|
1
1
|
module Doggy
|
2
2
|
class CLI::Push
|
3
|
-
|
4
|
-
|
5
|
-
def initialize(options, ids)
|
3
|
+
def initialize(options)
|
6
4
|
@options = options
|
7
|
-
@ids = ids
|
8
5
|
end
|
9
6
|
|
10
7
|
def run
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
8
|
+
push_resources('dashboards', Models::Dashboard) if should_push?('dashboards')
|
9
|
+
push_resources('monitors', Models::Monitor) if should_push?('monitors')
|
10
|
+
push_resources('screens', Models::Screen) if should_push?('screens')
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def should_push?(resource)
|
16
|
+
@options.empty? || @options[resource]
|
17
|
+
end
|
18
|
+
|
19
|
+
def push_resources(name, klass)
|
20
|
+
Doggy.ui.say "Pushing #{ name }"
|
21
|
+
local_resources = klass.all_local
|
22
|
+
local_resources.each(&:save)
|
26
23
|
end
|
27
24
|
end
|
28
25
|
end
|
26
|
+
|
data/lib/doggy/cli/unmute.rb
CHANGED
@@ -1,19 +1,15 @@
|
|
1
1
|
module Doggy
|
2
2
|
class CLI::Unmute
|
3
|
-
attr_reader :options, :ids
|
4
|
-
|
5
3
|
def initialize(options, ids)
|
6
4
|
@options = options
|
7
|
-
@ids
|
5
|
+
@ids = ids
|
8
6
|
end
|
9
7
|
|
10
8
|
def run
|
11
|
-
|
12
|
-
|
13
|
-
rescue DoggyError
|
14
|
-
puts "Unmute failed."
|
15
|
-
exit 1
|
16
|
-
end
|
9
|
+
monitors = @ids.map { |id| Doggy::Models::Monitor.find(id) }
|
10
|
+
monitors.each(&:unmute)
|
17
11
|
end
|
18
12
|
end
|
19
13
|
end
|
14
|
+
|
15
|
+
|
data/lib/doggy/model.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
require "json"
|
2
|
+
require "parallel"
|
3
|
+
require "uri"
|
4
|
+
require "virtus"
|
5
|
+
|
6
|
+
module Doggy
|
7
|
+
class Model
|
8
|
+
include Virtus.model
|
9
|
+
|
10
|
+
# This stores the path on disk. We don't define it as a model attribute so
|
11
|
+
# it doesn't get serialized.
|
12
|
+
attr_accessor :path
|
13
|
+
|
14
|
+
# This stores whether the resource has been loaded locally or remotely.
|
15
|
+
attr_accessor :loading_source
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def root=(root)
|
19
|
+
@root = root.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
def root
|
23
|
+
@root || nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def find(id)
|
27
|
+
attributes = request(:get, resource_url(id))
|
28
|
+
resource = new(attributes)
|
29
|
+
|
30
|
+
resource.loading_source = :remote
|
31
|
+
resource
|
32
|
+
end
|
33
|
+
|
34
|
+
def assign_paths(remote_resources, local_resources)
|
35
|
+
remote_resources.each do |remote|
|
36
|
+
local = local_resources.find { |l| l.id == remote.id }
|
37
|
+
next unless local
|
38
|
+
|
39
|
+
remote.path = local.path
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def all
|
44
|
+
collection = request(:get, resource_url)
|
45
|
+
if collection.is_a?(Hash) && collection.keys.length == 1
|
46
|
+
collection = collection.values.first
|
47
|
+
end
|
48
|
+
|
49
|
+
ids = collection
|
50
|
+
.map { |record| new(record) }
|
51
|
+
.select { |instance| instance.managed? }
|
52
|
+
.map { |instance| instance.id }
|
53
|
+
|
54
|
+
Parallel.map(ids) { |id| find(id) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def all_local
|
58
|
+
@all_local ||= begin
|
59
|
+
# TODO: Add serializer support here
|
60
|
+
files = Dir[Doggy.object_root.join("**/*.json")]
|
61
|
+
resources = Parallel.map(files) do |file|
|
62
|
+
raw = File.read(file)
|
63
|
+
|
64
|
+
begin
|
65
|
+
attributes = JSON.parse(raw)
|
66
|
+
rescue JSON::ParserError
|
67
|
+
Doggy.ui.error "Could not parse #{ file }."
|
68
|
+
next
|
69
|
+
end
|
70
|
+
|
71
|
+
next unless infer_type(attributes) == self
|
72
|
+
|
73
|
+
resource = new(attributes)
|
74
|
+
resource.path = file
|
75
|
+
resource.loading_source = :local
|
76
|
+
resource
|
77
|
+
end
|
78
|
+
|
79
|
+
resources.compact
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def infer_type(attributes)
|
84
|
+
return Models::Dashboard if attributes['graphs']
|
85
|
+
return Models::Monitor if attributes['message']
|
86
|
+
return Models::Screen if attributes['board_title']
|
87
|
+
end
|
88
|
+
|
89
|
+
def request(method, url, body = nil)
|
90
|
+
uri = URI(url)
|
91
|
+
uri.query = "api_key=#{ Doggy.api_key }&application_key=#{ Doggy.application_key }"
|
92
|
+
|
93
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
94
|
+
http.use_ssl = (uri.scheme == 'https')
|
95
|
+
|
96
|
+
request = case method
|
97
|
+
when :get then Net::HTTP::Get.new(uri.request_uri)
|
98
|
+
when :post then Net::HTTP::Post.new(uri.request_uri)
|
99
|
+
when :put then Net::HTTP::Put.new(uri.request_uri)
|
100
|
+
end
|
101
|
+
|
102
|
+
request.content_type = 'application/json'
|
103
|
+
request.body = body if body
|
104
|
+
|
105
|
+
response = http.request(request)
|
106
|
+
JSON.parse(response.body)
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
|
111
|
+
def resource_url(id = nil)
|
112
|
+
raise NotImplementedError, "#resource_url has to be implemented."
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def initialize(attributes = nil)
|
117
|
+
root_key = self.class.root
|
118
|
+
|
119
|
+
return super unless attributes && root_key
|
120
|
+
return super unless attributes[root_key].is_a?(Hash)
|
121
|
+
|
122
|
+
attributes = attributes[root_key]
|
123
|
+
super(attributes)
|
124
|
+
end
|
125
|
+
|
126
|
+
def save_local
|
127
|
+
@path ||= Doggy.object_root.join("#{ id }.json")
|
128
|
+
File.open(@path, 'w') { |f| f.write(JSON.pretty_generate(to_h)) }
|
129
|
+
end
|
130
|
+
|
131
|
+
def save
|
132
|
+
ensure_managed_emoji!
|
133
|
+
|
134
|
+
body = JSON.dump(to_h)
|
135
|
+
if !id then
|
136
|
+
attributes = request(:post, resource_url, body)
|
137
|
+
self.id = self.class.new(attributes).id
|
138
|
+
save_local
|
139
|
+
else
|
140
|
+
request(:put, resource_url(id), body)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
protected
|
145
|
+
|
146
|
+
def resource_url(id = nil)
|
147
|
+
self.class.resource_url(id)
|
148
|
+
end
|
149
|
+
|
150
|
+
def request(method, uri, body = nil)
|
151
|
+
self.class.request(method, uri, body)
|
152
|
+
end
|
153
|
+
end # Model
|
154
|
+
end # Doggy
|
155
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Doggy
|
2
|
+
module Models
|
3
|
+
class Dashboard < Doggy::Model
|
4
|
+
self.root = 'dash'
|
5
|
+
|
6
|
+
attribute :id, Integer
|
7
|
+
attribute :title, String
|
8
|
+
attribute :description, String
|
9
|
+
|
10
|
+
attribute :graphs, Array[Hash]
|
11
|
+
attribute :template_variables, Array[Hash]
|
12
|
+
|
13
|
+
def self.resource_url(id = nil)
|
14
|
+
"https://app.datadoghq.com/api/v1/dash".tap do |base_url|
|
15
|
+
base_url << "/#{ id }" if id
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def managed?
|
20
|
+
!(title =~ Doggy::DOG_SKIP_REGEX)
|
21
|
+
end
|
22
|
+
|
23
|
+
def ensure_managed_emoji!
|
24
|
+
return unless managed?
|
25
|
+
self.title += " \xF0\x9F\x90\xB6"
|
26
|
+
end
|
27
|
+
|
28
|
+
def human_url
|
29
|
+
"https://app.datadoghq.com/dash/#{ id }"
|
30
|
+
end
|
31
|
+
|
32
|
+
# Dashboards don't have a direct edit URL
|
33
|
+
alias_method :human_edit_url, :human_url
|
34
|
+
end # Dashboard
|
35
|
+
end # Models
|
36
|
+
end # Doggy
|
37
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Doggy
|
2
|
+
module Models
|
3
|
+
class Monitor < Doggy::Model
|
4
|
+
class Options
|
5
|
+
include Virtus.model
|
6
|
+
attr_accessor :monitor
|
7
|
+
|
8
|
+
attribute :silenced, Hash
|
9
|
+
attribute :notify_audit, Boolean
|
10
|
+
attribute :notify_no_data, Boolean
|
11
|
+
attribute :no_data_timeframe, Integer
|
12
|
+
attribute :timeout_h, Integer
|
13
|
+
attribute :escalation_message, String
|
14
|
+
|
15
|
+
def to_h
|
16
|
+
return super unless monitor.id && monitor.loading_source == :local
|
17
|
+
|
18
|
+
# Pull remote silenced state. If we don't send this value, Datadog
|
19
|
+
# assumes that we want to unmute the monitor.
|
20
|
+
remote_monitor = Monitor.find(monitor.id)
|
21
|
+
self.silenced = remote_monitor.options.silenced
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attribute :id, Integer
|
27
|
+
attribute :org_id, Integer
|
28
|
+
attribute :name, String
|
29
|
+
|
30
|
+
attribute :message, String
|
31
|
+
attribute :query, String
|
32
|
+
attribute :options, Options
|
33
|
+
attribute :tags, Array[String]
|
34
|
+
attribute :type, String
|
35
|
+
attribute :multi, Boolean
|
36
|
+
|
37
|
+
def self.resource_url(id = nil)
|
38
|
+
"https://app.datadoghq.com/api/v1/monitor".tap do |base_url|
|
39
|
+
base_url << "/#{ id }" if id
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(attributes = nil)
|
44
|
+
super(attributes)
|
45
|
+
|
46
|
+
options.monitor = self
|
47
|
+
end
|
48
|
+
|
49
|
+
def managed?
|
50
|
+
!(name =~ Doggy::DOG_SKIP_REGEX)
|
51
|
+
end
|
52
|
+
|
53
|
+
def ensure_managed_emoji!
|
54
|
+
return unless managed?
|
55
|
+
self.name += " \xF0\x9F\x90\xB6"
|
56
|
+
end
|
57
|
+
|
58
|
+
def mute
|
59
|
+
return unless id
|
60
|
+
request(:post, "#{ resource_url(id) }/mute")
|
61
|
+
end
|
62
|
+
|
63
|
+
def unmute
|
64
|
+
return unless id
|
65
|
+
request(:post, "#{ resource_url(id) }/unmute")
|
66
|
+
end
|
67
|
+
|
68
|
+
def human_url
|
69
|
+
"https://app.datadoghq.com/monitors##{ id }"
|
70
|
+
end
|
71
|
+
|
72
|
+
def human_edit_url
|
73
|
+
"https://app.datadoghq.com/monitors##{ id }/edit"
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_h
|
77
|
+
super.merge(options: options.to_h)
|
78
|
+
end
|
79
|
+
end # Monitor
|
80
|
+
end # Models
|
81
|
+
end # Doggy
|
82
|
+
|