Skip to content

Commit c67cf02

Browse files
committed
feat: enhance resolveIncludes function to support keyword replacement and nested includes
1 parent 5fa8642 commit c67cf02

File tree

2 files changed

+85
-7
lines changed

2 files changed

+85
-7
lines changed

src/context/yaml/index.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,32 @@ const includeType = new yaml.Type('!include', {
3131
const schema = yaml.DEFAULT_SCHEMA.extend([includeType]);
3232

3333
// Function to resolve includes
34-
function resolveIncludes(obj, basePath) {
34+
function resolveIncludes(obj, basePath, mappings?: KeywordMappings, disableKeywordReplacement?: boolean) {
3535
if (Array.isArray(obj)) {
36-
return obj.map(item => resolveIncludes(item, basePath));
36+
return obj.map(item => resolveIncludes(item, basePath, mappings, disableKeywordReplacement));
3737
}
3838

3939
if (obj && typeof obj === 'object') {
4040
if (obj.__include) {
4141
const filePath = path.resolve(basePath, obj.__include);
4242
if (fs.existsSync(filePath)) {
43-
const content = fs.readFileSync(filePath, 'utf8');
44-
return resolveIncludes(yaml.load(content, { schema }), path.dirname(filePath));
43+
let content = fs.readFileSync(filePath, 'utf8');
44+
45+
// Apply keyword replacement to included file content if mappings are provided
46+
if (mappings && !disableKeywordReplacement) {
47+
content = keywordReplace(content, mappings);
48+
} else if (mappings && disableKeywordReplacement) {
49+
content = wrapArrayReplaceMarkersInQuotes(content, mappings);
50+
}
51+
52+
return resolveIncludes(yaml.load(content, { schema }), path.dirname(filePath), mappings, disableKeywordReplacement);
4553
}
4654
throw new Error(`Include file not found: ${filePath}`);
4755
}
4856

4957
const result = {};
5058
for (const [key, value] of Object.entries(obj)) {
51-
result[key] = resolveIncludes(value, basePath);
59+
result[key] = resolveIncludes(value, basePath, mappings, disableKeywordReplacement);
5260
}
5361
return result;
5462
}
@@ -125,7 +133,7 @@ export default class YAMLContext {
125133

126134
Object.assign(
127135
this.assets,
128-
resolveIncludes(loadedYaml, path.dirname(fPath))
136+
resolveIncludes(loadedYaml, path.dirname(fPath), this.mappings, opts.disableKeywordReplacement)
129137
);
130138
} catch (err) {
131139
log.debug(err.stack);

test/context/yaml/actions.test.js

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,77 @@ roles: !include roles.yaml
137137
]);
138138
});
139139

140+
it('should process logStreams with includes', async () => {
141+
const dir = path.join(testDataDir, 'yaml', 'logstreams-includes');
142+
cleanThenMkdir(dir);
143+
144+
const logStreamsYaml = `
145+
- name: LoggingSAAS
146+
isPriority: false
147+
filters:
148+
- type: category
149+
name: auth.login.fail
150+
- type: category
151+
name: auth.login.notification
152+
- type: category
153+
name: auth.login.success
154+
- type: category
155+
name: auth.logout.fail
156+
sink:
157+
httpContentFormat: JSONLINES
158+
httpContentType: application/json
159+
httpEndpoint: "##LOGGING_WEBHOOK_URL##"
160+
type: http
161+
- name: SIEM
162+
isPriority: false
163+
filters:
164+
- type: category
165+
name: auth.login.fail
166+
- type: category
167+
name: auth.login.notification
168+
- type: category
169+
name: auth.login.success
170+
sink:
171+
httpContentFormat: JSONLINES
172+
httpContentType: application/json
173+
httpEndpoint: "##SIEM_WEBHOOK_URL##"
174+
type: http
175+
`;
176+
fs.writeFileSync(path.join(dir, 'logStreams.yaml'), logStreamsYaml);
177+
178+
const mainYaml = `
179+
tenant:
180+
friendly_name: 'Test Tenant'
181+
182+
logStreams: !include logStreams.yaml
183+
`;
184+
fs.writeFileSync(path.join(dir, 'tenant.yaml'), mainYaml);
185+
186+
const config = {
187+
AUTH0_INPUT_FILE: path.join(dir, 'tenant.yaml'),
188+
AUTH0_KEYWORD_REPLACE_MAPPINGS: {
189+
LOGGING_WEBHOOK_URL: 'https://logging.com/inputs/test',
190+
SIEM_WEBHOOK_URL: 'https://siem.example.com/webhook'
191+
}
192+
};
193+
const context = new Context(config, mockMgmtClient());
194+
await context.loadAssetsFromLocal();
195+
196+
expect(context.assets.logStreams).to.have.length(2);
197+
expect(context.assets.logStreams[0]).to.deep.include({
198+
name: 'LoggingSAAS',
199+
isPriority: false,
200+
type: 'http'
201+
});
202+
expect(context.assets.logStreams[0].sink.httpEndpoint).to.equal('https://logging.com/inputs/test');
203+
expect(context.assets.logStreams[1]).to.deep.include({
204+
name: 'SIEM',
205+
isPriority: false,
206+
type: 'http'
207+
});
208+
expect(context.assets.logStreams[1].sink.httpEndpoint).to.equal('https://siem.example.com/webhook');
209+
});
210+
140211
it('should error on missing include file', async () => {
141212
const dir = path.join(testDataDir, 'yaml', 'missing-include');
142213
cleanThenMkdir(dir);
@@ -154,7 +225,6 @@ clients: !include missing.yaml
154225
/Include file not found/
155226
);
156227
});
157-
158228
it('should dump actions', async () => {
159229
const dir = path.join(testDataDir, 'yaml', 'actionsDump');
160230
cleanThenMkdir(dir);

0 commit comments

Comments
 (0)