do_snapshot 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7053ccac28ae155fac055356c0e2ef2e95373ffb
4
+ data.tar.gz: d315d1e4faa88f2b6562b64ab79d8480e01e25c5
5
+ SHA512:
6
+ metadata.gz: 77ca8773db485352055ca4118661451bcf1c55a538e248de7e7ed3eb44e6e8e5d6314ebb66645ed54f57f4fb31c76a21993000b1f97cc0f01223195cb3daeb80
7
+ data.tar.gz: 7d91bb1e124c979af67c26aed36c31e4d3e1d0f958b1ac01ca1f8e122fe8dba8e8e9e2585c1cd4d4973855e572470df7c7ea1a047b5b8e54c2fa754a3e80612a
data/.gitignore ADDED
@@ -0,0 +1,25 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ .idea
24
+ .irb_history
25
+
data/.rubocop.yml ADDED
@@ -0,0 +1,11 @@
1
+ LineLength:
2
+ Max: 200
3
+
4
+ HashSyntax:
5
+ EnforcedStyle: ruby19
6
+
7
+ SingleSpaceBeforeFirstArg:
8
+ Enabled: false
9
+
10
+ TrailingComma:
11
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in do_snapshot.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Alexander Merkulov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # DoSnapshot
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'do_snapshot'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install do_snapshot
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/do_snapshot/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
data/bin/do_snapshot ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # resolve bin path, ignoring symlinks
4
+ require 'pathname'
5
+ bin_file = Pathname.new(__FILE__).realpath
6
+
7
+ # add self to libpath
8
+ $:.unshift File.expand_path('../../lib', bin_file)
9
+
10
+ require 'do_snapshot/cli'
11
+
12
+ DoSnapshot::CLI.start(ARGV)
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'do_snapshot/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'do_snapshot'
8
+ spec.version = DoSnapshot::VERSION
9
+ spec.authors = ['Alexander Merkulov']
10
+ spec.email = ['sasha@merqlove.ru']
11
+ spec.summary = %q{Snapshot runner for Digital Ocean droplets. Use it with Cron or other tools.}
12
+ spec.description = %q{Snapshot runner for Digital Ocean droplets. Use it with Cron or other tools.}
13
+ spec.homepage = 'http://github.com/merqlove/do_snapshot'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'digitalocean', '~> 1.2'
22
+ spec.add_dependency 'thor'
23
+ spec.add_dependency 'pony'
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.6'
26
+ spec.add_development_dependency 'rake'
27
+ end
@@ -0,0 +1,46 @@
1
+ require 'do_snapshot/version'
2
+ require 'logger'
3
+
4
+
5
+ # Used primary for creating snapshot's as backups for DigitalOcean
6
+ #
7
+ module DoSnapshot
8
+ # Shared logger
9
+ #
10
+ class Log
11
+ class << self
12
+ attr_accessor :logger
13
+ attr_accessor :thor_log
14
+
15
+ def log(type, message)
16
+ logger.send(type, message) if logger
17
+ end
18
+
19
+ def info(message)
20
+ log :info, message
21
+ say message, :green
22
+ end
23
+
24
+ def warning(message)
25
+ log :warn, message
26
+ say message, :yellow
27
+ end
28
+
29
+ def error(message)
30
+ log :error, message
31
+ say message, :red
32
+ end
33
+
34
+ def debug(message)
35
+ log :debug, message
36
+ say message, :white if logger && logger.level == Logger::DEBUG
37
+ end
38
+
39
+ protected
40
+
41
+ def say(message, color)
42
+ thor_log.say message, color if thor_log
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,73 @@
1
+ require 'do_snapshot'
2
+ require 'do_snapshot/command'
3
+ require 'thor'
4
+
5
+ module DoSnapshot
6
+ # CLI is here
7
+ #
8
+ class CLI < Thor
9
+ default_task :create
10
+
11
+ def initialize(*args)
12
+ super
13
+ if options.include? 'log'
14
+ Log.logger = Logger.new(options['log'])
15
+ Log.thor_log = Thor::Shell::Color.new
16
+ Log.logger.level = options.include?('trace') ? Logger::DEBUG : Logger::INFO
17
+ end
18
+ try_keys_first
19
+ end
20
+
21
+ desc 'create', 'create and cleanup snapshot\'s'
22
+ long_desc <<-LONGDESC
23
+ `do_snapshot create` will create and cleanup snapshots on your droplets.
24
+
25
+ You can optionally specify parameters to select or exclude some droplets.
26
+
27
+ Advanced options example for MAIL feature:
28
+
29
+ --mail to:mail@somehost.com from:from@host.com --smtp address:smtp.gmail.com user_name:someuser password:somepassword
30
+
31
+ For more details look here: https://github.com/benprew/pony
32
+
33
+ Example:
34
+
35
+ > $ do_snapshot --keep 5
36
+
37
+ > $ do_snapshot --only 123456 1234567 --store 3
38
+
39
+ > $ do_snapshot --exclude 123456 123457
40
+
41
+ > $ do_snapshot --keep 10 --stop true --mail to:yourmail@example.com
42
+ LONGDESC
43
+ option :only, type: :array, default: [], aliases: ['-o'], banner: '123456 123456 123456', desc: 'Use only selected droplets.'
44
+ option :exclude, type: :array, default: [], aliases: ['-e'], banner: '123456 123456 123456', desc: 'Except some droplets.'
45
+ option :keep, type: :numeric, default: 10, aliases: ['-k'], banner: '5', desc: 'How much snapshots you want to keep?'
46
+ option :stop, type: :boolean, aliases: ['-s'], banner: 'true', desc: 'Stop creating snapshots if maximum is reached.'
47
+ option :mail, type: :hash, default: {}, aliases: ['-m'], banner: 'to:yourmail@example.com', desc: 'Receive mail if maximum is reached.'
48
+ option :smtp, type: :hash, default: {}, aliases: ['-t'], banner: 'user_name:yourmail@example.com password:password', desc: 'SMTP options.'
49
+ option :log, type: :string, aliases: ['-l'], banner: '/Users/someone/.do_snapshot/main.log', desc: 'Log file path. By default logging is disabled.'
50
+ option :trace, type: :boolean, aliases: ['-d'], desc: 'Debug mode. Not implented now. Switched only log mode.'
51
+ def create
52
+ DoSnapshot::Command.execute options, %w( log trace )
53
+ rescue => e
54
+ Log.error e.message
55
+ backtrace(e) if options.include? 'trace'
56
+ end
57
+
58
+ protected
59
+
60
+ def backtrace(e)
61
+ e.backtrace.each do |t|
62
+ Log.error t
63
+ end
64
+ end
65
+
66
+ # Check DigitalOcean API keys
67
+ def try_keys_first
68
+ Log.debug 'Checking DigitalOcean Id\'s.'
69
+ fail Thor::Error, 'You must have DIGITAL_OCEAN_CLIENT_ID in environment.' if !ENV['DIGITAL_OCEAN_CLIENT_ID'] || ENV['DIGITAL_OCEAN_CLIENT_ID'].empty?
70
+ fail Thor::Error, 'You must have DIGITAL_OCEAN_API_KEY in environment.' if !ENV['DIGITAL_OCEAN_API_KEY'] || ENV['DIGITAL_OCEAN_API_KEY'].empty?
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,166 @@
1
+ require 'digitalocean'
2
+ require 'date'
3
+ require 'pony'
4
+ require 'core_ext/hash'
5
+
6
+ module DoSnapshot
7
+ # Our commands live here :)
8
+ #
9
+ class Command
10
+ class << self
11
+ def execute(options, skip)
12
+ return unless options
13
+
14
+ options.each_pair do |key, option|
15
+ send("#{key}=", option) unless skip.include? key
16
+ end
17
+
18
+ self.notify = false
19
+
20
+ work_droplets
21
+ email_message if notify && mail
22
+
23
+ Log.info 'All snapshots requested'
24
+ end
25
+
26
+ protected
27
+
28
+ attr_accessor :droplets
29
+ attr_accessor :exclude
30
+ attr_accessor :only
31
+ attr_accessor :keep
32
+ attr_accessor :mail
33
+ attr_accessor :smtp
34
+ attr_accessor :stop
35
+ attr_accessor :notify
36
+
37
+ # Getting droplets list from API.
38
+ # And store into object.
39
+ #
40
+ def load_droplets
41
+ set_id
42
+ Log.debug 'Loading list of DigitalOcean droplets'
43
+ droplets = Digitalocean::Droplet.all
44
+ fail droplets.message unless droplets.status == 'OK'
45
+ self.droplets = droplets.droplets
46
+ end
47
+
48
+ # Working with received list of droplets.
49
+ #
50
+ def work_droplets
51
+ load_droplets
52
+ Log.debug 'Working with list of DigitalOcean droplets'
53
+ droplets.each do |droplet|
54
+ id = droplet.id.to_s
55
+ next if exclude.include? id
56
+ next if !only.empty? && !only.include?(id)
57
+
58
+ instance = Digitalocean::Droplet.find(id)
59
+ fail instance.message unless instance.status == 'OK'
60
+
61
+ prepare_instance instance.droplet
62
+ end
63
+ end
64
+
65
+ # Preparing instance to take snapshot.
66
+ # Instance must be powered off first!
67
+ #
68
+ def prepare_instance(instance)
69
+ return unless instance
70
+ Log.debug "Preparing droplet id: #{instance.id} name: #{instance.name} to snapshot."
71
+
72
+ warning_size = "For droplet with id: #{instance.id} and name: #{instance.name} the maximum #{keep} is reached."
73
+
74
+ if instance.snapshots.size >= keep && stop
75
+ Log.warning warning_size
76
+ self.notify = true
77
+ return
78
+ end
79
+
80
+ # Stopping instance.
81
+ Log.debug 'Shutting down droplet.'
82
+ unless instance.status.include? 'off'
83
+ event = Digitalocean::Droplet.power_off(instance.id)
84
+ if event.status.include? 'OK'
85
+ sleep 1.3 until get_event_status(event.event_id)
86
+ end
87
+ end
88
+
89
+ # Create snapshot.
90
+ create_snapshot instance, warning_size
91
+ end
92
+
93
+ # Trying to create a snapshot.
94
+ #
95
+ def create_snapshot(instance, warning_size)
96
+ Log.debug "Start creating snapshot for droplet id: #{instance.id} name: #{instance.name}."
97
+
98
+ today = DateTime.now
99
+ name = "#{instance.name}_#{today.strftime('%Y_%m_%d')}"
100
+ event = Digitalocean::Droplet.snapshot(instance.id, name: name)
101
+ snapshot_size = instance.snapshots.size
102
+
103
+ if !event
104
+ fail 'Something wrong with DigitalOcean or with your connection :)'
105
+ elsif event && !event.status.include?('OK')
106
+ fail event.message
107
+ end
108
+
109
+ Log.debug 'Wait until snapshot will be created.'
110
+
111
+ sleep 10 until get_event_status(event.event_id)
112
+
113
+ snapshot_size += 1
114
+
115
+ Log.info "Snapshot name: #{name} created successfully."
116
+ Log.info "Droplet id: #{instance.id} name: #{instance.name} snapshots: #{snapshot_size}."
117
+
118
+ if snapshot_size > keep
119
+ Log.warning warning_size if snapshot_size > keep
120
+ self.notify = true
121
+ end
122
+ end
123
+
124
+ # Looking for event status.
125
+ #
126
+ # Before snapshot we to know that machine has powered off.
127
+ #
128
+ def get_event_status(id)
129
+ event = Digitalocean::Event.find(id)
130
+ fail event.message unless event.status.include?('OK')
131
+ event.event.percentage && event.event.percentage.include?('100') ? true : false
132
+ end
133
+
134
+ # Sending message via Hash params.
135
+ #
136
+ # Options:: -m to:mail@somehost.com from:from@host.com -t address:smtp.gmail.com user_name:someuser password:somepassword
137
+ #
138
+ def email_message
139
+ mail.symbolize_keys!
140
+ smtp.symbolize_keys!
141
+
142
+ mail[:subject] = 'DigitalOcean Snapshot maximum is reached.' unless mail[:subject]
143
+ mail[:body] = "Please cleanup your Digital Ocean account. \nSnapshot maximum is reached." unless mail[:body]
144
+ mail[:from] = 'noreply@someonelse.com' unless mail[:from]
145
+ mail[:via] = :smtp unless mail[:via]
146
+
147
+ smtp[:domain] = 'localhost.localdomain' unless smtp[:domain]
148
+ smtp[:port] = '25' unless smtp[:port]
149
+
150
+ mail[:via_options] = smtp
151
+
152
+ Log.debug 'Sending e-mail notification.'
153
+ # Look into your inbox :)
154
+ Pony.mail(mail)
155
+ end
156
+
157
+ # Set id's of Digital Ocean API.
158
+ #
159
+ def set_id
160
+ Log.debug 'Setting DigitalOcean Id\'s.'
161
+ Digitalocean.client_id = ENV['DIGITAL_OCEAN_CLIENT_ID']
162
+ Digitalocean.api_key = ENV['DIGITAL_OCEAN_API_KEY']
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,36 @@
1
+ # Rails symbolize keys methods for Hash
2
+ #
3
+ class Hash
4
+ # Returns a new hash with all keys converted using the block operation.
5
+ #
6
+ # hash = { name: 'Rob', age: '28' }
7
+ #
8
+ # hash.transform_keys{ |key| key.to_s.upcase }
9
+ # # => {"NAME"=>"Rob", "AGE"=>"28"}
10
+ def transform_keys
11
+ result = {}
12
+ each_key do |key|
13
+ result[yield(key)] = self[key]
14
+ end
15
+ result
16
+ end
17
+
18
+ # Destructively convert all keys using the block operations.
19
+ # Same as transform_keys but modifies +self+.
20
+ def transform_keys!
21
+ keys.each do |key|
22
+ self[yield(key)] = delete(key)
23
+ end
24
+ self
25
+ end
26
+
27
+ def symbolize_keys
28
+ transform_keys { |key| key.to_sym rescue key }
29
+ end
30
+
31
+ # Destructively convert all keys to symbols, as long as they respond
32
+ # to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
33
+ def symbolize_keys!
34
+ transform_keys! { |key| key.to_sym rescue key }
35
+ end
36
+ end
@@ -0,0 +1,5 @@
1
+ # Current version
2
+ #
3
+ module DoSnapshot
4
+ VERSION = '0.0.1'
5
+ end
data/test/.keep ADDED
File without changes
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: do_snapshot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alexander Merkulov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-07-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: digitalocean
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
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: pony
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: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.6'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Snapshot runner for Digital Ocean droplets. Use it with Cron or other
84
+ tools.
85
+ email:
86
+ - sasha@merqlove.ru
87
+ executables:
88
+ - do_snapshot
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - ".gitignore"
93
+ - ".rubocop.yml"
94
+ - ".ruby-version"
95
+ - Gemfile
96
+ - LICENSE.txt
97
+ - README.md
98
+ - Rakefile
99
+ - bin/do_snapshot
100
+ - do_snapshot.gemspec
101
+ - lib/do_snapshot.rb
102
+ - lib/do_snapshot/cli.rb
103
+ - lib/do_snapshot/command.rb
104
+ - lib/do_snapshot/core_ext/hash.rb
105
+ - lib/do_snapshot/version.rb
106
+ - test/.keep
107
+ homepage: http://github.com/merqlove/do_snapshot
108
+ licenses:
109
+ - MIT
110
+ metadata: {}
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 2.2.2
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Snapshot runner for Digital Ocean droplets. Use it with Cron or other tools.
131
+ test_files:
132
+ - test/.keep