apigeelint
Version:
Node module and tool to lint a bundle for an Apigee API Proxy or sharedflow.
209 lines (196 loc) • 7.45 kB
JavaScript
/*
Copyright © 2019-2020,2024,2026 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const lintUtil = require("../lintUtil.js"),
xpath = require("xpath"),
ruleId = lintUtil.getRuleId(),
debug = require("debug")("apigeelint:" + ruleId);
const plugin = {
ruleId,
name: "Check MessageLogging / CloudLogging hygiene",
message:
"In the MessageLogging policy, the CloudLogging element should use correct hygiene.",
fatal: false,
severity: 1, // 1=warning, 2=error
nodeType: "Policy",
enabled: true,
};
const onPolicy = function (policy, cb) {
let flagged = false;
if (policy.getType() === "MessageLogging") {
debug(`found policy ${policy.getName()}`);
const clElements = xpath.select(
"/MessageLogging/CloudLogging",
policy.getElement(),
);
try {
if (clElements && clElements[0]) {
debug(`found ${clElements.length} CloudLogging element(s)`);
if (clElements[1]) {
flagged = true;
policy.addMessage({
plugin,
message: "Policy has more than one CloudLogging element.",
severity: 2, // 1=warning, 2=error
line: clElements[1].lineNumber,
column: clElements[1].columnNumber,
});
}
// now check the zeroth element
const requiredChildren = ["LogName", "Message"];
const optionalChildren = ["Labels", "ResourceType"];
const validChildren = requiredChildren.concat(optionalChildren);
const found = {};
const children = xpath.select(`*`, clElements[0]);
children.forEach((child) => {
if (validChildren.includes(child.tagName)) {
if (found[child.tagName]) {
flagged = true;
policy.addMessage({
plugin,
message: `Found more than one CloudLogging/${child.tagName} element.`,
severity: 2, // 1=warning, 2=error
line: child.lineNumber,
column: child.columnNumber,
});
} else {
found[child.tagName] = 1;
}
} else {
flagged = true;
policy.addMessage({
plugin,
message: `Unsupported element <${child.tagName}> inside CloudLogging.`,
severity: 2, // 1=warning, 2=error
line: child.lineNumber,
column: child.columnNumber,
});
}
});
requiredChildren.forEach((requiredElementName) => {
if (!found[requiredElementName]) {
flagged = true;
policy.addMessage({
plugin,
message: `Policy is missing a required Element: CloudLogging/${requiredElementName} element.`,
severity: 2, // 1=warning, 2=error
line: clElements[0].lineNumber,
column: clElements[0].columnNumber,
});
}
});
if (found.ResourceType) {
const rtypeElt = xpath.select(`ResourceType`, clElements[0])[0];
const textValue =
rtypeElt.childNodes &&
rtypeElt.childNodes[0] &&
rtypeElt.childNodes[0].nodeValue;
debug(`rtypeElt textValue '${textValue}'`);
if (!textValue || !textValue.trim()) {
flagged = true;
policy.addMessage({
plugin,
message: `ResourceType element is present but empty. Remove it or specify 'global'.`,
severity: 1, // 1=warning, 2=error
line: rtypeElt.lineNumber,
column: rtypeElt.columnNumber,
});
} else if (textValue != "global") {
flagged = true;
policy.addMessage({
plugin,
message: `The value '${textValue}' should not be used here. ResourceType should be 'global'`,
severity: 1, // 1=warning, 2=error
line: rtypeElt.lineNumber,
column: rtypeElt.columnNumber,
});
}
}
if (found.Labels) {
// do some checks here
const labelsElt = xpath.select(`Labels`, clElements[0])[0];
// the only allowed child of Labels is Label, and that should have just Key + Value
const labelsChildren = xpath.select(`*`, labelsElt);
labelsChildren.forEach((labelsChild) => {
if (labelsChild.tagName != "Label") {
flagged = true;
policy.addMessage({
plugin,
message: `Unsupported element '${labelsChild.tagName}'`,
severity: 2, // 1=warning, 2=error
line: labelsChild.lineNumber,
column: labelsChild.columnNumber,
});
} else {
// check each child
const validLabelChildren = ["Key", "Value"];
const found = {};
xpath.select(`*`, labelsChild).forEach((labelChild) => {
if (!validLabelChildren.includes(labelChild.tagName)) {
flagged = true;
policy.addMessage({
plugin,
message: `Unsupported element '${labelChild.tagName}'`,
severity: 2, // 1=warning, 2=error
line: labelChild.lineNumber,
column: labelChild.columnNumber,
});
} else {
if (found[labelChild.tagName]) {
flagged = true;
policy.addMessage({
plugin,
message: `Found more than one CloudLogging/${labelChild.tagName} element.`,
severity: 2, // 1=warning, 2=error
line: labelChild.lineNumber,
column: labelChild.columnNumber,
});
} else {
found[labelChild.tagName] = 1;
}
}
});
validLabelChildren.forEach((requiredElementName) => {
if (!found[requiredElementName]) {
flagged = true;
policy.addMessage({
plugin,
message: `Label is missing a required Element: ${requiredElementName}.`,
severity: 2, // 1=warning, 2=error
line: labelsChild.lineNumber,
column: labelsChild.columnNumber,
});
}
});
}
});
}
}
/* c8 ignore start */
} catch (e1) {
flagged = true;
policy.addMessage({
plugin,
message:
"Exception while examining CloudLogging element: " + e1.message,
});
}
/* c8 ignore stop */
}
if (typeof cb == "function") {
cb(null, flagged);
}
};
module.exports = {
plugin,
onPolicy,
};