chronos-api 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c8b3bb07696860917d8bccddca5b914f7919887e
4
+ data.tar.gz: f7e52fdd59a659d95095cc3ec032129a1edc1dff
5
+ SHA512:
6
+ metadata.gz: 5e7ea78dbb616f3511a943aa28dcafb9ac394906fbcfa352a01feaf0e25f32fde0bd01c5e0285d8369e8e8c1adbb7a9539883968b15c657617f0174724b531e9
7
+ data.tar.gz: e9ad14c088f26051c7348efc8c7492984bbb6d29bc11c7441f715870313c833a265fd7c4dabea59bab06a074bece3bd40ebe664a927bb955fa75b9a9163ae189
@@ -0,0 +1,7 @@
1
+ .bundle
2
+ .DS_Store
3
+ *.swp
4
+ *.gem
5
+ Gemfile.lock
6
+ vendor
7
+ coverage/
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.5
6
+ - 2.2.0
7
+ script: bundle exec rake
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Algolia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
@@ -0,0 +1,164 @@
1
+ chronos-api
2
+ ============
3
+
4
+ [![Gem Version](https://badge.fury.io/rb/chronos-api.svg)](http://badge.fury.io/rb/chronos-api) [![travis-ci](https://travis-ci.org/algolia/chronos-api.png?branch=master)](https://travis-ci.org/algolia/chronos-api)
5
+
6
+ This gem provides a CLI and a simple REST API client for [Chronos](https://github.com/mesos/chronos).
7
+
8
+ Installation
9
+ ------------
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'chronos-api', :require => 'chronos'
15
+ ```
16
+
17
+ And then run:
18
+
19
+ ```shell
20
+ $ bundle install
21
+ ```
22
+
23
+ Usage
24
+ -----
25
+
26
+ If you're running Chronos locally on port 4400, there is no setup to do in Ruby. If you're not or change the path or port, you'll have to point the gem to your socket or local/remote port. For example:
27
+
28
+ ```ruby
29
+ Chronos.url = 'http://example.com:8080'
30
+ ```
31
+
32
+ It's possible to use `ENV` variables to configure the endpoint as well:
33
+
34
+ ```shell
35
+ $ CHRONOS_URL=http://remote.chronos.example.com:8080 irb
36
+ irb(main):001:0> require 'chronos'
37
+ => true
38
+ irb(main):002:0> Chronos.url
39
+ => "http://remote.chronos.example.com:8080"
40
+ ```
41
+
42
+ ## Authentification
43
+
44
+ You have two options to set authentification if your Chronos API requires it:
45
+
46
+ ```ruby
47
+ Chronos.options = {:username => 'your-user-name', :password => 'your-secret-password'}
48
+ ```
49
+
50
+ or
51
+
52
+ ```shell
53
+ $ export CHRONOS_USER=your-user-name
54
+ $ export CHRONOS_PASSWORD=your-secret-password
55
+ $ irb
56
+ irb(main):001:0> require 'chronos'
57
+ => true
58
+ irb(main):002:0> Chronos.options
59
+ => {:username => "your-user-name", :password => "your-secret-password"}
60
+ ```
61
+
62
+ or
63
+
64
+ ```shell
65
+ $ chronos -c http://USERNAME:PASSWORD@HOST:PORT
66
+ ```
67
+
68
+ ## List
69
+
70
+ To list the current scheduled jobs:
71
+
72
+ ```ruby
73
+ require 'chronos'
74
+ jobs = Chronos.list
75
+ ```
76
+
77
+ or
78
+
79
+ ```shell
80
+ $ chronos list
81
+ ```
82
+
83
+ ## Add a job
84
+
85
+ To add a new job:
86
+
87
+ ```ruby
88
+ require 'chronos'
89
+ Chronos.add({
90
+ name: 'myjob',
91
+ schedule: 'R10/2012-10-01T05:52:00Z/PT2S',
92
+ epsilon: 'PT15M',
93
+ command: 'echo foobar',
94
+ owner: 'chronos@algolia.com',
95
+ async: false
96
+ })
97
+ ```
98
+
99
+ or
100
+
101
+ ```shell
102
+ $ chronos add --job /path/to/job/details.json
103
+ ```
104
+
105
+ ## Delete a job
106
+
107
+ To delete a job:
108
+
109
+ ```ruby
110
+ require 'chronos'
111
+ Chronos.delete('job_name')
112
+ ```
113
+
114
+ or
115
+
116
+ ```shell
117
+ $ chronos delete --name job_name
118
+ ```
119
+
120
+ ## Delete all jobs
121
+
122
+ To delete all jobs:
123
+
124
+ ```ruby
125
+ require 'chronos'
126
+ Chronos.delete_all
127
+ ```
128
+
129
+ or
130
+
131
+ ```shell
132
+ $ chronos delete_all
133
+ ```
134
+
135
+ ## Manually start a job
136
+
137
+ To manually start a job:
138
+
139
+ ```ruby
140
+ require 'chronos'
141
+ Chronos.start('job_name')
142
+ ```
143
+
144
+ or
145
+
146
+ ```shell
147
+ $ chronos start --name job_name
148
+ ```
149
+
150
+ Contributing
151
+ ------------
152
+
153
+ Please fork and send pull request.
154
+ Make sure to have test cases for your changes.
155
+
156
+ Credits
157
+ -------
158
+
159
+ This gem has been highly inspired by [marathon-api](https://github.com/otto-de/marathon-api).
160
+
161
+ License
162
+ -------
163
+
164
+ This program is licensed under the MIT license. See LICENSE for details.
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+
3
+ require 'rake'
4
+ require 'json'
5
+ require 'chronos'
6
+ require 'rspec/core/rake_task'
7
+
8
+ task :default => [:spec]
9
+
10
+ RSpec::Core::RakeTask.new do |t|
11
+ t.pattern = 'spec/**/*_spec.rb'
12
+ end
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'chronos'))
4
+ require 'trollop'
5
+ require 'json'
6
+ require 'terminal-table'
7
+
8
+ ## Sub commands
9
+
10
+ SUB_COMMANDS = %w[list delete delete_all start add] # TODO: delete_tasks add_dependency add_docker graph task]
11
+
12
+ # list
13
+ def subcmd_list(cmd_opts)
14
+ rows = []
15
+ Chronos.list.each do |job|
16
+ cmd = job['command'] || ''
17
+ cmd = "#{cmd[0..50]}..." if cmd.size > 50
18
+ rows << [ job['name'], job['owner'], cmd, job['cpus'], job['mem'], job['disk'], job['disabled'], job['schedule'] ]
19
+ end
20
+ puts Terminal::Table.new :headings => ['Name', 'Owner', 'Command', 'CPU', 'MEM', 'Disk', 'Disabled', 'Schedule'], :rows => rows
21
+ end
22
+
23
+ # add
24
+ def subcmd_add(cmd_opts)
25
+ job = JSON.parse(cmd_opts[:job] == '-' ? $stdin.read : File.read(cmd_opts[:job]))
26
+ Chronos.add(job)
27
+ end
28
+
29
+ # delete
30
+ def subcmd_delete(cmd_opts)
31
+ Chronos.delete(cmd_opts[:name])
32
+ end
33
+
34
+ # delete_all
35
+ def subcmd_delete_all(cmd_opts)
36
+ Chronos.delete_all
37
+ end
38
+
39
+ # start
40
+ def subcmd_start(cmd_opts)
41
+ Chronos.start(cmd_opts[:name])
42
+ end
43
+
44
+ ## Commands
45
+
46
+ # parse global options
47
+ def parse_global_opts
48
+ global_opts = Trollop.options do
49
+ version Chronos::VERSION
50
+ banner <<-EOS
51
+ Usage: chronos [global options] [command] [options]
52
+
53
+ Available commands:
54
+
55
+ list List all jobs
56
+ delete Delete a job
57
+ delete_all Delete all jobs
58
+ start Start a job
59
+ add Add a new job
60
+
61
+ TODO:
62
+ #delete_tasks Delete all tasks of a job
63
+ #add_dependency Add a new job add_dependency
64
+ #add_docker Add a new docker job
65
+ #graph See the dependencies graph
66
+ #task Get a task status
67
+
68
+ Global options:
69
+ EOS
70
+
71
+ opt :url, 'Chronos host (default http://localhost:4400, or CHRONOS_URL)',
72
+ :short => '-c', :type => String
73
+ opt :username, 'User name to authenticate against Chronos (optional, default unset, or CHRONOS_USER).',
74
+ :short => '-u', :type => String
75
+ opt :password, 'Password to authenticate against Chronos (optional, default unset, or CHRONOS_PASSWORD).',
76
+ :short => '-p', :type => String
77
+ stop_on SUB_COMMANDS
78
+ end
79
+ return global_opts
80
+ end
81
+
82
+ # set global options to Marathon API
83
+ def set_global_opts(global_opts)
84
+ # Set client's URL
85
+ Chronos.url = global_opts[:url] if global_opts[:url]
86
+ global_opts.delete(:url)
87
+ # Set client's options
88
+ Chronos.options = global_opts if global_opts.size > 0
89
+ end
90
+
91
+ # get the subcommand
92
+ def parse_subcmd
93
+ cmd = ARGV.shift
94
+ return cmd
95
+ end
96
+
97
+ # parse subcommand specific options
98
+ def parse_subcmd_opts(cmd)
99
+ cmd_opts = case cmd
100
+ when 'add'
101
+ Trollop.options do
102
+ opt :job, 'A JSON formatted file to read job details from. (use - to read from stdin)', short: 'j', type: String, required: true
103
+ end
104
+ when 'delete'
105
+ Trollop.options do
106
+ opt :name, 'The name of the job to delete', short: 'n', type: String, required: true
107
+ end
108
+ when 'start'
109
+ Trollop.options do
110
+ opt :name, 'The name of the job to start', short: 'n', type: String, required: true
111
+ end
112
+ else
113
+ {}
114
+ end
115
+
116
+ return cmd_opts
117
+ end
118
+
119
+ # Run selected subcmd
120
+ def run_subcmd(cmd, cmd_opts)
121
+ if SUB_COMMANDS.include?(cmd)
122
+ send("subcmd_#{cmd}", cmd_opts)
123
+ else
124
+ Trollop.die "unknown subcommand #{cmd.inspect}"
125
+ end
126
+ end
127
+
128
+ global_opts = parse_global_opts
129
+ set_global_opts(global_opts)
130
+
131
+ cmd = parse_subcmd
132
+ cmd_opts = parse_subcmd_opts(cmd)
133
+
134
+ run_subcmd(cmd, cmd_opts)
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/chronos/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = "chronos-api"
6
+ gem.version = Chronos::VERSION
7
+ gem.authors = ["Algolia Team"]
8
+ gem.email = %w{support@algolia.com}
9
+ gem.description = %q{A simple REST client for the Chronos API}
10
+ gem.summary = %q{A simple REST client for the Chronos API}
11
+ gem.homepage = 'https://github.com/algolia/chronos-api'
12
+ gem.license = 'MIT'
13
+
14
+ gem.files = `git ls-files`.split($\)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = %w{lib}
18
+
19
+ gem.add_dependency 'json'
20
+ gem.add_dependency 'httparty', '~> 0.11.0'
21
+ gem.add_dependency 'trollop', '~> 2.0.0'
22
+ gem.add_dependency 'terminal-table', '~> 1.4.3'
23
+
24
+ gem.add_development_dependency 'rake'
25
+ gem.add_development_dependency 'rspec', '~> 3.0'
26
+ gem.add_development_dependency 'rspec-its'
27
+ gem.add_development_dependency 'vcr', '>= 2.7.0'
28
+ gem.add_development_dependency 'webmock'
29
+ gem.add_development_dependency 'pry'
30
+ end
@@ -0,0 +1,95 @@
1
+ require 'rubygems/package'
2
+ require 'httparty'
3
+ require 'json'
4
+ require 'uri'
5
+
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+
8
+ # The top-level module for this gem. It's purpose is to hold global
9
+ # configuration variables that are used as defaults in other classes.
10
+ module Chronos
11
+
12
+ attr_accessor :logger
13
+
14
+ require 'chronos/version'
15
+ require 'chronos/error'
16
+ require 'chronos/connection'
17
+
18
+ DEFAULT_URL = 'http://localhost:4400'
19
+
20
+ # Get the chronos url from environment
21
+ def env_url
22
+ ENV['CHRONOS_URL']
23
+ end
24
+
25
+ # Get marathon options from environment
26
+ def env_options
27
+ opts = {}
28
+ opts[:username] = ENV['CHRONOS_USER'] if ENV['CHRONOS_USER']
29
+ opts[:password] = ENV['CHRONOS_PASSWORD'] if ENV['CHRONOS_PASSWORD']
30
+ opts
31
+ end
32
+
33
+ # Get the marathon API URL
34
+ def url
35
+ @url ||= env_url || DEFAULT_URL
36
+ @url
37
+ end
38
+
39
+ # Get options for connecting to marathon API
40
+ def options
41
+ @options ||= env_options
42
+ end
43
+
44
+ # Set a new url
45
+ def url=(new_url)
46
+ @url = new_url
47
+ reset_connection!
48
+ end
49
+
50
+ # Set new options
51
+ def options=(new_options)
52
+ @options = env_options.merge(new_options || {})
53
+ reset_connection!
54
+ end
55
+
56
+ # Set a new connection
57
+ def connection
58
+ @connection ||= Connection.new(url, options)
59
+ end
60
+
61
+ # Reset the connection
62
+ def reset_connection!
63
+ @connection = nil
64
+ end
65
+
66
+ # list jobs
67
+ def list
68
+ connection.get('/scheduler/jobs')
69
+ end
70
+
71
+ ## add job
72
+ def add(job)
73
+ connection.post('/scheduler/iso8601', nil, body: job)
74
+ end
75
+
76
+ # delete job
77
+ def delete(name)
78
+ connection.delete("/scheduler/job/#{URI.encode name}")
79
+ end
80
+
81
+ # delete all jobs
82
+ def delete_all
83
+ connection.delete('/scheduler/jobs')
84
+ end
85
+
86
+ # start a job
87
+ def start(name)
88
+ connection.put("/scheduler/job/#{URI.encode name}")
89
+ end
90
+
91
+ module_function :connection, :env_options, :env_url, :logger, :logger=,
92
+ :options, :options=, :url, :url= ,:reset_connection!,
93
+ :list, :add, :delete, :delete_all, :start
94
+
95
+ end
@@ -0,0 +1,107 @@
1
+ # This class represents a Chronos API Connection.
2
+ class Chronos::Connection
3
+
4
+ include Chronos::Error
5
+ include HTTParty
6
+
7
+ headers(
8
+ 'Content-Type' => 'application/json',
9
+ 'Accept' => 'application/json',
10
+ 'User-Agent' => "Chronos-API #{Chronos::VERSION}"
11
+ )
12
+
13
+ default_timeout 5
14
+ maintain_method_across_redirects
15
+
16
+ attr_reader :url, :options
17
+
18
+ # Create a new API connection.
19
+ # ++url++: URL of the chronos API.
20
+ # ++options++: Hash with options for chronos API.
21
+ def initialize(url, options = {})
22
+ @url = url
23
+ @options = options
24
+ if @options[:username] and @options[:password]
25
+ @options[:basic_auth] = {
26
+ :username => @options[:username],
27
+ :password => @options[:password]
28
+ }
29
+ @options.delete(:username)
30
+ @options.delete(:password)
31
+ end
32
+ end
33
+
34
+ # Delegate all HTTP methods to the #request.
35
+ [:get, :put, :post, :delete].each do |method|
36
+ define_method(method) { |*args, &block| request(method, *args) }
37
+ end
38
+
39
+ def to_s
40
+ "Chronos::Connection { :url => #{url} :options => #{options} }"
41
+ end
42
+
43
+ private
44
+
45
+ # Create URL suffix for a hash of query parameters.
46
+ # URL escaping is done internally.
47
+ # ++query++: Hash of query parameters.
48
+ def query_params(query)
49
+ query = query.select { |k,v| !v.nil? }
50
+ URI.escape(query.map { |k,v| "#{k}=#{v}" }.join('&'))
51
+ end
52
+
53
+ # Create request object.
54
+ # ++http_method++: GET/POST/PUT/DELETE.
55
+ # ++path++: Relative path to connection's URL.
56
+ # ++query++: Optional query parameters.
57
+ # ++opts++: Optional options. Ex. opts[:body] is used for PUT/POST request.
58
+ def compile_request_params(http_method, path, query = nil, opts = nil)
59
+ query ||= {}
60
+ opts ||= {}
61
+ headers = opts.delete(:headers) || {}
62
+ opts[:body] = opts[:body].to_json unless opts[:body].nil?
63
+ {
64
+ :method => http_method,
65
+ :url => "#{@url}#{path}",
66
+ :query => query
67
+ }.merge(opts).reject { |_, v| v.nil? }
68
+ end
69
+
70
+ # Create full URL with query parameters.
71
+ # ++request++: hash containing :url and optional :query
72
+ def build_url(request)
73
+ url = URI.escape(request[:url])
74
+ if request[:query].size > 0
75
+ url += '?' + query_params(request[:query])
76
+ end
77
+ url
78
+ end
79
+
80
+ # Parse response or raise error.
81
+ # ++response++: response from HTTParty call.
82
+ def parse_response(response)
83
+ if response.success?
84
+ response.parsed_response
85
+ else
86
+ raise Chronos::Error.from_response(response)
87
+ end
88
+ end
89
+
90
+ # Send a request to the server and parse response.
91
+ # ++http_method++: GET/POST/PUT/DELETE.
92
+ # ++path++: Relative path to connection's URL.
93
+ # ++query++: Optional query parameters.
94
+ # ++opts++: Optional options. Ex. opts[:body] is used for PUT/POST request.
95
+ def request(*args)
96
+ request = compile_request_params(*args)
97
+ url = build_url(request)
98
+ parse_response(self.class.send(request[:method], url, request))
99
+ rescue => e
100
+ if e.class == SocketError or e.class.name.start_with?('Errno::')
101
+ raise IOError, "HTTP call failed: #{e.message}"
102
+ else
103
+ raise e
104
+ end
105
+ end
106
+
107
+ end
@@ -0,0 +1,68 @@
1
+ # This module holds the Errors for the gem.
2
+ module Chronos::Error
3
+ # The default error. It's never actually raised, but can be used to catch all
4
+ # gem-specific errors that are thrown as they all subclass from this.
5
+ class ChronosError < StandardError; end
6
+
7
+ # Raised when invalid arguments are passed to a method.
8
+ class ArgumentError < ChronosError; end
9
+
10
+ # Raised when a request returns a 400 or 422.
11
+ class ClientError < ChronosError; end
12
+
13
+ # Raised when a request returns a 404.
14
+ class NotFoundError < ChronosError; end
15
+
16
+ # Raised when there is an unexpected response code / body.
17
+ class UnexpectedResponseError < ChronosError; end
18
+
19
+ # Raised when a request times out.
20
+ class TimeoutError < ChronosError; end
21
+
22
+ # Raised when login fails.
23
+ class AuthenticationError < ChronosError; end
24
+
25
+ # Raised when an IO action fails.
26
+ class IOError < ChronosError; end
27
+
28
+ # Raise error specific to http response.
29
+ # ++response++: HTTParty response object.
30
+ def from_response(response)
31
+ error_class(response).new(error_message(response))
32
+ end
33
+
34
+ private
35
+
36
+ # Get reponse code specific error class.
37
+ # ++response++: HTTParty response object.
38
+ def error_class(response)
39
+ case response.code
40
+ when 400
41
+ ClientError
42
+ when 422
43
+ ClientError
44
+ when 404
45
+ NotFoundError
46
+ else
47
+ UnexpectedResponseError
48
+ end
49
+ end
50
+
51
+ # Get response code from http response.
52
+ # ++response++: HTTParty response object.
53
+ def error_message(response)
54
+ body = response.parsed_response
55
+ if not body.is_a?(Hash)
56
+ body
57
+ elsif body['message']
58
+ body['message']
59
+ elsif body['errors']
60
+ body['errors']
61
+ else
62
+ body
63
+ end
64
+ end
65
+
66
+ module_function :error_class, :error_message, :from_response
67
+
68
+ end
@@ -0,0 +1,3 @@
1
+ module Chronos
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chronos do
4
+
5
+ describe '.url=' do
6
+ subject { described_class }
7
+
8
+ it 'sets new url' do
9
+ described_class.url = 'http://foo'
10
+ expect(described_class.url).to eq('http://foo')
11
+
12
+ # reset connection after running this spec
13
+ described_class.url = nil
14
+ end
15
+
16
+ it 'resets connection' do
17
+ old_connection = described_class.connection
18
+ described_class.url = 'http://bar'
19
+
20
+ expect(described_class.connection).not_to be(old_connection)
21
+
22
+ # reset connection after running this spec
23
+ described_class.url = nil
24
+ end
25
+ end
26
+
27
+ describe '.options=' do
28
+ subject { described_class }
29
+
30
+ it 'sets new options' do
31
+ described_class.options = {:foo => 'bar'}
32
+ expect(described_class.options).to eq({:foo => 'bar'})
33
+
34
+ # reset connection after running this spec
35
+ described_class.options = nil
36
+ end
37
+
38
+ it 'resets connection' do
39
+ old_connection = described_class.connection
40
+ described_class.options = {:foo => 'bar'}
41
+
42
+ expect(described_class.connection).not_to be(old_connection)
43
+
44
+ # reset connection after running this spec
45
+ described_class.options = nil
46
+ end
47
+
48
+ it 'adds :basic_auth options for :username and :password' do
49
+ described_class.options = {:username => 'user', :password => 'password'}
50
+ expect(described_class.connection.options)
51
+ .to eq({:basic_auth => {:username => 'user', :password => 'password'}})
52
+
53
+ # reset connection after running this spec
54
+ described_class.options = nil
55
+ end
56
+ end
57
+
58
+ describe '.list' do
59
+ subject { described_class }
60
+
61
+ it 'lists jobs', :vcr do
62
+ jobs = described_class.list
63
+ expect(jobs.size).to eq(1)
64
+ expect(jobs[0]['name']).to eq('SAMPLE_JOB1')
65
+ end
66
+ end
67
+
68
+ describe '.add' do
69
+ subject { described_class }
70
+
71
+ it 'adds a job', :vcr do
72
+ described_class.add({
73
+ schedule: 'R10/2012-10-01T05:52:00Z/PT2S',
74
+ name: 'SAMPLE_JOB1',
75
+ epsilon: 'PT15M',
76
+ command: 'echo FOO',
77
+ owner: 'chronos@algolia.com',
78
+ async: false
79
+ })
80
+ end
81
+ end
82
+
83
+ describe '.delete' do
84
+ subject { described_class }
85
+
86
+ it 'deletes a job', :vcr do
87
+ described_class.delete('SAMPLE_JOB1')
88
+ end
89
+
90
+ it 'raises a 400 if the job to delete doesn\'t exist', :vcr do
91
+ expect {
92
+ described_class.delete('doesnt_exist')
93
+ }.to raise_error(Chronos::Error::ClientError)
94
+ end
95
+ end
96
+
97
+
98
+ describe '.delete_all' do
99
+ subject { described_class }
100
+
101
+ it 'deletes all jobs', :vcr do
102
+ described_class.delete_all
103
+ end
104
+ end
105
+
106
+ describe '.start' do
107
+ subject { described_class }
108
+
109
+ it 'starts a job', :vcr do
110
+ described_class.start('SAMPLE_JOB1')
111
+ end
112
+
113
+ it 'raises a 400 if the jobs to start doesn\'t exist', :vcr do
114
+ expect {
115
+ described_class.start('doesnt_exist')
116
+ }.to raise_error(Chronos::Error::ClientError)
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chronos::Connection do
4
+
5
+ describe '#to_s' do
6
+ subject { described_class.new('http://foo:4400') }
7
+
8
+ let(:expected_string) do
9
+ "Chronos::Connection { :url => http://foo:4400 :options => {} }"
10
+ end
11
+
12
+ its(:to_s) { should == expected_string }
13
+ end
14
+
15
+ describe '#request' do
16
+ subject { described_class.new('http://foo.example.org:8080') }
17
+
18
+ it 'raises IOError on SocketError' do
19
+ allow(described_class).to receive(:send) { raise SocketError.new }
20
+ expect {
21
+ subject.get('/path')
22
+ }.to raise_error(Chronos::Error::IOError)
23
+ end
24
+
25
+ it 'raises IOError on Errno' do
26
+ allow(described_class).to receive(:send) { raise Errno::EINTR.new }
27
+ expect {
28
+ subject.get('/path')
29
+ }.to raise_error(Chronos::Error::IOError)
30
+ end
31
+
32
+ it 'raises original error when unknown' do
33
+ allow(described_class).to receive(:send) { raise RuntimeError.new }
34
+ expect {
35
+ subject.get('/path')
36
+ }.to raise_error(RuntimeError)
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chronos::Error do
4
+
5
+ describe '.error_class' do
6
+ subject { described_class }
7
+
8
+ it 'returns ClientError on 400' do
9
+ expect(subject.error_class(Net::HTTPResponse.new(1.1, 400, 'Client Error')))
10
+ .to be(Chronos::Error::ClientError)
11
+ end
12
+
13
+ it 'returns ClientError on 422' do
14
+ expect(subject.error_class(Net::HTTPResponse.new(1.1, 422, 'Client Error')))
15
+ .to be(Chronos::Error::ClientError)
16
+ end
17
+
18
+ it 'returns NotFoundError on 404' do
19
+ expect(subject.error_class(Net::HTTPResponse.new(1.1, 404, 'Not Found')))
20
+ .to be(Chronos::Error::NotFoundError)
21
+ end
22
+
23
+ it 'returns UnexpectedResponseError anything else' do
24
+ expect(subject.error_class(Net::HTTPResponse.new(1.1, 599, 'Whatever')))
25
+ .to be(Chronos::Error::UnexpectedResponseError)
26
+ end
27
+ end
28
+
29
+ describe '.error_message' do
30
+ subject { described_class }
31
+
32
+ it 'returns "message" from respose json' do
33
+ r = { 'message' => 'fooo' }
34
+ expect(r).to receive(:parsed_response) { r }
35
+ expect(subject.error_message(r)).to eq('fooo')
36
+ end
37
+
38
+ it 'returns "errors" from respose json' do
39
+ r = { 'errors' => 'fooo' }
40
+ expect(r).to receive(:parsed_response) { r }
41
+ expect(subject.error_message(r)).to eq('fooo')
42
+ end
43
+
44
+ it 'returns full hash from respose json, if keys are missing' do
45
+ r = { 'bars' => 'fooo' }
46
+ expect(r).to receive(:parsed_response) { r }
47
+ expect(subject.error_message(r)).to eq(r)
48
+ end
49
+
50
+ it 'returns full body if not a hash with "message"' do
51
+ r = 'fooo'
52
+ expect(r).to receive(:parsed_response) { r }
53
+ expect(subject.error_message(r)).to eq('fooo')
54
+ end
55
+ end
56
+
57
+ end
@@ -0,0 +1,25 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: http://localhost:4400/scheduler/iso8601
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"schedule":"R10/2012-10-01T05:52:00Z/PT2S","name":"SAMPLE_JOB1","epsilon":"PT15M","command":"echo FOO","owner":"chronos@algolia.com","async":false}'
9
+ headers:
10
+ Content-Type:
11
+ - application/json
12
+ Accept:
13
+ - application/json
14
+ User-Agent:
15
+ - Chronos-API 0.0.1
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ body:
21
+ encoding: UTF-8
22
+ string: ""
23
+ http_version:
24
+ recorded_at: Fri, 03 Apr 2015 19:18:04 GMT
25
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,25 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: delete
5
+ uri: http://localhost:4400/scheduler/job/SAMPLE_JOB1
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Content-Type:
11
+ - application/json
12
+ Accept:
13
+ - application/json
14
+ User-Agent:
15
+ - Chronos-API 0.0.1
16
+ response:
17
+ status:
18
+ code: 204
19
+ message: OK
20
+ body:
21
+ encoding: UTF-8
22
+ string: ""
23
+ http_version:
24
+ recorded_at: Fri, 03 Apr 2015 19:29:16 GMT
25
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,25 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: delete
5
+ uri: http://localhost:4400/scheduler/job/doesnt_exist
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Content-Type:
11
+ - application/json
12
+ Accept:
13
+ - application/json
14
+ User-Agent:
15
+ - Chronos-API 0.0.1
16
+ response:
17
+ status:
18
+ code: 400
19
+ message: Bad Request
20
+ body:
21
+ encoding: UTF-8
22
+ string: "requirement failed: Job 'doesnt_exist' not found"
23
+ http_version:
24
+ recorded_at: Fri, 03 Apr 2015 19:41:52 GMT
25
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,25 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: delete
5
+ uri: http://localhost:4400/scheduler/jobs
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Content-Type:
11
+ - application/json
12
+ Accept:
13
+ - application/json
14
+ User-Agent:
15
+ - Chronos-API 0.0.1
16
+ response:
17
+ status:
18
+ code: 204
19
+ message: OK
20
+ body:
21
+ encoding: UTF-8
22
+ string: ""
23
+ http_version:
24
+ recorded_at: Fri, 03 Apr 2015 19:31:17 GMT
25
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,28 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: http://localhost:4400/scheduler/jobs
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ Content-Type:
11
+ - application/json
12
+ Accept:
13
+ - application/json
14
+ User-Agent:
15
+ - Chronos-API 1.1.0
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ headers:
21
+ Content-Type:
22
+ - application/json
23
+ body:
24
+ encoding: UTF-8
25
+ string: '[{"name":"SAMPLE_JOB1","command":"echo FOO","shell":true,"epsilon":"PT15M","executor":"","executorFlags":"","retries":2,"owner":"chronos@algolia.com","async":false,"successCount":0,"errorCount":0,"lastSuccess":"","lastError":"","cpus":0.1,"disk":256.0,"mem":128.0,"disabled":false,"softError":false,"errorsSinceLastSuccess":0,"uris":[],"environmentVariables":[],"arguments":[],"highPriority":false,"runAsUser":"root","schedule":"R10/2012-10-01T05:52:00Z/PT2S","scheduleTimeZone":""}]'
26
+ http_version:
27
+ recorded_at: Fri, 03 Apr 2015 19:18:04 GMT
28
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,25 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: put
5
+ uri: http://localhost:4400/scheduler/job/doesnt_exist
6
+ body:
7
+ encoding: UTF-8
8
+ string: ''
9
+ headers:
10
+ Content-Type:
11
+ - application/json
12
+ Accept:
13
+ - application/json
14
+ User-Agent:
15
+ - Chronos-API 0.0.1
16
+ response:
17
+ status:
18
+ code: 400
19
+ message: Bad Request
20
+ body:
21
+ encoding: UTF-8
22
+ string: ""
23
+ http_version:
24
+ recorded_at: Fri, 03 Apr 2015 19:44:16 GMT
25
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,25 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: put
5
+ uri: http://localhost:4400/scheduler/job/SAMPLE_JOB1
6
+ body:
7
+ encoding: UTF-8
8
+ string: ''
9
+ headers:
10
+ Content-Type:
11
+ - application/json
12
+ Accept:
13
+ - application/json
14
+ User-Agent:
15
+ - Chronos-API 0.0.1
16
+ response:
17
+ status:
18
+ code: 204
19
+ message: OK
20
+ body:
21
+ encoding: UTF-8
22
+ string: ""
23
+ http_version:
24
+ recorded_at: Fri, 03 Apr 2015 19:32:45 GMT
25
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,9 @@
1
+ {
2
+ "schedule": "R10/2012-10-01T05:52:00Z/PT2S",
3
+ "name": "SAMPLE_JOB1",
4
+ "epsilon": "PT15M",
5
+ "command": "echo FOO",
6
+ "owner": "chronos@algolia.com",
7
+ "async": false
8
+ }
9
+
@@ -0,0 +1,30 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'rspec'
4
+ require 'rspec/its'
5
+ require 'vcr'
6
+ require 'webmock'
7
+ require 'chronos'
8
+
9
+ VCR.configure do |c|
10
+ c.allow_http_connections_when_no_cassette = false
11
+ c.cassette_library_dir = "spec/fixtures"
12
+ #c.debug_logger = $stdout
13
+ c.hook_into :webmock
14
+ c.configure_rspec_metadata!
15
+ end
16
+
17
+ RSpec.shared_context "local paths" do
18
+ def project_dir
19
+ File.expand_path(File.join(File.dirname(__FILE__), '..'))
20
+ end
21
+ end
22
+
23
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
24
+
25
+ RSpec.configure do |c|
26
+ c.mock_with :rspec
27
+ c.color = true
28
+ c.formatter = :documentation
29
+ c.tty = true
30
+ end
metadata ADDED
@@ -0,0 +1,221 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chronos-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Algolia Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: httparty
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.11.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.11.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: trollop
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 2.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 2.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: terminal-table
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.4.3
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.4.3
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-its
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: vcr
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: 2.7.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: 2.7.0
125
+ - !ruby/object:Gem::Dependency
126
+ name: webmock
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description: A simple REST client for the Chronos API
154
+ email:
155
+ - support@algolia.com
156
+ executables:
157
+ - chronos
158
+ extensions: []
159
+ extra_rdoc_files: []
160
+ files:
161
+ - ".gitignore"
162
+ - ".travis.yml"
163
+ - Gemfile
164
+ - LICENSE
165
+ - README.md
166
+ - Rakefile
167
+ - bin/chronos
168
+ - chronos-api.gemspec
169
+ - lib/chronos.rb
170
+ - lib/chronos/connection.rb
171
+ - lib/chronos/error.rb
172
+ - lib/chronos/version.rb
173
+ - spec/chronos/chronos_spec.rb
174
+ - spec/chronos/connection_spec.rb
175
+ - spec/chronos/error_spec.rb
176
+ - spec/fixtures/Chronos/_add/adds_a_job.yml
177
+ - spec/fixtures/Chronos/_delete/deletes_a_job.yml
178
+ - spec/fixtures/Chronos/_delete/raises_a_400_if_the_job_to_delete_doesn_t_exist.yml
179
+ - spec/fixtures/Chronos/_delete_all/deletes_all_jobs.yml
180
+ - spec/fixtures/Chronos/_list/lists_jobs.yml
181
+ - spec/fixtures/Chronos/_start/raises_a_400_if_the_jobs_to_start_doesn_t_exist.yml
182
+ - spec/fixtures/Chronos/_start/starts_a_job.yml
183
+ - spec/fixtures/add.json
184
+ - spec/spec_helper.rb
185
+ homepage: https://github.com/algolia/chronos-api
186
+ licenses:
187
+ - MIT
188
+ metadata: {}
189
+ post_install_message:
190
+ rdoc_options: []
191
+ require_paths:
192
+ - lib
193
+ required_ruby_version: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - ">="
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ required_rubygems_version: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ requirements: []
204
+ rubyforge_project:
205
+ rubygems_version: 2.2.2
206
+ signing_key:
207
+ specification_version: 4
208
+ summary: A simple REST client for the Chronos API
209
+ test_files:
210
+ - spec/chronos/chronos_spec.rb
211
+ - spec/chronos/connection_spec.rb
212
+ - spec/chronos/error_spec.rb
213
+ - spec/fixtures/Chronos/_add/adds_a_job.yml
214
+ - spec/fixtures/Chronos/_delete/deletes_a_job.yml
215
+ - spec/fixtures/Chronos/_delete/raises_a_400_if_the_job_to_delete_doesn_t_exist.yml
216
+ - spec/fixtures/Chronos/_delete_all/deletes_all_jobs.yml
217
+ - spec/fixtures/Chronos/_list/lists_jobs.yml
218
+ - spec/fixtures/Chronos/_start/raises_a_400_if_the_jobs_to_start_doesn_t_exist.yml
219
+ - spec/fixtures/Chronos/_start/starts_a_job.yml
220
+ - spec/fixtures/add.json
221
+ - spec/spec_helper.rb