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