CloudyScripts 2.11.48 → 2.12.49

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/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