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 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
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.project ADDED
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <projectDescription>
3
+ <name>marathon-srv</name>
4
+ <comment></comment>
5
+ <projects>
6
+ </projects>
7
+ <buildSpec>
8
+ </buildSpec>
9
+ <natures>
10
+ </natures>
11
+ </projectDescription>
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.4
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in marathon-srv.gemspec
4
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/marathon-srv ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'marathon/srv'
5
+
6
+ Marathon::Srv::CLI.start( ARGV )
@@ -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
@@ -0,0 +1,5 @@
1
+ module Marathon
2
+ module Srv
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ require "marathon/srv/version"
2
+ require "marathon/srv/client"
3
+ require "marathon/srv/cli"
4
+
5
+ module Marathon
6
+ module Srv
7
+
8
+
9
+ end
10
+ end
@@ -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
@@ -0,0 +1,9 @@
1
+ {
2
+ "app":
3
+ {
4
+ "container":
5
+ {
6
+ "type": "NOTDOCKER!"
7
+ }
8
+ }
9
+ }
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: []