"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.deleteBucket = exports.deleteBucketDirectory = exports.copyFileInBucket = exports.getFileFromBucket = exports.storeFileToBucket = exports.getBucketDirectoryStructure = exports.createAirnodeBucket = exports.getAirnodeBucket = void 0;
const promise_utils_1 = require("@api3/promise-utils");
const storage_1 = require("@google-cloud/storage");
const infrastructure_1 = require("../utils/infrastructure");
const logger = __importStar(require("../utils/logger"));
const initializeGcsService = () => new storage_1.Storage();
const initializeGcsBucket = (bucketName) => initializeGcsService().bucket(bucketName);
const getAirnodeBucket = async () => {
    const storage = initializeGcsService();
    logger.debug('Listing GCS buckets');
    const goBuckets = await (0, promise_utils_1.go)(() => storage.getBuckets());
    if (!goBuckets.success) {
        throw new Error(`Failed to list GCS buckets: ${goBuckets.error}`);
    }
    const [buckets] = goBuckets.data;
    const airnodeBuckets = buckets.filter((bucket) => bucket.name.match(infrastructure_1.BUCKET_NAME_REGEX));
    if (airnodeBuckets.length > 1) {
        throw new Error(`Multiple Airnode buckets found, stopping. Buckets: ${JSON.stringify(airnodeBuckets)}`);
    }
    const bucket = airnodeBuckets[0];
    if (!bucket) {
        logger.debug('No Airnode GCS bucket found');
        return null;
    }
    const goMetadata = await (0, promise_utils_1.go)(() => bucket.getMetadata());
    if (!goMetadata.success) {
        throw new Error(`Failed to fetch metadata for bucket '${bucket.name}': ${goMetadata.error}`);
    }
    return {
        name: bucket.name,
        region: goMetadata.data[0].location,
    };
};
exports.getAirnodeBucket = getAirnodeBucket;
const createAirnodeBucket = async (cloudProvider) => {
    const storage = initializeGcsService();
    const bucketName = (0, infrastructure_1.generateBucketName)();
    logger.debug(`Creating GCS bucket '${bucketName}' in ${cloudProvider.region}`);
    const goCreate = await (0, promise_utils_1.go)(() => storage.createBucket(bucketName, { location: cloudProvider.region }));
    if (!goCreate.success) {
        throw new Error(`Failed to create an GCS bucket: ${goCreate.error}`);
    }
    const [bucket] = goCreate.data;
    // Setting uniform bucket-level access
    // https://cloud.google.com/storage/docs/uniform-bucket-level-access
    logger.debug(`Setting uniform bucket-level access for GCS bucket '${bucketName}'`);
    const goMetadata = await (0, promise_utils_1.go)(() => bucket.setMetadata({
        iamConfiguration: { uniformBucketLevelAccess: { enabled: true }, publicAccessPrevention: 'enforced' },
    }));
    if (!goMetadata.success) {
        throw new Error(`Failed to setup a uniform bucket-level access for bucket '${bucketName}': ${goMetadata.error}`);
    }
    // Adding roles that are by default added when adding bucket via GCP Console. Some are missing when creating bucket via SDK,
    // causing user not being able to see the bucket content in the GCP Console.
    const policy = {
        bindings: [
            {
                role: 'roles/storage.legacyBucketReader',
                members: [`projectViewer:${cloudProvider.projectId}`],
            },
            {
                role: 'roles/storage.legacyBucketOwner',
                members: [`projectEditor:${cloudProvider.projectId}`, `projectOwner:${cloudProvider.projectId}`],
            },
            {
                role: 'roles/storage.legacyObjectReader',
                members: [`projectViewer:${cloudProvider.projectId}`],
            },
            {
                role: 'roles/storage.legacyObjectOwner',
                members: [`projectEditor:${cloudProvider.projectId}`, `projectOwner:${cloudProvider.projectId}`],
            },
        ],
    };
    logger.debug(`Setting necessary IAM policy for GCS bucket '${bucketName}'`);
    const goPolicy = await (0, promise_utils_1.go)(() => bucket.iam.setPolicy(policy));
    if (!goPolicy.success) {
        throw new Error(`Failed to setup IAM policy for bucket '${bucketName}': ${goPolicy.error}`);
    }
    return {
        name: bucketName,
        region: cloudProvider.region,
    };
};
exports.createAirnodeBucket = createAirnodeBucket;
const getBucketDirectoryStructure = async (bucket) => {
    const { name: bucketName } = bucket;
    const gcsBucket = initializeGcsBucket(bucketName);
    const goGet = await (0, promise_utils_1.go)(() => gcsBucket.getFiles());
    if (!goGet.success) {
        throw new Error(`Failed to list content of bucket '${bucketName}': ${goGet.error}`);
    }
    return (0, infrastructure_1.translatePathsToDirectoryStructure)(goGet.data[0].map((file) => file.name));
};
exports.getBucketDirectoryStructure = getBucketDirectoryStructure;
const storeFileToBucket = async (bucket, bucketFilePath, filePath) => {
    const { name: bucketName } = bucket;
    const gcsBucket = initializeGcsBucket(bucketName);
    logger.debug(`Storing file '${filePath}' as '${bucketFilePath}' to GCS bucket '${bucketName}'`);
    const goSave = await (0, promise_utils_1.go)(() => gcsBucket.upload(filePath, { destination: bucketFilePath }));
    if (!goSave.success) {
        throw new Error(`Failed to store file '${filePath}' to GCS bucket '${bucketName}': ${goSave.error}`);
    }
};
exports.storeFileToBucket = storeFileToBucket;
const getFileFromBucket = async (bucket, filePath) => {
    const { name: bucketName } = bucket;
    const gcsBucket = initializeGcsBucket(bucketName);
    const file = gcsBucket.file(filePath);
    logger.debug(`Fetching file '${filePath}' from GCS bucket '${bucketName}'`);
    const goDownload = await (0, promise_utils_1.go)(() => file.download());
    if (!goDownload.success) {
        throw new Error(`Failed to fetch file '${filePath}' from GCS bucket '${bucketName}': ${goDownload.error}`);
    }
    return goDownload.data[0].toString('utf-8');
};
exports.getFileFromBucket = getFileFromBucket;
const copyFileInBucket = async (bucket, fromFilePath, toFilePath) => {
    const { name: bucketName } = bucket;
    const gcsBucket = initializeGcsBucket(bucketName);
    const file = gcsBucket.file(fromFilePath);
    logger.debug(`Copying file '${fromFilePath}' to file '${toFilePath}' within GCS bucket '${bucketName}'`);
    const goCopy = await (0, promise_utils_1.go)(() => file.copy(toFilePath));
    if (!goCopy.success) {
        throw new Error(`Failed to copy file '${fromFilePath}' to file '${toFilePath}' within GCS bucket '${bucketName}': ${goCopy.error}`);
    }
};
exports.copyFileInBucket = copyFileInBucket;
const gatherBucketKeys = (directory) => [
    ...Object.values(directory.children).reduce((results, fsItem) => [
        ...results,
        ...(fsItem.type === infrastructure_1.FileSystemType.File ? [fsItem.bucketKey] : gatherBucketKeys(fsItem)),
    ], []),
];
const deleteBucketDirectory = async (bucket, directory) => {
    const { name: bucketName } = bucket;
    const gcsBucket = initializeGcsBucket(bucketName);
    const bucketKeys = gatherBucketKeys(directory);
    logger.debug(`Deleting files from GCS bucket '${bucketName}': ${JSON.stringify(bucketKeys)}`);
    for (const bucketKey of bucketKeys.reverse()) {
        // I could use Bucket.deleteFiles() instead but you can't list the files to be deleted and it makes multiple API calls anyway
        logger.debug(`Deleting file '${gcsBucket}' from GCS bucket '${bucketName}'`);
        const goDelete = await (0, promise_utils_1.go)(() => gcsBucket.file(bucketKey).delete());
        if (!goDelete.success) {
            throw new Error(`Failed to delete bucket file '${bucketKey}': ${goDelete.error}`);
        }
    }
};
exports.deleteBucketDirectory = deleteBucketDirectory;
const deleteBucket = async (bucket) => {
    const { name: bucketName } = bucket;
    const gcsBucket = initializeGcsBucket(bucketName);
    logger.debug(`Deleting GCS bucket '${bucketName}'`);
    const goDelete = await (0, promise_utils_1.go)(() => gcsBucket.delete());
    if (!goDelete.success) {
        throw new Error(`Failed to delete GCS bucket '${bucketName}': ${goDelete.error}`);
    }
};
exports.deleteBucket = deleteBucket;
//# sourceMappingURL=gcp.js.map