321 lines
7.7 KiB
TypeScript
Raw Normal View History

2022-02-10 15:47:44 +01:00
import crypto from 'crypto';
2022-02-10 21:43:54 +01:00
import fs from 'fs/promises';
2022-02-10 15:47:44 +01:00
import * as buildpacks from '../buildPacks';
import * as importers from '../importers';
import { dockerInstance } from '../docker';
import {
asyncExecShell,
asyncSleep,
createDirectories,
getDomain,
getEngine,
saveBuildLog
} from '../common';
2022-02-10 15:47:44 +01:00
import * as db from '$lib/database';
import { decrypt } from '$lib/crypto';
import { sentry } from '$lib/common';
2022-02-10 15:47:44 +01:00
import {
copyBaseConfigurationFiles,
makeLabelForStandaloneApplication,
setDefaultConfiguration
} from '$lib/buildPacks/common';
import yaml from 'js-yaml';
2022-02-10 15:47:44 +01:00
export default async function (job) {
/*
Edge cases:
1 - Change build pack and redeploy, what should happen?
*/
let {
id: applicationId,
repository,
branch,
buildPack,
name,
destinationDocker,
destinationDockerId,
gitSource,
build_id: buildId,
configHash,
port,
installCommand,
buildCommand,
startCommand,
fqdn,
baseDirectory,
publishDirectory,
projectId,
secrets,
2022-03-19 23:46:33 +01:00
phpModules,
2022-02-10 15:47:44 +01:00
type,
pullmergeRequestId = null,
sourceBranch = null,
2022-03-20 23:51:50 +01:00
settings,
persistentStorage
2022-02-10 15:47:44 +01:00
} = job.data;
const { debug } = settings;
2022-03-20 14:20:29 +01:00
await asyncSleep(1000);
2022-03-20 14:20:29 +01:00
await db.prisma.build.updateMany({
2022-03-20 15:03:24 +01:00
where: {
status: 'queued',
id: { not: buildId },
applicationId,
createdAt: { lt: new Date(new Date().getTime() - 60 * 60 * 1000) }
},
2022-03-20 14:20:29 +01:00
data: { status: 'failed' }
});
2022-02-10 15:47:44 +01:00
let imageId = applicationId;
let domain = getDomain(fqdn);
2022-03-20 23:51:50 +01:00
let volumes =
persistentStorage?.map((storage) => {
2022-03-21 21:46:49 +01:00
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${
type !== 'docker' ? '/app' : ''
2022-03-21 21:46:49 +01:00
}${storage.path}`;
2022-03-20 23:51:50 +01:00
}) || [];
2022-02-10 15:47:44 +01:00
// Previews, we need to get the source branch and set subdomain
if (pullmergeRequestId) {
branch = sourceBranch;
domain = `${pullmergeRequestId}.${domain}`;
imageId = `${applicationId}-${pullmergeRequestId}`;
}
let deployNeeded = true;
let destinationType;
if (destinationDockerId) {
destinationType = 'docker';
}
if (destinationType === 'docker') {
const docker = dockerInstance({ destinationDocker });
const host = getEngine(destinationDocker.engine);
await db.prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
const { workdir, repodir } = await createDirectories({ repository, buildId });
2022-02-10 15:47:44 +01:00
const configuration = await setDefaultConfiguration(job.data);
buildPack = configuration.buildPack;
port = configuration.port;
installCommand = configuration.installCommand;
startCommand = configuration.startCommand;
buildCommand = configuration.buildCommand;
publishDirectory = configuration.publishDirectory;
baseDirectory = configuration.baseDirectory;
2022-02-10 15:47:44 +01:00
let commit = await importers[gitSource.type]({
applicationId,
debug,
workdir,
repodir,
githubAppId: gitSource.githubApp?.id,
gitlabAppId: gitSource.gitlabApp?.id,
repository,
branch,
buildId,
2022-02-10 15:47:44 +01:00
apiUrl: gitSource.apiUrl,
projectId,
deployKeyId: gitSource.gitlabApp?.deployKeyId || null,
privateSshKey: decrypt(gitSource.gitlabApp?.privateSshKey) || null
});
2022-03-22 09:35:24 +01:00
if (!commit) {
throw new Error('No commit found?');
}
2022-02-10 15:47:44 +01:00
let tag = commit.slice(0, 7);
if (pullmergeRequestId) {
tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`;
}
try {
db.prisma.build.update({ where: { id: buildId }, data: { commit } });
2022-02-10 15:47:44 +01:00
} catch (err) {
console.log(err);
}
if (!pullmergeRequestId) {
const currentHash = crypto
.createHash('sha256')
.update(
JSON.stringify({
buildPack,
port,
installCommand,
buildCommand,
startCommand,
secrets,
branch,
repository,
fqdn
})
)
.digest('hex');
if (configHash !== currentHash) {
await db.prisma.application.update({
where: { id: applicationId },
data: { configHash: currentHash }
});
deployNeeded = true;
if (configHash) {
saveBuildLog({ line: 'Configuration changed.', buildId, applicationId });
}
} else {
deployNeeded = false;
}
} else {
deployNeeded = true;
}
const image = await docker.engine.getImage(`${applicationId}:${tag}`);
let imageFound = false;
try {
await image.inspect();
imageFound = false;
} catch (error) {
//
}
if (!imageFound || deployNeeded) {
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId);
if (buildpacks[buildPack])
await buildpacks[buildPack]({
buildId,
2022-02-10 15:47:44 +01:00
applicationId,
domain,
name,
type,
pullmergeRequestId,
buildPack,
repository,
branch,
projectId,
publishDirectory,
debug,
commit,
tag,
workdir,
docker,
port,
installCommand,
buildCommand,
startCommand,
baseDirectory,
2022-03-19 23:46:33 +01:00
secrets,
phpModules
2022-02-10 15:47:44 +01:00
});
else {
saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId });
throw new Error(`Build pack ${buildPack} not found.`);
}
deployNeeded = true;
} else {
deployNeeded = false;
saveBuildLog({ line: 'Nothing changed.', buildId, applicationId });
}
// Deploy to Docker Engine
try {
await asyncExecShell(`DOCKER_HOST=${host} docker stop -t 0 ${imageId}`);
await asyncExecShell(`DOCKER_HOST=${host} docker rm ${imageId}`);
} catch (error) {
//
}
const envs = [];
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
envs.push(`${secret.name}=${secret.value}`);
}
} else {
if (!secret.isPRMRSecret) {
envs.push(`${secret.name}=${secret.value}`);
}
}
2022-02-10 15:47:44 +01:00
});
}
2022-02-10 21:43:54 +01:00
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
2022-02-10 15:47:44 +01:00
const labels = makeLabelForStandaloneApplication({
applicationId,
fqdn,
name,
type,
pullmergeRequestId,
buildPack,
repository,
branch,
projectId,
port,
commit,
installCommand,
buildCommand,
startCommand,
baseDirectory,
publishDirectory
});
2022-02-28 09:31:36 +01:00
let envFound = false;
try {
envFound = !!(await fs.stat(`${workdir}/.env`));
} catch (error) {
//
}
2022-02-10 21:43:54 +01:00
try {
saveBuildLog({ line: 'Deployment started.', buildId, applicationId });
// for await (const volume of volumes) {
// const id = volume.split(':')[0];
// try {
// await asyncExecShell(`DOCKER_HOST=${host} docker volume inspect ${id}`);
// } catch (error) {
// await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${id}`);
// }
// }
const composeVolumes = volumes.map((volume) => {
return {
[`${volume.split(':')[0]}`]: {
name: volume.split(':')[0]
}
};
});
const compose = {
version: '3.8',
services: {
[imageId]: {
image: `${applicationId}:${tag}`,
container_name: imageId,
volumes,
env_file: envFound ? [`${workdir}/.env`] : [],
networks: [docker.network],
labels: labels,
depends_on: [],
restart: 'always'
}
},
networks: {
[docker.network]: {
external: true
}
},
volumes: Object.assign({}, ...composeVolumes)
};
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(compose));
await asyncExecShell(
`DOCKER_HOST=${host} docker compose --project-directory ${workdir} up -d`
2022-02-10 21:43:54 +01:00
);
// const { stderr } = await asyncExecShell(
// `DOCKER_HOST=${host} docker run ${envFound && `--env-file=${workdir}/.env`} ${labels.join(
// ' '
// )} --name ${imageId} --network ${docker.network} --restart always ${volumes.length > 0 ? volumes : ''
// } -d ${applicationId}:${tag}`
// );
2022-02-10 21:43:54 +01:00
saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
} catch (error) {
saveBuildLog({ line: error, buildId, applicationId });
sentry.captureException(error);
2022-02-10 21:43:54 +01:00
throw new Error(error);
}
2022-03-01 14:02:46 +01:00
saveBuildLog({ line: 'Proxy will be updated shortly.', buildId, applicationId });
2022-02-10 15:47:44 +01:00
}
}