rightimage_tools 0.2.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -1
- data/Rakefile +4 -23
- data/VERSION +1 -1
- data/bin/azure_api +31 -0
- data/bin/azure_migrate +174 -0
- data/bin/azure_publish +213 -0
- data/bin/image_mover +2 -2
- data/bin/mci_merge +10 -4
- data/bin/mci_report +232 -0
- data/bin/mcicp +52 -0
- data/bin/report_tool +404 -0
- data/bin/update_mcis +65 -0
- data/bin/update_s3_index +30 -4
- data/lib/azure_helper.rb +90 -0
- data/lib/mci.rb +95 -71
- data/lib/s3_html_indexer.rb +171 -52
- data/lib/util.rb +8 -7
- data/spec/mci_spec.rb +4 -10
- data/ui/build/empty_bucket_index.html +81 -0
- data/ui/build/index-to.html +241 -0
- data/ui/build/report_viewer.html +397 -0
- metadata +68 -42
data/bin/update_mcis
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require "rest_connection"
|
5
|
+
|
6
|
+
# Set this to key in the st_collection that matches the ID
|
7
|
+
# of the ServerTemplate you want to replicated MCIs from.
|
8
|
+
SOURCE_KEY = :base
|
9
|
+
|
10
|
+
# Collection of all ServerTemplate IDs to update
|
11
|
+
# Staging
|
12
|
+
st_collection = {
|
13
|
+
:base_rsb => 243993001,
|
14
|
+
:lamp_rsb => 243995001,
|
15
|
+
:base => 243994001,
|
16
|
+
:lamp_mysql_5_1 => 243996001,
|
17
|
+
:lamp_mysql_5_5 => 243997001,
|
18
|
+
:lamp_trial => 243998001,
|
19
|
+
:load_balancer => 243999001,
|
20
|
+
:php_appserver => 244000001,
|
21
|
+
:rails_passenger => 244001001,
|
22
|
+
:tomcat_app => 244002001,
|
23
|
+
:db_mysql_5_1 => 244003001,
|
24
|
+
:db_mysql_5_5 => 244004001,
|
25
|
+
:storage_toolbox => 244005001,
|
26
|
+
:sys_dns => 237966001
|
27
|
+
}
|
28
|
+
|
29
|
+
# Production (v13)
|
30
|
+
#st_collection = {
|
31
|
+
# :base_rsb => 91124,
|
32
|
+
# :lamp_rsb => 129529,
|
33
|
+
# :base => 104181,
|
34
|
+
# :lamp_mysql_5_1 => 106233,
|
35
|
+
# :lamp_mysql_5_5 => 226676001,
|
36
|
+
# :lamp_trial => 226834001,
|
37
|
+
# :load_balancer => 120077,
|
38
|
+
# :php_appserver => 27958,
|
39
|
+
# :rails_passenger => 139492,
|
40
|
+
# :tomcat_app => 122208,
|
41
|
+
# :db_mysql_5_1 => 107666,
|
42
|
+
# :db_mysql_5_5 => 214130001,
|
43
|
+
# :storage_toolbox => 104346
|
44
|
+
#}
|
45
|
+
|
46
|
+
# Display Mapping (useful for verifying collection is okay)
|
47
|
+
#st_collection.each { |k,v| puts "\n\n#{k} == #{ServerTemplate.find(v).nickname}\n\n\n" }
|
48
|
+
|
49
|
+
puts "Rest_connection log set to /tmp/rc.log"
|
50
|
+
ENV['REST_CONNECTION_LOG'] = "/tmp/rc.log"
|
51
|
+
|
52
|
+
# Run mcicp from SOURCE_KEY to collection of servertemplates
|
53
|
+
src_id = st_collection[SOURCE_KEY]
|
54
|
+
st_collection.each do |dst_key, dst_id|
|
55
|
+
next if dst_id == src_id
|
56
|
+
puts '-'*25
|
57
|
+
puts "Copying MCIs from #{SOURCE_KEY} to #{dst_key}"
|
58
|
+
skip_param = dst_key.to_s =~ /mysql_5_5/ ? "--skip Ubuntu" : ""
|
59
|
+
bin = File.expand_path("../mcicp",__FILE__)
|
60
|
+
cmd = "#{bin} --from #{src_id} --to #{dst_id} #{skip_param}"
|
61
|
+
puts cmd
|
62
|
+
puts `#{cmd}`
|
63
|
+
puts '-'*25
|
64
|
+
end
|
65
|
+
|
data/bin/update_s3_index
CHANGED
@@ -1,12 +1,38 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
|
3
4
|
THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
|
4
5
|
require File.expand_path(File.join(File.dirname(THIS_FILE), '..', 'lib', 's3_html_indexer'))
|
5
6
|
|
6
|
-
raise "ERROR: you must define AWS_IMAGE_BUCKET in you environment. " unless ENV["AWS_IMAGE_BUCKET"]
|
7
7
|
raise "ERROR: you must define AWS_ACCESS_KEY_ID in you environment. " unless ENV["AWS_ACCESS_KEY_ID"]
|
8
8
|
raise "ERROR: you must define AWS_SECRET_ACCESS_KEY in you environment. " unless ENV["AWS_SECRET_ACCESS_KEY"]
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
aws_image_bucket = ARGV.shift || ENV["AWS_IMAGE_BUCKET"]
|
11
|
+
raise "ERROR: you must pass in the bucket to generate an index for. pass in all to generate index for all buckets" unless aws_image_bucket
|
12
|
+
|
13
|
+
if aws_image_bucket.downcase == "all"
|
14
|
+
buckets = %w(
|
15
|
+
rightscale-rightimage-base
|
16
|
+
rightscale-rightimage-base-dev
|
17
|
+
rightscale-cloudstack
|
18
|
+
rightscale-cloudstack-dev
|
19
|
+
rightscale-openstack
|
20
|
+
rightscale-openstack-dev
|
21
|
+
rightscale-eucalyptus
|
22
|
+
rightscale-eucalyptus-dev
|
23
|
+
rightscale-ec2
|
24
|
+
rightscale-ec2-dev
|
25
|
+
rightscale-google
|
26
|
+
rightscale-google-dev
|
27
|
+
rightscale-azure
|
28
|
+
rightscale-azure-dev
|
29
|
+
)
|
30
|
+
else
|
31
|
+
buckets = [aws_image_bucket]
|
32
|
+
end
|
33
|
+
|
34
|
+
buckets.each do |b|
|
35
|
+
indexer = RightImageTools::S3HtmlIndexer.new(b, ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_ACCESS_KEY"])
|
36
|
+
indexer.to_html("index.html")
|
37
|
+
indexer.upload_index("index.html")
|
38
|
+
end
|
data/lib/azure_helper.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
module AzureHelper
|
2
|
+
RS_LINUX_ACCOUNTS = %w(
|
3
|
+
rightscalewestus
|
4
|
+
rightscaleeastus
|
5
|
+
rightscaleeastasia
|
6
|
+
rightscalesoutheastasia
|
7
|
+
rightscalenortheurope
|
8
|
+
rightscalewesteurope
|
9
|
+
)
|
10
|
+
RS_WINDOWS_ACCOUNTS = %w(
|
11
|
+
rightscalewestuswin
|
12
|
+
rightscaleeastuswin
|
13
|
+
rightscaleeastasiawin
|
14
|
+
rightscaleseasiawin
|
15
|
+
rightscalenortheuropewin
|
16
|
+
rightscalewesteuropewin
|
17
|
+
)
|
18
|
+
AZURE_LINUX_LOCATIONS = {
|
19
|
+
'rightscalewesteurope' => 'West Europe',
|
20
|
+
'rightscalesoutheastasia' => 'Southeast Asia',
|
21
|
+
'rightscaleeastasia' => 'East Asia',
|
22
|
+
'rightscalenortheurope' => 'North Europe',
|
23
|
+
'rightscalewestus' => 'West US',
|
24
|
+
'rightscaleeastus' => 'East US'
|
25
|
+
}
|
26
|
+
# AZURE_WINDOWS_LOCATIONS = {
|
27
|
+
# 'westeuropewin' => 'West Europe',
|
28
|
+
# 'seasiawin' => 'Southeast Asia',
|
29
|
+
# 'eastasiawin' => 'East Asia',
|
30
|
+
# 'northeuropewin' => 'North Europe',
|
31
|
+
# 'westuswin' => 'West US',
|
32
|
+
# 'eastuswin' => 'East US'
|
33
|
+
# }
|
34
|
+
def api_dump(api, method_name, *args)
|
35
|
+
result = api.__send__(method_name, *args)
|
36
|
+
ensure
|
37
|
+
puts "--- #{method_name} ---"
|
38
|
+
if api.request
|
39
|
+
puts "verb: #{api.request.verb}"
|
40
|
+
puts "path: #{api.request.path.inspect}"
|
41
|
+
puts "req headers: #{api.request.headers.inspect}"
|
42
|
+
puts "req body: #{api.request.body.inspect}"
|
43
|
+
puts " "
|
44
|
+
if api.response
|
45
|
+
puts "resp code: #{api.response.code.inspect}"
|
46
|
+
puts "resp headers: #{api.response.headers.inspect}"
|
47
|
+
puts "resp body: #{api.response.body.inspect}"
|
48
|
+
puts " "
|
49
|
+
pp result
|
50
|
+
end
|
51
|
+
puts "==========================================================================="
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def lookup_password(account)
|
56
|
+
credsfile = "~/.azurecreds.yml"
|
57
|
+
creds_msg = <<-EOF
|
58
|
+
Please set up a #{credsfile} file in the following format:
|
59
|
+
<service account name1>: <shared key2>
|
60
|
+
<service account name1>: <shared key2>
|
61
|
+
EOF
|
62
|
+
begin
|
63
|
+
creds = YAML.load_file(File.expand_path(credsfile))
|
64
|
+
rescue
|
65
|
+
puts creds_msg
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
password = creds[account]
|
69
|
+
unless password
|
70
|
+
puts "Can not find creds for account #{account} in file"
|
71
|
+
puts creds_msg
|
72
|
+
exit 1
|
73
|
+
end
|
74
|
+
password
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse_url(url)
|
78
|
+
uri = URI.parse(url)
|
79
|
+
path = uri.path.reverse.chomp("/").reverse
|
80
|
+
(container, blob) = path.split("/",2)
|
81
|
+
account = uri.host.split(".").first
|
82
|
+
# puts "ACCOUNT: #{account}"
|
83
|
+
# puts "CONTAINER: #{container}"
|
84
|
+
# puts "BLOB NAME: #{blob}"
|
85
|
+
|
86
|
+
password = lookup_password(account)
|
87
|
+
endpoint = "https://#{account}.blob.core.windows.net"
|
88
|
+
return [account,password,endpoint,container,blob]
|
89
|
+
end
|
90
|
+
end
|
data/lib/mci.rb
CHANGED
@@ -46,27 +46,30 @@ module RightImageTools
|
|
46
46
|
# Adds an image to an mci, if the image is not already attached to the mci
|
47
47
|
#
|
48
48
|
# === Parameters
|
49
|
-
# image_id(String)
|
50
|
-
#
|
51
|
-
#
|
49
|
+
# image_id(String) - rightscale id of the image to add, which will equal
|
50
|
+
# the ami-id for amazon and the resource_uid for other clouds
|
51
|
+
# cloud_id(Number) - rightscale if of the cloud
|
52
|
+
# mci_name(String) - name of the mci to add this image to
|
53
|
+
# mci_id(String) - or (optionally) the rightscale id to add this image to
|
54
|
+
# instance_type - default instance type of the image, if not supplied
|
55
|
+
# can usually guess
|
52
56
|
#
|
53
57
|
# === Return
|
54
58
|
# mci(MultiCloudImageCloudSettingInternal):: new or existing MCI setting
|
55
59
|
def add_image_to_mci(options)
|
56
|
-
cloud_id = options[:cloud_id].to_i
|
57
|
-
image_id = options[:image_id]
|
60
|
+
cloud_id = options[:cloud_id].to_i
|
61
|
+
image_id = options[:image_id].to_s
|
58
62
|
mci_name = options[:name] || options[:mci_name]
|
59
63
|
mci_id = options[:mci_id] || options[:id]
|
60
64
|
description = options[:description]
|
61
65
|
|
62
66
|
raise ArgumentError, ":cloud_id not supplied" unless cloud_id > 0
|
63
67
|
raise ArgumentError, ":image_id not supplied" unless image_id =~ /./
|
64
|
-
raise ArgumentError, "MCI name (:name) or id (:mci_id) not supplied" unless (mci_name =~ /./ || mci_id =~ /^\d+$/)
|
65
|
-
raise ArgumentError, "MCI (:mci_id) supplied but an integer" if mci_id and mci_id !~ /^\d+$/
|
66
|
-
|
67
|
-
image_id = image_id.split("/").last
|
68
|
+
raise ArgumentError, "MCI name (:name) or id (:mci_id) not supplied" unless (mci_name =~ /./ || mci_id.to_s =~ /^\d+$/)
|
69
|
+
raise ArgumentError, "MCI (:mci_id) supplied but an integer" if mci_id and mci_id.to_s !~ /^\d+$/
|
68
70
|
|
69
71
|
if api_version(cloud_id) == "1.0"
|
72
|
+
image_id = image_id.split("/").last
|
70
73
|
unless image_id =~ /^ami-[0-9a-z]+$/
|
71
74
|
raise ArgumentError, "image_id #{image_id} doesn't look like an amazon ami"
|
72
75
|
end
|
@@ -77,9 +80,7 @@ module RightImageTools
|
|
77
80
|
#validate_mci_name(mci_name) unless mci
|
78
81
|
mci = create_mci(cloud_id, mci_name, description) unless mci
|
79
82
|
add_rightlink_tag(mci)
|
80
|
-
mci_setting = find_or_create_cloud_setting(mci, image_id, cloud_id)
|
81
|
-
|
82
|
-
# Create the MCIs, if they don't exist.
|
83
|
+
mci_setting = find_or_create_cloud_setting(mci, image_id, cloud_id, options)
|
83
84
|
|
84
85
|
return mci.href
|
85
86
|
end
|
@@ -87,44 +88,75 @@ module RightImageTools
|
|
87
88
|
def validate_mci_name(name)
|
88
89
|
end
|
89
90
|
|
90
|
-
def guess_instance_type(mci_name)
|
91
|
-
mci_name =~ /i386/ ? "m1.small" : "m1.large"
|
92
|
-
end
|
93
|
-
|
94
91
|
def find_cloud_setting(mci,cloud_id)
|
95
|
-
|
96
|
-
mci_setting = mci.multi_cloud_image_cloud_settings.select { |setting| setting.cloud_id.to_i == cloud_id.to_i }.first
|
92
|
+
mci.multi_cloud_image_cloud_settings.find { |s| s.cloud_id.to_i == cloud_id.to_i }
|
97
93
|
end
|
98
94
|
|
99
|
-
def
|
100
|
-
mci_setting = nil;
|
95
|
+
def guess_default_instance_type(cloud_id)
|
101
96
|
if api_version(cloud_id) == "1.0"
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
97
|
+
return "m1.small"
|
98
|
+
else
|
99
|
+
names = ["small instance", "1gb server", "nano-h-5", "standard.small", "small", "standard", "n1-standard-1-d", "m1.small", "s2", "1g" ]
|
100
|
+
types = McInstanceType.find_all(cloud_id)
|
101
|
+
type = types.find { |t| names.include? t.name.downcase }
|
102
|
+
raise "Could not guess default instance type for cloud #{cloud_id}" unless type
|
103
|
+
return type.href
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def find_or_create_cloud_setting(mci, image_id, cloud_id, options = {})
|
108
|
+
# debugger
|
109
|
+
mci_setting = find_cloud_setting(mci, cloud_id)
|
110
|
+
instance_type = options[:instance_type] || guess_default_instance_type(cloud_id)
|
111
|
+
|
112
|
+
if mci_setting
|
113
|
+
unless setting_exists?(mci_setting, image_id, cloud_id)
|
114
|
+
@logger.warn("Replacing image for cloud #{cloud_id} for MCI #{mci.rs_id}")
|
115
|
+
dummy_setting = nil
|
116
|
+
if mci.multi_cloud_image_cloud_settings.length <= 1
|
117
|
+
dummy_setting = create_dummy_setting(mci, cloud_id)
|
111
118
|
end
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
119
|
+
res = mci_setting.destroy
|
120
|
+
raise "Non success code returned #{res.inspect} " if res.code.to_s !~ /^2\d\d$/
|
121
|
+
mci_setting = create_cloud_setting(mci, image_id, cloud_id, instance_type)
|
122
|
+
destroy_dummy_setting(dummy_setting) if dummy_setting
|
116
123
|
end
|
117
|
-
else
|
118
|
-
|
124
|
+
else
|
125
|
+
@logger.info("Adding cloud images to MCI #{mci.href}")
|
126
|
+
mci_setting = create_cloud_setting(mci, image_id, cloud_id, instance_type)
|
127
|
+
#returns nil
|
119
128
|
end
|
120
129
|
mci_setting
|
121
130
|
end
|
122
131
|
|
123
|
-
def
|
132
|
+
def setting_exists?(mci_setting, image_id, cloud_id)
|
133
|
+
if api_version(cloud_id) == "1.0"
|
134
|
+
return mci_setting.image_href.include?(image_id)
|
135
|
+
else
|
136
|
+
image = McImage.find_all(cloud_id.to_i).find { |img| img.resource_uid.to_s == image_id.to_s }
|
137
|
+
raise "No image found for cloud #{cloud_id} with id #{image_id}" unless image
|
138
|
+
return mci_setting.image == image.href
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Create dummy/destroy dummy settings get around a bug in which we have to replace
|
143
|
+
# the (lone) setting for an MCI but can't delete it since its the last one left
|
144
|
+
# So temporarily add a dummy image to get around the check, then delete it right
|
145
|
+
# after
|
146
|
+
def create_dummy_setting(mci, cloud_id)
|
147
|
+
dummy_cloud_id = cloud_id.to_i == 1 ? 2 : 1
|
148
|
+
dummy_image_id = dummy_cloud_id == 1 ? "ami-41814f28" : "ami-f45beff5"
|
149
|
+
create_cloud_setting(mci, dummy_image_id, dummy_cloud_id, "m1.small")
|
150
|
+
end
|
151
|
+
|
152
|
+
def destroy_dummy_setting(setting)
|
153
|
+
setting.destroy
|
154
|
+
end
|
155
|
+
|
156
|
+
def create_cloud_setting(mci, image_id, cloud_id, instance_type)
|
124
157
|
mci_setting = nil
|
125
158
|
if api_version(cloud_id) == "1.0"
|
126
159
|
# create the setting
|
127
|
-
#update_connection_settings(MultiCloudImageInternal)
|
128
160
|
image_href = "#{@api_url}/ec2_images/#{image_id}?cloud_id=#{cloud_id}"
|
129
161
|
mci_setting = MultiCloudImageCloudSettingInternal.create(
|
130
162
|
:multi_cloud_image_href => mci.href,
|
@@ -132,62 +164,55 @@ module RightImageTools
|
|
132
164
|
:ec2_image_href => image_href,
|
133
165
|
:aws_instance_type => instance_type)
|
134
166
|
else
|
135
|
-
|
167
|
+
image = McImage.find_all(cloud_id.to_i).find { |img| img.resource_uid.to_s == image_id.to_s }
|
168
|
+
raise "No image found for cloud #{cloud_id} with id #{image_id}" unless image
|
169
|
+
@logger.debug("Found image id #{image_id} with href #{image.href}")
|
170
|
+
mci_id = mci.href.split("/").last
|
171
|
+
# debugger
|
172
|
+
mci_setting = McMultiCloudImageSetting.create(mci_id,
|
173
|
+
:cloud_href => "/api/clouds/#{cloud_id}",
|
174
|
+
:image_href => image.href,
|
175
|
+
:instance_type_href => instance_type)
|
136
176
|
end
|
137
177
|
mci_setting
|
138
178
|
end
|
139
179
|
|
140
180
|
# return MultiCloudImageInternal, or nil
|
141
|
-
def find_mci_by_id(cloud_id,mci_id)
|
142
|
-
|
143
|
-
|
144
|
-
else
|
145
|
-
existing_mci = McMultiCloudImage.find(mci_id.to_i)
|
146
|
-
end
|
181
|
+
def find_mci_by_id(cloud_id, mci_id)
|
182
|
+
existing_mci = MultiCloudImage.find(mci_id.to_i)
|
183
|
+
existing_mci.find_and_flatten_settings
|
147
184
|
raise ArgumentError, "Could not find #{existing_mci.rs_id}" unless existing_mci
|
148
185
|
@logger.info("Using mci #{existing_mci.rs_id} named #{existing_mci.name}")
|
149
186
|
existing_mci
|
150
187
|
end
|
151
188
|
|
152
189
|
def find_mci(cloud_id, mci_name)
|
153
|
-
mcis =
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
mci_ids = mcis.map { |m| m.rs_id }
|
159
|
-
@logger.warn("Found multiple MCIs with name #{mci_name}: #{mci_ids.join(', ')}") if mcis.length > 1
|
160
|
-
if mcis.length > 0
|
161
|
-
existing_mci = MultiCloudImageInternal.find(mcis.first.rs_id.to_i)
|
162
|
-
end
|
163
|
-
else
|
164
|
-
# Double filter, find_with_filter is a wildcard ended match
|
165
|
-
# Also revision 0 is HEAD
|
166
|
-
#update_connection_settings(McMultiCloudImage)
|
167
|
-
mcis = McMultiCloudImage.
|
168
|
-
find_with_filter(:name=>mci_name).
|
169
|
-
select {|mci| mci.name == mci_name}.
|
170
|
-
select {|mci| mci.revision == 0}
|
171
|
-
@logger.warn("Found multiple MCIs with name #{mci_name}") if mcis.length > 1
|
190
|
+
mcis = MultiCloudImage.find_all.
|
191
|
+
select {|n| n.is_head_version && n.name == mci_name }
|
192
|
+
mci_ids = mcis.map { |m| m.rs_id }
|
193
|
+
@logger.warn("Found multiple MCIs with name #{mci_name}: #{mci_ids.join(', ')}") if mcis.length > 1
|
194
|
+
if mcis.length > 0
|
172
195
|
existing_mci = mcis.first
|
196
|
+
existing_mci.find_and_flatten_settings
|
197
|
+
@logger.info("Found mci #{existing_mci.rs_id}") if existing_mci
|
198
|
+
return existing_mci
|
199
|
+
else
|
200
|
+
return nil
|
173
201
|
end
|
174
|
-
|
175
|
-
@logger.info("Found mci #{existing_mci.rs_id}") if existing_mci
|
176
|
-
existing_mci
|
177
202
|
end
|
178
203
|
|
179
204
|
def create_mci(cloud_id, mci_name, description = nil)
|
180
205
|
description ||= default_description(mci_name, api_version(cloud_id) == "1.0")
|
181
206
|
@logger.info("Creating mci #{mci_name}")
|
182
|
-
mci = nil
|
183
207
|
|
184
|
-
#update_connection_settings(MultiCloudImageInternal)
|
185
208
|
mci = MultiCloudImageInternal.create(
|
186
209
|
:name => mci_name,
|
187
210
|
:description => description)
|
188
211
|
|
189
|
-
|
190
|
-
|
212
|
+
raise "Could not create MCI" unless mci
|
213
|
+
mci = MultiCloudImage.find(mci.rs_id.to_i)
|
214
|
+
mci.find_and_flatten_settings
|
215
|
+
return mci
|
191
216
|
end
|
192
217
|
|
193
218
|
def add_rightlink_tag(mci)
|
@@ -202,7 +227,6 @@ module RightImageTools
|
|
202
227
|
@logger.info "Adding tag #{tag} to #{mci.href}"
|
203
228
|
href = mci.href
|
204
229
|
result = Tag.set(href, ["#{tag}"])
|
205
|
-
#update_connection_settings(Tag)
|
206
230
|
@logger.debug("Successfully tagged MCI. Code: #{result.code}")
|
207
231
|
rescue Exception => e
|
208
232
|
@logger.error("Failed to tag MCI #{mci.name}! #{e.inspect}")
|
data/lib/s3_html_indexer.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'rubygems'
|
2
|
+
#require 'ruby-debug'
|
2
3
|
require 'right_aws'
|
4
|
+
require 'pp'
|
3
5
|
|
4
6
|
module RightImageTools
|
5
7
|
|
@@ -8,29 +10,56 @@ module RightImageTools
|
|
8
10
|
def initialize(bucket, id=nil, key=nil)
|
9
11
|
@s3 = RightAws::S3.new(id, key)
|
10
12
|
@dev_bucket = bucket.downcase.include?("dev")
|
13
|
+
|
14
|
+
# If the bucket holds base images.
|
15
|
+
@base_bucket = bucket.downcase.include?("rightimage-base")
|
16
|
+
|
17
|
+
# If the bucket holds reports for the public clouds, ec2, Google, or Azure.
|
18
|
+
@public_bucket = bucket.downcase =~ /ec2|google|azure/
|
19
|
+
|
20
|
+
@cloudstack_bucket = bucket.downcase.include?("cloudstack")
|
11
21
|
@bucket = @s3.bucket(bucket)
|
12
22
|
@keys = @bucket.keys
|
13
23
|
@auto_hash = Hash.new{ |h,k| h[k] = Hash.new &h.default_proc }
|
14
24
|
end
|
15
25
|
|
16
|
-
def ver(key)
|
17
|
-
key.match(/v[0-9]+\.[0-9]+\.[0-9]+/).to_s
|
18
|
-
end
|
19
|
-
|
20
26
|
def to_html(filename)
|
21
27
|
# Create hash hierarchy from bucket list
|
22
|
-
@keys.reject! { |path| path.full_name =~ /index.html/ }
|
23
28
|
#@keys.sort! {|x,y| ver(x.full_name) <=> ver(y.full_name) } #TODO: sort by rightlink version
|
24
29
|
@keys.each do |path|
|
25
30
|
|
26
31
|
next unless is_public?(path)
|
32
|
+
|
33
|
+
path_key = path.full_name
|
34
|
+
# Ignore index.html and report_viewer.html .
|
35
|
+
next if path_key =~ /index.html/
|
36
|
+
next if path_key =~ /report_viewer.html/
|
37
|
+
path_key.gsub!('xen/','xenserver/')
|
38
|
+
path_key.gsub!(/(vmware|esxi)\/(vmware|esxi)\//,'esxi/')
|
39
|
+
path_key.gsub!('vmware/','esxi/')
|
40
|
+
|
41
|
+
#if path_key.split("/").size != 5
|
42
|
+
# puts "WARNING: Skipping malformed path: #{path}"
|
43
|
+
# next
|
44
|
+
#end
|
45
|
+
|
46
|
+
# Skip files if they are too small to be an image.
|
47
|
+
# Except for mega cloud buckets that collect reports.
|
48
|
+
if path.size.to_i < 1024*1024*100 and not @public_bucket
|
49
|
+
puts "WARNING: Skipping #{path}: it appears to be too small (<100 MB) to be an image"
|
50
|
+
next
|
51
|
+
end
|
27
52
|
|
28
53
|
sub = @auto_hash
|
29
|
-
|
54
|
+
path_key.split( "/" ).each do |dir|
|
30
55
|
sub = sub[dir]
|
31
56
|
end
|
32
57
|
|
33
|
-
|
58
|
+
# Retrieve image and report links duple.
|
59
|
+
links = fix_public_endpoint(path.public_link)
|
60
|
+
|
61
|
+
sub[:link] = links[0]
|
62
|
+
sub[:report] = links[1]
|
34
63
|
sub[:size] = path.size
|
35
64
|
sub[:date] = path.last_modified.strftime("%m/%d/%Y")
|
36
65
|
sub[:md5sum] = path.e_tag.gsub(/\"/, "")
|
@@ -48,27 +77,49 @@ module RightImageTools
|
|
48
77
|
add_style
|
49
78
|
display_dirs(k,v)
|
50
79
|
output("</html>")
|
80
|
+
puts "Output written to #{@file.path}"
|
51
81
|
@file.close
|
52
82
|
end
|
53
83
|
end
|
54
84
|
|
55
85
|
def upload_index(filename)
|
56
|
-
|
86
|
+
#
|
87
|
+
if File.exists?("index.html")
|
88
|
+
@bucket.put("index.html", File.open(filename), {}, 'public-read')
|
89
|
+
File.delete("index.html")
|
90
|
+
|
91
|
+
# Upload amalgamated report_viewer.html with index.html.
|
92
|
+
Dir.chdir(File.join(File.dirname(File.expand_path(__FILE__)), "..", "ui", "build"))
|
93
|
+
@bucket.put("report_viewer.html", File.open("report_viewer.html"), {}, 'public-read')
|
94
|
+
else
|
95
|
+
# Upload index.html for empty buckets.
|
96
|
+
Dir.chdir(File.join(File.dirname(File.expand_path(__FILE__)), "..", "ui", "build"))
|
97
|
+
@bucket.put("index.html", File.open("empty_bucket_index.html"), {}, 'public-read')
|
98
|
+
puts "Uploaded empty bucket index.html."
|
99
|
+
end
|
57
100
|
end
|
58
101
|
|
59
102
|
private
|
103
|
+
def ver(key)
|
104
|
+
key.match(/v[0-9]+\.[0-9]+\.[0-9]+/).to_s
|
105
|
+
end
|
60
106
|
|
61
107
|
def is_public?(key)
|
62
108
|
key.refresh
|
63
|
-
puts "Key: #{key.name}"
|
109
|
+
# puts "Key: #{key.name}"
|
110
|
+
is_public = false
|
64
111
|
key.grantees.each do |g|
|
65
|
-
puts "grantee:#{g.name} perms:#{g.perms}"
|
112
|
+
# puts "grantee:#{g.name} perms:#{g.perms}"
|
66
113
|
if g.name.to_s == "AllUsers" && g.perms.to_s == "READ"
|
67
|
-
|
68
|
-
return true
|
114
|
+
is_public = true
|
69
115
|
end
|
70
116
|
end
|
71
|
-
|
117
|
+
if is_public
|
118
|
+
puts "PUBLIC: #{key.name}"
|
119
|
+
else
|
120
|
+
puts "NOT PUBLIC: #{key.name}"
|
121
|
+
end
|
122
|
+
return is_public
|
72
123
|
end
|
73
124
|
|
74
125
|
def display_dirs(k,v,level=0)
|
@@ -81,19 +132,47 @@ module RightImageTools
|
|
81
132
|
add_footer(k, level)
|
82
133
|
else
|
83
134
|
output("<tr class='code'>")
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
135
|
+
# Link to images stored on S3 bucket.
|
136
|
+
if not @public_bucket
|
137
|
+
output("<td><a href='#{v[:link]}'>#{k}</a></td>")
|
138
|
+
# Otherwise, the bucket only collects reports.
|
139
|
+
else
|
140
|
+
# Remove '.js.' extension in public cloud buckets.
|
141
|
+
kp = k.gsub(/\.js$/,"")
|
142
|
+
output("<td>#{kp}</td>")
|
143
|
+
end
|
144
|
+
# If bucket does not have report key, do not add "notes" link.
|
145
|
+
if not v[:report].empty?
|
146
|
+
output("<td><a href='#{v[:report]}'>notes</a></td>")
|
147
|
+
else
|
148
|
+
# Empty 'notes' cell.
|
149
|
+
output("<td></td>")
|
150
|
+
end
|
151
|
+
# Hide fields for public cloud images.
|
152
|
+
if not @public_bucket
|
153
|
+
output("<td>#{v[:size].to_i/(1024*1024)}MB</td>")
|
154
|
+
output("<td>#{v[:date]}</td>")
|
155
|
+
output("<td>#{v[:md5sum]}</td>")
|
156
|
+
else
|
157
|
+
output("<td>#{v[:date]}</td>")
|
158
|
+
end
|
88
159
|
output("</tr>")
|
89
160
|
end
|
90
161
|
end
|
91
|
-
|
162
|
+
|
163
|
+
# Refer to "Base Images" or "RightImages" based on bucket.
|
92
164
|
def disclaimer
|
93
165
|
if @dev_bucket
|
94
|
-
|
166
|
+
prod_bucket = @bucket.name.sub("-dev","")
|
167
|
+
disclaimer = "The following "
|
168
|
+
disclaimer << (@base_bucket ? "Base Images" : "RightImages")
|
169
|
+
disclaimer << " are currently in RightScale development and testing. They are not supported by RightScale. For "
|
170
|
+
disclaimer << (@base_bucket ? "Base Images" : "RightImages")
|
171
|
+
disclaimer << " that RightScale supports, please see our <a href='http://#{prod_bucket}.s3.amazonaws.com/index.html'>production images</a>."
|
95
172
|
else
|
96
|
-
"The following
|
173
|
+
disclaimer = "The following "
|
174
|
+
disclaimer << (@base_bucket ? "Base Images" : "RightImages")
|
175
|
+
disclaimer << " are tested and supported by RightScale for general use. For further information regarding use of these images with RightScale, please contact your RightScale account manager or sign up for an account at <a href='http://www.rightscale.com'>www.rightscale.com</a>."
|
97
176
|
end
|
98
177
|
end
|
99
178
|
|
@@ -110,22 +189,35 @@ module RightImageTools
|
|
110
189
|
when 1
|
111
190
|
# Hypervisor
|
112
191
|
output("<div class='hypervisor common'>")
|
113
|
-
|
192
|
+
# Include "Hypervisor" in subheader if not base image bucket.
|
193
|
+
if @base_bucket
|
194
|
+
output("<h#{level-1}>#{str.upcase}</h#{level-1}>\n")
|
195
|
+
else
|
196
|
+
output("<h#{level-1}>#{str.upcase} Hypervisor</h#{level-1}>\n")
|
197
|
+
end
|
114
198
|
output("</div>")
|
115
199
|
output("<div class='platform common'>")
|
116
200
|
output("<table style='padding:none; size:90%'>")
|
117
201
|
output("<tr style='border: solid 1px black;'>")
|
118
202
|
output("<th style='text-align:left'>Image</th>")
|
119
|
-
|
120
|
-
output("<th
|
121
|
-
|
203
|
+
# Leave 'Notes' header empty.
|
204
|
+
output("<th></th>")
|
205
|
+
if not @public_bucket
|
206
|
+
output("<th>Size</th>")
|
207
|
+
output("<th>Date</th>")
|
208
|
+
output("<th>MD5</th>")
|
209
|
+
else
|
210
|
+
output("<th>Date</th>")
|
211
|
+
end
|
122
212
|
output("</tr>")
|
123
213
|
when 2
|
124
214
|
return
|
125
215
|
# OS
|
126
216
|
when 3
|
127
217
|
return
|
218
|
+
# OS Version
|
128
219
|
else
|
220
|
+
return
|
129
221
|
# image
|
130
222
|
# output("<div class='platform'>")
|
131
223
|
end
|
@@ -145,25 +237,55 @@ module RightImageTools
|
|
145
237
|
when 3
|
146
238
|
return
|
147
239
|
else
|
240
|
+
return
|
148
241
|
# output("</div>")
|
149
242
|
end
|
150
243
|
end
|
151
244
|
|
152
245
|
# https://s3.amazonaws.com/rightscale-cloudstack/foo
|
153
246
|
# to
|
154
|
-
#
|
247
|
+
# http://rightscale-cloudstack.s3.amazonaws.com
|
155
248
|
def fix_public_endpoint(link)
|
249
|
+
link = link.dup
|
250
|
+
# http apparently more reliable than https when importing cloudstack images (w-4839)
|
251
|
+
if @cloudstack_bucket
|
252
|
+
link.sub!("https","http")
|
253
|
+
link.sub!(":443","")
|
254
|
+
end
|
156
255
|
link_ary = link.split("/")
|
157
256
|
bucket = link_ary[3]
|
158
257
|
host = link_ary[2]
|
159
258
|
link_ary[2] = "#{bucket}.#{host}"
|
160
259
|
link_ary.delete_at(3)
|
161
|
-
link_ary.join("/")
|
260
|
+
new_link = link_ary.join("/")
|
261
|
+
|
262
|
+
# Relative link to report.
|
263
|
+
link_ary.slice!(1..2)
|
264
|
+
|
265
|
+
|
266
|
+
|
267
|
+
|
268
|
+
#!!! Change the (usually double) compressed image extension to ".js".
|
269
|
+
#if not @public_bucket
|
270
|
+
# link_ary[-1].gsub!(/\.[^.]+\.[^.]+$/,'.js')
|
271
|
+
#end
|
272
|
+
|
273
|
+
# If report doesn't exist, return empty string.
|
274
|
+
# Relative url begins at link_ary[1].
|
275
|
+
if not RightAws::S3::Key.create(@bucket,link_ary.slice(1..-1).join("/").gsub!(/\.[^.]+\.[^.]+$/,'.js')).exists?
|
276
|
+
return [new_link , '']
|
277
|
+
end
|
278
|
+
|
279
|
+
# Replace "https://" with image viewer prefix.
|
280
|
+
link_ary[0] = 'report_viewer.html?image='
|
281
|
+
report_link = link_ary.join("/")
|
282
|
+
|
283
|
+
# Return image link and report link.
|
284
|
+
return [new_link , report_link]
|
162
285
|
end
|
163
286
|
|
164
287
|
def output(str)
|
165
288
|
@file.write "#{str}\n" if @file
|
166
|
-
p str
|
167
289
|
end
|
168
290
|
|
169
291
|
def add_style
|
@@ -176,14 +298,14 @@ module RightImageTools
|
|
176
298
|
th { text-align:center }
|
177
299
|
|
178
300
|
#header {
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
301
|
+
background:#235186;
|
302
|
+
padding:2px 20px;
|
303
|
+
position:relative;
|
304
|
+
height:25px;
|
183
305
|
}
|
184
306
|
|
185
307
|
#header #logo {
|
186
|
-
|
308
|
+
margin-top:3px;
|
187
309
|
}
|
188
310
|
|
189
311
|
.common {
|
@@ -191,39 +313,39 @@ module RightImageTools
|
|
191
313
|
margin-left:auto;
|
192
314
|
margin-right:auto;
|
193
315
|
font-weight: bold;
|
194
|
-
|
195
|
-
|
316
|
+
padding:2px 20px;
|
317
|
+
position:relative;
|
196
318
|
}
|
197
319
|
|
198
320
|
.bucket {
|
199
321
|
font-size: 24px;
|
200
322
|
color: #F5F2A9;
|
201
|
-
|
202
|
-
|
323
|
+
background:#235186;
|
324
|
+
padding:10px 30px 20px;
|
203
325
|
}
|
204
326
|
|
205
327
|
.version {
|
206
328
|
color: #F5F2A9;
|
207
|
-
|
329
|
+
background:#235186;
|
208
330
|
}
|
209
331
|
|
210
332
|
.hypervisor {
|
211
333
|
color: #235186;
|
212
|
-
|
334
|
+
background:#F5F2A9;
|
213
335
|
}
|
214
336
|
|
215
337
|
.platform {
|
216
|
-
|
217
|
-
|
218
|
-
|
338
|
+
background: #FFFFFF;
|
339
|
+
width:90%;
|
340
|
+
margin-left: auto ;
|
219
341
|
margin-right: auto ;
|
220
342
|
position: relative;
|
221
343
|
}
|
222
344
|
|
223
345
|
.disclaimer {
|
224
|
-
|
225
|
-
|
226
|
-
|
346
|
+
background: #FFFFFF;
|
347
|
+
width:90%;
|
348
|
+
margin-left: auto ;
|
227
349
|
margin-right: auto ;
|
228
350
|
position: relative;
|
229
351
|
border: 2px solid black;
|
@@ -231,10 +353,10 @@ module RightImageTools
|
|
231
353
|
}
|
232
354
|
|
233
355
|
.code {
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
356
|
+
font-family:'Courier New', monospace;
|
357
|
+
color: #000000;
|
358
|
+
position:relative;
|
359
|
+
left:10px;
|
238
360
|
}
|
239
361
|
")
|
240
362
|
output "</style>"
|
@@ -245,7 +367,4 @@ module RightImageTools
|
|
245
367
|
end
|
246
368
|
|
247
369
|
end
|
248
|
-
end
|
249
|
-
|
250
|
-
|
251
|
-
|
370
|
+
end
|