Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
H
heroku-buildpack-static
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Python-Dev
heroku-buildpack-static
Commits
ff012e3a
Commit
ff012e3a
authored
Jul 11, 2016
by
Terence Lee
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
rearchitect tests to support a real proxy API and routing frontend for SSL termination
parent
a60c0ad2
Changes
21
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
320 additions
and
54 deletions
+320
-54
index.html
spec/fixtures/proxies/public_html/foo/bar/index.html
+0
-1
index.html
spec/fixtures/proxies/public_html/foo/baz/index.html
+0
-1
simple_spec.rb
spec/simple_spec.rb
+42
-10
app_runner.rb
spec/support/app_runner.rb
+52
-25
buildpack_builder.rb
spec/support/buildpack_builder.rb
+9
-16
container_runner.rb
spec/support/container_runner.rb
+29
-0
Dockerfile
spec/support/docker/app/Dockerfile
+1
-1
init.sh
spec/support/docker/app/init.sh
+0
-0
Dockerfile
spec/support/docker/proxy/Dockerfile
+11
-0
Gemfile
spec/support/docker/proxy/Gemfile
+3
-0
Gemfile.lock
spec/support/docker/proxy/Gemfile.lock
+20
-0
config.ru
spec/support/docker/proxy/config.ru
+7
-0
Dockerfile
spec/support/docker/router/Dockerfile
+9
-0
nginx.conf
spec/support/docker/router/docker/conf/nginx.conf
+31
-0
.keep
spec/support/docker/router/docker/hook/.keep
+0
-0
docker_builder.rb
spec/support/docker_builder.rb
+23
-0
path_helper.rb
spec/support/path_helper.rb
+4
-0
proxy_builder.rb
spec/support/proxy_builder.rb
+18
-0
proxy_runner.rb
spec/support/proxy_runner.rb
+14
-0
router_builder.rb
spec/support/router_builder.rb
+18
-0
router_runner.rb
spec/support/router_runner.rb
+29
-0
No files found.
spec/fixtures/proxies/public_html/foo/bar/index.html
deleted
100644 → 0
View file @
a60c0ad2
api
spec/fixtures/proxies/public_html/foo/baz/index.html
deleted
100644 → 0
View file @
a60c0ad2
baz
spec/simple_spec.rb
View file @
ff012e3a
require
"fileutils"
require_relative
"spec_helper"
require_relative
"support/app_runner"
require_relative
"support/router_runner"
require_relative
"support/buildpack_builder"
require_relative
"support/router_builder"
require_relative
"support/proxy_builder"
require_relative
"support/proxy_runner"
require_relative
"support/path_helper"
RSpec
.
describe
"Simple"
do
before
(
:all
)
do
@debug
=
true
BuildpackBuilder
.
new
(
@debug
,
ENV
[
'CIRCLECI'
])
RouterBuilder
.
new
(
@debug
,
ENV
[
'CIRCLECI'
])
ProxyBuilder
.
new
(
@debug
,
ENV
[
"CIRCLE_CI"
])
end
after
do
app
.
destroy
end
let
(
:app
)
{
AppRunner
.
new
(
name
,
env
,
@debug
)
}
let
(
:proxy
)
{
nil
}
let
(
:app
)
{
AppRunner
.
new
(
name
,
proxy
,
env
,
@debug
)
}
let
(
:name
)
{
"hello_world"
}
let
(
:env
)
{
Hash
.
new
}
let
(
:name
)
{
"hello_world"
}
let
(
:env
)
{
Hash
.
new
}
it
"should serve out of public_html by default"
do
response
=
app
.
get
(
"/"
)
...
...
@@ -89,7 +96,7 @@ RSpec.describe "Simple" do
it
"should redirect and respect the http code & remove the port"
do
response
=
app
.
get
(
"/old/gone"
)
expect
(
response
.
code
).
to
eq
(
"302"
)
expect
(
response
[
"location"
]).
to
eq
(
"http://
#{
App
Runner
::
HOST_IP
}
/"
)
expect
(
response
[
"location"
]).
to
eq
(
"http://
#{
Router
Runner
::
HOST_IP
}
/"
)
end
context
"interpolation"
do
...
...
@@ -102,7 +109,7 @@ RSpec.describe "Simple" do
it
"should redirect using interpolated urls"
do
response
=
app
.
get
(
"/old/interpolation"
)
expect
(
response
.
code
).
to
eq
(
"302"
)
expect
(
response
[
"location"
]).
to
eq
(
"http://
#{
App
Runner
::
HOST_IP
}
/interpolation.html"
)
expect
(
response
[
"location"
]).
to
eq
(
"http://
#{
Router
Runner
::
HOST_IP
}
/interpolation.html"
)
end
end
end
...
...
@@ -133,6 +140,7 @@ RSpec.describe "Simple" do
include
PathHelper
let
(
:name
)
{
"proxies"
}
let
(
:proxy
)
{
true
}
let
(
:static_json_path
)
{
fixtures_path
(
"proxies/static.json"
)
}
let
(
:setup_static_json
)
do
Proc
.
new
do
|
path
|
...
...
@@ -141,7 +149,7 @@ RSpec.describe "Simple" do
{
"proxies": {
"/api/": {
"origin": "http://
#{
AppRunner
::
HOST_IP
}
:
#{
AppRunner
::
HOST_PORT
}#{
path
}
"
"origin": "http://
#{
@proxy_ip_address
}#{
path
}
"
}
}
}
...
...
@@ -151,6 +159,10 @@ STATIC_JSON
end
end
before
do
@proxy_ip_address
=
app
.
proxy
.
ip_address
end
after
do
FileUtils
.
rm
(
static_json_path
)
end
...
...
@@ -186,10 +198,10 @@ STATIC_JSON
{
"proxies": {
"/api/": {
"origin": "http://
#{
AppRunner
::
HOST_IP
}
:
#{
AppRunner
::
HOST_PORT
}
/foo"
"origin": "http://
#{
@proxy_ip_address
}
/foo"
},
"/proxy/": {
"origin": "http://
#{
AppRunner
::
HOST_IP
}
:
#{
AppRunner
::
HOST_PORT
}
/foo"
"origin": "http://
#{
@proxy_ip_address
}
/foo"
}
},
"routes": {
...
...
@@ -214,6 +226,14 @@ STATIC_JSON
end
context
"env var substitution"
do
let
(
:proxy
)
do
<<
CONFIG_RU
get "/foo/bar/" do
"api"
end
CONFIG_RU
end
before
do
File
.
open
(
static_json_path
,
"w"
)
do
|
file
|
file
.
puts
<<
STATIC_JSON
...
...
@@ -230,7 +250,7 @@ STATIC_JSON
let
(
:env
)
do
{
"PROXY_HOST"
=>
"
#{
AppRunner
::
HOST_IP
}
:
#{
AppRunner
::
HOST_PORT
}
"
"PROXY_HOST"
=>
"
${PROXY_IP_ADDRESS
}"
}
end
...
...
@@ -347,6 +367,17 @@ STATIC_JSON
let
(
:name
)
{
"proxies"
}
let
(
:static_json_path
)
{
fixtures_path
(
"proxies/static.json"
)
}
let
(
:proxy
)
do
<<
PROXY
get "/foo/bar/" do
"api"
end
get "/foo/baz/" do
"baz"
end
PROXY
end
let
(
:setup_static_json
)
do
Proc
.
new
do
|
path
|
File
.
open
(
static_json_path
,
"w"
)
do
|
file
|
...
...
@@ -354,7 +385,7 @@ STATIC_JSON
{
"proxies": {
"/api/": {
"origin": "http://
#{
AppRunner
::
HOST_IP
}
:
#{
AppRunner
::
HOST_PORT
}#{
path
}
"
"origin": "http://
#{
@proxy_ip_address
}#{
path
}
"
}
},
"headers": {
...
...
@@ -370,6 +401,7 @@ STATIC_JSON
end
before
do
@proxy_ip_address
=
app
.
proxy
.
ip_address
setup_static_json
.
call
(
"/foo/"
)
end
...
...
spec/support/app_runner.rb
View file @
ff012e3a
...
...
@@ -6,38 +6,56 @@ require "docker"
require
"concurrent/atomic/count_down_latch"
require_relative
"path_helper"
require_relative
"buildpack_builder"
require_relative
"router_runner"
require_relative
"../../scripts/config/lib/nginx_config_util"
class
AppRunner
include
PathHelper
def
self
.
boot2docker_ip
%x(boot2docker ip)
.
match
(
/([0-9]{1,3}\.){3}[0-9]{1,3}/
)[
0
]
rescue
Errno
::
ENOENT
end
HOST_PORT
=
"3000"
HOST_IP
=
boot2docker_ip
||
"127.0.0.1"
CONTAINER_PORT
=
"3000"
attr_reader
:proxy
def
initialize
(
fixture
,
env
=
{},
debug
=
false
)
def
initialize
(
fixture
,
proxy
=
nil
,
env
=
{},
debug
=
false
)
@run
=
false
@debug
=
debug
env
.
merge!
(
"STATIC_DEBUG"
=>
true
)
if
@debug
@tmpdir
=
nil
@proxy
=
nil
env
.
merge!
(
"STATIC_DEBUG"
=>
"true"
)
if
@debug
@container
=
Docker
::
Container
.
create
(
app_options
=
{
"name"
=>
"app"
,
"Image"
=>
BuildpackBuilder
::
TAG
,
# Env format is [KEY1=VAL1 KEY2=VAL2]
"Env"
=>
env
.
to_a
.
map
{
|
i
|
i
.
join
(
"="
)
},
"HostConfig"
=>
{
"Binds"
=>
[
"
#{
fixtures_path
(
fixture
)
}
:/src"
],
"PortBindings"
=>
{
"
#{
CONTAINER_PORT
}
/tcp"
=>
[{
"HostIp"
=>
HOST_IP
,
"HostPort"
=>
HOST_PORT
,
}]
}
"Binds"
=>
[
"
#{
fixtures_path
(
fixture
)
}
:/src"
]
}
)
}
if
proxy
app_options
[
"Links"
]
=
[
"proxy:proxy"
]
if
proxy
.
is_a?
(
String
)
@tmpdir
=
Dir
.
mktmpdir
File
.
open
(
"
#{
@tmpdir
}
/config.ru"
,
"w"
)
do
|
file
|
file
.
puts
%q{require "sinatra"}
file
.
puts
proxy
file
.
puts
"run Sinatra::Application"
end
end
@proxy
=
ProxyRunner
.
new
(
@tmpdir
)
@proxy
.
start
# need to interpolate the PROXY_IP_ADDRESS since env is a parameter to this constructor and
# the proxy app needs to be started first to get the ip address docker provides.
# it's a bootstrapping problem to do env var substitution
env
.
select
{
|
_
,
value
|
value
.
include?
(
"${PROXY_IP_ADDRESS}"
)
}.
each
do
|
key
,
value
|
env
[
key
]
=
NginxConfigUtil
.
interpolate
(
value
,
{
"PROXY_IP_ADDRESS"
=>
@proxy
.
ip_address
})
app_options
[
"Env"
]
=
env
.
to_a
.
map
{
|
i
|
i
.
join
(
"="
)
}
end
end
@app
=
Docker
::
Container
.
create
(
app_options
)
@router
=
RouterRunner
.
new
end
def
run
(
capture_io
=
false
)
...
...
@@ -47,16 +65,17 @@ class AppRunner
io_stream
=
StringIO
.
new
run_thread
=
Thread
.
new
{
latch
.
wait
(
0.5
)
yield
(
@container
)
yield
}
container_thread
=
Thread
.
new
{
@
container
.
tap
(
&
:start
).
attach
do
|
stream
,
chunk
|
@
app
.
tap
(
&
:start
).
attach
do
|
stream
,
chunk
|
io_message
=
"
#{
stream
}
:
#{
chunk
}
"
puts
io_message
if
@debug
io_stream
<<
io_message
if
capture_io
latch
.
count_down
if
chunk
.
include?
(
"Starting nginx..."
)
end
}
@router
.
start
retn
=
run_thread
.
value
...
...
@@ -66,7 +85,8 @@ class AppRunner
retn
end
ensure
@container
.
stop
@app
.
stop
@router
.
stop
container_thread
.
join
io_stream
.
close_write
@run
=
false
...
...
@@ -81,15 +101,22 @@ class AppRunner
end
def
destroy
@container
.
delete
(
force:
true
)
unless
@debug
if
@proxy
@proxy
.
stop
@proxy
.
destroy
end
@router
.
destroy
@app
.
delete
(
force:
true
)
ensure
FileUtils
.
rm_rf
(
@tmpdir
)
if
@tmpdir
end
private
def
get_retry
(
path
,
max_retries
)
network_retry
(
max_retries
)
do
uri
=
URI
(
path
)
uri
.
host
=
HOST_IP
if
uri
.
host
.
nil?
uri
.
port
=
HOST_PORT
if
(
uri
.
host
==
HOST_IP
&&
uri
.
port
!=
HOST_PORT
)
||
uri
.
port
.
nil?
uri
.
host
=
RouterRunner
::
HOST_IP
if
uri
.
host
.
nil?
uri
.
port
=
RouterRunner
::
HOST_PORT
if
(
uri
.
host
==
RouterRunner
::
HOST_IP
&&
uri
.
port
!=
RouterRunner
::
HOST_PORT
)
||
uri
.
port
.
nil?
uri
.
scheme
=
"http"
if
uri
.
scheme
.
nil?
Net
::
HTTP
.
get_response
(
URI
(
uri
.
to_s
))
...
...
spec/support/buildpack_builder.rb
View file @
ff012e3a
require
"docker"
require_relative
"path_helper"
require_relative
"docker_builder"
class
BuildpackBuilder
include
PathHelper
include
DockerBuilder
TAG
=
"hone/static:cedar-14"
def
initialize
(
debug
=
false
,
intermediates
=
false
)
@debug
=
debug
@intermediates
=
intermediates
@image
=
build_image
end
def
build_image
print_output
=
if
@debug
->
(
chunk
)
{
json
=
JSON
.
parse
(
chunk
)
puts
json
[
"stream"
]
}
else
->
(
chunk
)
{
nil
}
end
Docker
::
Image
.
build_from_dir
(
buildpack_path
.
to_s
,
't'
=>
TAG
,
'rm'
=>
!
@intermediates
,
'dockerfile'
=>
"spec/support/docker/Dockerfile"
,
&
print_output
)
@image
=
build
(
context:
buildpack_path
.
to_s
,
dockerfile:
docker_path
(
"app/Dockerfile"
).
relative_path_from
(
buildpack_path
),
tag:
TAG
,
intermediates:
@intermediates
,
debug:
@debug
)
end
end
spec/support/container_runner.rb
0 → 100644
View file @
ff012e3a
require
"fiber"
require
"docker"
class
ContainerRunner
attr_reader
:ip_address
def
initialize
(
options
)
@container
=
Docker
::
Container
.
create
(
options
)
@ip_address
=
nil
@thread
=
nil
end
def
start
@thread
=
Fiber
.
new
{
@container
.
start
Fiber
.
yield
@container
.
json
[
"NetworkSettings"
][
"IPAddress"
]
}
@ip_address
=
@thread
.
resume
end
def
stop
@container
.
stop
@thread
.
resume
if
@thread
.
alive?
end
def
destroy
@container
.
delete
(
force:
true
)
end
end
spec/support/docker/Dockerfile
→
spec/support/docker/
app/
Dockerfile
View file @
ff012e3a
...
...
@@ -14,6 +14,6 @@ EXPOSE 3000
WORKDIR
/app
COPY
./spec/support/docker/init.sh /usr/bin/init.sh
COPY
./spec/support/docker/
app/
init.sh /usr/bin/init.sh
ENTRYPOINT
["/usr/bin/init.sh"]
CMD
"/app/bin/boot"
spec/support/docker/init.sh
→
spec/support/docker/
app/
init.sh
View file @
ff012e3a
File moved
spec/support/docker/proxy/Dockerfile
0 → 100644
View file @
ff012e3a
FROM
ruby:2.3.1-alpine
RUN
mkdir
-p
/app
WORKDIR
/app
ADD
Gemfile* /app/
RUN
bundle
install
--path
/app/vendor/bundle
ADD
config.ru /app/config/
EXPOSE
80
CMD
bundle exec rackup /app/config/config.ru --host 0.0.0.0 -p 80
spec/support/docker/proxy/Gemfile
0 → 100644
View file @
ff012e3a
source
"https://rubygems.org"
gem
"sinatra"
spec/support/docker/proxy/Gemfile.lock
0 → 100644
View file @
ff012e3a
GEM
remote: https://rubygems.org/
specs:
rack (1.6.4)
rack-protection (1.5.3)
rack
sinatra (1.4.7)
rack (~> 1.5)
rack-protection (~> 1.4)
tilt (>= 1.3, < 3)
tilt (2.0.5)
PLATFORMS
ruby
DEPENDENCIES
sinatra
BUNDLED WITH
1.11.2
spec/support/docker/proxy/config.ru
0 → 100644
View file @
ff012e3a
require
"sinatra"
get
"/*"
do
"api"
end
run
Sinatra
::
Application
spec/support/docker/router/Dockerfile
0 → 100644
View file @
ff012e3a
FROM
matsumotory/ngx-mruby:latest
RUN
echo
$'
\n
US
\n
Texas
\n
Austin
\n
Heroku
\n\n
example.com
\n\n
'
\
| openssl req
-x509
-nodes
-days
365
-newkey
rsa:1024
\
-keyout
/etc/ssl/private/myssl.key
\
-out
/etc/ssl/certs/myssl.crt
RUN
mkdir
-p
/root/conf/
&&
\
touch
/root/conf/extend.conf
spec/support/docker/router/docker/conf/nginx.conf
0 → 100644
View file @
ff012e3a
user
daemon
;
daemon
off
;
master_process
off
;
worker_processes
1
;
error_log
stderr
;
events
{
worker_connections
1024
;
}
http
{
upstream
backend
{
server
app
:
3000
;
}
server
{
listen
80
;
listen
443
ssl
;
ssl_certificate
/etc/ssl/certs/myssl.crt
;
ssl_certificate_key
/etc/ssl/private/myssl.key
;
location
/
{
proxy_pass
http://backend
;
proxy_set_header
Host
$host
;
proxy_set_header
X-Real-IP
$remote_addr
;
proxy_set_header
X-Forwarded-For
$proxy_add_x_forwarded_for
;
proxy_set_header
X-Forwarded-Proto
$scheme
;
}
}
}
spec/support/docker/router/docker/hook/.keep
0 → 100644
View file @
ff012e3a
spec/support/docker_builder.rb
0 → 100644
View file @
ff012e3a
require
"docker"
module
DockerBuilder
def
build
(
context
:,
tag
:,
intermediates
:,
debug
:,
dockerfile:
nil
)
print_output
=
if
debug
->
(
chunk
)
{
json
=
JSON
.
parse
(
chunk
)
puts
json
[
"stream"
]
}
else
->
(
chunk
)
{
nil
}
end
options
=
{
't'
=>
tag
,
'rm'
=>
!
intermediates
,
}
options
[
"dockerfile"
]
=
dockerfile
Docker
::
Image
.
build_from_dir
(
context
,
options
,
&
print_output
)
end
end
spec/support/path_helper.rb
View file @
ff012e3a
...
...
@@ -6,6 +6,10 @@ module PathHelper
def
buildpack_path
(
*
path
)
__build_path
(
"../../"
,
*
path
)
end
def
docker_path
(
*
path
)
__build_path
(
"/docker"
,
*
path
)
end
private
def
__build_path
(
name
,
*
path
)
...
...
spec/support/proxy_builder.rb
0 → 100644
View file @
ff012e3a
require_relative
"docker_builder"
require_relative
"path_helper"
class
ProxyBuilder
include
DockerBuilder
include
PathHelper
TAG
=
"hone/static-proxy:latest"
def
initialize
(
debug
=
false
,
intermediates
=
false
)
@build
=
build
(
context:
docker_path
(
"proxy"
).
to_s
,
debug:
debug
,
tag:
TAG
,
intermediates:
intermediates
)
end
end
spec/support/proxy_runner.rb
0 → 100644
View file @
ff012e3a
require_relative
"proxy_builder"
require_relative
"container_runner"
class
ProxyRunner
<
ContainerRunner
def
initialize
(
config_ru
=
nil
)
options
=
{
"name"
=>
"proxy"
,
"Image"
=>
ProxyBuilder
::
TAG
}
options
[
"HostConfig"
]
=
{
"Binds"
=>
[
"
#{
config_ru
}
:/app/config/"
]
}
if
config_ru
super
(
options
)
end
end
spec/support/router_builder.rb
0 → 100644
View file @
ff012e3a
require_relative
"path_helper"
require_relative
"docker_builder"
class
RouterBuilder
include
PathHelper
include
DockerBuilder
TAG
=
"hone/static-router:latest"
def
initialize
(
debug
=
false
,
intermediates
=
false
)
@image
=
build
(
context:
docker_path
(
"/router"
).
to_s
,
tag:
TAG
,
intermediates:
intermediates
,
debug:
debug
)
end
end
spec/support/router_runner.rb
0 → 100644
View file @
ff012e3a
require_relative
"router_builder"
require_relative
"container_runner"
class
RouterRunner
<
ContainerRunner
def
self
.
boot2docker_ip
%x(boot2docker ip)
.
match
(
/([0-9]{1,3}\.){3}[0-9]{1,3}/
)[
0
]
rescue
Errno
::
ENOENT
end
CONTAINER_PORT
=
"80"
HOST_PORT
=
"80"
HOST_IP
=
boot2docker_ip
||
"127.0.0.1"
def
initialize
super
({
"name"
=>
"router"
,
"Image"
=>
RouterBuilder
::
TAG
,
"HostConfig"
=>
{
"Links"
=>
[
"app:app"
],
"PortBindings"
=>
{
"
#{
CONTAINER_PORT
}
/tcp"
=>
[{
"HostIp"
=>
HOST_IP
,
"HostPort"
=>
HOST_PORT
,
}]
}
}
})
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment