feat: simpleDockerfile deployment

This commit is contained in:
Andras Bacsai
2022-12-01 12:58:45 +01:00
parent a129be0dbd
commit 2e56086661
21 changed files with 459 additions and 184 deletions

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Application" ADD COLUMN "simpleDockerfile" TEXT;

View File

@@ -98,7 +98,7 @@ model TeamInvitation {
} }
model Application { model Application {
id String @id @default(cuid()) id String @id @default(cuid())
name String name String
fqdn String? fqdn String?
repository String? repository String?
@@ -124,23 +124,25 @@ model Application {
dockerComposeFile String? dockerComposeFile String?
dockerComposeFileLocation String? dockerComposeFileLocation String?
dockerComposeConfiguration String? dockerComposeConfiguration String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
destinationDockerId String? destinationDockerId String?
gitSourceId String? gitSourceId String?
gitCommitHash String? gitCommitHash String?
baseImage String? baseImage String?
baseBuildImage String? baseBuildImage String?
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
persistentStorage ApplicationPersistentStorage[]
settings ApplicationSettings? settings ApplicationSettings?
secrets Secret[]
teams Team[]
connectedDatabase ApplicationConnectedDatabase?
previewApplication PreviewApplication[]
dockerRegistryId String? dockerRegistryId String?
dockerRegistry DockerRegistry? @relation(fields: [dockerRegistryId], references: [id]) simpleDockerfile String?
persistentStorage ApplicationPersistentStorage[]
secrets Secret[]
teams Team[]
connectedDatabase ApplicationConnectedDatabase?
previewApplication PreviewApplication[]
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
dockerRegistry DockerRegistry? @relation(fields: [dockerRegistryId], references: [id])
} }
model PreviewApplication { model PreviewApplication {

View File

@@ -3,7 +3,7 @@ import crypto from 'crypto';
import fs from 'fs/promises'; import fs from 'fs/promises';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common'; import { copyBaseConfigurationFiles, makeLabelForSimpleDockerfile, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common';
import { createDirectories, decrypt, defaultComposeConfiguration, executeDockerCmd, getDomain, prisma, decryptApplication, isDev } from '../lib/common'; import { createDirectories, decrypt, defaultComposeConfiguration, executeDockerCmd, getDomain, prisma, decryptApplication, isDev } from '../lib/common';
import * as importers from '../lib/importers'; import * as importers from '../lib/importers';
import * as buildpacks from '../lib/buildPacks'; import * as buildpacks from '../lib/buildPacks';
@@ -39,10 +39,155 @@ import * as buildpacks from '../lib/buildPacks';
actions.push(async () => { actions.push(async () => {
let application = await prisma.application.findUnique({ where: { id: queueBuild.applicationId }, include: { destinationDocker: true, gitSource: { include: { githubApp: true, gitlabApp: true } }, persistentStorage: true, secrets: true, settings: true, teams: true } }) let application = await prisma.application.findUnique({ where: { id: queueBuild.applicationId }, include: { destinationDocker: true, gitSource: { include: { githubApp: true, gitlabApp: true } }, persistentStorage: true, secrets: true, settings: true, teams: true } })
let { id: buildId, type, sourceBranch = null, pullmergeRequestId = null, previewApplicationId = null, forceRebuild, sourceRepository = null } = queueBuild let { id: buildId, type, gitSourceId, sourceBranch = null, pullmergeRequestId = null, previewApplicationId = null, forceRebuild, sourceRepository = null } = queueBuild
application = decryptApplication(application) application = decryptApplication(application)
if (!gitSourceId && application.simpleDockerfile) {
const {
id: applicationId,
destinationDocker,
destinationDockerId,
secrets,
port,
persistentStorage,
exposePort,
simpleDockerfile
} = application
const { workdir } = await createDirectories({ repository: applicationId, buildId });
try {
if (queueBuild.status === 'running') {
await saveBuildLog({ line: 'Building halted, restarting...', buildId, applicationId: application.id });
}
const volumes =
persistentStorage?.map((storage) => {
if (storage.oldPath) {
return `${applicationId}${storage.path.replace(/\//gi, '-').replace('-app', '')}:${storage.path}`;
}
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
}) || [];
if (destinationDockerId) {
await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
try {
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker ps -a --filter 'label=com.docker.compose.service=${applicationId}' --format {{.ID}}|xargs -r -n 1 docker stop -t 0`
})
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker ps -a --filter 'label=com.docker.compose.service=${applicationId}' --format {{.ID}}|xargs -r -n 1 docker rm --force`
})
} catch (error) {
//
}
const envs = [
`PORT=${port}`
];
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (pullmergeRequestId) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
envs.push(`${secret.name}=${isSecretFound[0].value}`);
} else {
envs.push(`${secret.name}=${secret.value}`);
}
} else {
if (!secret.isPRMRSecret) {
envs.push(`${secret.name}=${secret.value}`);
}
}
});
}
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
let envFound = false;
try {
envFound = !!(await fs.stat(`${workdir}/.env`));
} catch (error) {
//
}
await fs.writeFile(`${workdir}/Dockerfile`, simpleDockerfile);
const labels = makeLabelForSimpleDockerfile({
applicationId,
type,
port: exposePort ? `${exposePort}:${port}` : port,
});
try {
await saveBuildLog({ line: 'Deployment initiated', buildId, applicationId });
const composeVolumes = volumes.map((volume) => {
return {
[`${volume.split(':')[0]}`]: {
name: volume.split(':')[0]
}
};
});
const composeFile = {
version: '3.8',
services: {
[applicationId]: {
build: {
context: workdir,
},
image: `${applicationId}:${buildId}`,
container_name: applicationId,
volumes,
labels,
env_file: envFound ? [`${workdir}/.env`] : [],
depends_on: [],
expose: [port],
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
...defaultComposeConfiguration(destinationDocker.network),
}
},
networks: {
[destinationDocker.network]: {
external: true
}
},
volumes: Object.assign({}, ...composeVolumes)
};
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
await saveBuildLog({ line: 'Deployed successfully 🎉', buildId, applicationId });
} catch (error) {
await saveBuildLog({ line: error, buildId, applicationId });
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
if (foundBuild) {
await prisma.build.update({
where: { id: buildId },
data: {
status: 'failed'
}
});
}
throw new Error(error);
}
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
}
} catch (error) {
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
if (foundBuild) {
await prisma.build.update({
where: { id: buildId },
data: {
status: 'failed'
}
});
}
if (error !== 1) {
await saveBuildLog({ line: error, buildId, applicationId: application.id });
}
} finally {
if (!isDev) {
await fs.rm(workdir, { recursive: true, force: true });
}
}
return;
}
const originalApplicationId = application.id const originalApplicationId = application.id
const { const {
id: applicationId, id: applicationId,
@@ -415,8 +560,7 @@ import * as buildpacks from '../lib/buildPacks';
}); });
} }
} }
} } catch (error) {
catch (error) {
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } }) const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
if (foundBuild) { if (foundBuild) {
await prisma.build.update({ await prisma.build.update({

View File

@@ -675,7 +675,14 @@ export async function buildImage({
await saveBuildLog({ line: `Building production image built successful 🎉`, buildId, applicationId }); await saveBuildLog({ line: `Building production image built successful 🎉`, buildId, applicationId });
} }
} }
export function makeLabelForSimpleDockerfile({ applicationId, port, type }) {
return [
'coolify.managed=true',
`coolify.version=${version}`,
`coolify.applicationId=${applicationId}`,
`coolify.type=standalone-application`
];
}
export function makeLabelForStandaloneApplication({ export function makeLabelForStandaloneApplication({
applicationId, applicationId,
fqdn, fqdn,

View File

@@ -1091,7 +1091,7 @@ export const createDirectories = async ({
repository: string; repository: string;
buildId: string; buildId: string;
}): Promise<{ workdir: string; repodir: string }> => { }): Promise<{ workdir: string; repodir: string }> => {
repository = repository.replaceAll(' ', '') if (repository) repository = repository.replaceAll(' ', '')
const repodir = `/tmp/build-sources/${repository}/`; const repodir = `/tmp/build-sources/${repository}/`;
const workdir = `/tmp/build-sources/${repository}/${buildId}`; const workdir = `/tmp/build-sources/${repository}/${buildId}`;
let workdirFound = false; let workdirFound = false;

View File

@@ -48,7 +48,6 @@ export async function checkContainer({ dockerId, container, remove = false }: {
isRunning, isRunning,
isRestarting, isRestarting,
isExited isExited
} }
}; };
} catch (err) { } catch (err) {

View File

@@ -334,7 +334,8 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
baseDatabaseBranch, baseDatabaseBranch,
dockerComposeFile, dockerComposeFile,
dockerComposeFileLocation, dockerComposeFileLocation,
dockerComposeConfiguration dockerComposeConfiguration,
simpleDockerfile
} = request.body } = request.body
if (port) port = Number(port); if (port) port = Number(port);
if (exposePort) { if (exposePort) {
@@ -373,6 +374,7 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
dockerComposeFile, dockerComposeFile,
dockerComposeFileLocation, dockerComposeFileLocation,
dockerComposeConfiguration, dockerComposeConfiguration,
simpleDockerfile,
...defaultConfiguration, ...defaultConfiguration,
connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } } connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
} }
@@ -395,6 +397,7 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
dockerComposeFile, dockerComposeFile,
dockerComposeFileLocation, dockerComposeFileLocation,
dockerComposeConfiguration, dockerComposeConfiguration,
simpleDockerfile,
...defaultConfiguration ...defaultConfiguration
} }
}); });
@@ -770,22 +773,37 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
await prisma.application.update({ where: { id }, data: { configHash } }); await prisma.application.update({ where: { id }, data: { configHash } });
} }
await prisma.application.update({ where: { id }, data: { updatedAt: new Date() } }); await prisma.application.update({ where: { id }, data: { updatedAt: new Date() } });
await prisma.build.create({ if (application.gitSourceId) {
data: { await prisma.build.create({
id: buildId, data: {
applicationId: id, id: buildId,
sourceBranch: branch, applicationId: id,
branch: application.branch, sourceBranch: branch,
pullmergeRequestId: pullmergeRequestId?.toString(), branch: application.branch,
forceRebuild, pullmergeRequestId: pullmergeRequestId?.toString(),
destinationDockerId: application.destinationDocker?.id, forceRebuild,
gitSourceId: application.gitSource?.id, destinationDockerId: application.destinationDocker?.id,
githubAppId: application.gitSource?.githubApp?.id, gitSourceId: application.gitSource?.id,
gitlabAppId: application.gitSource?.gitlabApp?.id, githubAppId: application.gitSource?.githubApp?.id,
status: 'queued', gitlabAppId: application.gitSource?.gitlabApp?.id,
type: pullmergeRequestId ? application.gitSource?.githubApp?.id ? 'manual_pr' : 'manual_mr' : 'manual' status: 'queued',
} type: pullmergeRequestId ? application.gitSource?.githubApp?.id ? 'manual_pr' : 'manual_mr' : 'manual'
}); }
});
} else {
await prisma.build.create({
data: {
id: buildId,
applicationId: id,
branch: 'latest',
forceRebuild,
destinationDockerId: application.destinationDocker?.id,
status: 'queued',
type: 'manual'
}
});
}
return { return {
buildId buildId
}; };
@@ -800,20 +818,29 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
export async function saveApplicationSource(request: FastifyRequest<SaveApplicationSource>, reply: FastifyReply) { export async function saveApplicationSource(request: FastifyRequest<SaveApplicationSource>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params
const { gitSourceId, forPublic, type } = request.body const { gitSourceId, forPublic, type, simpleDockerfile } = request.body
if (forPublic) { if (forPublic) {
const publicGit = await prisma.gitSource.findFirst({ where: { type, forPublic } }); if (gitSourceId) {
await prisma.application.update({
where: { id },
data: { gitSource: { connect: { id: gitSourceId } } }
});
} else {
const publicGit = await prisma.gitSource.findFirst({ where: { type, forPublic } });
await prisma.application.update({
where: { id },
data: { gitSource: { connect: { id: publicGit.id } } }
});
}
}
if (simpleDockerfile) {
await prisma.application.update({ await prisma.application.update({
where: { id }, where: { id },
data: { gitSource: { connect: { id: publicGit.id } } } data: { simpleDockerfile }
});
} else {
await prisma.application.update({
where: { id },
data: { gitSource: { connect: { id: gitSourceId } } }
}); });
} }
return reply.code(201).send() return reply.code(201).send()
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@@ -916,11 +943,11 @@ export async function getBuildPack(request) {
const teamId = request.user?.teamId; const teamId = request.user?.teamId;
const application: any = await getApplicationFromDB(id, teamId); const application: any = await getApplicationFromDB(id, teamId);
return { return {
type: application.gitSource.type, type: application.gitSource?.type || 'dockerRegistry',
projectId: application.projectId, projectId: application.projectId,
repository: application.repository, repository: application.repository,
branch: application.branch, branch: application.branch,
apiUrl: application.gitSource.apiUrl, apiUrl: application.gitSource?.apiUrl || null,
isPublicRepository: application.settings.isPublicRepository isPublicRepository: application.settings.isPublicRepository
} }
} catch ({ status, message }) { } catch ({ status, message }) {

View File

@@ -25,7 +25,8 @@ export interface SaveApplication extends OnlyId {
baseDatabaseBranch: string, baseDatabaseBranch: string,
dockerComposeFile: string, dockerComposeFile: string,
dockerComposeFileLocation: string, dockerComposeFileLocation: string,
dockerComposeConfiguration: string dockerComposeConfiguration: string,
simpleDockerfile: string
} }
} }
export interface SaveApplicationSettings extends OnlyId { export interface SaveApplicationSettings extends OnlyId {
@@ -56,7 +57,7 @@ export interface GetImages {
Body: { buildPack: string, deploymentType: string } Body: { buildPack: string, deploymentType: string }
} }
export interface SaveApplicationSource extends OnlyId { export interface SaveApplicationSource extends OnlyId {
Body: { gitSourceId?: string | null, forPublic?: boolean, type?: string } Body: { gitSourceId?: string | null, forPublic?: boolean, type?: string, simpleDockerfile?: string }
} }
export interface CheckRepository extends OnlyId { export interface CheckRepository extends OnlyId {
Querystring: { repository: string, branch: string } Querystring: { repository: string, branch: string }

View File

@@ -190,7 +190,7 @@ export async function showDashboard(request: FastifyRequest) {
let foundUnconfiguredApplication = false; let foundUnconfiguredApplication = false;
for (const application of applications) { for (const application of applications) {
if (!application.buildPack || !application.destinationDockerId || !application.branch || (!application.settings?.isBot && !application?.fqdn) && application.buildPack !== "compose") { if (((!application.buildPack || !application.branch) && !application.simpleDockerfile) || !application.destinationDockerId || (!application.settings?.isBot && !application?.fqdn) && application.buildPack !== "compose") {
foundUnconfiguredApplication = true foundUnconfiguredApplication = true
} }
} }

View File

@@ -42,4 +42,6 @@
<Icons.Heroku {isAbsolute} /> <Icons.Heroku {isAbsolute} />
{:else if application.buildPack?.toLowerCase() === 'compose'} {:else if application.buildPack?.toLowerCase() === 'compose'}
<Icons.Compose {isAbsolute} /> <Icons.Compose {isAbsolute} />
{:else if application.simpleDockerfile}
<Icons.Docker {isAbsolute} />
{/if} {/if}

View File

@@ -19,14 +19,14 @@ export async function refreshStatus(list: Array<any>) {
} }
export async function getStatus(resource: any, force: boolean = false) { export async function getStatus(resource: any, force: boolean = false) {
const { id, buildPack, dualCerts, engine } = resource; const { id, buildPack, dualCerts, engine, simpleDockerfile } = resource;
let newStatus = 'stopped'; let newStatus = 'stopped';
// Already set and we're not forcing // Already set and we're not forcing
if (getStore(containerStatus)[id] && !force) return getStore(containerStatus)[id]; if (getStore(containerStatus)[id] && !force) return getStore(containerStatus)[id];
try { try {
if (buildPack) { // Application if (buildPack || simpleDockerfile) { // Application
const response = await get(`/applications/${id}/status`); const response = await get(`/applications/${id}/status`);
newStatus = parseApplicationsResponse(response); newStatus = parseApplicationsResponse(response);
} else if (typeof dualCerts !== 'undefined') { // Service } else if (typeof dualCerts !== 'undefined') { // Service

View File

@@ -57,14 +57,15 @@ export const appSession: Writable<AppSession> = writable({
export const disabledButton: Writable<boolean> = writable(false); export const disabledButton: Writable<boolean> = writable(false);
export const isDeploymentEnabled: Writable<boolean> = writable(false); export const isDeploymentEnabled: Writable<boolean> = writable(false);
export function checkIfDeploymentEnabledApplications(isAdmin: boolean, application: any) { export function checkIfDeploymentEnabledApplications(isAdmin: boolean, application: any) {
return ( return !!(
isAdmin && isAdmin &&
(application.buildPack === 'compose') || (application.buildPack === 'compose') ||
(application.fqdn || application.settings.isBot) && (application.fqdn || application.settings.isBot) &&
application.gitSource && ((application.gitSource &&
application.repository && application.repository &&
application.destinationDocker && application.buildPack) || application.simpleDockerfile) &&
application.buildPack application.destinationDocker
); );
} }
export function checkIfDeploymentEnabledServices(isAdmin: boolean, service: any) { export function checkIfDeploymentEnabledServices(isAdmin: boolean, service: any) {

View File

@@ -218,28 +218,30 @@
<li class="menu-title"> <li class="menu-title">
<span>Advanced</span> <span>Advanced</span>
</li> </li>
<li {#if !application.simpleDockerfile}
class="rounded" <li
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/revert`} class="rounded"
> class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/revert`}
<a href={`/applications/${$page.params.id}/revert`} class="no-underline w-full">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M20 5v14l-12 -7z" />
<line x1="4" y1="5" x2="4" y2="19" />
</svg>
Revert</a
> >
</li> <a href={`/applications/${$page.params.id}/revert`} class="no-underline w-full">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M20 5v14l-12 -7z" />
<line x1="4" y1="5" x2="4" y2="19" />
</svg>
Revert</a
>
</li>
{/if}
<li <li
class="rounded" class="rounded"
class:text-stone-600={$status.application.overallStatus !== 'healthy'} class:text-stone-600={$status.application.overallStatus !== 'healthy'}
@@ -265,7 +267,7 @@
</svg>Monitoring</a </svg>Monitoring</a
> >
</li> </li>
{#if !application.settings.isBot} {#if !application.settings.isBot && !application.simpleDockerfile}
<li <li
class="rounded" class="rounded"
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/previews`} class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/previews`}

View File

@@ -2,8 +2,14 @@
import type { Load } from '@sveltejs/kit'; import type { Load } from '@sveltejs/kit';
function checkConfiguration(application: any): string | null { function checkConfiguration(application: any): string | null {
let configurationPhase = null; let configurationPhase = null;
if (!application.gitSourceId) { if (!application.gitSourceId && !application.simpleDockerfile) {
configurationPhase = 'source'; return (configurationPhase = 'source');
}
if (application.simpleDockerfile) {
if (!application.destinationDockerId) {
configurationPhase = 'destination';
}
return configurationPhase;
} else if (!application.repository && !application.branch) { } else if (!application.repository && !application.branch) {
configurationPhase = 'repository'; configurationPhase = 'repository';
} else if (!application.destinationDockerId) { } else if (!application.destinationDockerId) {

View File

@@ -9,6 +9,12 @@
redirect: `/applications/${params.id}` redirect: `/applications/${params.id}`
}; };
} }
if (application.simpleDockerfile) {
return {
status: 302,
redirect: `/applications/${params.id}`
};
}
const response = await get(`/applications/${params.id}/configuration/buildpack`); const response = await get(`/applications/${params.id}/configuration/buildpack`);
return { return {
props: { props: {
@@ -47,7 +53,7 @@
const { id } = $page.params; const { id } = $page.params;
let htmlUrl = application.gitSource.htmlUrl; let htmlUrl = application.gitSource?.htmlUrl || null;
let scanning: boolean = true; let scanning: boolean = true;
let foundConfig: any = null; let foundConfig: any = null;

View File

@@ -25,6 +25,8 @@
</script> </script>
<script lang="ts"> <script lang="ts">
export let sources: any;
import { page } from '$app/stores'; import { page } from '$app/stores';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { get, post } from '$lib/api'; import { get, post } from '$lib/api';
@@ -33,11 +35,12 @@
import { appSession } from '$lib/store'; import { appSession } from '$lib/store';
import PublicRepository from './_PublicRepository.svelte'; import PublicRepository from './_PublicRepository.svelte';
import DocLink from '$lib/components/DocLink.svelte'; import DocLink from '$lib/components/DocLink.svelte';
import Beta from '$lib/components/Beta.svelte';
const { id } = $page.params; const { id } = $page.params;
const from = $page.url.searchParams.get('from'); const from = $page.url.searchParams.get('from');
let simpleDockerfile: any = null;
export let sources: any;
const filteredSources = sources.filter( const filteredSources = sources.filter(
(source: any) => (source: any) =>
(source.type === 'github' && source.githubAppId && source.githubApp.installationId) || (source.type === 'github' && source.githubAppId && source.githubApp.installationId) ||
@@ -61,18 +64,20 @@
return errorNotification(error); return errorNotification(error);
} }
} }
async function newSource() { async function handleDockerImage() {
const { id } = await post('/sources/new', {}); try {
return await goto(`/sources/${id}`, { replaceState: true }); await post(`/applications/${id}/configuration/source`, { simpleDockerfile });
return await goto(from || `/applications/${id}/configuration/destination`);
} catch (error) {
return errorNotification(error);
}
} }
</script> </script>
<div class="max-w-screen-2xl mx-auto px-9"> <div class="max-w-screen-2xl mx-auto px-9">
{#if !filteredSources} {#if !filteredSources}
<div class="title pb-8">Git App</div> <div class="title pb-8">Git App</div>
{/if} <div class="flex flex-wrap justify-center">
<div class="flex flex-wrap justify-center">
{#if !filteredSources}
<div class="flex-col"> <div class="flex-col">
<div class="pb-2 text-center font-bold"> <div class="pb-2 text-center font-bold">
{$t('application.configuration.no_configurable_git')} {$t('application.configuration.no_configurable_git')}
@@ -95,7 +100,13 @@
</a> </a>
</div> </div>
</div> </div>
{:else} </div>
{/if}
{#if ownSources.length > 0 || otherSources.length > 0}
<div class="title pb-8">Integrated with Git App</div>
{/if}
{#if ownSources.length > 0}
<div class="flex flex-wrap justify-center">
<div class="flex flex-col lg:flex-row lg:flex-wrap justify-center"> <div class="flex flex-col lg:flex-row lg:flex-wrap justify-center">
{#each ownSources as source} {#each ownSources as source}
<div class="p-2 relative"> <div class="p-2 relative">
@@ -240,11 +251,25 @@
</div> </div>
{/each} {/each}
</div> </div>
{/if} </div>
</div> {/if}
<div class="flex flex-row items-center"> <div class="flex flex-row items-center">
<div class="title py-4 pr-4">Public Repository</div> <div class="title py-4 pr-4">Public Repository from Git</div>
<DocLink url="https://docs.coollabs.io/coolify/applications/#public-repository" /> <DocLink url="https://docs.coollabs.io/coolify/applications/#public-repository" />
</div> </div>
<PublicRepository /> <PublicRepository />
<div class="flex flex-row items-center pt-10">
<div class="title py-4 pr-4">Simple Dockerfile <Beta /></div>
<DocLink url="https://docs.coollabs.io/coolify/applications/#dockerfile" />
</div>
<div class="mx-auto max-w-screen-2xl">
<form class="flex flex-col" on:submit|preventDefault={handleDockerImage}>
<div class="flex flex-col space-y-2 w-full">
<div class="flex flex-row space-x-2">
<textarea required class="w-full" rows="10" bind:value={simpleDockerfile} />
<button class="btn btn-primary" type="submit">Deploy Dockerfile</button>
</div>
</div>
</form>
</div>
</div> </div>

View File

@@ -124,7 +124,7 @@
description={$t('application.enable_auto_deploy_webhooks')} description={$t('application.enable_auto_deploy_webhooks')}
/> />
</div> </div>
{#if !application.settings.isBot} {#if !application.settings.isBot && !application.simpleDockerfile}
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<Setting <Setting
id="previews" id="previews"

View File

@@ -66,6 +66,7 @@
save: false, save: false,
reloadCompose: false reloadCompose: false
}; };
let isSimpleDockerfile = !!application.simpleDockerfile;
let fqdnEl: any = null; let fqdnEl: any = null;
let forceSave = false; let forceSave = false;
let isPublicRepository = application.settings?.isPublicRepository; let isPublicRepository = application.settings?.isPublicRepository;
@@ -268,7 +269,7 @@
} }
} }
} }
await saveForm(id, application,baseDatabaseBranch, dockerComposeConfiguration); await saveForm(id, application, baseDatabaseBranch, dockerComposeConfiguration);
setLocation(application, settings); setLocation(application, settings);
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application); $isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
@@ -492,79 +493,84 @@
<label for="name">{$t('forms.name')}</label> <label for="name">{$t('forms.name')}</label>
<input name="name" id="name" class="w-full" bind:value={application.name} required /> <input name="name" id="name" class="w-full" bind:value={application.name} required />
</div> </div>
<div class="grid grid-cols-2 items-center"> {#if !isSimpleDockerfile}
<label for="gitSource">{$t('application.git_source')}</label> <div class="grid grid-cols-2 items-center">
{#if isDisabled || application.settings.isPublicRepository} <label for="gitSource">{$t('application.git_source')}</label>
<input {#if isDisabled || application.settings.isPublicRepository}
disabled={isDisabled || application.settings.isPublicRepository} <input
class="w-full" disabled={isDisabled || application.settings.isPublicRepository}
value={application.gitSource?.name} class="w-full"
/>
{:else}
<a
href={`/applications/${id}/configuration/source?from=/applications/${id}`}
class="no-underline"
><input
value={application.gitSource?.name} value={application.gitSource?.name}
id="gitSource" />
class="cursor-pointer hover:bg-coolgray-500 w-full" {:else}
/></a <a
> href={`/applications/${id}/configuration/source?from=/applications/${id}`}
{/if} class="no-underline"
</div> ><input
<div class="grid grid-cols-2 items-center"> value={application.gitSource?.name}
<label for="repository">Git commit</label> id="gitSource"
<div class="flex gap-2"> class="cursor-pointer hover:bg-coolgray-500 w-full"
<input /></a
id="commit"
name="commit"
class="w-full"
disabled={isDisabled}
placeholder="default: latest commit"
bind:value={application.gitCommitHash}
/>
<a
href="{application.gitSource
.htmlUrl}/{application.repository}/commits/{application.branch}"
target="_blank noreferrer"
class="btn btn-primary text-xs"
>Commits<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
stroke-width="3"
stroke="currentColor"
class="w-3 h-3 text-white ml-2"
> >
<path {/if}
stroke-linecap="round"
stroke-linejoin="round"
d="M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25"
/>
</svg></a
>
</div> </div>
</div>
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<label for="repository">{$t('application.git_repository')}</label> <label for="repository">Git commit</label>
{#if isDisabled || application.settings.isPublicRepository} <div class="flex gap-2">
<input <input
class="w-full" id="commit"
disabled={isDisabled || application.settings.isPublicRepository} name="commit"
value="{application.repository}/{application.branch}" class="w-full"
/> disabled={isDisabled}
{:else} placeholder="default: latest commit"
<a bind:value={application.gitCommitHash}
href={`/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`} />
class="no-underline" <a
><input href="{application.gitSource
.htmlUrl}/{application.repository}/commits/{application.branch}"
target="_blank noreferrer"
class="btn btn-primary text-xs"
>Commits<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
stroke-width="3"
stroke="currentColor"
class="w-3 h-3 text-white ml-2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25"
/>
</svg></a
>
</div>
</div>
<div class="grid grid-cols-2 items-center">
<label for="repository">{$t('application.git_repository')}</label>
{#if isDisabled || application.settings.isPublicRepository}
<input
class="w-full"
disabled={isDisabled || application.settings.isPublicRepository}
value="{application.repository}/{application.branch}" value="{application.repository}/{application.branch}"
id="repository" />
class="cursor-pointer hover:bg-coolgray-500 w-full" {:else}
/></a <a
> href={`/applications/${id}/configuration/repository?from=/applications/${id}&to=/applications/${id}/configuration/buildpack`}
{/if} class="no-underline"
</div> ><input
value="{application.repository}/{application.branch}"
id="repository"
class="cursor-pointer hover:bg-coolgray-500 w-full"
/></a
>
{/if}
</div>
{:else}
{/if}
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<label for="registry">Docker Registry</label> <label for="registry">Docker Registry</label>
{#if isDisabled} {#if isDisabled}
@@ -586,23 +592,29 @@
> >
{/if} {/if}
</div> </div>
<div class="grid grid-cols-2 items-center"> {#if !isSimpleDockerfile}
<label for="buildPack">{$t('application.build_pack')} </label> <div class="grid grid-cols-2 items-center">
{#if isDisabled} <label for="buildPack">{$t('application.build_pack')} </label>
<input class="capitalize w-full" disabled={isDisabled} value={application.buildPack} /> {#if isDisabled}
{:else}
<a
href={`/applications/${id}/configuration/buildpack?from=/applications/${id}`}
class="no-underline"
>
<input <input
class="capitalize w-full"
disabled={isDisabled}
value={application.buildPack} value={application.buildPack}
id="buildPack" />
class="cursor-pointer hover:bg-coolgray-500 capitalize w-full" {:else}
/></a <a
> href={`/applications/${id}/configuration/buildpack?from=/applications/${id}`}
{/if} class="no-underline"
</div> >
<input
value={application.buildPack}
id="buildPack"
class="cursor-pointer hover:bg-coolgray-500 capitalize w-full"
/></a
>
{/if}
</div>
{/if}
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<label for="destination">{$t('application.destination')}</label> <label for="destination">{$t('application.destination')}</label>
<div class="no-underline"> <div class="no-underline">
@@ -712,7 +724,44 @@
{/if} {/if}
{/if} {/if}
</div> </div>
{#if application.buildPack !== 'compose'} {#if isSimpleDockerfile}
<div class="title font-bold pb-3 pt-10 border-b border-coolgray-500 mb-6">
Configuration
</div>
<div class="grid grid-flow-row gap-2 px-4 pr-5">
<div class="grid grid-cols-2 items-center pt-4">
<label for="simpleDockerfile">Dockerfile</label>
<div class="flex gap-2">
<textarea
rows=10
id="simpleDockerfile"
name="simpleDockerfile"
class="w-full"
disabled={isDisabled}
bind:value={application.simpleDockerfile}
/>
</div>
</div>
<div class="grid grid-cols-2 items-center">
<label for="port"
>{$t('forms.port')}
<Explainer
explanation={'The port your application listens inside the docker container.'}
/></label
>
<input
class="w-full"
disabled={isDisabled}
readonly={!$appSession.isAdmin}
name="port"
id="port"
bind:value={application.port}
placeholder="{$t('forms.default')}: 3000"
/>
</div>
</div>
{:else if application.buildPack !== 'compose'}
<div class="title font-bold pb-3 pt-10 border-b border-coolgray-500 mb-6"> <div class="title font-bold pb-3 pt-10 border-b border-coolgray-500 mb-6">
Configuration Configuration
</div> </div>

View File

@@ -20,6 +20,7 @@
<script lang="ts"> <script lang="ts">
export let secrets: any; export let secrets: any;
export let application: any;
export let previewSecrets: any; export let previewSecrets: any;
import pLimit from 'p-limit'; import pLimit from 'p-limit';
import { page } from '$app/stores'; import { page } from '$app/stores';
@@ -28,7 +29,6 @@
import Secret from './_Secret.svelte'; import Secret from './_Secret.svelte';
import PreviewSecret from './_PreviewSecret.svelte'; import PreviewSecret from './_PreviewSecret.svelte';
import { errorNotification } from '$lib/common'; import { errorNotification } from '$lib/common';
import { t } from '$lib/translations';
import Explainer from '$lib/components/Explainer.svelte'; import Explainer from '$lib/components/Explainer.svelte';
const limit = pLimit(1); const limit = pLimit(1);
@@ -110,6 +110,7 @@
<div class="lg:pt-0 pt-10"> <div class="lg:pt-0 pt-10">
<Secret on:refresh={refreshSecrets} length={secrets.length} isNewSecret /> <Secret on:refresh={refreshSecrets} length={secrets.length} isNewSecret />
</div> </div>
{#if !application.settings.isBot && !application.simpleDockerfile}
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2"> <div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2">
<div class="title font-bold pb-3 pt-8"> <div class="title font-bold pb-3 pt-8">
Preview Secrets <Explainer Preview Secrets <Explainer
@@ -133,6 +134,7 @@
{:else} {:else}
Add secrets first to see Preview Secrets. Add secrets first to see Preview Secrets.
{/if} {/if}
{/if}
</div> </div>
<form on:submit|preventDefault={getValues} class="mb-12 w-full"> <form on:submit|preventDefault={getValues} class="mb-12 w-full">
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2 pt-10"> <div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2 pt-10">

View File

@@ -86,7 +86,7 @@
<Storage on:refresh={refreshStorage} {storage} /> <Storage on:refresh={refreshStorage} {storage} />
{/key} {/key}
{/each} {/each}
<div class="title pt-10"> <div class="Preview Secrets" class:pt-10={predefinedVolumes.length > 0}>
Add New Volume <Explainer Add New Volume <Explainer
position="dropdown-bottom" position="dropdown-bottom"
explanation={$t('application.storage.persistent_storage_explainer')} explanation={$t('application.storage.persistent_storage_explainer')}

View File

@@ -151,7 +151,7 @@
} }
async function getStatus(resources: any, force: boolean = false) { async function getStatus(resources: any, force: boolean = false) {
const { id, buildPack, dualCerts, type } = resources; const { id, buildPack, dualCerts, type, simpleDockerfile } = resources;
if (buildPack && applications.length + filtered.otherApplications.length > 10 && !force) { if (buildPack && applications.length + filtered.otherApplications.length > 10 && !force) {
noInitialStatus.applications = true; noInitialStatus.applications = true;
return; return;
@@ -172,7 +172,7 @@
numberOfGetStatus++; numberOfGetStatus++;
let isRunning = false; let isRunning = false;
let isDegraded = false; let isDegraded = false;
if (buildPack) { if (buildPack || simpleDockerfile) {
const response = await get(`/applications/${id}/status`); const response = await get(`/applications/${id}/status`);
if (response.length === 0) { if (response.length === 0) {
isRunning = false; isRunning = false;