rightimage_tools 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,7 +1,7 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gem "rest_connection"
4
- gem "ruby-debug"
4
+ gem "right_aws"
5
5
 
6
6
  # Add dependencies to develop your gem here.
7
7
  # Include everything needed to run rake, tests, features, etc.
data/Rakefile CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'bundler'
5
+
5
6
  begin
6
7
  Bundler.setup(:default, :development)
7
8
  rescue Bundler::BundlerError => e
@@ -10,14 +11,18 @@ rescue Bundler::BundlerError => e
10
11
  exit e.status_code
11
12
  end
12
13
  require 'rake'
14
+ require 'lib/rightimage_tools'
13
15
 
14
16
  require 'jeweler'
15
17
  Jeweler::Tasks.new do |gem|
16
18
  # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
19
  gem.name = "rightimage_tools"
20
+ gem.version = RightImageTools::VERSION
18
21
  gem.homepage = "http://github.com/rightscale/rightimage_tools"
19
22
  gem.license = "MIT"
20
23
  gem.summary = %Q{A set of tools to support the building of RightImages}
24
+ gem.files = %w(LICENSE.txt VERSION README.rdoc Rakefile Gemfile) + FileList["{bin,lib}/**/*"].to_a
25
+ gem.executables = %w(image_mover mci_merge update_s3_index)
21
26
  gem.description = gem.summary
22
27
  gem.email = "peter.schroeter@rightscale.com"
23
28
  gem.authors = ["Peter Schroeter"]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
data/bin/image_mover ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'optparse'
5
+ require 'right_aws'
6
+
7
+ THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
8
+ require File.expand_path(File.join(File.dirname(THIS_FILE), '..', 'lib', 's3_html_indexer'))
9
+
10
+ options = {}
11
+ opts = OptionParser.new
12
+ opts.on('-k', '--key KEY', "S3 key path (Ex: kvm/centos/5.6/RightImage_CentOS_5.6_x64_v5.7.14.qcow2.bz2)") { |key| options[:key] = key }
13
+ opts.on('-s', '--source BUCKET', "Source S3 bucket (Ex: rightscale-cloudstack-dev)") { |bucket| options[:bucket_from] = bucket }
14
+ opts.on('-d', '--destination BUCKET', "Destination S3 bucket (Ex: rightscale-cloudstack)") { |bucket| options[:bucket_to] = bucket }
15
+ opts.on_tail('-h', '--help', "Show this message") do
16
+ puts "#{opts}\n"
17
+ return false
18
+ end
19
+
20
+ begin
21
+ opts.parse!
22
+ mandatory = [:bucket_to, :bucket_from, :key]
23
+ missing = mandatory.select { |param| options[param].nil? }
24
+ if not missing.empty?
25
+ puts "Missing options: #{missing.join(", ")}"
26
+ puts opts
27
+ exit
28
+ end
29
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument
30
+ puts $!.to_s
31
+ puts opts
32
+ exit
33
+ end
34
+
35
+ raise "ERROR: you must define AWS_ACCESS_KEY_ID in you environment. " unless ENV["AWS_ACCESS_KEY_ID"]
36
+ raise "ERROR: you must define AWS_SECRET_ACCESS_KEY in you environment. " unless ENV["AWS_SECRET_ACCESS_KEY"]
37
+
38
+ s3 = RightAws::S3.new(ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_ACCESS_KEY"])
39
+ bucket_from = s3.bucket(options[:bucket_from])
40
+ bucket_to = s3.bucket(options[:bucket_to])
41
+
42
+ puts "Moving file #{options[:key]} from #{options[:bucket_from]} to #{options[:bucket_to]}"
43
+ key_from = RightAws::S3::Key.create(bucket_from, options[:key])
44
+ key_to = RightAws::S3::Key.create(bucket_to, options[:key])
45
+ key_from.move(key_to)
46
+ raise "File move failed" unless key_to.exists?
47
+
48
+ puts "Granting public read"
49
+ grant = RightAws::S3::Grantee.new(key_to, 'http://acs.amazonaws.com/groups/global/AllUsers', 'READ', :apply_and_refresh)
50
+ raise "Grant update failed" unless grant
51
+
52
+ puts "Reindexing source bucket #{bucket_from}"
53
+ indexer_from = RightImage::S3HtmlIndexer.new(options[:bucket_from], ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_ACCESS_KEY"])
54
+ indexer_from.to_html("index-from.html")
55
+ indexer_from.upload_index("index-from.html")
56
+
57
+ puts "Reindexing destination bucket #{bucket_to}"
58
+ indexer_to = RightImage::S3HtmlIndexer.new(options[:bucket_to], ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_ACCESS_KEY"])
59
+ indexer_to.to_html("index-to.html")
60
+ indexer_to.upload_index("index-to.html")
data/bin/mci_merge ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'optparse'
5
+ #require 'ruby-debug'
6
+ require 'rest_connection'
7
+ require 'tmpdir'
8
+
9
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'mci'))
10
+
11
+ def aws_id_from_href(href)
12
+ id = href.split("/").last
13
+ id.split("?").first
14
+ end
15
+
16
+ def ask(prompt)
17
+ puts prompt+" "
18
+ res = gets
19
+ unless res =~ /^[yY]/
20
+ puts "Aborting"
21
+ exit 0
22
+ end
23
+ end
24
+
25
+ def parse_options
26
+ opts = OptionParser.new
27
+ options = {}
28
+ opts.on('-s', '--source MCI_SUFFIX', "Suffix of MCI to copy from i.e. 'v5.8.0_Beta_Dev2'") { |name| options[:src] = name}
29
+ opts.on('-d', '--destination MCI_SUFFIX', "Suffix of MCI to copy to i.e. 'v5.8'") { |name| options[:dest] = name}
30
+ opts.on_tail('-h', '--help', "Show help") do
31
+ puts "#{opts}\n"
32
+ return false
33
+ end
34
+
35
+ begin
36
+ opts.parse!
37
+ mandatory = [:src, :dest]
38
+ missing = mandatory.select { |param| options[param].nil? }
39
+ if not missing.empty?
40
+ puts "Missing options: #{missing.join(", ")}"
41
+ puts opts
42
+ exit
43
+ end
44
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument
45
+ puts $!.to_s
46
+ puts opts
47
+ exit
48
+ end
49
+ options
50
+ end
51
+
52
+ options = parse_options
53
+ ENV['REST_CONNECTION_LOG'] = Dir.tmpdir + "/mci_merge_rest.log"
54
+
55
+ mcis = MultiCloudImage.find_all
56
+ sources = []
57
+ dests = []
58
+
59
+ mcis.each do |mci|
60
+ if mci.name =~ /RightImage.*#{Regexp.escape(options[:src])}$/
61
+ sources << MultiCloudImageInternal.find(mci.rs_id.to_i)
62
+ end
63
+ end
64
+
65
+ puts "Found #{sources.length} MCIs to move: "
66
+
67
+ sources.each_with_index do |mci, i|
68
+ # print out source mci
69
+ print i.to_s.ljust(2)+") "
70
+ puts mci.name
71
+ clouds = mci.multi_cloud_image_cloud_settings.map {|s| s.cloud}
72
+ puts " CLOUDS: "+clouds.join(", ")
73
+ print " -->"
74
+
75
+ # find a destination mci and print that if found
76
+ # else note we'll make a new mci for the destination
77
+ name_no_suffix = mci.name.sub(options[:src],"")
78
+ dest_mci_name = name_no_suffix + options[:dest]
79
+ found = mcis.find { |new_mci| new_mci.name == dest_mci_name }
80
+ if found
81
+ dest_mci = MultiCloudImageInternal.find(found.rs_id.to_i)
82
+ dests << dest_mci
83
+ print dest_mci.name
84
+ else
85
+ dests << dest_mci_name
86
+ print "NEW MCI: "
87
+ print dest_mci_name
88
+ end
89
+ puts ""
90
+ end
91
+
92
+ ask("Continue - Y/N?")
93
+
94
+ mci_tool = RightImageTools::MCI.new
95
+ sources.each_with_index do |src_mci, i|
96
+ dest_mci = dests[i]
97
+ if dest_mci.class == String
98
+ src_mci.multi_cloud_image_cloud_settings.each do |s|
99
+ image_id = aws_id_from_href(s.image_href)
100
+ puts "New MCI with image: cloud_id=#{s.cloud_id.to_i}, mci_name=#{dest_mci}, image_id=#{image_id}"
101
+ mci_tool.add_image_to_mci(
102
+ :cloud_id=>s.cloud_id.to_i,
103
+ :name=>dest_mci,
104
+ :image_id=>image_id)
105
+ end
106
+ else
107
+ src_mci.multi_cloud_image_cloud_settings.each do |s|
108
+ image_id = aws_id_from_href(s.image_href)
109
+ puts "Adding image to MCI #{dest_mci.rs_id}: cloud_id=#{s.cloud_id.to_i}, mci_id=#{dest_mci.rs_id}, image_id=#{image_id}"
110
+ mci_tool.add_image_to_mci(
111
+ :cloud_id=>s.cloud_id.to_i,
112
+ :mci_id=>dest_mci.rs_id,
113
+ :image_id=>image_id)
114
+ end
115
+ end
116
+ end
117
+
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
4
+ require File.expand_path(File.join(File.dirname(THIS_FILE), '..', 'lib', 's3_html_indexer'))
5
+
6
+ raise "ERROR: you must define AWS_IMAGE_BUCKET in you environment. " unless ENV["AWS_IMAGE_BUCKET"]
7
+ raise "ERROR: you must define AWS_ACCESS_KEY_ID in you environment. " unless ENV["AWS_ACCESS_KEY_ID"]
8
+ raise "ERROR: you must define AWS_SECRET_ACCESS_KEY in you environment. " unless ENV["AWS_SECRET_ACCESS_KEY"]
9
+
10
+ indexer = RightImageTools::S3HtmlIndexer.new(ENV["AWS_IMAGE_BUCKET"], ENV["AWS_ACCESS_KEY_ID"], ENV["AWS_SECRET_ACCESS_KEY"])
11
+ indexer.to_html("index.html")
12
+ indexer.upload_index("index.html")
data/lib/id_list.rb ADDED
@@ -0,0 +1,71 @@
1
+ module RightImage
2
+ require 'json'
3
+
4
+ # Manages a global store of image ids for images that have been
5
+ # on a single rightimage creator instance.
6
+ # The image data is persisted on the local filesystem.
7
+ #
8
+ class IdList
9
+
10
+ def initialize(logger = nil)
11
+ @log = (logger) ? logger : Logger.new(STDOUT)
12
+ @file = ::File.join(ENV["HOME"], "rightimage_id_list")
13
+ end
14
+
15
+ # Pass in id that the cloud provider passes back upon registration
16
+ # NOTE: Be sure to set storage type for EC2 "EBS" images so we can
17
+ # properly know what kind of MCI to make later.
18
+ #
19
+ def add(id, storage_type = nil)
20
+ list_load
21
+ key = id.to_s
22
+ key.chomp!
23
+ @log.info("Adding #{key} to #{(@list) ? 'existing' : 'empty'} id list.")
24
+ entry = { key => { } }
25
+ entry[key]["storage_type"] = storage_type if storage_type
26
+ @list.merge!(entry)
27
+ list_save
28
+ end
29
+
30
+ # Returns a hash of image ids registered from this instance.
31
+ # The keys are image ids, the values contain metadata
32
+ # (like "storage_type")
33
+ # Intended to be used in a loop. For Example:
34
+ # images = RightImage::IdList.new(Chef::Log).to_hash
35
+ # images.each do |id, params|
36
+ # ...
37
+ # end
38
+ #
39
+ def to_hash
40
+ list_load
41
+ @log.info("Loaded #{(@list)?"existing":"empty"} id list.")
42
+ @list
43
+ end
44
+
45
+ # Wipes out the list by deleting the file.
46
+ #
47
+ def clear
48
+ if ::File.exists?(@file)
49
+ @log.info("Deleted id list file.")
50
+ ::File.delete(@file)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def list_save
57
+ ::File.open(@file, "w") { |f| f.write(@list.to_json) }
58
+ end
59
+
60
+ def list_load
61
+ @list = { }
62
+ if ::File.exists?(@file)
63
+ json = nil
64
+ ::File.open(@file, "r") { |f| json = f.read() }
65
+ @list = JSON.parse(json) if json
66
+ end
67
+ end
68
+ end
69
+
70
+ end
71
+
data/lib/mci.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'logger'
3
- require 'ruby-debug'
3
+ #require 'ruby-debug'
4
4
  require 'rest_connection'
5
5
 
6
6
  module RightImageTools
@@ -55,12 +55,14 @@ module RightImageTools
55
55
  def add_image_to_mci(options)
56
56
  cloud_id = options[:cloud_id].to_i
57
57
  image_id = options[:image_id]
58
- mci_name = options[:name]
58
+ mci_name = options[:name] || options[:mci_name]
59
+ mci_id = options[:mci_id] || options[:id]
59
60
  description = options[:description]
60
61
 
61
62
  raise ArgumentError, ":cloud_id not supplied" unless cloud_id > 0
62
63
  raise ArgumentError, ":image_id not supplied" unless image_id =~ /./
63
- raise ArgumentError, "MCI name (:name) not supplied" unless mci_name =~ /./
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+$/
64
66
 
65
67
  image_id = image_id.split("/").last
66
68
 
@@ -69,8 +71,9 @@ module RightImageTools
69
71
  raise ArgumentError, "image_id #{image_id} doesn't look like an amazon ami"
70
72
  end
71
73
  end
72
-
73
- mci = find_mci(cloud_id, mci_name)
74
+
75
+ mci = find_mci_by_id(cloud_id, mci_id) if mci_id
76
+ mci = find_mci(cloud_id, mci_name) unless mci
74
77
  #validate_mci_name(mci_name) unless mci
75
78
  mci = create_mci(cloud_id, mci_name, description) unless mci
76
79
  add_rightlink_tag(mci)
@@ -135,6 +138,17 @@ module RightImageTools
135
138
  end
136
139
 
137
140
  # return MultiCloudImageInternal, or nil
141
+ def find_mci_by_id(cloud_id,mci_id)
142
+ if api_version(cloud_id) == "1.0"
143
+ existing_mci = MultiCloudImageInternal.find(mci_id.to_i)
144
+ else
145
+ existing_mci = McMultiCloudImage.find(mci_id.to_i)
146
+ end
147
+ raise ArgumentError, "Could not find #{existing_mci.rs_id}" unless existing_mci
148
+ @logger.info("Using mci #{existing_mci.rs_id} named #{existing_mci.name}")
149
+ existing_mci
150
+ end
151
+
138
152
  def find_mci(cloud_id, mci_name)
139
153
  mcis = []
140
154
  if api_version(cloud_id) == "1.0"
@@ -3,3 +3,7 @@ module RightImageTools
3
3
  end
4
4
 
5
5
  require File.join(File.dirname(__FILE__), 'mci')
6
+ require File.join(File.dirname(__FILE__), 'id_list')
7
+ require File.join(File.dirname(__FILE__), 'util')
8
+ # command line tool
9
+ #require File.join(File.dirname(__FILE__), 's3_html_indexer')
@@ -0,0 +1,251 @@
1
+ require 'rubygems'
2
+ require 'right_aws'
3
+
4
+ module RightImageTools
5
+
6
+ class S3HtmlIndexer
7
+
8
+ def initialize(bucket, id=nil, key=nil)
9
+ @s3 = RightAws::S3.new(id, key)
10
+ @dev_bucket = bucket.downcase.include?("dev")
11
+ @bucket = @s3.bucket(bucket)
12
+ @keys = @bucket.keys
13
+ @auto_hash = Hash.new{ |h,k| h[k] = Hash.new &h.default_proc }
14
+ end
15
+
16
+ def ver(key)
17
+ key.match(/v[0-9]+\.[0-9]+\.[0-9]+/).to_s
18
+ end
19
+
20
+ def to_html(filename)
21
+ # Create hash hierarchy from bucket list
22
+ @keys.reject! { |path| path.full_name =~ /index.html/ }
23
+ #@keys.sort! {|x,y| ver(x.full_name) <=> ver(y.full_name) } #TODO: sort by rightlink version
24
+ @keys.each do |path|
25
+
26
+ next unless is_public?(path)
27
+
28
+ sub = @auto_hash
29
+ path.full_name.split( "/" ).each do |dir|
30
+ sub = sub[dir]
31
+ end
32
+
33
+ sub[:link] = fix_public_endpoint(path.public_link)
34
+ sub[:size] = path.size
35
+ sub[:date] = path.last_modified.strftime("%m/%d/%Y")
36
+ sub[:md5sum] = path.e_tag.gsub(/\"/, "")
37
+
38
+ end
39
+
40
+ # make a non-auto hash copy
41
+ dirs = Hash.new
42
+ dirs = dirs.merge(@auto_hash)
43
+
44
+ # visualize hierarchy
45
+ dirs.each do |k,v|
46
+ @file = File.open(filename, "w")
47
+ output("<html>")
48
+ add_style
49
+ display_dirs(k,v)
50
+ output("</html>")
51
+ @file.close
52
+ end
53
+ end
54
+
55
+ def upload_index(filename)
56
+ @bucket.put("index.html", File.open(filename), {}, 'public-read')
57
+ end
58
+
59
+ private
60
+
61
+ def is_public?(key)
62
+ key.refresh
63
+ puts "Key: #{key.name}"
64
+ key.grantees.each do |g|
65
+ puts "grantee:#{g.name} perms:#{g.perms}"
66
+ if g.name.to_s == "AllUsers" && g.perms.to_s == "READ"
67
+ puts "Is public!!!"
68
+ return true
69
+ end
70
+ end
71
+ false
72
+ end
73
+
74
+ def display_dirs(k,v,level=0)
75
+ return unless v
76
+ unless v.keys.include?(:link)
77
+ add_header(k, level)
78
+ v.sort_by {|k2, v2| k2}.each do |k2, v2|
79
+ display_dirs(k2, v2, level+1)
80
+ end
81
+ add_footer(k, level)
82
+ else
83
+ output("<tr class='code'>")
84
+ output("<td><a href='#{v[:link]}'>#{k}</a></td>")
85
+ output("<td>#{v[:size].to_i/(1024*1024)}MB</td>")
86
+ output("<td>#{v[:date]}</td>")
87
+ output("<td>#{v[:md5sum]}</td>")
88
+ output("</tr>")
89
+ end
90
+ end
91
+
92
+ def disclaimer
93
+ if @dev_bucket
94
+ "The following RightImages are currently in RightScale development and testing. They are not supported by RightScale. For RightImages that RightScale supports, please see our <a href='http://rightscale-cloudstack.s3-website-us-west-1.amazonaws.com/index.html'>production images</a>"
95
+ else
96
+ "The following RightImages 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
+ end
98
+ end
99
+
100
+ def add_header(str, level)
101
+ case level
102
+ when 0
103
+ # bucket name
104
+ output("<div id='header'><img alt='Embedded Image' width='140' height='19' src='#{encoded_logo}' /></div>")
105
+ output("<div class='bucket common'>#{str.gsub(/rightscale-/,"").upcase}")
106
+ output("<br/><br/>")
107
+ output("<div class='disclaimer code common'>#{disclaimer}</div>")
108
+ output("<br/>")
109
+ output("</div>")
110
+ when 1
111
+ # Hypervisor
112
+ output("<div class='hypervisor common'>")
113
+ output("<h#{level-1}>#{str.upcase} Hypervisor</h#{level-1}>\n")
114
+ output("</div>")
115
+ output("<div class='platform common'>")
116
+ output("<table style='padding:none; size:90%'>")
117
+ output("<tr style='border: solid 1px black;'>")
118
+ output("<th style='text-align:left'>Image</th>")
119
+ output("<th>Size</th>")
120
+ output("<th>Date</th>")
121
+ output("<th>MD5</th>")
122
+ output("</tr>")
123
+ when 2
124
+ return
125
+ # OS
126
+ when 3
127
+ return
128
+ else
129
+ # image
130
+ # output("<div class='platform'>")
131
+ end
132
+ end
133
+
134
+ def add_footer(str, level)
135
+ case level
136
+ when 0
137
+ when 1
138
+ output("</table>")
139
+ output("</div>")
140
+ output("<br/>")
141
+ output("</div>")
142
+ output("<br/><br/>")
143
+ when 2
144
+ return
145
+ when 3
146
+ return
147
+ else
148
+ # output("</div>")
149
+ end
150
+ end
151
+
152
+ # https://s3.amazonaws.com/rightscale-cloudstack/foo
153
+ # to
154
+ # https://rightscale-cloudstack.s3.amazonaws.com
155
+ def fix_public_endpoint(link)
156
+ link_ary = link.split("/")
157
+ bucket = link_ary[3]
158
+ host = link_ary[2]
159
+ link_ary[2] = "#{bucket}.#{host}"
160
+ link_ary.delete_at(3)
161
+ link_ary.join("/")
162
+ end
163
+
164
+ def output(str)
165
+ @file.write "#{str}\n" if @file
166
+ p str
167
+ end
168
+
169
+ def add_style
170
+ output "<style>"
171
+ output( "body {min-width: 600px; margin:0; padding:0; font-size:70.5%; /* font-family:'Lucida Sans', Verdana, Arial, sans-serif; */ font-family:'Lucida Grande','Lucida Sans Unicode','Lucida Sans',Verdana,lucida,sans-serif; background:#FFF; height:100%}
172
+ html {height:100%}
173
+ a img {border:none}
174
+ a {color:#1e4a7e}
175
+ table { width:90% }
176
+ th { text-align:center }
177
+
178
+ #header {
179
+ background:#235186;
180
+ padding:2px 20px;
181
+ position:relative;
182
+ height:25px;
183
+ }
184
+
185
+ #header #logo {
186
+ margin-top:3px;
187
+ }
188
+
189
+ .common {
190
+ font-size: 16px;
191
+ margin-left:auto;
192
+ margin-right:auto;
193
+ font-weight: bold;
194
+ padding:2px 20px;
195
+ position:relative;
196
+ }
197
+
198
+ .bucket {
199
+ font-size: 24px;
200
+ color: #F5F2A9;
201
+ background:#235186;
202
+ padding:10px 30px 20px;
203
+ }
204
+
205
+ .version {
206
+ color: #F5F2A9;
207
+ background:#235186;
208
+ }
209
+
210
+ .hypervisor {
211
+ color: #235186;
212
+ background:#F5F2A9;
213
+ }
214
+
215
+ .platform {
216
+ background: #FFFFFF;
217
+ width:90%;
218
+ margin-left: auto ;
219
+ margin-right: auto ;
220
+ position: relative;
221
+ }
222
+
223
+ .disclaimer {
224
+ background: #FFFFFF;
225
+ width:90%;
226
+ margin-left: auto ;
227
+ margin-right: auto ;
228
+ position: relative;
229
+ border: 2px solid black;
230
+ font-weight: normal;
231
+ }
232
+
233
+ .code {
234
+ font-family:'Courier New', monospace;
235
+ color: #000000;
236
+ position:relative;
237
+ left:10px;
238
+ }
239
+ ")
240
+ output "</style>"
241
+ end
242
+
243
+ def encoded_logo
244
+ "data:image/png;base64,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%3D%3D"
245
+ end
246
+
247
+ end
248
+ end
249
+
250
+
251
+
data/lib/util.rb ADDED
@@ -0,0 +1,50 @@
1
+ module RightImage
2
+
3
+ RELEASE_LEVELS = ["Alpha", "Beta", "Ga"]
4
+ HIDDEN_RELEASE_LEVELS = [ "Ga" ]
5
+
6
+ class Util
7
+
8
+ def self.create_image_name(image_prefix, os_name, os_version, os_arch, os_modifier, right_image_version, right_image_build = nil, image_build_number = nil, storage_type = nil, release_level = nil, suffix = nil)
9
+
10
+ image_name = "#{image_prefix}_#{os_name}_#{os_version.to_s}_#{os_arch}"
11
+
12
+ unless os_modifier.nil? || os_modifier.empty?
13
+ image_name << "_#{os_modifier}"
14
+ end
15
+
16
+ package_version = rightimage_version(right_image_version, right_image_build, image_build_number)
17
+ image_name << "_v#{package_version}"
18
+ image_name << "_#{storage_type}" if storage_type && storage_type.downcase != "s3"
19
+
20
+ if release_level
21
+ level = release_level.capitalize
22
+ raise "ERROR: release_level must be set to 'Alpha', 'GA', or 'Beta'" unless RELEASE_LEVELS.include?(level)
23
+ image_name << "_#{level}" unless HIDDEN_RELEASE_LEVELS.include?(level)
24
+ end
25
+
26
+ image_name << "_#{suffix.capitalize}" if suffix
27
+
28
+ image_name
29
+ end
30
+
31
+ def self.rightimage_package_version(version, build_number=nil)
32
+ raise "ERROR: version must be a 'N.N' format." unless version.to_s =~ /^[0-9]*\.[0-9]*$/
33
+ ver = "#{version.to_s}"
34
+ ver << ".#{build_number}" if build_number
35
+ ver
36
+ end
37
+
38
+ def self.rightimage_version(package_version, package_build=nil, image_build = nil)
39
+ ver = rightimage_package_version(package_version, package_build)
40
+ ver << ".#{image_build}" unless image_build == 0 || image_build == nil
41
+ ver
42
+ end
43
+
44
+ def self.create_mci_name(image_prefix, os_name, os_version, os_arch, os_modifier, right_image_version, storage_type = nil, release_level = nil, suffix = nil)
45
+ create_image_name(image_prefix, os_name, os_version, os_arch, os_modifier, right_image_version, nil, nil, storage_type, release_level, suffix)
46
+ end
47
+
48
+
49
+ end
50
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rightimage_tools
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 0.1.0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Peter Schroeter
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-02-07 00:00:00 Z
18
+ date: 2012-02-27 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  version_requirements: &id001 !ruby/object:Gem::Requirement
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: "0"
44
44
  requirement: *id002
45
45
  prerelease: false
46
- name: ruby-debug
46
+ name: right_aws
47
47
  type: :runtime
48
48
  - !ruby/object:Gem::Dependency
49
49
  version_requirements: &id003 !ruby/object:Gem::Requirement
@@ -123,26 +123,29 @@ dependencies:
123
123
  type: :development
124
124
  description: A set of tools to support the building of RightImages
125
125
  email: peter.schroeter@rightscale.com
126
- executables: []
127
-
126
+ executables:
127
+ - image_mover
128
+ - mci_merge
129
+ - update_s3_index
128
130
  extensions: []
129
131
 
130
132
  extra_rdoc_files:
131
133
  - LICENSE.txt
132
134
  - README.rdoc
133
135
  files:
134
- - .document
135
- - .rspec
136
136
  - Gemfile
137
- - Gemfile.lock
138
137
  - LICENSE.txt
139
138
  - README.rdoc
140
139
  - Rakefile
141
140
  - VERSION
141
+ - bin/image_mover
142
+ - bin/mci_merge
143
+ - bin/update_s3_index
144
+ - lib/id_list.rb
142
145
  - lib/mci.rb
143
146
  - lib/rightimage_tools.rb
144
- - spec/mci_spec.rb
145
- - spec/spec_helper.rb
147
+ - lib/s3_html_indexer.rb
148
+ - lib/util.rb
146
149
  homepage: http://github.com/rightscale/rightimage_tools
147
150
  licenses:
148
151
  - MIT
data/.document DELETED
@@ -1,5 +0,0 @@
1
- lib/**/*.rb
2
- bin/*
3
- -
4
- features/**/*.feature
5
- LICENSE.txt
data/.rspec DELETED
@@ -1 +0,0 @@
1
- --color
data/Gemfile.lock DELETED
@@ -1,50 +0,0 @@
1
- GEM
2
- remote: http://rubygems.org/
3
- specs:
4
- activesupport (2.3.10)
5
- columnize (0.3.4)
6
- diff-lcs (1.1.3)
7
- flexmock (0.9.0)
8
- git (1.2.5)
9
- highline (1.6.2)
10
- jeweler (1.6.4)
11
- bundler (~> 1.0)
12
- git (>= 1.2.5)
13
- rake
14
- json (1.6.1)
15
- linecache (0.46)
16
- rbx-require-relative (> 0.0.4)
17
- net-ssh (2.1.4)
18
- rake (0.9.2.2)
19
- rbx-require-relative (0.0.5)
20
- rcov (1.0.0)
21
- rest_connection (0.1.2)
22
- activesupport (= 2.3.10)
23
- highline
24
- json
25
- net-ssh (= 2.1.4)
26
- rspec (2.3.0)
27
- rspec-core (~> 2.3.0)
28
- rspec-expectations (~> 2.3.0)
29
- rspec-mocks (~> 2.3.0)
30
- rspec-core (2.3.1)
31
- rspec-expectations (2.3.0)
32
- diff-lcs (~> 1.1.2)
33
- rspec-mocks (2.3.0)
34
- ruby-debug (0.10.4)
35
- columnize (>= 0.1)
36
- ruby-debug-base (~> 0.10.4.0)
37
- ruby-debug-base (0.10.4)
38
- linecache (>= 0.3)
39
-
40
- PLATFORMS
41
- ruby
42
-
43
- DEPENDENCIES
44
- bundler (~> 1.0.0)
45
- flexmock
46
- jeweler (~> 1.6.4)
47
- rcov
48
- rest_connection
49
- rspec (~> 2.3.0)
50
- ruby-debug
data/spec/mci_spec.rb DELETED
@@ -1,97 +0,0 @@
1
- require 'flexmock/rspec'
2
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
-
4
- describe RightImageTools::MCI do
5
-
6
- before :each do
7
- @mci = RightImageTools::MCI.new
8
-
9
- @mock_mci_name = "Mock_RightImage_Ubuntu_10.04_x64_v5.7"
10
- @mock_ami = "ami-2ehahaha"
11
-
12
- @mock_mci = flexmock(MultiCloudImage,
13
- :is_head_version => true,
14
- :name => @mock_mci_name,
15
- :rs_id => "10000",
16
- :version=>0,
17
- :href=>"https://my.rightscale.com/api/acct/99999/multi_cloud_images/10000")
18
-
19
- @mock_mci_setting1 = flexmock(MultiCloudImageCloudSettingInternal,
20
- :href=>"https://my.rightscale.com/api/acct/99999/multi_cloud_image_cloud_settings/20000",
21
- :aws_instance_type =>"m1.large",
22
- :image_name => @mock_mci_name,
23
- :image_href => "https://my.rightscale.com/api/acct/0/ec2_images/#{@mock_ami}?cloud_id=5",
24
- :cloud_id=>5,
25
- :cloud=>"AWS AP-Tokyo")
26
-
27
- @mock_mci_i = flexmock(MultiCloudImageInternal,
28
- :is_head_version => @mock_mci.is_head_version,
29
- :name=>@mock_mci.name,
30
- :rs_id => @mock_mci.rs_id,
31
- :version=>@mock_mci.version,
32
- :href=>@mock_mci.href,
33
- :multi_cloud_image_cloud_settings=>[@mock_mci_setting1])
34
- end
35
-
36
- # it "finds a MCI for gateway cloud (cloudstack)" do
37
- # pending "TODO"
38
- # end
39
- # it "creates a MCI for gateway cloud (cloudstack)" do
40
- # pending "TODO"
41
- # end
42
- # it "associates an image for a gateway cloud (cloudstack)" do
43
- # pending "TODO"
44
- # end
45
- # it "replaces an image association for a gateway cloud (cloudstack)" do
46
- # pending "TODO"
47
- # end
48
- #
49
- it "finds a MCI for EC2 cloud" do
50
- flexmock(MultiCloudImage).should_receive(:find_all).and_return([@mock_mci])
51
- flexmock(MultiCloudImageInternal).should_receive(:find).with(10000).and_return(@mock_mci_i)
52
-
53
- @mci.find_mci(6, @mock_mci_name).rs_id.should == "10000"
54
- end
55
- it "creates a MCI for EC2 mock cloud" do
56
- flexmock(MultiCloudImageInternal).should_receive(:create).with(
57
- :name=>@mock_mci_name,
58
- :description => "blah"
59
- ).and_return(flexmock(MultiCloudImageInternal))
60
- @mci.create_mci(6, @mock_mci_name, "blah")
61
- end
62
-
63
- it "associates an image to an EC2 cloud" do
64
- flexmock(MultiCloudImage).should_receive(:find_all).and_return([@mock_mci])
65
- flexmock(MultiCloudImageInternal).should_receive(:find).with(10000).and_return(@mock_mci_i)
66
- flexmock(Tag).should_receive(:set).and_return(flexmock(Tag, :code=>"204"))
67
- flexmock(MultiCloudImageCloudSettingInternal).should_receive(:create)
68
-
69
- @mci.add_image_to_mci(
70
- :cloud_id=>6,
71
- :name=>@mock_mci_name,
72
- :image_id=>"ami-b1ahb1ah")
73
- end
74
-
75
- it "replaces an image association for an EC2 cloud" do
76
- flexmock(MultiCloudImage).should_receive(:find_all).and_return([@mock_mci])
77
- flexmock(MultiCloudImageInternal).should_receive(:find).with(10000).and_return(@mock_mci_i)
78
- flexmock(Tag).should_receive(:set).and_return(flexmock(Tag, :code=>"204"))
79
- flexmock(MultiCloudImageCloudSettingInternal).should_receive(:destroy).and_return(flexmock("Response",:code=>"200"))
80
- flexmock(MultiCloudImageCloudSettingInternal).should_receive(:create)
81
-
82
- @mci.add_image_to_mci(
83
- :cloud_id=>5,
84
- :name=>@mock_mci_name,
85
- :image_id=>"ami-b1ahb1ah")
86
- end
87
-
88
- it "sets a default description correctly" do
89
- flexmock(MultiCloudImageInternal).should_receive(:create).with(
90
- :name=>@mock_mci_name,
91
- :description => "Development build: Ubuntu 10.04 with 64-bit architecture (x64) and RightLink 5.7."
92
- ).and_return(@mock_mci)
93
-
94
- @mci.create_mci(5, @mock_mci_name)
95
- end
96
-
97
- end
data/spec/spec_helper.rb DELETED
@@ -1,13 +0,0 @@
1
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
- $LOAD_PATH.unshift(File.dirname(__FILE__))
3
- require 'rspec'
4
- require 'rightimage_tools'
5
-
6
-
7
- # Requires supporting files with custom matchers and macros, etc,
8
- # in ./support/ and its subdirectories.
9
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
10
-
11
- RSpec.configure do |config|
12
- config.mock_with :flexmock
13
- end