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 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.11'
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
@@ -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
- # Encrypts the device and mounting it using dm-crypt tools.
9
- # Params
10
- # * name: name of the virtual volume
11
- # * password: paraphrase to be used for encryption
12
- # * device: device to be encrypted
13
- # * path: path to which the encrypted device is mounted
14
- def encrypt_storage(name, password, device, path)
15
- if file_exists?(device)
16
- if !file_exists?("/dev/mapper/#{name}")
17
- @logger.debug("mapper device #{name} not yet existing")
18
- #device not configured, go ahead
19
- remote_execute("cryptsetup luksFormat -q #{device}", password)
20
- @logger.debug("device #{device} formatted as #{name}")
21
- remote_execute("cryptsetup luksOpen #{device} #{name}",password)
22
- @logger.debug("device #{device} / #{name} opened")
23
- self.create_filesystem("ext3", "/dev/mapper/#{name}")
24
- @logger.debug("filesystem created on /dev/mapper/#{name}")
25
- self.mkdir(path)
26
- self.mount("/dev/mapper/#{name}", path)
27
- #TODO: make a final check that everything worked? ?
28
- else
29
- #device already exists, just re-activate it
30
- @logger.debug("mapper device #{name} is existing")
31
- remote_execute("cryptsetup luksOpen #{device} #{name}")
32
- @logger.debug("device #{device} /dev/mapper/#{name} opened")
33
- self.mkdir(path) unless file_exists?(path)
34
- self.mount("/dev/mapper/#{name}", path) unless drive_mounted_as?("/dev/mapper/#{name}", path)
35
- end
36
- else
37
- #device does not even exist
38
- raise Exception.new("device #{device} does not exist")
39
- end
40
-
41
- end
42
-
43
- # Check if the storage is encrypted (not yet implemented).
44
- def test_storage_encryption(password, mount_point, path)
45
- end
46
-
47
- def undo_encryption(name, path)
48
- remote_execute("umount #{path}", nil, true)
49
- @logger.debug("drive #{path} unmounted")
50
- remote_execute("cryptsetup luksClose /dev/mapper/#{name}", nil, true)
51
- @logger.debug("closed /dev/mapper/#{name} unmounted")
52
- end
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
@@ -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
- # Executes the specified #exec_string on a remote session specified.
116
- # When #push_data is specified, the data will be used as input for the
117
- # command and thus allow to respond in advance to commands that ask the user
118
- # something.
119
- # The method will return true if nothing was written into stderr, otherwise false.
120
- # When #raise_exception is set, an exception will be raised instead of
121
- # returning false.
122
- def remote_execute(exec_string, push_data = nil, raise_exception = false)
123
- exec_string = "echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt" unless push_data == nil
124
- stdout = []
125
- stderr = []
126
- result = remote_exec_helper(exec_string, stdout, stderr)
127
- em = "RemoteCommandHandler: #{exec_string} lead to stderr message: #{stderr.join().strip}"
128
- @logger.info(em) unless stderr.size == 0
129
- raise Exception.new(em) unless result == true || raise_exception == false
130
- result
131
- end
132
-
133
- # Executes the specified #exec_string on a remote session specified as #ssh_session
134
- # and logs the command-output into the specified #logger. When #push_data is
135
- # specified, the data will be used as input for the command and thus allows
136
- # to respond in advance to commands that ask the user something. If the output
137
- # in stdout contains the specified #search_string, the method returns true
138
- # otherwise false. Output to stderr will be logged.
139
- def stdout_contains?(exec_string, search_string = "", push_data = nil)
140
- exec_string = "echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt" unless push_data == nil
141
- stdout = []
142
- stderr = []
143
- remote_exec_helper(exec_string, stdout, stderr)
144
- @logger.info("RemoteCommandHandler: #{exec_string} lead to stderr message: #{stderr.join().strip}") unless stderr.size == 0
145
- stdout.join().include?(search_string)
146
- end
147
-
148
- # Executes the specified #exec_string on a remote session specified as #ssh_session.
149
- # When #push_data is specified, the data will be used as input for the command and thus allows
150
- # to respond in advance to commands that ask the user something. It returns
151
- # stdout. When #stdout or #stderr is specified as arrays, the respective output is
152
- # also written into those arrays.
153
- def get_output(exec_string, push_data = nil, stdout = [], stderr = [])
154
- exec_string = "echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt" unless push_data == nil
155
- stdout = []
156
- stderr = []
157
- remote_exec_helper(exec_string, stdout, stderr)
158
- stdout.join()
159
- end
160
-
161
- private
162
-
163
- # Executes the specified #exec_string on the opened remote session.
164
- # The method will return true if nothing was written into stderr, otherwise false.
165
- # All stdout-data is written into #stdout, all stderr-data is written into #stderr
166
- def remote_exec_helper(exec_string, stdout = [], stderr = [], debug = false)
167
- result = true
168
- the_channel = @ssh_session.open_channel do |channel|
169
- channel.exec(exec_string) do |ch, success|
170
- if success
171
- @logger.debug("RemoteCommandHandler: starts executing #{exec_string}") if debug
172
- ch.on_data() do |ch, data|
173
- stdout << data unless data == nil || stdout == nil
174
- end
175
- ch.on_extended_data do |ch, type, data|
176
- stderr << data unless data == nil || stderr == nil
177
- result = false
178
- end
179
- ch.on_eof do |ch|
180
- @logger.debug("RemoteCommandHandler.on_eof:remote end is done sending data") if debug
181
- end
182
- ch.on_close do |ch|
183
- @logger.debug("RemoteCommandHandler.on_close:remote end is closing!") if debug
184
- end
185
- ch.on_open_failed do |ch, code, desc|
186
- @logger.debug("RemoteCommandHandler.on_open_failed: code=#{code} desc=#{desc}") if debug
187
- end
188
- ch.on_process do |ch|
189
- @logger.debug("RemoteCommandHandler.on_process; send line-feed/sleep") if debug
190
- sleep(1)
191
- ch.send_data("\n")
192
- end
193
- else
194
- stderr << "the remote command could not be invoked!" unless stderr == nil
195
- result = false
196
- end
197
- end
198
- end
199
- the_channel.wait
200
- result
201
- end
202
-
203
- end
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