genezio
Version:
Command line utility to interact with Genezio infrastructure.
120 lines (119 loc) • 6.27 kB
JavaScript
import { debugLogger, log } from "../../utils/logging.js";
import { getContainerRegistry, getContainerRegistryCredentials, } from "../../requests/containerRegistry.js";
import { spawnSync } from "child_process";
import { deployRequest } from "../../requests/deployCode.js";
import { createTemporaryFolder, deleteFolder, zipDirectoryToDestinationPath, } from "../../utils/file.js";
import path from "path";
import { getFrontendPresignedURL } from "../../requests/getFrontendPresignedURL.js";
import { uploadContentToS3 } from "../../requests/uploadContentToS3.js";
import { createFrontendProject } from "../../requests/createFrontendProject.js";
import { UserError } from "../../errors.js";
import { createHash } from "../../utils/strings.js";
export class ClusterCloudAdapter {
async deploy(input, projectConfiguration, cloudAdapterOptions, stack = [], sourceRepository) {
const stage = cloudAdapterOptions.stage || "";
log.info("Deploying your backend project to genezio infrastructure...");
// Login to container repository
let dockerlogin = null;
const containerRegistryInfo = await getContainerRegistry();
const containerRegistryCreds = await getContainerRegistryCredentials();
const promisesDeploy = input.map(async (element) => {
debugLogger.debug(`Get the registry push data for class name ${element.name}.`);
if (dockerlogin === null) {
spawnSync("docker", ["logout", containerRegistryInfo.repository]);
dockerlogin = spawnSync("docker", [
"login",
"-u",
containerRegistryInfo.username,
"-p",
containerRegistryCreds.password,
containerRegistryInfo.repository,
]);
if (dockerlogin.status !== 0) {
throw new UserError(`Error during container repository login. Exit code: ${dockerlogin.status}`);
}
}
debugLogger.debug(`Upload the content to Harbor registry for class ${element.name}.`);
const timestamp = Date.now().toString();
const tag = `${containerRegistryInfo.repository}/${containerRegistryInfo.username}/${projectConfiguration.name}/${element.name}:${timestamp}`.toLowerCase();
projectConfiguration.classes.find((c) => c.name === element.name).options = {
...projectConfiguration.classes.find((c) => c.name === element.name).options,
timestamp,
};
const dockerTag = spawnSync("docker", [
"tag",
`${projectConfiguration.name}-${element.name}`.toLowerCase(),
tag,
]);
if (dockerTag.status !== 0) {
throw new UserError(`Error during container image tag. Exit code: ${dockerTag.status}`);
}
// push image
debugLogger.debug(`Pushing the container image for ${element.name}...`);
const dockerPush = spawnSync("docker", ["push", tag]);
if (dockerPush.status !== 0) {
throw new UserError(`Error during container image push. Exit code: ${dockerPush.status}`);
}
debugLogger.debug(`Done uploading the container image for ${element.name}.`);
});
await Promise.all(promisesDeploy);
const response = await deployRequest(projectConfiguration, input, stage, stack, sourceRepository);
const classesInfo = response.classes.map((c) => ({
className: c.name,
methods: c.methods.map((m) => ({
name: m.name,
type: m.type,
cronString: m.cronString,
functionUrl: getFunctionUrl(c.cloudUrl, m.type, c.name, m.name),
})),
functionUrl: c.cloudUrl,
projectId: response.projectId,
}));
return {
projectId: response.projectId,
projectEnvId: response.projectEnvId,
classes: classesInfo,
functions: response.functions,
};
}
async deployFrontend(projectName, projectRegion, frontend, stage) {
const finalStageName = stage != "" && stage != "prod" ? `-${stage.replaceAll(/[/_.]/gm, "-")}` : "";
let finalSubdomain = frontend.subdomain + finalStageName;
debugLogger.debug("Subdomain:", finalSubdomain);
if (finalSubdomain.length > 63) {
debugLogger.debug("Subdomain is too long. Generating random subdomain.");
finalSubdomain = frontend.subdomain?.substring(0, 55) + "-" + createHash(stage, 4);
}
const archivePath = path.join(await createTemporaryFolder(), `${finalSubdomain}.zip`);
debugLogger.debug("Creating temporary folder", archivePath);
await zipDirectoryToDestinationPath(frontend.path, finalSubdomain, archivePath,
/* includeHiddenFiles= */ true);
debugLogger.debug("Getting presigned URL...");
const result = await getFrontendPresignedURL(finalSubdomain, projectName, stage);
if (!result.presignedURL) {
throw new Error("An error occurred (missing presignedUrl). Please try again!");
}
if (!result.userId) {
throw new Error("An error occurred (missing userId). Please try again!");
}
debugLogger.debug("Content of the folder zipped. Uploading to S3.");
await uploadContentToS3(result.presignedURL, archivePath, undefined, result.userId);
debugLogger.debug("Uploaded to S3.");
const finalDomain = await createFrontendProject(finalSubdomain, projectName, projectRegion, stage, [...(frontend.redirects || []), ...(frontend.rewrites || [])]);
// clean up temporary folder
await deleteFolder(path.dirname(archivePath));
return finalDomain;
}
}
function getFunctionUrl(baseUrl, methodType, className, methodName) {
if (methodType === "http") {
// trim the last slash of baseUrl if it exists
if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.slice(0, -1);
}
return `${baseUrl}/${className}/${methodName}`;
}
else {
return `${baseUrl}/${className}`;
}
}