haile 0.3.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: 621dd9bed5314f84e53e84a1a034d2ee7a3f5883
4
+ data.tar.gz: d215682f47b347ed7c05badf06656d98c7846a4d
5
+ SHA512:
6
+ metadata.gz: 91f6d330486939e5da6272e33d840de4415c7a43d69de16bf20687b1dd2f4b17735a81c7588b193a9896e119cc7df10f88035b619c491c00bd84b44cd6f0787d
7
+ data.tar.gz: 774672fdaa5ef6bbf9b9957dc53ab34fdd45e2e9cf278908de90c88b3978d741c700fdf0c869ae560797564a01df2179b7b5ac4a01ae7c6583750f973db040bd
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.swp
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in haile.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2013 Tobi Knaup
2
+ Copyright (c) 2014 Chris Kite
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # Haile
2
+
3
+ Client gem for the Marathon scheduler
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'haile'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install haile
18
+
19
+ ## Usage
20
+
21
+
22
+ ## Development
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/haile ADDED
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'marathon'))
4
+ require 'trollop'
5
+
6
+ SUB_COMMANDS = %w[endpoints kill kill_tasks start scale list list_tasks search]
7
+
8
+ global_opts = Trollop.options do
9
+ version Marathon::VERSION
10
+ banner <<-EOS
11
+ Usage: marathon [global options] [command] [options]
12
+
13
+ Available commands:
14
+
15
+ kill Kill an app and remove it from Marathon.
16
+ kill_tasks Kill a task or tasks belonging to a specified app.
17
+ list Show a list of running apps and their options.
18
+ list_tasks Show a list of an app's running tasks.
19
+ scale Scale the number of app instances.
20
+ search Search the current list of apps.
21
+ start Start a new app.
22
+
23
+ Global options:
24
+ EOS
25
+
26
+ opt :marathon_host, 'Marathon host (default http://localhost:8080, or MARATHON_HOST)', :short => '-H', :type => String
27
+ opt :marathon_user, 'User name to authenticate against Marathon (optional).', :short => '-U', :type => String
28
+ opt :marathon_pass, 'Password to authenticate against Marathon (optional).', :short => '-P', :type => String
29
+ stop_on SUB_COMMANDS
30
+ end
31
+
32
+ cmd = ARGV.shift # get the subcommand
33
+
34
+ cmd_opts = case cmd
35
+ when 'endpoints'
36
+ Trollop.options do
37
+ opt :id, 'A unique identifier for the app.', :short => '-i', :type => String
38
+ end
39
+ when 'kill_tasks'
40
+ Trollop.options do
41
+ opt :host, 'Scope task killing to the given host.', :short => '-H', :type => String
42
+ opt :id, 'A unique identifier for the app.', :short => '-i', :required => true, :type => String
43
+ opt :scale, 'If true, the app is scaled down after killing tasks', :short => '-s'
44
+ opt :task_id, 'A unique identifier for the task.', :short => '-t', :type => String
45
+ end
46
+ when 'list_tasks'
47
+ Trollop.options do
48
+ opt :id, 'A unique identifier for the app.', :short => '-i', :required => true, :type => String
49
+ end
50
+ when 'start'
51
+ Trollop.options do
52
+ opt :id, 'A unique identifier for the app.', :required => true, :short => '-i', :type => String
53
+ opt :command, 'The command to start the app.', :short => '-C', :type => String
54
+ opt :executor, 'The mesos executor to be used to launch the app.', :short => '-X', :type => String
55
+ opt :num_instances, 'The number of instances to run (default 1).', :default => 1, :short => '-n'
56
+ opt :cpus, 'The number of CPUs to give to this app, can be a fraction (default 1.0).', :default => 1.0, :short => '-c'
57
+ opt :mem, 'The memory limit for this app, in MB, can be a fraction (default 10.0).', :default => 10.0, :short => '-m'
58
+ opt :uri, 'URIs to download and unpack into the working directory.', :short => '-u', :type => :strings
59
+ opt :env, 'Environment variables to add to the process, as NAME=VALUE.', :short => '-e', :type => :strings
60
+ opt :constraint, 'Placement constraint for tasks, e.g. hostname:UNIQUE or rackid:CLUSTER', :type => :strings
61
+ end
62
+ when 'scale'
63
+ Trollop.options do
64
+ opt :id, 'A unique identifier for the app.', :required => true, :short => '-i', :type => String
65
+ opt :num_instances, 'The number of instances to run.', :required => true, :short => '-n', :type => Integer
66
+ end
67
+ when 'kill'
68
+ Trollop.options do
69
+ opt :id, 'A unique identifier for the app.', :required => true, :short => '-i', :type => String
70
+ end
71
+ when 'search'
72
+ Trollop.options do
73
+ opt :id, 'A unique identifier for the app.', :short => '-i', :type => String, :default => nil
74
+ opt :command, 'The command for the app.', :short => '-C', :type => String, :default => nil
75
+ end
76
+ else
77
+ {}
78
+ end
79
+
80
+ marathon = Marathon::Client.new(
81
+ global_opts[:marathon_host],
82
+ global_opts[:marathon_user],
83
+ global_opts[:marathon_pass]
84
+ )
85
+
86
+ def handle_listing(res)
87
+ if res.success?
88
+ apps = res.parsed_response['apps']
89
+
90
+ if apps.empty?
91
+ puts "No apps currently running"
92
+ else
93
+ apps.each do |app|
94
+ puts "App ID: #{app['id']}"
95
+ puts "Command: #{app['cmd']}"
96
+ puts "Instances: #{app['instances']}"
97
+ puts "CPUs: #{app['cpus']}"
98
+ puts "Memory: #{app['mem']} MB"
99
+ app['uris'].each do |uri|
100
+ puts "URI: #{uri}"
101
+ end
102
+ app['env'].each do |k, v|
103
+ puts "ENV: #{k}=#{v}"
104
+ end
105
+ app['constraints'].each do |constraint|
106
+ puts "Constraint: #{constraint.join(':')}"
107
+ end
108
+ puts
109
+ end
110
+ end
111
+
112
+ else
113
+ puts res
114
+ end
115
+ end
116
+
117
+ # Run
118
+ case cmd
119
+ when 'endpoints'
120
+ puts marathon.endpoints(cmd_opts[:id]).parsed_response
121
+ when 'start'
122
+ app_opts = {
123
+ :instances => cmd_opts[:num_instances] || 1,
124
+ :uris => cmd_opts[:uri] || [],
125
+ :cmd => cmd_opts[:command],
126
+ :env => cmd_opts[:env].nil? ? {} : Hash[cmd_opts[:env].map { |e| e.split('=', 2) }],
127
+ :cpus => cmd_opts[:cpus] || 1.0,
128
+ :mem => cmd_opts[:mem] || 10.0,
129
+ :constraints => (cmd_opts[:constraint] || []).map { |c| c.split(':') }
130
+ }
131
+ app_opts[:executor] = cmd_opts[:executor] unless cmd_opts[:executor] == nil
132
+ puts "Starting app '#{cmd_opts[:id]}'"
133
+ puts marathon.start(cmd_opts[:id], app_opts)
134
+ when 'scale'
135
+ puts "Scaling app '#{cmd_opts[:id]}' to #{cmd_opts[:num_instances]} instances"
136
+ res = marathon.scale(cmd_opts[:id], cmd_opts[:num_instances])
137
+ puts res
138
+ when 'kill'
139
+ puts "Killing app '#{cmd_opts[:id]}'"
140
+ puts marathon.kill(cmd_opts[:id])
141
+ when 'kill_tasks'
142
+ KILL_TASKS_KEYS = [:host, :scale, :task_id]
143
+
144
+ opts = cmd_opts.clone
145
+ opts.select! {|k, v| KILL_TASKS_KEYS.include?(k)}
146
+
147
+ puts marathon.kill_tasks(cmd_opts[:id], opts).parsed_response
148
+ when 'list'
149
+ handle_listing(marathon.list)
150
+ when 'list_tasks'
151
+ puts marathon.list_tasks(cmd_opts[:id]).parsed_response
152
+ when 'search'
153
+ handle_listing(marathon.search(cmd_opts[:id], cmd_opts[:command]))
154
+ else
155
+ Trollop.die "unknown subcommand #{cmd.inspect}"
156
+ end
data/haile.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'haile/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "haile"
8
+ spec.version = Haile::VERSION
9
+ spec.authors = ["Tobi Knaup", "Chris Kite"]
10
+ spec.description = %q{Ruby client for Marathon REST API}
11
+ spec.summary = %q{Ruby client for Marathon REST API}
12
+ spec.homepage = "https://github.com/chriskite/haile"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "trollop", "~> 2.0"
21
+ spec.add_dependency "httparty", "~> 0.11"
22
+ spec.add_dependency "multi_json", "~> 1.8"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake", "~> 0"
26
+ end
@@ -0,0 +1,110 @@
1
+ require 'uri'
2
+
3
+ module Haile
4
+ class Client
5
+ include HTTParty
6
+
7
+ headers(
8
+ 'Content-Type' => 'application/json',
9
+ 'Accept' => 'application/json'
10
+ )
11
+
12
+ query_string_normalizer proc { |query| MultiJson.dump(query) }
13
+ maintain_method_across_redirects
14
+ default_timeout 5
15
+
16
+ EDITABLE_APP_ATTRIBUTES = [
17
+ :cmd, :constraints, :container, :cpus, :env, :executor, :id, :instances,
18
+ :mem, :ports, :uris]
19
+
20
+ def initialize(url = nil, user = nil, pass = nil, proxy = nil)
21
+ @host = url || ENV['MARATHON_URL'] || 'http://localhost:8080'
22
+ @default_options = {}
23
+
24
+ if user && pass
25
+ @default_options[:basic_auth] = {:username => user, :password => pass}
26
+ end
27
+
28
+ if proxy
29
+ @default_options[:http_proxyaddr] = proxy[:addr]
30
+ @default_options[:http_proxyport] = proxy[:port]
31
+ @default_options[:http_proxyuser] = proxy[:user] if proxy[:user]
32
+ @default_options[:http_proxypass] = proxy[:pass] if proxy[:pass]
33
+ end
34
+ end
35
+
36
+ def list
37
+ wrap_request(:get, '/v2/apps')
38
+ end
39
+
40
+ def list_tasks(id)
41
+ wrap_request(:get, URI.escape("/v2/apps/#{id}/tasks"))
42
+ end
43
+
44
+ def search(id = nil, cmd = nil)
45
+ params = {}
46
+ params[:id] = id unless id.nil?
47
+ params[:cmd] = cmd unless cmd.nil?
48
+
49
+ wrap_request(:get, "/v2/apps?#{query_params(params)}")
50
+ end
51
+
52
+ def endpoints(id = nil)
53
+ if id.nil?
54
+ url = "/v2/tasks"
55
+ else
56
+ url = "/v2/apps/#{id}/tasks"
57
+ end
58
+
59
+ wrap_request(:get, url)
60
+ end
61
+
62
+ def start(id, opts)
63
+ body = opts.dup
64
+ body[:id] = id
65
+ wrap_request(:post, '/v2/apps/', :body => body)
66
+ end
67
+
68
+ def scale(id, num_instances)
69
+ # Fetch current state and update only the 'instances' attribute. Since the
70
+ # API only supports PUT, the full representation of the app must be
71
+ # supplied to update even just a single attribute.
72
+ app = wrap_request(:get, "/v2/apps/#{id}").parsed_response['app']
73
+ app.select! {|k, v| EDITABLE_APP_ATTRIBUTES.include?(k)}
74
+
75
+ app[:instances] = num_instances
76
+ wrap_request(:put, "/v2/apps/#{id}", :body => app)
77
+ end
78
+
79
+ def kill(id)
80
+ wrap_request(:delete, "/v2/apps/#{id}")
81
+ end
82
+
83
+ def kill_tasks(appId, params = {})
84
+ if params[:task_id].nil?
85
+ wrap_request(:delete, "/v2/apps/#{appId}/tasks?#{query_params(params)}")
86
+ else
87
+ query = params.clone
88
+ task_id = query[:task_id]
89
+ query.delete(:task_id)
90
+
91
+ wrap_request(:delete, "/v2/apps/#{appId}/tasks/#{task_id}?#{query_params(query)}")
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def wrap_request(method, url, options = {})
98
+ options = @default_options.merge(options)
99
+ http = self.class.send(method, @host + url, options)
100
+ Marathon::Response.new(http)
101
+ rescue => e
102
+ Marathon::Response.error(e.message)
103
+ end
104
+
105
+ def query_params(hash)
106
+ hash = hash.select { |k,v| !v.nil? }
107
+ URI.escape(hash.map { |k,v| "#{k}=#{v}" }.join('&'))
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,46 @@
1
+ module Haile
2
+ class Response
3
+
4
+ # TODO make this attr_reader and set the error some other way
5
+ attr_accessor :error
6
+
7
+ def initialize(http)
8
+ @http = http
9
+ @error = error_message_from_response
10
+ end
11
+
12
+ def success?
13
+ @http && @http.success?
14
+ end
15
+
16
+ def error?
17
+ !success?
18
+ end
19
+
20
+ def parsed_response
21
+ @http && @http.parsed_response
22
+ end
23
+
24
+ def self.error(message)
25
+ error = new(nil)
26
+ error.error = message
27
+ error
28
+ end
29
+
30
+ def to_s
31
+ if success?
32
+ "OK"
33
+ else
34
+ "ERROR: #{error}"
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def error_message_from_response
41
+ return if success?
42
+ return if @http.nil?
43
+ @http.body
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module Haile
2
+ VERSION = "0.3.0"
3
+ end
data/lib/haile.rb ADDED
@@ -0,0 +1,12 @@
1
+ __LIB_DIR__ = File.expand_path(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift __LIB_DIR__ unless $LOAD_PATH.include?(__LIB_DIR__)
3
+
4
+ require "rubygems"
5
+ require "httparty"
6
+
7
+ require "haile/version"
8
+ require "haile/client"
9
+ require "haile/response"
10
+
11
+ module Haile
12
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: haile
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Tobi Knaup
8
+ - Chris Kite
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-12-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: trollop
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: '2.0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ version: '2.0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: httparty
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: '0.11'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: '0.11'
42
+ - !ruby/object:Gem::Dependency
43
+ name: multi_json
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ~>
47
+ - !ruby/object:Gem::Version
48
+ version: '1.8'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: '1.8'
56
+ - !ruby/object:Gem::Dependency
57
+ name: bundler
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: '1.3'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '1.3'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rake
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ~>
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ description: Ruby client for Marathon REST API
85
+ email:
86
+ executables:
87
+ - haile
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - .gitignore
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - bin/haile
97
+ - haile.gemspec
98
+ - lib/haile.rb
99
+ - lib/haile/client.rb
100
+ - lib/haile/response.rb
101
+ - lib/haile/version.rb
102
+ homepage: https://github.com/chriskite/haile
103
+ licenses:
104
+ - MIT
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.0.0.rc.2
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: Ruby client for Marathon REST API
126
+ test_files: []