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 CHANGED
@@ -1,14 +1,16 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gem "rest_connection"
4
+ gem "right_api_client"
4
5
  gem "right_aws"
5
6
 
6
7
  # Add dependencies to develop your gem here.
7
8
  # Include everything needed to run rake, tests, features, etc.
8
9
  group :development do
9
10
  gem "rspec", "~> 2.3.0"
10
- gem "bundler", "~> 1.0.0"
11
+ # gem "bundler", "~> 1.1.4"
11
12
  gem "jeweler", "~> 1.6.4"
12
13
  gem "rcov", ">= 0"
13
14
  gem "flexmock"
15
+ gem "json", "~> 1.7.5"
14
16
  end
data/Rakefile CHANGED
@@ -22,7 +22,10 @@ Jeweler::Tasks.new do |gem|
22
22
  gem.license = "MIT"
23
23
  gem.summary = %Q{A set of tools to support the building of RightImages}
24
24
  gem.files = %w(LICENSE.txt VERSION README.rdoc Rakefile Gemfile) + FileList["{bin,lib,spec}/**/*"].to_a
25
- gem.executables = %w(image_mover mci_merge update_s3_index)
25
+ # Include built ui files while ignoring their sources.
26
+ # Used to include report_viewer.html.
27
+ gem.files.include FileList["ui/build/**/*"].to_a
28
+ gem.executables = %w(image_mover mci_merge report_tool update_s3_index)
26
29
  gem.description = gem.summary
27
30
  gem.email = "peter.schroeter@rightscale.com"
28
31
  gem.authors = ["Peter Schroeter"]
@@ -30,25 +33,3 @@ Jeweler::Tasks.new do |gem|
30
33
  end
31
34
  Jeweler::RubygemsDotOrgTasks.new
32
35
 
33
- require 'rspec/core'
34
- require 'rspec/core/rake_task'
35
- RSpec::Core::RakeTask.new(:spec) do |spec|
36
- spec.pattern = FileList['spec/**/*_spec.rb']
37
- end
38
-
39
- RSpec::Core::RakeTask.new(:rcov) do |spec|
40
- spec.pattern = 'spec/**/*_spec.rb'
41
- spec.rcov = true
42
- end
43
-
44
- task :default => :spec
45
-
46
- require 'rake/rdoctask'
47
- Rake::RDocTask.new do |rdoc|
48
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
-
50
- rdoc.rdoc_dir = 'rdoc'
51
- rdoc.title = "rightimage_tools #{version}"
52
- rdoc.rdoc_files.include('README*')
53
- rdoc.rdoc_files.include('lib/**/*.rb')
54
- end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.1
1
+ 0.5.0
data/bin/azure_api ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ruby-debug'
4
+ require 'right_cloud_api'
5
+ require File.expand_path("../../lib/azure_helper",__FILE__)
6
+ require 'cloud/azure/storage/manager'
7
+ require 'uri'
8
+ require 'yaml'
9
+
10
+ include AzureHelper
11
+
12
+ account = ARGV.shift
13
+ cmd = ARGV.shift
14
+ params = {}
15
+ ARGV.each do |p|
16
+ (k,v) = p.split(/[=:]/)
17
+ params[k] = v
18
+ end
19
+
20
+ unless account && cmd
21
+ puts "USAGE: azure_api <account> <api_command> [param1=value1] [param2=value2]"
22
+ puts "EXAMPLE: azure_api rightscalewestus ListBlobs Container=rightimage-linux"
23
+ exit 1
24
+ end
25
+
26
+ password = lookup_password(account)
27
+ endpoint = "https://#{account}.blob.core.windows.net"
28
+
29
+ @azure = RightScale::CloudApi::Azure::Storage::Manager.new(account, password, endpoint, :api_version=>'2012-02-12')
30
+ puts "CALLING #{cmd} WITH #{params.inspect}"
31
+ api_dump(@azure,cmd,params)
data/bin/azure_migrate ADDED
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ruby-debug'
4
+ require 'right_cloud_api'
5
+ require 'cloud/azure/storage/manager'
6
+ require File.expand_path("../../lib/azure_helper",__FILE__)
7
+ require 'uri'
8
+ require 'yaml'
9
+
10
+ include AzureHelper
11
+
12
+ class CopyState
13
+ attr_accessor :account
14
+ attr_accessor :api
15
+ attr_accessor :state
16
+ def initialize(api, account, state = "pending")
17
+ @api = api
18
+ @account = account
19
+ @state = state
20
+ end
21
+ end
22
+
23
+ url_src = ARGV.shift
24
+ url_dst = ARGV.shift
25
+ image_name = ARGV.shift
26
+ unless url_src =~ /^http/ and url_dst =~ /^http|^(linux|rswin|mswin|windows)$/
27
+ puts <<-EOF
28
+ USAGE: azure_migrate URL_TO_SRC_BLOB URL_TO_DEST_BLOB
29
+ USAGE: azure_migrate URL_TO_SRC_BLOB <linux|rswin|mswin> <image name>
30
+ if URL_TO_DEST_BLOB is 'windows' or 'linux', program will copy
31
+ image to all linux or windows image accounts. 'rswin' for rightscale
32
+ accounts (for testing), and mswin is microsoft (official publish)
33
+ also acceptable parameters
34
+ For the first usage you might use this for migrating to non-standard
35
+ accounts, such as test or services
36
+ Second usage is preferred for a publishing type event
37
+ EXAMPLE: azure_migrate https://linuxros.blob.core.windows.net/vm-images/RightImage-CentOS-6.2-x64-v5.8.8.1.vhd https://rightscalewestus.blob.core.windows.net/vm-images/RightImage-CentOS-6.2-x64-v5.8.8.1.vhd
38
+ EXAMPLE: azure_migrate https://linuxros.blob.core.windows.net/vm-images/RightImage-CentOS-6.2-x64-v5.8.8.1.vhd linux RightImage-CentOS-6.2-x64-v5.8.8.1.vhd
39
+ EXAMPLE: azure_migrate http://ds1p6hm133skyrl.blob.core.windows.net/vhds/1vqtgdr5.xwr20120719072616.vhd rswin RightImage-Windows-2008R2-SP1-x64-sqlsvr2012-v5.8.8.vhd
40
+ EOF
41
+ exit 1
42
+ end
43
+
44
+ # This function takes an url to a blob and grants read access to it
45
+ # by signing a request for read access with the account's shared key
46
+ # The request is made using GET parameters described in bits hash
47
+ def generate_shared_access_key(url)
48
+ (account,password,endpoint,container,blob) = parse_url(url)
49
+
50
+ url_new = url.dup
51
+ t = Time.now.utc
52
+ # t1 = Time.now.utc + (60*60*24)
53
+ # fmt = '%Y-%m-%d'
54
+ t1 = Time.now.utc + (3600*8)
55
+ fmt = '%Y-%m-%dT%H:%MZ'
56
+ sig_fields = ["sp","st","se","crsrc","si", "sv"]
57
+ get_param_fields = ["sp","st","se","sr", "sv", "sig"]
58
+ bits = {
59
+ 'sp' => 'r', # grant holder of this url read access
60
+ 'sr' => 'b', # for a blob
61
+ 'crsrc' => "/#{account}/#{container}/#{blob}", # denoted by this resource
62
+ 'st' => t.strftime(fmt), # starting now
63
+ 'se' => t1.strftime(fmt), # and ending in 8 hours
64
+ 'sv' => '2012-02-12' #api version, required
65
+ }
66
+ header = sig_fields.map {|b| bits[b] || ""}.join("\x0A")
67
+ header = header.toutf8 if header.respond_to?(:toutf8)
68
+ # Now sign the request with the shared key
69
+ bits["sig"] = Base64.encode64(HMAC::SHA256.new(Base64.decode64(password)).update(URI.decode(header)).digest).chomp
70
+ get_params = get_param_fields.map do |p|
71
+ p+"="+URI.escape(bits[p], Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
72
+ end
73
+ url_new << "?" << get_params.join("&")
74
+ # puts "HEADER: ---------------"
75
+ puts header
76
+ puts "-----------------------"
77
+ puts "URL: #{url}"
78
+ puts "SIGNATURE: #{bits["sig"]}"
79
+ puts "URL_SIGNED: #{url_new}"
80
+ return url_new
81
+ end
82
+
83
+ if url_dst == "linux"
84
+ account_names = RS_LINUX_ACCOUNTS
85
+ container = "rightimage-linux"
86
+ blob = image_name
87
+ elsif url_dst == "windows"
88
+ account_names = RS_LINUX_ACCOUNTS + RS_WINDOWS_ACCOUNTS
89
+ container = "rightimage-windows"
90
+ blob = image_name
91
+ elsif url_dst == "rswin"
92
+ account_names = RS_LINUX_ACCOUNTS
93
+ container = "rightimage-windows"
94
+ blob = image_name
95
+ elsif url_dst == "mswin"
96
+ account_names = RS_WINDOWS_ACCOUNTS
97
+ container = "rightimage-windows"
98
+ blob = image_name
99
+ else
100
+ (account,password,endpoint,container,blob) = parse_url(url_dst)
101
+ account_names = [account]
102
+ end
103
+
104
+ if url_dst =~ /^linux|^rswin|^mswin/
105
+ raise "Destination vhd name must be supplied when using mass copy" unless image_name and image_name =~ /./
106
+ raise "Image name must end in .vhd" unless image_name and image_name =~ /.vhd/
107
+ end
108
+
109
+ transfers = account_names.map do |account|
110
+ password = lookup_password(account)
111
+ endpoint = "https://#{account}.blob.core.windows.net"
112
+ CopyState.new(
113
+ RightScale::CloudApi::Azure::Storage::Manager.new(account, password, endpoint, :api_version=>'2012-02-12'),
114
+ account
115
+ )
116
+ end
117
+
118
+
119
+ url_src_signed = generate_shared_access_key(url_src)
120
+ puts "COPYING #{url_src}"
121
+ transfers.each do |t|
122
+ puts "TO #{t.account}/#{container}/#{blob}"
123
+ begin
124
+ t.api.GetBlobProperties('Container' => container, 'Blob' => blob)
125
+ rescue
126
+ end
127
+ if t.api.response.code.to_s =~ /^2/
128
+ puts "Blob already exists for #{t.account}, polling status"
129
+ else
130
+ puts "Running CopyBlob command for #{t.account} then polling status"
131
+ api_dump(t.api, "CopyBlob","Container"=>container,"Blob"=>blob,"Source"=>url_src_signed)
132
+ if t.api.response.code.to_s !~ /^2/
133
+ raise "ERROR account #{t.account} response code for CopyBlob not in 2xx range"
134
+ end
135
+ end
136
+ end
137
+
138
+ 240.times do
139
+ transfers.each_with_index do |t,i|
140
+ t.api.GetBlobProperties('Container' => container, 'Blob' => blob)
141
+ status = t.api.response.headers["x-ms-copy-status"].first
142
+ if t.api.response.code.to_s !~ /^2/
143
+ raise "ERROR account #{t.account} container #{container} blob #{blob} response code for GetBlobProperties not in 2xx range"
144
+ end
145
+ progress = t.api.response.headers["x-ms-copy-progress"].first
146
+ if progress =~ /[0-9]+\/[0-9]+/
147
+ (a,b) = progress.split("/")
148
+ progress = sprintf("%3s%" % [(a.to_i*100/b.to_i).to_s])
149
+ end
150
+ if status =~ /success/
151
+ progress = "DONE"
152
+ elsif status =~ /fail/
153
+ progress = "FAIL"
154
+ end
155
+ case status
156
+ when /fail/ then t.state = :failure
157
+ when /success/ then t.state = :success
158
+ when /pending/ then t.state = :pending
159
+ else t.state = :unknown
160
+ end
161
+ print "#{t.account.sub("rightscale","")}[#{progress}] "
162
+ print "\n" if (i % RS_LINUX_ACCOUNTS.size == (RS_LINUX_ACCOUNTS.size - 1))
163
+ end
164
+ # print "\n"
165
+ break unless transfers.find {|t| t.state == :pending }
166
+ sleep 30
167
+ end
168
+ if transfers.all? { |t| t.state == :success }
169
+ puts "ALL successful!"
170
+ exit 0
171
+ else
172
+ puts "ERROR occurred, or we timed out, maybe run this command again"
173
+ exit 1
174
+ end
data/bin/azure_publish ADDED
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #require 'ruby-debug'
4
+ require 'right_cloud_api'
5
+ require 'cloud/azure/storage/manager'
6
+ require File.expand_path("../../lib/azure_helper",__FILE__)
7
+ require 'uri'
8
+ require 'yaml'
9
+ require 'json'
10
+ require 'pp'
11
+
12
+ include AzureHelper
13
+
14
+ def deregister_image(accounts, image_name)
15
+ images = registered_images(image_name)
16
+ if images.size == 0
17
+ puts "Found no images matching name or blob"
18
+ else
19
+ puts "Found images to deregister: "
20
+ images.each_with_index do |image, i|
21
+ puts (i+1).to_s + ") Name: " + image['Name']
22
+ puts "Location: " + image['Location']
23
+ puts "Blob: " + image['MediaLink'].to_s if image['MediaLink']
24
+ puts #{i['Name']} #{i['MediaLink'].to_s} #{i['Location']}"
25
+ end
26
+ print "Proceed? [yes|no] "
27
+ proceed = STDIN.gets
28
+ if proceed =~ /^[yY]/
29
+ images.each_with_index do |image, i|
30
+ `azure vm image delete #{image['Name']}`
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def registered_images(image_name)
37
+ images = JSON.parse(`azure vm image list --json`.chomp)
38
+ registered = []
39
+ image_name = image_name.sub(".vhd","").downcase
40
+ images.each do |i|
41
+ # pp i
42
+ link_name = i["MediaLink"].to_s.dup
43
+ name = i["Name"].dup
44
+ link_name = link_name.sub(".vhd","").downcase
45
+ name = name.sub(".vhd","").downcase
46
+ AZURE_LINUX_LOCATIONS.values.each do |location|
47
+ link_name = link_name.sub(location.sub(" ","-"),"")
48
+ name = name.sub(location.sub(" ","-"),"")
49
+ end
50
+ # p "L #{link_name} IM #{image_name} N #{name}"
51
+ if link_name == image_name or
52
+ link_name.split("/").last == image_name or
53
+ name == image_name
54
+ registered << i
55
+ end
56
+ end
57
+ return registered
58
+ end
59
+
60
+ def register_image(accounts, container, image_url)
61
+ images = JSON.parse(`azure vm image list --json`)
62
+ accounts.each do |account, api|
63
+ next if account =~ /win$/
64
+ image_name = image_url.split("/").last
65
+ image_name = image_name.sub(".vhd","")
66
+ regional_image_url = "https://#{account}.blob.core.windows.net/#{container}/#{image_name}.vhd"
67
+ name_with_region = image_name + "-" + AZURE_LINUX_LOCATIONS[account].gsub(" ","-")
68
+ image_name_clean = image_name.gsub(/[_-]/," ")
69
+
70
+ if registered_images(name_with_region).size > 0
71
+ puts "Image #{name_with_region} already registered"
72
+ else
73
+ begin
74
+ results = api.GetBlobMetadata('Container' => container, 'Blob'=>"#{image_name}.vhd")
75
+ is_public = false
76
+ if api.response.headers.key?("x-ms-meta-rs_public")
77
+ is_public = true
78
+ end
79
+ found_image = true
80
+ rescue
81
+ found_image = false
82
+ end
83
+ if found_image
84
+ unless is_public
85
+ puts "WARNING: Image for region #{account} is not public, registering anyways"
86
+ end
87
+ puts "azure vm image create --label \"#{image_name_clean}\" --os #{OS_TYPE} --blob-url \"#{regional_image_url}\" \"#{name_with_region}\""
88
+ puts `azure vm image create --label \"#{image_name_clean}\" --os #{OS_TYPE} --blob-url \"#{regional_image_url}\" \"#{name_with_region}\"`
89
+ else
90
+ puts "Could not find image #{regional_image_url} for account #{account}"
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ def list_images(accounts, container)
97
+ accounts.each do |account, api|
98
+ results = api.ListBlobs('Container' => container)
99
+ if results['EnumerationResults']['Blobs']
100
+ blobs = results['EnumerationResults']['Blobs']['Blob']
101
+ else
102
+ blobs = []
103
+ end
104
+ if blobs.class == Hash
105
+ blobs = [blobs]
106
+ end
107
+ puts "=================== #{account} ====================="
108
+ blobs.each do |blob|
109
+ name = blob["Name"]
110
+ # no on-the-fly images
111
+ next if name =~ /os-i-[0-9a-f]{8,12}$/
112
+
113
+ ispublic = 'NOT_PUBLISHED'
114
+ results = api.GetBlobMetadata('Container'=>container,"Blob"=>name)
115
+ ispublic = "PUBLISHED" if api.response.headers.key?('x-ms-meta-rs_public')
116
+ # header = header.first if header.respond_to? "first"
117
+ # puts "RES"+results.response.inspect
118
+ puts "NAME #{name} ====== #{ispublic}"
119
+ puts "URL https://#{account}.blob.core.windows.net/#{container}/#{name}"
120
+ end
121
+ end
122
+ end
123
+
124
+ def set_public(accounts, container, image, publicize=true)
125
+ accounts.each do |account, api|
126
+ found_image = false
127
+ begin
128
+ results = api.GetBlobMetadata('Container' => container, 'Blob'=>image)
129
+ is_public = false
130
+ if api.response.headers.key?("x-ms-meta-rs_public")
131
+ is_public = true
132
+ end
133
+ found_image = true
134
+ rescue
135
+ puts "#{account} image #{image} not found"
136
+ end
137
+ # api_dump(api, "GetBlobProperties", {'Container' => container, 'Blob' => blob})
138
+ # api_dump(api, "GetBlobMetadata", {'Container' => container, 'Blob' => blob})
139
+ if found_image
140
+ if publicize && !is_public
141
+ timestamp = Time.now.gmtime.to_s
142
+ puts "#{account} ADDING KEY: x-ms-meta-rs_public: #{timestamp}"
143
+ api.SetBlobMetadata('Container' => container, 'Blob' => image, 'Metadata'=>{"rs_public"=>timestamp})
144
+ # api_dump(api, "SetBlobMetadata", 'Container' => container, 'Blob' => image, 'Metadata'=>{"rs_public"=>timestamp})
145
+ elsif !publicize && is_public
146
+ puts "#{account} REMOVING KEY: x-ms-meta-rs_public: #{timestamp}"
147
+ api.SetBlobMetadata('Container' => container, 'Blob' => image, 'Metadata'=>{})
148
+ # api_dump(api, "SetBlobMetadata", 'Container' => container, 'Blob' => image, 'Metadata'=>{})
149
+ elsif publicize && is_public
150
+ puts "#{account} image #{image} already public"
151
+ else #!publicize %% !is_public
152
+ puts "#{account} image #{image} for already NOT public"
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ cmd = ARGV.shift
159
+ platform = ARGV.shift
160
+ options = ARGV.dup
161
+ #pp options
162
+ unless cmd and cmd != "help"
163
+ puts <<-EOF
164
+ USAGE: azure_publish COMMAND [COMMAND OPTIONS]
165
+ USAGE: COMMAND set_public <linux|windows> <image_name.vhd>
166
+ USAGE: COMMAND unset_public <linux|windows> <image_name.vhd>
167
+ USAGE: COMMAND list_images <linux|windows>
168
+ USAGE: COMMAND register_image <linux|windows> <image_name.vhd>
169
+ USAGE: COMMAND deregister_image <linux|windows> <image_name.vhd|url_to_blob>
170
+ EXAMPLE: azure_publish list_images linux
171
+ EXAMPLE: azure_publish set_public linux RightImage-CentOS-6.2-x64-v5.8.8.1.vhd
172
+ EXAMPLE: azure_publish register_image linux RightImage-CentOS-6.2-x64-v5.8.8.1
173
+ EOF
174
+ exit 1
175
+ end
176
+
177
+ if platform == "linux"
178
+ account_names = RS_LINUX_ACCOUNTS
179
+ container = "rightimage-linux"
180
+ OS_TYPE = "linux"
181
+ # (a,p,ep,c,blob) = parse_url(url_src)
182
+ elsif platform == "windows"
183
+ account_names = RS_LINUX_ACCOUNTS + RS_WINDOWS_ACCOUNTS
184
+ container = "rightimage-windows"
185
+ OS_TYPE = "windows"
186
+ elsif platform == "rswin"
187
+ account_names = RS_LINUX_ACCOUNTS
188
+ container = "rightimage-windows"
189
+ OS_TYPE = "windows"
190
+ # (a,p,ep,c,blob) = parse_url(url_src)
191
+ elsif platform == "mswin"
192
+ account_names = RS_WINDOWS_ACCOUNTS
193
+ container = "rightimage-windows"
194
+ OS_TYPE = "windows"
195
+ # (a,p,ep,c,blob) = parse_url(url_src)
196
+ end
197
+
198
+ accounts = account_names.map do |account|
199
+ password = lookup_password(account)
200
+ endpoint = "https://#{account}.blob.core.windows.net"
201
+ [account, RightScale::CloudApi::Azure::Storage::Manager.new(account, password, endpoint, :api_version=>'2012-02-12')]
202
+ end
203
+
204
+ case cmd
205
+ when "list_images" then list_images(accounts, container)
206
+ when "set_public" then set_public(accounts, container, options[0], true)
207
+ when "unset_public" then set_public(accounts, container, options[0], false)
208
+ when "deregister_image" then deregister_image(accounts, options[0])
209
+ when "register_image" then register_image(accounts, container, options[0])
210
+ else raise "Unknown cmd #{cmd}"
211
+ end
212
+
213
+