-
Notifications
You must be signed in to change notification settings - Fork 171
Expand file tree
/
Copy pathpolicy_loader.lua
More file actions
191 lines (146 loc) · 5.19 KB
/
policy_loader.lua
File metadata and controls
191 lines (146 loc) · 5.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
--- Policy loader
-- This module loads a policy defined by its name and version.
-- It uses sandboxed require to isolate dependencies and not mutate global state.
-- That allows for loading several versions of the same policy with different dependencies.
-- And even loading several independent copies of the same policy with no shared state.
-- Each object returned by the loader is new table and shares only shared APIcast code.
local sandbox = require('resty.sandbox')
local cjson = require('cjson')
local format = string.format
local ipairs = ipairs
local pairs = pairs
local insert = table.insert
local concat = table.concat
local setmetatable = setmetatable
local pcall = pcall
local _M = {}
local resty_env = require('resty.env')
local re = require('ngx.re')
do
local function apicast_dir()
return resty_env.value('APICAST_DIR') or '.'
end
local function policy_load_path()
return resty_env.value('APICAST_POLICY_LOAD_PATH') or
format('%s/policies', apicast_dir())
end
function _M.policy_load_paths()
return re.split(policy_load_path(), ':', 'oj')
end
function _M.builtin_policy_load_path()
return resty_env.value('APICAST_BUILTIN_POLICY_LOAD_PATH') or format('%s/src/apicast/policy', apicast_dir())
end
end
-- Returns true if config validation has been enabled via ENV or if we are
-- running Test::Nginx integration tests. We know that the framework always
-- sets TEST_NGINX_BINARY so we can use it to detect whether we are running the
-- tests.
local function policy_config_validation_is_enabled()
return resty_env.enabled('APICAST_VALIDATE_POLICY_CONFIGS')
or resty_env.value('TEST_NGINX_BINARY')
end
local policy_config_validator = { validate_config = function() return true end }
if policy_config_validation_is_enabled() then
policy_config_validator = require('apicast.policy_config_validator')
end
local function read_manifest(path)
local handle = io.open(format('%s/%s', path, 'apicast-policy.json'))
if handle then
local contents = handle:read('*a')
handle:close()
return cjson.decode(contents)
end
end
local function lua_load_path(load_path)
return format('%s/?.lua', load_path)
end
local function load_manifest(name, version, path)
local manifest = read_manifest(path)
if manifest then
if manifest.version ~= version then
ngx.log(ngx.ERR, 'Not loading policy: ', name,
' path: ', path,
' version: ', version, '~= ', manifest.version)
return
end
return manifest, lua_load_path(path)
end
return nil, lua_load_path(path)
end
local function with_config_validator(policy, policy_config_schema)
local original_new = policy.new
local new_with_validator = function(config)
local is_valid, err = policy_config_validator.validate_config(
config, policy_config_schema)
if not is_valid then
error(format('Invalid config for policy: %s', err))
end
return original_new(config)
end
return setmetatable(
{ new = new_with_validator },
{ __index = policy }
)
end
function _M:load_path(name, version, paths)
local failures = {}
for _, path in ipairs(paths or self.policy_load_paths()) do
local manifest, load_path = load_manifest(name, version, format('%s/%s/%s', path, name, version) )
if manifest then
return load_path, manifest.configuration
else
insert(failures, load_path)
end
end
if version == 'builtin' then
local manifest, load_path = load_manifest(name, version, format('%s/%s', self.builtin_policy_load_path(), name) )
if manifest then
return load_path, manifest.configuration
else
insert(failures, load_path)
end
end
return nil, nil, failures
end
local package_cache = setmetatable({}, {
__index = function(t, k) local n = { }; t[k] = n; return n end
})
function _M:call(name, version, dir)
local v = version or 'builtin'
local load_path, policy_config_schema, invalid_paths = self:load_path(name, v, dir)
local cache_key = concat({name, v, dir and concat(dir, ',') or '' }, '-')
local cache = package_cache[cache_key]
local loader = sandbox.new(load_path and { load_path } or invalid_paths,
cache)
ngx.log(ngx.DEBUG, 'loading policy: ', name, ' version: ', v)
-- passing the "exclusive" flag for the require so it does not fallback to native require
-- it should load only policies and not other code and fail if there is no such policy
local res = loader('init', true)
if policy_config_validation_is_enabled() then
return with_config_validator(res, policy_config_schema)
else
return res
end
end
function _M:pcall(name, version, dir)
local ok, ret = pcall(self.call, self, name, version, dir)
if ok then
return ret
else
return nil, ret
end
end
-- Returns all the policy modules
function _M:get_all()
local policy_modules = {}
local policy_manifests_loader = require('apicast.policy_manifests_loader')
local manifests = policy_manifests_loader.get_all()
for policy_name, policy_manifests in pairs(manifests) do
for _, manifest in ipairs(policy_manifests) do
local policy = self:call(policy_name, manifest.version)
insert(policy_modules, policy)
end
end
return policy_modules
end
return setmetatable(_M, { __call = _M.call })