ncc-api 1.0.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.
@@ -0,0 +1,213 @@
1
+ #!ruby
2
+ # /* Copyright 2013 Proofpoint, Inc. All rights reserved.
3
+ # Copyright 2014 Evernote Corporation. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ # */
17
+
18
+
19
+ class Hash
20
+
21
+ def deep_soft_merge(other)
22
+ r = self.dup
23
+ other.each_pair do |key, value|
24
+ if r.has_key? key
25
+ if r[key].respond_to? :to_hash and
26
+ other[key].respond_to? :to_hash
27
+ r[key] =
28
+ r[key].to_hash.deep_soft_merge(other[key].to_hash)
29
+ end
30
+ else
31
+ r[key] = value
32
+ end
33
+ end
34
+ r
35
+ end
36
+
37
+ def delete_nil_values
38
+ self.keys.each { |k| delete(k) if self[k].nil? }
39
+ self
40
+ end
41
+
42
+ end
43
+
44
+ class NCC
45
+
46
+ end
47
+
48
+ class NCC::Instance
49
+
50
+ @@valid_statuses = %w(active build terminated error hard-reboot
51
+ reboot provider-operation shutting-down
52
+ suspending suspend unknown needs-verify)
53
+
54
+ attr_accessor :name, :environment, :id, :ip_address, :host,
55
+ :console_log
56
+ attr_reader :image, :size, :status
57
+
58
+ # Should be mixed in
59
+ def debug(msg=nil)
60
+ msg ||= yield
61
+ log 'debug', msg
62
+ end
63
+
64
+ def warn(msg)
65
+ log 'warn', msg
66
+ end
67
+
68
+ def log(level, msg)
69
+ @logger.send(level.intern, "#<#{me}>: #{msg}") if
70
+ @logger.respond_to? level.intern
71
+ end
72
+
73
+ def initialize(cfg, opt={})
74
+ @cfg = cfg
75
+ @logger = opt[:logger] if opt.has_key? :logger
76
+ self.id = opt['id']
77
+ self.name = opt['name']
78
+ self.size = opt['size']
79
+ self.image = opt['image']
80
+ self.environment = opt['environment']
81
+ self.role = opt['role']
82
+ self.host = opt['host']
83
+ self.ip_address = opt['ip_address']
84
+ self.console_log = opt['console_log']
85
+ self.extra = opt['extra']
86
+ end
87
+
88
+ def with_defaults(*defaults)
89
+ obj = self.dup
90
+ defaults.each do |d|
91
+ next if d.nil?
92
+ obj.name ||= d['name']
93
+ obj.size ||= d['size']
94
+ obj.image ||= d['image']
95
+ obj.environment ||= d['environment']
96
+ obj.role ||= d['role']
97
+ obj.extra = d['extra']
98
+ end
99
+ obj
100
+ end
101
+
102
+ def role
103
+ @role
104
+ end
105
+
106
+ def role=(value)
107
+ if value.nil?
108
+ @role = []
109
+ elsif value.respond_to? :join
110
+ @role = value
111
+ else
112
+ @role = value.split(/, */)
113
+ end
114
+ end
115
+
116
+ def set_without_validation(fields)
117
+ fields.each_pair do |field, value|
118
+ case field
119
+ when :id
120
+ @id = value
121
+ when :name
122
+ @name = value
123
+ when :size
124
+ @size = value
125
+ when :image
126
+ @image = value
127
+ when :environment
128
+ @environment = value
129
+ when :role
130
+ self.role = value
131
+ when :extra
132
+ @extra = value
133
+ when :status
134
+ self.status = value
135
+ when :ip_address
136
+ @ip_address = value
137
+ when :host
138
+ @host = value
139
+ when :console_log
140
+ @console_log = value
141
+ else
142
+ raise NCC::Error, "Invalid field #{field.inspect}"
143
+ end
144
+ end
145
+ end
146
+
147
+ def image=(newimage)
148
+ raise NCC::Error, "Invalid image #{newimage.inspect}" unless
149
+ newimage.nil? or @cfg[:images].has_key? newimage
150
+ @image = newimage
151
+ end
152
+
153
+ def size=(newsize)
154
+ raise NCC::Error, "Invalid size ${newsize.inspect}" unless
155
+ newsize.nil? or @cfg[:sizes].has_key? newsize
156
+ @size = newsize
157
+ end
158
+
159
+ def clear_extra
160
+ @extra = nil
161
+ end
162
+
163
+ def extra(param=nil)
164
+ if param.nil?
165
+ @extra
166
+ else
167
+ if !@extra.nil? and @extra.has_key? param
168
+ @extra[param]
169
+ else
170
+ { }
171
+ end
172
+ end
173
+ end
174
+
175
+ def extra=(newextra)
176
+ if !newextra.nil?
177
+ raise NCC::Error, "Invalid extra parameter of type " +
178
+ "#{newextra.class} (must be Hash)" unless
179
+ newextra.respond_to? :to_hash
180
+ if @extra.nil?
181
+ @extra = newextra.to_hash
182
+ else
183
+ @extra = @extra.deep_soft_merge(newextra.to_hash)
184
+ end
185
+ end
186
+ end
187
+
188
+ def status=(newstatus)
189
+ raise NCC::Error, "Invalid status #{newstatus.inspect}" unless
190
+ @@valid_statuses.include? newstatus
191
+ @status = newstatus
192
+ end
193
+
194
+ def to_hash
195
+ {
196
+ 'name' => name,
197
+ 'id' => id,
198
+ 'extra' => extra,
199
+ 'environment' => environment,
200
+ 'role' => role,
201
+ 'size' => size,
202
+ 'image' => image,
203
+ 'ip_address' => ip_address,
204
+ 'host' => host,
205
+ 'status' => status
206
+ }.delete_nil_values
207
+ end
208
+
209
+ def to_json
210
+ to_hash.to_json
211
+ end
212
+
213
+ end
data/lib/ncc-api.rb ADDED
@@ -0,0 +1,218 @@
1
+ #!ruby
2
+ # /* Copyright 2013 Proofpoint, Inc. All rights reserved.
3
+ # Copyright 2014 Evernote Corporation. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ # */
17
+
18
+ require 'rubygems'
19
+ require 'ncc'
20
+ require 'json'
21
+ require 'sinatra'
22
+ require 'fog'
23
+ require 'rack/logger'
24
+
25
+
26
+ configure :development do
27
+ set :logging, Logger::DEBUG
28
+ end
29
+
30
+ $ncc = NCC.new
31
+
32
+ def error_message(status, err)
33
+ status_message = case status
34
+ when 400
35
+ "400 Bad Request"
36
+ when 404
37
+ "404 Not Found"
38
+ when 500
39
+ "500 Internal Server Error"
40
+ when 503
41
+ "503 Service Unavailable"
42
+ end
43
+ status_message ||= status.to_s
44
+ data = { "status" => status_message, "message" => err.message }
45
+ if params.has_key? 'details'
46
+ data['details'] = err.backtrace
47
+ data['error'] = err.class
48
+ end
49
+ body = (params.has_key?('pretty') ? (JSON.pretty_generate(data) +
50
+ "\n") : data.to_json)
51
+ [status, { "content-type" => "application/json" }, body]
52
+ end
53
+
54
+ def respond(status, header={}, &block)
55
+ header.merge({ "content-type" => "application/json" })
56
+ begin
57
+ obj = yield
58
+ body = if header["content-type"] == "text/plain"
59
+ obj
60
+ else
61
+ params.has_key?('pretty') ? (JSON.pretty_generate(obj) + "\n") : obj.to_json
62
+ end
63
+ return [status, header, body]
64
+ rescue NCC::Error::NotFound => error
65
+ halt error_message(404, error)
66
+ rescue NCC::Error::Cloud => error
67
+ halt error_message(503, error)
68
+ rescue NCC::Error::Client => error
69
+ halt error_message(400, error)
70
+ rescue Exception => error
71
+ halt error_message(500, error)
72
+ end
73
+ end
74
+
75
+ get '/ncc_api' do
76
+ respond 200 do
77
+ $ncc.config['services'].to_hash.merge({ 'v2api' => $ncc.api_url })
78
+ end
79
+ end
80
+
81
+ get '/ncc_api/v2' do
82
+ respond 200 do
83
+ {
84
+ "clouds" => "/ncc_api/v2/clouds",
85
+ "images" => "/ncc_api/v2/images",
86
+ "sizes" => "/ncc_api/v2/sizes",
87
+ }
88
+ end
89
+ end
90
+
91
+ get '/ncc_api/v2/clouds' do
92
+ respond(200) { $ncc.clouds }
93
+ end
94
+
95
+ get '/ncc_api/v2/sizes' do
96
+ respond(200) { $ncc.sizes }
97
+ end
98
+
99
+ get '/ncc_api/v2/images' do
100
+ respond(200) { $ncc.images }
101
+ end
102
+
103
+ get '/ncc_api/v2/sizes/:size_id' do |size_id|
104
+ respond(200) { $ncc.sizes(size_id) }
105
+ end
106
+
107
+ get '/ncc_api/v2/images/:image_id' do |image_id|
108
+ respond(200) { $ncc.images(image_id) }
109
+ end
110
+
111
+ get '/ncc_api/v2/clouds/:cloud' do |cloud|
112
+ respond 200 do
113
+ {
114
+ 'name' => cloud,
115
+ 'status' => 'ok',
116
+ 'provider' => $ncc.clouds(cloud).provider,
117
+ 'service' => $ncc.clouds(cloud).fog.class.to_s
118
+ }
119
+ end
120
+ end
121
+
122
+ get '/ncc_api/v2/clouds/:cloud/sizes' do |cloud|
123
+ respond(200) { $ncc.clouds(cloud).sizes }
124
+ end
125
+
126
+ get '/ncc_api/v2/clouds/:cloud/sizes/:size_id' do |cloud, size_id|
127
+ respond(200) { $ncc.clouds(cloud).sizes(size_id) }
128
+ end
129
+
130
+ get '/ncc_api/v2/clouds/:cloud/images' do |cloud|
131
+ respond(200) { $ncc.clouds(cloud).images }
132
+ end
133
+
134
+ get '/ncc_api/v2/clouds/:cloud/images/:image_id' do |cloud, image_id|
135
+ respond(200) { $ncc.clouds(cloud).images(image_id) }
136
+ end
137
+
138
+ get '/ncc_api/v2/clouds/:cloud/instances' do |cloud|
139
+ respond(200) { $ncc.clouds(cloud).instances.map { |i| i.to_hash } }
140
+ end
141
+
142
+
143
+ get '/ncc_api/v2/clouds/:cloud/instances/:instance_id/console_log' do |cloud,
144
+ instance_id|
145
+ respond(200, 'content-type' => 'text/plain') do
146
+ # TODO influence last-modified with console log timestamp
147
+ $ncc.clouds(cloud).console_log(instance_id)['output']
148
+ end
149
+ end
150
+
151
+ post '/ncc_api/v2/clouds/:cloud/instances' do |cloud|
152
+ respond 201 do
153
+ begin
154
+ request.body.rewind
155
+ instance_spec = JSON.parse(request.body.read)
156
+ instance_req = instance_spec
157
+ $ncc.clouds(cloud).create_instance(instance_req)
158
+ rescue JSON::ParserError => e
159
+ raise NCC::Error::Client, "Error parsing request: #{e.message}"
160
+ end
161
+ end
162
+ end
163
+
164
+ get '/ncc_api/v2/clouds/:cloud/instances/:instance_id' do |cloud, instance_id|
165
+ respond(200) do
166
+ $ncc.clouds(cloud).instances(instance_id).to_hash
167
+ end
168
+ end
169
+
170
+
171
+ delete '/ncc_api/v2/clouds/:cloud/instances/:instance_id' do |cloud,
172
+ instance_id|
173
+ respond 204 do
174
+ $ncc.clouds(cloud).delete(instance_id)
175
+ nil
176
+ end
177
+ end
178
+
179
+ put '/ncc_api/v2/clouds/:cloud/instances/:instance_id' do |cloud, instance_id|
180
+ respond 202 do
181
+ instance = $ncc.clouds(cloud).instances(instance_id)
182
+ begin
183
+ request.body.rewind
184
+ update_spec = JSON.parse(request.body.read)
185
+ rescue JSON::ParserError => e
186
+ raise NCC::Error::Client, "Error parsing request #{e.message}"
187
+ end
188
+ actions = []
189
+ update_spec.each_pair do |key, value|
190
+ case key
191
+ when 'status'
192
+ if value == 'reboot'
193
+ actions << lambda { instance.status = 'reboot' }
194
+ $ncc.clouds(cloud).reboot(instance.id)
195
+ else
196
+ raise NCC::Error::Client,
197
+ "Cannot update to status #{value.inspect}"
198
+ end
199
+ else
200
+ raise NCC::Error::Client,
201
+ "Cannot update field #{key.inspect}"
202
+ end
203
+ end
204
+ actions.each { |action| action.call }
205
+ instance
206
+ end
207
+ end
208
+
209
+ # This is not doing the right thing, it's overwriting
210
+ # the body when it gets invoked after a normal route
211
+ # not_found do
212
+ # respond(404) do
213
+ # {
214
+ # "status" => "404 Not Found",
215
+ # "message" => "Not a supported resource type"
216
+ # }
217
+ # end
218
+ # end
data/lib/ncc.rb ADDED
@@ -0,0 +1,102 @@
1
+ #!ruby
2
+ # /* Copyright 2013 Proofpoint, Inc. All rights reserved.
3
+ # Copyright 2014 Evernote Corporation. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ # */
17
+
18
+
19
+ require 'ncc/config'
20
+ require 'ncc/connection'
21
+ require 'ncc/instance'
22
+ require 'ncc/error'
23
+ require 'cmdbclient'
24
+
25
+ class NCC
26
+ attr_reader :config, :inventory
27
+
28
+ def initialize(config_path=nil, opt={})
29
+ @logger = opt[:logger] if opt.has_key? :logger
30
+ config_path ||= [
31
+ '/etc/ncc-api']
32
+ config_path = [config_path] unless config_path.respond_to? :unshift
33
+ config_path.unshift(File.join(ENV['NCCAPI_HOME'], 'etc')) if
34
+ ENV['NCCAPI_HOME']
35
+ @config = NCC::Config.new(config_path, :logger => @logger)
36
+ @inventory = CMDBclient.new(@config)
37
+ @clouds = { }
38
+ end
39
+
40
+ def debug(msg)
41
+ if @logger.respond_to? :debug
42
+ @logger.debug "#{me}: #{msg}"
43
+ end
44
+ end
45
+
46
+ def me
47
+ self.class
48
+ end
49
+
50
+ def connect(cloud, opt={})
51
+ if ! @config[:clouds].has_key? cloud
52
+ raise NCC::Error::NotFound, "Cloud #{cloud} not provided"
53
+ end
54
+ @clouds[cloud] ||= NCC::Connection.connect(self, cloud, opt)
55
+ if @clouds[cloud].nil? or ! @clouds[cloud].current?
56
+ @clouds[cloud] = NCC::Connection.connect(self, cloud, opt)
57
+ end
58
+ @clouds[cloud]
59
+ end
60
+
61
+ def clouds(cloud=nil, opt={})
62
+ if cloud.nil?
63
+ @config[:clouds].keys
64
+ else
65
+ connect(cloud, :logger => @logger)
66
+ end
67
+ end
68
+
69
+ def sizes(size=nil)
70
+ if size.nil?
71
+ @config[:sizes].to_array
72
+ else
73
+ if @config[:sizes].has_key? size
74
+ @config[:sizes][size].to_hash
75
+ else
76
+ raise NCC::Error::NotFound, "No such size #{size.inspect}"
77
+ end
78
+ end
79
+ end
80
+
81
+ def images(image=nil)
82
+ if image.nil?
83
+ @config[:images].to_array
84
+ else
85
+ if @config[:images].has_key? image
86
+ @config[:images][image].to_hash
87
+ else
88
+ raise NCC::Error::NotFound, "No such image #{image.inspect}"
89
+ end
90
+ end
91
+ end
92
+
93
+ def api_url
94
+ if @config['services'].has_key? 'v2api'
95
+ @config['services']['v2api']
96
+ else
97
+ 'http://localhost/ncc_api/v2'
98
+ end
99
+ end
100
+
101
+ end
102
+
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ncc-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Brinkley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fog
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sinatra
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: uuidtools
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: The NOMS cloud controller API provides a ReSTful API abstracting and
56
+ aggregating a set of specific cloud computing providers (called "clouds").
57
+ email: jbrinkley@evernote.com
58
+ executables:
59
+ - ncc-api
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - lib/ncc/config.rb
64
+ - lib/ncc/connection/aws.rb
65
+ - lib/ncc/connection/openstack.rb
66
+ - lib/ncc/connection.rb
67
+ - lib/ncc/error.rb
68
+ - lib/ncc/instance.rb
69
+ - lib/ncc-api.rb
70
+ - lib/ncc.rb
71
+ - bin/ncc-api
72
+ homepage: https://github.com/evernote/ncc-api
73
+ licenses:
74
+ - Apache-2
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.0.14
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: NOMS Cloud Controller API
96
+ test_files: []