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