aeolus-image 0.0.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/COPYING +161 -0
  2. data/Rakefile +32 -19
  3. data/lib/aeolus_image.rb +35 -0
  4. data/lib/aeolus_image/active_resource_oauth_client.rb +62 -0
  5. data/lib/aeolus_image/import.rb +44 -0
  6. data/lib/aeolus_image/model/factory/base.rb +93 -0
  7. data/lib/aeolus_image/model/factory/build.rb +23 -0
  8. data/lib/aeolus_image/model/factory/builder.rb +46 -0
  9. data/lib/aeolus_image/model/factory/image.rb +23 -0
  10. data/lib/aeolus_image/model/factory/provider_image.rb +35 -0
  11. data/lib/aeolus_image/model/factory/target_image.rb +45 -0
  12. data/lib/aeolus_image/model/warehouse/icicle.rb +60 -0
  13. data/lib/aeolus_image/model/warehouse/image.rb +135 -0
  14. data/lib/aeolus_image/model/warehouse/image_build.rb +60 -0
  15. data/lib/aeolus_image/model/warehouse/provider_image.rb +36 -0
  16. data/lib/aeolus_image/model/warehouse/target_image.rb +53 -0
  17. data/lib/aeolus_image/model/warehouse/template.rb +35 -0
  18. data/lib/aeolus_image/model/warehouse/warehouse_client.rb +201 -0
  19. data/lib/aeolus_image/model/warehouse/warehouse_model.rb +236 -0
  20. data/spec/aeolus_image/model/factory/provider_image_spec.rb +12 -0
  21. data/spec/models/factory/base_spec.rb +94 -0
  22. data/spec/models/factory/builder_spec.rb +31 -0
  23. data/spec/models/factory/provider_image_spec.rb +21 -0
  24. data/spec/models/factory/target_image_spec.rb +21 -0
  25. data/spec/models/warehouse/image_build_spec.rb +189 -0
  26. data/spec/models/warehouse/image_spec.rb +241 -0
  27. data/spec/models/warehouse/provider_image_spec.rb +115 -0
  28. data/spec/models/warehouse/target_image_spec.rb +180 -0
  29. data/spec/models/warehouse/template_spec.rb +76 -0
  30. data/spec/models/warehouse/warehouse_client_spec.rb +445 -0
  31. data/spec/models/warehouse/warehouse_model_spec.rb +94 -0
  32. data/spec/spec_helper.rb +34 -47
  33. data/spec/vcr/cassettes/builder.yml +24 -0
  34. data/spec/vcr/cassettes/oauth.yml +80 -0
  35. data/spec/vcr/cassettes/oauth_fail_invalid.yml +30 -0
  36. data/spec/vcr/cassettes/oauth_fail_no.yml +22 -0
  37. data/spec/vcr/cassettes/oauth_success_valid.yml +30 -0
  38. data/spec/vcr_setup.rb +26 -0
  39. metadata +70 -46
  40. data/bin/aeolus-image +0 -6
  41. data/examples/aeolus-cli +0 -9
  42. data/examples/custom_repo.tdl +0 -18
  43. data/examples/image_description.xml +0 -3
  44. data/examples/tdl.rng +0 -207
  45. data/lib/base_command.rb +0 -134
  46. data/lib/build_command.rb +0 -68
  47. data/lib/config_parser.rb +0 -212
  48. data/lib/delete_command.rb +0 -9
  49. data/lib/import_command.rb +0 -44
  50. data/lib/list_command.rb +0 -141
  51. data/lib/push_command.rb +0 -72
  52. data/man/aeolus-image-build.1 +0 -36
  53. data/man/aeolus-image-import.1 +0 -57
  54. data/man/aeolus-image-list.1 +0 -80
  55. data/man/aeolus-image-push.1 +0 -40
  56. data/man/aeolus-image.1 +0 -16
  57. data/spec/base_command_spec.rb +0 -76
  58. data/spec/build_command_spec.rb +0 -63
  59. data/spec/config_parser_spec.rb +0 -82
  60. data/spec/import_command_spec.rb +0 -43
  61. data/spec/list_command_spec.rb +0 -21
  62. data/spec/push_command_spec.rb +0 -56
  63. data/spec/spec.opts +0 -3
@@ -0,0 +1,53 @@
1
+ # Copyright 2011 Red Hat, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Aeolus
16
+ module Image
17
+ module Warehouse
18
+ class TargetImage < WarehouseModel
19
+ @bucket_name = 'target_images'
20
+
21
+ def build
22
+ ImageBuild.find(@build) if @build
23
+ end
24
+
25
+ def provider_images
26
+ ProviderImage.where("($target_image == \"" + @uuid.to_s + "\")")
27
+ end
28
+
29
+ def target_template
30
+ Template.find(@template) if @template
31
+ end
32
+
33
+ # Deletes this targetimage and all child objects
34
+ def delete!
35
+ begin
36
+ provider_images.each do |pi|
37
+ pi.delete!
38
+ end
39
+ rescue NoMethodError
40
+ end
41
+ TargetImage.delete(@uuid)
42
+ end
43
+
44
+ def find_provider_image_by_provider_and_account(provider, provider_account)
45
+ conditions = ["$target_image == \"#{@uuid}\""]
46
+ conditions << "$provider == \"#{provider}\"" if provider
47
+ conditions << "$provider_account_identifier == \"#{provider_account}\"" if provider_account
48
+ ProviderImage.where('(' + conditions.join(' && ') + ')')
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,35 @@
1
+ # Copyright 2011 Red Hat, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Aeolus
16
+ module Image
17
+ module Warehouse
18
+ class Template < WarehouseModel
19
+ attr_reader :xml_body
20
+
21
+ @bucket_name = 'templates'
22
+
23
+ def initialize(obj)
24
+ super
25
+ @body = obj.body
26
+ @xml_body = Nokogiri::XML @body
27
+ end
28
+
29
+ def name
30
+ @name ||= @xml_body.xpath("/template/name").text
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,201 @@
1
+ # Copyright 2011 Red Hat, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'rubygems'
16
+
17
+ require 'rest-client'
18
+ require 'nokogiri'
19
+ require 'oauth'
20
+
21
+ #TODO: perform iwhd version-dependent URI mapping
22
+ module Aeolus
23
+ module Image
24
+ module Warehouse
25
+ class BucketObject
26
+ attr_reader :key
27
+
28
+ def initialize(connection, key, bucket)
29
+ @connection = connection
30
+ @key = key
31
+ @bucket = bucket
32
+ @path = "/#{@bucket.name}/#{@key}"
33
+ end
34
+
35
+ def self.create(connection, key, bucket, body, attrs = {})
36
+ obj = new(connection, key, bucket)
37
+ obj.set_body(body)
38
+ obj.set_attrs(attrs)
39
+ obj
40
+ end
41
+
42
+ def body
43
+ @connection.do_request @path, :plain => true
44
+ end
45
+
46
+ def set_body(body)
47
+ @connection.do_request @path, :content => body, :method => :put
48
+ end
49
+
50
+ def attr_list
51
+ result = @connection.do_request @path, :content => 'op=parts', :method => :post
52
+ return result.xpath('/object/object_attr/@name').to_a.map {|item| item.value}
53
+ end
54
+
55
+ def attrs(list)
56
+ attrs = {}
57
+ list.each do |att|
58
+ next if att.match('-')
59
+ attrs[att] = (@connection.do_request("#{@path}/#{att}", :plain => true) rescue nil)
60
+ end
61
+ attrs
62
+ end
63
+
64
+ def attr(name)
65
+ attrs([name])[name]
66
+ end
67
+
68
+ def set_attrs(hash)
69
+ hash.each do |name, content|
70
+ set_attr(name, content)
71
+ end
72
+ end
73
+
74
+ def set_attr(name, content)
75
+ path = "#{@path}/#{name}"
76
+ @connection.do_request path, :content => content, :method => :put
77
+ end
78
+
79
+ def delete!
80
+ @connection.do_request @path, :method => :delete
81
+ true
82
+ end
83
+
84
+ end
85
+
86
+ class Bucket
87
+ attr_accessor :name
88
+
89
+ def initialize(name, connection)
90
+ @name = name
91
+ @connection = connection
92
+ end
93
+
94
+ def to_s
95
+ "Bucket: #{@name}"
96
+ end
97
+
98
+ def object_names
99
+ result = @connection.do_request "/#{@name}"
100
+ result.xpath('/objects/object').map do |obj|
101
+ obj.at_xpath('./key/text()').to_s
102
+ end
103
+ end
104
+
105
+ def objects
106
+ object_names.map do |name|
107
+ object(name)
108
+ end
109
+ end
110
+
111
+ def object(key)
112
+ BucketObject.new @connection, key, self
113
+ end
114
+
115
+ def create_object(key, body, attrs)
116
+ BucketObject.create(@connection, key, self, body, attrs)
117
+ end
118
+
119
+ def include?(key)
120
+ object_names.include?(key)
121
+ end
122
+ end
123
+
124
+ class Connection
125
+ attr_accessor :uri
126
+
127
+ def initialize(uri)
128
+ @uri = uri
129
+ if WarehouseModel.use_oauth? && WarehouseModel.iwhd_url && uri.match(WarehouseModel.iwhd_url)
130
+ @consumer = OAuth::Consumer.new(
131
+ WarehouseModel.oauth_consumer_key,
132
+ WarehouseModel.oauth_consumer_secret,
133
+ :site => WarehouseModel.iwhd_url
134
+ )
135
+ @token = OAuth::AccessToken.new(@consumer)
136
+ end
137
+ end
138
+
139
+ def do_request(path = '/', opts={})
140
+ opts[:method] ||= :get
141
+ opts[:content] ||= ''
142
+ opts[:plain] ||= false
143
+ opts[:headers] ||= {}
144
+
145
+ # Using restclient seems to break OAuth POSTs, so use the oauth gem directly if we're using OAuth:
146
+ if @token
147
+ # TODO - I'm not sure how to pass :headers through here, but we don't actually use them anywhere
148
+ response = @token.request(opts[:method], (@uri + path).to_s, opts[:content].to_s)
149
+ # Errors don't cause exceptions like they do with RestClient, so detect and raise them:
150
+ if response.is_a?(Net::HTTPSuccess)
151
+ result = response.body
152
+ # Translate a few common errors to their RestClient counterparts that we're used to checking for:
153
+ elsif response.is_a?(Net::HTTPNotFound)
154
+ raise RestClient::ResourceNotFound
155
+ elsif response.is_a?(Net::HTTPInternalServerError)
156
+ raise RestClient::InternalServerError
157
+ # Otherwise, raise the error:
158
+ else
159
+ response.error!
160
+ end
161
+ else # no @token -- use RestClient
162
+ result = RestClient::Request.execute(:method => opts[:method], :url => @uri + path, :payload => opts[:content], :headers => opts[:headers])
163
+ end
164
+ return opts[:plain] ? result : Nokogiri::XML(result)
165
+ end
166
+ end
167
+
168
+ class Client
169
+
170
+ def initialize(uri)
171
+ @connection = Connection.new(uri)
172
+ end
173
+
174
+ def create_bucket(bucket)
175
+ @connection.do_request("/#{bucket}", :method => :put) rescue RestClient::InternalServerError
176
+ Bucket.new(bucket, @connection)
177
+ end
178
+
179
+ def bucket(bucket)
180
+ Bucket.new bucket, @connection
181
+ end
182
+
183
+ def buckets
184
+ @connection.do_request.xpath('/api/link[@rel="bucket"]').map do |obj|
185
+ obj.at_xpath('./@href').to_s.gsub(/.*\//, '')
186
+ end
187
+ end
188
+
189
+ def get_iwhd_version
190
+ result = @connection.do_request.at_xpath('/api[@service="image_warehouse"]/@version')
191
+ raise "Response does not contain <api> tag or version information" if result == nil
192
+ return result.value
193
+ end
194
+
195
+ def query(bucket_name, query_string)
196
+ @connection.do_request "/#{bucket_name}/_query", {:method => :post, :content => query_string}
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,236 @@
1
+ # Copyright 2011 Red Hat, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Aeolus
16
+ module Image
17
+ module Warehouse
18
+ class BucketObjectNotFound < Exception;end
19
+ class BucketNotFound < Exception;end
20
+
21
+ class WarehouseModel
22
+ attr_writer :body
23
+
24
+ def initialize(obj)
25
+ @obj = obj
26
+ @attrs = obj.attrs(obj.attr_list)
27
+ @attrs.each do |k,v|
28
+ self.class.send(:attr_writer, k.to_sym) unless respond_to?(:"#{k}=")
29
+ self.class.send(:attr_reader, k.to_sym) unless respond_to?(k.to_sym)
30
+ send(:"#{k}=", v)
31
+ end
32
+ end
33
+
34
+ def body
35
+ @obj.body
36
+ end
37
+
38
+ def ==(other_obj)
39
+ # If the objects have different instance variables defined, they're definitely not ==
40
+ return false unless instance_variables.sort == other_obj.instance_variables.sort
41
+ # Otherwise, ensure that they're all the same
42
+ instance_variables.each do |iv|
43
+ next if iv == "@obj" || iv == :@obj
44
+ return false unless other_obj.instance_variable_get(iv) == instance_variable_get(iv)
45
+ return false unless other_obj.body == body
46
+ end
47
+ # They have the same instance variables and values, so they're equal
48
+ true
49
+ end
50
+
51
+ def id
52
+ uuid
53
+ end
54
+
55
+ # Returns the bucket object represending this object
56
+ def bucket_object
57
+ @obj
58
+ end
59
+
60
+ # Set (and immediately update) an attribute on the object
61
+ # TODO: It might be nicer to offer a .save! that iterates over each attribute
62
+ # and calls this, to better match ActiveResource
63
+ def set_attr(key, value)
64
+ bucket_object.set_attr(key, value)
65
+ end
66
+
67
+
68
+ class << self
69
+ attr_accessor :warehouse, :bucket, :bucket_name
70
+
71
+ def set_warehouse_and_bucket
72
+ begin
73
+ @@config ||= load_config
74
+ self.warehouse = Warehouse::Client.new(@@config[:iwhd][:url])
75
+ self.bucket = self.warehouse.bucket(@bucket_name)
76
+ rescue
77
+ raise BucketNotFound
78
+ end
79
+ end
80
+
81
+ def bucket_objects
82
+ self.set_warehouse_and_bucket if self.bucket.nil?
83
+
84
+ begin
85
+ self.bucket.objects
86
+ rescue RestClient::ResourceNotFound
87
+ []
88
+ end
89
+ end
90
+
91
+ def first
92
+ obj = bucket_objects.first
93
+ obj ? self.new(obj) : nil
94
+ end
95
+
96
+ def last
97
+ obj = bucket_objects.last
98
+ obj ? self.new(obj) : nil
99
+ end
100
+
101
+ def all
102
+ bucket_objects.map do |wh_object|
103
+ self.new(wh_object)
104
+ end
105
+ end
106
+
107
+ def find(uuid)
108
+ self.set_warehouse_and_bucket if self.bucket.nil?
109
+ begin
110
+ if self.bucket.include?(uuid)
111
+ self.new(self.bucket.object(uuid))
112
+ else
113
+ nil
114
+ end
115
+ rescue RestClient::ResourceNotFound
116
+ nil
117
+ end
118
+ end
119
+
120
+ def where(query_string)
121
+ begin
122
+ self.set_warehouse_and_bucket if self.bucket.nil?
123
+ self.warehouse.query(@bucket_name, query_string).xpath('/objects/object').map do |obj|
124
+ self.new(self.bucket.object(obj.at_xpath('./key/text()').to_s))
125
+ end
126
+ rescue RestClient::ResourceNotFound
127
+ []
128
+ end
129
+ end
130
+
131
+ def delete(uuid)
132
+ self.set_warehouse_and_bucket if self.bucket.nil?
133
+ begin
134
+ if self.bucket.include?(uuid)
135
+ self.bucket.object(uuid).delete!
136
+ else
137
+ false
138
+ end
139
+ rescue RestClient::ResourceNotFound
140
+ false
141
+ end
142
+ end
143
+
144
+ def config
145
+ defined?(@@config) ? @@config : nil
146
+ end
147
+
148
+ def config=(conf)
149
+ @@config = conf
150
+ end
151
+
152
+ def use_oauth?
153
+ !!oauth_consumer_key && !!oauth_consumer_secret
154
+ end
155
+
156
+ def oauth_consumer_key
157
+ config[:iwhd][:oauth][:consumer_key] rescue nil
158
+ end
159
+
160
+ def oauth_consumer_secret
161
+ config[:iwhd][:oauth][:consumer_secret] rescue nil
162
+ end
163
+
164
+ def iwhd_url
165
+ config[:iwhd][:url]
166
+ end
167
+
168
+ def create!(key, body, attributes)
169
+ self.set_warehouse_and_bucket if self.bucket.nil?
170
+ unless self.warehouse.buckets.include?(self.bucket.name)
171
+ self.bucket = self.warehouse.create_bucket(self.bucket.name)
172
+ end
173
+ obj = self.bucket.create_object(key, body, attributes)
174
+ self.new(obj)
175
+ end
176
+
177
+ protected
178
+
179
+ # Copy over entirely too much code to load the config file
180
+ def load_config
181
+ # TODO - Is this always the case? We should probably have /etc/aeolus-cli or something too?
182
+ # Or allow Rails to override this
183
+ @config_location ||= "~/.aeolus-cli"
184
+ begin
185
+ file_str = read_file(@config_location)
186
+ if is_file?(@config_location) && !file_str.include?(":url")
187
+ lines = File.readlines(File.expand_path(@config_location)).map do |line|
188
+ "#" + line
189
+ end
190
+ File.open(File.expand_path(@config_location), 'w') do |file|
191
+ file.puts lines
192
+ end
193
+ write_file
194
+ end
195
+ write_file unless is_file?(@config_location)
196
+ YAML::load(File.open(File.expand_path(@config_location)))
197
+ rescue Errno::ENOENT
198
+ #TODO: Create a custom exception to wrap CLI Exceptions
199
+ raise "Unable to locate or write configuration file: \"" + @config_location + "\""
200
+ end
201
+ end
202
+
203
+ def write_file
204
+ example = File.read(File.expand_path(File.dirname(__FILE__) + "/../../examples/aeolus-cli"))
205
+ File.open(File.expand_path(@config_location), 'a+') do |f|
206
+ f.write(example)
207
+ end
208
+ end
209
+
210
+ def read_file(path)
211
+ begin
212
+ full_path = File.expand_path(path)
213
+ if is_file?(path)
214
+ File.read(full_path)
215
+ else
216
+ return nil
217
+ end
218
+ rescue
219
+ nil
220
+ end
221
+ end
222
+
223
+ def is_file?(path)
224
+ full_path = File.expand_path(path)
225
+ if File.exist?(full_path) && !File.directory?(full_path)
226
+ return true
227
+ end
228
+ false
229
+ end
230
+
231
+ end
232
+
233
+ end
234
+ end
235
+ end
236
+ end