vgh 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,46 @@
1
+ require 'open-uri'
2
+ require 'net/http'
3
+ require 'uri'
4
+
5
+ module VGH
6
+ module Extended_AWS
7
+ module Extended_EC2
8
+
9
+ # This class gathers metadata information about the current instance, used by
10
+ # the applications in this gem.
11
+ #
12
+ # == Usage
13
+ #
14
+ # data = Metadata.new
15
+ # id = data.instance_id
16
+ # root = data.root_device
17
+ #
18
+ class MetaData
19
+
20
+ # Query the API server for the instance ID
21
+ # @return [String, nil] The current instance's ID
22
+ def instance_id
23
+ begin
24
+ @instance_id = open('http://instance-data/latest/meta-data/instance-id').read
25
+ rescue
26
+ message.fatal 'Could not get Instance ID!'
27
+ end
28
+ end
29
+
30
+ # Query the API server for the root device
31
+ # @return [String, nil] The root device of the current instance
32
+ def root_device
33
+ begin
34
+ @root_device = open('http://instance-data/latest/meta-data/block-device-mapping/root').read
35
+ rescue
36
+ message.fatal 'Could not get the root device!'
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ end # module Extended_EC2
43
+ end # module Extended_AWS
44
+ end # module VGH
45
+
46
+ require 'vgh/output'
@@ -0,0 +1,90 @@
1
+ module VGH
2
+ module Extended_AWS
3
+ module Extended_EC2
4
+
5
+ # Creates a snapshot of the specified volume.
6
+ #
7
+ # == Usage
8
+ #
9
+ # snap = Snapshot.new.
10
+ # snap.create(id, tag)
11
+ #
12
+ class Snapshot
13
+
14
+ # Load all needed classes
15
+ def initialize
16
+ @instance_id = MetaData.new.instance_id
17
+ @fqdn = System.fqdn
18
+ end
19
+
20
+ # The workflow to create a snapshot:
21
+ # - create snapshot
22
+ # - add a name tag
23
+ # - add an info tag
24
+ # @param [String] volume_id The ID of the volume to snapshot
25
+ # @param [String] volume_tag The Tag of the volume to snapshot
26
+ def create(volume_id, volume_tag)
27
+ snapshot(volume_id, volume_tag)
28
+ name_tag
29
+ info_tag(volume_id)
30
+ message.info "Creating and tagging snapshot \"#{@snapshot.id}\""
31
+ end
32
+
33
+ # Creates a snapshot for the specified volume
34
+ # @param [String] volume_id The ID of the volume to snapshot
35
+ # @param [String] volume_tag The tag of the volume to snapshot
36
+ # @return [String, nil] The newly created snapshot object
37
+ def snapshot(volume_id, volume_tag)
38
+ @snapshot = ec2.volumes[volume_id].
39
+ create_snapshot("Backup for #{volume_id}(#{volume_tag})")
40
+ end
41
+
42
+ # Creates a name tag for the newly created snapshot.
43
+ # The name is the FQDN of the current instance.
44
+ def name_tag
45
+ ec2.snapshots[@snapshot.id].tag('Name', :value => @fqdn)
46
+ end
47
+
48
+ # Creates an info tag for the newly created snapshot
49
+ def info_tag(volume_id)
50
+ ec2.snapshots[@snapshot.id].tag("Backup_#{@instance_id}", :value => volume_id)
51
+ end
52
+
53
+ # Purges expired snapshots
54
+ def purge
55
+ expired.each do |snapshot|
56
+ message.info "Deleting expired snapshot (#{snapshot.id})"
57
+ snapshot.delete
58
+ end
59
+ end
60
+
61
+ # Creates a list of expired snapshots according to the expiration time
62
+ # specified in the app's configuration file
63
+ # @return [Array] An array of expired snapshot objects
64
+ def expired
65
+ @expired = []
66
+ all.each {|snapshot|
67
+ if snapshot.start_time < (Time.now - $cfg[:expiration]*24*60*60)
68
+ @expired.push snapshot
69
+ end
70
+ }
71
+ return @expired
72
+ end
73
+
74
+ # Returns a list of snapshots that are named the same with the current FQDN.
75
+ def all
76
+ @all ||= ec2.snapshots.
77
+ with_owner('self').
78
+ tagged('Name').tagged_values(@fqdn)
79
+ end
80
+
81
+ end # class Snapshot
82
+
83
+ end # module Extended_EC2
84
+ end # module Extended_AWS
85
+ end # module VGH
86
+
87
+ require 'vgh/system'
88
+ require 'vgh/output'
89
+ require 'vgh/configuration'
90
+ require 'vgh/extended_aws/extended_ec2/metadata'
@@ -0,0 +1,66 @@
1
+ module VGH
2
+ module Extended_AWS
3
+ module Extended_EC2
4
+
5
+ # Collects information about the EBS volumes attached to the current instance.
6
+ # == Usage
7
+ # @volume = Volume.new
8
+ # @volume.list.map {|id, info|
9
+ # puts id
10
+ # puts info[:tag]
11
+ # puts info[:device)
12
+ # }
13
+ #
14
+ class Volume
15
+
16
+ # Collects all needed variables
17
+ def initialize
18
+ @instance_id = MetaData.new.instance_id
19
+ end
20
+
21
+ # Creates a Hash collection of volumes containing their id, tag and device
22
+ # @return [Hash]
23
+ def list
24
+ @list = {}
25
+ mappings.map {|device, info|
26
+ volume_id = info.volume.id
27
+ @list[volume_id] = {
28
+ :device => device,
29
+ :tag => tag(volume_id)
30
+ }
31
+ }
32
+ return @list
33
+ end
34
+
35
+ # Returns a Hash containing the block device mappings of the current instance
36
+ # @return [Hash]
37
+ def mappings
38
+ message.info "Creating a list of volumes..."
39
+ @mappings ||= ec2.instances[@instance_id].block_device_mappings
40
+ end
41
+
42
+ # Get volume's Name tag
43
+ # @return [String] The tag of the volume or (NOTAG) if a tag does not exists.
44
+ def tag(volume_id)
45
+ v_tags = tags(volume_id)
46
+ v_tags.count == 0 ? @tag = '(NOTAG)' : @tag = v_tags.first.value
47
+ end
48
+
49
+ # Returns a collection of tags for the specified volume.
50
+ # @param [String] volume_id The id of the volume to query for tags.
51
+ def tags(volume_id)
52
+ @tags = ec2.tags.
53
+ filter('resource-type', 'volume').
54
+ filter('key', 'Name').
55
+ filter('resource-id', volume_id)
56
+ end
57
+
58
+ end # class Volume
59
+
60
+ end # module Extended_EC2
61
+ end # module Extended_AWS
62
+ end # module VGH
63
+
64
+ require 'vgh/output'
65
+ require 'vgh/configuration'
66
+ require 'vgh/extended_aws/extended_ec2/metadata'
@@ -0,0 +1,10 @@
1
+ module VGH
2
+ module Extended_AWS
3
+
4
+ # A collection of classes used with the AWS-SDK for Ruby (EC2).
5
+ module Extended_EC2
6
+
7
+ end
8
+
9
+ end # module Extended_AWS
10
+ end # module VGH
@@ -0,0 +1,10 @@
1
+ require 'aws-sdk'
2
+
3
+ module VGH
4
+
5
+ # A collection of classes used with the AWS-SDK for Ruby.
6
+ module Extended_AWS
7
+
8
+ end # module Extended_AWS
9
+
10
+ end # module VGH
@@ -0,0 +1,66 @@
1
+ require 'logger'
2
+
3
+ module VGH
4
+
5
+ # Returns log state
6
+ def log
7
+ if logging?
8
+ $log ||= Logging.new.log
9
+ else
10
+ $log ||= Logger.new('/dev/null')
11
+ end
12
+ end
13
+
14
+ # == Description:
15
+ #
16
+ # This class logs messages if logging is enabled from the command line
17
+ # options. The default location of the log files is +/var/log/vgh.log+.
18
+ #
19
+ # This class uses the Ruby Logger standard library.
20
+ #
21
+ #
22
+ # == Usage:
23
+ #
24
+ # log = Logging.new.log
25
+ # log.info "This is an info message"
26
+ #
27
+ class Logging
28
+
29
+ # Defaults
30
+ def defaults
31
+ @path = '/var/log/vgh.log'
32
+ @level = Logger::INFO
33
+ end
34
+
35
+ # Check log file existence
36
+ def initialize
37
+ defaults
38
+ validate_log_directory
39
+ end
40
+
41
+ # Creates a log directory and file if it does not already exist
42
+ def validate_log_directory
43
+ dir = File.dirname(@path)
44
+ Dir.mkdir(dir) unless File.exists?(dir)
45
+ end
46
+
47
+ # Opens the log file
48
+ def log_file
49
+ File.open(@path, File::WRONLY | File::APPEND | File::CREAT)
50
+ end
51
+
52
+ # Global, memoized, lazy initialized instance of a logger
53
+ def log
54
+ # Logger
55
+ @log ||= Logger.new(log_file)
56
+ @log.level = @level
57
+ @log.datetime_format = "%Y-%m-%d %H:%M " # simplify time output
58
+ @log
59
+ end
60
+
61
+ end # class Logging
62
+
63
+ end # module VGH
64
+
65
+ require 'vgh/cli'
66
+
data/lib/vgh/output.rb ADDED
@@ -0,0 +1,113 @@
1
+ module VGH
2
+
3
+ # Creates a global message method
4
+ def message
5
+ $message ||= Output.new
6
+ end
7
+
8
+ # == Description:
9
+ #
10
+ # This class sends messages to +STDOUT+ or to a log file. It takes the following
11
+ # standard methods:
12
+ # - +debug+
13
+ # - +info+
14
+ # - +warn+
15
+ # - +error+
16
+ # - +fatal+
17
+ #
18
+ # If +stdout+ is called then the message is only sent to +STDOUT+.
19
+ #
20
+ # If +header+ or +footer+ is called then it displays them.
21
+ #
22
+ #
23
+ # == Usage:
24
+ # # Load output
25
+ # message = Output.new
26
+ #
27
+ # message.header
28
+ #
29
+ # message.info "Starting code"
30
+ # # Your code here
31
+ # message.info "End code"
32
+ #
33
+ # message.footer
34
+ #
35
+ class Output
36
+
37
+ # Debug message
38
+ attr_reader :debug
39
+ # Inforational message
40
+ attr_reader :info
41
+ # Warning message
42
+ attr_reader :warn
43
+ # Error message
44
+ attr_reader :error
45
+ # Fatal Error message
46
+ attr_reader :fatal
47
+ # Header
48
+ attr_reader :header
49
+ # Footer
50
+ attr_reader :footer
51
+
52
+ # Writes a debug log message and outputs to screen
53
+ def debug(message)
54
+ log.debug(message)
55
+ stdout message
56
+ end
57
+
58
+ # Writes an info log message and outputs to screen
59
+ def info(message)
60
+ log.info(message)
61
+ stdout message
62
+ end
63
+
64
+ # Writes an warn log message and outputs to screen
65
+ def warn(message)
66
+ log.warn(message)
67
+ stdout message
68
+ end
69
+
70
+ # Writes an error log message and outputs to screen
71
+ def error(message)
72
+ log.error(message)
73
+ stdout message
74
+ end
75
+
76
+ # Writes a fatal log message and outputs to screen
77
+ def fatal(message)
78
+ log.fatal(message)
79
+ stdout message
80
+ end
81
+
82
+ # Outputs a message to screen
83
+ def stdout(message)
84
+ puts message if verbose?
85
+ end
86
+
87
+ # Returns the header
88
+ def header
89
+ stdout <<END_HEADER
90
+ ###############################################################################
91
+ VladGh.com - Scripts (v#{VERSION})
92
+ #{Time.now.strftime("%m/%d/%Y %H:%M:%S(%Z)")}
93
+ ###############################################################################
94
+
95
+ END_HEADER
96
+ end
97
+
98
+ # Returns the footer
99
+ def footer
100
+ stdout <<END_FOOTER
101
+
102
+ ###############################################################################
103
+ RUBY ROCKS!!!
104
+ ###############################################################################"
105
+ END_FOOTER
106
+ end
107
+
108
+ end # class Output
109
+ end # module VGH
110
+
111
+ require 'vgh/cli'
112
+ require 'vgh/configuration'
113
+ require 'vgh/logging'
@@ -0,0 +1,81 @@
1
+ module VGH
2
+ module System
3
+
4
+ # This class is able to suspend or resume logical volumes. The +lvm2+ package
5
+ # needs to be installed for this to work.
6
+ #
7
+ # Suspending a volume becomes useful if you want for example a consistent
8
+ # EC2 snapshot of it. Any I/O that has already been mapped by the device but
9
+ # has not yet completed will be flushed. Any further I/O to that device will
10
+ # be postponed for as long as the device is suspended.
11
+ #
12
+ #
13
+ # == Usage:
14
+ #
15
+ # lvm = System::LV.new
16
+ # lvm.suspend_lvs
17
+ # # run the code that takes the snapshot
18
+ # lvm.resume_lvs
19
+ #
20
+ class LV
21
+
22
+ # Loads variables and checks if LVM Tools are installed
23
+ def initialize
24
+ @dmcmd = '/sbin/dmsetup'
25
+ installed?
26
+ end
27
+
28
+ # Warn message if LVM tools are not installed
29
+ def installed?
30
+ message.warn "LVM Tools are not installed" unless File.exists?(@dmcmd)
31
+ end
32
+
33
+ # Test if logical volumes are present
34
+ # @return [Boolean]
35
+ def lvs_are_present?
36
+ lvs_are_present = false
37
+ @lvlist = `#{@dmcmd} ls | /bin/grep -E -v 'swap|root'`
38
+ if @lvlist != "No devices found\n" then
39
+ lvs_are_present = true
40
+ end
41
+ return lvs_are_present
42
+ end
43
+
44
+ # Suspend all logical volume
45
+ def suspend
46
+ if lvs_are_present?
47
+ suspend_lvs
48
+ else
49
+ message.info "No logical volumes found."
50
+ end
51
+ end
52
+
53
+ # The actual suspend action
54
+ def suspend_lvs
55
+ for lv_name in @lvlist.split[0]
56
+ message.info "Suspending Logical Volume '#{lv_name}'..."
57
+ `#{@dmcmd} suspend #{lv_name}`
58
+ end
59
+ end
60
+
61
+ # Resume all logical volumes
62
+ def resume
63
+ if lvs_are_present?
64
+ resume_lvs
65
+ end
66
+ end
67
+
68
+ # The actual resume action
69
+ def resume_lvs
70
+ for lv_name in @lvlist.split[0]
71
+ message.info "Resuming Logical Volume '#{lv_name}'..."
72
+ `#{@dmcmd} resume #{lv_name}`
73
+ end
74
+ end
75
+
76
+ end # class LV
77
+ end # module System
78
+ end # module VGH
79
+
80
+ require 'vgh/output'
81
+
@@ -0,0 +1,56 @@
1
+ module VGH
2
+ module System
3
+
4
+ # This class checks if a local MySQL server is present and running.
5
+ # The credentials need to be specified in the app's configuration file.
6
+ #
7
+ #
8
+ # == Usage
9
+ #
10
+ # mysql = MySQL.new
11
+ # mysql.flush
12
+ # # run backup
13
+ # mysql.unlock
14
+ #
15
+ class MySQL
16
+
17
+ # Load defaults
18
+ def initialize
19
+ @mysqladmin = '/usr/bin/mysqladmin'
20
+ @mysql = '/usr/bin/mysql'
21
+ @user = $cfg[:mysql_user]
22
+ @password = $cfg[:mysql_pwd]
23
+ end
24
+
25
+ # Check if server is running and we have access to the credentials file
26
+ # @return [Boolean]
27
+ def mysql_exists?
28
+ mysql_exists = false
29
+ if File.exists?(@mysqladmin)
30
+ mysql_exists = system "#{@mysqladmin} -s ping"
31
+ end
32
+ return mysql_exists
33
+ end
34
+
35
+ # Lock & Flush the MySQL tables
36
+ def flush
37
+ if mysql_exists?
38
+ message.info 'Locking MySQL tables...'
39
+ `#{@mysql} -u#{@user} -p#{@password} -e "FLUSH TABLES WITH READ LOCK"`
40
+ end
41
+ end
42
+
43
+ # Unlock the MySQL tables
44
+ def unlock
45
+ if mysql_exists?
46
+ message.info 'Unlocking MySQL tables...'
47
+ `#{@mysql} -u#{@user} -p#{@password} -e "UNLOCK TABLES"`
48
+ end
49
+ end
50
+
51
+ end # class MySQL
52
+ end # module System
53
+ end # module VGH
54
+
55
+ require 'vgh/output'
56
+ require 'vgh/configuration'
data/lib/vgh/system.rb ADDED
@@ -0,0 +1,21 @@
1
+ module VGH
2
+
3
+ # This is a parent class for different system actions performed by the scripts
4
+ # included in this gem. For more information see the classes defined under
5
+ # this namespace.
6
+ #
7
+ # == Usage
8
+ #
9
+ # fqdn = System.fqdn
10
+ #
11
+ module System
12
+
13
+ # FQDN
14
+ # @return [String] The FQDN of the current machine.
15
+ def self.fqdn
16
+ $fqdn ||= `hostname -f`
17
+ end
18
+
19
+ end # module System
20
+
21
+ end # module VGH
@@ -0,0 +1,9 @@
1
+ module VGH
2
+ # Version number
3
+ VERSION = "0.0.1"
4
+
5
+ # Returns the version number
6
+ def version
7
+ VERSION
8
+ end
9
+ end
data/lib/vgh.rb ADDED
@@ -0,0 +1,60 @@
1
+ # Try to load rubygems. Hey rubygems, I hate you.
2
+ begin
3
+ require 'rubygems'
4
+ rescue LoadError
5
+ end
6
+
7
+ require 'aws-sdk'
8
+ require 'sinatra'
9
+
10
+ require 'yaml'
11
+ require 'logger'
12
+ require 'erb'
13
+ require 'open-uri'
14
+ require 'net/http'
15
+ require 'uri'
16
+ require 'optparse'
17
+
18
+ # See the {file:README.rdoc README} file.
19
+ module VGH
20
+
21
+ # The main run method
22
+ def run
23
+
24
+ # Check if this script is run with root credentials
25
+ root_check
26
+
27
+ # Display header
28
+ show_header
29
+
30
+ # Run apps
31
+ case app
32
+ when 'ec2-backup'
33
+ APPS::EC2_Backup.new.run
34
+ end
35
+
36
+ # Display footer
37
+ show_footer
38
+
39
+ end # end run method
40
+
41
+ # Returns the header
42
+ def show_header
43
+ message.header
44
+ end
45
+
46
+ # Returns the footer
47
+ def show_footer
48
+ message.footer
49
+ end
50
+
51
+ # Raise error if this app is not run as root
52
+ def root_check
53
+ raise 'Must run as root' unless Process.uid == 0
54
+ end
55
+
56
+ end # module VGH
57
+
58
+ require "vgh/output"
59
+ require "vgh/apps"
60
+ require "vgh/apps/ec2_backup"
data/spec/cli_spec.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'vgh'
3
+ require 'vgh/apps'
4
+ require 'vgh/cli'
5
+
6
+ describe "Command Line" do
7
+ VGH::APPS.list.map {|app_name|
8
+ it "Should accept '#{app_name}' as the first argument" do
9
+ ARGV[0] = app_name
10
+ VGH::CLI.new.options[:app].should eq app_name
11
+ end
12
+ }
13
+
14
+ it "Should enable verbosity" do
15
+ ARGV[0] = VGH::APPS.list.shuffle.first
16
+ ARGV.push('-v')
17
+ VGH::CLI.new.options[:verbose].should be_true
18
+ end
19
+
20
+ it "Should enable logging" do
21
+ ARGV[0] = VGH::APPS.list.shuffle.first
22
+ ARGV.push('-l')
23
+ VGH::CLI.new.options[:logging].should be_true
24
+ end
25
+ end
data/spec/misc_spec.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'vgh'
3
+ require 'vgh/cli'
4
+ require 'vgh/apps'
5
+ require 'vgh/output'
6
+ require 'vgh/logging'
7
+ require 'vgh/configuration'
8
+
9
+ describe "Various Tests" do
10
+ it "Should have an array of supported apps" do
11
+ VGH::APPS.list.should be_an Array
12
+ end
13
+
14
+ it "Should write logs" do
15
+ log = VGH::Logging.new.log
16
+ log.should_receive(:info).with("RSpec Test")
17
+ log.info("RSpec Test")
18
+ end
19
+
20
+ it "Should write messages" do
21
+ message = VGH::Output.new
22
+ message.should_receive(:stdout).with("RSpec Test")
23
+ message.stdout("RSpec Test")
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ RSpec.configure do |config|
2
+ config.treat_symbols_as_metadata_keys_with_true_values = true
3
+ config.run_all_when_everything_filtered = true
4
+ config.filter_run :focus
5
+ config.order = 'random'
6
+ end
7
+