Skip to content

Commit

Permalink
Merge pull request #504 from 3scale/environment-configuration
Browse files Browse the repository at this point in the history
allow passing multiple environment configuration files
  • Loading branch information
mikz authored Nov 28, 2017
2 parents 715a563 + 20504a8 commit 17522a5
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 99 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Resolver can resolve nginx upstreams [PR #478](https://github.com/3scale/apicast/pull/478)
- Calls 3scale backend with the 'no_body' option enabled. This reduces network traffic in cases where APIcast does not need to parse the response body [PR #483](https://github.com/3scale/apicast/pull/483)
- Methods to modify policy chains [PR #505](https://github.com/3scale/apicast/pull/505)
- Ability to load several environment configurations [PR #504](https://github.com/3scale/apicast/pull/504)

## Changed

- Namespace all APIcast code in `apicast` folder. Possible BREAKING CHANGE for some customizations. [PR #486](https://github.com/3scale/apicast/pull/486)
- CLI ignores environment variables that are empty strings [PR #504](https://github.com/3scale/apicast/pull/504)

## Fixed

Expand Down
99 changes: 67 additions & 32 deletions gateway/src/apicast/cli/command/start.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ local exec = require('resty.execvp')
local resty_env = require('resty.env')

local Template = require('apicast.cli.template')
local configuration = require('apicast.cli.configuration')
local Environment = require('apicast.cli.environment')

local pl = {
path = require('pl.path'),
Expand All @@ -26,7 +26,7 @@ local _M = {

local mt = { __index = _M }

local function pick_openesty(candidates)
local function find_openresty_command(candidates)
for i=1, #candidates do
local ok = os.execute(('%s -V 2>/dev/null'):format(candidates[i]))

Expand All @@ -44,10 +44,13 @@ local function update_env(env)
end
end

local function nginx_config(context, dir, path, env)
update_env(env)

local template = Template:new(context, dir, true)
local function apicast_root()
return resty_env.value('APICAST_DIR') or pl.path.abspath('.')
end

local function nginx_config(context,path)
local template = Template:new(context, apicast_root(), true)
local tmp = pl.path.tmpname()
pl.file.write(tmp, template:render(path))
return tmp
Expand Down Expand Up @@ -75,40 +78,73 @@ local function get_log_level(self, options)
return log_level
end

function mt:__call(options)
local openresty = resty_env.value('APICAST_OPENRESTY_BINARY') or
resty_env.value('TEST_NGINX_BINARY') or
pick_openesty(self.openresty)
local dir = resty_env.get('APICAST_DIR') or pl.path.abspath('.')
local config = configuration.new(dir)
local path = options.template
local environment = options.dev and 'development' or options.environment
local context = config:load(environment)
local env = {

local function build_environment_config(options)
local config = Environment.new()

for i=1, #options.environment do
local ok, err = config:add(options.environment[i])

if not ok then
print('not loading ', options.environment[i], ': ', err)
end
end

if options.dev then config:add('development') end

config:save()

return config
end

local function openresty_binary(candidates)
return resty_env.value('APICAST_OPENRESTY_BINARY') or
resty_env.value('TEST_NGINX_BINARY') or
find_openresty_command(candidates)
end

local function build_env(options, config)
return {
APICAST_CONFIGURATION = options.configuration,
APICAST_CONFIGURATION_LOADER = options.boot and 'boot' or 'lazy',
APICAST_CONFIGURATION_CACHE = options.cache,
THREESCALE_DEPLOYMENT_ENV = environment,
THREESCALE_DEPLOYMENT_ENV = config.name,
}
end

local function build_context(options, config)
local context = config:context()

context.worker_processes = options.workers or context.worker_processes

if options.daemon then
context.daemon = 'on'
end


if options.master and options.master[1] == 'off' then
context.master_process = 'off'
end

context.prefix = dir
context.ca_bundle = pl.path.abspath(context.ca_bundle or pl.path.join(dir, 'conf', 'ca-bundle.crt'))

context.prefix = apicast_root()
context.ca_bundle = pl.path.abspath(context.ca_bundle or pl.path.join(context.prefix, 'conf', 'ca-bundle.crt'))

return context
end

function mt:__call(options)
local openresty = openresty_binary(self.openresty)
local config = build_environment_config(options)
local context = build_context(options, config)
local env = build_env(options, config)

local template_path = options.template

update_env(env)
-- also use env from the config file
update_env(config.env or {})
update_env(context.env or {})

local nginx = nginx_config(context, dir, path, env)
local nginx = nginx_config(context, template_path)

local log_level = get_log_level(self, options)
local log_file = options.log_file or self.log_file
Expand All @@ -130,33 +166,32 @@ local function configure(cmd)
cmd:usage("Usage: apicast-cli start [OPTIONS]")
cmd:option("--template", "Nginx config template.", 'conf/nginx.conf.liquid')

cmd:mutex(
cmd:option('-e --environment', "Deployment to start.", resty_env.get('THREESCALE_DEPLOYMENT_ENV')),
cmd:flag('--dev', 'Start in development environment')
)

cmd:option('-e --environment', "Deployment to start. Can also be a path to a Lua file.", resty_env.value('THREESCALE_DEPLOYMENT_ENV') or 'production'):count('*')
cmd:flag('--dev', 'Start in development environment')

cmd:flag("-m --master", "Test the nginx config"):args('?')
cmd:flag("-t --test", "Test the nginx config")
cmd:flag("--debug", "Debug mode. Prints more information.")
cmd:option("-c --configuration",
"Path to custom config file (JSON)",
resty_env.get('APICAST_CONFIGURATION'))
resty_env.value('APICAST_CONFIGURATION'))
cmd:flag("-d --daemon", "Daemonize.")
cmd:option("-w --workers",
"Number of worker processes to start.",
resty_env.get('APICAST_WORKERS') or 1)
resty_env.value('APICAST_WORKERS') or 1)
cmd:option("-p --pid", "Path to the PID file.")
cmd:mutex(
cmd:flag('-b --boot',
"Load configuration on boot.",
resty_env.get('APICAST_CONFIGURATION_LOADER') == 'boot'),
resty_env.value('APICAST_CONFIGURATION_LOADER') == 'boot'),
cmd:flag('-l --lazy',
"Load configuration on demand.",
resty_env.get('APICAST_CONFIGURATION_LOADER') == 'lazy')
resty_env.value('APICAST_CONFIGURATION_LOADER') == 'lazy')
)
cmd:option("-i --refresh-interval",
"Cache configuration for N seconds. Using 0 will reload on every request (not for production).",
resty_env.get('APICAST_CONFIGURATION_CACHE'))
resty_env.value('APICAST_CONFIGURATION_CACHE'))

cmd:mutex(
cmd:flag('-v --verbose',
Expand All @@ -165,8 +200,8 @@ local function configure(cmd)
cmd:flag('-q --quiet', "Decrease logging verbosity.")
:count(("0-%s"):format(_M.log_level - 1))
)
cmd:option('--log-level', 'Set log level', resty_env.get('APICAST_LOG_LEVEL') or 'warn')
cmd:option('--log-file', 'Set log file', resty_env.get('APICAST_LOG_FILE') or 'stderr')
cmd:option('--log-level', 'Set log level', resty_env.value('APICAST_LOG_LEVEL') or 'warn')
cmd:option('--log-file', 'Set log file', resty_env.value('APICAST_LOG_FILE') or 'stderr')

cmd:epilog([[
Example: apicast start --dev
Expand Down
55 changes: 0 additions & 55 deletions gateway/src/apicast/cli/configuration.lua

This file was deleted.

135 changes: 135 additions & 0 deletions gateway/src/apicast/cli/environment.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
--- Environment configuration
-- @module environment
-- This module is providing a configuration to APIcast before and during its initialization.
-- You can load several configuration files.
-- Fields from the ones added later override fields from the previous configurations.
local pl_path = require('pl.path')
local resty_env = require('resty.env')
local linked_list = require('apicast.linked_list')
local setmetatable = setmetatable
local loadfile = loadfile
local pcall = pcall
local require = require
local assert = assert
local error = error
local print = print
local pairs = pairs
local insert = table.insert
local concat = table.concat
local re = require('ngx.re')

local _M = {}
---
-- @field default_environment Default environment name.
-- @table self
_M.default_environment = 'production'

--- Default configuration.
-- @tfield ?string ca_bundle path to CA store file
-- @table environment.default_config default configuration
_M.default_config = {
ca_bundle = resty_env.value('SSL_CERT_FILE'),
}

local mt = { __index = _M }

--- Load an environment from files in ENV.
-- @treturn Environment
function _M.load()
local value = resty_env.value('APICAST_LOADED_ENVIRONMENTS')
local env = _M.new()

if not value then
return env
end

local environments = re.split(value, '\\|', 'jo')

for i=1,#environments do
assert(env:add(environments[i]))
end

return env
end

--- Initialize new environment.
-- @treturn Environment
function _M.new()
return setmetatable({ _context = linked_list.readonly(_M.default_config), loaded = {} }, mt)
end

local function expand_environment_name(name)
local root = resty_env.value('APICAST_DIR') or pl_path.abspath('.')
local pwd = resty_env.value('PWD')

local path = pl_path.abspath(name, pwd)
local exists = pl_path.isfile(path)

if exists then
return nil, path
end

path = pl_path.join(root, 'config', ("%s.lua"):format(name))
exists = pl_path.isfile(path)

if exists then
return name, path
end
end

---------------------
--- @type Environment
-- An instance of @{environment} configuration.

--- Add an environment name or configuration file.
-- @tparam string env environment name or path to a file
function _M:add(env)
local name, path = expand_environment_name(env)

if self.loaded[path] then
return true, 'already loaded'
end

if name and path then
self.name = name
print('loading ', name ,' environment configuration: ', path)
elseif path then
print('loading environment configuration: ', path)
else
return nil, 'no configuration found'
end

local config = loadfile(path, 't', {
print = print, inspect = require('inspect'), context = self._context,
pcall = pcall, require = require, assert = assert, error = error,
})

if not config then
return nil, 'invalid config'
end

self.loaded[path] = true

self._context = linked_list.readonly(config(), self._context)

return true
end

--- Read/write context
-- @treturn table context with all loaded environments combined
function _M:context()
return linked_list.readwrite({ }, self._context)
end

--- Store loaded environment file names into ENV.
function _M:save()
local environments = {}

for file,_ in pairs(self.loaded) do
insert(environments, file)
end

resty_env.set('APICAST_LOADED_ENVIRONMENTS', concat(environments, '|'))
end

return _M
2 changes: 1 addition & 1 deletion gateway/src/apicast/configuration_loader.lua
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ local modes = {
}

function _M.new(mode)
mode = mode or env.get('APICAST_CONFIGURATION_LOADER') or modes.default
mode = mode or env.value('APICAST_CONFIGURATION_LOADER') or modes.default
local loader = modes[mode]
ngx.log(ngx.INFO, 'using ', mode, ' configuration loader')
return assert(loader, 'invalid config loader mode')
Expand Down
Loading

0 comments on commit 17522a5

Please sign in to comment.