| Server IP : 34.87.99.72 / Your IP : 216.73.217.31 Web Server : Apache/2.4.46 (Unix) OpenSSL/1.1.1n System : Linux zlock-bitnami-lamp-prod-v2-vm 4.19.0-16-cloud-amd64 #1 SMP Debian 4.19.181-1 (2021-03-19) x86_64 User : bitnami ( 1000) PHP Version : 7.4.18 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /opt/bitnami/nami/node_modules/provisioner/lib/tools/ |
Upload File : |
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const nami_wrapper_1 = require("../nami_wrapper");
const YAML = require("js-yaml");
const released_nami_module_1 = require("./released_nami_module");
const app_catalogue_helper_1 = require("./app_catalogue_helper");
const s3_helper_1 = require("./s3_helper");
// input for the generic definitions
const definitionsDirectory = `${__dirname}/../../definitions`;
// path where version-specific definitions are stored
exports.definitionsS3Path = "s3://bitnami-autobuild-ondemand/multi-tier-definitions";
/**
* Class for managing generic definitions and creating version-specific definitions based on generic
* definition as input and modules.json file
*/
class DefinitionsHandler {
constructor(application, version) {
this.application = application;
this.version = version;
this.metadataDir = null;
}
/**
* Set version property based on latest version already stored as version-specific definition
*/
setVersionFromLatestStored() {
this.version = this.readStoredDefinitionVersion();
}
/**
* Set version property based on latest version released - i.e. from modules.json or
* based on on artifact version
*/
setVersionFromLatestReleased() {
this.version = this.releasedNamiModule.version;
}
/**
* Path to system where the version-specific definition should be stored for current application and version
*/
get localDefinitionPath() {
return `${this._getUpdatedDefinitionsPath()}/${this.application}-${this.version}.yml`;
}
/**
* Write version-specific definition to local folder
* @param contents contents of the definition, as string
*/
writeLocalDefinition(contents) {
console.log(`Writing definition to local path: ${this.localDefinitionPath}`);
nami_wrapper_1.file.write(this.localDefinitionPath, contents);
}
/**
* Path to S3 where the version-specific definition is stored for current application and version
*/
get storedDefinitionPath() {
return `${exports.definitionsS3Path}/${this.application}/${this.version}.yml`;
}
/**
* Path to S3 where the latest version number is stored for current application
*/
get storedDefinitionVersionPath() {
return `${exports.definitionsS3Path}/${this.application}/latest`;
}
/**
* Retrieve version-specific definition from S3 and return it
*/
readStoredDefinition() {
return s3_helper_1.readS3File(this.storedDefinitionPath);
}
/**
* Path to S3 where the latest stored definition is stored
*/
get latestStoredDefinition() {
const latestVersion = s3_helper_1.readS3File(this.storedDefinitionVersionPath);
return `${exports.definitionsS3Path}/${this.application}/${latestVersion}.yml`;
}
/**
* Retrieve latest definition from S3 and return it
*/
readLatestStoredDefinition() {
return s3_helper_1.readS3File(this.latestStoredDefinition);
}
/**
* Write version-specific definition to S3
* @param contents contents of the definition, as string
*/
writeStoredDefinition(contents) {
console.log(`Writing definition to s3: ${this.storedDefinitionPath}`);
s3_helper_1.writeS3File(this.storedDefinitionPath, contents);
}
/**
* Retrieve version of version-specific definition from S3 and return it
*/
readStoredDefinitionVersion() {
return s3_helper_1.readS3File(this.storedDefinitionVersionPath).trim();
}
/**
* Write currently set version as version of version-specific definition to S3
*/
writeStoredDefinitionVersion() {
s3_helper_1.writeS3File(this.storedDefinitionVersionPath, this.version);
}
/**
* Create or return ReleasedNamiModule object that can be used to handle modules.json
* metadata and/or retrieval of version from artifact
*/
get releasedNamiModule() {
if (!this._releasedNamiModule) {
this._releasedNamiModule = new released_nami_module_1.ReleasedNamiModule(this.application, this.version);
}
return this._releasedNamiModule;
}
/**
* Check whether the stored version-specific definition differs from a given definition
* @param data to compare
*/
doesStoredDefinitionDiffersFrom(data) {
let oldData;
// try to download current version of the definition, but do not fail as it may not exist yet
try {
console.log(`Reading stored definition: ${this.latestStoredDefinition}`);
oldData = this.readLatestStoredDefinition();
}
catch (e) {
console.log(`Unable to read stored definition: ${e.message}`);
}
return (oldData !== data);
}
/**
* Enhances the definition with version info, taken from this.version or latest
* released if any
*/
getVersionSpecificDefinition() {
let newDefinition;
let newData;
// if the version was not specified, assume latest released version
if (!this.version) {
this.setVersionFromLatestReleased();
}
// create cu
newDefinition = this.createVersionSpecificDefinition();
newData = convertToYaml(newDefinition);
// attempt to determine version from current version-specific definition, as it may be there
if (!this.version) {
this.version = newDefinition.details.version;
console.log("Missing version, fallback to new definition version");
console.log(`Version: ${this.version}`);
}
// make sure version is set - otherwise throw an error as it is needed to continue
if (!this.version) {
throw new Error(`Unable to determine version for ${this.application}`);
}
return newData;
}
/**
* Create version-specific definition based on generic definition and version information
* retrieved by ReleasedNamiModule object
*/
createVersionSpecificDefinition() {
let data = this._readRawDefinition();
console.log("RAW definition:");
console.log(`---\n${JSON.stringify(data)}\n---\n`);
data = this._updateVersionsFromVesionMap(data);
console.log("RAW definition (versions feed):");
console.log(`---\n${JSON.stringify(data)}\n---\n`);
data = this._addLocalSystemPackages(data);
console.log("RAW definition (local system packages feed):");
console.log(`---\n${JSON.stringify(data)}\n---\n`);
return data;
}
/**
* Provide a map of all clouds and topologies supported
* @result a
*/
getTopologiesForClouds() {
let result = {};
let standardTopologies = [];
const data = this._readRawDefinition();
if (data.supportedTopologies) {
for (let cloud of Object.keys(data.supportedTopologies)) {
const topologies = standardTopologies.concat(data.supportedTopologies[cloud]);
result[cloud] = [...new Set(topologies)]; // remove duplicated values
}
}
return result;
}
/**
* Retrieve the supported platforms for this definition
*/
getSupportedPlatforms() {
const data = this._readRawDefinition();
return data.supportedPlatforms || [];
}
_updateVersionsFromVesionMap(data) {
// map of all versions from module metadata to apply
let versionMap = this.releasedNamiModule.getAllNamiModuleVersionMap();
console.log("Components version map (initial):");
console.log(`---\n${JSON.stringify(versionMap, null, 2)}\n---`);
this._releasedNamiModule = new released_nami_module_1.ReleasedNamiModule(this.application, this.version);
// main module that is used in the definition, helpful for determining version
const mainModuleName = data.details.mainModule || data.details.key;
// list of modules known to be present in the original nami module definition ; if
// ReleasedNamiModule did not return version map, assume it should be only the main module
const knownModuleNames = versionMap ? Object.keys(versionMap) : [mainModuleName];
// list of all modules that are used in definition
let usedModules = [];
if (!versionMap) {
// if there is no nami module metadata, assume the version map only consists of main module ;
versionMap = {};
// get any versions hardcoded in the generic definition
this._retrieveVersionsDefinedInTiers(versionMap, data);
// in case this definition has no equivalent nami module (i.e. LAMP/MEAN), determine version
// from version of main module, as the version was not retrieved from anywhere else
this.version = this.version || versionMap[mainModuleName];
versionMap[mainModuleName] = this.version;
}
else {
// if versions were rertieved, apply additional versions hardcoded in the generic definition
this._retrieveVersionsDefinedInTiers(versionMap, data);
console.log("Components version map (tier hardcoded versions feed):");
console.log(`---\n${JSON.stringify(versionMap, null, 2)}\n---`);
}
// get all versions from others modules
let definedModules = this._retrieveModulesDefinedInTiers(data);
definedModules.forEach((m) => {
const moduleMetadata = new released_nami_module_1.ReleasedNamiModule(m);
const moduleVersionMap = moduleMetadata.getAllNamiModuleVersionMap();
Object.keys(moduleVersionMap || {}).forEach((module) => {
versionMap[module] = versionMap[module] ? versionMap[module] : moduleVersionMap[module];
});
});
console.log("Components version map (tier released components feed):");
console.log(`---\n${JSON.stringify(versionMap, null, 2)}\n---`);
// set version of the definition
data.details.version = this.version;
// for each tier, apply versions fom versionMap to each nami module installed
console.log("Updating versions in tiers data:");
for (let tier of Object.keys(data.tiers)) {
console.log(` ==> ${tier}`);
data.tiers[tier] = this._applyVersionsToTier(data.tiers[tier], versionMap, usedModules);
console.log(`---\n${JSON.stringify(data.tiers[tier])}\n---`);
}
// check there are no new modules in released nami module metadata that
// are not used in multi-tier definition - we should fail in this case to
// ensure it is added to the definition
for (let moduleName of knownModuleNames) {
if (usedModules.indexOf(moduleName) < 0) {
throw new Error(`Module ${moduleName} is not used in definition`);
}
}
return data;
}
_addLocalSystemPackages(data) {
const packageList = this.releasedNamiModule.getLocalSystemPackages();
// The dependencies above are hardcoded in the definition
data.dependencies = data.dependencies || {};
// Merge if needed
if (data.dependencies.systemPackages) {
Object.keys(data.dependencies.systemPackages || {}).forEach((distro) => {
let systemPackages = data.dependencies.systemPackages[distro] || [];
console.log(` => Adding system packages for ${distro}: ${JSON.stringify(systemPackages, null, 2)}`);
if (packageList[distro]) {
packageList[distro] = systemPackages.concat(packageList[distro]).sort();
// Remove duplicated
packageList[distro] = packageList[distro].filter((pkg, index, list) => list.indexOf(pkg, index + 1) < 0);
}
else {
packageList[distro] = systemPackages;
}
});
data.dependencies.systemPackages = packageList;
}
return data;
}
_applyVersionsToTier(data, versionMap, usedModules) {
if (data.modules) {
for (let moduleInfo of data.modules) {
// keep a list of all modules that were used in this definition, allows ensuring
// the definition uses all modules that are in the released nami module metadata
if (usedModules.indexOf(moduleInfo.name) < 0) {
usedModules.push(moduleInfo.name);
}
moduleInfo.version = versionMap[moduleInfo.name];
// ensure all modules have known versions, fail otherwise
if (!moduleInfo.version) {
throw new Error(`Unable to determine version for ${moduleInfo.name} for tier`);
}
}
}
return data;
}
/**
* Get any hardcoded versions from the generic definition - in some cases the definition
* includes additional modules that are not considered part of the release
* (i.e. jenkins-swarm or in cases like LAMP/MEAN where there is no modules JSON)
*/
_retrieveVersionsDefinedInTiers(versionMap, data) {
// list of all modules already handled by this method
let modulesHandled = [];
for (let tier of Object.keys(data.tiers || {})) {
const tierData = data.tiers[tier];
for (let moduleInfo of tierData.modules) {
if (moduleInfo.version) {
if (versionMap[moduleInfo.name]) {
// if module has version set, but was not yet processed by this method, fail as the
// version was set from released nami module metadata - and in this case it
// should not be in the generic definition
if (modulesHandled.indexOf(moduleInfo.name) < 0) {
throw new Error(`Module ${moduleInfo.name} version in definition and released data`);
}
// fail if versions are set differently across definition
if (moduleInfo.version !== versionMap[moduleInfo.name]) {
throw new Error(`Module ${moduleInfo.name} version mismatch`);
}
}
// set the version accordingly and store the module was already processed
versionMap[moduleInfo.name] = moduleInfo.version;
modulesHandled.push(moduleInfo.name);
}
}
}
}
/**
* Get all modules from the generic definition
*/
_retrieveModulesDefinedInTiers(data) {
// list of all modules already handled by this method
let modulesHandled = [];
for (let tier of Object.keys(data.tiers || {})) {
const tierData = data.tiers[tier];
tierData.modules.forEach((m) => {
let moduleName = m.name;
if (modulesHandled.indexOf(moduleName) === -1) {
modulesHandled.push(moduleName);
}
});
}
return modulesHandled;
}
_getUpdatedDefinitionsPath() {
return `definitions/updated`;
}
_getDefinitionsPath() {
return `definitions`;
}
_readRawDefinition() {
if (!this.rawDefinition) {
const rawDefinitionPath = `${this._getDefinitionsPath()}/${this.application}.yml`;
this.rawDefinition = YAML.safeLoad(nami_wrapper_1.file.read(rawDefinitionPath));
}
return this.rawDefinition;
}
getRawDefinition() {
return this._readRawDefinition();
}
}
exports.DefinitionsHandler = DefinitionsHandler;
function convertToYaml(data) {
let yaml = YAML.dump(data);
// ensure YAML begins with "---" so it can be properly parsed
if (yaml.substring(0, 3) !== "---") {
yaml = `---\n${yaml}`;
}
return yaml;
}
/**
* List all defined applications by checking the definitions folder for .yml files
*/
function listDefinedApplications() {
let applications = [];
console.log(`Getting definitions from ${definitionsDirectory} folder...`);
for (let name of nami_wrapper_1.file.glob(`${definitionsDirectory}/*.yml`)) {
let nameList = nami_wrapper_1.file.split(name);
let app = nameList[nameList.length - 1].replace(/\.yml$/, "");
applications.push(app);
console.log(` => ${app}`);
}
return applications;
}
/**
* List all definitions and versions and write them to file or stdout
*/
function listAllDefinitionVersions(outputFile) {
const s3 = nami_wrapper_1.s3cs();
let applications = listDefinedApplications();
const filelist = s3.list(exports.definitionsS3Path, { recursive: true });
let versionsForApp = {};
let result = [];
// map all files in S3 definitions storage that match /:app/:version.yml pattern
// and store them in the versionsForApp map
for (let filename of filelist) {
let match = filename.match(/^.*\/(.*?)\/(.*?)\.yml$/);
if (match) {
const fileApp = match[1];
const fileVersion = match[2];
versionsForApp[fileApp] = versionsForApp[fileApp] || [];
versionsForApp[fileApp].push(fileVersion);
}
}
for (let application of applications) {
// perform for applications that are a valid application at this time and have versions in S3
if (versionsForApp[application]) {
let versions = versionsForApp[application];
let handler = new DefinitionsHandler(application);
// try to sort versions using version-utils package if it is available
try {
versions = versions.sort((v1, v2) => {
return require("version-utils").compareVersions(v2, v1);
});
}
catch (e) {
console.log(`Unable to order versions from latest to oldest: ${e.message}`);
}
const supportedPlatforms = handler
.getSupportedPlatforms()
.map(o => o.bitnamiPlatforms)
.reduce((acum, cur) => acum.concat(cur), []);
// push the list in the format required by CI to the output list
result.push({
name: application,
versions: versions,
topologies: handler.getTopologiesForClouds(),
supportedPlatforms: supportedPlatforms,
latest: handler.readStoredDefinitionVersion()
});
}
}
// write list to file
if (outputFile) {
nami_wrapper_1.file.write(outputFile, JSON.stringify(result, null, 2));
}
}
exports.listAllDefinitionVersions = listAllDefinitionVersions;
/**
* Get a full definition for a given application
* It uses app-catalogue as metadata source
*/
function getFullDefinition(application, metadataDir) {
const versionsList = {};
const dh = new DefinitionsHandler(application);
const def = dh.getRawDefinition();
const dependencies = def.dependencies || {};
const packagesList = dependencies.systemPackages || {};
// Default supported platforms
const supportedPlatforms = def.supportedPlatforms;
for (let t of Object.keys(def.tiers)) {
const tierModules = [];
for (let sp of supportedPlatforms) {
packagesList[sp.distro] = packagesList[sp.distro] || [];
for (let mo of def.tiers[t].modules) {
const ach = new app_catalogue_helper_1.AppCatalogueHelper(mo.name, metadataDir, sp.os, sp.distro, sp.arch, mo.branch, mo.version);
// Currently, we use the same definition for all distributions and tiers. Therefore, we can
// only set a version per module. If a definition supports vmware (centos-7) we are using
// the debian-10 version. `versionsList` will store only the versions of the first round of modules (debian-10)
if (!versionsList[mo.name]) {
console.log(` * Updating ${mo.name} version: (${ach.branch} ~> ${ach.version})`);
versionsList[mo.name] = ach.version;
}
// Store the list of modules dependencies
packagesList[sp.distro] = packagesList[sp.distro].concat(ach.systemPackages);
// Update modules information (version)
if (tierModules.filter(m => m.name === mo.name).length === 0) {
// NOTE (jdrios) As we are storing only the versions of the debian-10 modules, this prevent the loop
// from using the versions of a different distro
mo.version = versionsList[mo.name];
tierModules.push(mo);
}
}
}
// Update app main version
def.details.version = versionsList[def.details.mainModule];
// Update tier modules
def.tiers[t].modules = tierModules;
}
// Update definition requirements
for (let sp of supportedPlatforms) {
// Sort
const pkgs = packagesList[sp.distro].sort();
// Remove duplicates
packagesList[sp.distro] = pkgs.filter((pkg, pos) => { return pkgs.indexOf(pkg) === pos; });
console.log(` * Updating dependencies for ${sp.distro}:`);
if (packagesList[sp.distro].length > 0) {
console.log(` - ${packagesList[sp.distro].join("\n - ")}`);
}
}
def.dependencies = { systemPackages: packagesList };
return def;
}
exports.getFullDefinition = getFullDefinition;
/**
* Get full definitions and upload them to S3
*/
function updateFullDefinitions(applications, metadataDir, force, upload) {
let failures = [];
if (!applications) {
applications = listDefinedApplications();
}
for (let application of applications) {
console.log(`\n === Updating ${application} ===`);
try {
const dh = new DefinitionsHandler(application);
const def = getFullDefinition(application, metadataDir);
dh.version = def.details.version;
const data = convertToYaml(def);
// Always write local definition
dh.writeLocalDefinition(data);
// Check whether the current latest definition differs from the one being calculated
const hasChanged = dh.doesStoredDefinitionDiffersFrom(data);
// if it has changed or the force parameter was set, write the definition and update version
if (hasChanged || force) {
console.log(`The new definition for ${application} differs from the latest stored definition!`);
if (upload) {
console.log(`Writing definition for ${application} ${dh.version}: ${dh.storedDefinitionPath}`);
dh.writeStoredDefinition(data);
console.log(`Updating latest file for ${application} ${dh.version}: ${dh.storedDefinitionVersionPath}`);
dh.writeStoredDefinitionVersion();
}
}
else {
console.log(`The new definition for ${application} does not differ from`, `the remote definition at ${dh.storedDefinitionPath}!`);
}
}
catch (e) {
console.log(`Unable to update ${application}: ${e.message}\n`, e.stack);
failures.push(application);
}
}
if (failures.length > 0) {
throw new Error(`Unable to udpate definitions for: ${failures.join(", ")}`);
}
}
exports.updateFullDefinitions = updateFullDefinitions;
/**
* Create and/or update latest definitions of all or some applications in the S3 storage
*/
function updateDefinitions(applications, force, upload) {
let failures = [];
if (!applications) {
applications = listDefinedApplications();
}
for (let application of applications) {
console.log(`\n === Updating ${application} ===`);
try {
const dh = new DefinitionsHandler(application);
const definition = dh.getVersionSpecificDefinition();
const hasChanged = dh.doesStoredDefinitionDiffersFrom(definition);
// Always write local definition
dh.writeLocalDefinition(definition);
// if it has changed or the force parameter was set, write the definition and update version
if (hasChanged && upload) {
console.log(`Writing definition for ${application} ${dh.version}:`, `${hasChanged ? "has changed" : "unchanged, but forced"}`);
dh.writeStoredDefinition(definition);
dh.writeStoredDefinitionVersion();
}
else {
console.log(`Not uploading definition for ${application} ${dh.version}: no changes`);
}
}
catch (e) {
console.log(`Unable to update ${application}: ${e.message}\n`, e.stack);
failures.push(application);
}
}
if (failures.length > 0) {
throw new Error(`Unable to udpate definitions for: ${failures.join(", ")}`);
}
}
exports.updateDefinitions = updateDefinitions;
/**
* If version is specified, use it ; otherwise get latest stored definition version
* stored on S3 and return it
* @param application Application key
* @param version Optional version to use
* @return The version to use
*/
function getApplicationVersion(application, version) {
if (!version) {
let handler = new DefinitionsHandler(application, version);
version = handler.readStoredDefinitionVersion();
}
return version;
}
exports.getApplicationVersion = getApplicationVersion;
/**
* Download version-specific definition and store it in a temporary file
* @param application Application for which to download the version-specific definition
* @param version Version for which to download the version-specific definition
* @return path to temporary file with the definition
*/
function downloadStoredDefinition(application, version) {
let handler = new DefinitionsHandler(application, getApplicationVersion(application, version));
const tempDir = nami_wrapper_1.os.createTempDir();
const tempFile = `${tempDir}/${handler.application}/${handler.version}.yml`;
nami_wrapper_1.file.write(tempFile, handler.readStoredDefinition());
return tempFile;
}
exports.downloadStoredDefinition = downloadStoredDefinition;