hoosegow 1.2.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.
@@ -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: []