datadog-cli 0.1.16

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5d58300d688ebd50bfe063c1186fff34202b7751
4
+ data.tar.gz: 9a7702250216f4e3b9e78c262a3f300403c1b0bd
5
+ SHA512:
6
+ metadata.gz: 2b895e0f651cbda6057a7ae79abf1f627228ab212444413e7299418807238da41f14ff45ce7fd603429f5bfc75fef9f14fe7ecf40792c7270732c5a4a0a167b4
7
+ data.tar.gz: 13e6772a1618a7b88d437f691cfe1549b0267b80b085adcf1ee098bda9b094464e77fe02491a05e72cc89f2ca72ae761fc6113c29e54f51758b83f2b8b8ab987
@@ -0,0 +1,3 @@
1
+ pkg/
2
+ # monitors/
3
+ datadog-cli.yaml
File without changes
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+ # source "https://artifacts.schibsted.io/artifactory/api/gems/rubygems-virtual"
3
+
4
+ gemspec
@@ -0,0 +1,30 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ datadog-cli (0.1.9)
5
+ jsonlint
6
+ liquid
7
+ thor
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ remote: https://artifacts.schibsted.io/artifactory/api/gems/rubygems-local/
12
+ specs:
13
+ jsonlint (0.2.0)
14
+ oj (~> 2)
15
+ trollop (~> 2)
16
+ liquid (3.0.6)
17
+ oj (2.17.3)
18
+ rake (11.2.2)
19
+ thor (0.19.1)
20
+ trollop (2.1.2)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ datadog-cli!
27
+ rake
28
+
29
+ BUNDLED WITH
30
+ 1.12.5
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+ # require "jsonlint/rake_task"
4
+
5
+ task default: [
6
+ :release,
7
+ ]
@@ -0,0 +1,82 @@
1
+ # datadog-cli
2
+
3
+ Track your datadog monitors.
4
+
5
+ When you have several environments and want to consitently guarantee alerts
6
+ across them it's better to have templates and extract the differences between
7
+ them to variables.
8
+
9
+
10
+ ## Installation
11
+
12
+ ```
13
+ gem install datadog-cli
14
+ ```
15
+
16
+
17
+ ## Usage
18
+
19
+ Right now the only supported Datadog feature are monitors with the
20
+ `monitor` subcommand. Adding a `dashboard` subcommand shouldn't be
21
+ the hardest thing.
22
+
23
+ ```
24
+ $ ./bin/datadog monitor
25
+ Commands:
26
+ datadog monitor check PATH # Checks if monitor(s) from PATH exist
27
+ datadog monitor download DIR [FILTER] [INVERT] # Downloads all monitors that match FILTER
28
+ datadog monitor generate PATH [DIR] [VARS] # Renders template(s) from PATH into DIR with a VARS file
29
+ datadog monitor help [COMMAND] # Describe subcommands or one specific subcommand
30
+ datadog monitor ls [FILTER] [EXCLUDE] # Lists all monitors that match FILTER
31
+ datadog monitor render PATH [VARS] # Renders template(s) from PATH with a VARS file
32
+ datadog monitor update PATH # Updates or creates monitor(s) from PATH
33
+
34
+ Options:
35
+ [--vars=VARS]
36
+ ```
37
+
38
+ Check https://github.schibsted.io/spt-payment/datadog-monitors for a real example
39
+ of how templates and variables can be structured.
40
+
41
+ The required access to the Datadog API can be set by either env vars or using a
42
+ configuration file. The env vars are `DATADOG_API_KEY` and `DATADOG_APP_KEY`.
43
+
44
+ If neither env vars are set the code does a file lookup. **In order**:
45
+
46
+ - Currentl working directory level: `./datadog-cli.yaml`.
47
+ - User level: (your home directory): `~/.datadog-cli.yaml`.
48
+ - System level: `/etc/datadog-cli.yaml`
49
+
50
+ The first found file prevails. There's no merging involved. The content of the file
51
+ should look like:
52
+
53
+ ```yaml
54
+ ---
55
+ creds:
56
+ api_key: 123456789-123456789-123456789-12
57
+ app_key: 123456789-123456789-123456789-1234567890
58
+ ```
59
+
60
+
61
+ ## What makes this different
62
+
63
+ It allows to:
64
+
65
+ - generate json monitors files from rendering templates against variables
66
+ - have as many variables files as needed as a way to tweak thresholds per
67
+ env. You can be lazy and use the `import: file.yaml` to det defaults.
68
+ - create/update monitors from an arbitrary folder
69
+
70
+ These steps combined allows people to grab monitors from one environment and
71
+ apply to another.
72
+
73
+ ## What this doesn't do
74
+
75
+ - This tool does not delete existing monitors. That's manual work left up
76
+ to you. The tool does a look up based on the monitor title to decide if
77
+ it will create or update. This can still mess up current monitors so it's
78
+ not a bad idea to download all current monitors, even if for archival
79
+ purposes.
80
+
81
+ - This tools uses the `liquid` gem which unfortunately does not support
82
+ recursive variable resolution like ansible brilliantly does.
@@ -0,0 +1,322 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.expand_path('../../lib', File.realpath(__FILE__))
3
+
4
+ require "thor"
5
+ require "http"
6
+ require "json"
7
+ require "yaml"
8
+ require "deep_merge"
9
+ require "fileutils"
10
+ require "tablelize"
11
+ require "colorize"
12
+ require "datadog"
13
+ require "liquid"
14
+
15
+ puts "Hello from datadog-cli"
16
+
17
+ module Datadog
18
+
19
+ DATADOG_API_KEY = "DATADOG_API_KEY"
20
+ DATADOG_APP_KEY = "DATADOG_APP_KEY"
21
+ DATADOG_CONFIG = "DATADOG_CONFIG"
22
+ DATADOG_ENDPOINT = "https://app.datadoghq.com/api/v1/monitor"
23
+ DATADOG_HEADERS = {
24
+ "Content-Type" => "application/json",
25
+ }
26
+
27
+ class Monitor < Thor
28
+ class_option :vars, :type => :string
29
+
30
+ desc "ls [FILTER] [EXCLUDE]", "Lists all monitors that match FILTER"
31
+ def ls filter = nil, exclude = false
32
+ rows = []
33
+ rows << ["ID", "NAME"]
34
+ data = _list()
35
+ data.each do |m|
36
+ if filter
37
+ next if exclude == "true" and m["name"].downcase.match /#{filter}/im
38
+ next if exclude == "false" and not m["name"].downcase.match /#{filter}/im
39
+ end
40
+
41
+ rows << [m["id"], m["name"]]
42
+ end
43
+ Tablelize::table rows
44
+ end
45
+
46
+ desc "download DIR [FILTER] [INVERT]", "Downloads all monitors that match FILTER"
47
+ def download dir, filter = nil, invert = false
48
+ data = _list()
49
+ time = Time.now.strftime('%Y%m%d-%H%M%S')
50
+ dir = "#{dir}/#{time}"
51
+ FileUtils.mkdir_p dir unless Dir.exists? dir
52
+ data.each do |m|
53
+ if filter
54
+ if invert == "true" and m["name"].downcase.match filter
55
+ next
56
+ elsif not m["name"].downcase.match filter
57
+ next
58
+ end
59
+ end
60
+
61
+ json = JSON.pretty_generate(m)
62
+ file = "#{dir}/#{m['name'].clean}.json"
63
+ File.write(file, json)
64
+ print "Download".green, " #{file}\n"
65
+ end
66
+ end
67
+
68
+ desc "check PATH", "Checks if monitor(s) from PATH exist"
69
+ def check path
70
+ if File.directory? path
71
+ Dir["#{path}/**/*"].each do |f|
72
+ check f unless File.directory? f
73
+ end
74
+ else
75
+ if _check(path)
76
+ print "Exists".green, " #{path}\n"
77
+ else
78
+ print "New".yellow, " #{path}\n"
79
+ end
80
+ end
81
+ end
82
+
83
+ desc "update PATH", "Updates or creates monitor(s) from PATH"
84
+ def update path
85
+ if File.directory? path
86
+ Dir["#{path}/**/*.json"].each do |f|
87
+ update f unless File.directory? f
88
+ end
89
+ else
90
+ ok = _update(path)
91
+ if ok.is_a? Numeric
92
+ print "Updated".cyan, " #{path}\n"
93
+ elsif ok
94
+ print "Created".green, " #{path}\n"
95
+ else
96
+ print "Failed".red, " #{path}\n"
97
+ abort "Aborting on failure to update"
98
+ end
99
+ end
100
+ end
101
+
102
+ desc "render PATH [VARS]", "Renders template(s) from PATH with a VARS file"
103
+ def render path, vars = nil
104
+ if File.directory? path
105
+ Dir["#{path}/**/*.json.j2"].each do |f|
106
+ render f, vars unless File.directory? f
107
+ end
108
+ else
109
+ text = _render(path, vars)
110
+ puts text
111
+ end
112
+ end
113
+
114
+ desc "generate PATH [DIR] [VARS]", "Renders template(s) from PATH into DIR with a VARS file"
115
+ def generate path, dir = nil, vars = nil
116
+ vars ||= options[:vars]
117
+ if File.directory? path
118
+ Dir["#{path}/**/*.json.j2"].each do |f|
119
+ generate f, dir, vars unless File.directory? f
120
+ end
121
+ else
122
+ text = _render(path, vars)
123
+ base = File.basename(path, ".j2")
124
+ dir ||= "."
125
+ FileUtils.mkdir_p dir unless Dir.exists? dir
126
+ path = "#{dir}/#{base}"
127
+ File.write(path, text)
128
+ _lint path
129
+ print "Wrote".green + " #{path}\n"
130
+ end
131
+ end
132
+
133
+ private
134
+ def _find_keys_from_env
135
+ api_key = ENV.fetch(DATADOG_API_KEY, nil)
136
+ app_key = ENV.fetch(DATADOG_APP_KEY, nil)
137
+
138
+ if api_key && app_key
139
+ @api_key = api_key
140
+ @app_key = app_key
141
+ return true
142
+ end
143
+ false
144
+ end
145
+
146
+ def _find_keys_from_files
147
+ files = [
148
+ File.expand_path("./datadog-cli.yaml"),
149
+ File.expand_path("~/.datadog-cli.yaml"),
150
+ "/etc/datadog-cli.yaml",
151
+ ]
152
+
153
+ config = ENV.fetch(DATADOG_CONFIG, nil)
154
+ files.unshift config if config
155
+
156
+ files.each do |file|
157
+ if File.exists? file
158
+ config = YAML.load_file(file)["creds"]
159
+ _log "Error", "Datadog keys were not found in #{file}" unless config
160
+ @api_key = config["api_key"]
161
+ @app_key = config["app_key"]
162
+ return true
163
+ end
164
+ end
165
+ false
166
+ end
167
+
168
+ def _find_keys
169
+ return if _find_keys_from_env
170
+ return if _find_keys_from_files
171
+ abort "Error: Could not found any datadog env keys or any config file."
172
+ end
173
+
174
+ def _init
175
+ return if @init
176
+ _find_keys
177
+ puts "==> Using API_KEY #{@api_key} and APP_KEY #{@app_key}"
178
+ @params = "api_key=#{@api_key}&application_key=#{@app_key}"
179
+ @init = true
180
+ end
181
+
182
+ def _request method, url = "", body = nil
183
+ url = "#{DATADOG_ENDPOINT}#{url}?#{@params}"
184
+ res = HTTP.request(method, url, body: body, headers: DATADOG_HEADERS)
185
+ if res.code >= 300
186
+ print "Error".red, " Request #{method} #{url} failed.\n#{res.to_s}\n"
187
+ return false
188
+ end
189
+ res.body.to_s
190
+ end
191
+
192
+ def _list
193
+ return @list if @list
194
+ _init
195
+ json = _request("get")
196
+ @list = JSON.load(json)
197
+ end
198
+
199
+ def _check file
200
+ json = File.read(file)
201
+ data = JSON.parse(json)
202
+ name = data["name"]
203
+ _list().each do |m|
204
+ return true if m["name"] == name
205
+ return true if m["name"].hyphenate == name
206
+ end
207
+ return false
208
+ end
209
+
210
+ def _check_name data
211
+ _list().each do |m|
212
+ return m["id"] if m["name"] == data["name"]
213
+ end
214
+ return false
215
+ end
216
+
217
+ def _get id, raw = false
218
+ _init
219
+ json = _request("get", "/#{id}").body.to_s
220
+ return json if raw
221
+ JSON.load(json)
222
+ end
223
+
224
+ def _clean data
225
+ data.delete "id"
226
+ data.delete "org_id"
227
+ data.delete "creator"
228
+ data.delete "created"
229
+ data.delete "created_at"
230
+ data.delete "modified"
231
+ data
232
+ end
233
+
234
+ def _update file
235
+ _init
236
+ _lint file
237
+
238
+ json = File.read(file)
239
+ data = JSON.load(json)
240
+ data = _clean(data)
241
+
242
+ id = _check_name data
243
+ if id
244
+ return id if _request("put", "/#{id}", json)
245
+ else
246
+ return true if _request("post", "", json)
247
+ end
248
+
249
+ false
250
+ end
251
+
252
+ def _lint file
253
+ `jsonlint #{file}`
254
+ return true if $?.exitstatus == 0
255
+ abort "Error: ".red + "File '#{file}' failed to lint."
256
+ end
257
+
258
+ def _get_vars vars
259
+ file = _find_vars(vars)
260
+ data = YAML.load_file(file)
261
+
262
+ if data["import"]
263
+ impf = "#{File.dirname(file)}/#{data["import"]}"
264
+ impd = YAML.load_file(impf)
265
+ data.deep_merge(impd)
266
+ end
267
+
268
+ data
269
+ end
270
+
271
+ def _self_render file
272
+ vars = _get_vars(file)
273
+ # template = File.read(file)
274
+ # renderer = Liquid::Template.parse(template)
275
+ # output = renderer.render(vars)
276
+ end
277
+
278
+ def _render file, vars
279
+ vars = _self_render(vars)
280
+ template = _encode_template(File.read(file))
281
+ renderer = Liquid::Template.parse(template)
282
+ output = _decode_template(renderer.render(vars))
283
+ end
284
+
285
+ def _encode_template text
286
+ text
287
+ .gsub(/\{\{#(.*?)\}\}/, "#{5.chr}\\1#{3.chr}")
288
+ .gsub(/\{\{\/(.*?)\}\}/, "#{6.chr}\\1#{3.chr}")
289
+ .gsub(/\{\{\^(.*?)\}\}/, "#{7.chr}\\1#{3.chr}")
290
+ end
291
+
292
+ def _decode_template text
293
+ text
294
+ .gsub(/#{5.chr}(.*?)#{3.chr}/, "{{#\\1}}")
295
+ .gsub(/#{6.chr}(.*?)#{3.chr}/, "{{\/\\1}}")
296
+ .gsub(/#{7.chr}(.*?)#{3.chr}/, "{{\^\\1}}")
297
+ end
298
+
299
+ def _find_vars vars
300
+ # files = [vars, options[:vars], "#{File.dirname(file)}/vars.yaml"]
301
+ files = [vars, options[:vars]]
302
+ files.each do |file|
303
+ return file if file and File.exist? file
304
+ end
305
+ abort "Error: Couldn't find a valid vars file." +
306
+ " You can always pass it via --vars <path/to/vars.yaml>."
307
+ end
308
+
309
+ end
310
+
311
+ class Cli < Thor
312
+ desc "version", "Shows the version"
313
+ def version
314
+ puts "v#{VERSION}"
315
+ end
316
+
317
+ desc "monitor COMMAND", "Manage your datadog monitors"
318
+ subcommand "monitor", Monitor
319
+ end
320
+ end
321
+
322
+ Datadog::Cli.start(ARGV)
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+
3
+ require "datadog/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "datadog-cli"
7
+ spec.version = Datadog::VERSION
8
+ spec.summary = "Manage your datadog monitors"
9
+ spec.description = "Manage your datadog monitors."
10
+ spec.homepage = "https://github.com/jpedro/datadog-cli"
11
+ spec.authors = ["jpedro"]
12
+ spec.email = ["jpedro.barbosa@gmail.com"]
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files`.split $/
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_dependency "deep_merge"
20
+ spec.add_dependency "thor"
21
+ spec.add_dependency "http"
22
+ spec.add_dependency "liquid"
23
+ spec.add_dependency "jsonlint"
24
+ spec.add_dependency "colorize"
25
+ spec.add_dependency "tablelize"
26
+ spec.add_development_dependency "rake"
27
+ end
@@ -0,0 +1,7 @@
1
+ require "datadog/version"
2
+ require "datadog/ext/string"
3
+
4
+
5
+ module Datadog
6
+
7
+ end
@@ -0,0 +1,24 @@
1
+ unless String.method_defined? :clean
2
+ class String
3
+ def clean
4
+ self
5
+ .gsub(/[^0-9A-Za-z]/,'-')
6
+ .gsub(/\-{2,}/,'-')
7
+ .gsub(/^\-/, '')
8
+ .gsub(/\-$/, '')
9
+ .downcase
10
+ end
11
+ end
12
+ end
13
+
14
+ unless String.method_defined? :hyphenate
15
+ class String
16
+ def hyphenate
17
+ self
18
+ .gsub(/([A-Z]+)([A-Z][a-z])/,'\1-\2')
19
+ .gsub(/([a-z\d])([A-Z])/,'\1-\2')
20
+ .clean
21
+ .downcase
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ module Datadog
2
+
3
+ VERSION = "0.1.16"
4
+
5
+ end
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: datadog-cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.16
5
+ platform: ruby
6
+ authors:
7
+ - jpedro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-02-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: deep_merge
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: http
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: liquid
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: jsonlint
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: colorize
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: tablelize
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Manage your datadog monitors.
126
+ email:
127
+ - jpedro.barbosa@gmail.com
128
+ executables:
129
+ - datadog
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - ".gitignore"
134
+ - ".gitmodules"
135
+ - Gemfile
136
+ - Gemfile.lock
137
+ - Rakefile
138
+ - Readme.md
139
+ - bin/datadog
140
+ - datadog-cli.gemspec
141
+ - lib/datadog.rb
142
+ - lib/datadog/ext/string.rb
143
+ - lib/datadog/version.rb
144
+ homepage: https://github.com/jpedro/datadog-cli
145
+ licenses:
146
+ - MIT
147
+ metadata: {}
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubyforge_project:
164
+ rubygems_version: 2.6.13
165
+ signing_key:
166
+ specification_version: 4
167
+ summary: Manage your datadog monitors
168
+ test_files: []