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/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
|