var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
/**
 * Copyright (C) 2024 Hedera Hashgraph, 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
 *
 *      http://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.
 *
 */
import * as fs from 'fs';
import { Listr } from 'listr2';
import * as path from 'path';
import { SoloError, IllegalArgumentError, MissingArgumentError } from './errors.js';
import * as constants from './constants.js';
import { ConfigManager } from './config_manager.js';
import { K8 } from './k8.js';
import { Templates } from './templates.js';
import { Flags as flags } from '../commands/flags.js';
import * as Base64 from 'js-base64';
import chalk from 'chalk';
import { SoloLogger } from './logging.js';
import { Duration } from './time/duration.js';
import { sleep } from './helpers.js';
import { inject, injectable } from 'tsyringe-neo';
import { patchInject } from './container_helper.js';
/** PlatformInstaller install platform code in the root-container of a network pod */
let PlatformInstaller = class PlatformInstaller {
    logger;
    k8;
    configManager;
    constructor(logger, k8, configManager) {
        this.logger = logger;
        this.k8 = k8;
        this.configManager = configManager;
        this.logger = patchInject(logger, SoloLogger, this.constructor.name);
        this.k8 = patchInject(k8, K8, this.constructor.name);
        this.configManager = patchInject(configManager, ConfigManager, this.constructor.name);
    }
    _getNamespace() {
        const ns = this.configManager.getFlag(flags.namespace);
        if (!ns)
            throw new MissingArgumentError('namespace is not set');
        return ns;
    }
    validatePlatformReleaseDir(releaseDir) {
        if (!releaseDir)
            throw new MissingArgumentError('releaseDir is required');
        if (!fs.existsSync(releaseDir)) {
            throw new IllegalArgumentError('releaseDir does not exists', releaseDir);
        }
        const dataDir = `${releaseDir}/data`;
        const appsDir = `${releaseDir}/${constants.HEDERA_DATA_APPS_DIR}`;
        const libDir = `${releaseDir}/${constants.HEDERA_DATA_LIB_DIR}`;
        if (!fs.existsSync(dataDir)) {
            throw new IllegalArgumentError('releaseDir does not have data directory', releaseDir);
        }
        if (!fs.existsSync(appsDir)) {
            throw new IllegalArgumentError(`'${constants.HEDERA_DATA_APPS_DIR}' missing in '${releaseDir}'`, releaseDir);
        }
        if (!fs.existsSync(libDir)) {
            throw new IllegalArgumentError(`'${constants.HEDERA_DATA_LIB_DIR}' missing in '${releaseDir}'`, releaseDir);
        }
        // @ts-ignore
        if (!fs.statSync(appsDir).isEmpty()) {
            throw new IllegalArgumentError(`'${constants.HEDERA_DATA_APPS_DIR}' is empty in releaseDir: ${releaseDir}`, releaseDir);
        }
        // @ts-ignore
        if (!fs.statSync(libDir).isEmpty()) {
            throw new IllegalArgumentError(`'${constants.HEDERA_DATA_LIB_DIR}' is empty in releaseDir: ${releaseDir}`, releaseDir);
        }
    }
    /** Fetch and extract platform code into the container */
    async fetchPlatform(podName, tag) {
        if (!podName)
            throw new MissingArgumentError('podName is required');
        if (!tag)
            throw new MissingArgumentError('tag is required');
        try {
            const scriptName = 'extract-platform.sh';
            const sourcePath = path.join(constants.RESOURCES_DIR, scriptName); // script source path
            await this.copyFiles(podName, [sourcePath], constants.HEDERA_USER_HOME_DIR);
            // wait a few seconds before calling the script to avoid "No such file" error
            await sleep(Duration.ofSeconds(2));
            const extractScript = path.join(constants.HEDERA_USER_HOME_DIR, scriptName); // inside the container
            await this.k8.execContainer(podName, constants.ROOT_CONTAINER, `chmod +x ${extractScript}`);
            await this.k8.execContainer(podName, constants.ROOT_CONTAINER, [extractScript, tag]);
            return true;
        }
        catch (e) {
            const message = `failed to extract platform code in this pod '${podName}': ${e.message}`;
            this.logger.error(message, e);
            throw new SoloError(message, e);
        }
    }
    /**
     * Copy a list of files to a directory in the container
     * @param podName
     * @param srcFiles - list of source files
     * @param destDir - destination directory
     * @param [container] - name of the container
     * @returns a list of pathso of the copied files insider the container
     */
    async copyFiles(podName, srcFiles, destDir, container = constants.ROOT_CONTAINER) {
        try {
            const copiedFiles = [];
            // prepare the file mapping
            for (const srcPath of srcFiles) {
                if (!fs.existsSync(srcPath)) {
                    throw new SoloError(`file does not exist: ${srcPath}`);
                }
                if (!(await this.k8.hasDir(podName, container, destDir))) {
                    await this.k8.mkdir(podName, container, destDir);
                }
                this.logger.debug(`Copying file into ${podName}: ${srcPath} -> ${destDir}`);
                await this.k8.copyTo(podName, container, srcPath, destDir);
                const fileName = path.basename(srcPath);
                copiedFiles.push(path.join(destDir, fileName));
            }
            return copiedFiles;
        }
        catch (e) {
            throw new SoloError(`failed to copy files to pod '${podName}': ${e.message}`, e);
        }
    }
    async copyGossipKeys(nodeAlias, stagingDir, nodeAliases) {
        if (!nodeAlias)
            throw new MissingArgumentError('nodeAlias is required');
        if (!stagingDir)
            throw new MissingArgumentError('stagingDir is required');
        if (!nodeAliases || nodeAliases.length <= 0)
            throw new MissingArgumentError('nodeAliases cannot be empty');
        try {
            const srcFiles = [];
            // copy private keys for the node
            srcFiles.push(path.join(stagingDir, 'keys', Templates.renderGossipPemPrivateKeyFile(nodeAlias)));
            // copy all public keys for all nodes
            nodeAliases.forEach(nodeAlias => {
                srcFiles.push(path.join(stagingDir, 'keys', Templates.renderGossipPemPublicKeyFile(nodeAlias)));
            });
            const data = {};
            for (const srcFile of srcFiles) {
                const fileContents = fs.readFileSync(srcFile);
                const fileName = path.basename(srcFile);
                // @ts-ignore
                data[fileName] = Base64.encode(fileContents);
            }
            if (!(await this.k8.createSecret(Templates.renderGossipKeySecretName(nodeAlias), this._getNamespace(), 'Opaque', data, Templates.renderGossipKeySecretLabelObject(nodeAlias), true))) {
                throw new SoloError(`failed to create secret for gossip keys for node '${nodeAlias}'`);
            }
        }
        catch (e) {
            this.logger.error(`failed to copy gossip keys to secret '${Templates.renderGossipKeySecretName(nodeAlias)}': ${e.message}`, e);
            throw new SoloError(`failed to copy gossip keys to secret '${Templates.renderGossipKeySecretName(nodeAlias)}': ${e.message}`, e);
        }
    }
    async copyTLSKeys(nodeAliases, stagingDir) {
        if (!nodeAliases || nodeAliases.length <= 0)
            throw new MissingArgumentError('nodeAliases cannot be empty');
        if (!stagingDir)
            throw new MissingArgumentError('stagingDir is required');
        try {
            const data = {};
            for (const nodeAlias of nodeAliases) {
                const srcFiles = [];
                srcFiles.push(path.join(stagingDir, 'keys', Templates.renderTLSPemPrivateKeyFile(nodeAlias)));
                srcFiles.push(path.join(stagingDir, 'keys', Templates.renderTLSPemPublicKeyFile(nodeAlias)));
                for (const srcFile of srcFiles) {
                    const fileContents = fs.readFileSync(srcFile);
                    const fileName = path.basename(srcFile);
                    // @ts-ignore
                    data[fileName] = Base64.encode(fileContents);
                }
            }
            if (!(await this.k8.createSecret('network-node-hapi-app-secrets', this._getNamespace(), 'Opaque', data, undefined, true))) {
                throw new SoloError('failed to create secret for TLS keys');
            }
        }
        catch (e) {
            this.logger.error('failed to copy TLS keys to secret', e);
            throw new SoloError('failed to copy TLS keys to secret', e);
        }
    }
    async setPathPermission(podName, destPath, mode = '0755', recursive = true, container = constants.ROOT_CONTAINER) {
        if (!podName)
            throw new MissingArgumentError('podName is required');
        if (!destPath)
            throw new MissingArgumentError('destPath is required');
        const recursiveFlag = recursive ? '-R' : '';
        await this.k8.execContainer(podName, container, [
            'bash',
            '-c',
            `chown ${recursiveFlag} hedera:hedera ${destPath} 2>/dev/null || true`,
        ]);
        await this.k8.execContainer(podName, container, [
            'bash',
            '-c',
            `chmod ${recursiveFlag} ${mode} ${destPath} 2>/dev/null || true`,
        ]);
        return true;
    }
    async setPlatformDirPermissions(podName) {
        const self = this;
        if (!podName)
            throw new MissingArgumentError('podName is required');
        try {
            const destPaths = [constants.HEDERA_HAPI_PATH, constants.HEDERA_HGCAPP_DIR];
            for (const destPath of destPaths) {
                await self.setPathPermission(podName, destPath);
            }
            return true;
        }
        catch (e) {
            throw new SoloError(`failed to set permission in '${podName}'`, e);
        }
    }
    /** Return a list of task to perform node directory setup */
    taskSetup(podName, stagingDir, isGenesis) {
        const self = this;
        return new Listr([
            {
                title: 'Copy configuration files',
                task: async () => await self.copyConfigurationFiles(stagingDir, podName, isGenesis),
            },
            {
                title: 'Set file permissions',
                task: async () => await self.setPlatformDirPermissions(podName),
            },
        ], {
            concurrent: false,
            rendererOptions: {
                collapseSubtasks: false,
            },
        });
    }
    /**
     * Copy configuration files to the network consensus node pod
     * @param stagingDir - staging directory path
     * @param podName - network consensus node pod name
     * @param isGenesis - true if this is `solo node setup` and we are at genesis
     * @private
     */
    async copyConfigurationFiles(stagingDir, podName, isGenesis) {
        if (isGenesis) {
            const genesisNetworkJson = [path.join(stagingDir, 'genesis-network.json')];
            await this.copyFiles(podName, genesisNetworkJson, `${constants.HEDERA_HAPI_PATH}/data/config`);
        }
    }
    /**
     * Return a list of task to copy the node keys to the staging directory
     *
     * It assumes the staging directory has the following files and resources:
     * <li>${staging}/keys/s-public-<nodeAlias>.pem: private signing key for a node</li>
     * <li>${staging}/keys/s-private-<nodeAlias>.pem: public signing key for a node</li>
     * <li>${staging}/keys/a-public-<nodeAlias>.pem: private agreement key for a node</li>
     * <li>${staging}/keys/a-private-<nodeAlias>.pem: public agreement key for a node</li>
     * <li>${staging}/keys/hedera-<nodeAlias>.key: gRPC TLS key for a node</li>
     * <li>${staging}/keys/hedera-<nodeAlias>.crt: gRPC TLS cert for a node</li>
     *
     * @param stagingDir staging directory path
     * @param nodeAliases list of node ids
     */
    copyNodeKeys(stagingDir, nodeAliases) {
        const self = this;
        const subTasks = [];
        subTasks.push({
            title: 'Copy TLS keys',
            task: async () => await self.copyTLSKeys(nodeAliases, stagingDir),
        });
        for (const nodeAlias of nodeAliases) {
            subTasks.push({
                title: `Node: ${chalk.yellow(nodeAlias)}`,
                task: () => new Listr([
                    {
                        title: 'Copy Gossip keys',
                        task: async () => await self.copyGossipKeys(nodeAlias, stagingDir, nodeAliases),
                    },
                ], {
                    concurrent: false,
                    rendererOptions: {
                        collapseSubtasks: false,
                    },
                }),
            });
        }
        return subTasks;
    }
};
PlatformInstaller = __decorate([
    injectable(),
    __param(0, inject(SoloLogger)),
    __param(1, inject(K8)),
    __param(2, inject(ConfigManager)),
    __metadata("design:paramtypes", [SoloLogger,
        K8,
        ConfigManager])
], PlatformInstaller);
export { PlatformInstaller };
//# sourceMappingURL=platform_installer.js.map