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

@@ -306,7 +306,7 @@
>
{:else}
<button
class="opacity-50 tracking-tight font-bold text-xs rounded px-2 cursor-not-allowed"
class="opacity-50 tracking-tight font-bold text-xs rounded px-2 cursor-not-allowed"
disabled={upgradeDisabled}>Upgrading. It could take a while, please wait...</button
>
{/if}

View File

@@ -1,32 +1,18 @@
import { setDefaultConfiguration } from '$lib/api/applications/configuration';
import { saveServerLog } from '$lib/api/applications/logging';
import { docker } from '$lib/api/docker';
import Configuration from '$models/Configuration';
import type { Request } from '@sveltejs/kit';
export async function post(request: Request) {
try {
const { DOMAIN } = process.env;
const configuration = setDefaultConfiguration(request.body);
const services = (await docker.engine.listServices()).filter(
(r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application'
);
let foundDomain = false;
for (const service of services) {
const running = JSON.parse(service.Spec.Labels.configuration);
if (running) {
if (
running.publish.domain === configuration.publish.domain &&
running.repository.id !== configuration.repository.id &&
running.publish.path === configuration.publish.path
) {
foundDomain = true;
}
}
}
if (DOMAIN === configuration.publish.domain) foundDomain = true;
if (foundDomain) {
const configurationFound = await Configuration.find({
'repository.id': { '$ne': configuration.repository.id },
'publish.domain': configuration.publish.domain
}).select('-_id -__v -createdAt -updatedAt')
if (configurationFound.length > 0 || configuration.publish.domain === DOMAIN) {
return {
status: 200,
body: {
@@ -38,7 +24,7 @@ export async function post(request: Request) {
return {
status: 200,
body: { success: true, message: 'OK' }
};
}
} catch (error) {
await saveServerLog(error);
return {

View File

@@ -5,17 +5,17 @@ import type { Request } from '@sveltejs/kit';
export async function post(request: Request) {
const { name, organization, branch }: any = request.body || {};
if (name && organization && branch) {
const configurationFound = await Configuration.findOne({
const configurationFound = await Configuration.find({
'repository.name': name,
'repository.organization': organization,
'repository.branch': branch,
}).lean()
'repository.branch': branch
}).select('-_id -__v -createdAt -updatedAt')
if (configurationFound) {
return {
status: 200,
body: {
success: true,
...configurationFound
configuration: [...configurationFound]
}
};
}
@@ -46,6 +46,7 @@ export async function post(request: Request) {
}
return null;
});
if (found) {
return {
status: 200,

View File

@@ -0,0 +1,62 @@
import { updateServiceLabels } from '$lib/api/applications/configuration';
import { execShellAsync } from '$lib/api/common';
import { docker } from '$lib/api/docker';
import ApplicationLog from '$models/ApplicationLog';
import Configuration from '$models/Configuration';
import Deployment from '$models/Deployment';
import type { Request } from '@sveltejs/kit';
export async function post(request: Request) {
const { name, organization, branch, isPreviewDeploymentEnabled }: any = request.body || {};
if (name && organization && branch) {
const configuration = await Configuration.findOneAndUpdate({
'repository.name': name,
'repository.organization': organization,
'repository.branch': branch
}, { $set: { 'general.isPreviewDeploymentEnabled': isPreviewDeploymentEnabled, 'general.pullRequest': 0 } }, { new: true }).select('-_id -__v -createdAt -updatedAt')
if (!isPreviewDeploymentEnabled) {
const found = await Configuration.find({
'repository.name': name,
'repository.organization': organization,
'repository.branch': branch,
'general.pullRequest': { '$ne': 0 }
})
for (const prDeployment of found) {
await Configuration.findOneAndRemove({
'repository.name': name,
'repository.organization': organization,
'repository.branch': branch,
'publish.domain': prDeployment.publish.domain
})
const deploys = await Deployment.find({ organization, branch, name, domain: prDeployment.publish.domain });
for (const deploy of deploys) {
await ApplicationLog.deleteMany({ deployId: deploy.deployId });
await Deployment.deleteMany({ deployId: deploy.deployId });
}
await execShellAsync(`docker stack rm ${prDeployment.build.container.name}`);
}
return {
status: 200,
body: {
organization,
name,
branch
}
};
}
updateServiceLabels(configuration);
return {
status: 200,
body: {
success: true
}
};
}
return {
status: 500,
body: {
error: 'Cannot save.'
}
};
}

View File

@@ -1,32 +1,24 @@
import type { Request } from '@sveltejs/kit';
import Deployment from '$models/Deployment';
import { docker } from '$lib/api/docker';
import { precheckDeployment, setDefaultConfiguration } from '$lib/api/applications/configuration';
import cloneRepository from '$lib/api/applications/cloneRepository';
import { cleanupTmp } from '$lib/api/common';
import queueAndBuild from '$lib/api/applications/queueAndBuild';
import Configuration from '$models/Configuration';
export async function post(request: Request) {
let configuration;
try {
const services = (await docker.engine.listServices()).filter(
(r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application'
);
configuration = setDefaultConfiguration(request.body);
if (!configuration) {
return {
status: 500,
body: {
error: 'Whaaat?'
}
};
}
export async function post(request: Request) {
const configuration = setDefaultConfiguration(request.body);
if (!configuration) {
return {
status: 500,
body: {
error: 'Whaaat?'
}
};
}
try {
await cloneRepository(configuration);
const { foundService, imageChanged, configChanged, forceUpdate } = await precheckDeployment({
services,
configuration
});
const { foundService, imageChanged, configChanged, forceUpdate } = await precheckDeployment(configuration);
if (foundService && !forceUpdate && !imageChanged && !configChanged) {
cleanupTmp(configuration.general.workdir);
return {
@@ -56,7 +48,8 @@ export async function post(request: Request) {
}
const { id, organization, name, branch } = configuration.repository;
const { domain } = configuration.publish;
const { deployId, nickname } = configuration.general;
const { deployId, nickname, pullRequest } = configuration.general;
await new Deployment({
repoId: id,
branch,
@@ -66,11 +59,13 @@ export async function post(request: Request) {
name,
nickname
}).save();
await Configuration.findOneAndUpdate({
'repository.id': id,
'repository.organization': organization,
'repository.name': name,
'repository.branch': branch,
'general.pullRequest': 0,
},
{ ...configuration },
{ upsert: true, new: true })
@@ -86,6 +81,7 @@ export async function post(request: Request) {
}
};
} catch (error) {
console.log(error)
await Deployment.findOneAndUpdate(
{
repoId: configuration.repository.id,

View File

@@ -12,8 +12,6 @@ export async function get(request: Request) {
const deploy: any = await Deployment.findOne({ deployId })
.select('-_id -__v')
.sort({ createdAt: 'desc' });
const finalLogs: any = {};
finalLogs.progress = deploy.progress;
finalLogs.events = logs.map((log) => log.event);

View File

@@ -16,12 +16,12 @@ export async function get(request: Request) {
.select('-_id -__v -repoId')
.sort({ createdAt: 'desc' })
.limit(show);
const finalLogs = deploy.map((d) => {
const finalLogs = { ...d._doc };
const updatedAt = dayjs(d.updatedAt).utc();
finalLogs.took = updatedAt.diff(dayjs(d.createdAt)) / 1000;
finalLogs.since = updatedAt.fromNow();
finalLogs.isPr = d.domain.startsWith('pr')
return finalLogs;
});
return {

View File

@@ -1,61 +1,69 @@
import { purgeImagesContainers } from '$lib/api/applications/cleanup';
import { docker } from '$lib/api/docker';
import Deployment from '$models/Deployment';
import ApplicationLog from '$models/ApplicationLog';
import { delay, execShellAsync } from '$lib/api/common';
import Configuration from '$models/Configuration';
async function call(found) {
async function purgeImagesAsync(found) {
await delay(10000);
await purgeImagesContainers(found, true);
}
export async function post(request: Request) {
const { organization, name, branch } = request.body;
let found = false;
const { organization, name, branch, domain } = request.body;
try {
(await docker.engine.listServices())
.filter((r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application')
.map((s) => {
const running = JSON.parse(s.Spec.Labels.configuration);
if (
running.repository.organization === organization &&
running.repository.name === name &&
running.repository.branch === branch
) {
found = running;
const configurationFound = await Configuration.findOne({
'repository.organization': organization,
'repository.name': name,
'repository.branch': branch,
'publish.domain': domain
})
if (configurationFound) {
const id = configurationFound._id
if (configurationFound?.general?.pullRequest === 0) {
// Main deployment deletion request; deleting main + PRs
const allConfiguration = await Configuration.find({
'repository.name': name,
'repository.organization': organization,
'repository.branch': branch,
})
for (const config of allConfiguration) {
await Configuration.findOneAndRemove({
'repository.name': config.repository.name,
'repository.organization': config.repository.organization,
'repository.branch': config.repository.branch,
})
await execShellAsync(`docker stack rm ${config.build.container.name}`);
}
return null;
});
if (found) {
await Configuration.findOneAndRemove({
'repository.name': name,
'repository.organization': organization,
'repository.branch': branch,
})
const deploys = await Deployment.find({ organization, branch, name });
for (const deploy of deploys) {
await ApplicationLog.deleteMany({ deployId: deploy.deployId });
await Deployment.deleteMany({ deployId: deploy.deployId });
const deploys = await Deployment.find({ organization, branch, name })
for (const deploy of deploys) {
await ApplicationLog.deleteMany({ deployId: deploy.deployId });
await Deployment.deleteMany({ deployId: deploy.deployId });
}
purgeImagesAsync(configurationFound);
} else {
// Delete only PRs
await Configuration.findByIdAndRemove(id)
await execShellAsync(`docker stack rm ${configurationFound.build.container.name}`);
const deploys = await Deployment.find({ organization, branch, name, domain })
for (const deploy of deploys) {
await ApplicationLog.deleteMany({ deployId: deploy.deployId });
await Deployment.deleteMany({ deployId: deploy.deployId });
}
purgeImagesAsync(configurationFound);
}
await execShellAsync(`docker stack rm ${found.build.container.name}`);
call(found);
return {
status: 200,
body: {
organization,
name,
branch
}
};
} else {
return {
status: 500,
error: {
message: 'Nothing to do.'
}
};
}
return {
status: 200,
body: {
organization,
name,
branch
}
};
} catch (error) {
console.log(error)
return {
status: 500,
error: {

View File

@@ -1,14 +1,9 @@
import { docker } from '$lib/api/docker';
import type { Request } from '@sveltejs/kit';
import Configuration from '$models/Configuration'
export async function get(request: Request) {
// Should update this to get data from mongodb and update db with the currently running services on start!
const dockerServices = await docker.engine.listServices();
let applications: any = dockerServices.filter(
(r) =>
r.Spec.Labels.managedBy === 'coolify' &&
r.Spec.Labels.type === 'application' &&
r.Spec.Labels.configuration
);
let databases: any = dockerServices.filter(
(r) =>
r.Spec.Labels.managedBy === 'coolify' &&
@@ -21,15 +16,6 @@ export async function get(request: Request) {
r.Spec.Labels.type === 'service' &&
r.Spec.Labels.configuration
);
applications = applications.map((r) => {
if (JSON.parse(r.Spec.Labels.configuration)) {
return {
configuration: JSON.parse(r.Spec.Labels.configuration),
UpdatedAt: r.UpdatedAt
};
}
return {};
});
databases = databases.map((r) => {
if (JSON.parse(r.Spec.Labels.configuration)) {
return {
@@ -47,14 +33,23 @@ export async function get(request: Request) {
}
return {};
});
applications = [
...new Map(
applications.map((item) => [
item.configuration.publish.domain + item.configuration.publish.path,
item
])
).values()
];
const configurations = await Configuration.find({
'general.pullRequest': { '$in': [null, 0] }
}).select('-_id -__v -createdAt')
const applications = []
for (const configuration of configurations) {
const foundPRDeployments = await Configuration.find({
'repository.id': configuration.repository.id,
'repository.branch': configuration.repository.branch,
'general.pullRequest': { '$ne': 0 }
}).select('-_id -__v -createdAt')
const payload = {
configuration,
UpdatedAt: configuration.updatedAt,
prBuilds: foundPRDeployments.length > 0 ? true : false,
}
applications.push(payload)
}
return {
status: 200,
body: {

View File

@@ -4,11 +4,16 @@ import Deployment from '$models/Deployment';
import { docker } from '$lib/api/docker';
import { precheckDeployment, setDefaultConfiguration } from '$lib/api/applications/configuration';
import cloneRepository from '$lib/api/applications/cloneRepository';
import { cleanupTmp } from '$lib/api/common';
import { cleanupTmp, execShellAsync } from '$lib/api/common';
import queueAndBuild from '$lib/api/applications/queueAndBuild';
import Configuration from '$models/Configuration';
import ApplicationLog from '$models/ApplicationLog';
import { cleanupStuckedDeploymentsInDB } from '$lib/api/applications/cleanup';
export async function post(request: Request) {
let configuration;
const allowedGithubEvents = ['push', 'pull_request']
const allowedPRActions = ['opened', , 'reopened', 'synchronize', 'closed']
const githubEvent = request.headers['x-github-event']
const { GITHUP_APP_WEBHOOK_SECRET } = process.env;
const hmac = crypto.createHmac('sha256', GITHUP_APP_WEBHOOK_SECRET);
const digest = Buffer.from(
@@ -20,52 +25,92 @@ export async function post(request: Request) {
return {
status: 500,
body: {
error: 'Invalid request'
error: 'Invalid request.'
}
};
}
if (request.headers['x-github-event'] !== 'push') {
if (!allowedGithubEvents.includes(githubEvent)) {
return {
status: 500,
body: {
error: 'Not a push event.'
error: 'Event not allowed.'
}
};
}
try {
const services = (await docker.engine.listServices()).filter(
(r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application'
);
configuration = services.find((r) => {
if (request.body.ref.startsWith('refs')) {
const branch = request.body.ref.split('/')[2];
if (
JSON.parse(r.Spec.Labels.configuration).repository.id === request.body.repository.id &&
JSON.parse(r.Spec.Labels.configuration).repository.branch === branch
) {
return r;
const applications = await Configuration.find({
'repository.id': request.body.repository.id,
}).select('-_id -__v -createdAt -updatedAt')
if (githubEvent === 'push') {
configuration = applications.find((r) => {
if (request.body.ref.startsWith('refs')) {
if (r.repository.branch === request.body.ref.split('/')[2]) {
return r;
}
}
return null;
});
} else if (githubEvent === 'pull_request') {
if (!allowedPRActions.includes(request.body.action)) {
return {
status: 500,
body: {
error: 'PR action is not allowed.'
}
};
}
return null;
});
configuration = setDefaultConfiguration(JSON.parse(configuration.Spec.Labels.configuration));
configuration = applications.find((r) => r.repository.branch === request.body['pull_request'].base.ref);
if (configuration) {
if (!configuration.general.isPreviewDeploymentEnabled) {
return {
status: 500,
body: {
error: 'PR deployments are not enabled.'
}
};
}
configuration.general.pullRequest = request.body.number
}
}
if (!configuration) {
return {
status: 500,
body: {
error: 'Whaaat?'
error: 'No configuration found.'
}
};
}
configuration = setDefaultConfiguration(configuration);
const { id, organization, name, branch } = configuration.repository;
const { domain } = configuration.publish;
const { deployId, nickname, pullRequest } = configuration.general;
if (request.body.action === 'closed') {
const deploys = await Deployment.find({ organization, branch, name, domain });
for (const deploy of deploys) {
await ApplicationLog.deleteMany({ deployId: deploy.deployId });
await Deployment.deleteMany({ deployId: deploy.deployId });
}
await Configuration.findOneAndRemove({
'repository.id': id,
'repository.organization': organization,
'repository.name': name,
'repository.branch': branch,
'general.pullRequest': pullRequest
})
await execShellAsync(`docker stack rm ${configuration.build.container.name}`);
return {
status: 200,
body: {
success: true,
message: 'Removed'
}
};
}
await cloneRepository(configuration);
const { foundService, imageChanged, configChanged, forceUpdate } = await precheckDeployment({
services,
configuration
});
const { foundService, imageChanged, configChanged, forceUpdate } = await precheckDeployment(configuration);
if (foundService && !forceUpdate && !imageChanged && !configChanged) {
cleanupTmp(configuration.general.workdir);
return {
@@ -77,11 +122,11 @@ export async function post(request: Request) {
};
}
const alreadyQueued = await Deployment.find({
repoId: configuration.repository.id,
branch: configuration.repository.branch,
organization: configuration.repository.organization,
name: configuration.repository.name,
domain: configuration.publish.domain,
repoId: id,
branch: branch,
organization: organization,
name: name,
domain: domain,
progress: { $in: ['queued', 'inprogress'] }
});
if (alreadyQueued.length > 0) {
@@ -93,9 +138,7 @@ export async function post(request: Request) {
}
};
}
const { id, organization, name, branch } = configuration.repository;
const { domain } = configuration.publish;
const { deployId, nickname } = configuration.general;
await new Deployment({
repoId: id,
branch,
@@ -105,14 +148,29 @@ export async function post(request: Request) {
name,
nickname
}).save();
await Configuration.findOneAndUpdate({
'repository.id': id,
'repository.organization': organization,
'repository.name': name,
'repository.branch': branch,
},
{ ...configuration },
{ upsert: true, new: true })
if (githubEvent === 'pull_request') {
await Configuration.findOneAndUpdate({
'repository.id': id,
'repository.organization': organization,
'repository.name': name,
'repository.branch': branch,
'general.pullRequest': pullRequest
},
{ ...configuration },
{ upsert: true, new: true })
} else {
await Configuration.findOneAndUpdate({
'repository.id': id,
'repository.organization': organization,
'repository.name': name,
'repository.branch': branch,
'general.pullRequest': { '$in': [null, 0] }
},
{ ...configuration },
{ upsert: true, new: true })
}
queueAndBuild(configuration, imageChanged);
return {
@@ -125,28 +183,40 @@ export async function post(request: Request) {
}
};
} catch (error) {
await Deployment.findOneAndUpdate(
{
repoId: configuration.repository.id,
branch: configuration.repository.branch,
organization: configuration.repository.organization,
name: configuration.repository.name,
domain: configuration.publish.domain
},
{
repoId: configuration.repository.id,
branch: configuration.repository.branch,
organization: configuration.repository.organization,
name: configuration.repository.name,
domain: configuration.publish.domain,
progress: 'failed'
}
);
console.log(error)
// console.log(configuration)
if (configuration) {
cleanupTmp(configuration.general.workdir);
await Deployment.findOneAndUpdate(
{
repoId: configuration.repository.id,
branch: configuration.repository.branch,
organization: configuration.repository.organization,
name: configuration.repository.name,
domain: configuration.publish.domain
},
{
repoId: configuration.repository.id,
branch: configuration.repository.branch,
organization: configuration.repository.organization,
name: configuration.repository.name,
domain: configuration.publish.domain,
progress: 'failed'
}
);
}
return {
status: 500,
body: {
error: error.message || error
}
};
} finally {
try {
await cleanupStuckedDeploymentsInDB();
} catch (error) {
console.log(error)
}
}
}

View File

@@ -84,13 +84,15 @@
deployment.progress !== 'failed'}
class:bg-warmGray-800={deployment.progress !== 'done' &&
deployment.progress !== 'failed'}
class:hover:bg-red-200={deployment.progress === 'failed'}
class:hover:border-red-500={deployment.progress === 'failed'}
on:click={() => goto(`./logs/${deployment.deployId}`)}
>
<div class="font-bold text-sm px-3 flex justify-center items-center">
{deployment.branch}
<div class="flex space-x-2 px-2">
<div class="font-bold text-sm flex justify-center items-center">
{deployment.branch}
</div>
<div class="font-bold text-xs flex justify-center items-center text-warmGray-500">{deployment.isPr ? 'PR' : ''}</div>
</div>
<div class="flex-1" />
<div class="px-3 w-48">
<div

View File

@@ -1,5 +1,5 @@
<script>
import { application, initialApplication, initConf, dashboard } from '$store';
import { application, initialApplication, initConf, dashboard, prApplication } from '$store';
import { onDestroy } from 'svelte';
import Loading from '$components/Loading.svelte';
import Navbar from '$components/Application/Navbar.svelte';
@@ -14,15 +14,16 @@
async function setConfiguration() {
try {
const config = await request(`/api/v1/application/config`, $session, {
const { configuration } = await request(`/api/v1/application/config`, $session, {
body: {
name: $application.repository.name,
organization: $application.repository.organization,
branch: $application.repository.branch
}
});
$application = { ...config };
$initConf = JSON.parse(JSON.stringify($application));
$prApplication = configuration.filter((c) => c.general.pullRequest !== 0);
$application = configuration.find((c) => c.general.pullRequest === 0);
if (!$application) browser && goto('/dashboard/applications');
} catch (error) {
browser && goto('/dashboard/applications');
}

View File

@@ -67,9 +67,7 @@
<div class="pb-2 pt-5 space-y-4">
<div class="text-2xl font-bold border-gradient w-32">Database</div>
<div class="flex items-center pt-4">
{#if $database.config.general.type !== 'redis'}
<div class="font-bold w-64 text-warmGray-400">Connection string</div>
{/if}
<div class="font-bold w-64 text-warmGray-400">Connection string</div>
{#if $database.config.general.type === 'mongodb'}
<PasswordField
value={`mongodb://${$database.envs.MONGODB_USERNAME}:${$database.envs.MONGODB_PASSWORD}@${$database.config.general.deployId}:27017/${$database.envs.MONGODB_DATABASE}`}
@@ -86,6 +84,10 @@
<PasswordField
value={`http://${$database.envs.COUCHDB_USER}:${$database.envs.COUCHDB_PASSWORD}@${$database.config.general.deployId}:5984`}
/>
{:else if $database.config.general.type === 'redis'}
<PasswordField
value={`redis://${$database.envs.REDIS_PASSWORD}@${$database.config.general.deployId}:6379`}
/>
{:else if $database.config.general.type === 'clickhouse'}
<!-- {JSON.stringify($database)} -->
<!-- <textarea

View File

@@ -20,7 +20,7 @@
', toolbar=0, menubar=0, status=0'
);
const timer = setInterval(() => {
if (newWindow.closed) {
if (newWindow?.closed) {
clearInterval(timer);
browser && location.reload()
}

View File

@@ -26,9 +26,9 @@
<script>
export let allowRegistration;
export let sendErrors;
import { browser } from '$app/env';
import { session } from '$app/stores';
import { request } from '$lib/request';
import { toast } from '@zerodevx/svelte-toast';
import { fade } from 'svelte/transition';