marathon-srv 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []