bbrowning-deltacloud-client 0.0.9.7-java

Sign up to get free protection for your applications and to get access to all the features.
data/COPYING ADDED
@@ -0,0 +1,176 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
@@ -0,0 +1,61 @@
1
+ #
2
+ # Copyright (C) 2009 Red Hat, Inc.
3
+ #
4
+ # Licensed to the Apache Software Foundation (ASF) under one or more
5
+ # contributor license agreements. See the NOTICE file distributed with
6
+ # this work for additional information regarding copyright ownership. The
7
+ # ASF licenses this file to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance with the
9
+ # License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
+ # License for the specific language governing permissions and limitations
17
+ # under the License.
18
+
19
+ require 'rake/gempackagetask'
20
+
21
+ load 'deltacloud-client.gemspec'
22
+
23
+ desc "Generate documentation"
24
+ task 'documentation' do
25
+ load 'lib/documentation.rb'
26
+ end
27
+
28
+ @specs = ['ruby', 'java'].inject({}) do |hash, spec_platform|
29
+ $platform = spec_platform
30
+ hash.update(spec_platform => Gem::Specification.load('deltacloud-client.gemspec'))
31
+ end
32
+
33
+ @specs.values.each do |spec|
34
+ Rake::GemPackageTask.new(spec) do |pkg|
35
+ pkg.need_tar = true
36
+ end
37
+ end
38
+
39
+ if Gem.available?('rspec')
40
+ require 'spec/rake/spectask'
41
+ desc "Run all examples"
42
+ Spec::Rake::SpecTask.new('spec') do |t|
43
+ t.spec_files = FileList['specs/**/*_spec.rb']
44
+ end
45
+ end
46
+
47
+ desc "Setup Fixtures"
48
+ task 'fixtures' do
49
+ FileUtils.rm_rf( File.dirname( __FILE__ ) + '/specs/data' )
50
+ FileUtils.cp_r( File.dirname( __FILE__ ) + '/specs/fixtures', File.dirname( __FILE__ ) + '/specs/data' )
51
+ end
52
+
53
+ desc "Clean Fixtures"
54
+ task 'fixtures:clean' do
55
+ FileUtils.rm_rf( File.dirname( __FILE__ ) + '/specs/data' )
56
+ end
57
+
58
+ begin
59
+ require 'ci/reporter/rake/rspec'
60
+ rescue LoadError
61
+ end
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (C) 2009 Red Hat, Inc.
4
+ #
5
+ # Licensed to the Apache Software Foundation (ASF) under one or more
6
+ # contributor license agreements. See the NOTICE file distributed with
7
+ # this work for additional information regarding copyright ownership. The
8
+ # ASF licenses this file to you under the Apache License, Version 2.0 (the
9
+ # "License"); you may not use this file except in compliance with the
10
+ # License. You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17
+ # License for the specific language governing permissions and limitations
18
+ # under the License.
19
+
20
+ require 'rubygems'
21
+ require 'optparse'
22
+ require 'uri'
23
+ require 'deltacloud'
24
+ require 'plain_formatter'
25
+
26
+ include DeltaCloud::PlainFormatter
27
+
28
+ options = {
29
+ :verbose => false
30
+ }
31
+
32
+ @optparse = OptionParser.new do |opts|
33
+
34
+ opts.banner = <<BANNER
35
+ Usage:
36
+ deltacloudc collection operation [options]
37
+
38
+ URL format:
39
+ API_URL=http://[user]:[password]@[api_url][port][/uri]
40
+
41
+ Options:
42
+ BANNER
43
+ opts.on( '-i', '--id ID', 'ID for operation') { |id| options[:id] = id }
44
+ opts.on( '-d', '--image-id ID', 'Image ID') { |id| options[:image_id] = id }
45
+ opts.on( '-a', '--arch ARCH', 'Architecture (x86, x86_64)') { |id| options[:architecture] = id }
46
+ opts.on( '-p', '--hardware-profile HARDWARE_PROFILE', 'Hardware Profile') { |id| options[:hwp_id] = id }
47
+ opts.on( '-n', '--name NAME', 'Name (for instance eg.)') { |name| options[:name] = name }
48
+ opts.on( '-s', '--state STATE', 'Instance state (RUNNING, STOPPED)') { |state| options[:state] = state }
49
+ opts.on( '-u', '--url URL', 'API url ($API_URL variable)') { |url| options[:api_url] = url }
50
+ opts.on( '-l', '--list', 'List collections/operations') { |id| options[:list] = true }
51
+ opts.on( '-h', '--help', 'Display this screen' ) { puts opts ; exit }
52
+ opts.on( '-v', '--version', 'Display API version' ) { options[:version]=true }
53
+ opts.on( '-V', '--verbose', 'Print verbose messages' ) { options[:verbose]=true }
54
+ end
55
+
56
+ def invalid_usage(error_msg='')
57
+ puts "ERROR: #{error_msg}"
58
+ exit(1)
59
+ end
60
+
61
+ @optparse.parse!
62
+
63
+ # First try to get API_URL from environment
64
+ options[:api_url] = ENV['API_URL'] if options[:api_url].nil?
65
+
66
+ url = URI.parse(options[:api_url])
67
+ api_url = "http://#{url.host}#{url.port ? ":#{url.port}" : ''}#{url.path}"
68
+
69
+ options[:collection] = ARGV[0]
70
+ options[:operation] = ARGV[1]
71
+
72
+ # Connect to Deltacloud API and fetch all entry points
73
+ client = DeltaCloud.new(url.user || ENV['API_USER'], url.password || ENV['API_PASSWORD'], api_url)
74
+ collections = client.entry_points.keys
75
+
76
+ # Exclude collection which don't have methods in client library yet
77
+ collections.delete(:instance_states)
78
+
79
+ # If list parameter passed print out available collection
80
+ # with API documentation
81
+ if options[:list] and options[:collection].nil?
82
+ collections.each do |c|
83
+ puts sprintf("%-22s", c.to_s[0, 22])
84
+ end
85
+ exit(0)
86
+ end
87
+
88
+ # If collection parameter is present and user requested list
89
+ # print all operation defined for collection with API documentation
90
+ if options[:list] and options[:collection]
91
+ doc = client.documentation(options[:collection])
92
+ doc.operations.each do |c|
93
+ puts sprintf("%-20s: %s", c.operation, c.description)
94
+ end
95
+ exit(0)
96
+ end
97
+
98
+ if options[:version]
99
+ puts "Deltacloud API(#{client.driver_name}) 0.1"
100
+ exit(0)
101
+ end
102
+
103
+ # List items from collection (typically /instances)
104
+ # Do same if 'index' operation is set
105
+ if options[:collection] and ( options[:operation].nil? or options[:operation].eql?('index') )
106
+ invalid_usage("Unknown collection: #{options[:collection]}") unless collections.include?(options[:collection].to_sym)
107
+ params = {}
108
+ params.merge!(:architecture => options[:architecture]) if options[:architecture]
109
+ params.merge!(:id => options[:id]) if options[:id]
110
+ params.merge!(:state => options[:state]) if options[:state]
111
+ client.send(options[:collection].to_s, params).each do |model|
112
+ puts format(model)
113
+ end
114
+ exit(0)
115
+ end
116
+
117
+ if options[:collection] and options[:operation]
118
+
119
+ invalid_usage("Unknown collection: #{options[:collection]}") unless collections.include?(options[:collection].to_sym)
120
+
121
+ params = {}
122
+ params.merge!(:id => options[:id]) if options[:id]
123
+
124
+ # If collection is set and requested operation is 'show' just 'singularize'
125
+ # collection name and print item with specified id (-i parameter)
126
+ if options[:operation].eql?('show')
127
+ puts format(client.send(options[:collection].gsub(/s$/, ''), options[:id]))
128
+ exit(0)
129
+ end
130
+
131
+ # If collection is set and requested operation is create new instance,
132
+ # --image-id, --hardware-profile and --name parameters are used
133
+ # Returns created instance in plain form
134
+ if options[:collection].eql?('instances') and options[:operation].eql?('create')
135
+ invalid_usage("Missing image-id") unless options[:image_id]
136
+ if options[:name] and ! client.feature?(:instances, :user_name)
137
+ invalid_usage("Driver does not support user-supplied name")
138
+ end
139
+ params.merge!(:name => options[:name]) if options[:name]
140
+ params.merge!(:image_id => options[:image_id]) if options[:image_id]
141
+ params.merge!(:hwp_id => options[:hwp_id]) if options[:hwp_id]
142
+ instance = client.create_instance(options[:image_id], params)
143
+ puts format(instance)
144
+ exit(0)
145
+ end
146
+
147
+ # All other operations above collections is done there:
148
+ if options[:collection].eql?('instances')
149
+ instance = client.instance(options[:id])
150
+ instance.send("#{options[:operation]}!".to_s)
151
+ instance = client.instance(options[:id])
152
+ puts format(instance)
153
+ exit(0)
154
+ end
155
+ end
156
+
157
+ # If all above passed (eg. no parameters)
158
+ puts @optparse
data/init.rb ADDED
@@ -0,0 +1,20 @@
1
+ #
2
+ # Copyright (C) 2009 Red Hat, Inc.
3
+ #
4
+ # Licensed to the Apache Software Foundation (ASF) under one or more
5
+ # contributor license agreements. See the NOTICE file distributed with
6
+ # this work for additional information regarding copyright ownership. The
7
+ # ASF licenses this file to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance with the
9
+ # License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
+ # License for the specific language governing permissions and limitations
17
+ # under the License.
18
+
19
+
20
+ require 'deltacloud'
@@ -0,0 +1,572 @@
1
+ #
2
+ # Copyright (C) 2009 Red Hat, Inc.
3
+ #
4
+ # Licensed to the Apache Software Foundation (ASF) under one or more
5
+ # contributor license agreements. See the NOTICE file distributed with
6
+ # this work for additional information regarding copyright ownership. The
7
+ # ASF licenses this file to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance with the
9
+ # License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
+ # License for the specific language governing permissions and limitations
17
+ # under the License.
18
+
19
+ require 'nokogiri'
20
+ require 'rest_client'
21
+ require 'base64'
22
+ require 'logger'
23
+
24
+ module DeltaCloud
25
+
26
+ # Get a new API client instance
27
+ #
28
+ # @param [String, user_name] API user name
29
+ # @param [String, password] API password
30
+ # @param [String, user_name] API URL (eg. http://localhost:3001/api)
31
+ # @return [DeltaCloud::API]
32
+ def self.new(user_name, password, api_url, &block)
33
+ API.new(user_name, password, api_url, &block)
34
+ end
35
+
36
+ # Check given credentials if their are valid against
37
+ # backend cloud provider
38
+ #
39
+ # @param [String, user_name] API user name
40
+ # @param [String, password] API password
41
+ # @param [String, user_name] API URL (eg. http://localhost:3001/api)
42
+ # @return [true|false]
43
+ def self.valid_credentials?(user_name, password, api_url)
44
+ api=API.new(user_name, password, api_url)
45
+ result = false
46
+ api.request(:get, '', :force_auth => '1') do |response|
47
+ result = true if response.code.eql?(200)
48
+ end
49
+ return result
50
+ end
51
+
52
+ # Return a API driver for specified URL
53
+ #
54
+ # @param [String, url] API URL (eg. http://localhost:3001/api)
55
+ def self.driver_name(url)
56
+ API.new(nil, nil, url).driver_name
57
+ end
58
+
59
+ def self.define_class(name)
60
+ @defined_classes ||= []
61
+ if @defined_classes.include?(name)
62
+ self.module_eval("API::#{name}")
63
+ else
64
+ @defined_classes << name unless @defined_classes.include?(name)
65
+ API.const_set(name, Class.new)
66
+ end
67
+ end
68
+
69
+ def self.classes
70
+ @defined_classes || []
71
+ end
72
+
73
+ class API
74
+ attr_accessor :logger
75
+ attr_reader :api_uri, :driver_name, :api_version, :features, :entry_points
76
+
77
+ def initialize(user_name, password, api_url, opts={}, &block)
78
+ opts[:version] = true
79
+ @logger = opts[:verbose] ? Logger.new(STDERR) : []
80
+ @username, @password = user_name, password
81
+ @api_uri = URI.parse(api_url)
82
+ @features, @entry_points = {}, {}
83
+ @verbose = opts[:verbose] || false
84
+ discover_entry_points
85
+ yield self if block_given?
86
+ end
87
+
88
+ def connect(&block)
89
+ yield self
90
+ end
91
+
92
+ # Return API hostname
93
+ def api_host; @api_uri.host ; end
94
+
95
+ # Return API port
96
+ def api_port; @api_uri.port ; end
97
+
98
+ # Return API path
99
+ def api_path; @api_uri.path ; end
100
+
101
+ # Define methods based on 'rel' attribute in entry point
102
+ # Two methods are declared: 'images' and 'image'
103
+ def declare_entry_points_methods(entry_points)
104
+ logger = @logger
105
+ API.instance_eval do
106
+ entry_points.keys.select {|k| [:instance_states].include?(k)==false }.each do |model|
107
+ define_method model do |*args|
108
+ request(:get, "/#{model}", args.first) do |response|
109
+ # Define a new class based on model name
110
+ c = DeltaCloud.define_class("#{model.to_s.classify}")
111
+ # Create collection from index operation
112
+ base_object_collection(c, model, response)
113
+ end
114
+ end
115
+ logger << "[API] Added method #{model}\n"
116
+ define_method :"#{model.to_s.singularize}" do |*args|
117
+ request(:get, "/#{model}/#{args[0]}") do |response|
118
+ # Define a new class based on model name
119
+ c = DeltaCloud.define_class("#{model.to_s.classify}")
120
+ # Build class for returned object
121
+ base_object(c, model, response)
122
+ end
123
+ end
124
+ logger << "[API] Added method #{model.to_s.singularize}\n"
125
+ define_method :"fetch_#{model.to_s.singularize}" do |url|
126
+ id = url.grep(/\/#{model}\/(.*)$/)
127
+ self.send(model.to_s.singularize.to_sym, $1)
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ def base_object_collection(c, model, response)
134
+ collection = []
135
+ Nokogiri::XML(response).xpath("#{model}/#{model.to_s.singularize}").each do |item|
136
+ c.instance_eval do
137
+ attr_accessor :id
138
+ attr_accessor :uri
139
+ end
140
+ collection << xml_to_class(c, item)
141
+ end
142
+ return collection
143
+ end
144
+
145
+ # Add default attributes [id and href] to class
146
+ def base_object(c, model, response)
147
+ obj = nil
148
+ Nokogiri::XML(response).xpath("#{model.to_s.singularize}").each do |item|
149
+ c.instance_eval do
150
+ attr_accessor :id
151
+ attr_accessor :uri
152
+ end
153
+ obj = xml_to_class(c, item)
154
+ end
155
+ return obj
156
+ end
157
+
158
+ # Convert XML response to defined Ruby Class
159
+ def xml_to_class(c, item)
160
+ obj = c.new
161
+ # Set default attributes
162
+ obj.id = item['id']
163
+ api = self
164
+ c.instance_eval do
165
+ define_method :client do
166
+ api
167
+ end
168
+ end
169
+ obj.uri = item['href']
170
+ logger = @logger
171
+ logger << "[DC] Creating class #{obj.class.name}\n"
172
+ obj.instance_eval do
173
+ # Declare methods for all attributes in object
174
+ item.xpath('./*').each do |attribute|
175
+ # If attribute is a link to another object then
176
+ # create a method which request this object from API
177
+ if api.entry_points.keys.include?(:"#{attribute.name}s")
178
+ c.instance_eval do
179
+ define_method :"#{attribute.name.sanitize}" do
180
+ client.send(:"#{attribute.name}", attribute['id'] )
181
+ end
182
+ logger << "[DC] Added #{attribute.name} to class #{obj.class.name}\n"
183
+ end
184
+ else
185
+ # Define methods for other attributes
186
+ c.instance_eval do
187
+ case attribute.name
188
+ # When response cointains 'link' block, declare
189
+ # methods to call links inside. This is used for instance
190
+ # to dynamicaly create .stop!, .start! methods
191
+ when "actions":
192
+ actions = []
193
+ attribute.xpath('link').each do |link|
194
+ actions << [link['rel'], link[:href]]
195
+ define_method :"#{link['rel'].sanitize}!" do
196
+ client.request(:"#{link['method']}", link['href'], {}, {})
197
+ @current_state = client.send(:"#{item.name}", item['id']).state
198
+ obj.instance_eval do |o|
199
+ def state
200
+ @current_state
201
+ end
202
+ end
203
+ end
204
+ end
205
+ define_method :actions do
206
+ actions.collect { |a| a.first }
207
+ end
208
+ define_method :actions_urls do
209
+ urls = {}
210
+ actions.each { |a| urls[a.first] = a.last }
211
+ urls
212
+ end
213
+ # Property attribute is handled differently
214
+ when "property":
215
+ attr_accessor :"#{attribute['name'].sanitize}"
216
+ if attribute['value'] =~ /^(\d+)$/
217
+ obj.send(:"#{attribute['name'].sanitize}=",
218
+ DeltaCloud::HWP::FloatProperty.new(attribute, attribute['name']))
219
+ else
220
+ obj.send(:"#{attribute['name'].sanitize}=",
221
+ DeltaCloud::HWP::Property.new(attribute, attribute['name']))
222
+ end
223
+ # Public and private addresses are returned as Array
224
+ when "public_addresses", "private_addresses":
225
+ attr_accessor :"#{attribute.name.sanitize}"
226
+ obj.send(:"#{attribute.name.sanitize}=",
227
+ attribute.xpath('address').collect { |address| address.text })
228
+ # Value for other attributes are just returned using
229
+ # method with same name as attribute (eg. .owner_id, .state)
230
+ else
231
+ attr_accessor :"#{attribute.name.sanitize}"
232
+ obj.send(:"#{attribute.name.sanitize}=", attribute.text.convert)
233
+ logger << "[DC] Added method #{attribute.name}[#{attribute.text}] to #{obj.class.name}\n"
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
239
+ return obj
240
+ end
241
+
242
+ # Get /api and parse entry points
243
+ def discover_entry_points
244
+ return if discovered?
245
+ request(:get, @api_uri.to_s) do |response|
246
+ api_xml = Nokogiri::XML(response)
247
+ @driver_name = api_xml.xpath('/api').first['driver']
248
+ @api_version = api_xml.xpath('/api').first['version']
249
+ logger << "[API] Version #{@api_version}\n"
250
+ logger << "[API] Driver #{@driver_name}\n"
251
+ api_xml.css("api > link").each do |entry_point|
252
+ rel, href = entry_point['rel'].to_sym, entry_point['href']
253
+ @entry_points.store(rel, href)
254
+ logger << "[API] Entry point '#{rel}' added\n"
255
+ entry_point.css("feature").each do |feature|
256
+ @features[rel] ||= []
257
+ @features[rel] << feature['name'].to_sym
258
+ logger << "[API] Feature #{feature['name']} added to #{rel}\n"
259
+ end
260
+ end
261
+ end
262
+ declare_entry_points_methods(@entry_points)
263
+ end
264
+
265
+ def create_key(opts={}, &block)
266
+ params = { :name => opts[:name] }
267
+ key = nil
268
+ request(:post, entry_points[:keys], {}, params) do |response|
269
+ c = DeltaCloud.define_class("Key")
270
+ key = base_object(c, :key, response)
271
+ yield key if block_given?
272
+ end
273
+ return key
274
+ end
275
+
276
+ # Create a new instance, using image +image_id+. Possible optiosn are
277
+ #
278
+ # name - a user-defined name for the instance
279
+ # realm - a specific realm for placement of the instance
280
+ # hardware_profile - either a string giving the name of the
281
+ # hardware profile or a hash. The hash must have an
282
+ # entry +id+, giving the id of the hardware profile,
283
+ # and may contain additional names of properties,
284
+ # e.g. 'storage', to override entries in the
285
+ # hardware profile
286
+ def create_instance(image_id, opts={}, &block)
287
+ name = opts[:name]
288
+ realm_id = opts[:realm]
289
+ user_data = opts[:user_data]
290
+ key_name = opts[:key_name]
291
+ security_group = opts[:security_group]
292
+
293
+ params = {}
294
+ ( params[:realm_id] = realm_id ) if realm_id
295
+ ( params[:name] = name ) if name
296
+ ( params[:user_data] = user_data ) if user_data
297
+ ( params[:keyname] = key_name ) if key_name
298
+ ( params[:security_group] = security_group) if security_group
299
+
300
+ if opts[:hardware_profile].is_a?(String)
301
+ params[:hwp_id] = opts[:hardware_profile]
302
+ elsif opts[:hardware_profile].is_a?(Hash)
303
+ opts[:hardware_profile].each do |k,v|
304
+ params[:"hwp_#{k}"] = v
305
+ end
306
+ end
307
+
308
+ params[:image_id] = image_id
309
+ instance = nil
310
+
311
+ request(:post, entry_points[:instances], {}, params) do |response|
312
+ c = DeltaCloud.define_class("Instance")
313
+ instance = base_object(c, :instance, response)
314
+ yield instance if block_given?
315
+ end
316
+
317
+ return instance
318
+ end
319
+
320
+ # Basic request method
321
+ #
322
+ def request(*args, &block)
323
+ conf = {
324
+ :method => (args[0] || 'get').to_sym,
325
+ :path => (args[1]=~/^http/) ? args[1] : "#{api_uri.to_s}#{args[1]}",
326
+ :query_args => args[2] || {},
327
+ :form_data => args[3] || {}
328
+ }
329
+ if conf[:query_args] != {}
330
+ conf[:path] += '?' + URI.escape(conf[:query_args].collect{ |key, value| "#{key}=#{value}" }.join('&')).to_s
331
+ end
332
+ logger << "[#{conf[:method].to_s.upcase}] #{conf[:path]}\n"
333
+ if conf[:method].eql?(:post)
334
+ RestClient.send(:post, conf[:path], conf[:form_data], default_headers) do |response, request, block|
335
+ if response.respond_to?('body')
336
+ yield response.body if block_given?
337
+ else
338
+ yield response.to_s if block_given?
339
+ end
340
+ end
341
+ else
342
+ RestClient.send(conf[:method], conf[:path], default_headers) do |response, request, block|
343
+ if response.respond_to?('body')
344
+ yield response.body if block_given?
345
+ else
346
+ yield response.to_s if block_given?
347
+ end
348
+ end
349
+ end
350
+ end
351
+
352
+ # Check if specified collection have wanted feature
353
+ def feature?(collection, name)
354
+ @features.has_key?(collection) && @features[collection].include?(name)
355
+ end
356
+
357
+ # List available instance states and transitions between them
358
+ def instance_states
359
+ states = []
360
+ request(:get, entry_points[:instance_states]) do |response|
361
+ Nokogiri::XML(response).xpath('states/state').each do |state_el|
362
+ state = DeltaCloud::InstanceState::State.new(state_el['name'])
363
+ state_el.xpath('transition').each do |transition_el|
364
+ state.transitions << DeltaCloud::InstanceState::Transition.new(
365
+ transition_el['to'],
366
+ transition_el['action']
367
+ )
368
+ end
369
+ states << state
370
+ end
371
+ end
372
+ states
373
+ end
374
+
375
+ # Select instance state specified by name
376
+ def instance_state(name)
377
+ instance_states.select { |s| s.name.to_s.eql?(name.to_s) }.first
378
+ end
379
+
380
+ # Skip parsing /api when we already got entry points
381
+ def discovered?
382
+ true if @entry_points!={}
383
+ end
384
+
385
+ def documentation(collection, operation=nil)
386
+ data = {}
387
+ request(:get, "/docs/#{collection}") do |body|
388
+ document = Nokogiri::XML(body)
389
+ if operation
390
+ data[:operation] = operation
391
+ data[:description] = document.xpath('/docs/collection/operations/operation[@name = "'+operation+'"]/description').first.text.strip
392
+ return false unless data[:description]
393
+ data[:params] = []
394
+ (document/"/docs/collection/operations/operation[@name='#{operation}']/parameter").each do |param|
395
+ data[:params] << {
396
+ :name => param['name'],
397
+ :required => param['type'] == 'optional',
398
+ :type => (param/'class').text
399
+ }
400
+ end
401
+ else
402
+ data[:description] = (document/'/docs/collection/description').text
403
+ data[:collection] = collection
404
+ data[:operations] = (document/"/docs/collection/operations/operation").collect{ |o| o['name'] }
405
+ end
406
+ end
407
+ return Documentation.new(self, data)
408
+ end
409
+
410
+ private
411
+
412
+ def default_headers
413
+ # The linebreaks inserted every 60 characters in the Base64
414
+ # encoded header cause problems under JRuby
415
+ auth_header = "Basic "+Base64.encode64("#{@username}:#{@password}")
416
+ auth_header.gsub!("\n", "")
417
+ {
418
+ :authorization => auth_header,
419
+ :accept => "application/xml"
420
+ }
421
+ end
422
+
423
+ end
424
+
425
+ class Documentation
426
+
427
+ attr_reader :api, :description, :params, :collection_operations
428
+ attr_reader :collection, :operation
429
+
430
+ def initialize(api, opts={})
431
+ @description, @api = opts[:description], api
432
+ @params = parse_parameters(opts[:params]) if opts[:params]
433
+ @collection_operations = opts[:operations] if opts[:operations]
434
+ @collection = opts[:collection]
435
+ @operation = opts[:operation]
436
+ self
437
+ end
438
+
439
+ def operations
440
+ @collection_operations.collect { |o| api.documentation(@collection, o) }
441
+ end
442
+
443
+ class OperationParameter
444
+ attr_reader :name
445
+ attr_reader :type
446
+ attr_reader :required
447
+ attr_reader :description
448
+
449
+ def initialize(data)
450
+ @name, @type, @required, @description = data[:name], data[:type], data[:required], data[:description]
451
+ end
452
+
453
+ def to_comment
454
+ " # @param [#{@type}, #{@name}] #{@description}"
455
+ end
456
+
457
+ end
458
+
459
+ private
460
+
461
+ def parse_parameters(params)
462
+ params.collect { |p| OperationParameter.new(p) }
463
+ end
464
+
465
+ end
466
+
467
+ module InstanceState
468
+
469
+ class State
470
+ attr_reader :name
471
+ attr_reader :transitions
472
+
473
+ def initialize(name)
474
+ @name, @transitions = name, []
475
+ end
476
+ end
477
+
478
+ class Transition
479
+ attr_reader :to
480
+ attr_reader :action
481
+
482
+ def initialize(to, action)
483
+ @to = to
484
+ @action = action
485
+ end
486
+
487
+ def auto?
488
+ @action.nil?
489
+ end
490
+ end
491
+ end
492
+
493
+ module HWP
494
+
495
+ class Property
496
+ attr_reader :name, :unit, :value, :kind
497
+
498
+ def initialize(xml, name)
499
+ @name, @kind, @value, @unit = xml['name'], xml['kind'].to_sym, xml['value'], xml['unit']
500
+ declare_ranges(xml)
501
+ self
502
+ end
503
+
504
+ def present?
505
+ ! @value.nil?
506
+ end
507
+
508
+ private
509
+
510
+ def declare_ranges(xml)
511
+ case xml['kind']
512
+ when 'range':
513
+ self.class.instance_eval do
514
+ attr_reader :range
515
+ end
516
+ @range = { :from => xml.xpath('range').first['first'], :to => xml.xpath('range').first['last'] }
517
+ when 'enum':
518
+ self.class.instance_eval do
519
+ attr_reader :options
520
+ end
521
+ @options = xml.xpath('enum/entry').collect { |e| e['value'] }
522
+ end
523
+ end
524
+
525
+ end
526
+
527
+ # FloatProperty is like Property but return value is Float instead of String.
528
+ class FloatProperty < Property
529
+ def initialize(xml, name)
530
+ super(xml, name)
531
+ @value = @value.to_f if @value
532
+ end
533
+ end
534
+ end
535
+
536
+ end
537
+
538
+ class String
539
+
540
+ unless method_defined?(:classify)
541
+ # Create a class name from string
542
+ def classify
543
+ self.singularize.camelize
544
+ end
545
+ end
546
+
547
+ unless method_defined?(:camelize)
548
+ # Camelize converts strings to UpperCamelCase
549
+ def camelize
550
+ self.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
551
+ end
552
+ end
553
+
554
+ unless method_defined?(:singularize)
555
+ # Strip 's' character from end of string
556
+ def singularize
557
+ self.gsub(/s$/, '')
558
+ end
559
+ end
560
+
561
+ # Convert string to float if string value seems like Float
562
+ def convert
563
+ return self.to_f if self.strip =~ /^([\d\.]+$)/
564
+ self
565
+ end
566
+
567
+ # Simply converts whitespaces and - symbols to '_' which is safe for Ruby
568
+ def sanitize
569
+ self.gsub(/(\W+)/, '_')
570
+ end
571
+
572
+ end