CloudyScripts 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|