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 +1 -1
- data/lib/help/ec2_helper.rb +16 -0
- data/lib/help/remote_command_handler.rb +14 -0
- data/lib/help/state_transition_helper.rb +62 -1
- data/lib/scripts/ec2/copy_mswindows_ami.rb +449 -0
- data/lib/scripts/ec2/copy_mswindows_snapshot.rb +401 -0
- metadata +7 -5
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.
|
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.'
|
data/lib/help/ec2_helper.rb
CHANGED
@@ -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 "
|
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:
|
4
|
+
hash: 93
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 2.
|
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-
|
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
|