datadog-cli 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []