This commit is contained in:
Andras Bacsai
2021-06-07 21:33:11 +02:00
committed by GitHub
parent 9c173d1de0
commit 31b3f58b2c
36 changed files with 1480 additions and 1999 deletions

View File

@@ -11,7 +11,8 @@ export async function deleteSameDeployments(configuration) {
const running = JSON.parse(s.Spec.Labels.configuration);
if (
running.repository.id === configuration.repository.id &&
running.repository.branch === configuration.repository.branch
running.repository.branch === configuration.repository.branch &&
running.publish.domain === configuration.publish.domain
) {
await execShellAsync(`docker stack rm ${s.Spec.Labels['com.docker.stack.namespace']}`);
}

View File

@@ -4,7 +4,7 @@ import { execShellAsync } from '../common';
export default async function (configuration) {
try {
const { GITHUB_APP_PRIVATE_KEY } = process.env;
const { workdir } = configuration.general;
const { workdir, isPreviewDeploymentEnabled, pullRequest } = configuration.general;
const { organization, name, branch } = configuration.repository;
const github = configuration.github;
if (!github.installation.id || !github.app.id) {
@@ -37,8 +37,12 @@ export default async function (configuration) {
await execShellAsync(
`mkdir -p ${workdir} && git clone -q -b ${branch} https://x-access-token:${token}@github.com/${organization}/${name}.git ${workdir}/`
);
if (isPreviewDeploymentEnabled && pullRequest && pullRequest !== 0) {
await execShellAsync(`cd ${workdir} && git fetch origin pull/${pullRequest}/head:pull_${pullRequest} && git checkout pull_${pullRequest}`)
}
configuration.build.container.tag = (
await execShellAsync(`cd ${configuration.general.workdir}/ && git rev-parse HEAD`)
await execShellAsync(`cd ${workdir}/ && git rev-parse HEAD`)
)
.replace('\n', '')
.slice(0, 7);

View File

@@ -10,9 +10,8 @@ function getUniq() {
}
export function setDefaultConfiguration(configuration) {
const nickname = getUniq();
const nickname = configuration.general.nickname || getUniq();
const deployId = cuid();
const shaBase = JSON.stringify({ repository: configuration.repository });
const sha256 = crypto.createHash('sha256').update(shaBase).digest('hex');
@@ -21,12 +20,14 @@ export function setDefaultConfiguration(configuration) {
configuration.general.nickname = nickname;
configuration.general.deployId = deployId;
configuration.general.workdir = `/tmp/${deployId}`;
if (configuration.general.isPreviewDeploymentEnabled && configuration.general.pullRequest !== 0) {
configuration.build.container.name = `pr${configuration.general.pullRequest}-${sha256.slice(0, 8)}`
configuration.publish.domain = `pr${configuration.general.pullRequest}.${configuration.publish.domain}`
}
if (!configuration.publish.path) configuration.publish.path = '/';
if (!configuration.publish.port) {
if (
configuration.build.pack === 'nodejs' ||
configuration.build.pack === 'vuejs' ||
configuration.build.pack === 'nuxtjs' ||
configuration.build.pack === 'rust' ||
configuration.build.pack === 'nextjs' ||
@@ -74,20 +75,23 @@ export function setDefaultConfiguration(configuration) {
return configuration;
}
export async function precheckDeployment({ services, configuration }) {
export async function precheckDeployment(configuration) {
const services = (await docker.engine.listServices()).filter(
(r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application' && JSON.parse(r.Spec.Labels.configuration).publish.domain === configuration.publish.domain
);
let foundService = false;
let configChanged = false;
let imageChanged = false;
let forceUpdate = false;
for (const service of services) {
const running = JSON.parse(service.Spec.Labels.configuration);
if (running) {
if (
running.repository.id === configuration.repository.id &&
running.repository.branch === configuration.repository.branch
) {
foundService = true;
// Base service configuration changed
if (
!running.build.container.baseSHA ||
@@ -107,9 +111,32 @@ export async function precheckDeployment({ services, configuration }) {
(n) =>
n.DesiredState !== 'Running' && n.Image.split(':')[1] === running.build.container.tag
);
if (isError.length > 0) forceUpdate = true;
foundService = true;
if (isError.length > 0) {
forceUpdate = true;
}
const compareObjects = (a, b) => {
if (a === b) return true;
if (typeof a != 'object' || typeof b != 'object' || a == null || b == null) return false;
let keysA = Object.keys(a), keysB = Object.keys(b);
if (keysA.length != keysB.length) return false;
for (let key of keysA) {
if (!keysB.includes(key)) return false;
if (typeof a[key] === 'function' || typeof b[key] === 'function') {
if (a[key].toString() != b[key].toString()) return false;
} else {
if (!compareObjects(a[key], b[key])) return false;
}
}
return true;
}
const runningWithoutContainer = JSON.parse(JSON.stringify(running));
delete runningWithoutContainer.build.container;
@@ -118,16 +145,19 @@ export async function precheckDeployment({ services, configuration }) {
// If only the configuration changed
if (
JSON.stringify(runningWithoutContainer.build) !==
JSON.stringify(configurationWithoutContainer.build) ||
JSON.stringify(runningWithoutContainer.publish) !==
JSON.stringify(configurationWithoutContainer.publish)
)
!compareObjects(runningWithoutContainer.build,configurationWithoutContainer.build) ||
!compareObjects(runningWithoutContainer.publish,configurationWithoutContainer.publish) ||
runningWithoutContainer.general.isPreviewDeploymentEnabled !==
configurationWithoutContainer.general.isPreviewDeploymentEnabled
){
configChanged = true;
}
// If only the image changed
if (running.build.container.tag !== configuration.build.container.tag) imageChanged = true;
// If build pack changed, forceUpdate the service
if (running.build.pack !== configuration.build.pack) forceUpdate = true;
if (configuration.general.isPreviewDeploymentEnabled && configuration.general.pullRequest !== 0) forceUpdate = true
}
}
}
@@ -144,28 +174,5 @@ export async function precheckDeployment({ services, configuration }) {
}
export async function updateServiceLabels(configuration) {
// In case of any failure during deployment, still update the current configuration.
const services = (await docker.engine.listServices()).filter(
(r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application'
);
const found = services.find((s) => {
const config = JSON.parse(s.Spec.Labels.configuration);
if (
config.repository.id === configuration.repository.id &&
config.repository.branch === configuration.repository.branch
) {
return config;
}
return null;
});
if (found) {
const { ID } = found;
const Labels = { ...JSON.parse(found.Spec.Labels.configuration), ...configuration };
await execShellAsync(
`docker service update --label-add configuration='${JSON.stringify(
Labels
)}' --label-add com.docker.stack.image='${configuration.build.container.name}:${configuration.build.container.tag
}' ${ID}`
);
}
return await execShellAsync(`docker service update --label-add configuration='${JSON.stringify(configuration)}' ${configuration.build.container.name}_${configuration.build.container.name}`)
}

View File

@@ -1,9 +1,9 @@
import { docker } from '$lib/api/docker';
import { saveAppLog } from './logging';
import { promises as fs } from 'fs';
import { deleteSameDeployments } from './cleanup';
import { deleteSameDeployments, purgeImagesContainers } from './cleanup';
import yaml from 'js-yaml';
import { execShellAsync } from '../common';
import { delay, execShellAsync } from '../common';
export default async function (configuration, imageChanged) {
const generateEnvs = {};
@@ -11,6 +11,7 @@ export default async function (configuration, imageChanged) {
generateEnvs[secret.name] = secret.value;
}
const containerName = configuration.build.container.name;
const containerTag = configuration.build.container.tag;
// Only save SHA256 of it in the configuration label
const baseServiceConfiguration = configuration.baseServiceConfiguration;
@@ -20,7 +21,7 @@ export default async function (configuration, imageChanged) {
version: '3.8',
services: {
[containerName]: {
image: `${configuration.build.container.name}:${configuration.build.container.tag}`,
image: `${containerName}:${containerTag}`,
networks: [`${docker.network}`],
environment: generateEnvs,
deploy: {
@@ -31,21 +32,21 @@ export default async function (configuration, imageChanged) {
'configuration=' + JSON.stringify(configuration),
'traefik.enable=true',
'traefik.http.services.' +
configuration.build.container.name +
containerName +
`.loadbalancer.server.port=${configuration.publish.port}`,
'traefik.http.routers.' + configuration.build.container.name + '.entrypoints=websecure',
'traefik.http.routers.' + containerName + '.entrypoints=websecure',
'traefik.http.routers.' +
configuration.build.container.name +
containerName +
'.rule=Host(`' +
configuration.publish.domain +
'`) && PathPrefix(`' +
configuration.publish.path +
'`)',
'traefik.http.routers.' +
configuration.build.container.name +
containerName +
'.tls.certresolver=letsencrypt',
'traefik.http.routers.' +
configuration.build.container.name +
containerName +
'.middlewares=global-compress'
]
}
@@ -62,7 +63,7 @@ export default async function (configuration, imageChanged) {
if (imageChanged) {
// console.log('image changed')
await execShellAsync(
`docker service update --image ${configuration.build.container.name}:${configuration.build.container.tag} ${configuration.build.container.name}_${configuration.build.container.name}`
`docker service update --image ${containerName}:${containerTag} ${containerName}_${containerName}`
);
} else {
// console.log('new deployment or force deployment or config changed')
@@ -71,6 +72,11 @@ export default async function (configuration, imageChanged) {
`cat ${configuration.general.workdir}/stack.yml | docker stack deploy --prune -c - ${containerName}`
);
}
async function purgeImagesAsync(found) {
await delay(10000);
await purgeImagesContainers(found, true);
}
purgeImagesAsync(configuration)
await saveAppLog('### Published done!', configuration);
}

View File

@@ -46,6 +46,7 @@ const templates = {
pack: 'vuejs',
...defaultBuildAndDeploy,
directory: 'dist',
port: 80,
name: 'Vue'
},
gatsby: {

View File

@@ -10,7 +10,7 @@ import { saveAppLog } from './logging';
export default async function (configuration, imageChanged) {
const { id, organization, name, branch } = configuration.repository;
const { domain } = configuration.publish;
const { deployId} = configuration.general;
const { deployId } = configuration.general;
try {
await saveAppLog(`${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} Queued.`, configuration);
await copyFiles(configuration);
@@ -20,6 +20,7 @@ export default async function (configuration, imageChanged) {
{ repoId: id, branch, deployId, organization, name, domain },
{ repoId: id, branch, deployId, organization, name, domain, progress: 'done' }
);
await updateServiceLabels(configuration);
} catch (error) {
await Deployment.findOneAndUpdate(