Skip to content

Commit

Permalink
Merge pull request #214 from 3scale/track-gateway-usage
Browse files Browse the repository at this point in the history
send User-Agent with platform and system information
  • Loading branch information
mikz authored Jan 11, 2017
2 parents 771ffd3 + f21c89f commit 138d23c
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Added
- A CHANGELOG.md to track important changes
- User-Agent header with APIcast version and system information [PR #214](https://github.com/3scale/apicast/pull/214)

### Changed
- Require openresty 1.11.2 [PR #194](https://github.com/3scale/apicast/pull/194)
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ test-builder-image: builder-image clean ## Smoke test the builder image. Pass an
@echo -e $(SEPARATOR)
$(DOCKER_COMPOSE) run --rm test curl --fail -X POST http://gateway:8090/boot
@echo -e $(SEPARATOR)
$(DOCKER_COMPOSE) run --rm -e THREESCALE_PORTAL_ENDPOINT=https://echo-api.3scale.net gateway /opt/app/libexec/boot | grep lua-resty-http
$(DOCKER_COMPOSE) run --rm -e THREESCALE_PORTAL_ENDPOINT=https://echo-api.3scale.net gateway /opt/app/libexec/boot | grep 'APIcast/'
@echo -e $(SEPARATOR)

test-runtime-image: export IMAGE_NAME = apicast-release-test
Expand Down
18 changes: 15 additions & 3 deletions apicast/conf.d/apicast.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
set_by_lua $deployment 'return os.getenv("THREESCALE_DEPLOYMENT_ENV");';
set_by_lua $user_agent 'return require("user_agent")()';
set_by_lua_block $deployment {
local user_agent = require('user_agent')
return user_agent.platform() .. '+' .. user_agent.deployment()
}

# TODO: enable in the future when we support SSL
# ssl_certificate_by_lua_block { require('module').call() }
Expand All @@ -17,7 +21,8 @@ location = /threescale_authrep {
proxy_http_version 1.1;
proxy_pass $backend_endpoint$path;
proxy_set_header Host "$backend_host";
proxy_set_header X-3scale-User-Agent "nginx$deployment";
proxy_set_header User-Agent "$user_agent";
proxy_set_header X-3scale-User-Agent "$deployment";
proxy_set_header X-3scale-Version "$version";
proxy_set_header Connection "";

Expand Down Expand Up @@ -88,6 +93,9 @@ location = /_threescale/oauth_store_token {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host "$backend_host";
proxy_set_header User-Agent "$user_agent";
proxy_set_header X-3scale-User-Agent "$deployment";
proxy_set_header X-3scale-Version "$version";

proxy_pass $backend_endpoint/services/$service_id/oauth_access_tokens.xml?$backend_authentication_type=$backend_authentication_value;
}
Expand All @@ -98,14 +106,18 @@ location = /_threescale/check_credentials {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host "$backend_host";
proxy_set_header User-Agent "$user_agent";
proxy_set_header X-3scale-User-Agent "$deployment";
proxy_set_header X-3scale-Version "$version";

proxy_pass $backend_endpoint/transactions/oauth_authorize.xml?$backend_authentication_type=$backend_authentication_value&service_id=$service_id&$args;
}

location = /threescale_oauth_authrep {
internal;
proxy_set_header Host "$backend_host";
proxy_set_header X-3scale-User-Agent "nginx$deployment";
proxy_set_header User-Agent "$user_agent";
proxy_set_header X-3scale-User-Agent "$deployment";
proxy_set_header X-3scale-Version "$version";
proxy_set_header X-3scale-OAuth2-Grant-Type "authorization_code";

Expand Down
6 changes: 5 additions & 1 deletion apicast/src/apicast.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ local tonumber = tonumber
local math = math
local getenv = os.getenv
local reload_config = util.env_enabled('APICAST_RELOAD_CONFIG')
local user_agent = require('user_agent')

local _M = {
_VERSION = '0.1'
_VERSION = '2.0',
_NAME = 'APIcast'
}

local missing_configuration = getenv('APICAST_MISSING_CONFIGURATION') or 'log'
Expand All @@ -28,6 +30,8 @@ local function handle_missing_configuration(err)
end

function _M.init()
user_agent.cache()

math.randomseed(ngx.now())
-- First calls to math.random after a randomseed tend to be similar; discard them
for _=1,3 do math.random() end
Expand Down
3 changes: 3 additions & 0 deletions apicast/src/configuration_loader/remote_v1.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ local resty_url = require 'resty.url'
local http = require "resty.http"
local configuration = require 'configuration'
local util = require 'util'
local user_agent = require 'user_agent'

local _M = {
version = '0.1'
Expand Down Expand Up @@ -90,6 +91,8 @@ function _M.download(endpoint, _)
headers['Authorization'] = "Basic " .. ngx.encode_base64(concat({ user or '', pass or '' }, ':'))
end

headers['User-Agent'] = user_agent()

-- TODO: this does not fully implement HTTP spec, it first should send
-- request without Authentication and then send it after gettting 401

Expand Down
23 changes: 23 additions & 0 deletions apicast/src/module.lua
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ function _M.call(self, phase, ...)
end

local cache = {}

function _M.flush()
cache = {}
end

local prequire = function(file)

if cache[file] then
Expand All @@ -60,6 +65,11 @@ local prequire = function(file)
ok, ret = pcall(dofile, file)
end

if type(ret) == 'userdata' then
ngx.log(ngx.WARN, 'cyclic require detected: ', debug.traceback())
return false, ret
end

if ok then
cache[file] = ret
else
Expand Down Expand Up @@ -105,4 +115,17 @@ function _M.load(name, phase)
return nil, 'could not load plugin'
end

function _M:require()
local name = self.name

if not name then
return nil, 'not initialized'
end

local ok, ret = prequire(name)

if ok and ret then return ret
else return ok, ret end
end

return _M
75 changes: 75 additions & 0 deletions apicast/src/user_agent.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
local ffi = require 'ffi'
local module = require 'module'

local setmetatable = setmetatable

local _M = {
_VERSION = '2.0'
}

function _M.deployment()
return _M.threescale_deployment_env or 'unknown'
end

-- User-Agent: <product> / <product-version> <comment>
-- User-Agent: Mozilla/<version> (<system-information>) <platform> (<platform-details>) <extensions>

function _M.call()
return 'APIcast/' .. _M._VERSION .. ' (' .. _M.system_information() .. ') ' .. _M.platform()
end

function _M.system_information()
return ffi.os .. '; ' .. ffi.arch .. '; env:' .. _M.deployment()
end

function _M.platform()
local m = module.new()
local table, err = m:require()
local version = table and table._VERSION
local name = table and table._NAME or m.name

if not name then
return nil, 'missing module name'
end

if not table and err then
return nil, err
end

if version then
return name ..'/'.. version
else
return name
end
end

local mt = {
__call = _M.call,
__tostring = _M.call
}

function _M.reset()
local env = {
threescale_deployment_env = os.getenv('THREESCALE_DEPLOYMENT_ENV')
}

mt.__index = env

_M.env = env

mt.__call = _M.call
mt.__tostring = _M.call
end

function _M.cache()
_M.reset()

local user_agent = _M.call()

mt.__call = function() return user_agent end
mt.__tostring = mt.__call
end

setmetatable(_M, mt)

return _M
12 changes: 1 addition & 11 deletions bin/busted
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
#!/usr/bin/env sh

exec resty -e "$(cat <<LUA
if ngx ~= nil then
ngx.exit = function()end
end
pcall(require, 'luarocks.loader')
-- Busted command-line runner
require 'busted.runner'({ standalone = false })
LUA
)"
exec resty bin/busted.lua "$@"
8 changes: 8 additions & 0 deletions bin/busted.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
if ngx ~= nil then
ngx.exit = function()end
end

pcall(require, 'luarocks.loader')

-- Busted command-line runner
require 'busted.runner'({ standalone = false })
15 changes: 15 additions & 0 deletions spec/module_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('module', function()
end)

describe('.call', function()
after_each(function() module.flush() end)

it('returns', function()
package.loaded['foobar'] = { phase = function() return 'foobar' end }
Expand All @@ -24,4 +25,18 @@ describe('module', function()
end)

end)

describe(':require', function()
it('returns a module', function()
local foobar = { _VERSION = '1.1', _NAME = 'Foo Bar' }
package.loaded['foobar'] = foobar

local m = module.new('foobar')

local mod, err = m:require()

assert.equal(mod, foobar)
assert.falsy(err)
end)
end)
end)
71 changes: 71 additions & 0 deletions spec/user_agent_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
local user_agent = require 'user_agent'
local ffi = require("ffi")

describe('3scale', function()
before_each(function() user_agent.reset() end)

describe('.deployment', function()
it('reads from environment', function()
stub(os, 'getenv').on_call_with('THREESCALE_DEPLOYMENT_ENV').returns('foobar')

user_agent.reset()

assert.same('foobar', user_agent.deployment())
end)

it('uses internal structure', function()
user_agent.env.threescale_deployment_env = 'bar'

assert.same('bar', user_agent.deployment())
end)
end)

describe('.user_agent', function()
-- User-Agent: <product> / <product-version> <comment>
-- User-Agent: Mozilla/<version> (<system-information>) <platform> (<platform-details>) <extensions>

it('matches common format', function()

user_agent.env.threescale_deployment_env = 'production'

assert.match('APIcast/' .. user_agent._VERSION, user_agent.call())
end)

it('includes system information', function()
assert.match('(' .. user_agent.system_information() .. ')', user_agent.call())
end)

it('includes platform information', function()
assert.match(' ' .. user_agent.platform(), user_agent.call())
end)

it('works as tostring', function()
assert.equal(user_agent.call(), tostring(user_agent))
end)

it('works as function', function()
assert.equal(user_agent.call(), user_agent())
end)
end)


describe('.system_information', function()
it('includes os information', function()
assert.match(ffi.os ..'; ' .. ffi.arch, user_agent.system_information())
end)

it('includes deployment information', function()
user_agent.env.threescale_deployment_env = 'foobar'
assert.match(' env:foobar', user_agent.system_information())
end)
end)

describe('.platform', function()
it('includes os information', function()
local apicast = require('apicast')

assert.same('APIcast/' .. apicast._VERSION, user_agent.platform())
end)
end)

end)

0 comments on commit 138d23c

Please sign in to comment.