Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

convert cloud hosted custom module into a policy #1

Merged
merged 6 commits into from
Feb 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0.04 is an old version. Although the latest will be installed anyway unless we pin the version with ==.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. I think that is fine. This library is already installed in the apicast image so this just ensures it has the right version.

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