CloudyScripts 2.11.48 → 2.12.49

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 = '2.11.48'
15
+ s.version = '2.12.49' #<number cloud-stacks supported>.<number cloud-scripts>.<counting releases>
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.'
@@ -134,6 +134,22 @@ class Ec2Helper
134
134
  end
135
135
  end
136
136
 
137
+ # Get info from "imagesSet"=>"item"[0]=>"blockDeviceMapping"=>"item"[0]
138
+ def ami_blkdevmap_ebs_prop(ami_id, prop)
139
+ amis = @ec2_api.describe_images(:image_id => ami_id)
140
+ begin
141
+ if amis['imagesSet']['item'].size == 0
142
+ raise Exception.new("image #{ami_id} not found")
143
+ end
144
+ if amis['imagesSet']['item'][0]['blockDeviceMapping']['item'].size == 0
145
+ raise Exception.new("blockDeviceMapping not found for image #{ami_id}")
146
+ end
147
+ return amis['imagesSet']['item'][0]['blockDeviceMapping']['item'][0]['ebs'][prop.to_s]
148
+ rescue
149
+ raise Exception.new("image #{ami_id} not found")
150
+ end
151
+ end
152
+
137
153
  def instance_prop(instance_id, prop, instances = nil)
138
154
  if instances == nil
139
155
  instances = @ec2_api.describe_instances(:instance_id => instance_id)
@@ -246,6 +246,20 @@ class RemoteCommandHandler
246
246
  remote_exec_helper(e, nil, nil, false) #TODO: handle output in stderr?
247
247
  end
248
248
 
249
+ # dump and compress a device in a file locally
250
+ def local_dump_and_compress(source_device, target_filename)
251
+ e = "sh -c 'dd if=#{source_device} | gzip > #{target_filename}'"
252
+ @logger.debug "going to execute #{e}"
253
+ status = remote_exec_helper(e, nil, nil, true)
254
+ end
255
+
256
+ # idecompress and a file to a device locally
257
+ def local_decompress_and_dump(source_filename, target_device)
258
+ e = "sh -c 'gunzip -c #{source_filename} | dd of=#{target_device}'"
259
+ @logger.debug "going to execute #{e}"
260
+ status = remote_exec_helper(e, nil, nil, true)
261
+ end
262
+
249
263
  # Zip the complete contents of the source path into the destination file.
250
264
  # Returns the an array with stderr output messages.
251
265
  def zip(source_path, destination_file)
@@ -14,7 +14,7 @@ require "AWS"
14
14
 
15
15
  class AWS::EC2::Base
16
16
  def register_image_updated(options)
17
- puts "register_iamge_updated: #{options.inspect}"
17
+ puts "register_image_updated: #{options.inspect}"
18
18
  params = {}
19
19
  params["Name"] = options[:name].to_s
20
20
  params["BlockDeviceMapping.1.Ebs.SnapshotId"] = options[:snapshot_id].to_s
@@ -797,6 +797,48 @@ module StateTransitionHelper
797
797
  post_message("remote copy operation done")
798
798
  end
799
799
 
800
+ # dump and compress a device using dd and gzip
801
+ def local_dump_and_compress_device_to_file(source_device, target_filename)
802
+ post_message("going to start dumping and compressing source device '#{source_device}' to '#{target_filename}' file. This may take quite a time...")
803
+ @logger.debug "start dumping and compressing '#{source_device}' to '#{target_filename}'"
804
+ start_time = Time.new.to_i
805
+ if remote_handler().tools_installed?("dd") && remote_handler().tools_installed?("gzip")
806
+ @logger.debug "use dd and gzip command line"
807
+ status = remote_handler().local_dump_and_compress(source_device, target_filename)
808
+ if status == false
809
+ @logger.error "failed to dump and compress device"
810
+ raise Exception.new("failed to dump and compress device")
811
+ end
812
+ else
813
+ @logger.error "dd and/or gzip tools not installed"
814
+ raise Exception.new("dd and/or gzip tools not installed")
815
+ end
816
+ end_time = Time.new.to_i
817
+ @logger.info "dump and compress took #{(end_time-start_time)}s"
818
+ post_message("dumping and compressing done (took #{end_time-start_time})s")
819
+ end
820
+
821
+ # decompress and dump a file using gunzip and dd
822
+ def local_decompress_and_dump_file_to_device(source_filename, target_device)
823
+ post_message("going to start decompressing and dumping file '#{source_filename}' to '#{target_device}' target device. This may take quite a time...")
824
+ @logger.debug "start decompressing and dumping '#{source_filename}' to '#{target_device}'"
825
+ start_time = Time.new.to_i
826
+ if remote_handler().tools_installed?("dd") && remote_handler().tools_installed?("gunzip")
827
+ @logger.debug "use dd and gzip command line"
828
+ status = remote_handler().local_decompress_and_dump(source_filename, target_device)
829
+ if status == false
830
+ @logger.error "failed to decompress and dump file"
831
+ raise Exception.new("failed to decompress and dump file")
832
+ end
833
+ else
834
+ @logger.error "dd and/or gunzip tools not installed"
835
+ raise Exception.new("dd and/or gunzip tools not installed")
836
+ end
837
+ end_time = Time.new.to_i
838
+ @logger.info "decompress and dump filetook #{(end_time-start_time)}s"
839
+ post_message("decompressing and dumping done (took #{end_time-start_time})s")
840
+ end
841
+
800
842
  def disable_ssh_tty(host)
801
843
  post_message("going to disable SSH tty on #{host}...")
802
844
  @logger.debug "disable SSH tty on #{host}"
@@ -908,6 +950,11 @@ module StateTransitionHelper
908
950
  # aki-ea5df7eb ec2-public-images-ap-northeast-1/pv-grub-hd00_1.02-x86_64.gz.manifest.xml
909
951
  # aki-ec5df7ed ec2-public-images-ap-northeast-1/pv-grub-hd0_1.02-i386.gz.manifest.xml
910
952
  # aki-ee5df7ef ec2-public-images-ap-northeast-1/pv-grub-hd0_1.02-x86_64.gz.manifest.xml
953
+ # * SA-East-1
954
+ # aki-cc3ce3d1 ec2-public-images-sa-east-1/pv-grub-hd0_1.02-x86_64.gz.manifest.xml
955
+ # aki-bc3ce3a1 ec2-public-images-sa-east-1/pv-grub-hd0_1.02-i386.gz.manifest.xml
956
+ # aki-d23ce3cf ec2-public-images-sa-east-1/pv-grub-hd00_1.02-x86_64.gz.manifest.xml
957
+ # aki-823ce39f ec2-public-images-sa-east-1/pv-grub-hd00_1.02-i386.gz.manifest.xml
911
958
  def get_aws_kernel_image_aki(source_endpoint, source_aki, target_endpoint)
912
959
  map = { 'us-east-1' => {'aki-4c7d9525' => 'pv-grub-hd00-V1.01-i386',
913
960
  'aki-4e7d9527' => 'pv-grub-hd00-V1.01-x86_64',
@@ -980,6 +1027,18 @@ module StateTransitionHelper
980
1027
  #RHEL kernel Amazon Kernel ID
981
1028
  'aki-66c06a67' => 'aki-rhel-i386',
982
1029
  'aki-68c06a69' => 'aki-rhel-x86_64'
1030
+ },
1031
+ 'sa-east-1' => {'' => 'pv-grub-hd00-V1.01-i386',
1032
+ '' => 'pv-grub-hd00-V1.01-x86_64',
1033
+ '' => 'pv-grub-hd0-V1.01-i386',
1034
+ '' => 'pv-grub-hd0-V1.01-x86_64',
1035
+ 'aki-bc3ce3a1' => 'pv-grub-hd00_1.02-i386',
1036
+ 'aki-cc3ce3d1' => 'pv-grub-hd00_1.02-x86_64',
1037
+ 'aki-823ce39f' => 'pv-grub-hd0_1.02-i386',
1038
+ 'aki-d23ce3cf' => 'pv-grub-hd0_1.02-x86_64',
1039
+ #RHEL kernel Amazon Kernel ID
1040
+ '' => 'aki-rhel-i386',
1041
+ '' => 'aki-rhel-x86_64'
983
1042
  }
984
1043
  }
985
1044
  target_aki = ''
@@ -1024,6 +1083,8 @@ module StateTransitionHelper
1024
1083
  region = "ap-southeast-1"
1025
1084
  when /ap-northeast/
1026
1085
  region = "ap-northeast-1"
1086
+ when /sa-east-1/
1087
+ region = "sa-east-1"
1027
1088
  else
1028
1089
  region = "us-east-1"
1029
1090
  end
@@ -0,0 +1,449 @@
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 "help/ec2_helper"
6
+ require "AWS"
7
+ require 'pp'
8
+
9
+ # Copy a given snapshot to another region
10
+ # * start up instance in source-region, create a snapshot from the mounted EBS
11
+ # * then create volume from snapshot, attach volume, and mount it
12
+ # * start up instance in destination-region, create empty volume of same size, attache volume, and mount it
13
+ # * copy the destination key to the source instance
14
+ # * perform an rsynch
15
+ # sync -PHAXaz --rsh "ssh -i /home/${src_user}/.ssh/id_${dst_keypair}" --rsync-path "sudo rsync" ${src_dir}/ ${dst_user}@${dst_public_fqdn}:${dst_dir}/
16
+ # * create a snapshot of the volume
17
+ # * register the snapshot as AMI
18
+ # * clean-up everything
19
+
20
+ class CopyMsWindowsAmi < Ec2Script
21
+ # context information needed
22
+ # * the EC2 credentials (see #Ec2Script)
23
+ # * ami_id => the ID of the AMI to be copied in another region
24
+ # * target_ec2_handler => The EC2 handler connected to the region where the snapshot is being copied to
25
+ # * source_ssh_username => The username for ssh for source-instance (default = root)
26
+ # * source_key_name => Key name of the instance that manages the snaphot-volume in the source region
27
+ # * source_ssh_key_data => Key information for the security group that starts the AMI [if not set, use ssh_key_files]
28
+ # * source_ssh_key_files => Key information for the security group that starts the AMI
29
+ # * target_ssh_username => The username for ssh for target-instance (default = root)
30
+ # * target_key_name => Key name of the instance that manages the snaphot-volume in the target region
31
+ # * target_ssh_key_data => Key information for the security group that starts the AMI [if not set, use ssh_key_files]
32
+ # * target_ssh_key_files => Key information for the security group that starts the AMI
33
+ # * target_ami_id => ID of the AMI to start in the target region
34
+ # * name => name of new AMI to be created
35
+ # * description => description of new AMI to be created
36
+
37
+ def initialize(input_params)
38
+ super(input_params)
39
+ @local_ec2_helper = Ec2Helper.new(@input_params[:ec2_api_handler])
40
+ @remote_ec2_helper = Ec2Helper.new(@input_params[:target_ec2_handler])
41
+ end
42
+
43
+ def check_input_parameters()
44
+ # MS Windows AMI, source and target region
45
+ if @input_params[:ami_id] == nil && !(@input_params[:ami_id] =~ /^ami-.*$/)
46
+ raise Exception.new("Invalid AMI ID specified: #{@input_params[:ami_id]}")
47
+ end
48
+ if @local_ec2_helper.ami_prop(@input_params[:ami_id], 'rootDeviceType') != "ebs"
49
+ raise Exception.new("must be an EBS type image")
50
+ end
51
+ if @local_ec2_helper.ami_prop(@input_params[:ami_id], 'platform') != "windows"
52
+ raise Exception.new("Not a MS Windows AMI: #{@local_ec2_helper.ami_prop(@input_params[:ami_id], 'platform')}")
53
+ end
54
+ if @input_params[:helper_ami_id] == nil && !(@input_params[:helper_ami_id] =~ /^ami-.*$/)
55
+ raise Exception.new("Invalid Helper AMI ID specified: #{@input_params[:helper_ami_id]}")
56
+ end
57
+ if @local_ec2_helper.ami_prop(@input_params[:helper_ami_id], 'rootDeviceType') != "ebs"
58
+ raise Exception.new("must be an EBS type image")
59
+ end
60
+ if @local_ec2_helper.ami_prop(@input_params[:helper_ami_id], 'platform') != "windows"
61
+ raise Exception.new("Not a MS Windows AMI: #{@local_ec2_helper.ami_prop(@input_params[:helper_ami_id], 'platform')}")
62
+ end
63
+ # AWS Linux AMI, source and target region
64
+ if @input_params[:source_ami_id] == nil && !(@input_params[:source_ami_id] =~ /^ami-.*$/)
65
+ raise Exception.new("Invalid source AMI ID specified: #{@input_params[:source_ami_id]}")
66
+ end
67
+ if @input_params[:target_ami_id] == nil && !(@input_params[:target_ami_id] =~ /^ami-.*$/)
68
+ raise Exception.new("Invalid target AMI ID specified: #{@input_params[:target_ami_id]}")
69
+ end
70
+ # AWS SecurityGroup, source and target regions
71
+ if @input_params[:source_security_groups] == nil
72
+ @input_params[:source_security_groups] = "default"
73
+ end
74
+ if !@local_ec2_helper.check_open_port(@input_params[:source_security_groups], 22)
75
+ raise Exception.new("Port 22 must be opened for security group '#{@input_params[:source_security_groups]}' to connect via SSH in source-region")
76
+ end
77
+ if @input_params[:target_security_groups] == nil
78
+ @input_params[:target_security_groups] = "default"
79
+ end
80
+ if !@remote_ec2_helper.check_open_port(@input_params[:target_security_groups], 22)
81
+ raise Exception.new("Port 22 must be opened for security group '#{@input_params[:target_security_groups]}' to connect via SSH in target-region")
82
+ end
83
+ # Device to use for volume
84
+ if @input_params[:root_device_name] == nil
85
+ @input_params[:root_device_name] = "/dev/sda1"
86
+ end
87
+ if @input_params[:device_name] == nil
88
+ @input_params[:device_name] = "/dev/sdj"
89
+ end
90
+ if @input_params[:temp_device_name] == nil
91
+ @input_params[:temp_device_name] = "/dev/sdk"
92
+ end
93
+ if @input_params[:temp_device_name] == @input_params[:device_name]
94
+ raise Exception.new("Device name '#{@input_params[:device_name]}' and temporary device name '#{@input_params[:temp_device_name]}' must be different")
95
+ end
96
+ # SSH Parameters, source and target region
97
+ if @input_params[:source_ssh_username] == nil
98
+ @input_params[:source_ssh_username] = "root"
99
+ end
100
+ if @input_params[:target_ssh_username] == nil
101
+ @input_params[:target_ssh_username] = "root"
102
+ end
103
+ if @input_params[:fs_type] == nil
104
+ @input_params[:fs_type] = "ext3"
105
+ end
106
+ end
107
+
108
+ # Load the initial state for the script.
109
+ # Abstract method to be implemented by extending classes.
110
+ def load_initial_state()
111
+ CopyMsWindowsAmiState.load_state(@input_params)
112
+ end
113
+
114
+ private
115
+
116
+ # Here begins the state machine implementation
117
+ class CopyMsWindowsAmiState < ScriptExecutionState
118
+
119
+ def self.load_state(context)
120
+ InitialState.new(context)
121
+
122
+ end
123
+
124
+ def local_region
125
+ self.ec2_handler = (@context[:ec2_api_handler])
126
+ @local_ec2_helper = Ec2Helper.new(self.ec2_handler)
127
+ end
128
+
129
+ def remote_region
130
+ self.ec2_handler = (@context[:target_ec2_handler])
131
+ @remote_ec2_helper = Ec2Helper.new(self.ec2_handler)
132
+ end
133
+ end
134
+
135
+ # Initial State: Retrieve all information on this AMI we need later on
136
+ # and set some parameters such as the Availability Zone
137
+ # - Snapshot ID from this AMI
138
+ # - Volume size from this AMI
139
+ # - Availability Zone
140
+ #NB: less modification if we get the AZ from the launched instance
141
+ class InitialState < CopyMsWindowsAmiState
142
+ def enter()
143
+ local_region()
144
+ puts "Retrieving AMI parammeters"
145
+ @context[:snapshot_id] = @local_ec2_helper.ami_blkdevmap_ebs_prop(@context[:ami_id], 'snapshotId')
146
+ @context[:volume_size] = @local_ec2_helper.ami_blkdevmap_ebs_prop(@context[:ami_id], 'volumeSize')
147
+ @context[:architecture] = @local_ec2_helper.ami_prop(@context[:ami_id], 'architecture')
148
+ @context[:root_device_name] = @local_ec2_helper.ami_prop(@context[:ami_id], 'rootDeviceName')
149
+ #puts "Setting Source and Target Availability Zone if not set"
150
+ #@context[:source_availability_zone] = "us-east-1a" unless @context[:source_availability_zone] != nil
151
+ #@context[:target_availability_zone] = "us-west-1a" unless @context[:target_availability_zone] != nil
152
+ puts "DEBUG: Parameters: #{@context[:snapshot_id]}, #{@context[:volume_size]}, #{@context[:architecture]}, #{@context[:root_device_name]}"
153
+
154
+ #raise Exception.new("DEBUG: FORCED Script exit")
155
+ InitialStateDone.new(@context)
156
+ end
157
+ end
158
+
159
+ # Initial state: Launch an Amazon Linux AMI in the source Region
160
+ class InitialStateDone < CopyMsWindowsAmiState
161
+ def enter()
162
+ local_region()
163
+ result = launch_instance(@context[:source_ami_id], @context[:source_key_name], @context[:source_security_groups])
164
+ @context[:source_instance_id] = result.first
165
+ @context[:source_dns_name] = result[1]
166
+ @context[:source_availability_zone] = result[2]
167
+ #@context[:source_root_device_name] = @local_ec2_helper.ami_prop(@context[:source_ami_id], 'rootDeviceName')
168
+ @context[:source_root_device_name] = result[6]
169
+ puts "DEBUG: Parameters: #{@context[:source_instance_id]}, #{@context[:source_dns_name]}, #{@context[:source_availability_zone]}, #{@context[:source_root_device_name]}"
170
+
171
+ SourceInstanceLaunchedState.new(@context)
172
+ end
173
+ end
174
+
175
+ # Source instance is started.
176
+ # Steps:
177
+ # - create and attach a volume from the Snapshot of the AMI to copy
178
+ # - create and attach a temp volume of the same size to dump and compress the entire drive
179
+ # - create a filesystem on the temp volume and mount the temp volume
180
+ # - dump and compress the entire drive to the temp volume
181
+ class SourceInstanceLaunchedState < CopyMsWindowsAmiState
182
+ def enter()
183
+ local_region()
184
+ # Step1: create and attach a volume from the Snapshot of the AMI to copy
185
+ @context[:source_volume_id] = create_volume_from_snapshot(@context[:snapshot_id],
186
+ @context[:source_availability_zone])
187
+ source_device = @context[:device_name]
188
+ attach_volume(@context[:source_volume_id], @context[:source_instance_id], source_device)
189
+ connect(@context[:source_dns_name], @context[:source_ssh_username], nil, @context[:source_ssh_keydata])
190
+ # detect if there is a shift for device mapping (between AWS and the operating system of the system)
191
+ root_device_name = get_root_device_name()
192
+ # detect letters
193
+ aws_root_device = @context[:source_root_device_name]
194
+ aws_letter = aws_root_device.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
195
+ os_letter = root_device_name.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
196
+ aws_device_letter = source_device.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
197
+ if !aws_letter.eql?(os_letter)
198
+ post_message("Detected specific kernel with shift between AWS and Kernel OS for device naming")
199
+ end
200
+ while !aws_letter.eql?(os_letter)
201
+ aws_letter.succ!
202
+ aws_device_letter.succ!
203
+ end
204
+ source_device = "/dev/sd#{aws_device_letter}"
205
+ post_message("Using AWS name source device '#{@context[:device_name]}' and OS name '#{source_device}'")
206
+ @context[:source_device_name] = source_device
207
+ # Step2: create and attach a temp volume of the same size to dump and compress the entire drive
208
+ @context[:source_temp_volume_id] = create_volume(@context[:source_availability_zone], @context[:volume_size])
209
+ temp_device = @context[:temp_device_name]
210
+ attach_volume(@context[:source_temp_volume_id], @context[:source_instance_id], temp_device)
211
+ aws_device_letter = temp_device.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
212
+ while !aws_letter.eql?(os_letter)
213
+ aws_letter.succ!
214
+ aws_device_letter.succ!
215
+ end
216
+ temp_device="/dev/sd#{aws_device_letter}"
217
+ post_message("Using AWS name source device '#{@context[:temp_device_name]}' and OS name '#{temp_device}'")
218
+ # Step3: mount the temp volume
219
+ mount_point = "/mnt/tmp_#{@context[:source_temp_volume_id]}"
220
+ create_labeled_fs(@context[:source_dns_name], temp_device, @context[:fs_type], nil)
221
+ mount_fs(mount_point, temp_device)
222
+ disconnect()
223
+
224
+ SourceVolumeReadyState.new(@context)
225
+ end
226
+ end
227
+
228
+ # Source is ready
229
+ # Steps:
230
+ # - dump and compress the entire source drive to the temp volume
231
+ class SourceVolumeReadyState < CopyMsWindowsAmiState
232
+ def enter()
233
+ local_region()
234
+ connect(@context[:source_dns_name], @context[:source_ssh_username], nil, @context[:source_ssh_keydata])
235
+ mount_point = "/mnt/tmp_#{@context[:source_temp_volume_id]}"
236
+ @context[:source_filename] = "#{mount_point}" + "/" + "#{@context[:snapshot_id]}" + ".gz"
237
+ local_dump_and_compress_device_to_file(@context[:source_device_name], @context[:source_filename])
238
+ disconnect()
239
+
240
+ BackupedDataState.new(@context)
241
+ end
242
+ end
243
+
244
+ # Source is ready.
245
+ # Steps:
246
+ # - start an instance of AWS Linux AMI in the target region
247
+ class BackupedDataState < CopyMsWindowsAmiState
248
+ def enter()
249
+ remote_region()
250
+ result = launch_instance(@context[:target_ami_id], @context[:target_key_name], @context[:target_security_groups])
251
+ @context[:target_instance_id] = result.first
252
+ @context[:target_dns_name] = result[1]
253
+ @context[:target_availability_zone] = result[2]
254
+ #@context[:remote_root_device_name] = @remote_ec2_helper.ami_prop(@context[:target_ami_id], 'rootDeviceName')
255
+ @context[:target_root_device_name] = result[6]
256
+ puts "DEBUG: Parameters: #{@context[:target_instance_id]}, #{@context[:target_dns_name]}, #{@context[:target_availability_zone]}, #{@context[:target_root_device_name]}"
257
+
258
+ TargetInstanceLaunchedState.new(@context)
259
+ end
260
+ end
261
+
262
+ # Destination instance is started. Now configure storage.
263
+ # Steps:
264
+ # - create and attach a temp volume for receiving archive of the drive
265
+ # - create a filesystem on the temp volume and mount the temp volume
266
+ # - create and attach a volume for uncompressing and restoring the entire drive
267
+ class TargetInstanceLaunchedState < CopyMsWindowsAmiState
268
+ def enter()
269
+ remote_region()
270
+ # Step1: create and attach a temp volume for receiving archive of the drive
271
+ @context[:target_temp_volume_id] = create_volume(@context[:target_availability_zone], @context[:volume_size])
272
+ temp_device = @context[:temp_device_name]
273
+ attach_volume(@context[:target_temp_volume_id], @context[:target_instance_id], temp_device)
274
+ connect(@context[:target_dns_name], @context[:target_ssh_username], nil, @context[:target_ssh_keydata])
275
+ # detect if there is a shift for device mapping (between AWS and the operating system of the system)
276
+ root_device_name = get_root_device_name()
277
+ # detect letters
278
+ aws_root_device = @context[:target_root_device_name]
279
+ aws_letter = aws_root_device.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
280
+ os_letter = root_device_name.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
281
+ aws_device_letter = temp_device.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
282
+ if !aws_letter.eql?(os_letter)
283
+ post_message("Detected specific kernel with shift between AWS and Kernel OS for device naming")
284
+ end
285
+ while !aws_letter.eql?(os_letter)
286
+ aws_letter.succ!
287
+ aws_device_letter.succ!
288
+ end
289
+ temp_device = "/dev/sd#{aws_device_letter}"
290
+ post_message("Using AWS name source device '#{@context[:device_name]}' and OS name '#{temp_device}'")
291
+ # Step2: mount the temp volume
292
+ mount_point = "/mnt/tmp_#{@context[:target_temp_volume_id]}"
293
+ create_labeled_fs(@context[:target_dns_name], temp_device, @context[:fs_type], nil)
294
+ mount_fs(mount_point, temp_device)
295
+ # Step3: create and attach a volume for uncompressing and restoring the entire drive
296
+ @context[:target_volume_id] = create_volume(@context[:target_availability_zone], @context[:volume_size])
297
+ target_device = @context[:device_name]
298
+ attach_volume(@context[:target_volume_id], @context[:target_instance_id], target_device)
299
+ aws_device_letter = target_device.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
300
+ if !aws_letter.eql?(os_letter)
301
+ post_message("Detected specific kernel with shift between AWS and Kernel OS for device naming")
302
+ end
303
+ while !aws_letter.eql?(os_letter)
304
+ aws_letter.succ!
305
+ aws_device_letter.succ!
306
+ end
307
+ target_device = "/dev/sd#{aws_device_letter}"
308
+ @context[:target_device_name] = target_device
309
+ post_message("Using AWS name source device '#{@context[:device_name]}' and OS name '#{target_device}'")
310
+ disconnect()
311
+
312
+ TargetVolumeReadyState.new(@context)
313
+ end
314
+ end
315
+
316
+ # Storages are ready. Only thing missing: the key of the target region
317
+ # must be available on the instance in the source region to be able to perform
318
+ # a remote copy.
319
+ class TargetVolumeReadyState < CopyMsWindowsAmiState
320
+ def enter()
321
+ post_message("upload key of target-instance to source-instance...")
322
+ path_candidates = ["/#{@context[:source_ssh_username]}/.ssh/", "/home/#{@context[:source_ssh_username]}/.ssh/"]
323
+ key_path = determine_file(@context[:source_dns_name], @context[:source_ssh_username], @context[:source_ssh_keydata], path_candidates)
324
+ upload_file(@context[:source_dns_name], @context[:source_ssh_username], @context[:source_ssh_keydata],
325
+ @context[:target_ssh_keyfile], "#{key_path}#{@context[:target_key_name]}.pem")
326
+ post_message("credentials are in place to connect source and target (from source to target).")
327
+
328
+ KeyInPlaceState.new(@context)
329
+ end
330
+ end
331
+
332
+ # Now we can copy.
333
+ class KeyInPlaceState < CopyMsWindowsAmiState
334
+ def enter()
335
+ connect(@context[:target_dns_name], @context[:target_ssh_username], nil, @context[:target_ssh_keydata])
336
+ disable_ssh_tty(@context[:target_dns_name])
337
+ disconnect()
338
+ #
339
+ connect(@context[:source_dns_name], @context[:source_ssh_username], nil, @context[:source_ssh_keydata])
340
+ source_dir = "/mnt/tmp_#{@context[:source_temp_volume_id]}"
341
+ dest_dir = "/mnt/tmp_#{@context[:target_temp_volume_id]}"
342
+ remote_copy(@context[:source_ssh_username], @context[:target_key_name], source_dir,
343
+ @context[:target_dns_name], @context[:target_ssh_username], dest_dir)
344
+ disconnect()
345
+ #
346
+ connect(@context[:target_dns_name], @context[:target_ssh_username], nil, @context[:target_ssh_keydata])
347
+ enable_ssh_tty(@context[:target_dns_name])
348
+ disconnect()
349
+
350
+ DataCopiedState.new(@context)
351
+ end
352
+ end
353
+
354
+ # Decompress data on the device
355
+ class DataCopiedState < CopyMsWindowsAmiState
356
+ def enter()
357
+ remote_region()
358
+ connect(@context[:target_dns_name], @context[:target_ssh_username], nil, @context[:target_ssh_keydata])
359
+ mount_point = "/mnt/tmp_#{@context[:target_temp_volume_id]}"
360
+ @context[:source_filename] = "#{mount_point}" + "/" + "#{@context[:snapshot_id]}" + ".gz"
361
+ local_decompress_and_dump_file_to_device(@context[:source_filename], @context[:target_device_name])
362
+ disconnect()
363
+
364
+ RestoredDataState.new(@context)
365
+ end
366
+ end
367
+
368
+ # Data of snapshot now copied to the new volume.
369
+ # Steps:
370
+ # - detach the target volume
371
+ #XXX: TODO
372
+ class RestoredDataState < CopyMsWindowsAmiState
373
+ def enter()
374
+ remote_region()
375
+ detach_volume(@context[:target_volume_id], @context[:target_instance_id])
376
+
377
+ #@context[:new_snapshot_id] = create_snapshot(@context[:target_volume_id], "Created by CloudyScripts - copy_mswindows_ami")
378
+
379
+ TargetVolumeCreatedState.new(@context)
380
+ end
381
+ end
382
+
383
+ # Snapshot Operation done. Now this snapshot must be registered as AMI
384
+ # Steps:
385
+ # - launch same AMI as the one we want to copy and stop it (after it has boot up): use helper_ami_id
386
+ # - detach the volume of this instance and attach the new volume
387
+ # - start the instance to see if everything is good, then stop it
388
+ # - create an AMi from this instance
389
+ class TargetVolumeCreatedState < CopyMsWindowsAmiState
390
+ def enter()
391
+ remote_region()
392
+ #XXX: launch instance in the right AZ
393
+ result = launch_instance(@context[:helper_ami_id], @context[:target_key_name], @context[:target_security_groups])
394
+ @context[:ihelper_instance_id] = result.first
395
+ @context[:helper_dns_name] = result[1]
396
+ @context[:helper_availability_zone] = result[2]
397
+ @context[:target_root_device_name] = result[6]
398
+
399
+ #XXX:
400
+ # - wait for it to be running, and then stop it
401
+ # - wait for it to be stopped, and then sttart it with the new volume
402
+ shut_down_instance(@context[:source_instance_id])
403
+
404
+ AmiRegisteredState.new(@context)
405
+ end
406
+ end
407
+
408
+ # AMI is registered. Now only cleanup is missing, i.e. shut down instances and
409
+ # remote the volumes that were created. Start with cleaning the ressources
410
+ # in the local region.
411
+ # Steps:
412
+ # - cleanup source region
413
+ # - unmount temp volume
414
+ # - detach source and temp volume
415
+ # - terminate instance
416
+ # - delete source and temp volume
417
+ # - cleanup target region
418
+ # - unmount temp volume
419
+ # - detach temp volume
420
+ # - terminate instance
421
+ # - delete source and temp volume
422
+ class AmiRegisteredState < CopyMsWindowsAmiState
423
+ def enter()
424
+ local_region()
425
+ connect(@context[:source_dns_name], @context[:source_ssh_username], nil, @context[:source_ssh_keydata])
426
+ mount_point = "/mnt/tmp_#{@context[:source_temp_volume_id]}"
427
+ unmount_fs(mount_point)
428
+ disconnect()
429
+ detach_volume(@context[:source_temp_volume_id], @context[:source_instance_id])
430
+ detach_volume(@context[:source_volume_id], @context[:source_instance_id])
431
+ shut_down_instance(@context[:source_instance_id])
432
+ delete_volume(@context[:source_temp_volume_id])
433
+ delete_volume(@context[:source_volume_id])
434
+ #
435
+ remote_region()
436
+ connect(@context[:target_dns_name], @context[:target_ssh_username], nil, @context[:target_ssh_keydata])
437
+ mount_point = "/mnt/tmp_#{@context[:target_temp_volume_id]}"
438
+ unmount_fs(mount_point)
439
+ disconnect()
440
+ detach_volume(@context[:target_temp_volume_id], @context[:target_instance_id])
441
+ shut_down_instance(@context[:target_instance_id])
442
+ delete_volume(@context[:target_temp_volume_id])
443
+ delete_volume(@context[:target_volume_id])
444
+
445
+ Done.new(@context)
446
+ end
447
+ end
448
+
449
+ end
@@ -0,0 +1,401 @@
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 "help/ec2_helper"
6
+ require "AWS"
7
+ require 'pp'
8
+
9
+ # Copy a given snapshot to another region
10
+ # * start up instance in source-region, create a snapshot from the mounted EBS
11
+ # * then create volume from snapshot, attach volume, and mount it
12
+ # * start up instance in destination-region, create empty volume of same size, attache volume, and mount it
13
+ # * copy the destination key to the source instance
14
+ # * perform an rsynch
15
+ # sync -PHAXaz --rsh "ssh -i /home/${src_user}/.ssh/id_${dst_keypair}" --rsync-path "sudo rsync" ${src_dir}/ ${dst_user}@${dst_public_fqdn}:${dst_dir}/
16
+ # * create a snapshot of the volume
17
+ # * register the snapshot as AMI
18
+ # * clean-up everything
19
+
20
+ class CopyMsWindowsSnapshot < Ec2Script
21
+ # context information needed
22
+ # * the EC2 credentials (see #Ec2Script)
23
+ # * ami_id => the ID of the AMI to be copied in another region
24
+ # * target_ec2_handler => The EC2 handler connected to the region where the snapshot is being copied to
25
+ # * source_ssh_username => The username for ssh for source-instance (default = root)
26
+ # * source_key_name => Key name of the instance that manages the snaphot-volume in the source region
27
+ # * source_ssh_key_data => Key information for the security group that starts the AMI [if not set, use ssh_key_files]
28
+ # * source_ssh_key_files => Key information for the security group that starts the AMI
29
+ # * target_ssh_username => The username for ssh for target-instance (default = root)
30
+ # * target_key_name => Key name of the instance that manages the snaphot-volume in the target region
31
+ # * target_ssh_key_data => Key information for the security group that starts the AMI [if not set, use ssh_key_files]
32
+ # * target_ssh_key_files => Key information for the security group that starts the AMI
33
+ # * target_ami_id => ID of the AMI to start in the target region
34
+ # * name => name of new AMI to be created
35
+ # * description => description of new AMI to be created
36
+
37
+ def initialize(input_params)
38
+ super(input_params)
39
+ @local_ec2_helper = Ec2Helper.new(@input_params[:ec2_api_handler])
40
+ @remote_ec2_helper = Ec2Helper.new(@input_params[:target_ec2_handler])
41
+ end
42
+
43
+ def check_input_parameters()
44
+ if @input_params[:snapshot_id] == nil && !(@input_params[:snapshot_id] =~ /^snap-.*$/)
45
+ raise Exception.new("Invalid Snapshot ID specified: #{@input_params[:snapshot_id]}")
46
+ end
47
+ if @input_params[:source_ami_id] == nil && !(@input_params[:source_ami_id] =~ /^ami-.*$/)
48
+ raise Exception.new("Invalid source AMI ID specified: #{@input_params[:source_ami_id]}")
49
+ end
50
+ if @input_params[:target_ami_id] == nil && !(@input_params[:target_ami_id] =~ /^ami-.*$/)
51
+ raise Exception.new("Invalid target AMI ID specified: #{@input_params[:target_ami_id]}")
52
+ end
53
+ if @input_params[:source_security_groups] == nil
54
+ @input_params[:source_security_groups] = "default"
55
+ end
56
+ if !@local_ec2_helper.check_open_port(@input_params[:source_security_groups], 22)
57
+ raise Exception.new("Port 22 must be opened for security group '#{@input_params[:source_security_groups]}' to connect via SSH in source-region")
58
+ end
59
+ if @input_params[:target_security_groups] == nil
60
+ @input_params[:target_security_groups] = "default"
61
+ end
62
+ if !@remote_ec2_helper.check_open_port(@input_params[:target_security_groups], 22)
63
+ raise Exception.new("Port 22 must be opened for security group '#{@input_params[:target_security_groups]}' to connect via SSH in target-region")
64
+ end
65
+ if @input_params[:root_device_name] == nil
66
+ @input_params[:root_device_name] = "/dev/sda1"
67
+ end
68
+ if @input_params[:device_name] == nil
69
+ @input_params[:device_name] = "/dev/sdj"
70
+ end
71
+ if @input_params[:temp_device_name] == nil
72
+ @input_params[:temp_device_name] = "/dev/sdk"
73
+ end
74
+ if @input_params[:temp_device_name] == @input_params[:device_name]
75
+ raise Exception.new("Device name '#{@input_params[:device_name]}' and temporary device name '#{@input_params[:temp_device_name]}' must be different")
76
+ end
77
+ if @input_params[:source_ssh_username] == nil
78
+ @input_params[:source_ssh_username] = "root"
79
+ end
80
+ if @input_params[:target_ssh_username] == nil
81
+ @input_params[:target_ssh_username] = "root"
82
+ end
83
+ if @input_params[:fs_type] == nil
84
+ @input_params[:fs_type] = "ext3"
85
+ end
86
+ end
87
+
88
+ # Load the initial state for the script.
89
+ # Abstract method to be implemented by extending classes.
90
+ def load_initial_state()
91
+ CopyMsWindowsSnapshotState.load_state(@input_params)
92
+ end
93
+
94
+ private
95
+
96
+ # Here begins the state machine implementation
97
+ class CopyMsWindowsSnapshotState < ScriptExecutionState
98
+
99
+ def self.load_state(context)
100
+ InitialState.new(context)
101
+ end
102
+
103
+ def local_region
104
+ self.ec2_handler = (@context[:ec2_api_handler])
105
+ @local_ec2_helper = Ec2Helper.new(self.ec2_handler)
106
+ end
107
+
108
+ def remote_region
109
+ self.ec2_handler = (@context[:target_ec2_handler])
110
+ @remote_ec2_helper = Ec2Helper.new(self.ec2_handler)
111
+ end
112
+ end
113
+
114
+ # Initial State: Retrieve all information on this AMI we need later on
115
+ # and set some parameters such as the Availability Zone
116
+ # - Snapshot ID from this AMI
117
+ # - Volume size from this AMI
118
+ # - Availability Zone
119
+ #NB: less modification if we get the AZ from the launched instance
120
+ class InitialState < CopyMsWindowsSnapshotState
121
+ def enter()
122
+ local_region()
123
+ puts "Retrieving AMI parammeters"
124
+ @context[:volume_size] = @local_ec2_helper.snapshot_prop(@context[:snapshot_id], :volumeSize).to_i
125
+ #puts "Setting Source and Target Availability Zone if not set"
126
+ #@context[:source_availability_zone] = "us-east-1a" unless @context[:source_availability_zone] != nil
127
+ #@context[:target_availability_zone] = "us-west-1a" unless @context[:target_availability_zone] != nil
128
+ puts "DEBUG: Parameters: #{@context[:snapshot_id]}, #{@context[:volume_size]}"
129
+
130
+ InitialStateDone.new(@context)
131
+ end
132
+ end
133
+
134
+ # Initial state: Launch an Amazon Linux AMI in the source Region
135
+ class InitialStateDone < CopyMsWindowsSnapshotState
136
+ def enter()
137
+ local_region()
138
+ result = launch_instance(@context[:source_ami_id], @context[:source_key_name], @context[:source_security_groups])
139
+ @context[:source_instance_id] = result.first
140
+ @context[:source_dns_name] = result[1]
141
+ @context[:source_availability_zone] = result[2]
142
+ #@context[:source_root_device_name] = @local_ec2_helper.ami_prop(@context[:source_ami_id], 'rootDeviceName')
143
+ @context[:source_root_device_name] = result[6]
144
+ puts "DEBUG: Parameters: #{@context[:source_instance_id]}, #{@context[:source_dns_name]}, #{@context[:source_availability_zone]}, #{@context[:source_root_device_name]}"
145
+
146
+ SourceInstanceLaunchedState.new(@context)
147
+ end
148
+ end
149
+
150
+ # Source instance is started.
151
+ # Steps:
152
+ # - create and attach a volume from the Snapshot of the AMI to copy
153
+ # - create and attach a temp volume of the same size to dump and compress the entire drive
154
+ # - create a filesystem on the temp volume and mount the temp volume
155
+ # - dump and compress the entire drive to the temp volume
156
+ class SourceInstanceLaunchedState < CopyMsWindowsSnapshotState
157
+ def enter()
158
+ local_region()
159
+ # Step1: create and attach a volume from the Snapshot of the AMI to copy
160
+ @context[:source_volume_id] = create_volume_from_snapshot(@context[:snapshot_id],
161
+ @context[:source_availability_zone])
162
+ source_device = @context[:device_name]
163
+ attach_volume(@context[:source_volume_id], @context[:source_instance_id], source_device)
164
+ connect(@context[:source_dns_name], @context[:source_ssh_username], nil, @context[:source_ssh_keydata])
165
+ # detect if there is a shift for device mapping (between AWS and the operating system of the system)
166
+ root_device_name = get_root_device_name()
167
+ # detect letters
168
+ aws_root_device = @context[:source_root_device_name]
169
+ aws_letter = aws_root_device.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
170
+ os_letter = root_device_name.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
171
+ aws_device_letter = source_device.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
172
+ if !aws_letter.eql?(os_letter)
173
+ post_message("Detected specific kernel with shift between AWS and Kernel OS for device naming")
174
+ end
175
+ while !aws_letter.eql?(os_letter)
176
+ aws_letter.succ!
177
+ aws_device_letter.succ!
178
+ end
179
+ source_device = "/dev/sd#{aws_device_letter}"
180
+ post_message("Using AWS name source device '#{@context[:device_name]}' and OS name '#{source_device}'")
181
+ @context[:source_device_name] = source_device
182
+ # Step2: create and attach a temp volume of the same size to dump and compress the entire drive
183
+ @context[:source_temp_volume_id] = create_volume(@context[:source_availability_zone], @context[:volume_size])
184
+ temp_device = @context[:temp_device_name]
185
+ attach_volume(@context[:source_temp_volume_id], @context[:source_instance_id], temp_device)
186
+ aws_device_letter = temp_device.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
187
+ while !aws_letter.eql?(os_letter)
188
+ aws_letter.succ!
189
+ aws_device_letter.succ!
190
+ end
191
+ temp_device="/dev/sd#{aws_device_letter}"
192
+ post_message("Using AWS name source device '#{@context[:temp_device_name]}' and OS name '#{temp_device}'")
193
+ # Step3: mount the temp volume
194
+ mount_point = "/mnt/tmp_#{@context[:source_temp_volume_id]}"
195
+ create_labeled_fs(@context[:source_dns_name], temp_device, @context[:fs_type], nil)
196
+ mount_fs(mount_point, temp_device)
197
+ disconnect()
198
+
199
+ SourceVolumeReadyState.new(@context)
200
+ end
201
+ end
202
+
203
+ # Source is ready
204
+ # Steps:
205
+ # - dump and compress the entire source drive to the temp volume
206
+ class SourceVolumeReadyState < CopyMsWindowsSnapshotState
207
+ def enter()
208
+ local_region()
209
+ connect(@context[:source_dns_name], @context[:source_ssh_username], nil, @context[:source_ssh_keydata])
210
+ mount_point = "/mnt/tmp_#{@context[:source_temp_volume_id]}"
211
+ @context[:source_filename] = "#{mount_point}" + "/" + "#{@context[:snapshot_id]}" + ".gz"
212
+ local_dump_and_compress_device_to_file(@context[:source_device_name], @context[:source_filename])
213
+ disconnect()
214
+
215
+ BackupedDataState.new(@context)
216
+ end
217
+ end
218
+
219
+ # Source is ready.
220
+ # Steps:
221
+ # - start an instance of AWS Linux AMI in the target region
222
+ class BackupedDataState < CopyMsWindowsSnapshotState
223
+ def enter()
224
+ remote_region()
225
+ result = launch_instance(@context[:target_ami_id], @context[:target_key_name], @context[:target_security_groups])
226
+ @context[:target_instance_id] = result.first
227
+ @context[:target_dns_name] = result[1]
228
+ @context[:target_availability_zone] = result[2]
229
+ #@context[:remote_root_device_name] = @remote_ec2_helper.ami_prop(@context[:target_ami_id], 'rootDeviceName')
230
+ @context[:target_root_device_name] = result[6]
231
+ puts "DEBUG: Parameters: #{@context[:target_instance_id]}, #{@context[:target_dns_name]}, #{@context[:target_availability_zone]}, #{@context[:target_root_device_name]}"
232
+
233
+ TargetInstanceLaunchedState.new(@context)
234
+ end
235
+ end
236
+
237
+ # Destination instance is started. Now configure storage.
238
+ # Steps:
239
+ # - create and attach a temp volume for receiving archive of the drive
240
+ # - create a filesystem on the temp volume and mount the temp volume
241
+ # - create and attach a volume for uncompressing and restoring the entire drive
242
+ class TargetInstanceLaunchedState < CopyMsWindowsSnapshotState
243
+ def enter()
244
+ remote_region()
245
+ # Step1: create and attach a temp volume for receiving archive of the drive
246
+ @context[:target_temp_volume_id] = create_volume(@context[:target_availability_zone], @context[:volume_size])
247
+ temp_device = @context[:temp_device_name]
248
+ attach_volume(@context[:target_temp_volume_id], @context[:target_instance_id], temp_device)
249
+ connect(@context[:target_dns_name], @context[:target_ssh_username], nil, @context[:target_ssh_keydata])
250
+ # detect if there is a shift for device mapping (between AWS and the operating system of the system)
251
+ root_device_name = get_root_device_name()
252
+ # detect letters
253
+ aws_root_device = @context[:target_root_device_name]
254
+ aws_letter = aws_root_device.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
255
+ os_letter = root_device_name.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
256
+ aws_device_letter = temp_device.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
257
+ if !aws_letter.eql?(os_letter)
258
+ post_message("Detected specific kernel with shift between AWS and Kernel OS for device naming")
259
+ end
260
+ while !aws_letter.eql?(os_letter)
261
+ aws_letter.succ!
262
+ aws_device_letter.succ!
263
+ end
264
+ temp_device = "/dev/sd#{aws_device_letter}"
265
+ post_message("Using AWS name source device '#{@context[:device_name]}' and OS name '#{temp_device}'")
266
+ # Step2: mount the temp volume
267
+ mount_point = "/mnt/tmp_#{@context[:target_temp_volume_id]}"
268
+ create_labeled_fs(@context[:target_dns_name], temp_device, @context[:fs_type], nil)
269
+ mount_fs(mount_point, temp_device)
270
+ # Step3: create and attach a volume for uncompressing and restoring the entire drive
271
+ @context[:target_volume_id] = create_volume(@context[:target_availability_zone], @context[:volume_size])
272
+ target_device = @context[:device_name]
273
+ attach_volume(@context[:target_volume_id], @context[:target_instance_id], target_device)
274
+ aws_device_letter = target_device.split('/')[2].gsub('sd', '').gsub('xvd', '').gsub(/[0-9]/, '')
275
+ if !aws_letter.eql?(os_letter)
276
+ post_message("Detected specific kernel with shift between AWS and Kernel OS for device naming")
277
+ end
278
+ while !aws_letter.eql?(os_letter)
279
+ aws_letter.succ!
280
+ aws_device_letter.succ!
281
+ end
282
+ target_device = "/dev/sd#{aws_device_letter}"
283
+ @context[:target_device_name] = target_device
284
+ post_message("Using AWS name source device '#{@context[:device_name]}' and OS name '#{target_device}'")
285
+ disconnect()
286
+
287
+ TargetVolumeReadyState.new(@context)
288
+ end
289
+ end
290
+
291
+ # Storages are ready. Only thing missing: the key of the target region
292
+ # must be available on the instance in the source region to be able to perform
293
+ # a remote copy.
294
+ class TargetVolumeReadyState < CopyMsWindowsSnapshotState
295
+ def enter()
296
+ post_message("upload key of target-instance to source-instance...")
297
+ path_candidates = ["/#{@context[:source_ssh_username]}/.ssh/", "/home/#{@context[:source_ssh_username]}/.ssh/"]
298
+ key_path = determine_file(@context[:source_dns_name], @context[:source_ssh_username], @context[:source_ssh_keydata], path_candidates)
299
+ upload_file(@context[:source_dns_name], @context[:source_ssh_username], @context[:source_ssh_keydata],
300
+ @context[:target_ssh_keyfile], "#{key_path}#{@context[:target_key_name]}.pem")
301
+ post_message("credentials are in place to connect source and target (from source to target).")
302
+
303
+ KeyInPlaceState.new(@context)
304
+ end
305
+ end
306
+
307
+ # Now we can copy.
308
+ class KeyInPlaceState < CopyMsWindowsSnapshotState
309
+ def enter()
310
+ connect(@context[:target_dns_name], @context[:target_ssh_username], nil, @context[:target_ssh_keydata])
311
+ disable_ssh_tty(@context[:target_dns_name])
312
+ disconnect()
313
+ #
314
+ connect(@context[:source_dns_name], @context[:source_ssh_username], nil, @context[:source_ssh_keydata])
315
+ source_dir = "/mnt/tmp_#{@context[:source_temp_volume_id]}"
316
+ dest_dir = "/mnt/tmp_#{@context[:target_temp_volume_id]}"
317
+ remote_copy(@context[:source_ssh_username], @context[:target_key_name], source_dir,
318
+ @context[:target_dns_name], @context[:target_ssh_username], dest_dir)
319
+ disconnect()
320
+ #
321
+ connect(@context[:target_dns_name], @context[:target_ssh_username], nil, @context[:target_ssh_keydata])
322
+ enable_ssh_tty(@context[:target_dns_name])
323
+ disconnect()
324
+
325
+ DataCopiedState.new(@context)
326
+ end
327
+ end
328
+
329
+ # Decompress data on the device
330
+ class DataCopiedState < CopyMsWindowsSnapshotState
331
+ def enter()
332
+ remote_region()
333
+ connect(@context[:target_dns_name], @context[:target_ssh_username], nil, @context[:target_ssh_keydata])
334
+ mount_point = "/mnt/tmp_#{@context[:target_temp_volume_id]}"
335
+ @context[:source_filename] = "#{mount_point}" + "/" + "#{@context[:snapshot_id]}" + ".gz"
336
+ local_decompress_and_dump_file_to_device(@context[:source_filename], @context[:target_device_name])
337
+ disconnect()
338
+
339
+ RestoredDataState.new(@context)
340
+ end
341
+ end
342
+
343
+ # Data of snapshot now copied to the new volume.
344
+ # Steps:
345
+ # - detach the target volume
346
+ #XXX: TODO
347
+ class RestoredDataState < CopyMsWindowsSnapshotState
348
+ def enter()
349
+ remote_region()
350
+ detach_volume(@context[:target_volume_id], @context[:target_instance_id])
351
+ @context[:new_snapshot_id] = create_snapshot(@context[:target_volume_id], "Created by CloudyScripts - copy_mswindows_ami")
352
+ @context[:result][:snapshot_id] = @context[:new_snapshot_id]
353
+
354
+ #XXX: for testing, bypass cleanup
355
+ Done.new(@context)
356
+ #TargetSnapshotCreatedState.new(@context)
357
+ end
358
+ end
359
+
360
+ # AMI is registered. Now only cleanup is missing, i.e. shut down instances and
361
+ # remote the volumes that were created. Start with cleaning the ressources
362
+ # in the local region.
363
+ # Steps:
364
+ # - cleanup source region
365
+ # - unmount temp volume
366
+ # - detach source and temp volume
367
+ # - terminate instance
368
+ # - delete source and temp volume
369
+ # - cleanup target region
370
+ # - unmount temp volume
371
+ # - detach temp volume
372
+ # - terminate instance
373
+ # - delete source and temp volume
374
+ class TargetSnapshotCreatedState < CopyMsWindowsSnapshotState
375
+ def enter()
376
+ local_region()
377
+ connect(@context[:source_dns_name], @context[:source_ssh_username], nil, @context[:source_ssh_keydata])
378
+ mount_point = "/mnt/tmp_#{@context[:source_temp_volume_id]}"
379
+ unmount_fs(mount_point)
380
+ disconnect()
381
+ detach_volume(@context[:source_temp_volume_id], @context[:source_instance_id])
382
+ detach_volume(@context[:source_volume_id], @context[:source_instance_id])
383
+ shut_down_instance(@context[:source_instance_id])
384
+ delete_volume(@context[:source_temp_volume_id])
385
+ delete_volume(@context[:source_volume_id])
386
+ #
387
+ remote_region()
388
+ connect(@context[:target_dns_name], @context[:target_ssh_username], nil, @context[:target_ssh_keydata])
389
+ mount_point = "/mnt/tmp_#{@context[:target_temp_volume_id]}"
390
+ unmount_fs(mount_point)
391
+ disconnect()
392
+ detach_volume(@context[:target_temp_volume_id], @context[:target_instance_id])
393
+ shut_down_instance(@context[:target_instance_id])
394
+ delete_volume(@context[:target_temp_volume_id])
395
+ delete_volume(@context[:target_volume_id])
396
+
397
+ Done.new(@context)
398
+ end
399
+ end
400
+
401
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: CloudyScripts
3
3
  version: !ruby/object:Gem::Version
4
- hash: 67
4
+ hash: 93
5
5
  prerelease: false
6
6
  segments:
7
7
  - 2
8
- - 11
9
- - 48
10
- version: 2.11.48
8
+ - 12
9
+ - 49
10
+ version: 2.12.49
11
11
  platform: ruby
12
12
  authors:
13
13
  - Matthias Jung
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-11-29 00:00:00 +00:00
18
+ date: 2011-12-15 00:00:00 +00:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -251,10 +251,12 @@ files:
251
251
  - lib/audit/benchmark_ssh.zip
252
252
  - lib/scripts/vCloud/v_cloud_script.rb
253
253
  - lib/scripts/vCloud/open_port_checker_vm.rb
254
+ - lib/scripts/ec2/copy_mswindows_snapshot.rb
254
255
  - lib/scripts/ec2/port_range_detector.rb
255
256
  - lib/scripts/ec2/dm_encrypt.rb
256
257
  - lib/scripts/ec2/vpc_critical_ports_audit.rb
257
258
  - lib/scripts/ec2/ami2_ebs_conversion.rb
259
+ - lib/scripts/ec2/copy_mswindows_ami.rb
258
260
  - lib/scripts/ec2/audit_via_ssh.rb
259
261
  - lib/scripts/ec2/open_port_checker.rb
260
262
  - lib/scripts/ec2/copy_ami.rb