CloudyScripts 0.0.4 → 0.0.5
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/README.rdoc +3 -0
- data/Rakefile +1 -1
- data/lib/help/dm_crypt_helper.rb +28 -23
- data/lib/help/remote_command_handler.rb +43 -21
- data/lib/help/script_execution_state.rb +10 -4
- data/lib/scripts/ec2/ami2_ebs_conversion.rb +443 -0
- data/lib/scripts/ec2/dm_encrypt.rb +9 -10
- data/lib/scripts/ec2/ec2_script.rb +9 -0
- metadata +3 -2
data/README.rdoc
CHANGED
data/Rakefile
CHANGED
@@ -12,7 +12,7 @@ require 'rake/testtask'
|
|
12
12
|
|
13
13
|
spec = Gem::Specification.new do |s|
|
14
14
|
s.name = 'CloudyScripts'
|
15
|
-
s.version = '0.0.
|
15
|
+
s.version = '0.0.5'
|
16
16
|
s.has_rdoc = true
|
17
17
|
s.extra_rdoc_files = ['README.rdoc', 'LICENSE']
|
18
18
|
s.summary = 'Scripts to facilitate programming for infrastructure clouds.'
|
data/lib/help/dm_crypt_helper.rb
CHANGED
@@ -4,6 +4,11 @@ require 'help/remote_command_handler'
|
|
4
4
|
# (see #Scripts::EC2::DmEncrypt)
|
5
5
|
|
6
6
|
class DmCryptHelper
|
7
|
+
attr_accessor :logger
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@logger = Logger.new(STDOUT)
|
11
|
+
end
|
7
12
|
|
8
13
|
# Passes an remote command handler object
|
9
14
|
# (see #Help::RemoteCommandHandler)
|
@@ -46,15 +51,15 @@ class DmCryptHelper
|
|
46
51
|
else
|
47
52
|
mapper_exists = false
|
48
53
|
end
|
49
|
-
|
54
|
+
@logger.info "mapper exists = #{mapper_exists}"
|
50
55
|
exec_string = "cryptsetup create dm-#{name} #{device}"
|
51
56
|
if !mapper_exists
|
52
57
|
#mapper does not exist, create it
|
53
58
|
channel = @ssh_session.open_channel do |ch|
|
54
59
|
ch.send_data("#{password}\n")
|
55
|
-
|
60
|
+
@logger.debug "execute #{exec_string}"
|
56
61
|
ch.exec exec_string do |ch, success|
|
57
|
-
|
62
|
+
@logger.debug "success = #{success}"
|
58
63
|
if !success
|
59
64
|
err = "Failed during creation of encrypted partition"
|
60
65
|
#puts "#{err}: #{data}"
|
@@ -78,12 +83,12 @@ class DmCryptHelper
|
|
78
83
|
end
|
79
84
|
if !pv_exists
|
80
85
|
exec_string = "pvcreate /dev/mapper/dm-#{name}"
|
81
|
-
|
86
|
+
@logger.info "pv does not exist - execute: #{exec_string}"
|
82
87
|
#private volume does not exist, create it
|
83
88
|
channel = @ssh_session.open_channel do |ch|
|
84
89
|
ch.send_data("y\n")
|
85
90
|
ch.exec exec_string do |ch, success|
|
86
|
-
|
91
|
+
@logger.debug "success = #{success}"
|
87
92
|
if !success
|
88
93
|
err = "Failed during creation of physical volume"
|
89
94
|
#puts "#{err}: #{data}"
|
@@ -106,26 +111,26 @@ class DmCryptHelper
|
|
106
111
|
end
|
107
112
|
if !vg_exists
|
108
113
|
exec_string = "vgcreate vg-#{name} /dev/mapper/dm-#{name}"
|
109
|
-
|
114
|
+
@logger.info "vg_exists == false; execute #{exec_string}"
|
110
115
|
@ssh_session.exec! exec_string do |ch, stream, data|
|
111
116
|
if stream == :stderr && data != nil
|
112
117
|
err = "Failed during creation of volume group"
|
113
|
-
|
118
|
+
@logger.warn "#{err}: #{data}"
|
114
119
|
raise Exception.new(err)
|
115
120
|
end
|
116
121
|
end
|
117
122
|
#exec_string = "lvcreate -n lv-#{name} -L#{size_in_mb.to_s}M vg-#{name}"
|
118
123
|
exec_string = "lvcreate -n lv-#{name} -l100%FREE vg-#{name}"
|
119
|
-
|
124
|
+
@logger.info "execute #{exec_string}"
|
120
125
|
@ssh_session.exec! exec_string do |ch, stream, data|
|
121
126
|
if stream == :stderr && data != nil
|
122
127
|
err = "Failed during creation of logical volume"
|
123
|
-
|
128
|
+
@logger.debug "#{err}: #{data}"
|
124
129
|
raise Exception.new(err)
|
125
130
|
end
|
126
131
|
end
|
127
|
-
exec_string = "mkfs -t ext3 /dev/vg-#{name}/lv-#{name}"
|
128
|
-
|
132
|
+
exec_string = "mkfs -t ext3 /dev/vg-#{name}/lv-#{name}" #TODO: use method in remote_command_handler
|
133
|
+
@logger.info "execute #{exec_string}"
|
129
134
|
@ssh_session.exec! exec_string #do |ch, stream, data|
|
130
135
|
#if stream == :stderr && data != nil
|
131
136
|
#err = "Failed during creation of file-system"
|
@@ -139,11 +144,11 @@ class DmCryptHelper
|
|
139
144
|
end
|
140
145
|
else
|
141
146
|
exec_string = "/sbin/vgchange -a y vg-#{name}"
|
142
|
-
|
147
|
+
@logger.info "vg_exists == true; execute #{exec_string}"
|
143
148
|
@ssh_session.exec! exec_string do |ch, stream, data| #TODO: the right size instead L2G!
|
144
149
|
if stream == :stderr && data != nil
|
145
150
|
err = "Failed during re-activation of volume group"
|
146
|
-
|
151
|
+
@logger.info "#{err}: #{data}"
|
147
152
|
raise Exception.new(err)
|
148
153
|
end
|
149
154
|
end
|
@@ -158,29 +163,29 @@ class DmCryptHelper
|
|
158
163
|
# Undo encryption for the volume specified by name and path
|
159
164
|
def undo_encryption(name, path)
|
160
165
|
exec_string = "umount #{path}"
|
161
|
-
|
166
|
+
@logger.debug "going to execute #{exec_string}"
|
162
167
|
@ssh_session.exec! exec_string do |ch, stream, data|
|
163
|
-
|
168
|
+
@logger.debug "returns #{data}"
|
164
169
|
end
|
165
170
|
exec_string = "lvremove --verbose vg-#{name} -f" #[with confirmation?]
|
166
|
-
|
171
|
+
@logger.debug "going to execute #{exec_string}"
|
167
172
|
@ssh_session.exec! exec_string do |ch, stream, data|
|
168
|
-
|
173
|
+
@logger.debug "returns #{data}"
|
169
174
|
end
|
170
175
|
exec_string = "vgremove vg-#{name}"
|
171
|
-
|
176
|
+
@logger.debug "going to execute #{exec_string}"
|
172
177
|
@ssh_session.exec! exec_string do |ch, stream, data|
|
173
|
-
|
178
|
+
@logger.debug "returns #{data}"
|
174
179
|
end
|
175
180
|
exec_string = "pvremove /dev/mapper/dm-#{name}"
|
176
|
-
|
181
|
+
@logger.debug "going to execute #{exec_string}"
|
177
182
|
@ssh_session.exec! exec_string do |ch, stream, data|
|
178
|
-
|
183
|
+
@logger.debug "returns #{data}"
|
179
184
|
end
|
180
185
|
exec_string = "cryptsetup remove dm-#{name}"
|
181
|
-
|
186
|
+
@logger.debug "going to execute #{exec_string}"
|
182
187
|
@ssh_session.exec! exec_string do |ch, stream, data|
|
183
|
-
|
188
|
+
@logger.debug "returns #{data}"
|
184
189
|
end
|
185
190
|
end
|
186
191
|
|
@@ -4,8 +4,10 @@ require 'help/dm_crypt_helper'
|
|
4
4
|
|
5
5
|
# Provides methods to be executed via ssh to remote instances.
|
6
6
|
class RemoteCommandHandler
|
7
|
+
attr_accessor :logger
|
7
8
|
def initialize
|
8
9
|
@crypto = DmCryptHelper.new #TODO: instantiate helpers for different tools
|
10
|
+
@logger = Logger.new(STDOUT)
|
9
11
|
end
|
10
12
|
|
11
13
|
# Check if the path/file specified exists
|
@@ -23,7 +25,7 @@ class RemoteCommandHandler
|
|
23
25
|
# Params:
|
24
26
|
# * ip: ip address of the machine to connect to
|
25
27
|
# * keyfile: path of the keyfile to be used for authentication
|
26
|
-
def
|
28
|
+
def connect_with_keyfile(ip, keyfile)
|
27
29
|
@ssh_session = Net::SSH.start(ip, 'root', :keys => [keyfile])
|
28
30
|
@crypto.set_ssh(@ssh_session)
|
29
31
|
end
|
@@ -63,24 +65,46 @@ class RemoteCommandHandler
|
|
63
65
|
drive_mounted?(path) #TODO: must at least also check the name
|
64
66
|
end
|
65
67
|
|
68
|
+
def create_filesystem(fs_type, volume)
|
69
|
+
e = "echo y >tmp.txt; mkfs -t #{fs_type} #{volume} <tmp.txt; rm -f tmp.txt"
|
70
|
+
@logger.debug "exec #{e}"
|
71
|
+
@ssh_session.exec! e do |ch, stream, data|
|
72
|
+
@logger.debug "#{e}: output on #{stream.inspect}: #{data}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def mkdir(path)
|
77
|
+
e = "mkdir #{path}"
|
78
|
+
@logger.debug "exec #{e}"
|
79
|
+
@ssh_session.exec! e do |ch, stream, data|
|
80
|
+
@logger.debug "#{e}: output on #{stream.inspect}: #{data}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def mount(device, path)
|
85
|
+
e = "mount #{device} #{path}"
|
86
|
+
@logger.debug "exec #{e}"
|
87
|
+
@ssh_session.exec! e do |ch, stream, data|
|
88
|
+
@logger.debug "#{e}: output on #{stream.inspect}: #{data}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
66
92
|
# Checks if the drive on path is mounted
|
67
93
|
def drive_mounted?(path)
|
68
94
|
#check if drive mounted
|
69
95
|
drive_found = false
|
70
96
|
@ssh_session.exec! "mount" do |ch, stream, data|
|
71
97
|
if stream == :stdout
|
72
|
-
|
98
|
+
@logger.debug "mount command produces the following data: #{data}\n---------------"
|
73
99
|
if data.include?("on #{path} type")
|
74
100
|
drive_found = true
|
75
|
-
else
|
76
|
-
puts "not mounted: #{data}"
|
77
101
|
end
|
78
102
|
end
|
79
103
|
end
|
80
104
|
if drive_found
|
81
105
|
return RemoteCommandHandler.file_exists?(@ssh_session, path)
|
82
106
|
else
|
83
|
-
|
107
|
+
@logger.debug "not mounted (since #{path} non-existing)"
|
84
108
|
false
|
85
109
|
end
|
86
110
|
end
|
@@ -93,8 +117,6 @@ class RemoteCommandHandler
|
|
93
117
|
if stream == :stdout
|
94
118
|
if data.include?("#{device} on #{path} type")
|
95
119
|
drive_mounted = true
|
96
|
-
else
|
97
|
-
puts "not mounted: #{data}"
|
98
120
|
end
|
99
121
|
end
|
100
122
|
end
|
@@ -104,19 +126,10 @@ class RemoteCommandHandler
|
|
104
126
|
# Activates the encrypted volume, i.e. mounts it if not yet done.
|
105
127
|
def activate_encrypted_volume(name, path)
|
106
128
|
drive_mounted = drive_mounted?(path)
|
107
|
-
|
129
|
+
@logger.debug "drive #{path} mounted? #{drive_mounted}"
|
108
130
|
if !drive_mounted
|
109
|
-
|
110
|
-
|
111
|
-
puts "drive not mounted; execute: #{exec_string}"
|
112
|
-
@ssh_session.exec! "mount /dev/vg-#{name}/lv-#{name} #{path}" do |ch, stream, data|
|
113
|
-
if stream == :stderr && data != nil
|
114
|
-
err = "Failed during mounting encrypted device"
|
115
|
-
puts "#{err}: #{data}"
|
116
|
-
puts "mount /dev/vg-#{name}/lv-#{name} #{path}"
|
117
|
-
raise Exception.new(err)
|
118
|
-
end
|
119
|
-
end
|
131
|
+
mkdir(path)
|
132
|
+
mount("/dev/vg-#{name}/lv-#{name}", "#{path}")
|
120
133
|
end
|
121
134
|
end
|
122
135
|
|
@@ -128,11 +141,20 @@ class RemoteCommandHandler
|
|
128
141
|
# Unmount the specified path.
|
129
142
|
def umount(path)
|
130
143
|
exec_string = "umount #{path}"
|
131
|
-
|
144
|
+
@logger.debug "going to execute #{exec_string}"
|
132
145
|
@ssh_session.exec! exec_string do |ch, stream, data|
|
133
|
-
|
146
|
+
@logger.debug "ssh_api.umount: returns #{data}"
|
134
147
|
end
|
135
148
|
!drive_mounted?(path)
|
136
149
|
end
|
137
150
|
|
151
|
+
# Copy directory using options -avHx
|
152
|
+
def rsync(source_path, dest_path)
|
153
|
+
e = "rsync -avHx #{source_path} #{dest_path}"
|
154
|
+
@logger.debug "going to execute #{e}"
|
155
|
+
@ssh_session.exec! e do |ch, stream, data|
|
156
|
+
@logger.debug "#{e}: output on #{stream.inspect}: #{data}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
138
160
|
end
|
@@ -4,11 +4,16 @@
|
|
4
4
|
# return another state.
|
5
5
|
class ScriptExecutionState
|
6
6
|
# context information for the state (hash)
|
7
|
-
attr_reader :context
|
7
|
+
attr_reader :context, :logger
|
8
8
|
|
9
9
|
def initialize(context)
|
10
10
|
@context = context
|
11
11
|
@state_change_listeners = []
|
12
|
+
@logger = context[:logger]
|
13
|
+
if @logger == nil
|
14
|
+
@logger = Logger.new(STDOUT)
|
15
|
+
@logger.level = Logger::WARN
|
16
|
+
end
|
12
17
|
end
|
13
18
|
|
14
19
|
# Listener should extend #StateChangeListener (or implement a
|
@@ -20,16 +25,17 @@ class ScriptExecutionState
|
|
20
25
|
# Start the state machine using this state as initial state.
|
21
26
|
def start_state_machine
|
22
27
|
@current_state = self
|
23
|
-
|
28
|
+
@logger.info "start state machine with #{@current_state.inspect}"
|
24
29
|
while !@current_state.done? && !@current_state.failed?
|
25
30
|
begin
|
31
|
+
@logger.info "state machine: current state = #{@current_state.inspect}"
|
26
32
|
@current_state = @current_state.enter()
|
27
33
|
notify_state_change_listeners(@current_state)
|
28
34
|
rescue Exception => e
|
29
35
|
@current_state = FailedState.new(@context, e.to_s, @current_state)
|
30
36
|
notify_state_change_listeners(@current_state)
|
31
|
-
|
32
|
-
|
37
|
+
@logger.warn "Exception: #{e}"
|
38
|
+
@logger.warn "#{e.backtrace.join("\n")}"
|
33
39
|
end
|
34
40
|
end
|
35
41
|
@current_state
|
@@ -0,0 +1,443 @@
|
|
1
|
+
require "help/script_execution_state"
|
2
|
+
require "scripts/ec2/ec2_script"
|
3
|
+
require "help/remote_command_handler"
|
4
|
+
#require "help/dm_crypt_helper"
|
5
|
+
require "AWS"
|
6
|
+
|
7
|
+
class AWS::EC2::Base
|
8
|
+
def register_image_updated(options)
|
9
|
+
params = {}
|
10
|
+
params["Name"] = options[:name].to_s
|
11
|
+
params["BlockDeviceMapping.1.Ebs.SnapshotId"] = options[:snapshot_id].to_s
|
12
|
+
params["BlockDeviceMapping.1.DeviceName"] = options[:root_device_name].to_s
|
13
|
+
params["Description"] = options[:description].to_s
|
14
|
+
params["KernelId"] = options[:kernel_id].to_s
|
15
|
+
params["RamdiskId"] = options[:ramdisk_id].to_s
|
16
|
+
params["Architecture"] = options[:architecture].to_s
|
17
|
+
params["RootDeviceName"] = options[:root_device_name].to_s
|
18
|
+
return response_generator(:action => "RegisterImage", :params => params)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Creates a bootable EBS storage from an existing AMI.
|
23
|
+
#
|
24
|
+
|
25
|
+
class Ami2EbsConversion < Ec2Script
|
26
|
+
# Input parameters
|
27
|
+
# * aws_access_key => the Amazon AWS Access Key (see Your Account -> Security Credentials)
|
28
|
+
# * aws_secret_key => the Amazon AWS Secret Key
|
29
|
+
# * ami_id => the ID of the AMI to be converted
|
30
|
+
# * security_group_name => name of the security group to start
|
31
|
+
# * ssh_key_data => Key information for the security group that starts the AMI [if not set, use ssh_key_files]
|
32
|
+
# * ssh_key_files => Key information for the security group that starts the AMI
|
33
|
+
# * remote_command_handler => object that allows to connect via ssh and execute commands (optional)
|
34
|
+
# * ec2_api_handler => object that allows to access the EC2 API (optional)
|
35
|
+
# * ec2_api_server => server to connect to (option, default is us-east-1.ec2.amazonaws.com)
|
36
|
+
# * name => the name of the AMI to be created
|
37
|
+
# * description => description on AMI to be created (optional)
|
38
|
+
# * temp_device_name => [default /dev/sdj] device name used to attach the temporary storage; change this only if there's already a volume attacged as /dev/sdj (optional, default is /dev/sdj)
|
39
|
+
# * root_device_name"=> [default /dev/sda1] device name used for the root device (optional)
|
40
|
+
def initialize(input_params)
|
41
|
+
super(input_params)
|
42
|
+
@result = {:done => false}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Executes the script.
|
46
|
+
def start_script()
|
47
|
+
begin
|
48
|
+
# optional parameters and initialization
|
49
|
+
if @input_params[:name] == nil
|
50
|
+
@input_params[:name] = "Boot EBS (for AMI #{@input_params[:ami_id]}) at #{Time.now.strftime('%d/%m/%Y %H.%M.%S')}"
|
51
|
+
else
|
52
|
+
end
|
53
|
+
if @input_params[:description] == nil
|
54
|
+
@input_params[:description] = @input_params[:name]
|
55
|
+
end
|
56
|
+
if @input_params[:temp_device_name] == nil
|
57
|
+
@input_params[:temp_device_name] = "/dev/sdj"
|
58
|
+
end
|
59
|
+
if @input_params[:root_device_name] == nil
|
60
|
+
@input_params[:root_device_name] = "/dev/sda1"
|
61
|
+
end
|
62
|
+
# start state machine
|
63
|
+
current_state = Ami2EbsConversionState.load_state(@input_params)
|
64
|
+
@state_change_listeners.each() {|listener|
|
65
|
+
current_state.register_state_change_listener(listener)
|
66
|
+
}
|
67
|
+
end_state = current_state.start_state_machine()
|
68
|
+
if end_state.failed?
|
69
|
+
@result[:failed] = true
|
70
|
+
@result[:failure_reason] = current_state.end_state.failure_reason
|
71
|
+
@result[:end_state] = current_state.end_state
|
72
|
+
else
|
73
|
+
@result[:failed] = false
|
74
|
+
end
|
75
|
+
rescue Exception => e
|
76
|
+
@logger.warn "exception during encryption: #{e}"
|
77
|
+
@logger.warn e.backtrace.join("\n")
|
78
|
+
err = e.to_s
|
79
|
+
err += " (in #{current_state.end_state.to_s})" unless current_state == nil
|
80
|
+
@result[:failed] = true
|
81
|
+
@result[:failure_reason] = err
|
82
|
+
@result[:end_state] = current_state.end_state unless current_state == nil
|
83
|
+
ensure
|
84
|
+
begin
|
85
|
+
@input_params[:remote_command_handler].disconnect
|
86
|
+
rescue Exception => e2
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
@result[:done] = true
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns a hash with the following information:
|
95
|
+
# :done => if execution is done
|
96
|
+
#
|
97
|
+
def get_execution_result
|
98
|
+
@result
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
# Here begins the state machine implementation
|
104
|
+
class Ami2EbsConversionState < ScriptExecutionState
|
105
|
+
def self.load_state(context)
|
106
|
+
state = context[:initial_state] == nil ? InitialState.new(context) : context[:initial_state]
|
107
|
+
state
|
108
|
+
end
|
109
|
+
|
110
|
+
def connect
|
111
|
+
if @context[:remote_command_handler] == nil
|
112
|
+
@context[:remote_command_handler] = RemoteCommandHandler.new
|
113
|
+
end
|
114
|
+
connected = false
|
115
|
+
remaining_trials = 3
|
116
|
+
while !connected && remaining_trials > 0
|
117
|
+
remaining_trials -= 1
|
118
|
+
if @context[:ssh_keyfile] != nil
|
119
|
+
begin
|
120
|
+
@context[:remote_command_handler].connect_with_keyfile(@context[:dns_name], @context[:ssh_keyfile])
|
121
|
+
connected = true
|
122
|
+
rescue Exception => e
|
123
|
+
@logger.info("connection failed due to #{e}")
|
124
|
+
end
|
125
|
+
elsif @context[:ssh_keydata] != nil
|
126
|
+
begin
|
127
|
+
@context[:remote_command_handler].connect(@context[:dns_name], "root", @context[:ssh_keydata])
|
128
|
+
connected = true
|
129
|
+
rescue Exception => e
|
130
|
+
@logger.info("connection failed due to #{e}")
|
131
|
+
end
|
132
|
+
else
|
133
|
+
raise Exception.new("no key information specified")
|
134
|
+
end
|
135
|
+
if !connected
|
136
|
+
sleep(5) #try again
|
137
|
+
end
|
138
|
+
end
|
139
|
+
@logger.info "connected to #{@context[:dns_name]}"
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
# Nothing done yet. Start by instantiating an AMI (in the right zone?)
|
145
|
+
# which serves to create
|
146
|
+
class InitialState < Ami2EbsConversionState
|
147
|
+
def enter
|
148
|
+
startup_ami()
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def startup_ami()
|
154
|
+
@logger.debug "start up AMI #{@context[:ami_id]}"
|
155
|
+
res = @context[:ec2_api_handler].run_instances(:image_id => @context[:ami_id],
|
156
|
+
:security_group => @context[:security_group_name], :key_name => @context[:key_name])
|
157
|
+
puts "res = #{res.inspect}"#TODO:remove
|
158
|
+
instance_id = res['instancesSet']['item'][0]['instanceId']
|
159
|
+
@context[:instance_id] = instance_id
|
160
|
+
@logger.info "started instance #{instance_id}"
|
161
|
+
#availability_zone , key_name/group_name
|
162
|
+
started = false
|
163
|
+
while started == false
|
164
|
+
sleep(5)
|
165
|
+
res = @context[:ec2_api_handler].describe_instances(:instance_id => @context[:instance_id])
|
166
|
+
state = res['reservationSet']['item'][0]['instancesSet']['item'][0]['instanceState']
|
167
|
+
@logger.info "instance in state #{state['name']} (#{state['code']})"
|
168
|
+
if state['code'].to_i == 16
|
169
|
+
started = true
|
170
|
+
@context[:dns_name] = res['reservationSet']['item'][0]['instancesSet']['item'][0]['dnsName']
|
171
|
+
@context[:availability_zone] = res['reservationSet']['item'][0]['instancesSet']['item'][0]['placement']['availabilityZone']
|
172
|
+
@context[:kernel_id] = res['reservationSet']['item'][0]['instancesSet']['item'][0]['kernelId']
|
173
|
+
@context[:ramdisk_id] = res['reservationSet']['item'][0]['instancesSet']['item'][0]['ramdiskId']
|
174
|
+
@context[:architecture] = res['reservationSet']['item'][0]['instancesSet']['item'][0]['architecture']
|
175
|
+
elsif state['code'].to_i != 0
|
176
|
+
raise Exception.new('instance failed to start up')
|
177
|
+
end
|
178
|
+
end
|
179
|
+
AmiStarted.new(@context)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Ami started. Create a storage
|
184
|
+
class AmiStarted < Ami2EbsConversionState
|
185
|
+
def enter
|
186
|
+
create_storage()
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
|
191
|
+
def create_storage()
|
192
|
+
@logger.debug "create volume in zone #{@context[:availability_zone]}"
|
193
|
+
res = @context[:ec2_api_handler].create_volume(:availability_zone => @context[:availability_zone], :size => "10")
|
194
|
+
@context[:volume_id] = res['volumeId']
|
195
|
+
started = false
|
196
|
+
while !started
|
197
|
+
sleep(5)
|
198
|
+
#TODO: check for timeout?
|
199
|
+
res = @context[:ec2_api_handler].describe_volumes(:volume_id => @context[:volume_id])
|
200
|
+
state = res['volumeSet']['item'][0]['status']
|
201
|
+
@logger.debug "volume state #{state}"
|
202
|
+
if state == 'available'
|
203
|
+
started = true
|
204
|
+
end
|
205
|
+
end
|
206
|
+
StorageCreated.new(@context)
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
# Storage created. Attach it.
|
212
|
+
class StorageCreated < Ami2EbsConversionState
|
213
|
+
def enter
|
214
|
+
attach_storage()
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
|
219
|
+
def attach_storage()
|
220
|
+
@logger.debug "attach volume #{@context[:volume_id]} to instance #{@context[:instance_id]} on device #{@context[:temp_device_name]}"
|
221
|
+
@context[:ec2_api_handler].attach_volume(:volume_id => @context[:volume_id],
|
222
|
+
:instance_id => @context[:instance_id],
|
223
|
+
:device => @context[:temp_device_name]
|
224
|
+
)
|
225
|
+
done = false
|
226
|
+
while !done
|
227
|
+
sleep(5)
|
228
|
+
#TODO: check for timeout?
|
229
|
+
res = @context[:ec2_api_handler].describe_volumes(:volume_id => @context[:volume_id])
|
230
|
+
state = res['volumeSet']['item'][0]['status']
|
231
|
+
@logger.debug "storage attaching: #{state}"
|
232
|
+
if state == 'in-use'
|
233
|
+
done = true
|
234
|
+
end
|
235
|
+
end
|
236
|
+
StorageAttached.new(@context)
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
# Storage attached. Create a file-system and moun it
|
242
|
+
class StorageAttached < Ami2EbsConversionState
|
243
|
+
def enter
|
244
|
+
create_fs()
|
245
|
+
end
|
246
|
+
|
247
|
+
private
|
248
|
+
|
249
|
+
def create_fs()
|
250
|
+
@logger.debug "create filesystem on #{@context[:dns_name]} to #{@context[:temp_device_name]}"
|
251
|
+
connect()
|
252
|
+
@context[:remote_command_handler].create_filesystem("ext3", @context[:temp_device_name])
|
253
|
+
FileSystemCreated.new(@context)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# File system created. Mount it.
|
258
|
+
class FileSystemCreated < Ami2EbsConversionState
|
259
|
+
def enter
|
260
|
+
mount_fs()
|
261
|
+
end
|
262
|
+
|
263
|
+
private
|
264
|
+
|
265
|
+
def mount_fs()
|
266
|
+
@context[:path] = "/mnt/tmp_#{@context[:volume_id]}"
|
267
|
+
@logger.debug "mount #{@context[:temp_device_name]} on #{@context[:path]}"
|
268
|
+
@context[:remote_command_handler].mkdir(@context[:path])
|
269
|
+
@context[:remote_command_handler].mount(@context[:temp_device_name], @context[:path])
|
270
|
+
sleep(2) #give mount some time
|
271
|
+
if !@context[:remote_command_handler].drive_mounted?(@context[:path])
|
272
|
+
raise Exception.new("drive #{@context[:path]} not mounted")
|
273
|
+
end
|
274
|
+
FileSystemMounted.new(@context)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# File system created and mounted. Copy the root partition.
|
279
|
+
class FileSystemMounted < Ami2EbsConversionState
|
280
|
+
def enter
|
281
|
+
copy()
|
282
|
+
end
|
283
|
+
|
284
|
+
private
|
285
|
+
|
286
|
+
def copy()
|
287
|
+
@logger.debug "start copying to #{@context[:path]}"
|
288
|
+
start = Time.new.to_i
|
289
|
+
@context[:remote_command_handler].rsync("/", "#{@context[:path]}")
|
290
|
+
@context[:remote_command_handler].rsync("/dev/", "#{@context[:path]}/dev/")
|
291
|
+
endtime = Time.new.to_i
|
292
|
+
@logger.info "copy took #{(endtime-start)}s"
|
293
|
+
CopyDone.new(@context)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Copy operation done. Unmount volume.
|
298
|
+
class CopyDone < Ami2EbsConversionState
|
299
|
+
def enter
|
300
|
+
unmount()
|
301
|
+
end
|
302
|
+
|
303
|
+
private
|
304
|
+
|
305
|
+
def unmount()
|
306
|
+
@logger.debug "unmount #{@context[:path]}"
|
307
|
+
@context[:remote_command_handler].umount(@context[:path])
|
308
|
+
sleep(2) #give umount some time
|
309
|
+
if @context[:remote_command_handler].drive_mounted?(@context[:path])
|
310
|
+
raise Exception.new("drive #{@context[:path]} not unmounted")
|
311
|
+
end
|
312
|
+
VolumeUnmounted.new(@context)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# Volume unmounted. Detach it.
|
317
|
+
class VolumeUnmounted < Ami2EbsConversionState
|
318
|
+
def enter
|
319
|
+
detach()
|
320
|
+
end
|
321
|
+
|
322
|
+
private
|
323
|
+
|
324
|
+
def detach()
|
325
|
+
@logger.debug "detach volume #{@context[:volume_id]}"
|
326
|
+
@context[:ec2_api_handler].detach_volume(:volume_id => @context[:volume_id],
|
327
|
+
:instance_id => @context[:instance_id]
|
328
|
+
)
|
329
|
+
done = false
|
330
|
+
while !done
|
331
|
+
sleep(3)
|
332
|
+
#TODO: check for timeout?
|
333
|
+
res = @context[:ec2_api_handler].describe_volumes(:volume_id => @context[:volume_id])
|
334
|
+
@logger.debug "volume detaching: #{res.inspect}"
|
335
|
+
if res['volumeSet']['item'][0]['status'] == 'available'
|
336
|
+
done = true
|
337
|
+
end
|
338
|
+
end
|
339
|
+
VolumeDetached.new(@context)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
|
344
|
+
# VolumeDetached. Create snaphot
|
345
|
+
class VolumeDetached < Ami2EbsConversionState
|
346
|
+
def enter
|
347
|
+
create_snapshot()
|
348
|
+
end
|
349
|
+
|
350
|
+
private
|
351
|
+
|
352
|
+
def create_snapshot()
|
353
|
+
@logger.debug "create snapshot for volume #{@context[:volume_id]}"
|
354
|
+
res = @context[:ec2_api_handler].create_snapshot(:volume_id => @context[:volume_id])
|
355
|
+
@context[:snapshot_id] = res['snapshotId']
|
356
|
+
@logger.info "snapshot_id = #{@context[:snapshot_id]}"
|
357
|
+
done = false
|
358
|
+
while !done
|
359
|
+
sleep(5)
|
360
|
+
#TODO: check for timeout?
|
361
|
+
res = @context[:ec2_api_handler].describe_snapshots(:snapshot_id => @context[:snapshot_id])
|
362
|
+
@logger.debug "snapshot creating: #{res.inspect}"
|
363
|
+
if res['snapshotSet']['item'][0]['status'] == 'completed'
|
364
|
+
done = true
|
365
|
+
end
|
366
|
+
end
|
367
|
+
SnapshotCreated.new(@context)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
# Snapshot created. Delete volume.
|
372
|
+
class SnapshotCreated < Ami2EbsConversionState
|
373
|
+
def enter
|
374
|
+
delete_volume()
|
375
|
+
end
|
376
|
+
|
377
|
+
private
|
378
|
+
|
379
|
+
def delete_volume
|
380
|
+
@logger.debug "delete volume #{@context[:volume_id]}"
|
381
|
+
res = @context[:ec2_api_handler].delete_volume(:volume_id => @context[:volume_id])
|
382
|
+
VolumeDeleted.new(@context)
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
# Volume deleted. Register snapshot.
|
387
|
+
class VolumeDeleted < Ami2EbsConversionState
|
388
|
+
def enter
|
389
|
+
register()
|
390
|
+
end
|
391
|
+
|
392
|
+
private
|
393
|
+
|
394
|
+
def register()
|
395
|
+
@logger.debug "register snapshot #{@context[:snapshot_id]} as #{@context[:name]}"
|
396
|
+
res = @context[:ec2_api_handler].register_image_updated(:snapshot_id => @context[:snapshot_id],
|
397
|
+
:kernel_id => @context[:kernel_id], :architecture => @context[:architecture],
|
398
|
+
:root_device_name => @context[:root_device_name],
|
399
|
+
:description => @context[:description], :name => @context[:name],
|
400
|
+
:ramdisk_id => @context[:ramdisk_id]
|
401
|
+
)
|
402
|
+
@logger.debug "result of registration = #{res.inspect}"
|
403
|
+
@context[:image_id] = res['imageId']
|
404
|
+
@logger.info "resulting image_id = #{@context[:image_id]}"
|
405
|
+
SnapshotRegistered.new(@context)
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
# Snapshot registered. Shutdown instance.
|
410
|
+
class SnapshotRegistered < Ami2EbsConversionState
|
411
|
+
def enter
|
412
|
+
shut_down()
|
413
|
+
end
|
414
|
+
|
415
|
+
private
|
416
|
+
|
417
|
+
def shut_down()
|
418
|
+
@logger.debug "shutdown instance #{@context[:instance_id]}"
|
419
|
+
res = @context[:ec2_api_handler].terminate_instances(:instance_id => @context[:instance_id])
|
420
|
+
done = false
|
421
|
+
while done == false
|
422
|
+
sleep(5)
|
423
|
+
res = @context[:ec2_api_handler].describe_instances(:instance_id => @context[:instance_id])
|
424
|
+
state = res['reservationSet']['item'][0]['instancesSet']['item'][0]['instanceState']
|
425
|
+
@logger.debug "instance in state #{state['name']} (#{state['code']})"
|
426
|
+
if state['code'].to_i == 48
|
427
|
+
done = true
|
428
|
+
elsif state['code'].to_i != 32
|
429
|
+
raise Exception.new('instance failed to shut down')
|
430
|
+
end
|
431
|
+
end
|
432
|
+
Done.new(@context)
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
# Instance shutdown. Done.
|
437
|
+
class Done < Ami2EbsConversionState
|
438
|
+
def done?
|
439
|
+
true
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
end
|
@@ -54,8 +54,8 @@ class DmEncrypt < Ec2Script
|
|
54
54
|
@result[:failed] = false
|
55
55
|
end
|
56
56
|
rescue Exception => e
|
57
|
-
|
58
|
-
|
57
|
+
@logger.warn "exception during encryption: #{e}"
|
58
|
+
@logger.info e.backtrace.join("\n")
|
59
59
|
err = e.to_s
|
60
60
|
err += " (in #{current_state.end_state.to_s})" unless current_state == nil
|
61
61
|
@result[:failed] = true
|
@@ -65,7 +65,6 @@ class DmEncrypt < Ec2Script
|
|
65
65
|
begin
|
66
66
|
@input_params[:remote_command_handler].disconnect
|
67
67
|
rescue Exception => e2
|
68
|
-
puts "rescue disconnect: #{e2}"
|
69
68
|
end
|
70
69
|
end
|
71
70
|
|
@@ -99,9 +98,9 @@ class DmEncrypt < Ec2Script
|
|
99
98
|
private
|
100
99
|
|
101
100
|
def connect()
|
102
|
-
|
101
|
+
@logger.debug "InitialState.connect"
|
103
102
|
if @context[:ssh_key_file] != nil
|
104
|
-
@context[:remote_command_handler].
|
103
|
+
@context[:remote_command_handler].connect_with_keyfile(@context[:ip_address], @context[:ssh_key_file])
|
105
104
|
elsif @context[:ssh_key_data] != nil
|
106
105
|
@context[:remote_command_handler].connect(@context[:ip_address], "root", @context[:ssh_key_data])
|
107
106
|
else
|
@@ -119,12 +118,12 @@ class DmEncrypt < Ec2Script
|
|
119
118
|
|
120
119
|
private
|
121
120
|
def install_tools
|
122
|
-
|
121
|
+
@logger.debug "ConnectedState.install_tools"
|
123
122
|
if !tools_installed?
|
124
123
|
@context[:remote_command_handler].install("dm-crypt") #TODO: constant somewhere? admin parameter?
|
125
124
|
end
|
126
125
|
if tools_installed?
|
127
|
-
|
126
|
+
@logger.debug "system says that tools are installed"
|
128
127
|
ToolInstalledState.new(@context)
|
129
128
|
else
|
130
129
|
FailedState.new(@context, "Installation of Tools failed", ConnectedState.new(@context))
|
@@ -148,7 +147,7 @@ class DmEncrypt < Ec2Script
|
|
148
147
|
|
149
148
|
private
|
150
149
|
def create_encrypted_volume
|
151
|
-
|
150
|
+
@logger.debug "ToolInstalledState.create_encrypted_volume"
|
152
151
|
#first check if the drive is not yet mounted by someone else
|
153
152
|
if @context[:remote_command_handler].drive_mounted?(@context[:storage_path])
|
154
153
|
if !@context[:remote_command_handler].drive_mounted_as?(calc_device_name(), @context[:storage_path])
|
@@ -176,7 +175,7 @@ class DmEncrypt < Ec2Script
|
|
176
175
|
|
177
176
|
private
|
178
177
|
def mount_and_activate
|
179
|
-
|
178
|
+
@logger.debug "VolumeCreatedState.mount_and_activate"
|
180
179
|
@context[:remote_command_handler].activate_encrypted_volume(@context[:device_name],@context[:storage_path])
|
181
180
|
MountedAndActivatedState.new(@context)
|
182
181
|
end
|
@@ -190,7 +189,7 @@ class DmEncrypt < Ec2Script
|
|
190
189
|
|
191
190
|
private
|
192
191
|
def cleanup()
|
193
|
-
|
192
|
+
@logger.debug "MountedAndActivatedState.cleanup"
|
194
193
|
@context[:remote_command_handler].disconnect()
|
195
194
|
DoneState.new(@context)
|
196
195
|
end
|
@@ -4,16 +4,25 @@ class Ec2Script
|
|
4
4
|
# * aws_access_key => the Amazon AWS Access Key (see Your Account -> Security Credentials)
|
5
5
|
# * aws_secret_key => the Amazon AWS Secret Key
|
6
6
|
# * ec2_api_server => the API Server to connect to (optional, default is us-east-1 (=> <ec2_api_server>.ec2.amazonaws.com)
|
7
|
+
# * logger => allows to pass a ruby logger object used for logging (optional, default is a stdout logger with level WARN)
|
7
8
|
# Scripts may add specific key/value pairs.
|
8
9
|
def initialize(input_params)
|
9
10
|
@input_params = input_params
|
10
11
|
@state_change_listeners = []
|
12
|
+
if input_params[:logger] == nil
|
13
|
+
@logger = Logger.new(STDOUT)
|
14
|
+
@logger .level = Logger::WARN
|
15
|
+
input_params[:logger] = @logger
|
16
|
+
end
|
11
17
|
end
|
12
18
|
|
13
19
|
def register_state_change_listener(listener)
|
14
20
|
@state_change_listeners << listener
|
15
21
|
end
|
16
22
|
|
23
|
+
def start_script
|
24
|
+
raise Exception.new("must be implemented")
|
25
|
+
end
|
17
26
|
|
18
27
|
# Return a hash of results. Common values are:
|
19
28
|
# * :done => is true when the script has terminated, otherwise false
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: CloudyScripts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthias Jung
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-01-
|
12
|
+
date: 2010-01-07 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -50,6 +50,7 @@ files:
|
|
50
50
|
- lib/help/remote_command_handler.rb
|
51
51
|
- lib/help/script_execution_state.rb
|
52
52
|
- lib/help/state_change_listener.rb
|
53
|
+
- lib/scripts/ec2/ami2_ebs_conversion.rb
|
53
54
|
- lib/scripts/ec2/dm_encrypt.rb
|
54
55
|
- lib/scripts/ec2/ec2_script.rb
|
55
56
|
has_rdoc: true
|