vgh 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.
@@ -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
+