CloudyScripts 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ require 'rake/testtask'
12
12
 
13
13
  spec = Gem::Specification.new do |s|
14
14
  s.name = 'CloudyScripts'
15
- s.version = '0.0.6'
15
+ s.version = '0.0.7'
16
16
  s.has_rdoc = true
17
17
  s.extra_rdoc_files = ['README.rdoc', 'LICENSE']
18
18
  s.summary = 'Scripts to facilitate programming for infrastructure clouds.'
@@ -3,50 +3,18 @@ require 'help/remote_command_handler'
3
3
  # This class implements helper methods for Dm Encryption
4
4
  # (see #Scripts::EC2::DmEncrypt)
5
5
 
6
- class DmCryptHelper
7
- attr_accessor :logger
8
-
9
- def initialize
10
- @logger = Logger.new(STDOUT)
11
- end
12
-
13
- # Passes an remote command handler object
14
- # (see #Help::RemoteCommandHandler)
15
- def set_ssh(ssh_session)
16
- @ssh_session = ssh_session
17
- end
18
-
19
- # Installs the dm-crypt tools (if not yet done)
20
- def install()
21
- #TODO: dm-crypt seems to be installed automatically
22
- true
23
- end
24
-
25
- # Checks if the dm-crypt tool is installed (true/false)
26
- def tools_installed?()
27
- @ssh_session.exec! "which dmsetup" do |ch, stream, data|
28
- if stream == :stderr
29
- return false
30
- end
31
- end
32
- @ssh_session.exec! "which cryptsetup" do |ch, stream, data|
33
- if stream == :stderr
34
- return false
35
- end
36
- end
37
- #TODO: check also that "/dev/mapper /dev/mapper/control" exist
38
- true
39
- end
40
-
41
- # Encrypts the device and mounting it using dm-crypt tools.
6
+ class DmCryptHelper < RemoteCommandHandler
7
+
8
+ # Encrypts the device and mounting it using dm-crypt tools. Uses LVM to
9
+ # work with virtual devices.
42
10
  # Params
43
11
  # * name: name of the virtual volume
44
12
  # * password: paraphrase to be used for encryption
45
13
  # * device: device to be encrypted
46
14
  # * path: path to which the encrypted device is mounted
47
- def encrypt_storage(name, password, device, path)
15
+ def encrypt_storage_lvm(name, password, device, path)
48
16
  # first: check if a file in /dev/mapper exists
49
- if RemoteCommandHandler.file_exists?(@ssh_session, "/dev/mapper/dm-#{name}")
17
+ if file_exists?("/dev/mapper/dm-#{name}")
50
18
  mapper_exists = true
51
19
  else
52
20
  mapper_exists = false
@@ -138,7 +106,7 @@ class DmCryptHelper
138
106
  #raise Exception.new(err)
139
107
  #end
140
108
  #end
141
- if !RemoteCommandHandler.file_exists?(@ssh_session,"/dev/vg-#{name}/lv-#{name}")
109
+ if !file_exists?("/dev/vg-#{name}/lv-#{name}")
142
110
  err = "Missing file: /dev/vg-#{name}/lv-#{name}"
143
111
  raise Exception.new(err)
144
112
  end
@@ -155,13 +123,8 @@ class DmCryptHelper
155
123
  end
156
124
  end
157
125
 
158
- # Check if the storage is encrypted (not yet implemented).
159
- def test_storage_encryption(password, mount_point, path)
160
- raise Exception.new("not yet implemented")
161
- end
162
-
163
126
  # Undo encryption for the volume specified by name and path
164
- def undo_encryption(name, path)
127
+ def undo_encryption_lvm(name, path)
165
128
  exec_string = "umount #{path}"
166
129
  @logger.debug "going to execute #{exec_string}"
167
130
  @ssh_session.exec! exec_string do |ch, stream, data|
@@ -189,4 +152,53 @@ class DmCryptHelper
189
152
  end
190
153
  end
191
154
 
155
+ # Encrypts the device and mounting it using dm-crypt tools.
156
+ # Params
157
+ # * name: name of the virtual volume
158
+ # * password: paraphrase to be used for encryption
159
+ # * device: device to be encrypted
160
+ # * path: path to which the encrypted device is mounted
161
+ def encrypt_storage(name, password, device, path)
162
+ if !RemoteCommandHandler.remote_execute(@ssh_session, @logger, "cryptsetup isLuks #{device}")
163
+ raise Exception.new("device #{device} is already used differently")
164
+ end
165
+ if file_exists?(device)
166
+ if !file_exists?("/dev/mapper/#{name}")
167
+ @logger.debug("mapper device #{name} not yet existing")
168
+ #device not configured, go ahead
169
+ RemoteCommandHandler.remote_execute(@ssh_session, @logger, "cryptsetup luksFormat -q #{device}", password)
170
+ @logger.debug("device #{device} formatted as #{name}")
171
+ RemoteCommandHandler.remote_execute(@ssh_session, @logger, "cryptsetup luksOpen #{device} #{name}",password)
172
+ @logger.debug("device #{device} / #{name} opened")
173
+ self.create_filesystem("ext3", "/dev/mapper/#{name}")
174
+ @logger.debug("filesystem created on /dev/mapper/#{name}")
175
+ self.mkdir(path)
176
+ self.mount("/dev/mapper/#{name}", path)
177
+ #TODO: make a final check that everything worked? ?
178
+ else
179
+ #device already exists, just re-activate it
180
+ @logger.debug("mapper device #{name} is existing")
181
+ RemoteCommandHandler.remote_execute(@ssh_session, @logger, "cryptsetup luksOpen #{device} #{name}")
182
+ @logger.debug("device #{device} /dev/mapper/#{name} opened")
183
+ self.mkdir(path) unless file_exists?(path)
184
+ self.mount("/dev/mapper/#{name}", path) unless drive_mounted_as?("/dev/mapper/#{name}", path)
185
+ end
186
+ else
187
+ #device does not even exist
188
+ raise Exception.new("device #{device} does not exist")
189
+ end
190
+
191
+ end
192
+
193
+ # Check if the storage is encrypted (not yet implemented).
194
+ def test_storage_encryption(password, mount_point, path)
195
+ end
196
+
197
+ def undo_encryption(name, path)
198
+ RemoteCommandHandler.remote_execute(@ssh_session, @logger, "umount #{path}", nil, true)
199
+ @logger.debug("drive #{path} unmounted")
200
+ RemoteCommandHandler.remote_execute(@ssh_session, @logger, "cryptsetup luksClose /dev/mapper/#{name}", nil, true)
201
+ @logger.debug("closed /dev/mapper/#{name} unmounted")
202
+ end
203
+
192
204
  end
@@ -1,33 +1,19 @@
1
1
  require 'rubygems'
2
2
  require 'net/ssh'
3
- require 'help/dm_crypt_helper'
4
3
 
5
4
  # Provides methods to be executed via ssh to remote instances.
6
5
  class RemoteCommandHandler
7
- attr_accessor :logger
6
+ attr_accessor :logger, :ssh_session
8
7
  def initialize
9
- @crypto = DmCryptHelper.new #TODO: instantiate helpers for different tools
10
8
  @logger = Logger.new(STDOUT)
11
9
  end
12
10
 
13
- # Check if the path/file specified exists
14
- def self.file_exists?(ssh_session, path)
15
- result = true
16
- ssh_session.exec!("ls #{path}") do |ch, stream, data|
17
- if stream == :stderr
18
- result = false
19
- end
20
- end
21
- result
22
- end
23
-
24
11
  # Connect to the machine as root using a keyfile.
25
12
  # Params:
26
13
  # * ip: ip address of the machine to connect to
27
14
  # * keyfile: path of the keyfile to be used for authentication
28
15
  def connect_with_keyfile(ip, keyfile)
29
16
  @ssh_session = Net::SSH.start(ip, 'root', :keys => [keyfile])
30
- @crypto.set_ssh(@ssh_session)
31
17
  end
32
18
 
33
19
  # Connect to the machine as root using keydata from a keyfile.
@@ -37,7 +23,6 @@ class RemoteCommandHandler
37
23
  # * key_data: key_data to be used for authentication
38
24
  def connect(ip, user, key_data)
39
25
  @ssh_session = Net::SSH.start(ip, user, :key_data => [key_data])
40
- @crypto.set_ssh(@ssh_session)
41
26
  end
42
27
 
43
28
  # Disconnect the current handler
@@ -45,64 +30,49 @@ class RemoteCommandHandler
45
30
  @ssh_session.close
46
31
  end
47
32
 
33
+ # Check if the path/file specified exists
34
+ def file_exists?(path)
35
+ RemoteCommandHandler.remote_execute(@ssh_session, @logger, "ls #{path}")
36
+ end
37
+
38
+ # Returns the result of uname -a (Linux)
39
+ def retrieve_os()
40
+ RemoteCommandHandler.get_output(@ssh_session, "uname -r").strip
41
+ end
42
+
48
43
  # Installs the software package specified.
49
44
  def install(software_package)
50
- @crypto.install()
45
+ e = "yum -yq install #{software_package}; apt-get -yq install #{software_package}"
46
+ RemoteCommandHandler.remote_execute(@ssh_session, @logger, e)
51
47
  end
52
48
 
53
49
  # Checks if the software package specified is installed.
54
50
  def tools_installed?(software_package)
55
- @crypto.tools_installed?
56
- end
57
-
58
- # Encrypt the storage (using the crypto-helper used, e.g. #Help::DmCryptHelper)
59
- def encrypt_storage(name, password, device, path)
60
- @crypto.encrypt_storage(name, password, device, path)
61
- end
62
-
63
- # Check if the storage is encrypted (using the crypto-helper used, e.g. #Help::DmCryptHelper)
64
- def storage_encrypted?(password, device, path)
65
- drive_mounted?(path) #TODO: must at least also check the name
51
+ e = "which #{software_package}"
52
+ RemoteCommandHandler.remote_execute(@ssh_session, @logger, e)
66
53
  end
67
54
 
68
55
  def create_filesystem(fs_type, volume)
69
- e = "echo y >tmp.txt; mkfs -t #{fs_type} #{volume} <tmp.txt; rm -f tmp.txt"
70
- @logger.debug "exec #{e}"
71
- @ssh_session.exec! e do |ch, stream, data|
72
- @logger.debug "#{e}: output on #{stream.inspect}: #{data}"
73
- end
56
+ e = "mkfs -t #{fs_type} #{volume}"
57
+ RemoteCommandHandler.remote_execute(@ssh_session, @logger, e, "y") #TODO: quiet mode?
74
58
  end
75
59
 
76
60
  def mkdir(path)
77
61
  e = "mkdir #{path}"
78
- @logger.debug "exec #{e}"
79
- @ssh_session.exec! e do |ch, stream, data|
80
- @logger.debug "#{e}: output on #{stream.inspect}: #{data}"
81
- end
62
+ RemoteCommandHandler.remote_execute(@ssh_session, @logger, e, nil, true)
82
63
  end
83
64
 
84
65
  def mount(device, path)
85
66
  e = "mount #{device} #{path}"
86
- @logger.debug "exec #{e}"
87
- @ssh_session.exec! e do |ch, stream, data|
88
- @logger.debug "#{e}: output on #{stream.inspect}: #{data}"
89
- end
67
+ RemoteCommandHandler.remote_execute(@ssh_session, @logger, e, nil, true)
90
68
  end
91
69
 
92
70
  # Checks if the drive on path is mounted
93
71
  def drive_mounted?(path)
94
72
  #check if drive mounted
95
- drive_found = false
96
- @ssh_session.exec! "mount" do |ch, stream, data|
97
- if stream == :stdout
98
- @logger.debug "mount command produces the following data: #{data}\n---------------"
99
- if data.include?("on #{path} type")
100
- drive_found = true
101
- end
102
- end
103
- end
73
+ drive_found = RemoteCommandHandler.stdout_contains?(@ssh_session, @logger, "mount", "on #{path} type")
104
74
  if drive_found
105
- return RemoteCommandHandler.file_exists?(@ssh_session, path)
75
+ return file_exists?(path)
106
76
  else
107
77
  @logger.debug "not mounted (since #{path} non-existing)"
108
78
  false
@@ -112,49 +82,88 @@ class RemoteCommandHandler
112
82
  # Checks if the drive on path is mounted with the specific device
113
83
  def drive_mounted_as?(device, path)
114
84
  #check if drive mounted
115
- drive_mounted = false
116
- @ssh_session.exec! "mount" do |ch, stream, data|
117
- if stream == :stdout
118
- if data.include?("#{device} on #{path} type")
119
- drive_mounted = true
120
- end
121
- end
122
- end
123
- drive_mounted
124
- end
125
-
126
- # Activates the encrypted volume, i.e. mounts it if not yet done.
127
- def activate_encrypted_volume(name, path)
128
- drive_mounted = drive_mounted?(path)
129
- @logger.debug "drive #{path} mounted? #{drive_mounted}"
130
- if !drive_mounted
131
- mkdir(path)
132
- mount("/dev/vg-#{name}/lv-#{name}", "#{path}")
133
- end
134
- end
135
-
136
- # Unconfigure the storage (using the crypto-helper used, e.g. #Help::DmCryptHelper)
137
- def undo_encryption(name, path)
138
- @crypto.undo_encryption(name, path)
85
+ RemoteCommandHandler.stdout_contains?(@ssh_session, @logger, "mount", "#{device} on #{path} type")
139
86
  end
140
87
 
141
88
  # Unmount the specified path.
142
89
  def umount(path)
143
90
  exec_string = "umount #{path}"
144
- @logger.debug "going to execute #{exec_string}"
145
- @ssh_session.exec! exec_string do |ch, stream, data|
146
- @logger.debug "ssh_api.umount: returns #{data}"
147
- end
91
+ RemoteCommandHandler.remote_execute(@ssh_session, @logger, exec_string)
148
92
  !drive_mounted?(path)
149
93
  end
150
94
 
151
95
  # Copy directory using options -avHx
152
96
  def rsync(source_path, dest_path)
153
97
  e = "rsync -avHx #{source_path} #{dest_path}"
154
- @logger.debug "going to execute #{e}"
155
- @ssh_session.exec! e do |ch, stream, data|
156
- @logger.debug "#{e}: output on #{stream.inspect}: #{data}"
98
+ RemoteCommandHandler.remote_execute(@ssh_session, @logger, e, nil, true)
99
+ end
100
+
101
+ # Executes the specified #exec_string on a remote session specified as #ssh_session
102
+ # and logs the command-output into the specified #logger. When #push_data is
103
+ # specified, the data will be used as input for the command and thus allows
104
+ # to respond in advance to commands that ask the user something.
105
+ # The method will return true if nothing was written into stderr, otherwise false.
106
+ # When #raise_exception is set, an exception will be raised instead of
107
+ # returning false.
108
+ def self.remote_execute(ssh_session, logger, exec_string, push_data = nil, raise_exception = false)
109
+ logger.debug("RemoteCommandHandler: going to execute #{exec_string}")
110
+ result = true
111
+ exec_string = "echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt" unless push_data == nil
112
+ output = ""
113
+ ssh_session.exec(exec_string) do |ch, stream, data|
114
+ output += data unless data == nil
115
+ if stream == :stderr && data != nil
116
+ result = false
117
+ end
157
118
  end
119
+ ssh_session.loop
120
+ logger.info output unless logger == nil
121
+ raise Exception.new("RemoteCommandHandler: #{exec_string} lead to stderr message: #{output}") unless result == true || raise_exception == false
122
+ result
123
+ end
124
+
125
+ # Executes the specified #exec_string on a remote session specified as #ssh_session
126
+ # and logs the command-output into the specified #logger. When #push_data is
127
+ # specified, the data will be used as input for the command and thus allows
128
+ # to respond in advance to commands that ask the user something. If the output
129
+ # in stdout contains the specified #search_string, the method returns true
130
+ # otherwise false. Output to stderr will be logged.
131
+ def self.stdout_contains?(ssh_session, logger, exec_string, search_string = "", push_data = nil)
132
+ logger.debug("RemoteCommandHandler: going to execute #{exec_string}")
133
+ stdout = []
134
+ stderr = []
135
+ exec_string = "echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt" unless push_data == nil
136
+ ssh_session.exec(exec_string) do |ch, stream, data|
137
+ if stream == :stdout && data != nil
138
+ stdout << data
139
+ end
140
+ if stream == :stderr && data != nil
141
+ stderr << data
142
+ end
143
+ end
144
+ ssh_session.loop
145
+ logger.info("RemoteCommandHandler: #{exec_string} lead to stderr message: #{stderr.join("\n")}") unless stderr.size == 0
146
+ stdout.join("\n").include?(search_string)
147
+ end
148
+
149
+ # Executes the specified #exec_string on a remote session specified as #ssh_session.
150
+ # When #push_data is specified, the data will be used as input for the command and thus allows
151
+ # to respond in advance to commands that ask the user something. It returns
152
+ # stdout. When #stdout or #stderr is specified as arrays, the respective output is
153
+ # also written into those arrays.
154
+ def self.get_output(ssh_session, exec_string, push_data = nil, stdout = [], stderr = [])
155
+ exec_string = "echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt" unless push_data == nil
156
+ ssh_session.exec(exec_string) do |ch, stream, data|
157
+ if stream == :stdout && data != nil
158
+ stdout << data
159
+ end
160
+ if stream == :stderr && data != nil
161
+ stderr << data
162
+ end
163
+ end
164
+ ssh_session.loop
165
+ stdout.join("\n")
158
166
  end
159
167
 
168
+
160
169
  end
@@ -32,6 +32,7 @@ class ScriptExecutionState
32
32
  @current_state = @current_state.enter()
33
33
  notify_state_change_listeners(@current_state)
34
34
  rescue Exception => e
35
+ @context[:details] = e
35
36
  @current_state = FailedState.new(@context, e.to_s, @current_state)
36
37
  notify_state_change_listeners(@current_state)
37
38
  @logger.warn "Exception: #{e}"
@@ -7,6 +7,9 @@ require "AWS"
7
7
  # Script to Encrypt an EC2 Storage (aka Elastic Block Storage)
8
8
  #
9
9
  class DmEncrypt < Ec2Script
10
+ # dependencies: tools that need to be installed to make things work
11
+ TOOLS = ["cryptsetup","lvm"]
12
+
10
13
  # Input parameters
11
14
  # * aws_access_key => the Amazon AWS Access Key (see Your Account -> Security Credentials)
12
15
  # * aws_secret_key => the Amazon AWS Secret Key
@@ -21,9 +24,9 @@ class DmEncrypt < Ec2Script
21
24
  # * ec2_api_handler => object that allows to access the EC2 API (optional)
22
25
  # * ec2_api_server => server to connect to (option, default is us-east-1.ec2.amazonaws.com)
23
26
  #
27
+
24
28
  def initialize(input_params)
25
29
  super(input_params)
26
- @result = {:done => false}
27
30
  end
28
31
 
29
32
  # Executes the script.
@@ -106,6 +109,7 @@ class DmEncrypt < Ec2Script
106
109
  else
107
110
  raise Exception.new("no key information specified")
108
111
  end
112
+ @context[:result][:os] = @context[:remote_command_handler].retrieve_os()
109
113
  ConnectedState.new(@context)
110
114
  end
111
115
  end
@@ -120,7 +124,9 @@ class DmEncrypt < Ec2Script
120
124
  def install_tools
121
125
  @logger.debug "ConnectedState.install_tools"
122
126
  if !tools_installed?
123
- @context[:remote_command_handler].install("dm-crypt") #TODO: constant somewhere? admin parameter?
127
+ TOOLS.each() {|tool|
128
+ @context[:remote_command_handler].install(tool)
129
+ }
124
130
  end
125
131
  if tools_installed?
126
132
  @logger.debug "system says that tools are installed"
@@ -131,11 +137,12 @@ class DmEncrypt < Ec2Script
131
137
  end
132
138
 
133
139
  def tools_installed?
134
- if @context[:remote_command_handler].tools_installed?("dm-crypt")
135
- true
136
- else
137
- false
138
- end
140
+ TOOLS.each() {|tool|
141
+ if !@context[:remote_command_handler].tools_installed?(tool)
142
+ return false
143
+ end
144
+ }
145
+ true
139
146
  end
140
147
  end
141
148
 
@@ -148,16 +155,9 @@ class DmEncrypt < Ec2Script
148
155
  private
149
156
  def create_encrypted_volume
150
157
  @logger.debug "ToolInstalledState.create_encrypted_volume"
151
- #first check if the drive is not yet mounted by someone else
152
- if @context[:remote_command_handler].drive_mounted?(@context[:storage_path])
153
- if !@context[:remote_command_handler].drive_mounted_as?(calc_device_name(), @context[:storage_path])
154
- raise Exception.new("Drive is already used by another device")
155
- end
156
- end
157
- #
158
158
  @context[:remote_command_handler].encrypt_storage(@context[:device_name],
159
159
  @context[:paraphrase], @context[:device], @context[:storage_path])
160
- VolumeCreatedState.new(@context)
160
+ MountedAndActivatedState.new(@context)
161
161
  end
162
162
 
163
163
  def calc_device_name
@@ -167,21 +167,7 @@ class DmEncrypt < Ec2Script
167
167
 
168
168
  end
169
169
 
170
- # The encrypted Volume is created. Going to mount it.
171
- class VolumeCreatedState < DmEncryptState
172
- def enter
173
- mount_and_activate()
174
- end
175
-
176
- private
177
- def mount_and_activate
178
- @logger.debug "VolumeCreatedState.mount_and_activate"
179
- @context[:remote_command_handler].activate_encrypted_volume(@context[:device_name],@context[:storage_path])
180
- MountedAndActivatedState.new(@context)
181
- end
182
- end
183
-
184
- # The encrypted storages is mounted. Cleanup and done.
170
+ # The encrypted storages is mounted and activated. Cleanup and done.
185
171
  class MountedAndActivatedState < DmEncryptState
186
172
  def enter
187
173
  cleanup()
@@ -14,6 +14,8 @@ class Ec2Script
14
14
  @logger .level = Logger::WARN
15
15
  input_params[:logger] = @logger
16
16
  end
17
+ @result = {:done => false}
18
+ @input_params[:result] = @result
17
19
  end
18
20
 
19
21
  def register_state_change_listener(listener)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: CloudyScripts
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthias Jung
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-01-11 00:00:00 +01:00
12
+ date: 2010-01-15 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency