hoosegow 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'fileutils'
4
+ require_relative '../lib/hoosegow'
5
+
6
+ inmate_dir = '/hoosegow/inmate'
7
+ inmate_file = File.join(inmate_dir, 'inmate.rb')
8
+ if File.exist?(inmate_file)
9
+ system 'ls', '-l', inmate_dir
10
+ puts "ERROR: #{inmate_file} must not exist!"
11
+ exit 1
12
+ end
13
+
14
+ FileUtils.mkpath(inmate_dir)
15
+ File.write(inmate_file, <<INMATE)
16
+ class Hoosegow
17
+ module Inmate
18
+ def test_method(a, b)
19
+ yield :ok if block_given?
20
+ system "echo stdout in a child process"
21
+ $stderr.puts "stderr in this process"
22
+ raise b if a.to_s == 'raise'
23
+ a + b
24
+ end
25
+ end
26
+ end
27
+ INMATE
28
+
29
+ at_exit { FileUtils.remove_entry_secure(inmate_file) }
30
+
31
+ def main
32
+ each_hoosegow do |hoosegow|
33
+ try(hoosegow, :test_method, 1, 2)
34
+ try(hoosegow, :test_method, 1, 2) { |x| puts x }
35
+ try(hoosegow, :test_method, :raise, 'boom')
36
+ end
37
+ end
38
+
39
+ def try(hoosegow, method, *args, &block)
40
+ puts '-'*10
41
+ p :method => method, :args => args, :block? => !block.nil?
42
+ result = hoosegow.send(method, *args, &block)
43
+ p :result => result
44
+ rescue => error
45
+ p :error => error
46
+ #puts error.backtrace
47
+ end
48
+
49
+ def each_hoosegow
50
+ puts '*'*10, 'no_proxy => true'
51
+ yield Hoosegow.new(:no_proxy => true)
52
+ puts '*'*10, 'no_proxy => false'
53
+ with_fake_docker do |docker|
54
+ yield docker.inject(Hoosegow.new)
55
+ end
56
+ end
57
+
58
+ def with_fake_docker
59
+ yield FakeDocker.new
60
+ end
61
+
62
+ class FakeDocker
63
+ def inject(hoosegow)
64
+ docker = self
65
+ hoosegow.define_singleton_method(:docker) { docker }
66
+ hoosegow
67
+ end
68
+
69
+ def run_container(image_name, input_data)
70
+ Open3.popen3("./bin/hoosegow") do |stdin, stdout, stderr, wait_thr|
71
+ stdin.write(input_data)
72
+ stdin.close
73
+ ios = [stdout, stderr]
74
+ while ios.any? do
75
+ readers, _, _ = IO.select(ios, [], [], 1)
76
+ if readers.nil?
77
+ break unless wait_thr.alive?
78
+ else
79
+ readers.each do |reader|
80
+ begin
81
+ type = reader == stdout ? 1 : 2
82
+ data = reader.read_nonblock(2**15)
83
+ yield([type, data.bytesize].pack('CxxxN') + data)
84
+ rescue EOFError
85
+ ios.delete(reader)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ wait_thr.join
91
+ end
92
+ end
93
+ end
94
+
95
+ main
@@ -0,0 +1,109 @@
1
+ require_relative '../lib/hoosegow'
2
+
3
+ unless defined?(CONFIG)
4
+ begin
5
+ require_relative '../config'
6
+ rescue LoadError
7
+ CONFIG = {}
8
+ end
9
+ end
10
+
11
+ inmate_dir = File.join(File.dirname(__FILE__), 'test_inmate')
12
+ CONFIG[:inmate_dir] = inmate_dir
13
+ CONFIG[:image_name] ||= Hoosegow.new(CONFIG).image_name
14
+
15
+ describe Hoosegow::Docker do
16
+ context 'volumes' do
17
+ subject { described_class.new(:volumes => volumes) }
18
+
19
+ context 'unspecified' do
20
+ subject { described_class.new }
21
+ its(:volumes_for_create) { should be_empty }
22
+ its(:volumes_for_bind) { should be_empty }
23
+ end
24
+
25
+ context 'empty' do
26
+ let(:volumes) { {} }
27
+ its(:volumes_for_create) { should be_empty }
28
+ its(:volumes_for_bind) { should be_empty }
29
+ end
30
+
31
+ context 'with volumes' do
32
+ let(:volumes) { {
33
+ "/inside/path" => "/home/burke/data-for-container:rw",
34
+ "/other/path" => "/etc/shared-config",
35
+ } }
36
+ its(:volumes_for_create) { should == {
37
+ "/inside/path" => {},
38
+ "/other/path" => {},
39
+ } }
40
+ its(:volumes_for_bind) { should == [
41
+ "/home/burke/data-for-container:/inside/path:rw",
42
+ "/etc/shared-config:/other/path:ro",
43
+ ] }
44
+ end
45
+ end
46
+
47
+ context 'docker_url' do
48
+ it "correctly generates TCP urls" do
49
+ hoosegow = Hoosegow::Docker.new CONFIG.merge(:host => "1.1.1.1", :port => 1234)
50
+ expect(::Docker.url).to eq("tcp://1.1.1.1:1234")
51
+ end
52
+
53
+ it "correctly generates Unix urls" do
54
+ hoosegow = Hoosegow::Docker.new CONFIG.merge(:socket => "/path/to/socket")
55
+ expect(::Docker.url).to eq("unix:///path/to/socket")
56
+ end
57
+ end
58
+
59
+ context "callbacks" do
60
+ let(:cb) { lambda { |info| } }
61
+
62
+ it "calls after_create" do
63
+ expect(cb).to receive(:call).with { |*args| args.first.is_a? Hash }
64
+ docker = Hoosegow::Docker.new CONFIG.merge(:after_create => cb)
65
+ begin
66
+ docker.create_container CONFIG[:image_name]
67
+ ensure
68
+ docker.stop_container
69
+ docker.delete_container
70
+ end
71
+ end
72
+
73
+ it "calls after_start" do
74
+ expect(cb).to receive(:call).with { |*args| args.first.is_a? Hash }
75
+ docker = Hoosegow::Docker.new CONFIG.merge(:after_start => cb)
76
+ begin
77
+ docker.create_container CONFIG[:image_name]
78
+ docker.start_container
79
+ ensure
80
+ docker.stop_container
81
+ docker.delete_container
82
+ end
83
+ end
84
+
85
+ it "calls after_stop" do
86
+ expect(cb).to receive(:call).with { |*args| args.first.is_a? Hash }
87
+ docker = Hoosegow::Docker.new CONFIG.merge(:after_stop => cb)
88
+ begin
89
+ docker.create_container CONFIG[:image_name]
90
+ docker.start_container
91
+ ensure
92
+ docker.stop_container
93
+ docker.delete_container
94
+ end
95
+ end
96
+ end
97
+
98
+ context "image_exist?" do
99
+ it "returns true if the image exists" do
100
+ docker = Hoosegow::Docker.new CONFIG
101
+ expect(docker.image_exist?(CONFIG[:image_name])).to eq(true)
102
+ end
103
+
104
+ it "returns false if the image doesn't exist" do
105
+ docker = Hoosegow::Docker.new CONFIG
106
+ expect(docker.image_exist?("not_there")).to eq(false)
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,170 @@
1
+ require_relative '../lib/hoosegow'
2
+ require 'msgpack'
3
+
4
+ unless defined?(CONFIG)
5
+ begin
6
+ require_relative '../config'
7
+ rescue LoadError
8
+ CONFIG = {}
9
+ end
10
+ end
11
+
12
+ inmate_dir = File.join(File.dirname(__FILE__), 'test_inmate')
13
+ CONFIG[:inmate_dir] = inmate_dir
14
+ CONFIG[:image_name] ||= Hoosegow.new(CONFIG).image_name
15
+
16
+ describe Hoosegow do
17
+ context "no_proxy option" do
18
+ it "runs directly if set" do
19
+ hoosegow = Hoosegow.new CONFIG.merge(:no_proxy => true)
20
+ hoosegow.stub :proxy_send => "not raboof"
21
+ hoosegow.render_reverse("foobar").should eq("raboof")
22
+ end
23
+
24
+ it "runs via proxy if not set" do
25
+ hoosegow = Hoosegow.new CONFIG
26
+ hoosegow.stub :proxy_send => "not raboof"
27
+ hoosegow.render_reverse("foobar").should eq("not raboof")
28
+ hoosegow.cleanup
29
+ end
30
+ end
31
+
32
+ context "image_exists?" do
33
+ it "returns true for existing images" do
34
+ hoosegow = Hoosegow.new CONFIG
35
+ expect(hoosegow.image_exists?).to eq(true)
36
+ end
37
+
38
+ it "returns false for images that don't exist" do
39
+ hoosegow = Hoosegow.new CONFIG.merge(:image_name => "not_there")
40
+ expect(hoosegow.image_exists?).to eq(false)
41
+ end
42
+ end
43
+ end
44
+
45
+
46
+ describe Hoosegow::Protocol::Proxy do
47
+ subject(:proxy) { Hoosegow::Protocol::Proxy.new(:yield => block, :stdout => stdout, :stderr => stderr) }
48
+ let(:block) { double('block') }
49
+ let(:stdout) { double('stdout') }
50
+ let(:stderr) { double('stderr') }
51
+
52
+ it "encodes the method call" do
53
+ expect(MessagePack.unpack(proxy.encode_send(:method_name, ["arg1", {:name => 'value'}]))).to eq(['method_name', ["arg1", {'name' => 'value'}]])
54
+ end
55
+
56
+ it "decodes a yield" do
57
+ block.should_receive(:call).with('a', 'b')
58
+ proxy.receive(:stdout, MessagePack.pack([:yield, ['a', 'b']]))
59
+ end
60
+
61
+ context 'with no block' do
62
+ let(:block) { nil }
63
+ it "decodes a yield" do
64
+ proxy.receive(:stdout, MessagePack.pack([:yield, ['a', 'b']]))
65
+ end
66
+ end
67
+
68
+ it "decodes the return value" do
69
+ proxy.receive(:stdout, MessagePack.pack([:return, 1]))
70
+ expect(proxy.return_value).to eq(1)
71
+ end
72
+
73
+ it "decodes a known error class" do
74
+ expect { proxy.receive(:stdout, MessagePack.pack([:raise, {:class => 'RuntimeError', :message => 'I went boom'}])) }.to raise_error(Hoosegow::InmateRuntimeError, "RuntimeError: I went boom")
75
+ end
76
+
77
+ it "decodes an error" do
78
+ expect { proxy.receive(:stdout, MessagePack.pack([:raise, {:class => 'SomeInternalError', :message => 'I went boom'}])) }.to raise_error(Hoosegow::InmateRuntimeError, "SomeInternalError: I went boom")
79
+ end
80
+
81
+ it "decodes an error with a stack trace" do
82
+ expect { proxy.receive(:stdout, MessagePack.pack([:raise, {:class => 'SomeInternalError', :message => 'I went boom', :backtrace => ['file.rb:33:in `example\'']}])) }.to raise_error(Hoosegow::InmateRuntimeError, "SomeInternalError: I went boom\nfile.rb:33:in `example'")
83
+ end
84
+
85
+ it "decodes stdout" do
86
+ stdout.should_receive(:write).with('abc')
87
+ proxy.receive(:stdout, MessagePack.pack([:stdout, 'abc']))
88
+ end
89
+
90
+ it "decodes stderr" do
91
+ stderr.should_receive(:write).with('abc')
92
+ proxy.receive(:stderr, 'abc')
93
+ end
94
+
95
+ it "decodes the return value, across several reads" do
96
+ MessagePack.pack([:return, :abcdefghijklmn]).each_char do |char|
97
+ proxy.receive(:stdout, char)
98
+ end
99
+ expect(proxy.return_value).to eq('abcdefghijklmn')
100
+ end
101
+ end
102
+
103
+ describe Hoosegow::Protocol::Inmate do
104
+ it "calls appropriate render method" do
105
+ inmate = double('inmate')
106
+ inmate.should_receive(:render).with('foobar').
107
+ and_yield(:a, 1).
108
+ and_yield(:b, 2, 3).
109
+ and_return('raboof')
110
+
111
+ stdin = StringIO.new(MessagePack.pack(['render', ['foobar']]))
112
+ stdout = StringIO.new
113
+ stdout.set_encoding('BINARY')
114
+ r,w = IO.pipe
115
+
116
+ Hoosegow::Protocol::Inmate.run(:inmate => inmate, :stdin => stdin, :stdout => stdout, :intercepted => r)
117
+
118
+ expect(stdout.string).to eq( MessagePack.pack([:yield, [:a, 1]]) + MessagePack.pack([:yield, [:b, 2, 3]]) + MessagePack.pack([:return, 'raboof']) )
119
+ end
120
+
121
+ it "encodes exceptions" do
122
+ inmate = Object.new
123
+ def inmate.render(s) ; raise 'boom' ; end
124
+
125
+ stdin = StringIO.new(MessagePack.pack(['render', ['foobar']]))
126
+ stdout = StringIO.new
127
+ stdout.set_encoding('BINARY')
128
+ r,w = IO.pipe
129
+
130
+ Hoosegow::Protocol::Inmate.run(:inmate => inmate, :stdin => stdin, :stdout => stdout, :intercepted => r)
131
+
132
+ unpacked_type, unpacked_data = MessagePack.unpack(stdout.string)
133
+ expect(unpacked_type).to eq('raise')
134
+ expect(unpacked_data).to include('class' => 'RuntimeError')
135
+ expect(unpacked_data).to include('message' => 'boom')
136
+ expect(unpacked_data['backtrace']).to be_a(Array)
137
+ expect(unpacked_data['backtrace'].first).to eq("#{__FILE__}:#{__LINE__ - 14}:in `render'")
138
+ end
139
+
140
+ it "does not hang if stdin isn't closed" do
141
+ # Use a pipe so that we have a not-closed IO
142
+ stdin, feed_stdin = IO.pipe
143
+ feed_stdin.write(MessagePack.pack(['render', ['foobar']]))
144
+
145
+ inmate = double('inmate')
146
+ inmate.should_receive(:render).with('foobar').and_return('raboof')
147
+ stdout = StringIO.new
148
+ stdout.set_encoding('BINARY')
149
+ r,w = IO.pipe
150
+
151
+ timeout(2) { Hoosegow::Protocol::Inmate.run(:inmate => inmate, :stdin => stdin, :stdout => stdout, :intercepted => r) }
152
+ end
153
+
154
+ it "encodes stdout" do
155
+ inmate = double('inmate')
156
+ inmate.should_receive(:render).with('foobar').and_return('raboof')
157
+
158
+ stdin = StringIO.new(MessagePack.pack(['render', ['foobar']]))
159
+ stdout = StringIO.new
160
+ stdout.set_encoding('BINARY')
161
+ r,w = IO.pipe
162
+ w.puts "STDOUT from somewhere"
163
+
164
+ Hoosegow::Protocol::Inmate.run(:inmate => inmate, :stdin => stdin, :stdout => stdout, :intercepted => r)
165
+
166
+ encoded_stdout = MessagePack.pack([:stdout, "STDOUT from somewhere\n"])
167
+ encoded_return = MessagePack.pack([:return, 'raboof'])
168
+ expect([encoded_stdout+encoded_return, encoded_return+encoded_stdout]).to include(stdout.string)
169
+ end
170
+ end
@@ -0,0 +1,7 @@
1
+ class Hoosegow
2
+ module Inmate
3
+ def render_reverse(s)
4
+ s.reverse
5
+ end
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hoosegow
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Ben Toews
8
+ - Matt Burke
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-10-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: 10.3.2
21
+ - - "~>"
22
+ - !ruby/object:Gem::Version
23
+ version: '10.3'
24
+ type: :development
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ version: 10.3.2
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.3'
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 2.14.1
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '2.14'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 2.14.1
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.14'
54
+ - !ruby/object:Gem::Dependency
55
+ name: msgpack
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 0.5.6
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '0.5'
64
+ type: :runtime
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 0.5.6
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '0.5'
74
+ - !ruby/object:Gem::Dependency
75
+ name: yajl-ruby
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 1.1.0
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '1.1'
84
+ type: :runtime
85
+ prerelease: false
86
+ version_requirements: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 1.1.0
91
+ - - "~>"
92
+ - !ruby/object:Gem::Version
93
+ version: '1.1'
94
+ - !ruby/object:Gem::Dependency
95
+ name: docker-api
96
+ requirement: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: 1.13.6
101
+ type: :runtime
102
+ prerelease: false
103
+ version_requirements: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - "~>"
106
+ - !ruby/object:Gem::Version
107
+ version: 1.13.6
108
+ description: Hoosegow provides an RPC layer on top of Docker containers so that you
109
+ can isolate unsafe parts of your application.
110
+ email: mastahyeti@github.com
111
+ executables:
112
+ - hoosegow
113
+ extensions: []
114
+ extra_rdoc_files: []
115
+ files:
116
+ - Dockerfile
117
+ - Gemfile
118
+ - LICENSE
119
+ - README.md
120
+ - Rakefile
121
+ - bin/hoosegow
122
+ - docs/dispatch-seq.txt
123
+ - docs/dispatch.md
124
+ - docs/dispatch.png
125
+ - hoosegow.gemspec
126
+ - lib/hoosegow.rb
127
+ - lib/hoosegow/docker.rb
128
+ - lib/hoosegow/exceptions.rb
129
+ - lib/hoosegow/image_bundle.rb
130
+ - lib/hoosegow/protocol.rb
131
+ - script/proxy-integration-test
132
+ - spec/hoosegow_docker_spec.rb
133
+ - spec/hoosegow_spec.rb
134
+ - spec/test_inmate/inmate.rb
135
+ homepage: https://github.com/github/hoosegow
136
+ licenses:
137
+ - MIT
138
+ metadata: {}
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: 1.9.3
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubyforge_project:
155
+ rubygems_version: 2.2.2
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: A Docker jail for ruby code
159
+ test_files: []