"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;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
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 fs = __importStar(require("fs"));
const client_s3_1 = require("@aws-sdk/client-s3");
const concat_1 = __importDefault(require("lodash/concat"));
const compact_1 = __importDefault(require("lodash/compact"));
const promise_utils_1 = require("@api3/promise-utils");
const logger = __importStar(require("../utils/logger"));
const infrastructure_1 = require("../utils/infrastructure");
const DEFAULT_AWS_REGION = 'us-east-1';
const initializeS3Service = (region) => {
    return new client_s3_1.S3Client({ region });
};
const getAirnodeBucket = async () => {
    // We're using a default region here because we don't know where is the bucket stored at this point.
    const s3 = initializeS3Service(DEFAULT_AWS_REGION);
    logger.debug('Listing S3 buckets');
    const bucketsCommand = new client_s3_1.ListBucketsCommand({});
    const goBuckets = await (0, promise_utils_1.go)(() => s3.send(bucketsCommand));
    if (!goBuckets.success) {
        throw new Error(`Failed to list S3 buckets: ${goBuckets.error}`);
    }
    const airnodeBuckets = goBuckets.data.Buckets?.filter((bucket) => bucket.Name?.match(infrastructure_1.BUCKET_NAME_REGEX));
    if (airnodeBuckets && airnodeBuckets.length > 1) {
        throw new Error(`Multiple Airnode buckets found, stopping. Buckets: ${JSON.stringify(airnodeBuckets)}`);
    }
    const bucketName = airnodeBuckets?.[0]?.Name;
    if (!bucketName) {
        logger.debug('No Airnode S3 bucket found');
        return null;
    }
    const bucketLocationCommand = new client_s3_1.GetBucketLocationCommand({ Bucket: bucketName });
    const goBucketLocation = await (0, promise_utils_1.go)(() => s3.send(bucketLocationCommand));
    if (!goBucketLocation.success) {
        throw new Error(`Failed to get location for bucket '${bucketName}': ${goBucketLocation.error}`);
    }
    let region = goBucketLocation.data.LocationConstraint;
    // The `EU` option is listed as a possible one in the documentation
    // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html#API_GetBucketLocation_ResponseElements
    if (region === 'EU') {
        throw new Error(`Unknown bucket region '${region}'`);
    }
    // The documentation says that for buckets in the `us-east-1` region the value of `LocationConstraint` is null but it is actually undefined
    // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html#API_GetBucketLocation_ResponseElements
    if (region === undefined) {
        region = 'us-east-1';
    }
    return {
        name: bucketName,
        region,
    };
};
exports.getAirnodeBucket = getAirnodeBucket;
const createAirnodeBucket = async (cloudProvider) => {
    // If there's no Airnode bucket already available we create it in the region where the Airnode resources will be deployed
    const s3 = initializeS3Service(cloudProvider.region);
    const bucketName = (0, infrastructure_1.generateBucketName)();
    let createParams = { Bucket: bucketName };
    // If the region is `us-east-1` the configuration must be empty...
    // https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html#API_CreateBucket_RequestBody
    if (cloudProvider.region !== 'us-east-1') {
        createParams = { ...createParams, CreateBucketConfiguration: { LocationConstraint: cloudProvider.region } };
    }
    logger.debug(`Creating S3 bucket '${bucketName}' in '${cloudProvider.region}'`);
    const createCommand = new client_s3_1.CreateBucketCommand(createParams);
    const goCreate = await (0, promise_utils_1.go)(() => s3.send(createCommand));
    if (!goCreate.success) {
        throw new Error(`Failed to create an S3 bucket: ${goCreate.error}`);
    }
    // Enable bucket encryption
    logger.debug(`Setting encryption for S3 bucket '${bucketName}'`);
    const putEncryptionCommand = new client_s3_1.PutBucketEncryptionCommand({
        Bucket: bucketName,
        ServerSideEncryptionConfiguration: {
            Rules: [{ ApplyServerSideEncryptionByDefault: { SSEAlgorithm: 'AES256' }, BucketKeyEnabled: true }],
        },
    });
    const goPutEncryption = await (0, promise_utils_1.go)(() => s3.send(putEncryptionCommand));
    if (!goPutEncryption.success) {
        throw new Error(`Failed to enable encryption for bucket '${bucketName}': ${goPutEncryption.error}`);
    }
    // Blocking public access to the bucket
    // https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html
    logger.debug(`Setting public access block for S3 bucket '${bucketName}'`);
    const putPublicAccessBlockCommand = new client_s3_1.PutPublicAccessBlockCommand({
        Bucket: bucketName,
        PublicAccessBlockConfiguration: {
            BlockPublicAcls: true,
            BlockPublicPolicy: true,
            IgnorePublicAcls: true,
            RestrictPublicBuckets: true,
        },
    });
    const goPutPublicAccessBlock = await (0, promise_utils_1.go)(() => s3.send(putPublicAccessBlockCommand));
    if (!goPutPublicAccessBlock.success) {
        throw new Error(`Failed to setup a public access block for bucket '${bucketName}': ${goPutPublicAccessBlock.error}`);
    }
    return {
        name: bucketName,
        region: cloudProvider.region,
    };
};
exports.createAirnodeBucket = createAirnodeBucket;
const getBucketDirectoryStructure = async (bucket) => {
    const { name: bucketName, region: bucketRegion } = bucket;
    const s3 = initializeS3Service(bucketRegion);
    let paths = [];
    let truncated = true;
    let listParams = {
        Bucket: bucketName,
    };
    while (truncated) {
        logger.debug(`Listing objects for S3 bucket '${bucketName}'`);
        const listCommand = new client_s3_1.ListObjectsV2Command(listParams);
        const goList = await (0, promise_utils_1.go)(() => s3.send(listCommand));
        if (!goList.success) {
            throw new Error(`Failed to list content of bucket '${bucketName}': ${goList.error}`);
        }
        paths = (0, concat_1.default)(paths, (0, compact_1.default)(goList.data.Contents?.map((content) => content.Key)));
        truncated = !!goList.data.IsTruncated;
        if (goList.data.ContinuationToken) {
            listParams = { ...listParams, ContinuationToken: goList.data.ContinuationToken };
        }
    }
    return (0, infrastructure_1.translatePathsToDirectoryStructure)(paths);
};
exports.getBucketDirectoryStructure = getBucketDirectoryStructure;
const storeFileToBucket = async (bucket, bucketFilePath, filePath) => {
    const { name: bucketName, region: bucketRegion } = bucket;
    const s3 = initializeS3Service(bucketRegion);
    logger.debug(`Storing file '${filePath}' as '${bucketFilePath}' to S3 bucket '${bucketName}'`);
    const putCommand = new client_s3_1.PutObjectCommand({
        Bucket: bucketName,
        Key: bucketFilePath,
        Body: fs.readFileSync(filePath, { encoding: 'utf-8' }),
    });
    const goPut = await (0, promise_utils_1.go)(() => s3.send(putCommand));
    if (!goPut.success) {
        throw new Error(`Failed to store file '${filePath}' to S3 bucket '${bucketName}': ${goPut.error}`);
    }
};
exports.storeFileToBucket = storeFileToBucket;
const getFileFromBucket = async (bucket, filePath) => {
    const { name: bucketName, region: bucketRegion } = bucket;
    const s3 = initializeS3Service(bucketRegion);
    logger.debug(`Fetching file '${filePath}' from S3 bucket '${bucketName}'`);
    const getCommand = new client_s3_1.GetObjectCommand({ Bucket: bucketName, Key: filePath });
    const goGet = await (0, promise_utils_1.go)(() => s3.send(getCommand));
    if (!goGet.success) {
        throw new Error(`Failed to fetch file '${filePath}' from S3 bucket '${bucketName}': ${goGet.error}`);
    }
    if (!goGet.data.Body) {
        throw new Error(`The response for file '${filePath}' from S3 bucket '${bucketName}' contained an empty body`);
    }
    const goFileContent = await (0, promise_utils_1.go)(() => goGet.data.Body.transformToString('utf-8'));
    if (!goFileContent.success) {
        throw new Error(`The response for file '${filePath}' from S3 bucket '${bucketName}' is not parsable`);
    }
    return goFileContent.data;
};
exports.getFileFromBucket = getFileFromBucket;
const copyFileInBucket = async (bucket, fromFilePath, toFilePath) => {
    const { name: bucketName, region: bucketRegion } = bucket;
    const s3 = initializeS3Service(bucketRegion);
    logger.debug(`Copying file '${fromFilePath}' to file '${toFilePath}' within S3 bucket '${bucketName}'`);
    const copyCommand = new client_s3_1.CopyObjectCommand({
        Bucket: bucketName,
        CopySource: `/${bucketName}/${fromFilePath}`,
        Key: toFilePath,
    });
    const goCopy = await (0, promise_utils_1.go)(() => s3.send(copyCommand));
    if (!goCopy.success) {
        throw new Error(`Failed to copy file '${fromFilePath}' to file '${toFilePath}' within S3 bucket '${bucketName}': ${goCopy.error}`);
    }
};
exports.copyFileInBucket = copyFileInBucket;
const gatherBucketKeys = (directory) => [
    directory.bucketKey,
    ...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, region: bucketRegion } = bucket;
    const s3 = initializeS3Service(bucketRegion);
    const bucketKeys = gatherBucketKeys(directory);
    logger.debug(`Deleting files from S3 bucket '${bucketName}': ${JSON.stringify(bucketKeys)}`);
    const deleteCommand = new client_s3_1.DeleteObjectsCommand({
        Bucket: bucketName,
        Delete: { Objects: bucketKeys.map((bucketKey) => ({ Key: bucketKey })) },
    });
    const goDelete = await (0, promise_utils_1.go)(() => s3.send(deleteCommand));
    if (!goDelete.success) {
        throw new Error(`Failed to delete bucket directory '${directory.bucketKey}' and its content: ${goDelete.error}`);
    }
};
exports.deleteBucketDirectory = deleteBucketDirectory;
const deleteBucket = async (bucket) => {
    const { name: bucketName, region: bucketRegion } = bucket;
    const s3 = initializeS3Service(bucketRegion);
    logger.debug(`Deleting S3 bucket '${bucketName}'`);
    const deleteCommand = new client_s3_1.DeleteBucketCommand({ Bucket: bucketName });
    const goDelete = await (0, promise_utils_1.go)(() => s3.send(deleteCommand));
    if (!goDelete.success) {
        throw new Error(`Failed to delete S3 bucket '${bucketName}': ${goDelete.error}`);
    }
};
exports.deleteBucket = deleteBucket;
//# sourceMappingURL=aws.js.map