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.
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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