navyrb 0.0.1
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.
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/.simplecov +9 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +87 -0
- data/Guardfile +17 -0
- data/LICENSE.txt +22 -0
- data/README.md +28 -0
- data/Rakefile +2 -0
- data/lib/navy.rb +15 -0
- data/lib/navy/app_container_builder.rb +72 -0
- data/lib/navy/application.rb +63 -0
- data/lib/navy/command_builder.rb +73 -0
- data/lib/navy/configuration.rb +102 -0
- data/lib/navy/container.rb +75 -0
- data/lib/navy/container_building.rb +67 -0
- data/lib/navy/etcd.rb +133 -0
- data/lib/navy/logger.rb +53 -0
- data/lib/navy/router.rb +64 -0
- data/lib/navy/runner.rb +17 -0
- data/lib/navy/task_container_builder.rb +70 -0
- data/lib/navy/version.rb +3 -0
- data/navyrb.gemspec +22 -0
- data/spec/lib/navy/app_container_builder_spec.rb +171 -0
- data/spec/lib/navy/application_spec.rb +104 -0
- data/spec/lib/navy/command_builder_spec.rb +85 -0
- data/spec/lib/navy/configuration_spec.rb +165 -0
- data/spec/lib/navy/container_spec.rb +195 -0
- data/spec/lib/navy/etcd_spec.rb +202 -0
- data/spec/lib/navy/router_spec.rb +69 -0
- data/spec/lib/navy/runner_spec.rb +36 -0
- data/spec/lib/navy/task_container_builder_spec.rb +228 -0
- data/spec/spec_helper.rb +93 -0
- data/spec/support/mock_etcd.rb +24 -0
- metadata +144 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
class Navy::Container
|
2
|
+
attr_reader :specification, :dependencies, :options
|
3
|
+
|
4
|
+
def initialize(options = {})
|
5
|
+
@specification = options[:specification] || {}
|
6
|
+
@dependencies = options[:dependencies] || []
|
7
|
+
@logger = options[:logger] || Navy::Logger.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def daemon?
|
11
|
+
specification[:type] == "application"
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
specification[:container_name]
|
16
|
+
end
|
17
|
+
|
18
|
+
def app
|
19
|
+
specification[:name]
|
20
|
+
end
|
21
|
+
|
22
|
+
def can_be_started?(etcd)
|
23
|
+
dependencies.each do |dep|
|
24
|
+
return false unless desired?(etcd, dep)
|
25
|
+
end
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def can_never_be_started?(etcd)
|
30
|
+
dependencies.each do |dep|
|
31
|
+
return true if errored?(etcd, dep)
|
32
|
+
end
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def start
|
37
|
+
if daemon?
|
38
|
+
cmd = command_builder.build
|
39
|
+
Navy::Runner.launch(cmd)
|
40
|
+
else
|
41
|
+
commands.each do |command|
|
42
|
+
cmd = command_builder.build :command => command
|
43
|
+
success = Navy::Runner.launch(cmd, :logger => @logger)
|
44
|
+
return false unless success
|
45
|
+
end
|
46
|
+
true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def stop
|
51
|
+
cmd = ["docker rm -f", name]
|
52
|
+
Navy::Runner.launch(cmd)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def commands
|
58
|
+
specification[:cmds] || []
|
59
|
+
end
|
60
|
+
|
61
|
+
def command_builder
|
62
|
+
@command_builder ||= Navy::CommandBuilder.new(self)
|
63
|
+
end
|
64
|
+
|
65
|
+
def errored?(etcd, container_name)
|
66
|
+
actual = etcd.getJSON("/navy/containers/#{container_name}/actual")
|
67
|
+
actual["state"] == "error" if actual
|
68
|
+
end
|
69
|
+
|
70
|
+
def desired?(etcd, container_name)
|
71
|
+
desired = etcd.getJSON("/navy/containers/#{container_name}/desired")
|
72
|
+
actual = etcd.getJSON("/navy/containers/#{container_name}/actual")
|
73
|
+
desired == actual
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Navy::ContainerBuilding
|
2
|
+
private
|
3
|
+
|
4
|
+
def build_container
|
5
|
+
Navy::Container.new :dependencies => @dependencies,
|
6
|
+
:specification => @specification
|
7
|
+
end
|
8
|
+
|
9
|
+
def build_app_dependencies(app, config)
|
10
|
+
links = app.dependencies(config)
|
11
|
+
flags = {:convoy => options[:convoy]}
|
12
|
+
links.each do |link|
|
13
|
+
name = config.container_name(link, flags)
|
14
|
+
dependencies << name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_app_links(app, config)
|
19
|
+
links = app.linked_apps(config)
|
20
|
+
links.each do |link|
|
21
|
+
fqdn = app_fqdn(link, config)
|
22
|
+
env = app_env_addr(link)
|
23
|
+
specification[:env][env] = "https://#{fqdn}"
|
24
|
+
specification[:links] << ['host_proxy', fqdn]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def build_dep_links(app, config)
|
30
|
+
links = app.dependencies(config)
|
31
|
+
flags = {:convoy => options[:convoy]}
|
32
|
+
links.each do |link|
|
33
|
+
name = config.container_name(link, flags)
|
34
|
+
specification[:links] << [name, link]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def app_fqdn(app, config)
|
39
|
+
parts = []
|
40
|
+
parts << @convoy_id
|
41
|
+
parts << app
|
42
|
+
parts << @cluster
|
43
|
+
parts.compact.join('-')
|
44
|
+
end
|
45
|
+
|
46
|
+
def app_env_addr(app)
|
47
|
+
"#{app.upcase}_HOST_ADDR"
|
48
|
+
end
|
49
|
+
|
50
|
+
def build_env_flags(app, config)
|
51
|
+
if app.env_var?
|
52
|
+
specification[:env][app.env_var] = config.environment
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_volumes_flags(app, config)
|
57
|
+
app.volumes_from.each do |v|
|
58
|
+
specification[:volumes_from] << v
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def build_additional_args(app, config)
|
63
|
+
specification[:docker_args] = config.docker_args(app.name)
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
end
|
data/lib/navy/etcd.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
module Navy
|
5
|
+
class Etcd
|
6
|
+
Response = Struct.new(:etcd_index, :action, :node, :prevNode) do
|
7
|
+
def key
|
8
|
+
node.key
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Node
|
13
|
+
attr_reader :createdIndex, :modifiedIndex, :value, :key
|
14
|
+
|
15
|
+
def initialize(data)
|
16
|
+
data ||= {}
|
17
|
+
parse_data data, :createdIndex, :modifiedIndex,
|
18
|
+
:value, :key
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def parse_data(data, *attrs)
|
24
|
+
attrs.each do |attr|
|
25
|
+
instance_variable_set("@#{attr}", data[attr.to_s])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.client(options)
|
32
|
+
new(options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(options)
|
36
|
+
@options = options
|
37
|
+
end
|
38
|
+
|
39
|
+
def get(key, params = {})
|
40
|
+
response = make_get(key, params)
|
41
|
+
json = JSON.parse(response.body)
|
42
|
+
Response.new response['X-Etcd-Index'].to_i,
|
43
|
+
json['action'],
|
44
|
+
Node.new(json['node']),
|
45
|
+
Node.new(json['prevNode'])
|
46
|
+
end
|
47
|
+
|
48
|
+
def getJSON(key, params = {})
|
49
|
+
record = get(key, params)
|
50
|
+
json = record.node.value
|
51
|
+
JSON.parse(json) if json
|
52
|
+
end
|
53
|
+
|
54
|
+
def watch(key, options = {})
|
55
|
+
options[:wait] = true
|
56
|
+
get(key, options)
|
57
|
+
end
|
58
|
+
|
59
|
+
def set(key, value)
|
60
|
+
make_put(key, :value => value)
|
61
|
+
end
|
62
|
+
|
63
|
+
def setJSON(key, value)
|
64
|
+
make_put(key, :value => value.to_json)
|
65
|
+
end
|
66
|
+
|
67
|
+
def queueJSON(key, value)
|
68
|
+
make_post(key, :value => value.to_json)
|
69
|
+
end
|
70
|
+
|
71
|
+
def ls(dir)
|
72
|
+
response = make_get(dir)
|
73
|
+
dir = JSON.parse(response.body)["node"]
|
74
|
+
dir["nodes"].map {|i| i["key"]}
|
75
|
+
end
|
76
|
+
|
77
|
+
def delete(key, params = {})
|
78
|
+
response = make_delete(key, params)
|
79
|
+
(200...299).include? response.code.to_i
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def make_uri(path)
|
85
|
+
host = @options[:host]
|
86
|
+
port = @options[:port] || 4001
|
87
|
+
uri = URI("http://#{host}:#{port}")
|
88
|
+
uri.path = "/v2/keys" + path
|
89
|
+
uri
|
90
|
+
end
|
91
|
+
|
92
|
+
def make_http(uri)
|
93
|
+
Net::HTTP.new(uri.host, uri.port)
|
94
|
+
end
|
95
|
+
|
96
|
+
def make_query(params)
|
97
|
+
params.map do |k, v|
|
98
|
+
"#{k}=#{v}"
|
99
|
+
end.join '&'
|
100
|
+
end
|
101
|
+
|
102
|
+
def make_request(path, type, params = {})
|
103
|
+
uri = make_uri(path)
|
104
|
+
http = make_http(uri)
|
105
|
+
if params.length > 0
|
106
|
+
uri.query = make_query(params)
|
107
|
+
end
|
108
|
+
request = type.new(uri)
|
109
|
+
yield request if block_given?
|
110
|
+
http.request(request)
|
111
|
+
end
|
112
|
+
|
113
|
+
def make_delete(path, params = {})
|
114
|
+
make_request(path, Net::HTTP::Delete, params)
|
115
|
+
end
|
116
|
+
|
117
|
+
def make_put(path, postdata = {})
|
118
|
+
http, request = make_request(path, Net::HTTP::Put) do |request|
|
119
|
+
request.set_form_data(postdata)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def make_post(path, postdata = {})
|
124
|
+
http, request = make_request(path, Net::HTTP::Post) do |request|
|
125
|
+
request.set_form_data(postdata)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def make_get(path, params = {})
|
130
|
+
make_request(path, Net::HTTP::Get, params)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/lib/navy/logger.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
class Navy::Logger
|
2
|
+
COLOURS = {
|
3
|
+
:black => 30,
|
4
|
+
:red => 31,
|
5
|
+
:green => 32,
|
6
|
+
:yellow => 33,
|
7
|
+
:blue => 34,
|
8
|
+
:magenta => 35,
|
9
|
+
:cyan => 36,
|
10
|
+
:bright_black => 30,
|
11
|
+
:bright_red => 31,
|
12
|
+
:bright_green => 32,
|
13
|
+
:bright_yellow => 33,
|
14
|
+
:bright_blue => 34,
|
15
|
+
:bright_magenta => 35,
|
16
|
+
:bright_cyan => 36
|
17
|
+
}
|
18
|
+
|
19
|
+
attr_reader :prefix
|
20
|
+
|
21
|
+
def color(color, message)
|
22
|
+
"\033[0;#{COLOURS[color]}m#{message}\033[0;00m"
|
23
|
+
end
|
24
|
+
|
25
|
+
def notice(message)
|
26
|
+
log color(:cyan,message)
|
27
|
+
end
|
28
|
+
|
29
|
+
def info(message)
|
30
|
+
log color(:yellow, message)
|
31
|
+
end
|
32
|
+
|
33
|
+
def error(message)
|
34
|
+
log color(:red, message)
|
35
|
+
end
|
36
|
+
|
37
|
+
def debug(message)
|
38
|
+
log message
|
39
|
+
end
|
40
|
+
|
41
|
+
def threaded!
|
42
|
+
thread = Thread.current.object_id
|
43
|
+
colorid = (thread/7) % COLOURS.count
|
44
|
+
colorkey = COLOURS.keys[colorid]
|
45
|
+
@prefix = color(colorkey, "(#{thread}):")
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def log(message)
|
51
|
+
puts [prefix, message].join(' ')
|
52
|
+
end
|
53
|
+
end
|
data/lib/navy/router.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
class Navy::Router
|
2
|
+
class NullHandler; end
|
3
|
+
|
4
|
+
class Routes
|
5
|
+
def route(pattern, handlers)
|
6
|
+
pattern = pattern.gsub(/:([a-z]+)/, '(?<\1>[a-z0-9\-_]+)')
|
7
|
+
regex = Regexp.new(pattern)
|
8
|
+
maps[regex] = handlers
|
9
|
+
end
|
10
|
+
|
11
|
+
def maps
|
12
|
+
@maps ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def obtain(path)
|
16
|
+
@maps.each do |pattern, handler|
|
17
|
+
matches = pattern.match(path)
|
18
|
+
if matches
|
19
|
+
params = extract_params(matches)
|
20
|
+
return handler.new, params
|
21
|
+
end
|
22
|
+
end
|
23
|
+
return NullHandler.new, {}
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def extract_params(matches)
|
29
|
+
params = {}
|
30
|
+
matches.names.each do |name|
|
31
|
+
params[name] = matches[name]
|
32
|
+
end
|
33
|
+
params
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(options = {})
|
39
|
+
@routes = Routes.new
|
40
|
+
@options = options
|
41
|
+
yield @routes if block_given?
|
42
|
+
end
|
43
|
+
|
44
|
+
def route(request, options = {})
|
45
|
+
opts = @options.merge(options)
|
46
|
+
handler, params = @routes.obtain(request.key)
|
47
|
+
params = opts.merge params
|
48
|
+
dispatch(handler, params, request)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def dispatch(handler, params, request)
|
54
|
+
action = request.action
|
55
|
+
message = handler_method(action)
|
56
|
+
if handler.respond_to? message
|
57
|
+
handler.public_send(message, params, request)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def handler_method(action)
|
62
|
+
"handle_#{action}"
|
63
|
+
end
|
64
|
+
end
|
data/lib/navy/runner.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
|
4
|
+
class Navy::Runner
|
5
|
+
def self.launch(cmd, options={})
|
6
|
+
logger = options[:logger] || Navy::Logger.new
|
7
|
+
|
8
|
+
command = cmd.join(' ')
|
9
|
+
logger.notice("Launching #{command}")
|
10
|
+
stdout, stderr, status = Open3.capture3(cmd.join(' '))
|
11
|
+
unless status.success?
|
12
|
+
logger.color(:red, "Error")
|
13
|
+
logger.color(:red, stderr)
|
14
|
+
end
|
15
|
+
status.success?
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class Navy::TaskContainerBuilder
|
2
|
+
include Navy::ContainerBuilding
|
3
|
+
|
4
|
+
attr_reader :app, :config, :options, :dependencies, :specification, :convoy_id, :cluster
|
5
|
+
|
6
|
+
def initialize(app, config, options)
|
7
|
+
@app = app
|
8
|
+
@config = config
|
9
|
+
@options = options
|
10
|
+
@convoy_id = options[:convoy]
|
11
|
+
@cluster = options[:cluster]
|
12
|
+
@specification = {
|
13
|
+
:type => "task",
|
14
|
+
:env => {},
|
15
|
+
:links => [],
|
16
|
+
:volumes_from => []
|
17
|
+
}
|
18
|
+
@dependencies = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def build_pre
|
22
|
+
tasks = config.pre_tasks(app.name)
|
23
|
+
unless tasks.empty?
|
24
|
+
build_task_container_settings(:pre, app, config, tasks)
|
25
|
+
build_app_dependencies(app, config)
|
26
|
+
build_dep_links(app, config)
|
27
|
+
build_env_flags(app, config)
|
28
|
+
build_volumes_flags(app, config)
|
29
|
+
build_additional_args(app, config)
|
30
|
+
build_container
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def build_post
|
35
|
+
tasks = config.post_tasks(app.name)
|
36
|
+
unless tasks.empty?
|
37
|
+
build_task_container_settings(:post, app, config, tasks)
|
38
|
+
build_mode_dependencies(app, config)
|
39
|
+
build_app_links(app, config)
|
40
|
+
build_dep_links(app, config)
|
41
|
+
build_env_flags(app, config)
|
42
|
+
build_volumes_flags(app, config)
|
43
|
+
build_additional_args(app, config)
|
44
|
+
build_container
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def build_task_container_settings(type, app, config, cmds)
|
51
|
+
flags = {:mode => "#{type}tasks", :convoy => @convoy_id}
|
52
|
+
specification[:name] = app.name
|
53
|
+
specification[:container_name] = config.container_name(app.name, flags)
|
54
|
+
specification[:image] = app.image
|
55
|
+
specification[:cmds] = cmds
|
56
|
+
end
|
57
|
+
|
58
|
+
def build_mode_dependencies(app, config)
|
59
|
+
if app.modes
|
60
|
+
app.modes.each do |mode, cmd|
|
61
|
+
flags = {:mode => mode, :scale => 1, :convoy => @convoy_id}
|
62
|
+
dependencies << config.container_name(app.name, flags)
|
63
|
+
end
|
64
|
+
else
|
65
|
+
flags = {:scale => 1, :convoy => @convoy_id}
|
66
|
+
dependencies << config.container_name(app.name, flags)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|