CloudyScripts 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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