onering-agent 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NTliNjI0MzU5MTllMGFkNDg1OGQxMjBhNzBmZmFhOTIyMWY2ZTBkYg==
5
+ data.tar.gz: !binary |-
6
+ YjQ5Y2NiOTlkZjQ5YjE3OTIyZmZiNjZhNjdhOWI3MGJhNjhhMzgxZQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ YzFiYjNlMWYyMmY0MzZkM2NhMjBlZGVlYzE4Y2UyZjRjNDlhOGNmNzk1ZDdk
10
+ M2E2Njg2MDM2ZDg2YzcwYzk3ZjY0YmRlYWMyNzgzMmMyZDNiZGJhMWJjNWZj
11
+ MGNkNGU1NjNhYmRhMDY4MDA2NWVhYTNhMzk5N2Y4NThiMTI3MjA=
12
+ data.tar.gz: !binary |-
13
+ MjE5MzQ5ZTlhYzc1ZjIxNDkxMmE5NDM5OWRlYjgzNWVlYzQyNmVhN2U4ZTQ4
14
+ YjM2ZWM2MTZjODEwNWI2ZDM3Mjc5NTNkMjlkMTQzZDk1NWJlOTVmZDIyYmRm
15
+ OWIzOGU4MTZlZGNiNTQ5MzRhMmRiZGFmNDdkNTc2YTg5ZGUwZGU=
data/bin/onering ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+ $:.push 'lib'
3
+ require 'trollop'
4
+ require 'onering'
5
+ require 'hashlib'
6
+ require 'rainbow'
7
+ require 'pp'
8
+
9
+ plugins = Onering::CLI::Plugin.registered_plugins.collect{|i| i.name.split('::').last.downcase }
10
+ exclude_plugins = %w{devices}
11
+
12
+ global = Trollop::options do
13
+ banner <<-EOS
14
+ onering command line client utility - version #{Onering::Client::VERSION}
15
+
16
+ Usage:
17
+ onering [global] [plugin] [subcommand] [options]
18
+
19
+ Plugins (onering <plugin> --help for usage details):
20
+ #{(plugins - exclude_plugins).sort.join(', ')}
21
+
22
+ where [global] options are:
23
+ EOS
24
+
25
+ opt :url, "The URL of the Onering server to connect to", :short => '-s', :type => :string
26
+ opt :path, "The base path to prepend to all requests (default: /api)", :type => :string
27
+ opt :source, "Specify the source IP address to use (i.e. which network interface the request should originate from)", :short => '-I', :type => :string
28
+ opt :param, "Additional query string parameters to include with the request in the format FIELD=VALUE. Can be specified multiple times.", :short => '-p', :type => :string, :multi => true
29
+ opt :format, "The output format for return values (i.e.: json, yaml, text)", :short => '-t', :type => :string
30
+ opt :sslkey, "Location of the SSL client key to use for authentication", :short => '-c', :type => :string
31
+ opt :nosslverify, "Disable verification of the server SSL certificate", :type => :boolean
32
+ opt :apikey, "The API token to use for authentication", :short => '-k', :type => :string
33
+ opt :quiet, "Suppress standard output", :short => '-q'
34
+ opt :separator, "A string used to separate output values of delimited tabular data", :short => '-S', :default => "\t"
35
+ opt :verbosity, "Set the log level (fatal, error, warn, info, debug)", :short => '-v', :type => :string, :default => 'warn'
36
+
37
+ stop_on plugins
38
+ end
39
+
40
+
41
+ plugin = ARGV.shift
42
+ Trollop::die("plugin argument is requried") if plugin.nil?
43
+
44
+ Onering::Logger.setup({
45
+ :destination => 'stdout',
46
+ :threshold => global[:verbosity]
47
+ })
48
+
49
+ if plugins.include?(plugin)
50
+ begin
51
+ plugin = Onering::CLI.const_get(plugin.capitalize)
52
+ plugin.configure(global)
53
+
54
+ Onering::Logger.debug("Executing plugin #{plugin}\#run()", $0)
55
+ rv = plugin.run(ARGV)
56
+ Onering::CLI.output(rv, (global[:format] || plugin.default_format(rv, ARGV) || 'text'))
57
+
58
+ rescue Onering::API::Errors::Exception => e
59
+ Onering::Logger.fatal(e.message, e.class.name.split('::').last) rescue nil
60
+ exit 1
61
+
62
+ rescue Onering::Client::FatalError => e
63
+ exit 255
64
+
65
+ end
66
+ else
67
+ Trollop::die("unknown plugin #{plugin}")
68
+ end
@@ -0,0 +1,19 @@
1
+ serialnumber:serial
2
+ manufacturer:make
3
+ productname:model
4
+ boardmanufacturer:mbmake
5
+ boardproductname:mbmodel
6
+ boardserialnumber:mbserial
7
+ hostname
8
+ fqdn
9
+ default_ipaddress:ip
10
+ default_macaddress:mac
11
+ default_gateway:gateway
12
+ kernelrelease:kernel
13
+ kernelarguments:@kernelarguments
14
+ architecture:arch
15
+ operatingsystem:distro
16
+ operatingsystemrelease:version
17
+ signature
18
+ boottime:booted_at
19
+ onering:@onering
data/lib/onering.rb ADDED
@@ -0,0 +1,32 @@
1
+ $: << File.expand_path(File.dirname(__FILE__))
2
+
3
+ module Onering
4
+ FULL_CLIENT = true
5
+ end
6
+
7
+ require 'onering/version'
8
+ require 'onering/logger'
9
+
10
+ if not ENV['ONERING_LOGLEVEL'].nil?
11
+ Onering::Logger.setup({
12
+ :destination => 'stderr',
13
+ :threshold => ENV['ONERING_LOGLEVEL']
14
+ })
15
+ end
16
+
17
+ require 'onering/util'
18
+ require 'onering/api'
19
+ require 'onering/cli'
20
+
21
+ # require plugins
22
+ Dir[File.join(File.expand_path(File.dirname(__FILE__)),"onering","plugins","*.rb")].each do |i|
23
+ require i
24
+ end
25
+
26
+ # require cli submodules
27
+ Dir[File.join(File.expand_path(File.dirname(__FILE__)),"onering","cli","*.rb")].each do |i|
28
+ require i
29
+ end
30
+
31
+ # you've loaded the library, now load the config
32
+ Onering::Config.load()
@@ -0,0 +1,322 @@
1
+ require 'openssl'
2
+ require 'yaml'
3
+ require 'hashlib'
4
+ require 'deep_merge'
5
+ require 'addressable/uri'
6
+ require 'httparty'
7
+ require 'onering/config'
8
+
9
+ module Onering
10
+ class API
11
+ module Actions
12
+ class Retry < ::Exception; end
13
+ end
14
+
15
+ module Errors
16
+ class Exception < ::Exception; end
17
+ class NotConnected < Exception; end
18
+
19
+ class ClientError < Exception; end
20
+ class Unauthorized < ClientError; end
21
+ class Forbidden < ClientError; end
22
+ class NotFound < ClientError; end
23
+
24
+ class ServerError < Exception; end
25
+ class ConnectionTimeout < Exception; end
26
+ class AuthenticationMissing < Exception; end
27
+ end
28
+
29
+ include Onering::Util
30
+ include ::HTTParty
31
+
32
+ attr_accessor :url
33
+ format :json
34
+
35
+ DEFAULT_BASE="https://onering"
36
+ DEFAULT_PATH="/api"
37
+ DEFAULT_CLIENT_PEM=["~/.onering/client.pem", "/etc/onering/client.pem"]
38
+ DEFAULT_CLIENT_KEY=["~/.onering/client.key", "/etc/onering/client.key"]
39
+ DEFAULT_VALIDATION_PEM="/etc/onering/validation.pem"
40
+
41
+
42
+ def initialize(options={})
43
+ @_plugins = {}
44
+ options = {} if options.nil?
45
+ @_connection_options = options
46
+
47
+ # load and merge all config file sources
48
+ Onering::Config.load(@_connection_options[:configfile], @_connection_options.get(:config, {}))
49
+
50
+ if options.get('config.nosslverify', false) == true
51
+ # deliberately break SSL verification
52
+ Onering::Logger.warn("Disabling SSL peer verification for #{options.get('config.url')}")
53
+ OpenSSL::SSL.send(:const_set, :OLD_VERIFY_PEER, OpenSSL::SSL::VERIFY_PEER)
54
+ OpenSSL::SSL.send(:remove_const, :VERIFY_PEER)
55
+ OpenSSL::SSL.send(:const_set, :VERIFY_PEER, OpenSSL::SSL::VERIFY_NONE)
56
+ else
57
+ # restore SSL verification if it's currently broken
58
+ if defined?(OpenSSL::SSL::OLD_VERIFY_PEER)
59
+ if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE and OpenSSL::SSL::OLD_VERIFY_PEER != OpenSSL::SSL::VERIFY_NONE
60
+ OpenSSL::SSL.send(:remove_const, :VERIFY_PEER)
61
+ OpenSSL::SSL.send(:const_set, :VERIFY_PEER, OpenSSL::SSL::OLD_VERIFY_PEER)
62
+ end
63
+ end
64
+ end
65
+
66
+ if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE
67
+ Onering::Logger.warn("Disabling SSL peer verification for #{options.get('config.url')}")
68
+ end
69
+
70
+ # source interface specified
71
+ # !! HAX !! HAX !! HAX !! HAX !! HAX !! HAX !! HAX !! HAX !! HAX !! HAX !!
72
+ # Due to certain versions of Ruby's Net::HTTP not allowing you explicitly
73
+ # specify the source IP/interface to use, this horrific monkey patch is
74
+ # necessary, if not right.
75
+ #
76
+ # If at least some of your code doesn't make you feel bottomless shame
77
+ # then you aren't coding hard enough.
78
+ #
79
+ if options.get('config.source').is_a?(String)
80
+ if options.get('config.source') =~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/
81
+ # insert firing pin into the hack
82
+ TCPSocket.instance_eval do
83
+ (class << self; self; end).instance_eval do
84
+ alias_method :_stock_open, :open
85
+ attr_writer :_hack_local_ip
86
+
87
+ define_method(:open) do |conn_address, conn_port|
88
+ _stock_open(conn_address, conn_port, @_hack_local_ip)
89
+ end
90
+ end
91
+ end
92
+
93
+ # arm the hack
94
+ TCPSocket._hack_local_ip = options.get('config.source')
95
+
96
+ # sound the siren
97
+ Onering::Logger.info("Using local interface #{options.get('config.source')} to connect", "Onering::API")
98
+
99
+ else
100
+ raise "Invalid source IP address #{options.get('config.source')}"
101
+ end
102
+ end
103
+
104
+ # set API connectivity details
105
+ Onering::API.base_uri(options.get('config.url', Onering::Config.get(:url, DEFAULT_BASE)))
106
+ Onering::Logger.info("Server URL is #{Onering::API.base_uri}", "Onering::API")
107
+
108
+ # add default parameters
109
+ options.get('config.params',{}).each do |k,v|
110
+ _default_param(k,v)
111
+ end
112
+
113
+ connect(options) if options.get(:autoconnect, true)
114
+ end
115
+
116
+ def connect(options={})
117
+ # setup authentication
118
+ _setup_auth()
119
+
120
+ Onering::Logger.debug("Connection setup complete", "Onering::API")
121
+ return self
122
+ end
123
+
124
+
125
+ def request(method, endpoint, options={})
126
+ endpoint = [Onering::Config.get(:path, DEFAULT_PATH).strip, endpoint.sub(/^\//,'')].join('/')
127
+
128
+ Onering::Logger.debug("#{method.to_s.upcase} #{endpoint}#{(options[:query] || {}).empty? ? '' : '?'+options[:query].join('=', '&')}", "Onering::API")
129
+ options.get(:headers,[]).each do |name, value|
130
+ next if name == 'Content-Type' and value == 'application/json'
131
+ Onering::Logger.debug("+#{name}: #{value}", "Onering::API")
132
+ end
133
+
134
+ begin
135
+ case (method.to_sym rescue method)
136
+ when :post
137
+ rv = Onering::API.post(endpoint, options)
138
+ when :put
139
+ rv = Onering::API.put(endpoint, options)
140
+ when :delete
141
+ rv = Onering::API.delete(endpoint, options)
142
+ when :head
143
+ rv = Onering::API.head(endpoint, options)
144
+ else
145
+ rv = Onering::API.get(endpoint, options)
146
+ end
147
+ rescue SocketError => e
148
+ Onering::Logger.fatal!("Unable to connect to #{Onering::API.base_uri}", "Onering::API")
149
+ end
150
+
151
+ if rv.code >= 500
152
+ raise Errors::ServerError.new("HTTP #{rv.code} - #{Onering::Util.http_status(rv.code)} #{rv.parsed_response.get('error.message','') rescue ''}")
153
+ elsif rv.code >= 400
154
+ message = "HTTP #{rv.code} - #{Onering::Util.http_status(rv.code)} #{rv.parsed_response.get('error.message', '') rescue ''}"
155
+
156
+ case rv.code
157
+ when 401
158
+ raise Errors::Unauthorized.new(message)
159
+ when 403
160
+ raise Errors::Forbidden.new(message)
161
+ when 404
162
+ raise Errors::NotFound.new(message)
163
+ else
164
+ raise Errors::ClientError.new(message)
165
+ end
166
+ else
167
+ rv
168
+ end
169
+ end
170
+
171
+
172
+ def get(endpoint, options={})
173
+ request(:get, endpoint, options)
174
+ end
175
+
176
+ def post(endpoint, options={}, &block)
177
+ if block_given?
178
+ request(:post, endpoint, options.merge({
179
+ :body => yield
180
+ }))
181
+ else
182
+ request(:post, endpoint, options)
183
+ end
184
+ end
185
+
186
+ def put(endpoint, options={}, &block)
187
+ if block_given?
188
+ request(:put, endpoint, options.merge({
189
+ :body => yield
190
+ }))
191
+ else
192
+ request(:put, endpoint, options)
193
+ end
194
+ end
195
+
196
+ def delete(endpoint, options={})
197
+ request(:delete, endpoint, options)
198
+ end
199
+
200
+
201
+ # I'm not a huge fan of what's happening here, but metaprogramming is hard...
202
+ #
203
+ # "Don't let the perfect be the enemy of the good."
204
+ #
205
+ def method_missing(method, *args, &block)
206
+ modname = method.to_s.split('_').map(&:capitalize).join
207
+
208
+ if not (plugin = (Onering::API.const_get(modname) rescue nil)).nil?
209
+ @_plugins[method] ||= plugin.new.connect(@_connection_options)
210
+ return @_plugins[method]
211
+ else
212
+ super
213
+ end
214
+ end
215
+
216
+ def status()
217
+ Onering::API.new.get("/").parsed_response
218
+ end
219
+
220
+ # -----------------------------------------------------------------------------
221
+ def _setup_auth()
222
+ type = Onering::Config.get('authentication.type', :auto)
223
+ _setup_auth_token()
224
+ end
225
+
226
+ # -----------------------------------------------------------------------------
227
+ def _default_param(key, value)
228
+ @_default_params ||= {}
229
+ @_default_params[key] = value
230
+ Onering::API.default_params(@_default_params)
231
+ end
232
+
233
+ # -----------------------------------------------------------------------------
234
+ def _setup_auth_token()
235
+ Onering::Logger.info("Using token authentication mechanism", "Onering::API")
236
+
237
+ # get first keyfile found
238
+ key = Onering::Config.get('authentication.key', Onering::Config.get('authentication.keyfile'))
239
+
240
+ if key.nil?
241
+ if Onering::Config.get('authentication.bootstrap.enabled', true)
242
+ Onering::Logger.warn("Authentication token not found, attempting to autoregister client", "Onering::API")
243
+
244
+ if not (bootstrap = Onering::Config.get('authentication.bootstrap.key')).nil?
245
+ if bootstrap.to_s =~ /[0-9a-f]{32,64}/
246
+ # attempt to create key.yml from least-specific to most, first writable path wins
247
+ clients = [{
248
+ :path => "/etc/onering",
249
+ :name => fact('hardwareid'),
250
+ :keyname => 'system',
251
+ :autodelete => true
252
+ },{
253
+ :path => "~/.onering",
254
+ :name => ENV['USER'],
255
+ :keyname => 'cli',
256
+ :autodelete => false
257
+ }]
258
+
259
+ # for each client attempt...
260
+ clients.each do |client|
261
+ # expand and assemble path
262
+ client[:path] = (File.expand_path(client[:path]) rescue client[:path])
263
+ keyfile = File.join(client[:path], 'key.yml')
264
+
265
+ # skip this if we can't write to the parent directory
266
+ next unless File.writable?(client[:path])
267
+ Dir.mkdir(client[:path]) unless File.directory?(client[:path])
268
+ next if File.exists?(keyfile)
269
+
270
+ self.class.headers({
271
+ 'X-Auth-Bootstrap-Token' => bootstrap
272
+ })
273
+
274
+ # attempt to create/download the keyfile
275
+ Onering::Logger.debug("Requesting authentication token for #{client[:name].strip}; #{bootstrap}", "Onering::API")
276
+ response = self.class.get("/api/users/#{client[:name].strip}/tokens/#{client[:keyname]}")
277
+
278
+ # if successful, write the file
279
+ if response.code < 400 and response.body
280
+ File.open(keyfile, 'w').puts(YAML.dump({
281
+ 'authentication' => {
282
+ 'key' => response.body.strip.chomp
283
+ }
284
+ }))
285
+
286
+ key = response.body.strip.chomp
287
+
288
+ else
289
+ # all errors are fatal at this stage
290
+ Onering::Logger.fatal!("Cannot autoregister client: HTTP #{response.code} - #{(response.parsed_response || {}).get('error.message', 'Unknown error')}", "Onering::API")
291
+ end
292
+
293
+ self.class.headers({})
294
+
295
+ # we're done here...
296
+ break
297
+ end
298
+ else
299
+ raise Errors::AuthenticationMissing.new("Autoregistration failed: invalid bootstrap token specified")
300
+ end
301
+
302
+ else
303
+ raise Errors::AuthenticationMissing.new("Autoregistration failed: no bootstrap token specified")
304
+ end
305
+
306
+ else
307
+ raise Errors::AuthenticationMissing.new("Authentication token not found, and autoregistration disabled")
308
+ end
309
+ end
310
+
311
+ raise Errors::AuthenticationMissing.new("Token authentication specified, but cannot find a token config or as a command line argument") if key.nil?
312
+
313
+ # set auth mechanism
314
+ Onering::API.headers({
315
+ 'X-Auth-Mechanism' => 'token'
316
+ })
317
+
318
+ # set default parameters
319
+ _default_param(:token, key)
320
+ end
321
+ end
322
+ end