@catladder/cli
Version:
Panter cli tool for cloud CI/CD and DevOps
225 lines (224 loc) • 8.81 kB
JavaScript
;
var __importDefault = this && this.__importDefault || function (mod) {
return mod && mod.__esModule ? mod : {
"default": mod
};
};
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.upsertAllVariables = exports.clearBackups = exports.getVariableValueByRawName = exports.getAllVariables = exports.getProjectInfo = exports.doGitlabRequest = exports.getGitlabToken = exports.setupGitlabToken = exports.hasGitlabToken = void 0;
const pipeline_1 = require("../../../pipeline/src/index.js");
const lodash_es_1 = require("lodash-es");
const memoizee_1 = __importDefault(require("memoizee"));
const node_fetch_1 = __importDefault(require("node-fetch"));
const open_1 = __importDefault(require("open"));
const preferences_1 = require("./preferences");
const gitProjectInformation_1 = require("../git/gitProjectInformation");
const TOKEN_KEY = "gitlab-personal-access-token";
const hasGitlabToken = async () => await (0, preferences_1.hasPreference)(TOKEN_KEY);
exports.hasGitlabToken = hasGitlabToken;
const setupGitlabToken = async io => {
io.log("");
io.log("☝ in order to access the api, we need a personal access token");
io.log("Its best to create one specifically for catladder");
io.log("Scopes needed: api");
io.log("");
io.log("☝ we open up the settings page for you!");
io.log("");
const [shouldContinue, {
gitRemoteHost
}] = await Promise.all([io.confirm("Ok"), (0, gitProjectInformation_1.getGitRemoteHostAndPath)()]);
(0, open_1.default)(`https://${gitRemoteHost}/-/user_settings/personal_access_tokens`);
io.log("Please type in gitlab's personal access token");
const personalToken = await io.promptDirect({
type: "string",
name: "personalToken",
default: "",
message: "Your personal access token "
});
if (personalToken) {
await (0, preferences_1.setPreference)(TOKEN_KEY, personalToken);
}
};
exports.setupGitlabToken = setupGitlabToken;
const getGitlabToken = async io => {
if (!(await (0, exports.hasGitlabToken)())) {
if (!io) {
console.error("⚠️ gitlab token missing, please run catladder to set it up");
process.exit(1);
}
await (0, exports.setupGitlabToken)(io);
}
return (0, preferences_1.getPreference)(TOKEN_KEY);
};
exports.getGitlabToken = getGitlabToken;
const doGitlabRequest = async (io, path, data = undefined, method = "GET") => {
const [rootToken, {
gitRemoteHost
}] = await Promise.all([(0, exports.getGitlabToken)(io), (0, gitProjectInformation_1.getGitRemoteHostAndPath)()]);
//const method = data ? (update ? "PUT" : "POST") : "GET";
const result = await (0, node_fetch_1.default)(`https://${gitRemoteHost}/api/v4/${path}`, {
method,
headers: {
"Content-Type": "application/json",
"Private-Token": rootToken
},
body: data ? JSON.stringify(data) : undefined
});
if (result.status >= 200 && result.status < 400) {
if (result.headers.get("content-type") === "application/json") {
return result.json();
}
return null;
}
if (result.status === 404) {
throw new Error("not found");
}
throw new Error(`Could not send request to gitlab api ${path}: ${result.status} "${result.statusText}".\nResponse: ${JSON.stringify(await result.json(), null, 2)}`);
};
exports.doGitlabRequest = doGitlabRequest;
const getProjectInfo = async io => {
const {
gitRemotePath
} = await (0, gitProjectInformation_1.getGitRemoteHostAndPath)();
const project = await (0, exports.doGitlabRequest)(io, `projects/${encodeURIComponent(gitRemotePath)}`);
return project;
};
exports.getProjectInfo = getProjectInfo;
exports.getAllVariables = (0, memoizee_1.default)(async (io, n = 5) => {
const {
id
} = await (0, exports.getProjectInfo)(io);
let all = [];
let result = [];
let page = 1;
do {
// Create an array of promises for N pages
const promises = Array.from({
length: n
}, (_, i) => {
return (0, exports.doGitlabRequest)(io, `projects/${id}/variables?per_page=100&page=${page + i}`);
});
// Wait for all promises to resolve
result = await Promise.all(promises);
// Increment the page by N
page += n;
// Flatten the result array and add it to 'all'
all = [...all, ...result.flat()];
// Continue only if the last page had results
} while (result.length > 0 && result[result.length - 1].length > 0);
return all;
}, {
promise: true
});
const getVariableValueByRawName = async (io, rawName) => {
var _a;
const allVariables = await (0, exports.getAllVariables)(io);
return (_a = allVariables.find(v => v.key === rawName)) === null || _a === void 0 ? void 0 : _a.value;
};
exports.getVariableValueByRawName = getVariableValueByRawName;
const maskableRegex = new RegExp("^[a-zA-Z0-9_+=/@:.~-]{8,}$"); // SEE https://gitlab.com/gitlab-org/gitlab-foss/-/blob/master/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js#L20
const isMaskable = value => maskableRegex.test(value);
const createVariable = async (io, projectId, key, value, masked = true, environment_scope = "*") => {
return await (0, exports.doGitlabRequest)(io, `projects/${projectId}/variables`, {
key,
value,
masked: masked && isMaskable(value),
environment_scope
}, "POST");
};
const updateVariable = async (io, projectId, key, value, masked = true) => {
return await (0, exports.doGitlabRequest)(io, `projects/${projectId}/variables/${key}`, {
value,
masked: masked && isMaskable(value)
}, "PUT");
};
const deleteVariable = async (io, projectId, key) => {
return await (0, exports.doGitlabRequest)(io, `projects/${projectId}/variables/${key}`, undefined, "DELETE");
};
const getAllCatladderEnvVarsInGitlab = async io => {
const allVariables = await (0, exports.getAllVariables)(io).then(v => v.reduce((acc, variable) => {
var _a, _b, _c, _d;
const {
key
} = variable;
if (key.startsWith("CL_")) {
const matchBackup = key.match(/(CL_.*)_backup_([0-9]+)/);
if (matchBackup) {
const key = matchBackup[1];
const timestamp = Number(matchBackup[2]);
const backups = [...((_b = (_a = acc[key]) === null || _a === void 0 ? void 0 : _a.backups) !== null && _b !== void 0 ? _b : []), timestamp];
return {
...acc,
[key]: {
...((_c = acc[key]) !== null && _c !== void 0 ? _c : {}),
// add value
backups
}
};
}
return {
...acc,
[key]: {
backups: [],
...((_d = acc[key]) !== null && _d !== void 0 ? _d : {}),
// may add backups
value: variable.value
}
};
}
return acc;
}, {}));
return allVariables;
};
const getBackupKey = (fullKey, timestamp) => `${fullKey}_backup_${timestamp}`;
const clearBackups = async (io, keep) => {
const existingVariables = await getAllCatladderEnvVarsInGitlab(io);
const {
id
} = await (0, exports.getProjectInfo)(io);
for (const [key, {
backups
}] of Object.entries(existingVariables)) {
const backupsSorted = backups.sort((a, b) => b - a);
//const toKeep = backupsSorted.slice(0, keep);
const toDelete = backupsSorted.slice(keep);
for (const timestamp of toDelete) {
await deleteVariable(io, id, getBackupKey(key, timestamp));
}
}
};
exports.clearBackups = clearBackups;
const upsertAllVariables = async (io, variables, env, componentName, backup = true, masked = true) => {
var _a;
const {
id
} = await (0, exports.getProjectInfo)(io);
// we list all existing variables. We also gather backup versions, but its currently unused
const existingVariables = await getAllCatladderEnvVarsInGitlab(io);
for (const [key, value] of Object.entries(variables !== null && variables !== void 0 ? variables : {})) {
const fullKey = (0, pipeline_1.getSecretVarName)(env, componentName, key);
const valueSanitized = (0, lodash_es_1.isObject)(value) ? JSON.stringify(value) : `${value}`;
const exists = (0, lodash_es_1.has)(existingVariables, fullKey);
const oldValue = (_a = existingVariables[fullKey]) === null || _a === void 0 ? void 0 : _a.value;
const changed = oldValue !== valueSanitized;
if (changed) {
if (exists) {
io.log(`changed: ${key}`);
await updateVariable(io, id, fullKey, valueSanitized, masked);
// write backup
if (backup) {
await createVariable(io, id, getBackupKey(fullKey, new Date().getTime()), oldValue, masked, "_backup");
}
} else {
io.log(`new : ${key}`);
await createVariable(io, id, fullKey, valueSanitized, masked);
}
} else {
io.log(`skip : ${key}`);
}
}
exports.getAllVariables.clear();
};
exports.upsertAllVariables = upsertAllVariables;