terrac
Version:
A minimal private module registry for Terraform and OpenTofu
130 lines (129 loc) • 4.77 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BackendS3 = exports.configSchemaS3 = void 0;
const shared_1 = require("./shared");
const errors_1 = require("../errors");
const fs_extra_1 = require("fs-extra");
const lodash_1 = require("lodash");
const client_s3_1 = require("@aws-sdk/client-s3");
const Joi = require("joi");
exports.configSchemaS3 = Joi.object({
type: Joi.string().allow('s3').required().description('Backend type'),
bucket: Joi.string().required().description('Bucket name'),
region: Joi.string().pattern(/^(?:[a-z]+-){2}\d+$/).required().description('AWS region'),
keyPrefix: Joi.string().optional().allow('').description('Object key prefix'),
});
class BackendS3 {
constructor(config) {
this.config = config;
this.client = new client_s3_1.S3Client({
// this is a special env for testing
endpoint: process.env.TERRAC_BACKEND_S3_ENDPOINT,
region: config.region,
});
}
async upload(name, version, packagePath) {
await this.putObject(this.getPackageKey(name, version), (0, fs_extra_1.createReadStream)(packagePath));
}
async getSourceUrl(name, version) {
let targetVersion = version;
if (!targetVersion) {
const meta = await this.getMeta(name);
targetVersion = meta.version;
}
const key = this.getPackageKey(name, targetVersion);
if (!this.keyExists(key)) {
throw new errors_1.ModuleNotFoundError();
}
return `s3::https://s3-${this.config.region}.amazonaws.com/${this.config.bucket}/${key}`;
}
async list(name) {
const moduleList = [];
const prefix = this.config.keyPrefix || '';
if (name) {
if (!(await this.keyExists(this.getMetaKey(name)))) {
throw new errors_1.ModuleNotFoundError();
}
const meta = await this.getMeta(name);
for (const release of meta.releases) {
moduleList.push({
name,
version: release.version,
});
}
}
else {
const keys = await this.listKeys(prefix);
const names = (0, lodash_1.uniq)(keys.map(key => key.replace(prefix, '').split('/').shift()));
for (const name of names) {
moduleList.push({
name,
});
}
}
return moduleList;
}
async exists(name, version) {
const key = version ? this.getPackageKey(name, version) : this.getMetaKey(name);
return this.keyExists(key);
}
async getMeta(name) {
const bucket = this.config.bucket;
const key = this.getMetaKey(name);
if (await this.keyExists(key)) {
const getCommand = new client_s3_1.GetObjectCommand({
Bucket: bucket,
Key: key,
});
const response = await this.client.send(getCommand);
const data = await response.Body.transformToString();
return JSON.parse(data);
}
return (0, shared_1.getNewMeta)(name);
}
async saveMeta(meta) {
const metaKey = this.getMetaKey(meta.name);
await this.putObject(metaKey, JSON.stringify(meta));
}
async putObject(key, data) {
const putCommand = new client_s3_1.PutObjectCommand({
Bucket: this.config.bucket,
Key: key,
Body: data,
});
await this.client.send(putCommand);
}
async keyExists(key) {
try {
const command = new client_s3_1.HeadObjectCommand({
Bucket: this.config.bucket,
Key: key,
});
await this.client.send(command);
return true;
// eslint-disable-next-line unicorn/prefer-optional-catch-binding
}
catch (error) {
return false;
}
}
async listKeys(prefix) {
const command = new client_s3_1.ListObjectsV2Command({
Bucket: this.config.bucket,
Prefix: prefix,
});
const response = await this.client.send(command);
return response.Contents ? response.Contents.map(item => item.Key) : [];
}
getMetaKey(name) {
const baseKey = `${name}/meta.json`;
return this.config.keyPrefix ? `${this.config.keyPrefix}${baseKey}` : baseKey;
}
getPackageKey(name, version) {
var _a;
const baseKey = `${name}/${version}/module.zip`;
const keyPrefix = (_a = this.config.keyPrefix) !== null && _a !== void 0 ? _a : '';
return `${keyPrefix}${baseKey}`;
}
}
exports.BackendS3 = BackendS3;