Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
bai committed Sep 9, 2015
0 parents commit 7895f34
Show file tree
Hide file tree
Showing 24 changed files with 905 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in doggy.gemspec
gemspec
21 changes: 21 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2015 Vlad Gorodetsky

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Doggy

Doggy manages your DataDog dashboards, alerts, monitors, and screenboards.

## Installation

Add this line to your Gemfile:

```ruby
gem 'doggy'
```

And then execute:

$ bundle

Or install it yourself as:

$ gem install doggy

## Usage

```
# Export your DataDog credentials or use ejson
$ export DATADOG_API_KEY=api_key_goes_here
$ export DATADOG_APP_KEY=app_key_goes_here
# Download selected items from DataDog
$ doggy pull ID ID
# Download all items
$ doggy pull
# Upload selected items to DataDog
$ doggy push ID ID ID
# Upload all items to DataDog
$ doggy push
# Create a new dashboard
$ doggy create dash 'My New Dash'
# Delete selected items from both DataDog and local storage
$ doggy delete ID ID ID
```

Note that we currently don't support global upload due to high risk of overwriting things. We'll turn this feature on after initial testing period.

## Development

After checking out the repo, run `bundle install` to install dependencies.

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).

## Contributing

1. Fork it ( https://github.com/bai/doggy/fork )
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require "bundler/gem_tasks"
9 changes: 9 additions & 0 deletions bin/doggy
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env ruby

# Exit cleanly from an early interrupt
Signal.trap('INT') { exit 1 }

require 'bundler/setup'
require 'doggy/cli'

Doggy::CLI.start(ARGV, :debug => true)
28 changes: 28 additions & 0 deletions doggy.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'doggy/version'

Gem::Specification.new do |spec|
spec.name = "doggy"
spec.version = Doggy::VERSION
spec.authors = ["Vlad Gorodetsky"]
spec.email = ["[email protected]"]

spec.summary = %q{Syncs DataDog dashboards, alerts, screenboards, and monitors.}
spec.description = %q{Syncs DataDog dashboards, alerts, screenboards, and monitors.}
spec.homepage = "http://github.com/bai/doggy"
spec.license = "MIT"

spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
spec.bindir = "bin"
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_development_dependency "bundler", "~> 1.9"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_dependency "thor", "~> 0.19"
spec.add_dependency "dogapi", "~> 1.17"
spec.add_dependency "thread", "~> 0.2"
spec.add_dependency "ejson", "~> 1.0"
end
118 changes: 118 additions & 0 deletions lib/doggy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
require 'fileutils'
require 'pathname'
require 'json'
require 'yaml'
require 'dogapi'

require 'doggy/version'
require 'doggy/client'
require 'doggy/worker'
require 'doggy/serializer/json'
require 'doggy/serializer/yaml'
require 'doggy/model/dash'
require 'doggy/model/monitor'
require 'doggy/model/screen'

module Doggy
DOG_SKIP_REGEX = /\[dog\s+skip\]/i.freeze
DEFAULT_SERIALIZER_CLASS = Doggy::Serializer::Json

class DoggyError < StandardError
def self.status_code(code)
define_method(:status_code) { code }
end
end

class InvalidOption < DoggyError; status_code(15); end
class InvalidItemType < DoggyError; status_code(10); end

class << self
# @option arguments [Constant] :serializer A specific serializer class to use, will be initialized by doggy and passed the object instance
def serializer(options = {})
@serializer ||= options[:serializer] ? options[:serializer] : DEFAULT_SERIALIZER_CLASS
end

def client
Doggy::Client.new
end

# Absolute path of where alerts are stored on the filesystem.
#
# @return [Pathname]
def alerts_path
@alerts_path ||= Pathname.new('alerts').expand_path(Dir.pwd).expand_path.tap { |path| FileUtils.mkdir_p(path) }
end

# Absolute path of where dashes are stored on the filesystem.
#
# @return [Pathname]
def dashes_path
@dashes_path ||= Pathname.new('dashes').expand_path(Dir.pwd).expand_path.tap { |path| FileUtils.mkdir_p(path) }
end

# Absolute path of where screens are stored on the filesystem.
#
# @return [Pathname]
def screens_path
@screens_path ||= Pathname.new('screens').expand_path(Dir.pwd).expand_path.tap { |path| FileUtils.mkdir_p(path) }
end

# Cleans up directory
def clean_dir(dir)
Dir.foreach(dir) { |f| fn = File.join(dir, f); File.delete(fn) if f != '.' && f != '..'}
end

def all_local_items
@all_local_items ||= Dir[Doggy.dashes_path.join('**/*'), Doggy.alerts_path.join('**/*'), Doggy.screens_path.join('**/*')].inject({}) { |memo, file| memo.merge load_item(f) }
end

def load_item(f)
filetype = File.extname(f)

item = case filetype
when '.yaml', '.yml' then Doggy::Serializer::Yaml.load(File.read(f))
when '.json' then Doggy::Serializer::Json.load(File.read(f))
else raise InvalidItemType
end

{ [ determine_type(item), item['id'] ] => item }
end

def determine_type(item)
return 'dash' if item['graphs']
return 'monitor' if item['message']
return 'screen' if item['board_title']
raise InvalidItemType
end

def emit_shipit_deployment
Doggy.client.dog.emit_event(
Dogapi::Event.new(ENV['REVISION'], msg_title: "ShipIt Deployment by #{ENV['USER']}", tags: %w(audit shipit), source_type_name: 'shipit')
)
rescue => e
puts "Exception: #{e.message}"
end

def current_version
now = Time.now.to_i
month_ago = now - 3600 * 24 * 30
events = Doggy.client.dog.stream(month_ago, now, tags: %w(audit shipit))[1]['events']

events[0]['text'] # most recetly deployed SHA
rescue => e
puts "Exception: #{e.message}"
end

def all_remote_dashes
@all_remote_dashes ||= Doggy.client.dog.get_dashboards[1]['dashes'].inject({}) do |memo, dash|
memo.merge([ 'dash', dash['id'] ] => dash)
end
end

def all_remote_monitors
@all_remote_monitors ||= Doggy.client.dog.get_all_monitors[1].inject({}) do |memo, monitor|
memo.merge([ 'monitor', monitor['id'] ] => monitor)
end
end
end
end
97 changes: 97 additions & 0 deletions lib/doggy/cli.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
require 'thor'
require 'doggy'

module Doggy
class CLI < Thor
include Thor::Actions

def self.start(*)
super
rescue Exception => e
raise e
ensure
end

def initialize(*args)
super
rescue UnknownArgumentError => e
raise Doggy::InvalidOption, e.message
ensure
self.options ||= {}
end

check_unknown_options!(:except => [:config, :exec])
stop_on_unknown_option! :exec

desc "pull [SPACE SEPARATED IDs]", "Pulls objects from DataDog"
long_desc <<-D
Pull objects from DataDog. If pull is successful, Doggy exits with a status of 0.
If not, the error is displayed and Doggy exits status 1.
D
def pull(*ids)
require 'doggy/cli/pull'
Pull.new(options.dup, ids).run
end

desc "push [SPACE SEPARATED IDs]", "Pushes objects to DataDog"
long_desc <<-D
Pushes objects to DataDog. If push is successful, Doggy exits with a status of 0.
If not, the error is displayed and Doggy exits status 1.
D
def push(*ids)
require 'doggy/cli/push'
Push.new(options.dup, ids).run
end

desc "create OBJECT_TYPE OBJECT_NAME", "Creates a new object on DataDog"
long_desc <<-D
Creates a new object on DataDog. If create is successful, Doggy exits with a status of 0.
If not, the error is displayed and Doggy exits status 1.
D
def create(kind, name)
require 'doggy/cli/create'
Create.new(options.dup, kind, name).run
end

desc "delete SPACE SEPARATED IDs", "Deletes objects from DataDog"
long_desc <<-D
Deletes objects from DataDog. If delete is successful, Doggy exits with a status of 0.
If not, the error is displayed and Doggy exits status 1.
D
def delete(*ids)
require 'doggy/cli/delete'
Delete.new(options.dup, ids).run
end

desc "mute [SPACE SEPARATED IDs]", "Mutes monitor on DataDog"
long_desc <<-D
Mutes monitor on DataDog. If mute is successful, Doggy exits with a status of 0.
If not, the error is displayed and Doggy exits status 1.
D
def mute(*ids)
require 'doggy/cli/mute'
Mute.new(options.dup, ids).run
end

desc "unmute [SPACE SEPARATED IDs]", "Unmutes monitor on DataDog"
long_desc <<-D
Deletes objects from DataDog. If delete is successful, Doggy exits with a status of 0.
If not, the error is displayed and Doggy exits status 1.
D
def unmute(*ids)
require 'doggy/cli/unmute'
Unmute.new(options.dup, ids).run
end

desc "version", "Detects the most recent SHA deployed by ShipIt"
long_desc <<-D
Scans DataDog event stream for shipit events what contain most recently deployed version
of DataDog properties.
If not, the error is displayed and Doggy exits status 1.
D
def version
require 'doggy/cli/version'
Version.new.run
end
end
end
25 changes: 25 additions & 0 deletions lib/doggy/cli/create.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module Doggy
class CLI::Create
attr_reader :options, :kind, :name

def initialize(options, kind, name)
@options = options
@kind = kind
@name = name
end

def run
begin
case kind
when 'dash', 'dashboard' then Doggy::Dash.create(name)
when 'alert', 'monitor' then Doggy::Monitor.create(name)
when 'screen', 'screenboard' then Doggy::Screen.create(name)
else puts 'Unknown item type'
end
rescue DoggyError
puts "Create failed."
exit 1
end
end
end
end
Loading

0 comments on commit 7895f34

Please sign in to comment.