| Server IP : 54.250.94.235 / Your IP : 216.73.216.57 Web Server : Apache System : Linux ip-172-26-8-22 6.1.0-41-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.158-1 (2025-11-09) x86_64 User : bitnami ( 1000) PHP Version : 8.2.28 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /opt/bitnami/nami/lib/ |
Upload File : |
"use strict";
/*
* Copyright VMware, Inc.
* SPDX-License-Identifier: GPL-2.0-only
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Provisioner = void 0;
const fs = require("fs");
const path = require("path");
const glob = require("glob");
const YAML = require("js-yaml");
const vm = require("vm");
const cloud_1 = require("./cloud");
const index_1 = require("./platform/index");
const stack_definition_1 = require("./stack_definition");
const storage_1 = require("./storage");
const utils_1 = require("./utils");
const download_1 = require("./download");
const logger_1 = require("./logger");
const recipe_runner_1 = require("./recipe_runner");
const download = require("./download");
const utils = require("./utils");
const run_program_1 = require("./run_program");
const provisioner_options_handler_1 = require("./provisioner_options_handler");
/**
* Main class for the provisioner
*/
class Provisioner {
constructor(platform) {
/**
* Password to authenticate to other peers - such as database password
*/
this.peerPassword = "";
/**
* Whether peer password was provided
*/
this.peerPasswordSet = false;
/**
* Whether peer password was generated and should be stored
*/
this.storePeerPassword = false;
/**
* String with public endpoint (IP or FQDN)
*/
this.publicEndpoint = "";
/**
* Resolve host names to IP addresses if set
*/
this.resolveNames = false;
/**
* Cloud account identifier that was specified via CLI
*/
this.cloudAccountId = "";
/**
* Cloud storage account identifier.
*/
this.storageAccountName = "";
/**
* Cloud storage account key.
*/
this.storageAccountKey = "";
/**
* Unique deployment IDs
*/
this.sharedUniqueId = "";
/**
* List of local aliases
*/
this.localAliases = [];
this.options = new provisioner_options_handler_1.ProvisionerOptionsHandler();
/**
* Temporary working directory for modules
*/
this.modulesTempDir = "";
if (platform) {
this._platform = platform;
}
}
/**
* Initialize logger ; called from CLI after arguments are parsed
*/
initializeLogger(options) {
this._logger = new logger_1.WinstonLogger(options);
}
/**
* Instance of Cloud object
*/
get cloud() {
if (!this._cloud) {
this._cloud = cloud_1.createCloud({
cloudName: this.cloudName,
logger: this.logger,
platform: this.platform,
accountId: this.cloudAccountId
});
}
return this._cloud;
}
/**
* Instance of StorageManager, for reading and writing data
*/
get storageManager() {
if (!this._storageManager) {
this._storageManager = new storage_1.StorageManager(this);
}
return this._storageManager;
}
/**
* Stack definition, contains definition of all tiers in a multi-vm deployment
*/
get stackDefinition() {
if (!this._stackDefinition) {
const stackConfig = this.storageManager.getItem("stackconfig");
this._stackDefinition = new stack_definition_1.StackDefinition(this, {
data: stackConfig.data || {}
});
}
return this._stackDefinition;
}
/**
* Returns a unique id based on deployment unique id ; this can be used to generate
* one or more unique IDs that will be the same for same input on all nodes in a deployment
*/
getDerivedSharedUniqueId(prefix, defaultValue) {
if (this.sharedUniqueId && (this.sharedUniqueId !== "")) {
return utils_1.createHashAsHex(`${prefix}-${this.sharedUniqueId}`);
}
else {
return defaultValue;
}
}
/**
* Encode string to have the format of a Fernet key:
* "Fernet key must be 32 url-safe base64-encoded bytes."
* https://cryptography.io/en/latest/_modules/cryptography/fernet/#Fernet.generate_key
* @param str String to encode as fernet key
*/
getDerivedFernetKey(str) {
return Buffer.from(utils_1.createHashAsHex(str).substring(0, 32)).toString("base64");
}
/**
* Returns the deployemnt id common for machines launched as part of the multi-vm deployment
*/
get uniqueDeploymentId() {
return this.getDerivedSharedUniqueId("", "");
}
/**
* Logger instance
*/
get logger() {
if (!this._logger) {
throw new Error("Logger not yet initialized");
}
return this._logger;
}
/**
* Current tier definition (single part of multi-vm deployment)
* Note that it will be cached so that it can be handled from recipes
*/
get tierDefinition() {
if (!this._tierDefinition) {
this._tierDefinition = this.stackDefinition.getTier(this.instanceData);
}
return this._tierDefinition;
}
/**
* Instance of Platform object
*/
get platform() {
if (!this._platform) {
this._platform = index_1.getCurrentPlatform({
platformName: this.platformName,
logger: this.logger,
storageData: this.storageManager.getItem("platform")
});
}
return this._platform;
}
/**
* Perform initialization
*/
async initializeProvisioner() {
if (!this._initialized) {
this.recipeRunner = new recipe_runner_1.RecipeRunner({
provisioner: this,
storageName: "reciperunner"
});
this.recipeRunner.loadInternalDefinitions(this.recipesPath);
this.platform.modifyProcessPath({
add: this.platform.pathInfo.namiRuntimeBinDirectory,
addAtBeginning: true
});
this._initialized = true;
}
}
_moduleTempDir(mod) {
// The modules temp directory might be defined via CLI arguments so we can test local
// modules that are not publicly available yet.
if (!this.modulesTempDir) {
this.modulesTempDir = fs.mkdtempSync("/tmp/provisioner-");
}
return `${this.modulesTempDir}/${mod.baseName}`;
}
/**
* Download and unpack all required modules
*/
async unpackModules(asyncDownload) {
let promises = [];
await this.callRecipes("beforeUnpack", { errorOnFail: true });
for (const m of this.tierDefinition.modules) {
// The expected module temp dir has the form `<modules-temp-dir>/<name>-<version>`
const tempDir = this._moduleTempDir(m);
// If the modules temp directory was provided externally, it might contain the uncompressed
// modules already. If that's the case, we will use them directly.
if (!fs.existsSync(tempDir)) {
const promise = m.downloadAndUnpack(tempDir);
this.logger.info("Downloading", m.name, m.version);
this.logger.debug("Downloading from", m.downloadUrl, "to", tempDir);
if (asyncDownload) {
promises.push(promise);
}
else {
await promise;
}
}
else {
this.logger.info(`Skipping download of ${m.name}-${m.version}. It is already present in ${tempDir}`);
}
}
await Promise.all(promises);
// Write additional files bundled in the definition
for (let filePath in (this.tierDefinition.additionalFiles || {})) {
this.logger.debug(`Creating ${filePath}`);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, Buffer.from(this.tierDefinition.additionalFiles[filePath].data.content, "base64").toString());
fs.chmodSync(filePath, this.tierDefinition.additionalFiles[filePath].data.mode);
}
for (const m of this.tierDefinition.modules) {
const tempDir = this._moduleTempDir(m);
await m.installModuleFromDirectory(tempDir);
}
await this.callRecipes("afterUnpack", { errorOnFail: true });
}
/**
* Perform initial provisioning of the machine
*/
async provisionMachine() {
if (this.cloud.cloudTags.some((tag) => tag === "provisioned")) {
this.logger.info("Skipping provisioning.");
}
else {
this.logger.info("Provisioning machine.");
await this.callRecipes("provisionMachine", { errorOnFail: true });
}
}
/**
* Perform clean up of the machine for creating an image or cloud-specific tarball
*/
async cleanMachine() {
await this.callRecipes("cleanMachine", { errorOnFail: true });
}
/**
* Perform cleanup of the machine after unpacking process
*/
async unpackCleanup() {
await this.callRecipes("unpackCleanup", { errorOnFail: true });
}
/**
* Initialize all modules
*/
async initializeModules() {
try {
await this.callRecipes("beforeInitialize", { errorOnFail: true });
for (const m of this.tierDefinition.modules) {
if (m.skipInitialize) {
this.logger.debug("Skipping initialization for module", m.name);
}
else {
this.logger.info("Initializing module", m.name);
await m.initializeModule();
if (m.startAfterInitialize) {
// Ensure that the service is started with the proper memory configuration when startAfterInitialize is set
if (m.exportsFunction("resize")) {
const mem = this.platform.getMemoryInfo().totalPhysicalMB;
m.runExport("resize", ["--memory", `${mem}M`]);
}
this.logger.debug("Starting as service", m.name);
m.serviceCommand("start");
}
}
}
await this.callRecipes("afterInitialize", { errorOnFail: true });
}
catch (e) {
await this.callRecipes("afterFailedInitialize");
throw e;
}
}
/**
* Initialize settings based on cloud-specific parameters passed ; this allows
* clouds to pass information like tier via user-data
*/
async initializeCloudValues() {
// TODO: can this be moved to recipes somehow? not currently, because
// the conditions for other recipes may depend on tier tags etc
// Shared unique ID
const sharedUniqueId = await this.cloud.getUserData("PROVISIONER_SHARED_UNIQUE_ID_INPUT");
if (sharedUniqueId && (sharedUniqueId !== "")) {
this.logger.info(`Setting unique id from cloud using unique id input`);
this.setSharedUniqueId({
input: sharedUniqueId
});
}
// Peer password or peer password input (seed)
const peerPassword = await this.cloud.getUserData("PROVISIONER_PEER_PASSWORD");
if (peerPassword && (peerPassword !== "")) {
this.logger.info(`Setting peer password from cloud`);
await this.setPeerPassword({
value: peerPassword
});
}
else {
const peerPasswordInput = await this.cloud.getUserData("PROVISIONER_PEER_PASSWORD_INPUT");
if (peerPasswordInput && (peerPasswordInput !== "")) {
this.logger.info(`Setting peer password based on input from cloud`);
await this.setPeerPassword({
input: peerPasswordInput
});
if (!(sharedUniqueId && (sharedUniqueId !== ""))) {
this.logger.info(`Setting unique id from cloud using peer password input`);
this.setSharedUniqueId({
input: peerPasswordInput
});
}
}
}
// App username
const appUsername = await this.cloud.getUserData("PROVISIONER_APP_USERNAME");
if (appUsername && (appUsername !== "")) {
this.logger.info("Setting app username from cloud");
this.appUsername = appUsername;
}
// App password
let appPassword = await this.cloud.getUserData("PROVISIONER_APP_PASSWORD");
// Bitnami Launchpad
if (!appPassword)
appPassword = await this.cloud.getUserData("bitnami-base-password"); // Google LP & launcher
if (!appPassword)
appPassword = await this.cloud.getUserData("bitnami_application_password"); // Others
if (appPassword && (appPassword !== "")) {
this.logger.info("Setting app password from cloud");
this.appPassword = appPassword;
}
// App database
const appDatabase = await this.cloud.getUserData("PROVISIONER_APP_DATABASE");
if (appDatabase && (appDatabase !== "")) {
this.logger.info("Setting app database name from cloud");
this.appDatabase = appDatabase;
}
// Database connection options
const appRepository = await this.cloud.getUserData("PROVISIONER_APP_REPOSITORY");
if (appRepository && (appRepository !== "")) {
this.logger.info("Setting app repository from cloud");
this.appRepository = appRepository;
}
// Enable debug
const enableDebug = await this.cloud.getUserDataBoolean("PROVISIONER_ENABLE_DEBUG");
if (enableDebug) {
this.logger.logLevel = logger_1.LogLevel.TRACE;
}
// Public Endpoint
const publicEndpoint = await this.cloud.getUserData("PROVISIONER_PUBLIC_ENDPOINT");
if (publicEndpoint && (publicEndpoint !== "")) {
this.logger.info("Setting public endpoint from cloud");
this.publicEndpoint = publicEndpoint;
}
// Persistent node
const persistentNode = await this.cloud.getUserData("PROVISIONER_PERSISTENT_NODE");
if (persistentNode && (persistentNode !== "")) {
this.logger.info("Setting persistent node from cloud");
this.persistentNode = persistentNode;
}
// Boolean input
const booleanInput = await this.cloud.getUserData("PROVISIONER_BOOLEAN_INPUT");
if (booleanInput && (booleanInput !== "")) {
this.logger.info("Setting boolean input from cloud");
this.booleanInput = booleanInput;
}
// Config overrides
const configOverrides = await this.cloud.getUserData("PROVISIONER_CONFIG_OVERRIDES");
if (configOverrides && (configOverrides !== "")) {
this.logger.info("Setting config overrides from cloud");
this.configOverrides = configOverrides;
}
// Resolve names
const resolveNames = await this.cloud.getUserDataBoolean("PROVISIONER_RESOLVE_NAMES");
if (resolveNames) {
this.logger.info("Setting resolve names from cloud");
this.resolveNames = true;
}
const storageAccountName = await this.cloud.getUserData("PROVISIONER_STORAGE_ACCOUNT_NAME");
if (storageAccountName) {
this.logger.info("Storage account name detected.");
this.storageAccountName = storageAccountName;
}
const storageAccountKey = await this.cloud.getUserData("PROVISIONER_STORAGE_ACCOUNT_KEY");
if (storageAccountKey) {
this.logger.info("Storage account key detected.");
this.storageAccountKey = storageAccountKey;
}
this.saveConfiguration();
}
/**
* Call first boot recipes and wait for specified amount of seconds if specified
*/
async firstbootDelay() {
await this.callRecipes("firstboot");
if (this.tierDefinition.initializationDelay > 0) {
await utils_1.delayMs(this.tierDefinition.initializationDelay * 1000);
}
}
/**
* Start or stop all services in current tier
* @param cmd Either `start` or `stop`
* @param reverse Whether services should be stopped/started in reverse order
*/
async serviceCommand(cmd, reverse) {
let services = this.tierDefinition.services;
let eventCmd = cmd.substring(0, 1).toUpperCase() + cmd.substring(1);
if (reverse) {
services = services.reverse();
}
// ensure PATH is set properly
this.platform.modifyProcessPath({
add: this.platform.getAdditionalSystemPaths()
});
await this.callRecipes(`before${eventCmd}`);
for (const m of services) {
if (!(m.skipInitialize || m.skipService)) {
this.logger.info("Performing service", cmd, "operation for", m.name);
m.serviceCommand(cmd);
}
else {
this.logger.info("Skipping service", cmd, "operation for", m.name);
}
}
await this.callRecipes(`after${eventCmd}`);
}
get provisionerServiceName() {
return "bitnami";
}
_getValueFromInputOrValue(options, prefix) {
let result;
if (options.input) {
result = utils_1.createHashAsHex(`${prefix || ""}${options.input}`);
}
else if (options.value) {
result = options.value;
}
else {
this.logger.warn("Unable to determine value - none of input and value specified");
}
return result;
}
/**
* Generate hash for peerPassword based on any input string ; this can be used to pass
* random, pseudo-random or unique data to all tiers and generate usable password from it
*/
async setPeerPassword(options) {
this.peerPassword = this._getValueFromInputOrValue(options);
if (options.input) {
// if the password was generated from input, store it if this node is
// the one user accesses or store on all nodes for automated tests
this.storePeerPassword = true;
}
this.peerPasswordSet = true;
}
/**
* Log an error message if peer password was not set
*/
warnIfPeerPasswordNotSet() {
if (!this.peerPasswordSet) {
this.logger.warn("Peer password was not set - using default value");
}
}
/**
* Load configuration from StorageManager ; this will overwrite all current configuration
* with the one stored on disk
*/
loadConfiguration() {
const config = this.storageManager.getItem("configuration");
this._copyConfiguration(config.data, this, {
cloudName: null,
platformName: null,
cloudAccountId: ""
});
}
/**
* Save configuration to disk
*/
saveConfiguration() {
this.logger.info("Saving configuration info to disk");
const config = this.storageManager.getItem("configuration");
this._copyConfiguration(this, config.data, {});
config.save();
}
_copyConfiguration(from, to, defaults) {
for (const opt of [
"appUsername",
"appPassword",
"appDatabase",
"appRepository",
"persistentNode",
"booleanInput",
"configOverrides",
"resolveNames",
"peerPassword",
"peerPasswordSet",
"publicEndpoint",
"storePeerPassword",
"sharedUniqueId",
"platformName",
"cloudName",
"cloudAccountId",
"storageAccountName",
"storageAccountKey",
"provisioned"
]) {
to[opt] = from[opt] || defaults[opt];
}
}
_parseDefinition(body) {
const stackConfig = this.storageManager.getItem("stackconfig");
let data;
try {
if (body.match("^\s*\{")) {
data = JSON.parse(body);
}
else if (body.match("^\s*--")) {
data = YAML.load(body);
}
else {
throw new Error("unable to detect file format");
}
}
catch (e) {
throw new Error("Unable to parse stack definition: " + e.message);
}
stackConfig.data = data;
stackConfig.save();
}
/**
* Download stack definition from a remote URL and parse it
*/
async downloadDefinition(url) {
// TODO: i18n and encoding?
const result = await new download_1.Download({
returnData: true
}).download(url);
// TODO: errors ?
this._parseDefinition(result.body);
}
/**
* Load stack definition from a file and parse it
*/
loadDefinition(fileName) {
const contents = fs.readFileSync(fileName).toString();
this._parseDefinition(contents);
}
/**
* Evaluate code in a sandbox TODO
*/
evalInContext(code, sandboxInfo) {
let context = vm.createContext(Object.assign({
fs: fs,
path: path,
glob: glob,
Promise: Promise,
RegExp: RegExp,
console: console,
process: process,
require: require,
utils: utils,
download: download,
runProgram: run_program_1.runProgram,
provisioner: this,
platform: this.platform,
global: global,
logger: this.logger,
cloud: this.cloud,
recipes: this.recipeRunner,
}, sandboxInfo || {}));
return new vm.Script(code).runInContext(context);
}
/**
* Wrapper for recipeRunner that properly handles skipRecipes and onlyRecipes options
*/
async callRecipes(eventName, options) {
options = Object.assign({}, options || {});
options.skip = options.skip || this.skipRecipes;
options.only = options.only || this.onlyRecipes;
const result = await this.recipeRunner.call(eventName, options);
if (result.failed.length > 0) {
this.logger.warn(`Unable to run provisioner recipes for ${eventName}`);
}
return result;
}
_valueOrDefault(value, defaultValue) {
value = value || "";
if (value !== "") {
return value;
}
else {
return defaultValue;
}
}
/**
* Configured or default peer password
*/
get defaultOrPeerPassword() {
return this._valueOrDefault(this.peerPassword, this.stackDefinition.defaultPeerPassword);
}
/**
* Configured or default username
*/
get defaultOrAppUsername() {
return this._valueOrDefault(this.appUsername, this.stackDefinition.defaultUsername);
}
/**
* Configured or default password
*/
get defaultOrAppPassword() {
return this._valueOrDefault(this.appPassword, this.stackDefinition.defaultPassword);
}
/**
* Configured or default database name
*/
get defaultOrAppDatabase() {
return this._valueOrDefault(this.appDatabase, this.stackDefinition.defaultDatabase);
}
/**
* Configured or default persistent node
*/
get defaultOrPersistentNode() {
return this._valueOrDefault(this.persistentNode, this.stackDefinition.defaultPersistentNode);
}
/**
* Configured or default boolean input
*/
get defaultOrBooleanInput() {
return this._valueOrDefault(this.booleanInput, this.stackDefinition.defaultBooleanInput);
}
/**
* Configured or default config overrides
*/
get defaultOrConfigOverrides() {
return this._valueOrDefault(this.configOverrides, this.stackDefinition.defaultConfigOverrides);
}
/**
* Configured or default public endpoint
*/
get defaultOrPublicEndpoint() {
return this._valueOrDefault(this.publicEndpoint, this.stackDefinition.defaultPublicEndpoint);
}
/**
* Get list of local aliases
*/
appendLocalAliases(aliases) {
if (aliases.length > 0) {
this.localAliases = this.localAliases.concat(aliases);
}
else {
throw new Error("Empty list of aliases");
}
}
/**
* Get local hostname to use
*/
get localAddressHostname() {
// TODO: consider reading from definition file
return "provisioner-local";
}
/**
* Indicate first boot finish status
*/
async firstbootFinished(success) {
fs.writeFileSync(`${this.platform.pathInfo.namiAppPath}/.firstboot.status`, success ? "true" : "false");
if (success) {
await this.callRecipes("afterFirstboot");
}
else {
await this.callRecipes("afterFailedFirstboot");
}
}
/**
* Generate random password when deploying as single-vm ; only done if
* application has a password and it was not provided via cloud values or via CLI during firstboot
*/
async setRandomPasswordIfNotProvided() {
if ((((!this.appPassword) || (this.appPassword === "")) &&
(this.stackDefinition.defaultPassword && (this.stackDefinition.defaultPassword !== "")))) {
this.appPassword = utils_1.generateRandomPassword(12);
const message = "\n" +
"##################################################################\n" +
"# #\n" +
`# Setting Bitnami application password to '${this.appPassword}' #\n` +
"# #\n" +
"##################################################################\n" +
"\n";
this.logger.info(message);
await this.cloud.writeToConsole(message);
}
}
/**
* Remove app password and save configuration
*/
removeAppPassword() {
this.appPassword = "";
this.saveConfiguration();
}
/**
* Remove peer password and save configuration
*/
removePeerPassword() {
this.peerPassword = "";
this.saveConfiguration();
}
/**
* Generate unique deployment id from input specified from external source
*/
setSharedUniqueId(options) {
this.sharedUniqueId = this._getValueFromInputOrValue(options, "uniqueid-");
}
/**
* Unpack a tarball with any additional files to be used for testing
*/
async unpackCustomFiles() {
const optionPrefix = "provisioner_custom_files";
if (this.options.provided(`${optionPrefix}_tarball`)) {
const url = this.options.getValue(`${optionPrefix}_tarball`);
const prefix = this.options.getValue(`${optionPrefix}_prefix`, "/");
this.logger.info(`Downloading ${url} and unpacking to ${prefix}`);
await new download_1.Download({ unpackDirectory: prefix }).download(url);
}
}
}
exports.Provisioner = Provisioner;