doggy 0.2.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,14 +1,41 @@
1
1
  module Doggy
2
2
  class CLI::Edit
3
- attr_reader :options, :id
4
-
5
- def initialize(options, id)
3
+ def initialize(options, param)
6
4
  @options = options
7
- @id = id
5
+ @param = param
8
6
  end
9
7
 
10
8
  def run
11
- Doggy.edit(id)
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
+
@@ -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 = ids
5
+ @ids = ids
8
6
  end
9
7
 
10
8
  def run
11
- begin
12
- Doggy::Monitor.mute(ids)
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
+
@@ -1,21 +1,29 @@
1
1
  module Doggy
2
2
  class CLI::Pull
3
- attr_reader :options, :ids
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
- begin
12
- Doggy::Dash.download(ids)
13
- Doggy::Monitor.download(ids)
14
- Doggy::Screen.download(ids)
15
- rescue DoggyError
16
- puts "Pull failed."
17
- exit 1
18
- end
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
+
@@ -1,28 +1,26 @@
1
1
  module Doggy
2
2
  class CLI::Push
3
- attr_reader :options, :ids
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
- begin
12
- if ids.any?
13
- Doggy::Dash.upload(ids)
14
- Doggy::Monitor.upload(ids)
15
- Doggy::Screen.upload(ids)
16
- else
17
- Doggy::Dash.upload_all
18
- Doggy::Monitor.upload_all
19
- Doggy::Screen.upload_all
20
- Doggy.emit_shipit_deployment if ENV['SHIPIT']
21
- end
22
- rescue DoggyError
23
- puts "Push failed."
24
- exit 1
25
- end
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
+
@@ -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 = ids
5
+ @ids = ids
8
6
  end
9
7
 
10
8
  def run
11
- begin
12
- Doggy::Monitor.unmute(ids)
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
+
@@ -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
+