rightimage_tools 0.2.1 → 0.5.0
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/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/image_mover
CHANGED
@@ -50,11 +50,11 @@ grant = RightAws::S3::Grantee.new(key_to, 'http://acs.amazonaws.com/groups/globa
|
|
50
50
|
raise "Grant update failed" unless grant
|
51
51
|
|
52
52
|
puts "Reindexing source bucket #{bucket_from}"
|
53
|
-
indexer_from =
|
53
|
+
indexer_from = RightImageTools::S3HtmlIndexer.new(options[:bucket_from], ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_ACCESS_KEY"])
|
54
54
|
indexer_from.to_html("index-from.html")
|
55
55
|
indexer_from.upload_index("index-from.html")
|
56
56
|
|
57
57
|
puts "Reindexing destination bucket #{bucket_to}"
|
58
|
-
indexer_to =
|
58
|
+
indexer_to = RightImageTools::S3HtmlIndexer.new(options[:bucket_to], ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_ACCESS_KEY"])
|
59
59
|
indexer_to.to_html("index-to.html")
|
60
60
|
indexer_to.upload_index("index-to.html")
|
data/bin/mci_merge
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'optparse'
|
5
|
-
|
5
|
+
require 'ruby-debug'
|
6
6
|
require 'rest_connection'
|
7
7
|
require 'tmpdir'
|
8
8
|
|
@@ -57,16 +57,21 @@ sources = []
|
|
57
57
|
dests = []
|
58
58
|
|
59
59
|
mcis.each do |mci|
|
60
|
-
if mci.name =~ /RightImage.*#{Regexp.escape(options[:src])}
|
60
|
+
if mci.name =~ /RightImage.*#{Regexp.escape(options[:src])}(_EBS)?$/
|
61
61
|
sources << MultiCloudImageInternal.find(mci.rs_id.to_i)
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
+
if sources.length == 0
|
66
|
+
puts "Did not find any MCIs with suffix #{options[:src]} to copy"
|
67
|
+
exit 0
|
68
|
+
end
|
69
|
+
|
65
70
|
puts "Found #{sources.length} MCIs to move: "
|
66
71
|
|
67
72
|
sources.each_with_index do |mci, i|
|
68
73
|
# print out source mci
|
69
|
-
print i.to_s.ljust(2)+") "
|
74
|
+
print (i+1).to_s.ljust(2)+") "
|
70
75
|
puts mci.name
|
71
76
|
clouds = mci.multi_cloud_image_cloud_settings.map {|s| s.cloud}
|
72
77
|
puts " CLOUDS: "+clouds.join(", ")
|
@@ -74,8 +79,9 @@ sources.each_with_index do |mci, i|
|
|
74
79
|
|
75
80
|
# find a destination mci and print that if found
|
76
81
|
# else note we'll make a new mci for the destination
|
77
|
-
name_no_suffix = mci.name.sub(options[:src],"")
|
82
|
+
name_no_suffix = mci.name.sub(options[:src],"").sub("_EBS","")
|
78
83
|
dest_mci_name = name_no_suffix + options[:dest]
|
84
|
+
dest_mci_name += "_EBS" if mci.name =~ /_EBS/ and dest_mci_name !~ /_EBS/
|
79
85
|
found = mcis.find { |new_mci| new_mci.name == dest_mci_name }
|
80
86
|
if found
|
81
87
|
dest_mci = MultiCloudImageInternal.find(found.rs_id.to_i)
|
data/bin/mci_report
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'yaml'
|
5
|
+
require 'irb'
|
6
|
+
require 'right_api_client'
|
7
|
+
require 'rest_connection'
|
8
|
+
require 'trollop'
|
9
|
+
require 'highline/import'
|
10
|
+
require 'json'
|
11
|
+
|
12
|
+
# The only option needed for this program is the ServerTemplate ID.
|
13
|
+
trollop_parser = Trollop::Parser.new do
|
14
|
+
opt :st_id, "Generate a report for this Server Template (id)", :type => :integer
|
15
|
+
opt :st_list_file, "Generate a list of MCIs in Server Templates given in the file", :type => :string
|
16
|
+
opt :output_type, "Specify the type of output. Supported formats: text, xml, json", :type => :string
|
17
|
+
end
|
18
|
+
|
19
|
+
class MciReport
|
20
|
+
|
21
|
+
# Establish a connection to Right_API_Client during the initialization
|
22
|
+
def initialize
|
23
|
+
@client = RightApi::Client.new(YAML.load_file(File.expand_path('~/.right_api_client/login.yml', __FILE__)))
|
24
|
+
end
|
25
|
+
|
26
|
+
# This function creates a report of all the Multi Cloud Images in a ServerTemplate specified by the ST ID in the input.
|
27
|
+
def generate_mci_report(options)
|
28
|
+
mcis = @client.server_templates(:id => options[:st_id]).show.multi_cloud_images.index
|
29
|
+
|
30
|
+
# Go through all the Multi Cloud Images in the given ServerTemplate and pull the information required for printing the report.
|
31
|
+
outer_hash_array = Array.new
|
32
|
+
mcis.each do |mci|
|
33
|
+
outer_hash = Hash.new
|
34
|
+
# Name, Description, Revision, and MCI ID are obtained directly from the MCI object.
|
35
|
+
mci_id = mci.href.split("/").last.to_i
|
36
|
+
outer_hash['title'] = "Name: " + mci.name + " | MCI ID: " + mci_id.to_s + " | Revision: " + mci.show.revision.to_s + "\nDescription: " + mci.show.description
|
37
|
+
inner_hash_array = Array.new
|
38
|
+
inner_hash = Hash.new
|
39
|
+
|
40
|
+
# Add all titles in the first array element.
|
41
|
+
inner_hash['detail1'] = "Instance Type"
|
42
|
+
inner_hash['detail2'] = "Image Name"
|
43
|
+
inner_hash['detail3'] = "Cloud"
|
44
|
+
inner_hash_array << inner_hash
|
45
|
+
|
46
|
+
# This block uses the rest_connection for obtaining information about ec2 images.
|
47
|
+
rest_mci = MultiCloudImage.find(mci_id)
|
48
|
+
rest_mci_settings = rest_mci.find_and_flatten_settings
|
49
|
+
rest_mci_settings.each do |rest_mci_setting|
|
50
|
+
mci_params = rest_mci_setting.params
|
51
|
+
inner_hash = Hash.new
|
52
|
+
|
53
|
+
# Grab only the information about the ec2 images.
|
54
|
+
if mci_params['aws_instance_type']
|
55
|
+
inner_hash['detail1'] = mci_params['aws_instance_type']
|
56
|
+
inner_hash['detail2'] = mci_params['image_name']
|
57
|
+
inner_hash['detail3'] = mci_params['cloud']
|
58
|
+
end
|
59
|
+
inner_hash_array << inner_hash if inner_hash['detail1'] && inner_hash['detail2'] && inner_hash['detail3']
|
60
|
+
end
|
61
|
+
|
62
|
+
# This block uses the regular right_api_client for obtaining information about non-ec2 images.
|
63
|
+
right_mci_settings = mci.settings.index
|
64
|
+
right_mci_settings.each do |right_mci_setting|
|
65
|
+
inner_hash = Hash.new
|
66
|
+
|
67
|
+
# If the instance_type api method is not available, don't call the method rather print N/A for instance type in the report.
|
68
|
+
if right_mci_setting.api_methods.include?("instance_type")
|
69
|
+
inner_hash['detail1'] = right_mci_setting.instance_type.show.name
|
70
|
+
else
|
71
|
+
inner_hash['detail1'] = "N/A"
|
72
|
+
end
|
73
|
+
inner_hash['detail2'] = right_mci_setting.image.show.name
|
74
|
+
inner_hash['detail3'] = right_mci_setting.cloud.show.name
|
75
|
+
inner_hash_array << inner_hash if inner_hash['detail1'] && inner_hash['detail2'] && inner_hash['detail3']
|
76
|
+
end
|
77
|
+
outer_hash['detail'] = inner_hash_array
|
78
|
+
outer_hash_array << outer_hash
|
79
|
+
end
|
80
|
+
self.print_report(options, outer_hash_array, "RightScale MCI Image Report")
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
# This function gets the name and id hash and prepares a list of ServerTemplate IDs.
|
85
|
+
def servertemplate_list(servertemplate_name_id_hash)
|
86
|
+
list = []
|
87
|
+
servertemplate_name_id_hash.each do |name, id|
|
88
|
+
list << @client.server_templates(:id => id).show
|
89
|
+
end
|
90
|
+
list
|
91
|
+
end
|
92
|
+
|
93
|
+
# This function creates a report of all ServerTemplates specified in the input file.
|
94
|
+
def generate_servertemplate_report(options, servertemplate_name_id_hash)
|
95
|
+
outer_hash_array = Array.new
|
96
|
+
st_list = servertemplate_list(servertemplate_name_id_hash)
|
97
|
+
st_list.each do |st|
|
98
|
+
outer_hash = Hash.new
|
99
|
+
mcis = st.multi_cloud_images.index
|
100
|
+
outer_hash['title'] = "Name: " + st.name + " | ID: " + st.object_id.to_s
|
101
|
+
inner_hash_array = Array.new
|
102
|
+
inner_hash = Hash.new
|
103
|
+
inner_hash['detail1'] = "Revision"
|
104
|
+
inner_hash['detail2'] = "Name"
|
105
|
+
inner_hash['detail3'] = "MCI ID"
|
106
|
+
inner_hash_array << inner_hash if inner_hash['detail1'] && inner_hash['detail2'] && inner_hash['detail3']
|
107
|
+
|
108
|
+
# Go through all the Multi Cloud Images in the given ServerTemplate and pull the information required for printing the report.
|
109
|
+
mcis.each do |mci|
|
110
|
+
inner_hash = Hash.new
|
111
|
+
mci_id = mci.href.split("/").last.to_i
|
112
|
+
inner_hash['detail1'] = mci.show.revision
|
113
|
+
inner_hash['detail2'] = mci.name
|
114
|
+
inner_hash['detail3'] = mci_id
|
115
|
+
inner_hash_array << inner_hash if inner_hash['detail1'] && inner_hash['detail2'] && inner_hash['detail3']
|
116
|
+
end
|
117
|
+
outer_hash['detail'] = inner_hash_array.sort_by{ |inner_hash| inner_hash['detail2']}
|
118
|
+
outer_hash_array << outer_hash
|
119
|
+
end
|
120
|
+
self.print_report(options, outer_hash_array, "RightScale MCI ServerTemplate Report")
|
121
|
+
end
|
122
|
+
|
123
|
+
def print_report(options, outer_hash_array, report_title)
|
124
|
+
if options[:output_type] == "text"
|
125
|
+
self.print_text_report(outer_hash_array, report_title)
|
126
|
+
elsif options[:output_type] == "xml"
|
127
|
+
self.print_xml_report(outer_hash_array, report_title)
|
128
|
+
elsif options[:output_type] == "json"
|
129
|
+
self.print_json_report(outer_hash_array, report_title)
|
130
|
+
else
|
131
|
+
|
132
|
+
# Default to text report if no type is specified
|
133
|
+
self.print_text_report(outer_hash_array, report_title)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# This functions prints the formatted report in a text file
|
138
|
+
def print_text_report(outer_hash_array, report_title)
|
139
|
+
report_file_name = report_title.gsub(/\s+/, "_") + ".txt"
|
140
|
+
file_handler = File.open(report_file_name, 'w')
|
141
|
+
current_time = Time.new
|
142
|
+
|
143
|
+
file_handler.puts sprintf("%75s", "******** #{report_title} ********")
|
144
|
+
file_handler.puts ""
|
145
|
+
file_handler.puts "Reported generated at: " + current_time.strftime("%m/%d/%Y %H:%M:%S %Z")
|
146
|
+
file_handler.puts ""
|
147
|
+
|
148
|
+
outer_hash_array.each do |outer_hash|
|
149
|
+
file_handler.puts '-'*100
|
150
|
+
file_handler.puts outer_hash['title']
|
151
|
+
file_handler.puts '-'*100
|
152
|
+
inner_hash_array = outer_hash['detail']
|
153
|
+
title = inner_hash_array[0]
|
154
|
+
if title
|
155
|
+
title_str = sprintf("%-20s", title['detail1']) + sprintf("%-65s", title['detail2']) + sprintf("%-30s", title['detail3'])
|
156
|
+
title_line_str = sprintf("%-20s", '-'*title['detail1'].size) + sprintf("%-65s", '-'*title['detail2'].size) + sprintf("%-30s", '-'*title['detail3'].size)
|
157
|
+
file_handler.puts " " + title_str
|
158
|
+
file_handler.puts " " + title_line_str
|
159
|
+
inner_hash_array.delete(title)
|
160
|
+
end
|
161
|
+
inner_hash_array.each do |inner_hash|
|
162
|
+
detail_str = sprintf("%-20s", inner_hash['detail1']) + sprintf("%-65s", inner_hash['detail2']) + sprintf("%-30s", inner_hash['detail3'])
|
163
|
+
file_handler.puts " " + detail_str
|
164
|
+
end
|
165
|
+
file_handler.puts ""
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def print_xml_report(outer_hash_array, report_title)
|
170
|
+
report_file_name = report_title.gsub(/\s+/, "_") + ".xml"
|
171
|
+
file_handler = File.open(report_file_name, 'w')
|
172
|
+
current_time = Time.new
|
173
|
+
|
174
|
+
file_handler.puts "<mci_report>"
|
175
|
+
file_handler.puts "<report_title>"
|
176
|
+
file_handler.puts report_title
|
177
|
+
file_handler.puts "</report_title>"
|
178
|
+
file_handler.puts "<generated_time>"
|
179
|
+
file_handler.puts current_time.strftime("%m/%d/%Y %H:%M:%S %Z")
|
180
|
+
file_handler.puts "</generated_time>"
|
181
|
+
|
182
|
+
outer_hash_array.each do |outer_hash|
|
183
|
+
file_handler.puts "<group>"
|
184
|
+
file_handler.puts "<title>"
|
185
|
+
file_handler.puts outer_hash['title']
|
186
|
+
file_handler.puts "</title>"
|
187
|
+
inner_hash_array = outer_hash['detail']
|
188
|
+
title = inner_hash_array[0]
|
189
|
+
if title
|
190
|
+
inner_hash_array.delete(title)
|
191
|
+
inner_hash_array.each do |inner_hash|
|
192
|
+
detail_str = sprintf("%-20s", inner_hash['detail1']) + sprintf("%-65s", inner_hash['detail2']) + sprintf("%-30s", inner_hash['detail3'])
|
193
|
+
file_handler.puts "<#{title['detail1'].gsub(/\s+/, "_")}>"
|
194
|
+
file_handler.puts inner_hash['detail1'].to_s.strip
|
195
|
+
file_handler.puts "</#{title['detail1'].gsub(/\s+/, "_")}>"
|
196
|
+
file_handler.puts "<#{title['detail2'].gsub(/\s+/, "_")}>"
|
197
|
+
file_handler.puts inner_hash['detail2'].to_s.strip
|
198
|
+
file_handler.puts "</#{title['detail2'].gsub(/\s+/, "_")}>"
|
199
|
+
file_handler.puts "<#{title['detail3'].gsub(/\s+/, "_")}>"
|
200
|
+
file_handler.puts inner_hash['detail3'].to_s.strip
|
201
|
+
file_handler.puts "</#{title['detail3'].gsub(/\s+/, "_")}>"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
file_handler.puts "</group>"
|
205
|
+
end
|
206
|
+
file_handler.puts "</mci_report>"
|
207
|
+
end
|
208
|
+
|
209
|
+
def print_json_report(outer_hash_array, report_title)
|
210
|
+
report_file_name = report_title.gsub(/\s+/, "_") + ".json"
|
211
|
+
File.open(report_file_name,"w") do |f|
|
212
|
+
f.write(outer_hash_array.to_json)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
# Parse the given options and generate the report accordingly
|
219
|
+
options = Trollop::with_standard_exception_handling trollop_parser do
|
220
|
+
raise Trollop::HelpNeeded if ARGV.empty?
|
221
|
+
trollop_parser.parse ARGV
|
222
|
+
end
|
223
|
+
|
224
|
+
mr = MciReport.new
|
225
|
+
|
226
|
+
if options[:st_id]
|
227
|
+
mr.generate_mci_report(options)
|
228
|
+
elsif options[:st_list_file]
|
229
|
+
yaml_contents = YAML.load_file(File.expand_path(options[:st_list_file], __FILE__))
|
230
|
+
mr.generate_servertemplate_report(options, yaml_contents)
|
231
|
+
end
|
232
|
+
|
data/bin/mcicp
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rest_connection'
|
5
|
+
require 'trollop'
|
6
|
+
require 'highline/import'
|
7
|
+
|
8
|
+
options = Trollop::options do
|
9
|
+
opt :from, "Copy MCIs from this Server Template (id)", :type => :integer, :required => :true
|
10
|
+
opt :to, "Copy MCIs to this Server Template (id)", :type => :integer, :required => :true
|
11
|
+
opt :skip, "Skip MCIs matching a certain regex", :type => :string
|
12
|
+
opt :nice, "Non-Destructive update of the destination Server Template"
|
13
|
+
end
|
14
|
+
|
15
|
+
class MciCp
|
16
|
+
def self.go(options)
|
17
|
+
temp1 = ServerTemplate.find(options[:from])
|
18
|
+
temp2 = ServerTemplate.find(options[:to])
|
19
|
+
from_st = ServerTemplateInternal.new(:href => temp1.href)
|
20
|
+
mci_payload = from_st.multi_cloud_images
|
21
|
+
to_st = ServerTemplateInternal.new(:href => temp2.href)
|
22
|
+
to_delete = to_st.multi_cloud_images
|
23
|
+
mci_payload.each do |mci|
|
24
|
+
begin
|
25
|
+
if options[:skip] and mci['name'] =~ /#{options[:skip]}/
|
26
|
+
puts "skipping #{mci['name']}, matches skip criteria"
|
27
|
+
else
|
28
|
+
to_st.add_multi_cloud_image(mci['href'])
|
29
|
+
puts "adding #{mci['name']}"
|
30
|
+
end
|
31
|
+
rescue => e
|
32
|
+
puts "image #{mci['name']} already added, skipping" if ENV['DEBUG']
|
33
|
+
to_delete.delete(mci)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
default_mci = mci_payload.find {|d| d['name'] =~ /CentOS.+x64/ and d['name'] !~ /EBS/}
|
37
|
+
unless default_mci
|
38
|
+
default_mci = mci_payload.first
|
39
|
+
end
|
40
|
+
puts "setting default mci #{default_mci['name']}"
|
41
|
+
to_st.set_default_multi_cloud_image(default_mci['href'])
|
42
|
+
unless options[:nice]
|
43
|
+
to_delete.each do |mci|
|
44
|
+
to_st.delete_multi_cloud_image(mci['href'])
|
45
|
+
puts "deleting #{mci['name']}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
puts 'done.'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
MciCp.go(options)
|
data/bin/report_tool
ADDED
@@ -0,0 +1,404 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Copyright RightScale, Inc. All rights reserved. All access and use subject to the
|
4
|
+
# RightScale Terms of Service available at http://www.rightscale.com/terms.php and,
|
5
|
+
# if applicable, other agreements such as a RightScale Master Subscription Agreement.
|
6
|
+
|
7
|
+
#
|
8
|
+
# report_tool: Parses system calls and hint files to return a JSON summary of the running system.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
# JSON not in core-ruby.
|
13
|
+
require 'json'
|
14
|
+
# Platform checking.
|
15
|
+
require 'rbconfig'
|
16
|
+
|
17
|
+
# Monkeypatch to recursively clean empty hashes.
|
18
|
+
class Hash
|
19
|
+
def rec_delete_empty
|
20
|
+
delete_if{|k, v| v.nil? or v.empty? or v.instance_of?(Hash) && v.rec_delete_empty.empty?}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# Borrowed from ohai and modifies to detect hypervisor in rebundles.
|
26
|
+
def hyperChecker
|
27
|
+
#
|
28
|
+
# Modifed by Drew Waranis.
|
29
|
+
# RightScale, Inc. 2012
|
30
|
+
#
|
31
|
+
|
32
|
+
#
|
33
|
+
# Author:: Thom May (<thom@clearairturbulence.org>)
|
34
|
+
# Copyright:: Copyright (c) 2009 Opscode, Inc.
|
35
|
+
# License:: Apache License, Version 2.0
|
36
|
+
#
|
37
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
38
|
+
# you may not use this file except in compliance with the License.
|
39
|
+
# You may obtain a copy of the License at
|
40
|
+
#
|
41
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
42
|
+
#
|
43
|
+
# Unless required by applicable law or agreed to in writing, software
|
44
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
45
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
46
|
+
# See the License for the specific language governing permissions and
|
47
|
+
# limitations under the License.
|
48
|
+
#
|
49
|
+
|
50
|
+
virtualization = Hash.new
|
51
|
+
|
52
|
+
# Try virt-what first.
|
53
|
+
if File.exists?("/usr/sbin/virt-what")
|
54
|
+
return `/usr/sbin/virt-what`.split("\n").first
|
55
|
+
end
|
56
|
+
|
57
|
+
# Xen
|
58
|
+
# /proc/xen is an empty dir for EL6 + Linode Guests
|
59
|
+
if File.exists?("/proc/xen"); return "xen"; end
|
60
|
+
|
61
|
+
# Xen Notes:
|
62
|
+
# - cpuid of guests, if we could get it, would also be a clue
|
63
|
+
# - may be able to determine if under paravirt from /dev/xen/evtchn (See OHAI-253)
|
64
|
+
# - EL6 guests carry a 'hypervisor' cpu flag
|
65
|
+
# - Additional edge cases likely should not change the above assumptions
|
66
|
+
# but rather be additive - btm
|
67
|
+
|
68
|
+
# Detect from kernel module
|
69
|
+
if File.exists?("/proc/modules")
|
70
|
+
if File.read("/proc/modules") =~ /^kvm/; return "vbox"; end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Detect KVM/QEMU from cpuinfo, report as KVM
|
74
|
+
# We could pick KVM from 'Booting paravirtualized kernel on KVM' in dmesg
|
75
|
+
# 2.6.27-9-server (intrepid) has this / 2.6.18-6-amd64 (etch) does not
|
76
|
+
# It would be great if we could read pv_info in the kernel
|
77
|
+
# Wait for reply to: http://article.gmane.org/gmane.comp.emulators.kvm.devel/27885
|
78
|
+
if File.exists?("/proc/cpuinfo")
|
79
|
+
if File.read("/proc/cpuinfo") =~ /QEMU Virtual CPU/; return "kvm"; end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Detect OpenVZ / Virtuozzo.
|
83
|
+
# http://wiki.openvz.org/BC_proc_entries
|
84
|
+
if File.exists?("/proc/vz"); return "openvz"; end
|
85
|
+
|
86
|
+
# http://www.dmo.ca/blog/detecting-virtualization-on-linux
|
87
|
+
if File.exists?("/usr/sbin/dmidecode")
|
88
|
+
dmi_info = `/usr/sbin/dmidecode`
|
89
|
+
case dmi_info
|
90
|
+
when /Manufacturer: Microsoft/ then return "virtualpc"
|
91
|
+
when /Manufacturer: VMware/ then return "vmware"
|
92
|
+
when /Manufacturer: Xen/ then return "xen"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Detect Linux-VServer
|
97
|
+
if File.exists?("/proc/self/status")
|
98
|
+
vxid = File.read("/proc/self/status").match(/^(s_context|VxID): (\d+)$/)
|
99
|
+
if vxid and vxid[2] then return "linux-vserver"; end
|
100
|
+
end
|
101
|
+
|
102
|
+
return nil;
|
103
|
+
end
|
104
|
+
# End hypervisor checker.
|
105
|
+
|
106
|
+
|
107
|
+
# JSON class infrastructure.
|
108
|
+
|
109
|
+
# Inform parser what platform file is from.
|
110
|
+
class OS
|
111
|
+
def initialize()
|
112
|
+
# If it contains linux then Linux, otherwise Windows (future dev).
|
113
|
+
if Config::CONFIG["host_os"] =~ /linux/i
|
114
|
+
@os = "linux"
|
115
|
+
else
|
116
|
+
@os = "windows"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def to_hash(*a) {"os" => @os}.rec_delete_empty end
|
121
|
+
# Strip empty values
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
# Linux Standard Base.
|
126
|
+
# lsb_release: id (i), description (d), release (r), codename (c) .
|
127
|
+
class LSB
|
128
|
+
def initialize()
|
129
|
+
# Retrieve and split command output.
|
130
|
+
lsb = `lsb_release -ircs`.split
|
131
|
+
|
132
|
+
@id = lsb[0]
|
133
|
+
# Called separately to get full description with spaces.
|
134
|
+
# Sanitize newline and quotes.
|
135
|
+
@description = `lsb_release -ds`.sub("\n",'').gsub("\"",'')
|
136
|
+
@release = lsb[1]
|
137
|
+
@codename = lsb[2]
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_hash(*a)
|
141
|
+
{ "lsb" =>
|
142
|
+
{"id" => @id,
|
143
|
+
"description" => @description,
|
144
|
+
"release" => @release,
|
145
|
+
"codename" => @codename}
|
146
|
+
}.rec_delete_empty
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Kernel release name.
|
151
|
+
# Retrieved from /boot/grub/grub.conf if it is available,
|
152
|
+
# else from menu.lst if is available,
|
153
|
+
# else from the vmlinuz filename if it is available,
|
154
|
+
# else nil.
|
155
|
+
# rec_empty_delete strips empty and nil values.
|
156
|
+
class Kern
|
157
|
+
def initialize()
|
158
|
+
# kernel-release is on the first line beginning with "(optional whitespace)initrd".
|
159
|
+
# It is located after the first "-" and should not include an ending ".img", if present.
|
160
|
+
if File.exists? "/boot/grub/grub.conf"
|
161
|
+
@release = IO.read("/boot/grub/grub.conf").match(/^\s*initrd[^-]*-(.*?)(\.img)?$/)[1]
|
162
|
+
elsif File.exists? "/boot/grub/menu.lst"
|
163
|
+
@release = IO.read("/boot/grub/menu.lst").match(/^\s*initrd[^-]*-(.*?)(\.img)?$/)[1]
|
164
|
+
# As a fallback, get kernel-release from the newest vmlinux filename.
|
165
|
+
elsif Dir.entries("/boot/grub/").grep(/vmlinuz*/)
|
166
|
+
@release = (Dir.glob("/boot/vmlinuz*").max_by {|f| File.mtime(f)}).match(/vmlinuz[^-]*-(.*)/)[1]
|
167
|
+
else
|
168
|
+
@release = nil
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def to_hash(*a)
|
173
|
+
{"kernel" =>
|
174
|
+
{"release" => @release}
|
175
|
+
}.rec_delete_empty
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Report the presence (and features they enable) of selected modules.
|
180
|
+
class Mods
|
181
|
+
def initialize(release)
|
182
|
+
|
183
|
+
# Cancel if kernel version wasn't determined.
|
184
|
+
if release.nil?
|
185
|
+
@mods = nil
|
186
|
+
end
|
187
|
+
|
188
|
+
# Manual ist of which modules to check.
|
189
|
+
# Also update the case-statment below.
|
190
|
+
modules_list = %w"dm-mod xfs"
|
191
|
+
|
192
|
+
# Set locations based on kernel version.
|
193
|
+
kernel_config = "/boot/config-#{release}"
|
194
|
+
modules_dir = "/lib/modules/#{release}"
|
195
|
+
|
196
|
+
# Returned by the function to merge into the Kern hash.
|
197
|
+
@mods = Hash.new
|
198
|
+
|
199
|
+
modules_list.each do |mod|
|
200
|
+
case mod
|
201
|
+
when "dm-mod"
|
202
|
+
# Pretty print name.
|
203
|
+
feature = "LVM2 (dm-mod)"
|
204
|
+
# Flag name in the kerne's config file.
|
205
|
+
flag = "CONFIG_BLK_DEV_DM"
|
206
|
+
when "xfs"
|
207
|
+
feature = "XFS (xfs)"
|
208
|
+
flag = "CONFIG_XFS_FS"
|
209
|
+
else
|
210
|
+
# If module is not defined here, then skip.
|
211
|
+
next
|
212
|
+
end
|
213
|
+
|
214
|
+
# Check the kernel's config file, if it exists.
|
215
|
+
if File.exists? "#{kernel_config}"
|
216
|
+
# Check what the flag is set to.
|
217
|
+
case `cat #{kernel_config} | grep #{flag}=`.chomp.split("=")[1]
|
218
|
+
when "y"
|
219
|
+
status = "Built-In"
|
220
|
+
when "m"
|
221
|
+
status = "Module"
|
222
|
+
when "n"
|
223
|
+
status = "Not Selected"
|
224
|
+
end
|
225
|
+
# As a backup, check if it is dynamically compiled in /lib/modules .
|
226
|
+
elsif `find #{modules_dir} -name "#{mod}.ko"`
|
227
|
+
status = "Module"
|
228
|
+
else
|
229
|
+
status = "Not Available"
|
230
|
+
end
|
231
|
+
@mods[feature] = status
|
232
|
+
end
|
233
|
+
end
|
234
|
+
# Strips value if nil.
|
235
|
+
def to_hash(*a)
|
236
|
+
{"modules" => @mods }.rec_delete_empty
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
# List packages on Linux system.
|
241
|
+
# Takes the LSB's id as an argument.
|
242
|
+
class Packages
|
243
|
+
def initialize(id)
|
244
|
+
# Prep packages hash
|
245
|
+
packs = Hash.new
|
246
|
+
|
247
|
+
# Linux distro family specific options:
|
248
|
+
# Ubuntu = dpkg,
|
249
|
+
# CentOS/RHEL = rpm,
|
250
|
+
# or exit.
|
251
|
+
|
252
|
+
case id
|
253
|
+
when "Ubuntu"
|
254
|
+
# Read packages into a hash.
|
255
|
+
`dpkg-query -W`.each_line{ |line|
|
256
|
+
col = line.split
|
257
|
+
packs[col[0]] = col[1]
|
258
|
+
}
|
259
|
+
|
260
|
+
when "CentOS", /RedHat/
|
261
|
+
# Read packages into a hash.
|
262
|
+
`rpm -qa --qf "%{NAME}\t%{VERSION}\n"`.each_line{ |line|
|
263
|
+
col = line.split
|
264
|
+
packs[col[0]] = col[1]
|
265
|
+
}
|
266
|
+
else
|
267
|
+
packs["This distro"] = "is not supported."
|
268
|
+
exit
|
269
|
+
end
|
270
|
+
|
271
|
+
# Store in instance variable to extract rightlink version.
|
272
|
+
@packages = packs
|
273
|
+
end
|
274
|
+
|
275
|
+
def to_hash(*a)
|
276
|
+
{ "packages" => @packages }.rec_delete_empty
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
|
281
|
+
# Holds RS specific info.
|
282
|
+
# Takes hint hash as arg.
|
283
|
+
class RightScaleMirror
|
284
|
+
def initialize(hint)
|
285
|
+
@repo_freezedate = hint["freeze-date"]
|
286
|
+
@rubygems_freezedate = hint["freeze-date"]
|
287
|
+
@rightlink_version = hint["rightlink-version"]
|
288
|
+
end
|
289
|
+
|
290
|
+
def to_hash(*a)
|
291
|
+
{"rightscale-mirror" =>
|
292
|
+
{"repo-freezedate" => @repo_freezedate,
|
293
|
+
"rubygems-freezedate" => @rubygems_freezedate,
|
294
|
+
"rightlink-version" => @rightlink_version
|
295
|
+
}
|
296
|
+
}.rec_delete_empty
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Holds info about the image.
|
301
|
+
# MD5 sums added to report_hash in later step.
|
302
|
+
# Takes hint hash as arg.
|
303
|
+
class Image
|
304
|
+
def initialize(hint)
|
305
|
+
@build_date = hint["build-date"]
|
306
|
+
end
|
307
|
+
|
308
|
+
def to_hash(*a)
|
309
|
+
{"image" =>
|
310
|
+
{"build-date" => @build_date}
|
311
|
+
}.rec_delete_empty
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# Holds the type and version of hypervisor.
|
316
|
+
# Takes hint hash as arg.
|
317
|
+
class Virtualization
|
318
|
+
def initialize(hint)
|
319
|
+
# If the entry is in the hint file, use it.
|
320
|
+
if not hint["hypervisor"].nil?
|
321
|
+
@hypervisor = hint["hypervisor"]
|
322
|
+
# Checks if report_tool is being chrooted.
|
323
|
+
# If not, check for hypervisor.
|
324
|
+
# Chomp return character.
|
325
|
+
elsif `if [ "$(stat -c %d:%i /)" == "$(stat -c %d:%i /proc/1/root/. 2>/dev/null)" ];
|
326
|
+
then echo "true";
|
327
|
+
else echo "false";
|
328
|
+
fi`.chomp == "true"
|
329
|
+
@hypervisor = hyperChecker
|
330
|
+
# report_tool is running in a chroot and and the hypervisor cannot be properly determined.
|
331
|
+
else
|
332
|
+
@hypervisor = nil;
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def to_hash(*a)
|
337
|
+
{"virtualization" =>
|
338
|
+
{"hypervisor" => @hypervisor,
|
339
|
+
"version" => @version}
|
340
|
+
}.rec_delete_empty
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
# Name of the cloud the image is meant for.
|
345
|
+
class Cloud
|
346
|
+
def initialize()
|
347
|
+
# Safely ignores hint if not available.
|
348
|
+
if File.exists? "/etc/rightscale.d/cloud"
|
349
|
+
@provider = File.open("/etc/rightscale.d/cloud", &:readline)
|
350
|
+
else
|
351
|
+
@provider = nil
|
352
|
+
end
|
353
|
+
end
|
354
|
+
# Strips value if nil.
|
355
|
+
def to_hash(*a)
|
356
|
+
{"cloud" =>
|
357
|
+
{"provider" => @provider}
|
358
|
+
}.rec_delete_empty
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
# End JSON class infrastructure.
|
363
|
+
|
364
|
+
# Merge JSON of classes into report_hash.
|
365
|
+
report_hash = Hash.new
|
366
|
+
report_hash.merge!(OS.new)
|
367
|
+
# Switch on OS.
|
368
|
+
if report_hash["os"] != "linux"
|
369
|
+
puts "Windows support is coming... soon."
|
370
|
+
exit
|
371
|
+
end
|
372
|
+
|
373
|
+
# And the rest.
|
374
|
+
report_hash.merge!(LSB.new)
|
375
|
+
report_hash.merge!(Kern.new)
|
376
|
+
# Take kernel version as argument.
|
377
|
+
report_hash.merge!(Mods.new(report_hash["kernel"]["release"]))
|
378
|
+
report_hash.merge!(Cloud.new)
|
379
|
+
|
380
|
+
# Take platform as argument.
|
381
|
+
report_hash.merge!(Packages.new(report_hash["lsb"]["id"]))
|
382
|
+
|
383
|
+
# Give hint hash.
|
384
|
+
if File.exists? "/etc/rightscale.d/rightimage-release.js"
|
385
|
+
hint = JSON.parse(File.read("/etc/rightscale.d/rightimage-release.js"))
|
386
|
+
# Otherwise give empty hint hash.
|
387
|
+
else
|
388
|
+
hint = Hash.new
|
389
|
+
end
|
390
|
+
|
391
|
+
# Receive hint.
|
392
|
+
report_hash.merge!(RightScaleMirror.new(hint))
|
393
|
+
report_hash.merge!(Image.new(hint))
|
394
|
+
report_hash.merge!(Virtualization.new(hint))
|
395
|
+
|
396
|
+
# Print results if flag is set.
|
397
|
+
if(ARGV[0] == "print" )
|
398
|
+
puts JSON.pretty_generate(report_hash)
|
399
|
+
end
|
400
|
+
|
401
|
+
# Save JSON to /tmp.
|
402
|
+
File.open("/tmp/report.js","w") do |f|
|
403
|
+
f.write(JSON.pretty_generate(report_hash))
|
404
|
+
end
|