azkaban_scheduler 0.0.1

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: bb696acb2c67ae0e468b3289302b4066dea786a9
4
+ data.tar.gz: c9be59214ff6f278d726a8475143634c85fe4734
5
+ SHA512:
6
+ metadata.gz: a700b32c8abb25fe772212a55f6a568deb46cb1399e7da86c5fc2a52bad57b712da93e249ae411abed96c9e14eed8a4cc0b14f68d7a72ef185470bcb7f541d31
7
+ data.tar.gz: ade57a7249c77c9ae77d245fdbaa6cad406b60769398ab5accf537c8260d17ea3073086ac681c15d730470547a514e90a66036b207a0717870c1825cb8e99773
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ test/azkaban.yml
24
+ .ruby-version
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Dylan Thacker-Smith
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # AzkabanScheduler
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'azkaban_scheduler'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install azkaban_scheduler
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/azkaban_scheduler/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ task :default => 'test'
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << '.' << 'lib' << 'test'
8
+ t.test_files = FileList['test/**/*_test.rb']
9
+ t.verbose = false
10
+ end
@@ -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 'azkaban_scheduler/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "azkaban_scheduler"
8
+ spec.version = AzkabanScheduler::VERSION
9
+ spec.authors = ["Dylan Thacker-Smith"]
10
+ spec.email = ["Dylan.Smith@shopify.com"]
11
+ spec.summary = "Azkaban client that can update the schedule"
12
+ spec.homepage = "https://github.com/dylanahsmith/azkaban_scheduler"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
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("rubyzip", "~> 1.1")
21
+ spec.add_dependency("multipart-post", "~> 2.0")
22
+
23
+ spec.add_development_dependency("bundler", "~> 1.6")
24
+ spec.add_development_dependency("rake")
25
+ spec.add_development_dependency("minitest", "~> 5.4")
26
+ end
@@ -0,0 +1,6 @@
1
+ require 'azkaban_scheduler/errors'
2
+ require 'azkaban_scheduler/client'
3
+ require 'azkaban_scheduler/job'
4
+ require 'azkaban_scheduler/project'
5
+ require 'azkaban_scheduler/session'
6
+ require 'azkaban_scheduler/version'
@@ -0,0 +1,49 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ module AzkabanScheduler
5
+ class Client
6
+ def initialize(url)
7
+ uri = URI(url)
8
+ @http = Net::HTTP.new(uri.host, uri.port)
9
+ @http.use_ssl = uri.scheme == 'https'
10
+ end
11
+
12
+ def get(path, params=nil, headers=nil)
13
+ path += "?#{URI.encode_www_form(params)}" if params
14
+ req = Net::HTTP::Get.new(path)
15
+ send_request(req, headers)
16
+ end
17
+
18
+ def post(path, params=nil, headers=nil)
19
+ req = Net::HTTP::Post.new(path)
20
+ req.set_form_data(params) if params
21
+ send_request(req, headers)
22
+ end
23
+
24
+ def multipart_post(path, params, headers=nil)
25
+ req = Net::HTTP::Post::Multipart.new(path, params)
26
+ send_request(req, headers)
27
+ end
28
+
29
+ private
30
+
31
+ def send_request(request, headers)
32
+ request['Accept'] = 'application/json'
33
+ headers.each { |name, value| request[name] = value } if headers
34
+ response = @http.request(request)
35
+ dump_response(response) if ENV['DUMP_AZKABAN_RESPONSES']
36
+ response
37
+ end
38
+
39
+ def dump_response(response)
40
+ puts "HTTP/#{response.http_version} #{response.code} #{response.message}"
41
+ response.each_header do |name, value|
42
+ puts "#{name}: #{value}"
43
+ end
44
+ puts
45
+ puts "#{response.body}"
46
+ puts "-" * 60
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,8 @@
1
+ module AzkabanScheduler
2
+ class AzkabanError < StandardError; end
3
+ class AuthenticationError < AzkabanError; end
4
+ class ProjectNotFoundError < AzkabanError; end
5
+ class ProjectExistsError < AzkabanError; end
6
+ class InvalidProjectNameError < AzkabanError; end
7
+ class ProjectDescriptionEmptyError < AzkabanError; end
8
+ end
@@ -0,0 +1,13 @@
1
+ module AzkabanScheduler
2
+ class Job
3
+ def initialize(params)
4
+ @params = params
5
+ end
6
+
7
+ def write(io)
8
+ @params.each do |key, value|
9
+ io.puts("#{key}=#{value}")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ require 'stringio'
2
+ require 'zip'
3
+
4
+ module AzkabanScheduler
5
+ class Project
6
+ attr_accessor :name, :description, :jobs
7
+ attr_accessor :id, :version
8
+
9
+ def initialize(name, description)
10
+ @name = name
11
+ @description = description
12
+ @jobs = {}
13
+ end
14
+
15
+ def add_job(name, job)
16
+ @jobs[name] = job
17
+ end
18
+
19
+ def build
20
+ io = StringIO.new
21
+ write(io)
22
+ io.rewind
23
+ io
24
+ end
25
+
26
+ def write(io)
27
+ Zip::OutputStream.write_buffer(io) do |out|
28
+ @jobs.each do |name, job|
29
+ out.put_next_entry("#{name}.job")
30
+ job.write(out)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,16 @@
1
+ module AzkabanScheduler
2
+ class Schedule
3
+ Stats = Struct.new(:min, :max, :average)
4
+ attr_accessor :id, :stats
5
+
6
+ def initialize(project_name, flow_name, options={})
7
+ @project_name = project_name
8
+ @flow_name = flow_name
9
+ @period = options[:period]
10
+ @is_recurring = !!options[:period]
11
+ @time = options[:time] || Time.now.to_i
12
+ @failure_emails = options[:failure_emails]
13
+ @success_emails = options[:success_emails]
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,163 @@
1
+ require 'cgi'
2
+ require 'net/http/post/multipart'
3
+
4
+ module AzkabanScheduler
5
+ class Session
6
+ attr_accessor :id
7
+
8
+ def self.start(client, username, password)
9
+ params = {'action' => 'login', 'username' => username, 'password' => password}
10
+ response = client.post('/', params)
11
+ response.error! unless response.kind_of?(Net::HTTPSuccess)
12
+ result = JSON.parse(response.body)
13
+ unless result["status"] == "success"
14
+ error_message = result["error"]
15
+ if error_message == "Incorrect Login. Username\/Password not found."
16
+ raise AuthenticationError, error_message
17
+ end
18
+ raise AzkabanError, error_message
19
+ end
20
+ new(client, result['session.id'])
21
+ end
22
+
23
+ def initialize(client, id)
24
+ @client = client
25
+ @id = id
26
+ end
27
+
28
+ def create_project(project)
29
+ response = @client.post('/manager', {
30
+ 'session.id' => @id,
31
+ 'action' => 'create',
32
+ 'name' => project.name,
33
+ 'description' => project.description,
34
+ })
35
+ response.error! unless response.kind_of?(Net::HTTPSuccess)
36
+ result = JSON.parse(response.body)
37
+ if result['status'] != 'success'
38
+ error_message = result['message']
39
+ if error_message == "Active project with name #{project.name} already exists in db."
40
+ raise ProjectExistsError, error_message
41
+ elsif error_message == "Project names must start with a letter, followed by any number of letters, digits, '-' or '_'."
42
+ raise InvalidProjectNameError, error_message
43
+ elsif error_message == "Description cannot be empty."
44
+ raise ProjectDescriptionEmptyError, error_message
45
+ end
46
+ raise AzkabanError, error_message
47
+ end
48
+ result
49
+ end
50
+
51
+ def upload_project(project)
52
+ response = @client.multipart_post('/manager', {
53
+ 'session.id' => @id,
54
+ 'ajax' => 'upload',
55
+ 'project' => project.name,
56
+ 'file' => UploadIO.new(project.build, 'application/zip', 'file.zip'),
57
+ })
58
+ response.error! unless response.kind_of?(Net::HTTPSuccess)
59
+ result = JSON.parse(response.body)
60
+ if error_message = result['error']
61
+ if error_message == "Installation Failed. Project '#{project.name}' doesn't exist."
62
+ raise ProjectNotFoundError, error_message
63
+ end
64
+ raise AzkabanError, error_message
65
+ end
66
+ project.id = result['projectId']
67
+ project.version = result['version']
68
+ result
69
+ end
70
+
71
+ def delete_project(project_name)
72
+ response = @client.get('/manager', {
73
+ 'session.id' => @id,
74
+ 'project' => project_name,
75
+ 'delete' => 'true',
76
+ })
77
+ response.error! unless response.kind_of?(Net::HTTPSuccess) || response.kind_of?(Net::HTTPRedirection)
78
+ cookies = response_cookies(response)
79
+ unless cookies['azkaban.success.message']
80
+ error_message = cookies['azkaban.failure.message']
81
+ if error_message == "Project #{project_name} doesn't exist."
82
+ return false
83
+ end
84
+ raise AzkabanError, error_message
85
+ end
86
+ true
87
+ end
88
+
89
+ def list_schedules
90
+ response = @client.post('/schedule', { 'ajax' => 'loadFlow' }, session_id_cookie)
91
+ response.error! unless response.kind_of?(Net::HTTPSuccess)
92
+ result = JSON.parse(response.body)
93
+ result['items'] || []
94
+ end
95
+
96
+ def remove_schedule(schedule_id)
97
+ response = @client.post('/schedule', {
98
+ 'action' => 'removeSched',
99
+ 'scheduleId' => schedule_id,
100
+ }, session_id_cookie)
101
+ response.error! unless response.kind_of?(Net::HTTPSuccess)
102
+ result = JSON.parse(response.body)
103
+ unless result['status'] == 'success'
104
+ error_message = result['message']
105
+ return false if error_message == "Schedule with ID #{schedule_id} does not exist"
106
+ raise AzkabanError, error_message
107
+ end
108
+ true
109
+ end
110
+
111
+ def remove_all_schedules(project_name)
112
+ list_schedules.each do |schedule|
113
+ next unless schedule['projectname'] == project_name
114
+ schedule_id = schedule['scheduleid']
115
+ remove_schedule(schedule_id)
116
+ end
117
+ end
118
+
119
+ def post_schedule(project_id, project_name, flow, start_time, options={})
120
+ response = @client.post('/schedule', {
121
+ 'ajax' => 'scheduleFlow',
122
+ 'project' => project_name,
123
+ 'projectName' => project_name,
124
+ 'projectId' => project_id,
125
+ 'flow' => flow,
126
+ 'disabled' => options[:disabled] || '[]',
127
+ 'period' => options[:period] || "1d",
128
+ 'scheduleTime' => start_time.utc.strftime("%I,%M,%p,UTC"),
129
+ 'scheduleDate' => start_time.utc.strftime("%m/%d/%Y"),
130
+ 'is_recurring' => options[:period] ? 'on' : 'off',
131
+ 'concurrentOption' => options[:concurrent_option] || 'skip',
132
+ 'failureEmailsOverride' => (!!options[:failure_emails_override]).to_s,
133
+ 'successEmailsOverride' => (!!options[:success_emails_override]).to_s,
134
+ 'failureAction' => options[:failure_action] || 'finishCurrent',
135
+ 'failureEmails' => Array(options[:failure_emails]).join(', '),
136
+ 'successEmails' => Array(options[:success_emails]).join(', '),
137
+ 'notifyFailureFirst' => (!!options[:notify_failure_first]).to_s,
138
+ 'notifyFailureLast' => (!!options[:notify_failure_last]).to_s,
139
+ }, session_id_cookie)
140
+ response.error! unless response.kind_of?(Net::HTTPSuccess)
141
+ result = JSON.parse(response.body)
142
+ unless result['status'] == 'success'
143
+ raise AzkabanError, result['message']
144
+ end
145
+ nil
146
+ end
147
+
148
+ private
149
+
150
+ def response_cookies(response)
151
+ response['Set-Cookie'].split(',').each_with_object({}) do |cookie, hash|
152
+ name, value = cookie.split(';')[0].split('=', 2)
153
+ value = value.to_s.strip
154
+ value = value[1...-1] if value[0] == '"'.freeze && value[-1] == '"'.freeze
155
+ hash[name.strip] = value
156
+ end
157
+ end
158
+
159
+ def session_id_cookie
160
+ { 'Cookie' => "azkaban.browser.session.id=#{@id}" }
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,3 @@
1
+ module AzkabanScheduler
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,88 @@
1
+ require 'test_helper'
2
+
3
+ class SessionTest < Minitest::Test
4
+ include SessionTestHelper
5
+
6
+ def setup
7
+ @project = AzkabanScheduler::Project.new('Azkaban_Scheduler_Test', 'Used by the test suite')
8
+ @project.add_job('first', AzkabanScheduler::Job.new(type: 'command', command: 'echo "hello world"'))
9
+ @@once ||= setup_once || true
10
+ end
11
+
12
+ def setup_once
13
+ session.remove_all_schedules(@project.name)
14
+ session.delete_project(@project.name)
15
+ end
16
+
17
+ def test_start
18
+ refute session.id.empty?
19
+ end
20
+
21
+ def test_start_with_incorrect_password
22
+ assert_raises(AzkabanScheduler::AuthenticationError) do
23
+ AzkabanScheduler::Session.start(client, 'foo', 'bar')
24
+ end
25
+ end
26
+
27
+ def test_create_and_upload_project
28
+ session.create_project(@project)
29
+ result = session.upload_project(@project)
30
+ assert result['projectId']
31
+ assert result['version']
32
+ assert_equal result['projectId'], @project.id
33
+ assert_equal result['version'], @project.version
34
+ assert_equal true, session.delete_project(@project.name)
35
+ end
36
+
37
+ def test_post_schedule
38
+ session.create_project(@project)
39
+ session.upload_project(@project)
40
+ flow_name = 'first'
41
+ start_time = Time.now
42
+ session.post_schedule(@project.id, @project.name, flow_name, start_time,
43
+ period: '6h',
44
+ failure_emails_override: true,
45
+ notifyFailureFirst: true,
46
+ failure_emails: ['azkaban-scheduler-test@localhost'])
47
+ schedule = session.list_schedules.detect { |item| item['projectname'] == @project.name && item['flowname'] == flow_name }
48
+ assert schedule['scheduleid']
49
+ assert_equal (start_time.to_i - start_time.sec) * 1000, schedule['time']
50
+ assert_equal 6 * 60 * 60 * 1000, schedule['period']
51
+ ensure
52
+ session.remove_all_schedules(@project.name)
53
+ session.delete_project(@project.name)
54
+ end
55
+
56
+ def test_create_existing_project
57
+ session.create_project(@project)
58
+ assert_raises(AzkabanScheduler::ProjectExistsError) do
59
+ session.create_project(@project)
60
+ end
61
+ ensure
62
+ session.delete_project(@project.name)
63
+ end
64
+
65
+ def test_create_project_with_invalid_name
66
+ project = AzkabanScheduler::Project.new('Azkaban Scheduler Test', "description")
67
+ assert_raises(AzkabanScheduler::InvalidProjectNameError) do
68
+ session.create_project(project)
69
+ end
70
+ end
71
+
72
+ def test_create_project_with_invalid_name
73
+ project = AzkabanScheduler::Project.new('Azkaban_Scheduler_Test', "")
74
+ assert_raises(AzkabanScheduler::ProjectDescriptionEmptyError) do
75
+ session.create_project(project)
76
+ end
77
+ end
78
+
79
+ def test_upload_missing_project
80
+ assert_raises(AzkabanScheduler::ProjectNotFoundError) do
81
+ session.upload_project(@project)
82
+ end
83
+ end
84
+
85
+ def test_delete_missing_project
86
+ assert_equal false, session.delete_project(@project.name)
87
+ end
88
+ end
@@ -0,0 +1,20 @@
1
+ require 'azkaban_scheduler'
2
+
3
+ require "minitest/autorun"
4
+ require 'yaml'
5
+ require 'pathname'
6
+
7
+
8
+ module SessionTestHelper
9
+ def config
10
+ @@config ||= YAML.load(Pathname.new(File.dirname(__FILE__)).join('azkaban.yml').read)
11
+ end
12
+
13
+ def client
14
+ @@client ||= AzkabanScheduler::Client.new(config['url'])
15
+ end
16
+
17
+ def session
18
+ @@session ||= AzkabanScheduler::Session.start(client, config['username'], config['password'])
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: azkaban_scheduler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Dylan Thacker-Smith
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubyzip
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ requirement: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: '1.1'
25
+ prerelease: false
26
+ type: :runtime
27
+ - !ruby/object:Gem::Dependency
28
+ name: multipart-post
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ~>
37
+ - !ruby/object:Gem::Version
38
+ version: '2.0'
39
+ prerelease: false
40
+ type: :runtime
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ~>
51
+ - !ruby/object:Gem::Version
52
+ version: '1.6'
53
+ prerelease: false
54
+ type: :development
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ prerelease: false
68
+ type: :development
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '5.4'
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ~>
79
+ - !ruby/object:Gem::Version
80
+ version: '5.4'
81
+ prerelease: false
82
+ type: :development
83
+ description:
84
+ email:
85
+ - Dylan.Smith@shopify.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - .gitignore
91
+ - Gemfile
92
+ - LICENSE.txt
93
+ - README.md
94
+ - Rakefile
95
+ - azkaban_scheduler.gemspec
96
+ - lib/azkaban_scheduler.rb
97
+ - lib/azkaban_scheduler/client.rb
98
+ - lib/azkaban_scheduler/errors.rb
99
+ - lib/azkaban_scheduler/job.rb
100
+ - lib/azkaban_scheduler/project.rb
101
+ - lib/azkaban_scheduler/schedule.rb
102
+ - lib/azkaban_scheduler/session.rb
103
+ - lib/azkaban_scheduler/version.rb
104
+ - test/session_test.rb
105
+ - test/test_helper.rb
106
+ homepage: https://github.com/dylanahsmith/azkaban_scheduler
107
+ licenses:
108
+ - MIT
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.1.9
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Azkaban client that can update the schedule
130
+ test_files:
131
+ - test/session_test.rb
132
+ - test/test_helper.rb