fleet-ruby 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +67 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +43 -0
- data/LICENSE +201 -0
- data/README.md +151 -0
- data/Rakefile +8 -0
- data/circle.yml +13 -0
- data/fleet-ruby.gemspec +23 -0
- data/lib/fleet.rb +15 -0
- data/lib/fleet/client.rb +119 -0
- data/lib/fleet/client/machines.rb +18 -0
- data/lib/fleet/client/state.rb +18 -0
- data/lib/fleet/client/unit.rb +33 -0
- data/lib/fleet/configuration.rb +39 -0
- data/lib/fleet/connection.rb +33 -0
- data/lib/fleet/error.rb +41 -0
- data/lib/fleet/request.rb +73 -0
- data/lib/fleet/service_definition.rb +39 -0
- data/lib/fleet/version.rb +3 -0
- data/spec/fleet/client/machines_spec.rb +25 -0
- data/spec/fleet/client/state_spec.rb +25 -0
- data/spec/fleet/client/unit_spec.rb +86 -0
- data/spec/fleet/client_spec.rb +296 -0
- data/spec/fleet/configuration_spec.rb +46 -0
- data/spec/fleet/connection_spec.rb +80 -0
- data/spec/fleet/error_spec.rb +23 -0
- data/spec/fleet/request_spec.rb +117 -0
- data/spec/fleet/service_definition_spec.rb +38 -0
- data/spec/fleet_spec.rb +81 -0
- data/spec/spec_helper.rb +9 -0
- metadata +157 -0
data/Rakefile
ADDED
data/circle.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
dependencies:
|
2
|
+
override:
|
3
|
+
- 'rvm-exec 1.9.3 bundle install'
|
4
|
+
- 'rvm-exec 2.0.0 bundle install'
|
5
|
+
- 'rvm-exec 2.1.5 bundle install'
|
6
|
+
- 'rvm-exec 2.2.0 bundle install'
|
7
|
+
|
8
|
+
test:
|
9
|
+
override:
|
10
|
+
- 'rvm-exec 1.9.3 bundle exec rake'
|
11
|
+
- 'rvm-exec 2.0.0 bundle exec rake'
|
12
|
+
- 'rvm-exec 2.1.5 bundle exec rake'
|
13
|
+
- 'rvm-exec 2.2.0 bundle exec rake'
|
data/fleet-ruby.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path('../lib/fleet/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.authors = 'Barry Martin'
|
5
|
+
gem.email = 'nyxcharon@gmail.com'
|
6
|
+
gem.description = 'A simple REST client for the CoreOS Fleet API'
|
7
|
+
gem.summary = 'A simple REST client for the CoreOS Fleet API'
|
8
|
+
gem.homepage = 'https://github.com/nyxcharon/fleet-ruby.git'
|
9
|
+
gem.license = 'Apache 2'
|
10
|
+
gem.platform = Gem::Platform::RUBY
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = 'fleet-ruby'
|
15
|
+
gem.require_paths = %w(lib)
|
16
|
+
gem.version = '0.1.0'
|
17
|
+
gem.required_ruby_version = '>= 1.9.3'
|
18
|
+
gem.add_dependency 'excon', '>= 0.27.4'
|
19
|
+
gem.add_development_dependency 'rake'
|
20
|
+
gem.add_development_dependency 'rspec', '~> 3.0'
|
21
|
+
gem.add_development_dependency 'simplecov', '~> 0.9.0'
|
22
|
+
gem.add_development_dependency 'simplecov-rcov', '~> 0.2.3'
|
23
|
+
end
|
data/lib/fleet.rb
ADDED
data/lib/fleet/client.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'fleet/connection'
|
2
|
+
require 'fleet/error'
|
3
|
+
require 'fleet/request'
|
4
|
+
require 'fleet/service_definition'
|
5
|
+
require 'fleet/client/machines'
|
6
|
+
require 'fleet/client/unit'
|
7
|
+
require 'fleet/client/state'
|
8
|
+
|
9
|
+
module Fleet
|
10
|
+
class Client
|
11
|
+
|
12
|
+
attr_accessor(*Configuration::VALID_OPTIONS_KEYS)
|
13
|
+
|
14
|
+
def initialize(options={})
|
15
|
+
options = Fleet.options.merge(options)
|
16
|
+
Configuration::VALID_OPTIONS_KEYS.each do |key|
|
17
|
+
send("#{key}=", options[key])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
include Fleet::Connection
|
22
|
+
include Fleet::Request
|
23
|
+
|
24
|
+
include Fleet::Client::Machines
|
25
|
+
include Fleet::Client::Unit
|
26
|
+
include Fleet::Client::State
|
27
|
+
|
28
|
+
def list
|
29
|
+
machines = list_machines['machines'] || []
|
30
|
+
machine_ips = machines.each_with_object({}) do |machine, h|
|
31
|
+
h[machine['id']] = machine['primaryIP']
|
32
|
+
end
|
33
|
+
|
34
|
+
states = list_states['states'] || []
|
35
|
+
states.map do |service|
|
36
|
+
{
|
37
|
+
name: service['name'],
|
38
|
+
load_state: service['systemdLoadState'],
|
39
|
+
active_state: service['systemdActiveState'],
|
40
|
+
sub_state: service['systemdSubState'],
|
41
|
+
machine_id: service['machineID'],
|
42
|
+
machine_ip: machine_ips[service['machineID']]
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def list_unit_states
|
48
|
+
list_states['states'] || []
|
49
|
+
end
|
50
|
+
|
51
|
+
def list_fleet_machines
|
52
|
+
list_machines['machines'] || []
|
53
|
+
end
|
54
|
+
|
55
|
+
def submit(name, service_def)
|
56
|
+
unless name =~ /\A[a-zA-Z0-9:_.@-]+\Z/
|
57
|
+
raise ArgumentError, 'name may only contain [a-zA-Z0-9:_.@-]'
|
58
|
+
end
|
59
|
+
|
60
|
+
unless service_def.is_a?(ServiceDefinition)
|
61
|
+
service_def = ServiceDefinition.new(service_def)
|
62
|
+
end
|
63
|
+
|
64
|
+
begin
|
65
|
+
create_unit(name, service_def.to_unit(name))
|
66
|
+
rescue Fleet::PreconditionFailed
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def load(name, service_def=nil)
|
71
|
+
|
72
|
+
if service_def
|
73
|
+
submit(name, service_def)
|
74
|
+
end
|
75
|
+
|
76
|
+
opts = { 'desiredState' => 'loaded', 'name' => name }
|
77
|
+
update_unit(name, opts)
|
78
|
+
end
|
79
|
+
|
80
|
+
def start(name)
|
81
|
+
opts = { 'desiredState' => 'launched', 'name' => name }
|
82
|
+
update_unit(name, opts)
|
83
|
+
end
|
84
|
+
|
85
|
+
def stop(name)
|
86
|
+
opts = { 'desiredState' => 'loaded', 'name' => name }
|
87
|
+
update_unit(name, opts)
|
88
|
+
end
|
89
|
+
|
90
|
+
def unload(name)
|
91
|
+
opts = { 'desiredState' => 'inactive', 'name' => name }
|
92
|
+
update_unit(name, opts)
|
93
|
+
end
|
94
|
+
|
95
|
+
def destroy(name)
|
96
|
+
delete_unit(name)
|
97
|
+
end
|
98
|
+
|
99
|
+
def status(name)
|
100
|
+
get_unit(name)["currentState"].to_sym
|
101
|
+
end
|
102
|
+
|
103
|
+
def get_unit_state(name)
|
104
|
+
options = { unitName: name }
|
105
|
+
states = list_states(options)
|
106
|
+
if states["states"]
|
107
|
+
states["states"].first
|
108
|
+
else
|
109
|
+
fail NotFound, "Unit '#{name}' not found"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
|
115
|
+
def resource_path(resource, *parts)
|
116
|
+
parts.unshift('fleet', fleet_api_version, resource).join('/')
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Fleet
|
2
|
+
class Client
|
3
|
+
module Machines
|
4
|
+
MACHINES_RESOURCE = 'machines'.freeze
|
5
|
+
|
6
|
+
def list_machines
|
7
|
+
opts = { consistent: true, recursive: true, sorted: true }
|
8
|
+
get(machines_path, opts)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def machines_path(*parts)
|
14
|
+
resource_path(MACHINES_RESOURCE, *parts)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Fleet
|
2
|
+
class Client
|
3
|
+
module State
|
4
|
+
STATE_RESOURCE = 'state'.freeze
|
5
|
+
|
6
|
+
def list_states
|
7
|
+
opts = { consistent: true, recursive: true, sorted: true }
|
8
|
+
get(state_path, opts)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def state_path(*parts)
|
14
|
+
resource_path(STATE_RESOURCE, *parts)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Fleet
|
2
|
+
class Client
|
3
|
+
module Unit
|
4
|
+
UNITS_RESOURCE = 'units'.freeze
|
5
|
+
|
6
|
+
def list_units
|
7
|
+
get(units_path)
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_unit(name)
|
11
|
+
get(units_path(name))
|
12
|
+
end
|
13
|
+
|
14
|
+
alias_method :get_unit_file, :get_unit
|
15
|
+
|
16
|
+
def create_unit(name, unit)
|
17
|
+
put(units_path(name), unit)
|
18
|
+
end
|
19
|
+
|
20
|
+
alias_method :update_unit, :create_unit
|
21
|
+
|
22
|
+
def delete_unit(name)
|
23
|
+
delete(units_path(name))
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def units_path(*parts)
|
29
|
+
resource_path(UNITS_RESOURCE, *parts)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Fleet
|
4
|
+
module Configuration
|
5
|
+
|
6
|
+
VALID_OPTIONS_KEYS = [
|
7
|
+
:fleet_api_url,
|
8
|
+
:fleet_api_version,
|
9
|
+
:open_timeout,
|
10
|
+
:read_timeout,
|
11
|
+
:logger
|
12
|
+
]
|
13
|
+
|
14
|
+
DEFAULT_FLEET_API_URL = ENV['FLEETCTL_ENDPOINT'] || 'unix:///var/run/fleet.sock'
|
15
|
+
DEFAULT_FLEET_API_VERSION = 'v1'
|
16
|
+
DEFAULT_OPEN_TIMEOUT = 2
|
17
|
+
DEFAULT_READ_TIMEOUT = 5
|
18
|
+
DEFAULT_LOGGER = ::Logger.new(STDOUT)
|
19
|
+
|
20
|
+
attr_accessor(*VALID_OPTIONS_KEYS)
|
21
|
+
|
22
|
+
def self.extended(base)
|
23
|
+
base.reset
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return a has of all the current config options
|
27
|
+
def options
|
28
|
+
VALID_OPTIONS_KEYS.each_with_object({}) { |k, o| o[k] = send(k) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def reset
|
32
|
+
self.fleet_api_url = DEFAULT_FLEET_API_URL
|
33
|
+
self.fleet_api_version = DEFAULT_FLEET_API_VERSION
|
34
|
+
self.open_timeout = DEFAULT_OPEN_TIMEOUT
|
35
|
+
self.read_timeout = DEFAULT_READ_TIMEOUT
|
36
|
+
self.logger = DEFAULT_LOGGER
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'excon'
|
2
|
+
|
3
|
+
module Fleet
|
4
|
+
module Connection
|
5
|
+
|
6
|
+
def connection
|
7
|
+
options = {
|
8
|
+
read_timeout: read_timeout,
|
9
|
+
connect_timeout: open_timeout,
|
10
|
+
headers: { 'User-Agent' => user_agent, 'Accept' => 'application/json' }
|
11
|
+
}
|
12
|
+
|
13
|
+
uri = URI.parse(fleet_api_url)
|
14
|
+
if uri.scheme == 'unix'
|
15
|
+
uri, options = 'unix:///', { socket: uri.path }.merge(options)
|
16
|
+
else
|
17
|
+
uri = fleet_api_url
|
18
|
+
end
|
19
|
+
|
20
|
+
Excon.new(uri, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def user_agent
|
26
|
+
ua_chunks = []
|
27
|
+
ua_chunks << "fleet/#{Fleet::VERSION}"
|
28
|
+
ua_chunks << "(#{RUBY_ENGINE}; #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}; #{RUBY_PLATFORM})"
|
29
|
+
ua_chunks << "excon/#{Excon::VERSION}"
|
30
|
+
ua_chunks.join(' ')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/fleet/error.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Fleet
|
2
|
+
|
3
|
+
class Error < StandardError
|
4
|
+
attr_reader :error_code
|
5
|
+
attr_reader :cause
|
6
|
+
|
7
|
+
def initialize(msg, error_code=nil)
|
8
|
+
super(msg)
|
9
|
+
@error_code = error_code
|
10
|
+
end
|
11
|
+
|
12
|
+
HTTP_CODE_MAP = {
|
13
|
+
400 => 'BadRequest',
|
14
|
+
401 => 'Unauthorized',
|
15
|
+
403 => 'Forbidden',
|
16
|
+
404 => 'NotFound',
|
17
|
+
405 => 'MethodNotAllowed',
|
18
|
+
406 => 'NotAcceptable',
|
19
|
+
408 => 'RequestTimeout',
|
20
|
+
409 => 'Conflict',
|
21
|
+
412 => 'PreconditionFailed',
|
22
|
+
413 => 'RequestEntityTooLarge',
|
23
|
+
414 => 'RequestUriTooLong',
|
24
|
+
415 => 'UnsupportedMediaType',
|
25
|
+
416 => 'RequestRangeNotSatisfiable',
|
26
|
+
417 => 'ExpectationFailed',
|
27
|
+
500 => 'InternalServerError',
|
28
|
+
501 => 'NotImplemented',
|
29
|
+
502 => 'BadGateway',
|
30
|
+
503 => 'ServiceUnavailable',
|
31
|
+
504 => 'GatewayTimeout'
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
# Define a new error class for all of the HTTP codes in the HTTP_CODE_MAP
|
36
|
+
Error::HTTP_CODE_MAP.each do |code, class_name|
|
37
|
+
Fleet.const_set(class_name, Class.new(Error)).const_set('HTTP_CODE', code)
|
38
|
+
end
|
39
|
+
|
40
|
+
class ConnectionError < Error; end
|
41
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'fleet/version'
|
3
|
+
|
4
|
+
module Fleet
|
5
|
+
module Request
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
[:get, :put, :delete].each do |method|
|
10
|
+
define_method(method) do |path, options={}|
|
11
|
+
request(connection, method, path, options)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def request(connection, method, path, options)
|
16
|
+
response = perform_request(connection, method, path, options)
|
17
|
+
return response if method != :get
|
18
|
+
|
19
|
+
next_page_token = response.delete('nextPageToken')
|
20
|
+
while next_page_token
|
21
|
+
next_options = options.merge('nextPageToken' => next_page_token)
|
22
|
+
next_response = perform_request(connection, method, path, next_options)
|
23
|
+
next_page_token = next_response.delete('nextPageToken')
|
24
|
+
next_response.each { |k, v| response[k] += v }
|
25
|
+
end
|
26
|
+
response
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def perform_request(connection, method, path, options)
|
32
|
+
req = {
|
33
|
+
path: escape_path(path),
|
34
|
+
}
|
35
|
+
|
36
|
+
case method
|
37
|
+
when :get
|
38
|
+
req[:query] = options
|
39
|
+
when :put
|
40
|
+
req[:headers] = { 'Content-Type' => 'application/json' }
|
41
|
+
req[:body] = ::JSON.dump(options)
|
42
|
+
end
|
43
|
+
|
44
|
+
resp = connection.send(method, req)
|
45
|
+
|
46
|
+
if (400..600).include?(resp.status)
|
47
|
+
raise_error(resp)
|
48
|
+
end
|
49
|
+
|
50
|
+
case method
|
51
|
+
when :get
|
52
|
+
::JSON.parse(resp.body)
|
53
|
+
else
|
54
|
+
true
|
55
|
+
end
|
56
|
+
rescue Excon::Errors::SocketError => ex
|
57
|
+
raise Fleet::ConnectionError, ex.message
|
58
|
+
end
|
59
|
+
|
60
|
+
def escape_path(path)
|
61
|
+
URI.escape(path).gsub(/@/, '%40')
|
62
|
+
end
|
63
|
+
|
64
|
+
def raise_error(resp)
|
65
|
+
error = JSON.parse(resp.body)['error']
|
66
|
+
class_name = Fleet::Error::HTTP_CODE_MAP.fetch(resp.status, 'Error')
|
67
|
+
|
68
|
+
fail Fleet.const_get(class_name).new(
|
69
|
+
error['message'],
|
70
|
+
error['code'])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|