ncc-api 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []