CloudyScripts 0.0.4 → 0.0.5

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.
data/README.rdoc CHANGED
@@ -21,4 +21,7 @@
21
21
  # * #DmEncrypt (encrypt Amazon EBS Storage using dm-encrypt)
22
22
  #
23
23
  # =Questions and Suggestions
24
+ # Matthias Jung
24
25
  # matthias.jung@gmail.com
26
+ # http://elastic-security.com
27
+
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.4'
15
+ s.version = '0.0.5'
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.'
@@ -4,6 +4,11 @@ require 'help/remote_command_handler'
4
4
  # (see #Scripts::EC2::DmEncrypt)
5
5
 
6
6
  class DmCryptHelper
7
+ attr_accessor :logger
8
+
9
+ def initialize
10
+ @logger = Logger.new(STDOUT)
11
+ end
7
12
 
8
13
  # Passes an remote command handler object
9
14
  # (see #Help::RemoteCommandHandler)
@@ -46,15 +51,15 @@ class DmCryptHelper
46
51
  else
47
52
  mapper_exists = false
48
53
  end
49
- puts "mapper exists = #{mapper_exists}"
54
+ @logger.info "mapper exists = #{mapper_exists}"
50
55
  exec_string = "cryptsetup create dm-#{name} #{device}"
51
56
  if !mapper_exists
52
57
  #mapper does not exist, create it
53
58
  channel = @ssh_session.open_channel do |ch|
54
59
  ch.send_data("#{password}\n")
55
- puts "execute #{exec_string}"
60
+ @logger.debug "execute #{exec_string}"
56
61
  ch.exec exec_string do |ch, success|
57
- puts "success = #{success}"
62
+ @logger.debug "success = #{success}"
58
63
  if !success
59
64
  err = "Failed during creation of encrypted partition"
60
65
  #puts "#{err}: #{data}"
@@ -78,12 +83,12 @@ class DmCryptHelper
78
83
  end
79
84
  if !pv_exists
80
85
  exec_string = "pvcreate /dev/mapper/dm-#{name}"
81
- puts "pv does not exist - execute: #{exec_string}"
86
+ @logger.info "pv does not exist - execute: #{exec_string}"
82
87
  #private volume does not exist, create it
83
88
  channel = @ssh_session.open_channel do |ch|
84
89
  ch.send_data("y\n")
85
90
  ch.exec exec_string do |ch, success|
86
- puts "success = #{success}"
91
+ @logger.debug "success = #{success}"
87
92
  if !success
88
93
  err = "Failed during creation of physical volume"
89
94
  #puts "#{err}: #{data}"
@@ -106,26 +111,26 @@ class DmCryptHelper
106
111
  end
107
112
  if !vg_exists
108
113
  exec_string = "vgcreate vg-#{name} /dev/mapper/dm-#{name}"
109
- puts "vg_exists == false; execute #{exec_string}"
114
+ @logger.info "vg_exists == false; execute #{exec_string}"
110
115
  @ssh_session.exec! exec_string do |ch, stream, data|
111
116
  if stream == :stderr && data != nil
112
117
  err = "Failed during creation of volume group"
113
- puts "#{err}: #{data}"
118
+ @logger.warn "#{err}: #{data}"
114
119
  raise Exception.new(err)
115
120
  end
116
121
  end
117
122
  #exec_string = "lvcreate -n lv-#{name} -L#{size_in_mb.to_s}M vg-#{name}"
118
123
  exec_string = "lvcreate -n lv-#{name} -l100%FREE vg-#{name}"
119
- puts "execute #{exec_string}"
124
+ @logger.info "execute #{exec_string}"
120
125
  @ssh_session.exec! exec_string do |ch, stream, data|
121
126
  if stream == :stderr && data != nil
122
127
  err = "Failed during creation of logical volume"
123
- puts "#{err}: #{data}"
128
+ @logger.debug "#{err}: #{data}"
124
129
  raise Exception.new(err)
125
130
  end
126
131
  end
127
- exec_string = "mkfs -t ext3 /dev/vg-#{name}/lv-#{name}"
128
- puts "execute #{exec_string}"
132
+ exec_string = "mkfs -t ext3 /dev/vg-#{name}/lv-#{name}" #TODO: use method in remote_command_handler
133
+ @logger.info "execute #{exec_string}"
129
134
  @ssh_session.exec! exec_string #do |ch, stream, data|
130
135
  #if stream == :stderr && data != nil
131
136
  #err = "Failed during creation of file-system"
@@ -139,11 +144,11 @@ class DmCryptHelper
139
144
  end
140
145
  else
141
146
  exec_string = "/sbin/vgchange -a y vg-#{name}"
142
- puts "vg_exists == true; execute #{exec_string}"
147
+ @logger.info "vg_exists == true; execute #{exec_string}"
143
148
  @ssh_session.exec! exec_string do |ch, stream, data| #TODO: the right size instead L2G!
144
149
  if stream == :stderr && data != nil
145
150
  err = "Failed during re-activation of volume group"
146
- puts "#{err}: #{data}"
151
+ @logger.info "#{err}: #{data}"
147
152
  raise Exception.new(err)
148
153
  end
149
154
  end
@@ -158,29 +163,29 @@ class DmCryptHelper
158
163
  # Undo encryption for the volume specified by name and path
159
164
  def undo_encryption(name, path)
160
165
  exec_string = "umount #{path}"
161
- puts "going to execute #{exec_string}"
166
+ @logger.debug "going to execute #{exec_string}"
162
167
  @ssh_session.exec! exec_string do |ch, stream, data|
163
- puts "returns #{data}"
168
+ @logger.debug "returns #{data}"
164
169
  end
165
170
  exec_string = "lvremove --verbose vg-#{name} -f" #[with confirmation?]
166
- puts "going to execute #{exec_string}"
171
+ @logger.debug "going to execute #{exec_string}"
167
172
  @ssh_session.exec! exec_string do |ch, stream, data|
168
- puts "returns #{data}"
173
+ @logger.debug "returns #{data}"
169
174
  end
170
175
  exec_string = "vgremove vg-#{name}"
171
- puts "going to execute #{exec_string}"
176
+ @logger.debug "going to execute #{exec_string}"
172
177
  @ssh_session.exec! exec_string do |ch, stream, data|
173
- puts "returns #{data}"
178
+ @logger.debug "returns #{data}"
174
179
  end
175
180
  exec_string = "pvremove /dev/mapper/dm-#{name}"
176
- puts "going to execute #{exec_string}"
181
+ @logger.debug "going to execute #{exec_string}"
177
182
  @ssh_session.exec! exec_string do |ch, stream, data|
178
- puts "returns #{data}"
183
+ @logger.debug "returns #{data}"
179
184
  end
180
185
  exec_string = "cryptsetup remove dm-#{name}"
181
- puts "going to execute #{exec_string}"
186
+ @logger.debug "going to execute #{exec_string}"
182
187
  @ssh_session.exec! exec_string do |ch, stream, data|
183
- puts "returns #{data}"
188
+ @logger.debug "returns #{data}"
184
189
  end
185
190
  end
186
191
 
@@ -4,8 +4,10 @@ require 'help/dm_crypt_helper'
4
4
 
5
5
  # Provides methods to be executed via ssh to remote instances.
6
6
  class RemoteCommandHandler
7
+ attr_accessor :logger
7
8
  def initialize
8
9
  @crypto = DmCryptHelper.new #TODO: instantiate helpers for different tools
10
+ @logger = Logger.new(STDOUT)
9
11
  end
10
12
 
11
13
  # Check if the path/file specified exists
@@ -23,7 +25,7 @@ class RemoteCommandHandler
23
25
  # Params:
24
26
  # * ip: ip address of the machine to connect to
25
27
  # * keyfile: path of the keyfile to be used for authentication
26
- def connect(ip, keyfile)
28
+ def connect_with_keyfile(ip, keyfile)
27
29
  @ssh_session = Net::SSH.start(ip, 'root', :keys => [keyfile])
28
30
  @crypto.set_ssh(@ssh_session)
29
31
  end
@@ -63,24 +65,46 @@ class RemoteCommandHandler
63
65
  drive_mounted?(path) #TODO: must at least also check the name
64
66
  end
65
67
 
68
+ 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
74
+ end
75
+
76
+ def mkdir(path)
77
+ 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
82
+ end
83
+
84
+ def mount(device, path)
85
+ 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
90
+ end
91
+
66
92
  # Checks if the drive on path is mounted
67
93
  def drive_mounted?(path)
68
94
  #check if drive mounted
69
95
  drive_found = false
70
96
  @ssh_session.exec! "mount" do |ch, stream, data|
71
97
  if stream == :stdout
72
- puts "mount command produces the following data: #{data}\n---------------"
98
+ @logger.debug "mount command produces the following data: #{data}\n---------------"
73
99
  if data.include?("on #{path} type")
74
100
  drive_found = true
75
- else
76
- puts "not mounted: #{data}"
77
101
  end
78
102
  end
79
103
  end
80
104
  if drive_found
81
105
  return RemoteCommandHandler.file_exists?(@ssh_session, path)
82
106
  else
83
- puts "not mounted (since #{path} non-existing)"
107
+ @logger.debug "not mounted (since #{path} non-existing)"
84
108
  false
85
109
  end
86
110
  end
@@ -93,8 +117,6 @@ class RemoteCommandHandler
93
117
  if stream == :stdout
94
118
  if data.include?("#{device} on #{path} type")
95
119
  drive_mounted = true
96
- else
97
- puts "not mounted: #{data}"
98
120
  end
99
121
  end
100
122
  end
@@ -104,19 +126,10 @@ class RemoteCommandHandler
104
126
  # Activates the encrypted volume, i.e. mounts it if not yet done.
105
127
  def activate_encrypted_volume(name, path)
106
128
  drive_mounted = drive_mounted?(path)
107
- puts "drive #{path} mounted? #{drive_mounted}"
129
+ @logger.debug "drive #{path} mounted? #{drive_mounted}"
108
130
  if !drive_mounted
109
- @ssh_session.exec! "mkdir #{path}"
110
- exec_string = "mount /dev/vg-#{name}/lv-#{name} #{path}"
111
- puts "drive not mounted; execute: #{exec_string}"
112
- @ssh_session.exec! "mount /dev/vg-#{name}/lv-#{name} #{path}" do |ch, stream, data|
113
- if stream == :stderr && data != nil
114
- err = "Failed during mounting encrypted device"
115
- puts "#{err}: #{data}"
116
- puts "mount /dev/vg-#{name}/lv-#{name} #{path}"
117
- raise Exception.new(err)
118
- end
119
- end
131
+ mkdir(path)
132
+ mount("/dev/vg-#{name}/lv-#{name}", "#{path}")
120
133
  end
121
134
  end
122
135
 
@@ -128,11 +141,20 @@ class RemoteCommandHandler
128
141
  # Unmount the specified path.
129
142
  def umount(path)
130
143
  exec_string = "umount #{path}"
131
- puts "going to execute #{exec_string}"
144
+ @logger.debug "going to execute #{exec_string}"
132
145
  @ssh_session.exec! exec_string do |ch, stream, data|
133
- puts "ssh_api.umount: returns #{data}"
146
+ @logger.debug "ssh_api.umount: returns #{data}"
134
147
  end
135
148
  !drive_mounted?(path)
136
149
  end
137
150
 
151
+ # Copy directory using options -avHx
152
+ def rsync(source_path, dest_path)
153
+ 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}"
157
+ end
158
+ end
159
+
138
160
  end
@@ -4,11 +4,16 @@
4
4
  # return another state.
5
5
  class ScriptExecutionState
6
6
  # context information for the state (hash)
7
- attr_reader :context
7
+ attr_reader :context, :logger
8
8
 
9
9
  def initialize(context)
10
10
  @context = context
11
11
  @state_change_listeners = []
12
+ @logger = context[:logger]
13
+ if @logger == nil
14
+ @logger = Logger.new(STDOUT)
15
+ @logger.level = Logger::WARN
16
+ end
12
17
  end
13
18
 
14
19
  # Listener should extend #StateChangeListener (or implement a
@@ -20,16 +25,17 @@ class ScriptExecutionState
20
25
  # Start the state machine using this state as initial state.
21
26
  def start_state_machine
22
27
  @current_state = self
23
- puts "start state machine with #{@current_state.inspect}"
28
+ @logger.info "start state machine with #{@current_state.inspect}"
24
29
  while !@current_state.done? && !@current_state.failed?
25
30
  begin
31
+ @logger.info "state machine: current state = #{@current_state.inspect}"
26
32
  @current_state = @current_state.enter()
27
33
  notify_state_change_listeners(@current_state)
28
34
  rescue Exception => e
29
35
  @current_state = FailedState.new(@context, e.to_s, @current_state)
30
36
  notify_state_change_listeners(@current_state)
31
- puts "Exception: #{e}"
32
- puts "#{e.backtrace.join("\n")}"
37
+ @logger.warn "Exception: #{e}"
38
+ @logger.warn "#{e.backtrace.join("\n")}"
33
39
  end
34
40
  end
35
41
  @current_state
@@ -0,0 +1,443 @@
1
+ require "help/script_execution_state"
2
+ require "scripts/ec2/ec2_script"
3
+ require "help/remote_command_handler"
4
+ #require "help/dm_crypt_helper"
5
+ require "AWS"
6
+
7
+ class AWS::EC2::Base
8
+ def register_image_updated(options)
9
+ params = {}
10
+ params["Name"] = options[:name].to_s
11
+ params["BlockDeviceMapping.1.Ebs.SnapshotId"] = options[:snapshot_id].to_s
12
+ params["BlockDeviceMapping.1.DeviceName"] = options[:root_device_name].to_s
13
+ params["Description"] = options[:description].to_s
14
+ params["KernelId"] = options[:kernel_id].to_s
15
+ params["RamdiskId"] = options[:ramdisk_id].to_s
16
+ params["Architecture"] = options[:architecture].to_s
17
+ params["RootDeviceName"] = options[:root_device_name].to_s
18
+ return response_generator(:action => "RegisterImage", :params => params)
19
+ end
20
+ end
21
+
22
+ # Creates a bootable EBS storage from an existing AMI.
23
+ #
24
+
25
+ class Ami2EbsConversion < Ec2Script
26
+ # Input parameters
27
+ # * aws_access_key => the Amazon AWS Access Key (see Your Account -> Security Credentials)
28
+ # * aws_secret_key => the Amazon AWS Secret Key
29
+ # * ami_id => the ID of the AMI to be converted
30
+ # * security_group_name => name of the security group to start
31
+ # * ssh_key_data => Key information for the security group that starts the AMI [if not set, use ssh_key_files]
32
+ # * ssh_key_files => Key information for the security group that starts the AMI
33
+ # * remote_command_handler => object that allows to connect via ssh and execute commands (optional)
34
+ # * ec2_api_handler => object that allows to access the EC2 API (optional)
35
+ # * ec2_api_server => server to connect to (option, default is us-east-1.ec2.amazonaws.com)
36
+ # * name => the name of the AMI to be created
37
+ # * description => description on AMI to be created (optional)
38
+ # * temp_device_name => [default /dev/sdj] device name used to attach the temporary storage; change this only if there's already a volume attacged as /dev/sdj (optional, default is /dev/sdj)
39
+ # * root_device_name"=> [default /dev/sda1] device name used for the root device (optional)
40
+ def initialize(input_params)
41
+ super(input_params)
42
+ @result = {:done => false}
43
+ end
44
+
45
+ # Executes the script.
46
+ def start_script()
47
+ begin
48
+ # optional parameters and initialization
49
+ if @input_params[:name] == nil
50
+ @input_params[:name] = "Boot EBS (for AMI #{@input_params[:ami_id]}) at #{Time.now.strftime('%d/%m/%Y %H.%M.%S')}"
51
+ else
52
+ end
53
+ if @input_params[:description] == nil
54
+ @input_params[:description] = @input_params[:name]
55
+ end
56
+ if @input_params[:temp_device_name] == nil
57
+ @input_params[:temp_device_name] = "/dev/sdj"
58
+ end
59
+ if @input_params[:root_device_name] == nil
60
+ @input_params[:root_device_name] = "/dev/sda1"
61
+ end
62
+ # start state machine
63
+ current_state = Ami2EbsConversionState.load_state(@input_params)
64
+ @state_change_listeners.each() {|listener|
65
+ current_state.register_state_change_listener(listener)
66
+ }
67
+ end_state = current_state.start_state_machine()
68
+ if end_state.failed?
69
+ @result[:failed] = true
70
+ @result[:failure_reason] = current_state.end_state.failure_reason
71
+ @result[:end_state] = current_state.end_state
72
+ else
73
+ @result[:failed] = false
74
+ end
75
+ rescue Exception => e
76
+ @logger.warn "exception during encryption: #{e}"
77
+ @logger.warn e.backtrace.join("\n")
78
+ err = e.to_s
79
+ err += " (in #{current_state.end_state.to_s})" unless current_state == nil
80
+ @result[:failed] = true
81
+ @result[:failure_reason] = err
82
+ @result[:end_state] = current_state.end_state unless current_state == nil
83
+ ensure
84
+ begin
85
+ @input_params[:remote_command_handler].disconnect
86
+ rescue Exception => e2
87
+ end
88
+ end
89
+
90
+ #
91
+ @result[:done] = true
92
+ end
93
+
94
+ # Returns a hash with the following information:
95
+ # :done => if execution is done
96
+ #
97
+ def get_execution_result
98
+ @result
99
+ end
100
+
101
+ private
102
+
103
+ # Here begins the state machine implementation
104
+ class Ami2EbsConversionState < ScriptExecutionState
105
+ def self.load_state(context)
106
+ state = context[:initial_state] == nil ? InitialState.new(context) : context[:initial_state]
107
+ state
108
+ end
109
+
110
+ def connect
111
+ if @context[:remote_command_handler] == nil
112
+ @context[:remote_command_handler] = RemoteCommandHandler.new
113
+ end
114
+ connected = false
115
+ remaining_trials = 3
116
+ while !connected && remaining_trials > 0
117
+ remaining_trials -= 1
118
+ if @context[:ssh_keyfile] != nil
119
+ begin
120
+ @context[:remote_command_handler].connect_with_keyfile(@context[:dns_name], @context[:ssh_keyfile])
121
+ connected = true
122
+ rescue Exception => e
123
+ @logger.info("connection failed due to #{e}")
124
+ end
125
+ elsif @context[:ssh_keydata] != nil
126
+ begin
127
+ @context[:remote_command_handler].connect(@context[:dns_name], "root", @context[:ssh_keydata])
128
+ connected = true
129
+ rescue Exception => e
130
+ @logger.info("connection failed due to #{e}")
131
+ end
132
+ else
133
+ raise Exception.new("no key information specified")
134
+ end
135
+ if !connected
136
+ sleep(5) #try again
137
+ end
138
+ end
139
+ @logger.info "connected to #{@context[:dns_name]}"
140
+ end
141
+
142
+ end
143
+
144
+ # Nothing done yet. Start by instantiating an AMI (in the right zone?)
145
+ # which serves to create
146
+ class InitialState < Ami2EbsConversionState
147
+ def enter
148
+ startup_ami()
149
+ end
150
+
151
+ private
152
+
153
+ def startup_ami()
154
+ @logger.debug "start up AMI #{@context[:ami_id]}"
155
+ res = @context[:ec2_api_handler].run_instances(:image_id => @context[:ami_id],
156
+ :security_group => @context[:security_group_name], :key_name => @context[:key_name])
157
+ puts "res = #{res.inspect}"#TODO:remove
158
+ instance_id = res['instancesSet']['item'][0]['instanceId']
159
+ @context[:instance_id] = instance_id
160
+ @logger.info "started instance #{instance_id}"
161
+ #availability_zone , key_name/group_name
162
+ started = false
163
+ while started == false
164
+ sleep(5)
165
+ res = @context[:ec2_api_handler].describe_instances(:instance_id => @context[:instance_id])
166
+ state = res['reservationSet']['item'][0]['instancesSet']['item'][0]['instanceState']
167
+ @logger.info "instance in state #{state['name']} (#{state['code']})"
168
+ if state['code'].to_i == 16
169
+ started = true
170
+ @context[:dns_name] = res['reservationSet']['item'][0]['instancesSet']['item'][0]['dnsName']
171
+ @context[:availability_zone] = res['reservationSet']['item'][0]['instancesSet']['item'][0]['placement']['availabilityZone']
172
+ @context[:kernel_id] = res['reservationSet']['item'][0]['instancesSet']['item'][0]['kernelId']
173
+ @context[:ramdisk_id] = res['reservationSet']['item'][0]['instancesSet']['item'][0]['ramdiskId']
174
+ @context[:architecture] = res['reservationSet']['item'][0]['instancesSet']['item'][0]['architecture']
175
+ elsif state['code'].to_i != 0
176
+ raise Exception.new('instance failed to start up')
177
+ end
178
+ end
179
+ AmiStarted.new(@context)
180
+ end
181
+ end
182
+
183
+ # Ami started. Create a storage
184
+ class AmiStarted < Ami2EbsConversionState
185
+ def enter
186
+ create_storage()
187
+ end
188
+
189
+ private
190
+
191
+ def create_storage()
192
+ @logger.debug "create volume in zone #{@context[:availability_zone]}"
193
+ res = @context[:ec2_api_handler].create_volume(:availability_zone => @context[:availability_zone], :size => "10")
194
+ @context[:volume_id] = res['volumeId']
195
+ started = false
196
+ while !started
197
+ sleep(5)
198
+ #TODO: check for timeout?
199
+ res = @context[:ec2_api_handler].describe_volumes(:volume_id => @context[:volume_id])
200
+ state = res['volumeSet']['item'][0]['status']
201
+ @logger.debug "volume state #{state}"
202
+ if state == 'available'
203
+ started = true
204
+ end
205
+ end
206
+ StorageCreated.new(@context)
207
+ end
208
+
209
+ end
210
+
211
+ # Storage created. Attach it.
212
+ class StorageCreated < Ami2EbsConversionState
213
+ def enter
214
+ attach_storage()
215
+ end
216
+
217
+ private
218
+
219
+ def attach_storage()
220
+ @logger.debug "attach volume #{@context[:volume_id]} to instance #{@context[:instance_id]} on device #{@context[:temp_device_name]}"
221
+ @context[:ec2_api_handler].attach_volume(:volume_id => @context[:volume_id],
222
+ :instance_id => @context[:instance_id],
223
+ :device => @context[:temp_device_name]
224
+ )
225
+ done = false
226
+ while !done
227
+ sleep(5)
228
+ #TODO: check for timeout?
229
+ res = @context[:ec2_api_handler].describe_volumes(:volume_id => @context[:volume_id])
230
+ state = res['volumeSet']['item'][0]['status']
231
+ @logger.debug "storage attaching: #{state}"
232
+ if state == 'in-use'
233
+ done = true
234
+ end
235
+ end
236
+ StorageAttached.new(@context)
237
+ end
238
+
239
+ end
240
+
241
+ # Storage attached. Create a file-system and moun it
242
+ class StorageAttached < Ami2EbsConversionState
243
+ def enter
244
+ create_fs()
245
+ end
246
+
247
+ private
248
+
249
+ def create_fs()
250
+ @logger.debug "create filesystem on #{@context[:dns_name]} to #{@context[:temp_device_name]}"
251
+ connect()
252
+ @context[:remote_command_handler].create_filesystem("ext3", @context[:temp_device_name])
253
+ FileSystemCreated.new(@context)
254
+ end
255
+ end
256
+
257
+ # File system created. Mount it.
258
+ class FileSystemCreated < Ami2EbsConversionState
259
+ def enter
260
+ mount_fs()
261
+ end
262
+
263
+ private
264
+
265
+ def mount_fs()
266
+ @context[:path] = "/mnt/tmp_#{@context[:volume_id]}"
267
+ @logger.debug "mount #{@context[:temp_device_name]} on #{@context[:path]}"
268
+ @context[:remote_command_handler].mkdir(@context[:path])
269
+ @context[:remote_command_handler].mount(@context[:temp_device_name], @context[:path])
270
+ sleep(2) #give mount some time
271
+ if !@context[:remote_command_handler].drive_mounted?(@context[:path])
272
+ raise Exception.new("drive #{@context[:path]} not mounted")
273
+ end
274
+ FileSystemMounted.new(@context)
275
+ end
276
+ end
277
+
278
+ # File system created and mounted. Copy the root partition.
279
+ class FileSystemMounted < Ami2EbsConversionState
280
+ def enter
281
+ copy()
282
+ end
283
+
284
+ private
285
+
286
+ def copy()
287
+ @logger.debug "start copying to #{@context[:path]}"
288
+ start = Time.new.to_i
289
+ @context[:remote_command_handler].rsync("/", "#{@context[:path]}")
290
+ @context[:remote_command_handler].rsync("/dev/", "#{@context[:path]}/dev/")
291
+ endtime = Time.new.to_i
292
+ @logger.info "copy took #{(endtime-start)}s"
293
+ CopyDone.new(@context)
294
+ end
295
+ end
296
+
297
+ # Copy operation done. Unmount volume.
298
+ class CopyDone < Ami2EbsConversionState
299
+ def enter
300
+ unmount()
301
+ end
302
+
303
+ private
304
+
305
+ def unmount()
306
+ @logger.debug "unmount #{@context[:path]}"
307
+ @context[:remote_command_handler].umount(@context[:path])
308
+ sleep(2) #give umount some time
309
+ if @context[:remote_command_handler].drive_mounted?(@context[:path])
310
+ raise Exception.new("drive #{@context[:path]} not unmounted")
311
+ end
312
+ VolumeUnmounted.new(@context)
313
+ end
314
+ end
315
+
316
+ # Volume unmounted. Detach it.
317
+ class VolumeUnmounted < Ami2EbsConversionState
318
+ def enter
319
+ detach()
320
+ end
321
+
322
+ private
323
+
324
+ def detach()
325
+ @logger.debug "detach volume #{@context[:volume_id]}"
326
+ @context[:ec2_api_handler].detach_volume(:volume_id => @context[:volume_id],
327
+ :instance_id => @context[:instance_id]
328
+ )
329
+ done = false
330
+ while !done
331
+ sleep(3)
332
+ #TODO: check for timeout?
333
+ res = @context[:ec2_api_handler].describe_volumes(:volume_id => @context[:volume_id])
334
+ @logger.debug "volume detaching: #{res.inspect}"
335
+ if res['volumeSet']['item'][0]['status'] == 'available'
336
+ done = true
337
+ end
338
+ end
339
+ VolumeDetached.new(@context)
340
+ end
341
+ end
342
+
343
+
344
+ # VolumeDetached. Create snaphot
345
+ class VolumeDetached < Ami2EbsConversionState
346
+ def enter
347
+ create_snapshot()
348
+ end
349
+
350
+ private
351
+
352
+ def create_snapshot()
353
+ @logger.debug "create snapshot for volume #{@context[:volume_id]}"
354
+ res = @context[:ec2_api_handler].create_snapshot(:volume_id => @context[:volume_id])
355
+ @context[:snapshot_id] = res['snapshotId']
356
+ @logger.info "snapshot_id = #{@context[:snapshot_id]}"
357
+ done = false
358
+ while !done
359
+ sleep(5)
360
+ #TODO: check for timeout?
361
+ res = @context[:ec2_api_handler].describe_snapshots(:snapshot_id => @context[:snapshot_id])
362
+ @logger.debug "snapshot creating: #{res.inspect}"
363
+ if res['snapshotSet']['item'][0]['status'] == 'completed'
364
+ done = true
365
+ end
366
+ end
367
+ SnapshotCreated.new(@context)
368
+ end
369
+ end
370
+
371
+ # Snapshot created. Delete volume.
372
+ class SnapshotCreated < Ami2EbsConversionState
373
+ def enter
374
+ delete_volume()
375
+ end
376
+
377
+ private
378
+
379
+ def delete_volume
380
+ @logger.debug "delete volume #{@context[:volume_id]}"
381
+ res = @context[:ec2_api_handler].delete_volume(:volume_id => @context[:volume_id])
382
+ VolumeDeleted.new(@context)
383
+ end
384
+ end
385
+
386
+ # Volume deleted. Register snapshot.
387
+ class VolumeDeleted < Ami2EbsConversionState
388
+ def enter
389
+ register()
390
+ end
391
+
392
+ private
393
+
394
+ def register()
395
+ @logger.debug "register snapshot #{@context[:snapshot_id]} as #{@context[:name]}"
396
+ res = @context[:ec2_api_handler].register_image_updated(:snapshot_id => @context[:snapshot_id],
397
+ :kernel_id => @context[:kernel_id], :architecture => @context[:architecture],
398
+ :root_device_name => @context[:root_device_name],
399
+ :description => @context[:description], :name => @context[:name],
400
+ :ramdisk_id => @context[:ramdisk_id]
401
+ )
402
+ @logger.debug "result of registration = #{res.inspect}"
403
+ @context[:image_id] = res['imageId']
404
+ @logger.info "resulting image_id = #{@context[:image_id]}"
405
+ SnapshotRegistered.new(@context)
406
+ end
407
+ end
408
+
409
+ # Snapshot registered. Shutdown instance.
410
+ class SnapshotRegistered < Ami2EbsConversionState
411
+ def enter
412
+ shut_down()
413
+ end
414
+
415
+ private
416
+
417
+ def shut_down()
418
+ @logger.debug "shutdown instance #{@context[:instance_id]}"
419
+ res = @context[:ec2_api_handler].terminate_instances(:instance_id => @context[:instance_id])
420
+ done = false
421
+ while done == false
422
+ sleep(5)
423
+ res = @context[:ec2_api_handler].describe_instances(:instance_id => @context[:instance_id])
424
+ state = res['reservationSet']['item'][0]['instancesSet']['item'][0]['instanceState']
425
+ @logger.debug "instance in state #{state['name']} (#{state['code']})"
426
+ if state['code'].to_i == 48
427
+ done = true
428
+ elsif state['code'].to_i != 32
429
+ raise Exception.new('instance failed to shut down')
430
+ end
431
+ end
432
+ Done.new(@context)
433
+ end
434
+ end
435
+
436
+ # Instance shutdown. Done.
437
+ class Done < Ami2EbsConversionState
438
+ def done?
439
+ true
440
+ end
441
+ end
442
+
443
+ end
@@ -54,8 +54,8 @@ class DmEncrypt < Ec2Script
54
54
  @result[:failed] = false
55
55
  end
56
56
  rescue Exception => e
57
- puts "exception during encryption: #{e}"
58
- puts e.backtrace.join("\n")
57
+ @logger.warn "exception during encryption: #{e}"
58
+ @logger.info e.backtrace.join("\n")
59
59
  err = e.to_s
60
60
  err += " (in #{current_state.end_state.to_s})" unless current_state == nil
61
61
  @result[:failed] = true
@@ -65,7 +65,6 @@ class DmEncrypt < Ec2Script
65
65
  begin
66
66
  @input_params[:remote_command_handler].disconnect
67
67
  rescue Exception => e2
68
- puts "rescue disconnect: #{e2}"
69
68
  end
70
69
  end
71
70
 
@@ -99,9 +98,9 @@ class DmEncrypt < Ec2Script
99
98
  private
100
99
 
101
100
  def connect()
102
- puts "InitialState.connect"
101
+ @logger.debug "InitialState.connect"
103
102
  if @context[:ssh_key_file] != nil
104
- @context[:remote_command_handler].connect(@context[:ip_address], @context[:ssh_key_file])
103
+ @context[:remote_command_handler].connect_with_keyfile(@context[:ip_address], @context[:ssh_key_file])
105
104
  elsif @context[:ssh_key_data] != nil
106
105
  @context[:remote_command_handler].connect(@context[:ip_address], "root", @context[:ssh_key_data])
107
106
  else
@@ -119,12 +118,12 @@ class DmEncrypt < Ec2Script
119
118
 
120
119
  private
121
120
  def install_tools
122
- puts "ConnectedState.install_tools"
121
+ @logger.debug "ConnectedState.install_tools"
123
122
  if !tools_installed?
124
123
  @context[:remote_command_handler].install("dm-crypt") #TODO: constant somewhere? admin parameter?
125
124
  end
126
125
  if tools_installed?
127
- puts "system says that tools are installed"
126
+ @logger.debug "system says that tools are installed"
128
127
  ToolInstalledState.new(@context)
129
128
  else
130
129
  FailedState.new(@context, "Installation of Tools failed", ConnectedState.new(@context))
@@ -148,7 +147,7 @@ class DmEncrypt < Ec2Script
148
147
 
149
148
  private
150
149
  def create_encrypted_volume
151
- puts "ToolInstalledState.create_encrypted_volume"
150
+ @logger.debug "ToolInstalledState.create_encrypted_volume"
152
151
  #first check if the drive is not yet mounted by someone else
153
152
  if @context[:remote_command_handler].drive_mounted?(@context[:storage_path])
154
153
  if !@context[:remote_command_handler].drive_mounted_as?(calc_device_name(), @context[:storage_path])
@@ -176,7 +175,7 @@ class DmEncrypt < Ec2Script
176
175
 
177
176
  private
178
177
  def mount_and_activate
179
- puts "VolumeCreatedState.mount_and_activate"
178
+ @logger.debug "VolumeCreatedState.mount_and_activate"
180
179
  @context[:remote_command_handler].activate_encrypted_volume(@context[:device_name],@context[:storage_path])
181
180
  MountedAndActivatedState.new(@context)
182
181
  end
@@ -190,7 +189,7 @@ class DmEncrypt < Ec2Script
190
189
 
191
190
  private
192
191
  def cleanup()
193
- puts "MountedAndActivatedState.cleanup"
192
+ @logger.debug "MountedAndActivatedState.cleanup"
194
193
  @context[:remote_command_handler].disconnect()
195
194
  DoneState.new(@context)
196
195
  end
@@ -4,16 +4,25 @@ class Ec2Script
4
4
  # * aws_access_key => the Amazon AWS Access Key (see Your Account -> Security Credentials)
5
5
  # * aws_secret_key => the Amazon AWS Secret Key
6
6
  # * ec2_api_server => the API Server to connect to (optional, default is us-east-1 (=> <ec2_api_server>.ec2.amazonaws.com)
7
+ # * logger => allows to pass a ruby logger object used for logging (optional, default is a stdout logger with level WARN)
7
8
  # Scripts may add specific key/value pairs.
8
9
  def initialize(input_params)
9
10
  @input_params = input_params
10
11
  @state_change_listeners = []
12
+ if input_params[:logger] == nil
13
+ @logger = Logger.new(STDOUT)
14
+ @logger .level = Logger::WARN
15
+ input_params[:logger] = @logger
16
+ end
11
17
  end
12
18
 
13
19
  def register_state_change_listener(listener)
14
20
  @state_change_listeners << listener
15
21
  end
16
22
 
23
+ def start_script
24
+ raise Exception.new("must be implemented")
25
+ end
17
26
 
18
27
  # Return a hash of results. Common values are:
19
28
  # * :done => is true when the script has terminated, otherwise false
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.4
4
+ version: 0.0.5
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-05 00:00:00 +01:00
12
+ date: 2010-01-07 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -50,6 +50,7 @@ files:
50
50
  - lib/help/remote_command_handler.rb
51
51
  - lib/help/script_execution_state.rb
52
52
  - lib/help/state_change_listener.rb
53
+ - lib/scripts/ec2/ami2_ebs_conversion.rb
53
54
  - lib/scripts/ec2/dm_encrypt.rb
54
55
  - lib/scripts/ec2/ec2_script.rb
55
56
  has_rdoc: true