Skip to content

Commit

Permalink
Merge pull request #1 from 3scale/apicast-policy-chain
Browse files Browse the repository at this point in the history
convert cloud hosted custom module into a policy
  • Loading branch information
mikz authored Feb 19, 2018
2 parents 3183b1e + 5925384 commit de631c1
Show file tree
Hide file tree
Showing 23 changed files with 447 additions and 129 deletions.
61 changes: 57 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,79 @@
version: 2
jobs:
build:
deploy:
docker:
- image: quay.io/3scale/s2i:v1.1.5-ce
- image: quay.io/3scale/s2i:v1.1.8-ce
environment:
APICAST_VERSION: master
environment:
REGISTRY: "${DOCKER_REGISTRY}/3scale"
DOCKER_REGISTRY: "quay.io"
working_directory: /root/apicast-cloud
steps:
- checkout
- setup_remote_docker:
reusable: true
- run: cd apicast && make builder test
- run: cd apicast && make build test
- run: cd mapping-service && make build IMAGE_TAG=${CIRCLE_TAG:-${CIRCLE_BRANCH}}
- deploy:
name: Push docker image
command: |
if [ -n "${CIRCLE_TAG}" ] || [ -n "${CIRCLE_BRANCH}" ]; then
docker login -u="${DOCKER_USER}" -p="${DOCKER_PASS}" "${DOCKER_REGISTRY}"
docker login -u="${DOCKER_USERNAME}" --password-stdin "${DOCKER_REGISTRY}" <<< "${DOCKER_PASSWORD}"
(cd apicast && make push REMOTE_IMAGE_NAME=apicast-cloud-hosted:apicast-${CIRCLE_TAG:-${CIRCLE_BRANCH}})
(cd mapping-service && make push IMAGE_TAG=${CIRCLE_TAG:-${CIRCLE_BRANCH}})
fi
apicast-test:
docker:
- image: quay.io/3scale/s2i-openresty-centos7:1.13.6.1-rover6
environment:
TEST_NGINX_BINARY: openresty
LUA_BIN_PATH: /opt/app-root/bin
working_directory: /opt/app-root/apicast-cloud-hosted
steps:
- checkout
- restore_cache:
keys:
- apicast-cloud-hosted-rover-{{ arch }}-{{ checksum "apicast/Roverfile.lock" }}
- apicast-cloud-hosted-rover-{{ arch }}-{{ .Branch }}
- apicast-cloud-hosted-rover-{{ arch }}-master
- run: cd apicast && rover install
- save_cache:
key: apicast-cloud-hosted-rover-{{ arch }}-{{ checksum "apicast/Roverfile.lock" }}
paths:
- apicast/lua_modules
- restore_cache:
keys:
- apicast-cloud-hosted-cpanm-{{ arch }}-{{ checksum "apicast/cpanfile" }}
- apicast-cloud-hosted-cpanm-{{ arch }}-{{ .Branch }}
- apicast-cloud-hosted-{{ arch }}-master
- run: /usr/libexec/s2i/entrypoint cpanm --notest --installdeps ./apicast
- save_cache:
key: apicast-cloud-hosted-cpanm-{{ arch }}-{{ checksum "apicast/cpanfile" }}
paths:
- ~/perl5
- run: mkdir -p apicast/tmp/junit
- run:
command: cd apicast && /usr/libexec/s2i/entrypoint sh -c 'rover exec prove --harness=TAP::Harness::JUnit $(circleci tests glob "t/**/*.t" | circleci tests split --split-by=timings --timings-type=filename)'
environment:
JUNIT_OUTPUT_FILE: tmp/junit/prove.xml
TEST_NGINX_ERROR_LOG: tmp/prove.log
- store_artifacts:
path: apicast/tmp
destination: tmp
- store_test_results:
path: apicast/tmp/junit
workflows:
version: 2
build_and_test:
jobs:
- apicast-test
- deploy:
context: org-global
requires:
- apicast-test

# TODO: remove this once CircleCI 2.0 supports building from tags
# https://discuss.circleci.com/t/git-tag-deploys-in-2-0/9493/5
deployment:
Expand Down
1 change: 0 additions & 1 deletion apicast/.env

This file was deleted.

2 changes: 2 additions & 0 deletions apicast/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lua_modules
t/servroot
42 changes: 0 additions & 42 deletions apicast/.s2i/bin/assemble

This file was deleted.

23 changes: 0 additions & 23 deletions apicast/.s2i/bin/assemble-runtime

This file was deleted.

1 change: 1 addition & 0 deletions apicast/.s2i/environment
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
APICAST_LOADED_ENVIRONMENTS=cloud_hosted
2 changes: 2 additions & 0 deletions apicast/.s2iignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.idea
*.swp
lua_modules
19 changes: 15 additions & 4 deletions apicast/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.DEFAULT_GOAL := help

APICAST_VERSION ?= v3.0.0
APICAST_VERSION ?= v3.2.0-alpha2
RUNTIME_IMAGE ?= quay.io/3scale/apicast:$(APICAST_VERSION)
BUILDER_IMAGE ?= $(RUNTIME_IMAGE)-builder
IMAGE_TAG ?= $(APICAST_VERSION)
Expand All @@ -9,15 +9,23 @@ REGISTRY ?= quay.io/3scale
LOCAL_IMAGE_NAME ?= $(IMAGE_NAME):$(IMAGE_TAG)
REMOTE_IMAGE_NAME ?= $(IMAGE_NAME):$(IMAGE_TAG)
LOG_LEVEL ?= notice
LOGLEVEL ?= 2
PULL_POLICY ?= always

build: ## Build the image
s2i build . $(BUILDER_IMAGE) $(LOCAL_IMAGE_NAME) --environment-file=.env --assemble-user=root --runtime-image=$(RUNTIME_IMAGE)
s2i build . $(BUILDER_IMAGE) $(LOCAL_IMAGE_NAME) \
--runtime-image=$(RUNTIME_IMAGE) --loglevel=$(LOGLEVEL) \
--pull-policy=$(PULL_POLICY) --runtime-pull-policy=$(PULL_POLICY)

builder: ## Build the builder image
s2i build . $(BUILDER_IMAGE) $(LOCAL_IMAGE_NAME) \
--loglevel=$(LOGLEVEL) --pull-policy=$(PULL_POLICY)

test: ## Run tests (try to start the image)
docker run -it --rm $(LOCAL_IMAGE_NAME) bin/apicast -d
docker run -it --rm $(LOCAL_IMAGE_NAME) bin/apicast --daemon --dev

start: ## Start APIcast
docker run -it --publish 8080:8080 --env-file=.env --env APICAST_LOG_LEVEL=$(LOG_LEVEL) --rm $(LOCAL_IMAGE_NAME) bin/apicast
docker run -it --publish 8080:8080 --env-file=.env --env APICAST_LOG_LEVEL=$(LOG_LEVEL) --rm $(LOCAL_IMAGE_NAME) bin/apicast --lazy --dev

push: ## Push image to the registry
docker tag $(LOCAL_IMAGE_NAME) $(REGISTRY)/$(REMOTE_IMAGE_NAME)
Expand All @@ -27,3 +35,6 @@ push: ## Push image to the registry
# Check http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
help: ## Print this help
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

clean:
rm -rf lua_modules
10 changes: 10 additions & 0 deletions apicast/Roverfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
luarocks {
group 'production' {
module { 'lua-resty-iputils' },
},

group { 'development', 'test' } {
module { 'apicast' },
module { 'lua-resty-repl' },
}
}
14 changes: 14 additions & 0 deletions apicast/Roverfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apicast scm-1|348144131998f97e2190fa3b3f1c8ba70d2339d3|development,test
argparse 0.5.0-1||development,test
inspect 3.1.1-0||development,test
liquid scm-1|811a73e38fdd9fdea116be4baf310ca326b96c77|development,test
lua-resty-env 0.4.0-1||development,test
lua-resty-execvp 0.1.0-1||development,test
lua-resty-http 0.12-0||development,test
lua-resty-iputils 0.3.0-1||production
lua-resty-jwt 0.1.11-0||development,test
lua-resty-repl 0.0.6-0|3878f41b7e8f97b1c96919db19dbee9496569dda|development,test
lua-resty-url 0.2.0-1||development,test
luafilesystem 1.7.0-2||development,test
penlight 1.5.4-1||development,test
router 2.1-0||development,test
13 changes: 13 additions & 0 deletions apicast/config/cloud_hosted.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
local PolicyChain = require('apicast.policy_chain')
local policy_chain = context.policy_chain

if not arg then -- {arg} is defined only when executing the CLI
policy_chain:insert(PolicyChain.load_policy('cloud_hosted.rate_limit', '0.1', {
limit = os.getenv('RATE_LIMIT') or 5,
burst = os.getenv('RATE_LIMIT_BURST') or 50 }), 1)
policy_chain:insert(PolicyChain.load_policy('cloud_hosted.balancer_blacklist', '0.1'), 1)
end

return {
policy_chain = policy_chain
}
1 change: 1 addition & 0 deletions apicast/cpanfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
requires 'Test::APIcast', '0.04';
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ local iputils = require("resty.iputils")
local default_balancer = require('resty.balancer.round_robin').call
local resty_balancer = require('resty.balancer')

local _M = { _VERSION = '0.0', _NAME = 'IP Blacklist' }
local _M = require('apicast.policy').new('IP Blacklist', '0.1')
local mt = { __index = _M }

local ipv4 = {
Expand All @@ -25,7 +25,6 @@ for _,cidrs in pairs(ipv4) do
end
end


function _M.new()
return setmetatable({}, mt)
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
return require('balancer_blacklist')
1 change: 1 addition & 0 deletions apicast/policies/cloud_hosted.rate_limit/0.1/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
return require('rate_limit')
69 changes: 69 additions & 0 deletions apicast/policies/cloud_hosted.rate_limit/0.1/rate_limit.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
local tonumber = tonumber

local limit_req = require "resty.limit.req"

local _M = require('apicast.policy').new('Rate Limit', '0.1')

local new = _M.new

local function new_limiter(limit, burst)
local limiter, err = limit_req.new("rate_limit_req_store", tonumber(limit), tonumber(burst) or 0)

if limiter then
ngx.log(ngx.NOTICE, 'rate limit: ', limit, '/s', ' burst: ', burst or limit, '/s')
elseif not arg then -- if not being loaded on the CLI
ngx.log(ngx.ERR, 'error loading rate limiter: ', err)
end

return limiter
end

local empty = {}

function _M.new(configuration)
local policy = new(configuration)
local config = configuration or empty

local limit = config.limit
local burst = config.burst

policy.status = config.status

if limit then
policy.limiter = new_limiter(limit, burst)
else
ngx.log(ngx.NOTICE, 'rate limit not set')
end

return policy
end

function _M:access(context)
local limiter = self.limiter

if not limiter then return nil, 'missing limiter' end

local key = context.host or ngx.var.host
local status = self.status or 503

local delay, err = limiter:incoming(key, true)

if not delay then
ngx.log(ngx.WARN, err, ' request over limit, key: ', key)
if err == "rejected" then
return ngx.exit(status)
end
ngx.log(ngx.ERR, "failed to limit req: ", err)
return ngx.exit(500)
end

if delay >= 0.001 then
local excess = err

ngx.log(ngx.WARN, 'delaying request: ', key, ' for ', delay, 's, excess: ', excess)
ngx.sleep(delay)
end
end


return _M
1 change: 1 addition & 0 deletions apicast/policies/cloud_hosted.upstream/0.1/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
return require('upstream')
37 changes: 37 additions & 0 deletions apicast/policies/cloud_hosted.upstream/0.1/upstream.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
local resty_resolver = require('resty.resolver')
local resty_url = require('resty.url')
local format = string.format

local _M = require('apicast.policy').new('Upstream', '0.1')

local new = _M.new

local empty = {}
function _M.new(configuration)
local policy = new(configuration)
local config = configuration or empty

local url = resty_url.parse(config.url) or empty
local host = config.host or url.host

policy.host = host
policy.url = url

return policy
end

function _M:content()
local url = self.url
local host = self.host

ngx.ctx.upstream = resty_resolver:instance():get_servers(url.host, { port = url.port })
ngx.var.proxy_pass = format('%s://upstream%s', url.scheme, url.path or '')
ngx.req.set_header('Host', host or ngx.var.host)

if not ngx.headers_sent then
ngx.exec("@upstream")
end
end


return _M
Loading

0 comments on commit de631c1

Please sign in to comment.