CloudyScripts 0.0.8 → 0.0.9
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/LICENSE +7 -7
- data/README.rdoc +27 -27
- data/Rakefile +50 -50
- data/lib/cloudyscripts.rb +26 -26
- data/lib/help/dm_crypt_helper.rb +204 -204
- data/lib/help/ec2_helper.rb +54 -54
- data/lib/help/remote_command_handler.rb +203 -190
- data/lib/help/script_execution_state.rb +97 -95
- data/lib/help/state_change_listener.rb +13 -13
- data/lib/scripts/ec2/ami2_ebs_conversion.rb +446 -442
- data/lib/scripts/ec2/dm_encrypt.rb +194 -190
- data/lib/scripts/ec2/ec2_script.rb +41 -41
- metadata +2 -2
data/lib/help/ec2_helper.rb
CHANGED
@@ -1,54 +1,54 @@
|
|
1
|
-
require "AWS"
|
2
|
-
|
3
|
-
# Implements some helper methods around the EC2 API and methods that are
|
4
|
-
# not yet implemented in the amazon-ec2 gem
|
5
|
-
|
6
|
-
class AWS::EC2::Base
|
7
|
-
def describe_instance_attribute(options)
|
8
|
-
params = {}
|
9
|
-
params["InstanceId"] = options[:instance_id].to_s
|
10
|
-
params["Attribute"] = "rootDeviceName" unless options[:attributes][:rootDeviceName] == nil
|
11
|
-
return response_generator(:action => "DescribeInstanceAttribute", :params => params)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
class Ec2Helper
|
16
|
-
# expects an instance of AWS::EC2::Base from the amazon-ec2 gem
|
17
|
-
def initialize(ec2_api)
|
18
|
-
@ec2_api = ec2_api
|
19
|
-
end
|
20
|
-
|
21
|
-
# Checks if the specified volume is acting as a root-device for the instance
|
22
|
-
# to which it is attached. It therefore first calls ec2_describe_volumes() to
|
23
|
-
# retrieve the instance linked to the volume specified, then calls
|
24
|
-
# ec2_describe_instance_attribute() to retrieve the rootDeviceName of that
|
25
|
-
# instance, and finally calls describe_instances() to retrieve all volumes
|
26
|
-
# to check against volume_id and rootDeviceName.
|
27
|
-
def is_root_device?(volume_id)
|
28
|
-
vols = @ec2_api.describe_volumes(:volume_id => volume_id)
|
29
|
-
if vols['volumeSet']['item'][0]['attachmentSet'] == nil || vols['volumeSet']['item'][0]['attachmentSet']['item'].size == 0
|
30
|
-
#not linked to any instance, cannot be a root-device
|
31
|
-
return false
|
32
|
-
end
|
33
|
-
instance_id = vols['volumeSet']['item'][0]['attachmentSet']['item'][0]['instanceId']
|
34
|
-
res = @ec2_api.describe_instance_attribute(:instance_id => instance_id, :attributes => {:rootDeviceName => true})
|
35
|
-
if res["rootDeviceName"] == nil
|
36
|
-
return false
|
37
|
-
end
|
38
|
-
rdn = res['rootDeviceName']['value']
|
39
|
-
res = @ec2_api.describe_instances(:instance_id => instance_id)
|
40
|
-
if res['reservationSet']['item'][0]['instancesSet']['item'][0]['blockDeviceMapping']['item'].size == 0
|
41
|
-
# volume unattached in the meantime
|
42
|
-
return false
|
43
|
-
end
|
44
|
-
attached = res['reservationSet']['item'][0]['instancesSet']['item'][0]['blockDeviceMapping']['item']
|
45
|
-
attached.each() {|ebs|
|
46
|
-
volume = ebs['ebs']['volumeId']
|
47
|
-
device_name = ebs['deviceName']
|
48
|
-
if volume == volume_id && rdn == device_name
|
49
|
-
return true
|
50
|
-
end
|
51
|
-
}
|
52
|
-
return false
|
53
|
-
end
|
54
|
-
end
|
1
|
+
require "AWS"
|
2
|
+
|
3
|
+
# Implements some helper methods around the EC2 API and methods that are
|
4
|
+
# not yet implemented in the amazon-ec2 gem
|
5
|
+
|
6
|
+
class AWS::EC2::Base
|
7
|
+
def describe_instance_attribute(options)
|
8
|
+
params = {}
|
9
|
+
params["InstanceId"] = options[:instance_id].to_s
|
10
|
+
params["Attribute"] = "rootDeviceName" unless options[:attributes][:rootDeviceName] == nil
|
11
|
+
return response_generator(:action => "DescribeInstanceAttribute", :params => params)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Ec2Helper
|
16
|
+
# expects an instance of AWS::EC2::Base from the amazon-ec2 gem
|
17
|
+
def initialize(ec2_api)
|
18
|
+
@ec2_api = ec2_api
|
19
|
+
end
|
20
|
+
|
21
|
+
# Checks if the specified volume is acting as a root-device for the instance
|
22
|
+
# to which it is attached. It therefore first calls ec2_describe_volumes() to
|
23
|
+
# retrieve the instance linked to the volume specified, then calls
|
24
|
+
# ec2_describe_instance_attribute() to retrieve the rootDeviceName of that
|
25
|
+
# instance, and finally calls describe_instances() to retrieve all volumes
|
26
|
+
# to check against volume_id and rootDeviceName.
|
27
|
+
def is_root_device?(volume_id)
|
28
|
+
vols = @ec2_api.describe_volumes(:volume_id => volume_id)
|
29
|
+
if vols['volumeSet']['item'][0]['attachmentSet'] == nil || vols['volumeSet']['item'][0]['attachmentSet']['item'].size == 0
|
30
|
+
#not linked to any instance, cannot be a root-device
|
31
|
+
return false
|
32
|
+
end
|
33
|
+
instance_id = vols['volumeSet']['item'][0]['attachmentSet']['item'][0]['instanceId']
|
34
|
+
res = @ec2_api.describe_instance_attribute(:instance_id => instance_id, :attributes => {:rootDeviceName => true})
|
35
|
+
if res["rootDeviceName"] == nil
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
rdn = res['rootDeviceName']['value']
|
39
|
+
res = @ec2_api.describe_instances(:instance_id => instance_id)
|
40
|
+
if res['reservationSet']['item'][0]['instancesSet']['item'][0]['blockDeviceMapping']['item'].size == 0
|
41
|
+
# volume unattached in the meantime
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
attached = res['reservationSet']['item'][0]['instancesSet']['item'][0]['blockDeviceMapping']['item']
|
45
|
+
attached.each() {|ebs|
|
46
|
+
volume = ebs['ebs']['volumeId']
|
47
|
+
device_name = ebs['deviceName']
|
48
|
+
if volume == volume_id && rdn == device_name
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
}
|
52
|
+
return false
|
53
|
+
end
|
54
|
+
end
|
@@ -1,190 +1,203 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'net/ssh'
|
3
|
-
|
4
|
-
# Provides methods to be executed via ssh to remote instances.
|
5
|
-
class RemoteCommandHandler
|
6
|
-
attr_accessor :logger, :ssh_session
|
7
|
-
def initialize
|
8
|
-
@logger = Logger.new(STDOUT)
|
9
|
-
end
|
10
|
-
|
11
|
-
# Connect to the machine as root using a keyfile.
|
12
|
-
# Params:
|
13
|
-
# * ip: ip address of the machine to connect to
|
14
|
-
# * keyfile: path of the keyfile to be used for authentication
|
15
|
-
def connect_with_keyfile(ip, keyfile)
|
16
|
-
@ssh_session = Net::SSH.start(ip, 'root', :keys => [keyfile])
|
17
|
-
end
|
18
|
-
|
19
|
-
# Connect to the machine as root using keydata from a keyfile.
|
20
|
-
# Params:
|
21
|
-
# * ip: ip address of the machine to connect to
|
22
|
-
# * user: user name
|
23
|
-
# * key_data: key_data to be used for authentication
|
24
|
-
def connect(ip, user, key_data)
|
25
|
-
@ssh_session = Net::SSH.start(ip, user, :key_data => [key_data])
|
26
|
-
end
|
27
|
-
|
28
|
-
# Disconnect the current handler
|
29
|
-
def disconnect
|
30
|
-
@ssh_session.close
|
31
|
-
end
|
32
|
-
|
33
|
-
# Check if the path/file specified exists
|
34
|
-
def file_exists?(path)
|
35
|
-
|
36
|
-
end
|
37
|
-
|
38
|
-
# Returns the result of uname -a (Linux)
|
39
|
-
def retrieve_os()
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
# Installs the software package specified.
|
44
|
-
def install(software_package)
|
45
|
-
e = "yum -yq install #{software_package}
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
e
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
#
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
stderr
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
#
|
135
|
-
#
|
136
|
-
# to respond in advance to commands that ask the user something.
|
137
|
-
#
|
138
|
-
#
|
139
|
-
def
|
140
|
-
exec_string = "echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt" unless push_data == nil
|
141
|
-
stdout = []
|
142
|
-
stderr = []
|
143
|
-
remote_exec_helper(
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
#
|
150
|
-
#
|
151
|
-
#
|
152
|
-
#
|
153
|
-
def
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
ch.
|
173
|
-
|
174
|
-
end
|
175
|
-
ch.
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
1
|
+
require 'rubygems'
|
2
|
+
require 'net/ssh'
|
3
|
+
|
4
|
+
# Provides methods to be executed via ssh to remote instances.
|
5
|
+
class RemoteCommandHandler
|
6
|
+
attr_accessor :logger, :ssh_session
|
7
|
+
def initialize
|
8
|
+
@logger = Logger.new(STDOUT)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Connect to the machine as root using a keyfile.
|
12
|
+
# Params:
|
13
|
+
# * ip: ip address of the machine to connect to
|
14
|
+
# * keyfile: path of the keyfile to be used for authentication
|
15
|
+
def connect_with_keyfile(ip, keyfile)
|
16
|
+
@ssh_session = Net::SSH.start(ip, 'root', :keys => [keyfile])
|
17
|
+
end
|
18
|
+
|
19
|
+
# Connect to the machine as root using keydata from a keyfile.
|
20
|
+
# Params:
|
21
|
+
# * ip: ip address of the machine to connect to
|
22
|
+
# * user: user name
|
23
|
+
# * key_data: key_data to be used for authentication
|
24
|
+
def connect(ip, user, key_data)
|
25
|
+
@ssh_session = Net::SSH.start(ip, user, :key_data => [key_data])
|
26
|
+
end
|
27
|
+
|
28
|
+
# Disconnect the current handler
|
29
|
+
def disconnect
|
30
|
+
@ssh_session.close
|
31
|
+
end
|
32
|
+
|
33
|
+
# Check if the path/file specified exists
|
34
|
+
def file_exists?(path)
|
35
|
+
remote_execute("ls #{path}")
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the result of uname -a (Linux)
|
39
|
+
def retrieve_os()
|
40
|
+
get_output("uname -r").strip
|
41
|
+
end
|
42
|
+
|
43
|
+
# Installs the software package specified.
|
44
|
+
def install(software_package)
|
45
|
+
e = "yum -yq install #{software_package}"
|
46
|
+
yum = remote_execute(e)
|
47
|
+
if !yum
|
48
|
+
@logger.info("yum installation failed; try apt-get")
|
49
|
+
e = "apt-get -yq install #{software_package}"
|
50
|
+
apt = remote_execute(e)
|
51
|
+
@logger.info("apt=get installation? #{apt}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Checks if the software package specified is installed.
|
56
|
+
def tools_installed?(software_package)
|
57
|
+
exec_string = "which #{software_package}"
|
58
|
+
stdout = []
|
59
|
+
stderr = []
|
60
|
+
result = remote_exec_helper(exec_string, stdout, stderr)
|
61
|
+
return result == true && stdout.size > 0
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_filesystem(fs_type, volume)
|
65
|
+
e = "mkfs -t #{fs_type} #{volume}"
|
66
|
+
remote_execute(e, "y") #TODO: quiet mode?
|
67
|
+
end
|
68
|
+
|
69
|
+
def mkdir(path)
|
70
|
+
e = "mkdir #{path}"
|
71
|
+
remote_execute(e, nil, true)
|
72
|
+
end
|
73
|
+
|
74
|
+
def mount(device, path)
|
75
|
+
e = "mount #{device} #{path}"
|
76
|
+
remote_execute(e, nil, true)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Checks if the drive on path is mounted
|
80
|
+
def drive_mounted?(path)
|
81
|
+
#check if drive mounted
|
82
|
+
drive_found = stdout_contains?("mount", "on #{path} type")
|
83
|
+
if drive_found
|
84
|
+
return file_exists?(path)
|
85
|
+
else
|
86
|
+
@logger.debug "not mounted (since #{path} non-existing)"
|
87
|
+
false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Checks if the drive on path is mounted with the specific device
|
92
|
+
def drive_mounted_as?(device, path)
|
93
|
+
#check if drive mounted
|
94
|
+
stdout_contains?("mount", "#{device} on #{path} type")
|
95
|
+
end
|
96
|
+
|
97
|
+
# Unmount the specified path.
|
98
|
+
def umount(path)
|
99
|
+
exec_string = "umount #{path}"
|
100
|
+
remote_execute(exec_string)
|
101
|
+
!drive_mounted?(path)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Copy directory using options -avHx
|
105
|
+
def rsync(source_path, dest_path, exclude_path = nil)
|
106
|
+
exclude = ""
|
107
|
+
if exclude_path != nil
|
108
|
+
exclude = "--exclude #{exclude_path}"
|
109
|
+
end
|
110
|
+
e = "rsync -avHx #{exclude} #{source_path} #{dest_path}"
|
111
|
+
@logger.debug "going to execute #{e}"
|
112
|
+
remote_exec_helper(e, nil, nil, false)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Executes the specified #exec_string on a remote session specified.
|
116
|
+
# When #push_data is specified, the data will be used as input for the
|
117
|
+
# command and thus allow to respond in advance to commands that ask the user
|
118
|
+
# something.
|
119
|
+
# The method will return true if nothing was written into stderr, otherwise false.
|
120
|
+
# When #raise_exception is set, an exception will be raised instead of
|
121
|
+
# returning false.
|
122
|
+
def remote_execute(exec_string, push_data = nil, raise_exception = false)
|
123
|
+
exec_string = "echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt" unless push_data == nil
|
124
|
+
stdout = []
|
125
|
+
stderr = []
|
126
|
+
result = remote_exec_helper(exec_string, stdout, stderr)
|
127
|
+
em = "RemoteCommandHandler: #{exec_string} lead to stderr message: #{stderr.join().strip}"
|
128
|
+
@logger.info(em) unless stderr.size == 0
|
129
|
+
raise Exception.new(em) unless result == true || raise_exception == false
|
130
|
+
result
|
131
|
+
end
|
132
|
+
|
133
|
+
# Executes the specified #exec_string on a remote session specified as #ssh_session
|
134
|
+
# and logs the command-output into the specified #logger. When #push_data is
|
135
|
+
# specified, the data will be used as input for the command and thus allows
|
136
|
+
# to respond in advance to commands that ask the user something. If the output
|
137
|
+
# in stdout contains the specified #search_string, the method returns true
|
138
|
+
# otherwise false. Output to stderr will be logged.
|
139
|
+
def stdout_contains?(exec_string, search_string = "", push_data = nil)
|
140
|
+
exec_string = "echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt" unless push_data == nil
|
141
|
+
stdout = []
|
142
|
+
stderr = []
|
143
|
+
remote_exec_helper(exec_string, stdout, stderr)
|
144
|
+
@logger.info("RemoteCommandHandler: #{exec_string} lead to stderr message: #{stderr.join().strip}") unless stderr.size == 0
|
145
|
+
stdout.join().include?(search_string)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Executes the specified #exec_string on a remote session specified as #ssh_session.
|
149
|
+
# When #push_data is specified, the data will be used as input for the command and thus allows
|
150
|
+
# to respond in advance to commands that ask the user something. It returns
|
151
|
+
# stdout. When #stdout or #stderr is specified as arrays, the respective output is
|
152
|
+
# also written into those arrays.
|
153
|
+
def get_output(exec_string, push_data = nil, stdout = [], stderr = [])
|
154
|
+
exec_string = "echo #{push_data} >tmp.txt; #{exec_string} <tmp.txt; rm -f tmp.txt" unless push_data == nil
|
155
|
+
stdout = []
|
156
|
+
stderr = []
|
157
|
+
remote_exec_helper(exec_string, stdout, stderr)
|
158
|
+
stdout.join()
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
# Executes the specified #exec_string on the opened remote session.
|
164
|
+
# The method will return true if nothing was written into stderr, otherwise false.
|
165
|
+
# All stdout-data is written into #stdout, all stderr-data is written into #stderr
|
166
|
+
def remote_exec_helper(exec_string, stdout = [], stderr = [], debug = false)
|
167
|
+
result = true
|
168
|
+
the_channel = @ssh_session.open_channel do |channel|
|
169
|
+
channel.exec(exec_string) do |ch, success|
|
170
|
+
if success
|
171
|
+
@logger.debug("RemoteCommandHandler: starts executing #{exec_string}") if debug
|
172
|
+
ch.on_data() do |ch, data|
|
173
|
+
stdout << data unless data == nil || stdout == nil
|
174
|
+
end
|
175
|
+
ch.on_extended_data do |ch, type, data|
|
176
|
+
stderr << data unless data == nil || stderr == nil
|
177
|
+
result = false
|
178
|
+
end
|
179
|
+
ch.on_eof do |ch|
|
180
|
+
@logger.debug("RemoteCommandHandler.on_eof:remote end is done sending data") if debug
|
181
|
+
end
|
182
|
+
ch.on_close do |ch|
|
183
|
+
@logger.debug("RemoteCommandHandler.on_close:remote end is closing!") if debug
|
184
|
+
end
|
185
|
+
ch.on_open_failed do |ch, code, desc|
|
186
|
+
@logger.debug("RemoteCommandHandler.on_open_failed: code=#{code} desc=#{desc}") if debug
|
187
|
+
end
|
188
|
+
ch.on_process do |ch|
|
189
|
+
@logger.debug("RemoteCommandHandler.on_process; send line-feed/sleep") if debug
|
190
|
+
sleep(1)
|
191
|
+
ch.send_data("\n")
|
192
|
+
end
|
193
|
+
else
|
194
|
+
stderr << "the remote command could not be invoked!" unless stderr == nil
|
195
|
+
result = false
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
the_channel.wait
|
200
|
+
result
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|