marathon-srv 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 +9 -0
- data/.project +11 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/README.md +55 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/marathon-srv +6 -0
- data/lib/marathon/srv/cli.rb +80 -0
- data/lib/marathon/srv/client.rb +119 -0
- data/lib/marathon/srv/version.rb +5 -0
- data/lib/marathon/srv.rb +10 -0
- data/marathon-srv.gemspec +28 -0
- data/response.json +9 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6293ca2434d9b7b39ac223b7e24bc00a42b50c10
|
4
|
+
data.tar.gz: 8e8a23f59a5c680836970b5e986ad117743ee9bb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c6776a6a33981f6298fc5b333f9bcba523fba2f886452cbf6ba6350ef6c0b36c334ba17cac0dd80ab1804fd00015eea22416b1c45031f2309925c72069765fe6
|
7
|
+
data.tar.gz: 22ab808eec706682649244e5502145c7c672a46da3eea4cadf6a013be7448db55cbc51cb97f2eabe138b65cc2b4847c7a77456d3dc9eda8fd0e9167282225e8c
|
data/.gitignore
ADDED
data/.project
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# MarathonSRV
|
2
|
+
|
3
|
+
This is a small CLI gem that retrieves Mesos slave IP addresses and port numbers for an application id and container port combination.
|
4
|
+
The idea is that this side-steps the _current_ limitations in surfacing SRV records for these BRIDGEd ports in mesos-dns.
|
5
|
+
|
6
|
+
tl;dr poor man's port/service discovery for BRIDGEd Docker Containerized Marathon applications
|
7
|
+
|
8
|
+
## Usage via CLI
|
9
|
+
|
10
|
+
$ marathon-srv find \
|
11
|
+
--marathon http://marathon.domain.tld:8080 \
|
12
|
+
--app-id best-redis-cluster \
|
13
|
+
--container-port 6379 \
|
14
|
+
--username username \
|
15
|
+
--password password
|
16
|
+
mesos-s1,tcp,31050
|
17
|
+
mesos-s2,tcp,31507
|
18
|
+
mesos-s11,tcp,31496
|
19
|
+
|
20
|
+
$ marathon-srv help find
|
21
|
+
Usage:
|
22
|
+
marathon-srv find --container-port=CONTAINER_PORT --marathon=MARATHON
|
23
|
+
|
24
|
+
Options:
|
25
|
+
--marathon=MARATHON # Marathon API URL
|
26
|
+
[--app-id=APP_ID] # Marathon application id
|
27
|
+
[--protocol=PROTOCOL]
|
28
|
+
# Default: tcp
|
29
|
+
--container-port=CONTAINER_PORT # Docker container-side port to translate
|
30
|
+
[--username=USERNAME]
|
31
|
+
[--password=PASSWORD]
|
32
|
+
[--healthy=HEALTHY] # Consider healthy application instances/tasks only
|
33
|
+
# Default: true
|
34
|
+
[--verbose=VERBOSE] # Output Logger::DEBUG information to STDOUT
|
35
|
+
[--json=JSON] # JSON encode response ( instead of CSV )
|
36
|
+
|
37
|
+
Returns a list of ip addresses and port numbers for an application name and service port number
|
38
|
+
|
39
|
+
## Usage via Marathon::Srv::Client object
|
40
|
+
|
41
|
+
client = Marathon::Srv::Client.new "http://marathon.domain.tld:8080", "username", "password", {:log_level => Logger::DEBUG}
|
42
|
+
client.get_bridged_port_array "best-redis-cluster", true # 2nd parameter : consider healthy tasks only
|
43
|
+
|
44
|
+
( n.b.: The Client object returns all BRIDGEd ports, the CLI filters these down to only --container-port )
|
45
|
+
|
46
|
+
## Notes
|
47
|
+
|
48
|
+
### Tests
|
49
|
+
|
50
|
+
Are there for the Client, not there for the CLI, and a bit spotty in general
|
51
|
+
|
52
|
+
### Health checks
|
53
|
+
|
54
|
+
When considering only healthy tasks, all health checks must pass. For TCP health checks, port index is not considered - if _any_ of the health checks fail, the
|
55
|
+
task is dropped and will not contribute host/port entries.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "marathon/srv"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/exe/marathon-srv
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require "marathon/srv"
|
2
|
+
require "marathon/srv/client"
|
3
|
+
require "rubygems"
|
4
|
+
require "thor"
|
5
|
+
require "logger"
|
6
|
+
require "json"
|
7
|
+
|
8
|
+
module Marathon
|
9
|
+
|
10
|
+
module Srv
|
11
|
+
|
12
|
+
class CLI < Thor
|
13
|
+
class_option :verbose, :default => false, :desc => "Output Logger::DEBUG information to STDOUT"
|
14
|
+
class_option :json, :default => false, :desc => "JSON encode response ( instead of CSV )"
|
15
|
+
|
16
|
+
desc "find", "Returns a list of ip addresses and port numbers for an application name and service port number"
|
17
|
+
option :marathon, :required => true, :desc => "Marathon API URL"
|
18
|
+
option :app_id, :reqired => true, :desc => "Marathon application id"
|
19
|
+
option :protocol, :default => "tcp"
|
20
|
+
option :container_port, :required => true, :desc => "Docker container-side port to translate"
|
21
|
+
option :username, :required => false
|
22
|
+
option :password, :required => false
|
23
|
+
option :healthy, :default => true, :desc => "Consider healthy application instances/tasks only"
|
24
|
+
def find
|
25
|
+
|
26
|
+
client = Marathon::Srv::Client.new options[:marathon], options[:username], options[:password], {:log_level => (options[:verbose] ? Logger::DEBUG : Logger::ERROR)}
|
27
|
+
|
28
|
+
begin
|
29
|
+
hosts = client.get_bridged_port_array options[:app_id], (options[:healthy] ? true : falses)
|
30
|
+
|
31
|
+
|
32
|
+
hosts.reject! do |host|
|
33
|
+
|
34
|
+
host[:services].reject! do |protocol, services|
|
35
|
+
|
36
|
+
services.reject! {|port, host_port| port.to_s != options[:container_port]}
|
37
|
+
services.size == 0
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
host[:services].size == 0
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
if options[:json]
|
46
|
+
puts JSON hosts
|
47
|
+
else
|
48
|
+
|
49
|
+
lines=[]
|
50
|
+
hosts.each do |host|
|
51
|
+
|
52
|
+
line=[]
|
53
|
+
line.push host[:host]
|
54
|
+
host[:services].each do |protocol, services|
|
55
|
+
line.push protocol
|
56
|
+
services.each do |port, host_port|
|
57
|
+
line.push host_port
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
lines.push line.join ","
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
puts lines.join "\n"
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
rescue Exception => e
|
71
|
+
fail e
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require "uri"
|
2
|
+
require "net/http"
|
3
|
+
require "logger"
|
4
|
+
require "rubygems"
|
5
|
+
require "json"
|
6
|
+
|
7
|
+
module Marathon
|
8
|
+
|
9
|
+
module Srv
|
10
|
+
|
11
|
+
class InvalidResponseCodeError < Exception; end
|
12
|
+
class NoRunningTasksFoundError < Exception; end
|
13
|
+
class NotDockerContainerizedApplicationError < Exception; end
|
14
|
+
class NoHealthChecksDefinedError < Exception; end
|
15
|
+
class InvalidJSONResponseError < Exception; end
|
16
|
+
|
17
|
+
class Client
|
18
|
+
|
19
|
+
API_VERSION = "v2"
|
20
|
+
|
21
|
+
PATH_FIND_APP = "/#{API_VERSION}/apps/%s"
|
22
|
+
|
23
|
+
attr_reader :marathon_api
|
24
|
+
|
25
|
+
def initialize(marathon_api_url, username, password, options = {})
|
26
|
+
|
27
|
+
@marathon_api = marathon_api_url
|
28
|
+
@username = username
|
29
|
+
@password = password
|
30
|
+
|
31
|
+
@logger = Logger.new(STDOUT)
|
32
|
+
@logger.level = options[:log_level] || Logger::ERROR
|
33
|
+
|
34
|
+
@logger.debug "Initialized with options #{options}"
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_bridged_port_array(application_id, healthy_tasks_only=false)
|
39
|
+
|
40
|
+
@logger.debug "Attempting to fetch application #{application_id} object from API, healthy_tasks_only: #{healthy_tasks_only} "
|
41
|
+
|
42
|
+
api = URI::join(marathon_api, PATH_FIND_APP % application_id)
|
43
|
+
Net::HTTP.new(api.host, api.port).start do |http|
|
44
|
+
|
45
|
+
req = Net::HTTP::Get.new api
|
46
|
+
(req.basic_auth @username, @password) if (@username != nil && @password != nil)
|
47
|
+
|
48
|
+
response = http.request req
|
49
|
+
@logger.debug "Received response: #{response}"
|
50
|
+
|
51
|
+
raise Marathon::Srv::InvalidResponseCodeError.new response.code unless response.code == "200"
|
52
|
+
|
53
|
+
# parse JSON
|
54
|
+
begin
|
55
|
+
@logger.debug "Parsing body #{response.body}"
|
56
|
+
app = (JSON response.body)["app"]
|
57
|
+
|
58
|
+
@logger.debug "Retrieved app object #{app}"
|
59
|
+
|
60
|
+
raise Marathon::Srv::NotDockerContainerizedApplicationError.new unless app["container"]["type"] == "DOCKER"
|
61
|
+
raise Marathon::Srv::NoRunningTasksFoundError.new unless app["tasks"].size > 0
|
62
|
+
raise Marathon::Srv::NoHealthChecksDefinedError.new unless app["healthChecks"] != nil && app["healthChecks"].size > 0
|
63
|
+
|
64
|
+
# collect slave ports of (healthy) tasks
|
65
|
+
ports=[]
|
66
|
+
app["tasks"].each do |task|
|
67
|
+
|
68
|
+
if(healthy_tasks_only)
|
69
|
+
@logger.debug "Verifying health checks for task #{task}"
|
70
|
+
# all health checks must be passing
|
71
|
+
passing=true
|
72
|
+
task["healthCheckResults"].each do |health_check_result|
|
73
|
+
(passing=false; @logger.debug "%s has failing health check, not considering it" % task; break) unless health_check_result["alive"] == true
|
74
|
+
|
75
|
+
end
|
76
|
+
@logger.debug "All health checks passing - filtering ports for task #{task}"
|
77
|
+
ports.push filter_ports(app, task) if passing
|
78
|
+
|
79
|
+
else
|
80
|
+
# just add task
|
81
|
+
@logger.debug "Ignoring health checks - filtering ports for task #{task}"
|
82
|
+
ports.push filter_ports(app, task)
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
@logger.debug "Collected ports #{ports}"
|
89
|
+
ports
|
90
|
+
|
91
|
+
rescue JSON::ParserError => e
|
92
|
+
raise Marathon::Srv::InvalidJSONResponseError.new e
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
def filter_ports(app, task)
|
103
|
+
port = {:host => task["host"], :services => {}}
|
104
|
+
app["container"]["docker"]["portMappings"].each_with_index do |port_mapping, port_i|
|
105
|
+
port[:services][port_mapping["protocol"]] = {} unless port[:services].has_key? port_mapping["protocol"]
|
106
|
+
port[:services][port_mapping["protocol"]][port_mapping["containerPort"]] = task["ports"][port_i]
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
port
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
data/lib/marathon/srv.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'marathon/srv/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "marathon-srv"
|
8
|
+
spec.version = Marathon::Srv::VERSION
|
9
|
+
spec.authors = ["Andras Szerdahelyi"]
|
10
|
+
spec.email = ["andras.szerdahelyi@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{CLI to 'resolve' Marathon managed docker containers to slave ip and container ports to host ports }
|
13
|
+
spec.description = %q{Uses the Marathon API to match one or more ports set up under BRIDGE Docker networking for a Marathon application (grouped or not) to slave ip/s and slave (host) port/s}
|
14
|
+
spec.homepage = "https://github.com/andlaz/marathon-srv"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "thor", "~> 0.19.1"
|
22
|
+
spec.add_dependency "json", "~> 1.8.3"
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
26
|
+
spec.add_development_dependency "rspec-mocks", "~> 3.4.1"
|
27
|
+
spec.add_development_dependency "webmock", "~> 1.22.6"
|
28
|
+
end
|
data/response.json
ADDED
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: marathon-srv
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andras Szerdahelyi
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.19.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.19.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.8.3
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.8.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.11'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.11'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec-mocks
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 3.4.1
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 3.4.1
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: webmock
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.22.6
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.22.6
|
111
|
+
description: Uses the Marathon API to match one or more ports set up under BRIDGE
|
112
|
+
Docker networking for a Marathon application (grouped or not) to slave ip/s and
|
113
|
+
slave (host) port/s
|
114
|
+
email:
|
115
|
+
- andras.szerdahelyi@gmail.com
|
116
|
+
executables:
|
117
|
+
- marathon-srv
|
118
|
+
extensions: []
|
119
|
+
extra_rdoc_files: []
|
120
|
+
files:
|
121
|
+
- ".gitignore"
|
122
|
+
- ".project"
|
123
|
+
- ".rspec"
|
124
|
+
- ".travis.yml"
|
125
|
+
- Gemfile
|
126
|
+
- README.md
|
127
|
+
- Rakefile
|
128
|
+
- bin/console
|
129
|
+
- bin/setup
|
130
|
+
- exe/marathon-srv
|
131
|
+
- lib/marathon/srv.rb
|
132
|
+
- lib/marathon/srv/cli.rb
|
133
|
+
- lib/marathon/srv/client.rb
|
134
|
+
- lib/marathon/srv/version.rb
|
135
|
+
- marathon-srv.gemspec
|
136
|
+
- response.json
|
137
|
+
homepage: https://github.com/andlaz/marathon-srv
|
138
|
+
licenses: []
|
139
|
+
metadata: {}
|
140
|
+
post_install_message:
|
141
|
+
rdoc_options: []
|
142
|
+
require_paths:
|
143
|
+
- lib
|
144
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
requirements: []
|
155
|
+
rubyforge_project:
|
156
|
+
rubygems_version: 2.4.8
|
157
|
+
signing_key:
|
158
|
+
specification_version: 4
|
159
|
+
summary: CLI to 'resolve' Marathon managed docker containers to slave ip and container
|
160
|
+
ports to host ports
|
161
|
+
test_files: []
|