CloudyScripts 0.0.11 → 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +7 -7
- data/README.rdoc +27 -27
- data/Rakefile +50 -50
- data/lib/cloudyscripts.rb +26 -26
- data/lib/help/dm_crypt_helper.rb +53 -54
- data/lib/help/ec2_helper.rb +54 -54
- data/lib/help/remote_command_handler.rb +213 -203
- data/lib/help/script_execution_state.rb +108 -97
- data/lib/help/state_change_listener.rb +13 -13
- data/lib/help/state_transition_helper.rb +362 -0
- data/lib/scripts/ec2/ami2_ebs_conversion.rb +180 -477
- data/lib/scripts/ec2/dm_encrypt.rb +147 -205
- data/lib/scripts/ec2/download_snapshot.rb +153 -0
- data/lib/scripts/ec2/ec2_script.rb +101 -52
- metadata +4 -2
data/LICENSE
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
Copyright (c) 2010 Matthias Jung
|
2
|
-
|
3
|
-
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:
|
4
|
-
|
5
|
-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
-
|
7
|
-
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.
|
1
|
+
Copyright (c) 2010 Matthias Jung
|
2
|
+
|
3
|
+
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:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
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.
|
data/README.rdoc
CHANGED
@@ -1,27 +1,27 @@
|
|
1
|
-
# =About
|
2
|
-
# CloudyScripts is a library that implements tasks that support common
|
3
|
-
# usecases on Cloud Computing Infrastructures (such as Amazon EC2 or Rackspace).
|
4
|
-
# It aims to facilitate the implementation of usecases that are not directly
|
5
|
-
# available via the providers' API (e.g. like encrypting storage,
|
6
|
-
# migrating instances betweem accounts, activating HTTPS). The scripts typically
|
7
|
-
# use the provider APIs plus remote access to command-line tools installed on
|
8
|
-
# the instances themselves.
|
9
|
-
#
|
10
|
-
# =Installation and Usage
|
11
|
-
# ==Installation
|
12
|
-
# <tt>gem install CloudyScripts</tt>
|
13
|
-
#
|
14
|
-
# ==Usage
|
15
|
-
# All scripts are available under /lib/scripts/<provider>
|
16
|
-
# They are initialized with a set of parameters and return a well-define
|
17
|
-
# set of return values.
|
18
|
-
#
|
19
|
-
# =Scripts
|
20
|
-
# Here are the scripts implemented so far:
|
21
|
-
# * #DmEncrypt (encrypt Amazon EBS Storage using dm-encrypt)
|
22
|
-
#
|
23
|
-
# =Questions and Suggestions
|
24
|
-
# Matthias Jung
|
25
|
-
# matthias.jung@gmail.com
|
26
|
-
# http://elastic-security.com
|
27
|
-
|
1
|
+
# =About
|
2
|
+
# CloudyScripts is a library that implements tasks that support common
|
3
|
+
# usecases on Cloud Computing Infrastructures (such as Amazon EC2 or Rackspace).
|
4
|
+
# It aims to facilitate the implementation of usecases that are not directly
|
5
|
+
# available via the providers' API (e.g. like encrypting storage,
|
6
|
+
# migrating instances betweem accounts, activating HTTPS). The scripts typically
|
7
|
+
# use the provider APIs plus remote access to command-line tools installed on
|
8
|
+
# the instances themselves.
|
9
|
+
#
|
10
|
+
# =Installation and Usage
|
11
|
+
# ==Installation
|
12
|
+
# <tt>gem install CloudyScripts</tt>
|
13
|
+
#
|
14
|
+
# ==Usage
|
15
|
+
# All scripts are available under /lib/scripts/<provider>
|
16
|
+
# They are initialized with a set of parameters and return a well-define
|
17
|
+
# set of return values.
|
18
|
+
#
|
19
|
+
# =Scripts
|
20
|
+
# Here are the scripts implemented so far:
|
21
|
+
# * #DmEncrypt (encrypt Amazon EBS Storage using dm-encrypt)
|
22
|
+
#
|
23
|
+
# =Questions and Suggestions
|
24
|
+
# Matthias Jung
|
25
|
+
# matthias.jung@gmail.com
|
26
|
+
# http://elastic-security.com
|
27
|
+
|
data/Rakefile
CHANGED
@@ -1,50 +1,50 @@
|
|
1
|
-
#
|
2
|
-
# To change this template, choose Tools | Templates
|
3
|
-
# and open the template in the editor.
|
4
|
-
|
5
|
-
|
6
|
-
require 'rubygems'
|
7
|
-
require 'rake'
|
8
|
-
require 'rake/clean'
|
9
|
-
require 'rake/gempackagetask'
|
10
|
-
require 'rake/rdoctask'
|
11
|
-
require 'rake/testtask'
|
12
|
-
|
13
|
-
spec = Gem::Specification.new do |s|
|
14
|
-
s.name = 'CloudyScripts'
|
15
|
-
s.version = '0.0.
|
16
|
-
s.has_rdoc = true
|
17
|
-
s.extra_rdoc_files = ['README.rdoc', 'LICENSE']
|
18
|
-
s.summary = 'Scripts to facilitate programming for infrastructure clouds.'
|
19
|
-
s.description = s.summary
|
20
|
-
s.homepage = "http://elastic-security.com"
|
21
|
-
s.rubyforge_project = "cloudyscripts"
|
22
|
-
s.author = 'Matthias Jung'
|
23
|
-
s.email = 'matthias.jung@gmail.com'
|
24
|
-
# s.executables = ['your_executable_here']
|
25
|
-
s.files = %w(LICENSE README.rdoc Rakefile) + Dir.glob("{bin,lib,spec}/**/*")
|
26
|
-
s.require_path = "lib"
|
27
|
-
s.bindir = "bin"
|
28
|
-
s.has_rdoc = true
|
29
|
-
s.add_dependency("amazon-ec2")
|
30
|
-
s.add_dependency("net-ssh")
|
31
|
-
end
|
32
|
-
|
33
|
-
Rake::GemPackageTask.new(spec) do |p|
|
34
|
-
p.gem_spec = spec
|
35
|
-
p.need_tar = true
|
36
|
-
p.need_zip = true
|
37
|
-
end
|
38
|
-
|
39
|
-
Rake::RDocTask.new do |rdoc|
|
40
|
-
files =['README', 'LICENSE', 'lib/**/*.rb']
|
41
|
-
rdoc.rdoc_files.add(files)
|
42
|
-
rdoc.main = "README" # page to start on
|
43
|
-
rdoc.title = "CloudyScripts Docs"
|
44
|
-
rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
|
45
|
-
rdoc.options << '--line-numbers'
|
46
|
-
end
|
47
|
-
|
48
|
-
Rake::TestTask.new do |t|
|
49
|
-
t.test_files = FileList['test/**/*.rb']
|
50
|
-
end
|
1
|
+
#
|
2
|
+
# To change this template, choose Tools | Templates
|
3
|
+
# and open the template in the editor.
|
4
|
+
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'rake'
|
8
|
+
require 'rake/clean'
|
9
|
+
require 'rake/gempackagetask'
|
10
|
+
require 'rake/rdoctask'
|
11
|
+
require 'rake/testtask'
|
12
|
+
|
13
|
+
spec = Gem::Specification.new do |s|
|
14
|
+
s.name = 'CloudyScripts'
|
15
|
+
s.version = '0.0.12'
|
16
|
+
s.has_rdoc = true
|
17
|
+
s.extra_rdoc_files = ['README.rdoc', 'LICENSE']
|
18
|
+
s.summary = 'Scripts to facilitate programming for infrastructure clouds.'
|
19
|
+
s.description = s.summary
|
20
|
+
s.homepage = "http://elastic-security.com"
|
21
|
+
s.rubyforge_project = "cloudyscripts"
|
22
|
+
s.author = 'Matthias Jung'
|
23
|
+
s.email = 'matthias.jung@gmail.com'
|
24
|
+
# s.executables = ['your_executable_here']
|
25
|
+
s.files = %w(LICENSE README.rdoc Rakefile) + Dir.glob("{bin,lib,spec}/**/*")
|
26
|
+
s.require_path = "lib"
|
27
|
+
s.bindir = "bin"
|
28
|
+
s.has_rdoc = true
|
29
|
+
s.add_dependency("amazon-ec2")
|
30
|
+
s.add_dependency("net-ssh")
|
31
|
+
end
|
32
|
+
|
33
|
+
Rake::GemPackageTask.new(spec) do |p|
|
34
|
+
p.gem_spec = spec
|
35
|
+
p.need_tar = true
|
36
|
+
p.need_zip = true
|
37
|
+
end
|
38
|
+
|
39
|
+
Rake::RDocTask.new do |rdoc|
|
40
|
+
files =['README', 'LICENSE', 'lib/**/*.rb']
|
41
|
+
rdoc.rdoc_files.add(files)
|
42
|
+
rdoc.main = "README" # page to start on
|
43
|
+
rdoc.title = "CloudyScripts Docs"
|
44
|
+
rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
|
45
|
+
rdoc.options << '--line-numbers'
|
46
|
+
end
|
47
|
+
|
48
|
+
Rake::TestTask.new do |t|
|
49
|
+
t.test_files = FileList['test/**/*.rb']
|
50
|
+
end
|
data/lib/cloudyscripts.rb
CHANGED
@@ -1,26 +1,26 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'net/ssh'
|
3
|
-
require 'AWS'
|
4
|
-
|
5
|
-
# =About
|
6
|
-
# CloudyScripts is a library that implements tasks that support common
|
7
|
-
# usecases on Cloud Computing Infrastructures (such as Amazon EC2 or Rackspace).
|
8
|
-
# It aims to facilitate the implementation of usecases that are not directly
|
9
|
-
# available via the providers' API (e.g. like encrypting storage,
|
10
|
-
# migrating instances betweem accounts, activating HTTPS). The scripts typically
|
11
|
-
# use the provider APIs plus remote access to command-line tools installed on
|
12
|
-
# the instances themselves.
|
13
|
-
#
|
14
|
-
# =Installation and Usage
|
15
|
-
# ===Installation
|
16
|
-
# <tt>gem install CloudyScripts</tt>
|
17
|
-
#
|
18
|
-
# ===Usage
|
19
|
-
# All scripts are available under /lib/scripts/<em>provider</em>>
|
20
|
-
#
|
21
|
-
# ===Scripts
|
22
|
-
# Here are the scripts implemented so far:
|
23
|
-
# * #Scripts::EC2::DmEncrypt (encrypt Amazon EBS Storage using dm-encrypt)
|
24
|
-
#
|
25
|
-
class CloudyScripts
|
26
|
-
end
|
1
|
+
require 'rubygems'
|
2
|
+
require 'net/ssh'
|
3
|
+
require 'AWS'
|
4
|
+
|
5
|
+
# =About
|
6
|
+
# CloudyScripts is a library that implements tasks that support common
|
7
|
+
# usecases on Cloud Computing Infrastructures (such as Amazon EC2 or Rackspace).
|
8
|
+
# It aims to facilitate the implementation of usecases that are not directly
|
9
|
+
# available via the providers' API (e.g. like encrypting storage,
|
10
|
+
# migrating instances betweem accounts, activating HTTPS). The scripts typically
|
11
|
+
# use the provider APIs plus remote access to command-line tools installed on
|
12
|
+
# the instances themselves.
|
13
|
+
#
|
14
|
+
# =Installation and Usage
|
15
|
+
# ===Installation
|
16
|
+
# <tt>gem install CloudyScripts</tt>
|
17
|
+
#
|
18
|
+
# ===Usage
|
19
|
+
# All scripts are available under /lib/scripts/<em>provider</em>>
|
20
|
+
#
|
21
|
+
# ===Scripts
|
22
|
+
# Here are the scripts implemented so far:
|
23
|
+
# * #Scripts::EC2::DmEncrypt (encrypt Amazon EBS Storage using dm-encrypt)
|
24
|
+
#
|
25
|
+
class CloudyScripts
|
26
|
+
end
|
data/lib/help/dm_crypt_helper.rb
CHANGED
@@ -1,54 +1,53 @@
|
|
1
|
-
require 'help/remote_command_handler'
|
2
|
-
|
3
|
-
# This class implements helper methods for Dm Encryption
|
4
|
-
# (see #Scripts::EC2::DmEncrypt)
|
5
|
-
|
6
|
-
class DmCryptHelper < RemoteCommandHandler
|
7
|
-
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# *
|
11
|
-
# *
|
12
|
-
# *
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
#device
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
self.
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
self.
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
#device does not
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
1
|
+
require 'help/remote_command_handler'
|
2
|
+
|
3
|
+
# This class implements helper methods for Dm Encryption
|
4
|
+
# (see #Scripts::EC2::DmEncrypt)
|
5
|
+
|
6
|
+
class DmCryptHelper < RemoteCommandHandler
|
7
|
+
# Encrypts the device and mounting it using dm-crypt tools.
|
8
|
+
# Params
|
9
|
+
# * name: name of the virtual volume
|
10
|
+
# * password: paraphrase to be used for encryption
|
11
|
+
# * device: device to be encrypted
|
12
|
+
# * path: path to which the encrypted device is mounted
|
13
|
+
def encrypt_storage(name, password, device, path)
|
14
|
+
if file_exists?(device)
|
15
|
+
if !file_exists?("/dev/mapper/#{name}")
|
16
|
+
@logger.debug("mapper device #{name} not yet existing")
|
17
|
+
#device not configured, go ahead
|
18
|
+
remote_execute("cryptsetup luksFormat -q #{device}", password)
|
19
|
+
@logger.debug("device #{device} formatted as #{name}")
|
20
|
+
remote_execute("cryptsetup luksOpen #{device} #{name}",password)
|
21
|
+
@logger.debug("device #{device} / #{name} opened")
|
22
|
+
self.create_filesystem("ext3", "/dev/mapper/#{name}")
|
23
|
+
@logger.debug("filesystem created on /dev/mapper/#{name}")
|
24
|
+
self.mkdir(path)
|
25
|
+
self.mount("/dev/mapper/#{name}", path)
|
26
|
+
#TODO: make a final check that everything worked? ?
|
27
|
+
else
|
28
|
+
#device already exists, just re-activate it
|
29
|
+
@logger.debug("mapper device #{name} is existing")
|
30
|
+
remote_execute("cryptsetup luksOpen #{device} #{name}")
|
31
|
+
@logger.debug("device #{device} /dev/mapper/#{name} opened")
|
32
|
+
self.mkdir(path) unless file_exists?(path)
|
33
|
+
self.mount("/dev/mapper/#{name}", path) unless drive_mounted_as?("/dev/mapper/#{name}", path)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
#device does not even exist
|
37
|
+
raise Exception.new("device #{device} does not exist")
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
# Check if the storage is encrypted (not yet implemented).
|
43
|
+
def test_storage_encryption(password, mount_point, path)
|
44
|
+
end
|
45
|
+
|
46
|
+
def undo_encryption(name, path)
|
47
|
+
remote_execute("umount #{path}", nil, true)
|
48
|
+
@logger.debug("drive #{path} unmounted")
|
49
|
+
remote_execute("cryptsetup luksClose /dev/mapper/#{name}", nil, true)
|
50
|
+
@logger.debug("closed /dev/mapper/#{name} unmounted")
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/lib/help/ec2_helper.rb
CHANGED
@@ -1,54 +1,54 @@
|
|
1
|
-
require "AWS"
|
2
|
-
|
3
|
-
# Implements some helper methods around the EC2 API and methods that are
|
4
|
-
# not yet implemented in the amazon-ec2 gem
|
5
|
-
|
6
|
-
class AWS::EC2::Base
|
7
|
-
def describe_instance_attribute(options)
|
8
|
-
params = {}
|
9
|
-
params["InstanceId"] = options[:instance_id].to_s
|
10
|
-
params["Attribute"] = "rootDeviceName" unless options[:attributes][:rootDeviceName] == nil
|
11
|
-
return response_generator(:action => "DescribeInstanceAttribute", :params => params)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
class Ec2Helper
|
16
|
-
# expects an instance of AWS::EC2::Base from the amazon-ec2 gem
|
17
|
-
def initialize(ec2_api)
|
18
|
-
@ec2_api = ec2_api
|
19
|
-
end
|
20
|
-
|
21
|
-
# Checks if the specified volume is acting as a root-device for the instance
|
22
|
-
# to which it is attached. It therefore first calls ec2_describe_volumes() to
|
23
|
-
# retrieve the instance linked to the volume specified, then calls
|
24
|
-
# ec2_describe_instance_attribute() to retrieve the rootDeviceName of that
|
25
|
-
# instance, and finally calls describe_instances() to retrieve all volumes
|
26
|
-
# to check against volume_id and rootDeviceName.
|
27
|
-
def is_root_device?(volume_id)
|
28
|
-
vols = @ec2_api.describe_volumes(:volume_id => volume_id)
|
29
|
-
if vols['volumeSet']['item'][0]['attachmentSet'] == nil || vols['volumeSet']['item'][0]['attachmentSet']['item'].size == 0
|
30
|
-
#not linked to any instance, cannot be a root-device
|
31
|
-
return false
|
32
|
-
end
|
33
|
-
instance_id = vols['volumeSet']['item'][0]['attachmentSet']['item'][0]['instanceId']
|
34
|
-
res = @ec2_api.describe_instance_attribute(:instance_id => instance_id, :attributes => {:rootDeviceName => true})
|
35
|
-
if res["rootDeviceName"] == nil
|
36
|
-
return false
|
37
|
-
end
|
38
|
-
rdn = res['rootDeviceName']['value']
|
39
|
-
res = @ec2_api.describe_instances(:instance_id => instance_id)
|
40
|
-
if res['reservationSet']['item'][0]['instancesSet']['item'][0]['blockDeviceMapping']['item'].size == 0
|
41
|
-
# volume unattached in the meantime
|
42
|
-
return false
|
43
|
-
end
|
44
|
-
attached = res['reservationSet']['item'][0]['instancesSet']['item'][0]['blockDeviceMapping']['item']
|
45
|
-
attached.each() {|ebs|
|
46
|
-
volume = ebs['ebs']['volumeId']
|
47
|
-
device_name = ebs['deviceName']
|
48
|
-
if volume == volume_id && rdn == device_name
|
49
|
-
return true
|
50
|
-
end
|
51
|
-
}
|
52
|
-
return false
|
53
|
-
end
|
54
|
-
end
|
1
|
+
require "AWS"
|
2
|
+
|
3
|
+
# Implements some helper methods around the EC2 API and methods that are
|
4
|
+
# not yet implemented in the amazon-ec2 gem
|
5
|
+
|
6
|
+
class AWS::EC2::Base
|
7
|
+
def describe_instance_attribute(options)
|
8
|
+
params = {}
|
9
|
+
params["InstanceId"] = options[:instance_id].to_s
|
10
|
+
params["Attribute"] = "rootDeviceName" unless options[:attributes][:rootDeviceName] == nil
|
11
|
+
return response_generator(:action => "DescribeInstanceAttribute", :params => params)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Ec2Helper
|
16
|
+
# expects an instance of AWS::EC2::Base from the amazon-ec2 gem
|
17
|
+
def initialize(ec2_api)
|
18
|
+
@ec2_api = ec2_api
|
19
|
+
end
|
20
|
+
|
21
|
+
# Checks if the specified volume is acting as a root-device for the instance
|
22
|
+
# to which it is attached. It therefore first calls ec2_describe_volumes() to
|
23
|
+
# retrieve the instance linked to the volume specified, then calls
|
24
|
+
# ec2_describe_instance_attribute() to retrieve the rootDeviceName of that
|
25
|
+
# instance, and finally calls describe_instances() to retrieve all volumes
|
26
|
+
# to check against volume_id and rootDeviceName.
|
27
|
+
def is_root_device?(volume_id)
|
28
|
+
vols = @ec2_api.describe_volumes(:volume_id => volume_id)
|
29
|
+
if vols['volumeSet']['item'][0]['attachmentSet'] == nil || vols['volumeSet']['item'][0]['attachmentSet']['item'].size == 0
|
30
|
+
#not linked to any instance, cannot be a root-device
|
31
|
+
return false
|
32
|
+
end
|
33
|
+
instance_id = vols['volumeSet']['item'][0]['attachmentSet']['item'][0]['instanceId']
|
34
|
+
res = @ec2_api.describe_instance_attribute(:instance_id => instance_id, :attributes => {:rootDeviceName => true})
|
35
|
+
if res["rootDeviceName"] == nil
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
rdn = res['rootDeviceName']['value']
|
39
|
+
res = @ec2_api.describe_instances(:instance_id => instance_id)
|
40
|
+
if res['reservationSet']['item'][0]['instancesSet']['item'][0]['blockDeviceMapping']['item'].size == 0
|
41
|
+
# volume unattached in the meantime
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
attached = res['reservationSet']['item'][0]['instancesSet']['item'][0]['blockDeviceMapping']['item']
|
45
|
+
attached.each() {|ebs|
|
46
|
+
volume = ebs['ebs']['volumeId']
|
47
|
+
device_name = ebs['deviceName']
|
48
|
+
if volume == volume_id && rdn == device_name
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
}
|
52
|
+
return false
|
53
|
+
end
|
54
|
+
end
|
@@ -1,203 +1,213 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'net/ssh'
|
3
|
-
|
4
|
-
# Provides methods to be executed via ssh to remote instances.
|
5
|
-
class RemoteCommandHandler
|
6
|
-
attr_accessor :logger, :ssh_session
|
7
|
-
def initialize
|
8
|
-
@logger = Logger.new(STDOUT)
|
9
|
-
end
|
10
|
-
|
11
|
-
# Connect to the machine as root using a keyfile.
|
12
|
-
# Params:
|
13
|
-
# * ip: ip address of the machine to connect to
|
14
|
-
# * keyfile: path of the keyfile to be used for authentication
|
15
|
-
def connect_with_keyfile(ip, keyfile)
|
16
|
-
@ssh_session = Net::SSH.start(ip, 'root', :keys => [keyfile])
|
17
|
-
end
|
18
|
-
|
19
|
-
# Connect to the machine as root using keydata from a keyfile.
|
20
|
-
# Params:
|
21
|
-
# * ip: ip address of the machine to connect to
|
22
|
-
# * user: user name
|
23
|
-
# * key_data: key_data to be used for authentication
|
24
|
-
def connect(ip, user, key_data)
|
25
|
-
@ssh_session = Net::SSH.start(ip, user, :key_data => [key_data])
|
26
|
-
end
|
27
|
-
|
28
|
-
# Disconnect the current handler
|
29
|
-
def disconnect
|
30
|
-
@ssh_session.close
|
31
|
-
end
|
32
|
-
|
33
|
-
# Check if the path/file specified exists
|
34
|
-
def file_exists?(path)
|
35
|
-
remote_execute("ls #{path}")
|
36
|
-
end
|
37
|
-
|
38
|
-
# Returns the result of uname -a (Linux)
|
39
|
-
def retrieve_os()
|
40
|
-
get_output("uname -r").strip
|
41
|
-
end
|
42
|
-
|
43
|
-
# Installs the software package specified.
|
44
|
-
def install(software_package)
|
45
|
-
e = "yum -yq install #{software_package}"
|
46
|
-
yum = remote_execute(e)
|
47
|
-
if !yum
|
48
|
-
@logger.info("yum installation failed; try apt-get")
|
49
|
-
e = "apt-get -yq install #{software_package}"
|
50
|
-
apt = remote_execute(e)
|
51
|
-
@logger.info("apt=get installation? #{apt}")
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# Checks if the software package specified is installed.
|
56
|
-
def tools_installed?(software_package)
|
57
|
-
exec_string = "which #{software_package}"
|
58
|
-
stdout = []
|
59
|
-
stderr = []
|
60
|
-
result = remote_exec_helper(exec_string, stdout, stderr)
|
61
|
-
return result == true && stdout.size > 0
|
62
|
-
end
|
63
|
-
|
64
|
-
def create_filesystem(fs_type, volume)
|
65
|
-
e = "mkfs -t #{fs_type} #{volume}"
|
66
|
-
remote_execute(e, "y") #TODO: quiet mode?
|
67
|
-
end
|
68
|
-
|
69
|
-
def mkdir(path)
|
70
|
-
e = "mkdir #{path}"
|
71
|
-
remote_execute(e, nil, true)
|
72
|
-
end
|
73
|
-
|
74
|
-
def mount(device, path)
|
75
|
-
e = "mount #{device} #{path}"
|
76
|
-
remote_execute(e, nil, true)
|
77
|
-
end
|
78
|
-
|
79
|
-
# Checks if the drive on path is mounted
|
80
|
-
def drive_mounted?(path)
|
81
|
-
#check if drive mounted
|
82
|
-
drive_found = stdout_contains?("mount", "on #{path} type")
|
83
|
-
if drive_found
|
84
|
-
return file_exists?(path)
|
85
|
-
else
|
86
|
-
@logger.debug "not mounted (since #{path} non-existing)"
|
87
|
-
false
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
# Checks if the drive on path is mounted with the specific device
|
92
|
-
def drive_mounted_as?(device, path)
|
93
|
-
#check if drive mounted
|
94
|
-
stdout_contains?("mount", "#{device} on #{path} type")
|
95
|
-
end
|
96
|
-
|
97
|
-
# Unmount the specified path.
|
98
|
-
def umount(path)
|
99
|
-
exec_string = "umount #{path}"
|
100
|
-
remote_execute(exec_string)
|
101
|
-
!drive_mounted?(path)
|
102
|
-
end
|
103
|
-
|
104
|
-
# Copy directory using options -avHx
|
105
|
-
def rsync(source_path, dest_path, exclude_path = nil)
|
106
|
-
exclude = ""
|
107
|
-
if exclude_path != nil
|
108
|
-
exclude = "--exclude #{exclude_path}"
|
109
|
-
end
|
110
|
-
e = "rsync -avHx #{exclude} #{source_path} #{dest_path}"
|
111
|
-
@logger.debug "going to execute #{e}"
|
112
|
-
remote_exec_helper(e, nil, nil, false)
|
113
|
-
end
|
114
|
-
|
115
|
-
#
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
#
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
stdout
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
ch.
|
183
|
-
|
184
|
-
end
|
185
|
-
ch.
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
1
|
+
require 'rubygems'
|
2
|
+
require 'net/ssh'
|
3
|
+
|
4
|
+
# Provides methods to be executed via ssh to remote instances.
|
5
|
+
class RemoteCommandHandler
|
6
|
+
attr_accessor :logger, :ssh_session
|
7
|
+
def initialize
|
8
|
+
@logger = Logger.new(STDOUT)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Connect to the machine as root using a keyfile.
|
12
|
+
# Params:
|
13
|
+
# * ip: ip address of the machine to connect to
|
14
|
+
# * keyfile: path of the keyfile to be used for authentication
|
15
|
+
def connect_with_keyfile(ip, keyfile)
|
16
|
+
@ssh_session = Net::SSH.start(ip, 'root', :keys => [keyfile])
|
17
|
+
end
|
18
|
+
|
19
|
+
# Connect to the machine as root using keydata from a keyfile.
|
20
|
+
# Params:
|
21
|
+
# * ip: ip address of the machine to connect to
|
22
|
+
# * user: user name
|
23
|
+
# * key_data: key_data to be used for authentication
|
24
|
+
def connect(ip, user, key_data)
|
25
|
+
@ssh_session = Net::SSH.start(ip, user, :key_data => [key_data])
|
26
|
+
end
|
27
|
+
|
28
|
+
# Disconnect the current handler
|
29
|
+
def disconnect
|
30
|
+
@ssh_session.close
|
31
|
+
end
|
32
|
+
|
33
|
+
# Check if the path/file specified exists
|
34
|
+
def file_exists?(path)
|
35
|
+
remote_execute("ls #{path}")
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the result of uname -a (Linux)
|
39
|
+
def retrieve_os()
|
40
|
+
get_output("uname -r").strip
|
41
|
+
end
|
42
|
+
|
43
|
+
# Installs the software package specified.
|
44
|
+
def install(software_package)
|
45
|
+
e = "yum -yq install #{software_package}"
|
46
|
+
yum = remote_execute(e)
|
47
|
+
if !yum
|
48
|
+
@logger.info("yum installation failed; try apt-get")
|
49
|
+
e = "apt-get -yq install #{software_package}"
|
50
|
+
apt = remote_execute(e)
|
51
|
+
@logger.info("apt=get installation? #{apt}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Checks if the software package specified is installed.
|
56
|
+
def tools_installed?(software_package)
|
57
|
+
exec_string = "which #{software_package}"
|
58
|
+
stdout = []
|
59
|
+
stderr = []
|
60
|
+
result = remote_exec_helper(exec_string, stdout, stderr)
|
61
|
+
return result == true && stdout.size > 0
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_filesystem(fs_type, volume)
|
65
|
+
e = "mkfs -t #{fs_type} #{volume}"
|
66
|
+
remote_execute(e, "y") #TODO: quiet mode?
|
67
|
+
end
|
68
|
+
|
69
|
+
def mkdir(path)
|
70
|
+
e = "mkdir #{path}"
|
71
|
+
remote_execute(e, nil, true)
|
72
|
+
end
|
73
|
+
|
74
|
+
def mount(device, path)
|
75
|
+
e = "mount #{device} #{path}"
|
76
|
+
remote_execute(e, nil, true)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Checks if the drive on path is mounted
|
80
|
+
def drive_mounted?(path)
|
81
|
+
#check if drive mounted
|
82
|
+
drive_found = stdout_contains?("mount", "on #{path} type")
|
83
|
+
if drive_found
|
84
|
+
return file_exists?(path)
|
85
|
+
else
|
86
|
+
@logger.debug "not mounted (since #{path} non-existing)"
|
87
|
+
false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Checks if the drive on path is mounted with the specific device
|
92
|
+
def drive_mounted_as?(device, path)
|
93
|
+
#check if drive mounted
|
94
|
+
stdout_contains?("mount", "#{device} on #{path} type")
|
95
|
+
end
|
96
|
+
|
97
|
+
# Unmount the specified path.
|
98
|
+
def umount(path)
|
99
|
+
exec_string = "umount #{path}"
|
100
|
+
remote_execute(exec_string)
|
101
|
+
!drive_mounted?(path)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Copy directory using options -avHx
|
105
|
+
def rsync(source_path, dest_path, exclude_path = nil)
|
106
|
+
exclude = ""
|
107
|
+
if exclude_path != nil
|
108
|
+
exclude = "--exclude #{exclude_path}"
|
109
|
+
end
|
110
|
+
e = "rsync -avHx #{exclude} #{source_path} #{dest_path}"
|
111
|
+
@logger.debug "going to execute #{e}"
|
112
|
+
remote_exec_helper(e, nil, nil, false)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Zip the complete contents of the source path into the destination file.
|
116
|
+
def zip(source_path, destination_file)
|
117
|
+
begin
|
118
|
+
exec = "cd #{source_path}; zip #{destination_file} *"
|
119
|
+
remote_execute(exec, nil, true)
|
120
|
+
rescue Exception => e
|
121
|
+
raise Exception.new("zip failed due to #{e.message}")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Executes the specified #exec_string on a remote session specified.
|
126
|
+
# When #push_data is specified, the data will be used as input for the
|
127
|
+
# command and thus allow to respond in advance to commands that ask the user
|
128
|
+
# something.
|
129
|
+
# The method will return true if nothing was written into stderr, otherwise false.
|
130
|
+
# When #raise_exception is set, an exception will be raised instead of
|
131
|
+
# returning false.
|
132
|
+
def remote_execute(exec_string, push_data = nil, raise_exception = false)
|
133
|
+
exec_string = "echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt" unless push_data == nil
|
134
|
+
stdout = []
|
135
|
+
stderr = []
|
136
|
+
result = remote_exec_helper(exec_string, stdout, stderr)
|
137
|
+
em = "RemoteCommandHandler: #{exec_string} lead to stderr message: #{stderr.join().strip}"
|
138
|
+
@logger.info(em) unless stderr.size == 0
|
139
|
+
raise Exception.new(em) unless result == true || raise_exception == false
|
140
|
+
result
|
141
|
+
end
|
142
|
+
|
143
|
+
# Executes the specified #exec_string on a remote session specified as #ssh_session
|
144
|
+
# and logs the command-output into the specified #logger. When #push_data is
|
145
|
+
# specified, the data will be used as input for the command and thus allows
|
146
|
+
# to respond in advance to commands that ask the user something. If the output
|
147
|
+
# in stdout contains the specified #search_string, the method returns true
|
148
|
+
# otherwise false. Output to stderr will be logged.
|
149
|
+
def stdout_contains?(exec_string, search_string = "", push_data = nil)
|
150
|
+
exec_string = "echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt" unless push_data == nil
|
151
|
+
stdout = []
|
152
|
+
stderr = []
|
153
|
+
remote_exec_helper(exec_string, stdout, stderr)
|
154
|
+
@logger.info("RemoteCommandHandler: #{exec_string} lead to stderr message: #{stderr.join().strip}") unless stderr.size == 0
|
155
|
+
stdout.join().include?(search_string)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Executes the specified #exec_string on a remote session specified as #ssh_session.
|
159
|
+
# When #push_data is specified, the data will be used as input for the command and thus allows
|
160
|
+
# to respond in advance to commands that ask the user something. It returns
|
161
|
+
# stdout. When #stdout or #stderr is specified as arrays, the respective output is
|
162
|
+
# also written into those arrays.
|
163
|
+
def get_output(exec_string, push_data = nil, stdout = [], stderr = [])
|
164
|
+
exec_string = "echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt" unless push_data == nil
|
165
|
+
stdout = []
|
166
|
+
stderr = []
|
167
|
+
remote_exec_helper(exec_string, stdout, stderr)
|
168
|
+
stdout.join()
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
# Executes the specified #exec_string on the opened remote session.
|
174
|
+
# The method will return true if nothing was written into stderr, otherwise false.
|
175
|
+
# All stdout-data is written into #stdout, all stderr-data is written into #stderr
|
176
|
+
def remote_exec_helper(exec_string, stdout = [], stderr = [], debug = false)
|
177
|
+
result = true
|
178
|
+
the_channel = @ssh_session.open_channel do |channel|
|
179
|
+
channel.exec(exec_string) do |ch, success|
|
180
|
+
if success
|
181
|
+
@logger.debug("RemoteCommandHandler: starts executing #{exec_string}") if debug
|
182
|
+
ch.on_data() do |ch, data|
|
183
|
+
stdout << data unless data == nil || stdout == nil
|
184
|
+
end
|
185
|
+
ch.on_extended_data do |ch, type, data|
|
186
|
+
stderr << data unless data == nil || stderr == nil
|
187
|
+
result = false
|
188
|
+
end
|
189
|
+
ch.on_eof do |ch|
|
190
|
+
@logger.debug("RemoteCommandHandler.on_eof:remote end is done sending data") if debug
|
191
|
+
end
|
192
|
+
ch.on_close do |ch|
|
193
|
+
@logger.debug("RemoteCommandHandler.on_close:remote end is closing!") if debug
|
194
|
+
end
|
195
|
+
ch.on_open_failed do |ch, code, desc|
|
196
|
+
@logger.debug("RemoteCommandHandler.on_open_failed: code=#{code} desc=#{desc}") if debug
|
197
|
+
end
|
198
|
+
ch.on_process do |ch|
|
199
|
+
@logger.debug("RemoteCommandHandler.on_process; send line-feed/sleep") if debug
|
200
|
+
sleep(1)
|
201
|
+
ch.send_data("\n")
|
202
|
+
end
|
203
|
+
else
|
204
|
+
stderr << "the remote command could not be invoked!" unless stderr == nil
|
205
|
+
result = false
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
the_channel.wait
|
210
|
+
result
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|