pvdgm-bs-client 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 +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +75 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/bin/beanstalk_api +8 -0
- data/lib/pvdgm-bs-client.rb +15 -0
- data/lib/pvdgm-bs-client/base_resource.rb +103 -0
- data/lib/pvdgm-bs-client/beanstalk_api_options.rb +136 -0
- data/lib/pvdgm-bs-client/prompters/job_prompter.rb +32 -0
- data/lib/pvdgm-bs-client/prompters/to_tube_prompter.rb +25 -0
- data/lib/pvdgm-bs-client/prompters/tube_prompter.rb +25 -0
- data/lib/pvdgm-bs-client/resources/job.rb +61 -0
- data/lib/pvdgm-bs-client/resources/statistics.rb +29 -0
- data/lib/pvdgm-bs-client/resources/tube.rb +124 -0
- data/spec/base_resource_spec.rb +208 -0
- data/spec/beanstalk_api_options_spec.rb +206 -0
- data/spec/spec_helper.rb +21 -0
- metadata +179 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
module JobPrompter
|
2
|
+
|
3
|
+
def job_id(allow_none=false)
|
4
|
+
return options[:job_id] if options[:job_id]
|
5
|
+
return options[:job_id] = ENV['JOB_ID'] if ENV['JOB_ID']
|
6
|
+
return options[:job_Id] = prompt_for_job_id(allow_none)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def prompt_for_job_id(allow_none)
|
12
|
+
puts
|
13
|
+
command = prompter.choose do | menu |
|
14
|
+
menu.prompt = "Which tube state do you want to peek into? "
|
15
|
+
menu.choice("Ready") { 'ready' }
|
16
|
+
menu.choice("Delayed") { 'delayed' }
|
17
|
+
menu.choice("Buried") { 'buried' }
|
18
|
+
end
|
19
|
+
|
20
|
+
result = get("beanstalk/tubes/#{Base64.urlsafe_encode64(tube_name)}/#{command}")
|
21
|
+
return -1 if result.has_key?('error')
|
22
|
+
|
23
|
+
# Build a menu of the job
|
24
|
+
puts
|
25
|
+
return prompter.choose do | menu |
|
26
|
+
menu.prompt = "Select the job: "
|
27
|
+
menu.choice("No Selection") { -1 } if allow_none
|
28
|
+
menu.choice("Job ID: #{result.keys.first}") { result.keys.first }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ToTubePrompter
|
2
|
+
|
3
|
+
def to_tube_name(allow_none=false)
|
4
|
+
return options[:to_tube] if options[:to_tube]
|
5
|
+
return options[:to_tube] = ENV['TO_TUBE_NAME'] if ENV['TO_TUBE_NAME']
|
6
|
+
return options[:to_tube] = prompt_for_to_tube_name(allow_none)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def prompt_for_to_tube_name(allow_none)
|
12
|
+
result = get("beanstalk/tubes")
|
13
|
+
|
14
|
+
# Build a menu of the tube names
|
15
|
+
puts
|
16
|
+
return prompter.choose do | menu |
|
17
|
+
menu.prompt = "Select the destination beanstalk tube: "
|
18
|
+
menu.choice("No Selection") { -1 } if allow_none
|
19
|
+
result.each do | tube_name |
|
20
|
+
menu.choice(tube_name) { tube_name }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module TubePrompter
|
2
|
+
|
3
|
+
def tube_name(allow_none=false)
|
4
|
+
return options[:tube] if options[:tube]
|
5
|
+
return options[:tube] = ENV['TUBE_NAME'] if ENV['TUBE_NAME']
|
6
|
+
return options[:tube] = prompt_for_tube_name(allow_none)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def prompt_for_tube_name(allow_none)
|
12
|
+
result = get("beanstalk/tubes")
|
13
|
+
|
14
|
+
# Build a menu of the tube names
|
15
|
+
puts
|
16
|
+
return prompter.choose do | menu |
|
17
|
+
menu.prompt = "Select the beanstalk tube: "
|
18
|
+
menu.choice("No Selection") { -1 } if allow_none
|
19
|
+
result.each do | tube_name |
|
20
|
+
menu.choice(tube_name) { tube_name }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Resources
|
2
|
+
|
3
|
+
class Job < BaseResource
|
4
|
+
include TubePrompter
|
5
|
+
include JobPrompter
|
6
|
+
|
7
|
+
def list
|
8
|
+
tube = tube_name
|
9
|
+
job = job_id
|
10
|
+
|
11
|
+
if job == -1
|
12
|
+
puts "\nNo jobs in state"
|
13
|
+
puts
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
result = get("beanstalk/jobs/#{job}")
|
18
|
+
|
19
|
+
if result.has_key?('error')
|
20
|
+
puts
|
21
|
+
puts result['error']
|
22
|
+
else
|
23
|
+
table = Terminal::Table.new headings: [ 'Id', 'Body' ] do | t |
|
24
|
+
t << [ result['job'].keys.first, result['job'].values.first ]
|
25
|
+
end
|
26
|
+
puts table
|
27
|
+
puts
|
28
|
+
|
29
|
+
table = Terminal::Table.new headings: [ 'Key', 'Value' ] do | t |
|
30
|
+
result['statistics'].each_pair do | key, value |
|
31
|
+
t << [ key, value ]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
puts table
|
35
|
+
end
|
36
|
+
puts
|
37
|
+
end
|
38
|
+
|
39
|
+
def destroy
|
40
|
+
tube = tube_name
|
41
|
+
job = job_id(true)
|
42
|
+
|
43
|
+
if job == -1
|
44
|
+
puts "\nNo jobs in state"
|
45
|
+
puts
|
46
|
+
return
|
47
|
+
end
|
48
|
+
|
49
|
+
result = delete("beanstalk/jobs/#{job}")
|
50
|
+
if result.has_key?('error')
|
51
|
+
puts
|
52
|
+
puts result['error']
|
53
|
+
else
|
54
|
+
puts result['job']
|
55
|
+
end
|
56
|
+
puts
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Resources
|
2
|
+
|
3
|
+
class Statistics < BaseResource
|
4
|
+
|
5
|
+
def list
|
6
|
+
|
7
|
+
filter = Proc.new do | key |
|
8
|
+
if options[:all_stats]
|
9
|
+
true
|
10
|
+
else
|
11
|
+
not key =~ /(^cmd-|^max-|^pid|^version|^rusage|^binlog|^id)/
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
result = get("beanstalk")
|
16
|
+
puts "\nBeanstalk global statistics"
|
17
|
+
table = Terminal::Table.new headings: [ 'Key', 'Value' ] do | t |
|
18
|
+
result.keys.sort.each do | key |
|
19
|
+
next unless filter.call(key)
|
20
|
+
t << [ key, result[key] ]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
puts table
|
24
|
+
puts
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Resources
|
2
|
+
|
3
|
+
class Tube < BaseResource
|
4
|
+
include TubePrompter
|
5
|
+
include ToTubePrompter
|
6
|
+
|
7
|
+
def list
|
8
|
+
result = get("beanstalk/tubes")
|
9
|
+
puts "\nBeanstalk tubes"
|
10
|
+
table = Terminal::Table.new headings: [ 'Tube Name' ] do | t |
|
11
|
+
result.each do | tube_name |
|
12
|
+
t << [ tube_name ]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
puts table
|
16
|
+
puts
|
17
|
+
end
|
18
|
+
|
19
|
+
def show
|
20
|
+
tube = tube_name
|
21
|
+
result = get("beanstalk/tubes/#{Base64.urlsafe_encode64(tube_name)}")
|
22
|
+
puts "\nStatistics for tube: #{tube}"
|
23
|
+
table = Terminal::Table.new headings: [ 'Key', 'Value' ] do | t |
|
24
|
+
result.each_pair do | key, value |
|
25
|
+
t << [ key, value ]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
puts table
|
29
|
+
puts
|
30
|
+
end
|
31
|
+
|
32
|
+
def ready
|
33
|
+
tube = tube_name
|
34
|
+
result = get("beanstalk/tubes/#{Base64.urlsafe_encode64(tube_name)}/ready")
|
35
|
+
if result.has_key?('error')
|
36
|
+
puts
|
37
|
+
puts result['error']
|
38
|
+
else
|
39
|
+
puts "\nJobs in the Ready state:"
|
40
|
+
table = Terminal::Table.new headings: [ 'Id', 'Body' ] do | t |
|
41
|
+
t << [ result.keys.first, result.values.first ]
|
42
|
+
end
|
43
|
+
puts table
|
44
|
+
end
|
45
|
+
puts
|
46
|
+
end
|
47
|
+
|
48
|
+
def delayed
|
49
|
+
tube = tube_name
|
50
|
+
result = get("beanstalk/tubes/#{Base64.urlsafe_encode64(tube_name)}/delayed")
|
51
|
+
if result.has_key?('error')
|
52
|
+
puts
|
53
|
+
puts result['error']
|
54
|
+
else
|
55
|
+
puts "\nJobs in the Delayed state:"
|
56
|
+
table = Terminal::Table.new headings: [ 'Id', 'Body' ] do | t |
|
57
|
+
t << [ result.keys.first, result.values.first ]
|
58
|
+
end
|
59
|
+
puts table
|
60
|
+
end
|
61
|
+
puts
|
62
|
+
end
|
63
|
+
|
64
|
+
def buried
|
65
|
+
tube = tube_name
|
66
|
+
result = get("beanstalk/tubes/#{Base64.urlsafe_encode64(tube_name)}/buried")
|
67
|
+
if result.has_key?('error')
|
68
|
+
puts
|
69
|
+
puts result['error']
|
70
|
+
else
|
71
|
+
puts "\nJobs in the Buried state:"
|
72
|
+
table = Terminal::Table.new headings: [ 'Id', 'Body' ] do | t |
|
73
|
+
t << [ result.keys.first, result.values.first ]
|
74
|
+
end
|
75
|
+
puts table
|
76
|
+
end
|
77
|
+
puts
|
78
|
+
end
|
79
|
+
|
80
|
+
def delete_one_job
|
81
|
+
tube = tube_name
|
82
|
+
result = delete("beanstalk/tubes/#{Base64.urlsafe_encode64(tube_name)}/one_job")
|
83
|
+
puts "\nJob deleted"
|
84
|
+
puts result['eat_job']
|
85
|
+
puts
|
86
|
+
end
|
87
|
+
|
88
|
+
def delete_all_jobs
|
89
|
+
tube = tube_name
|
90
|
+
result = delete("beanstalk/tubes/#{Base64.urlsafe_encode64(tube_name)}/all_jobs")
|
91
|
+
puts "\nAll jobs deleted"
|
92
|
+
puts result['eat_job']
|
93
|
+
puts
|
94
|
+
end
|
95
|
+
|
96
|
+
def move
|
97
|
+
tube = tube_name
|
98
|
+
to_tube = to_tube_name
|
99
|
+
result = delete("beanstalk/tubes/#{Base64.urlsafe_encode64(tube_name)}/move/#{Base64.urlsafe_encode64(to_tube)}")
|
100
|
+
puts "\nAll jobs moved from '#{tube}' to '#{to_tube}':"
|
101
|
+
puts result['eat_job']
|
102
|
+
puts
|
103
|
+
end
|
104
|
+
|
105
|
+
def kill_worker
|
106
|
+
tube = tube_name
|
107
|
+
result = delete("beanstalk/tubes/#{Base64.urlsafe_encode64(tube_name)}/worker")
|
108
|
+
puts "\nWorker killed:"
|
109
|
+
puts result['kill_worker']
|
110
|
+
puts
|
111
|
+
end
|
112
|
+
|
113
|
+
def kick
|
114
|
+
tube = tube_name
|
115
|
+
num_jobs = prompter.ask("Number of jobs to kick: ", Integer) { |q| q.default = 1 }
|
116
|
+
result = get("beanstalk/tubes/#{Base64.urlsafe_encode64(tube_name)}/kick/#{num_jobs}")
|
117
|
+
puts "\nKick #{num_jobs} in tube '#{tube}':"
|
118
|
+
puts result['kick']
|
119
|
+
puts
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BaseResource do
|
4
|
+
|
5
|
+
context 'Private Methods' do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@cut = BaseResource.new({ use_ssl: false, server: 'localhost:3000', api_token: 'sltc_api_token'})
|
9
|
+
end
|
10
|
+
|
11
|
+
context '#invoke_rest' do
|
12
|
+
|
13
|
+
it "should return a raw string if a non-JSON response is made from the block" do
|
14
|
+
result = @cut.send(:invoke_rest, false) do
|
15
|
+
"abc123"
|
16
|
+
end
|
17
|
+
expect(result).to eq("abc123")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should parse a response as JSON by default" do
|
21
|
+
result = @cut.send(:invoke_rest) do
|
22
|
+
'{ "message": "from the other side" }'
|
23
|
+
end
|
24
|
+
expect(result).to eq({ 'message' => 'from the other side' })
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should output an error message and exit if the JSON payload is invalid" do
|
28
|
+
expect {
|
29
|
+
@cut.send(:invoke_rest) do
|
30
|
+
'{ message => 3'
|
31
|
+
end
|
32
|
+
}.to raise_error(SystemExit)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should output an error message and exit if there is a non-403,404 exception" do
|
36
|
+
mock_response = double("Response")
|
37
|
+
mock_response.should_receive(:code).any_number_of_times.and_return(500)
|
38
|
+
mock_response.should_receive(:description).and_return("System error")
|
39
|
+
|
40
|
+
expect {
|
41
|
+
@cut.send(:invoke_rest) do
|
42
|
+
raise RestClient::Exception.new(mock_response, 500)
|
43
|
+
end
|
44
|
+
}.to raise_error(SystemExit)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should output an error message and exit if there is a 403 exception with no json" do
|
48
|
+
mock_response = double("Response")
|
49
|
+
mock_response.should_receive(:code).any_number_of_times.and_return(403)
|
50
|
+
mock_response.should_receive(:description).and_return("Some other error")
|
51
|
+
|
52
|
+
expect {
|
53
|
+
@cut.send(:invoke_rest, false) do
|
54
|
+
raise RestClient::Exception.new(mock_response, 403)
|
55
|
+
end
|
56
|
+
}.to raise_error(SystemExit)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should output an error message and exit if there is a 404 exception with error json" do
|
60
|
+
mock_response = double("Response")
|
61
|
+
mock_response.should_receive(:code).any_number_of_times.and_return(404)
|
62
|
+
mock_response.should_receive(:description).and_return("Some other error")
|
63
|
+
mock_response.should_receive(:http_body).and_return('{ "error" : "Error message" }')
|
64
|
+
|
65
|
+
expect {
|
66
|
+
@cut.send(:invoke_rest) do
|
67
|
+
raise RestClient::Exception.new(mock_response, 404)
|
68
|
+
end
|
69
|
+
}.to raise_error(SystemExit)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should output an error message and exit if there is a 403 exception with validation error json" do
|
73
|
+
mock_response = double("Response")
|
74
|
+
mock_response.should_receive(:code).any_number_of_times.and_return(403)
|
75
|
+
mock_response.should_receive(:description).and_return("Some other error")
|
76
|
+
mock_response.should_receive(:http_body).and_return('{ "validation_error" : { "name" : [ "Error 1", "Error 2" ] } }')
|
77
|
+
|
78
|
+
expect {
|
79
|
+
@cut.send(:invoke_rest) do
|
80
|
+
raise RestClient::Exception.new(mock_response, 403)
|
81
|
+
end
|
82
|
+
}.to raise_error(SystemExit)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should output an error message and exit if there is a 403 exception with some other error json" do
|
86
|
+
mock_response = double("Response")
|
87
|
+
mock_response.should_receive(:code).any_number_of_times.and_return(403)
|
88
|
+
mock_response.should_receive(:description).and_return("Some other error")
|
89
|
+
mock_response.should_receive(:http_body).and_return('{ "other_error" : { "name" : [ "Error 1", "Error 2" ] } }')
|
90
|
+
|
91
|
+
expect {
|
92
|
+
@cut.send(:invoke_rest) do
|
93
|
+
raise RestClient::Exception.new(mock_response, 403)
|
94
|
+
end
|
95
|
+
}.to raise_error(SystemExit)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should output an error message and exit if there is a 403 exception with invalid validation error json" do
|
99
|
+
mock_response = double("Response")
|
100
|
+
mock_response.should_receive(:code).any_number_of_times.and_return(403)
|
101
|
+
mock_response.should_receive(:description).and_return("Some other error")
|
102
|
+
mock_response.should_receive(:http_body).any_number_of_times.and_return('{ "validation_error" : { "name" : "Error 1", "Error 2" ] } }')
|
103
|
+
|
104
|
+
expect {
|
105
|
+
@cut.send(:invoke_rest) do
|
106
|
+
raise RestClient::Exception.new(mock_response, 403)
|
107
|
+
end
|
108
|
+
}.to raise_error(SystemExit)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should output an error message and exit if there is an unknown exception raised in the block" do
|
112
|
+
expect {
|
113
|
+
@cut.send(:invoke_rest) do
|
114
|
+
raise "Some unknown exception"
|
115
|
+
end
|
116
|
+
}.to raise_error(SystemExit)
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
context '#get' do
|
122
|
+
|
123
|
+
it "should return the response from the GET call" do
|
124
|
+
@cut.should_receive(:invoke_rest).with(true).and_yield
|
125
|
+
|
126
|
+
RestClient.should_receive(:get).
|
127
|
+
with('http://localhost:3000/tubes', {"Authorization"=>"Token token=\"sltc_api_token\"" }).
|
128
|
+
and_return({ hey: 'guy' })
|
129
|
+
|
130
|
+
expect(@cut.send(:get, 'tubes')).to eq({ hey: 'guy' })
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
context '#delete' do
|
136
|
+
|
137
|
+
it "should return the response from the DELETE call" do
|
138
|
+
@cut.should_receive(:invoke_rest).with(true).and_yield
|
139
|
+
|
140
|
+
RestClient.should_receive(:delete).
|
141
|
+
with('http://localhost:3000/tubes', {"Authorization"=>"Token token=\"sltc_api_token\"" }).
|
142
|
+
and_return({ hey: 'guy' })
|
143
|
+
|
144
|
+
expect(@cut.send(:delete, 'tubes')).to eq({ hey: 'guy' })
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
context '#post' do
|
150
|
+
|
151
|
+
it "should return the response from the POST call" do
|
152
|
+
@cut.should_receive(:invoke_rest).with(true).and_yield
|
153
|
+
|
154
|
+
RestClient.should_receive(:post).
|
155
|
+
with('http://localhost:3000/tubes',
|
156
|
+
{"param1"=>"pvalue"},
|
157
|
+
{"Authorization"=>"Token token=\"sltc_api_token\"" }).
|
158
|
+
and_return({ hey: 'guy' })
|
159
|
+
|
160
|
+
expect(@cut.send(:post, 'tubes', { 'param1' => 'pvalue' })).to eq({ hey: 'guy' })
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
context '#put' do
|
166
|
+
|
167
|
+
it "should return the response from the PUT call" do
|
168
|
+
@cut.should_receive(:invoke_rest).with(true).and_yield
|
169
|
+
|
170
|
+
RestClient.should_receive(:put).
|
171
|
+
with('http://localhost:3000/tubes',
|
172
|
+
{"param1"=>"pvalue"},
|
173
|
+
{"Authorization"=>"Token token=\"sltc_api_token\"" }).
|
174
|
+
and_return({ hey: 'guy' })
|
175
|
+
|
176
|
+
expect(@cut.send(:put, 'tubes', { 'param1' => 'pvalue' })).to eq({ hey: 'guy' })
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
context '#build_url' do
|
182
|
+
|
183
|
+
it "should return the properly formatted url" do
|
184
|
+
@cut.options[:use_ssl] = false
|
185
|
+
@cut.options[:server] = 'www.bob.com'
|
186
|
+
|
187
|
+
expect(@cut.send(:build_url, 'the_uri')).to eq('http://www.bob.com/the_uri')
|
188
|
+
|
189
|
+
@cut.options[:use_ssl] = true
|
190
|
+
|
191
|
+
expect(@cut.send(:build_url, 'the_uri')).to eq('https://www.bob.com/the_uri')
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
|
196
|
+
context '#authentication_headers' do
|
197
|
+
|
198
|
+
it "should return a hash containing the authentication headers" do
|
199
|
+
@cut.options[:api_token] = 'this is the token string'
|
200
|
+
expect(@cut.send(:authentication_headers)).
|
201
|
+
to eq( { 'Authorization' => "Token token=\"this is the token string\"" } )
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|