diff --git a/prisma/migrations/20220402135305_python/migration.sql b/prisma/migrations/20220402135305_python/migration.sql new file mode 100644 index 000000000..0c253d539 --- /dev/null +++ b/prisma/migrations/20220402135305_python/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "Application" ADD COLUMN "pythonModule" TEXT; +ALTER TABLE "Application" ADD COLUMN "pythonVariable" TEXT; +ALTER TABLE "Application" ADD COLUMN "pythonWSGI" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a849ae36f..b7fab7185 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -87,6 +87,9 @@ model Application { baseDirectory String? publishDirectory String? phpModules String? + pythonWSGI String? + pythonModule String? + pythonVariable String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt settings ApplicationSettings? diff --git a/src/lib/buildPacks/common.ts b/src/lib/buildPacks/common.ts index 3f8b4b43e..441794217 100644 --- a/src/lib/buildPacks/common.ts +++ b/src/lib/buildPacks/common.ts @@ -100,10 +100,14 @@ export const setDefaultConfiguration = async (data) => { if (buildPack === 'static') port = 80; else if (buildPack === 'node') port = 3000; else if (buildPack === 'php') port = 80; + else if (buildPack === 'python') port = 8000; } - if (!installCommand) installCommand = template?.installCommand || 'yarn install'; - if (!startCommand) startCommand = template?.startCommand || 'yarn start'; - if (!buildCommand) buildCommand = template?.buildCommand || null; + if (template) { + if (!installCommand) installCommand = template?.installCommand || 'yarn install'; + if (!startCommand) startCommand = template?.startCommand || 'yarn start'; + if (!buildCommand) buildCommand = template?.buildCommand || null; + } + if (!publishDirectory) publishDirectory = template?.publishDirectory || null; if (baseDirectory) { if (!baseDirectory.startsWith('/')) baseDirectory = `/${baseDirectory}`; diff --git a/src/lib/buildPacks/index.ts b/src/lib/buildPacks/index.ts index 41a7655f9..babbe8f17 100644 --- a/src/lib/buildPacks/index.ts +++ b/src/lib/buildPacks/index.ts @@ -12,6 +12,7 @@ import php from './php'; import rust from './rust'; import astro from './static'; import eleventy from './static'; +import python from './python'; export { node, @@ -27,5 +28,6 @@ export { php, rust, astro, - eleventy + eleventy, + python }; diff --git a/src/lib/buildPacks/python.ts b/src/lib/buildPacks/python.ts new file mode 100644 index 000000000..1aad28fe6 --- /dev/null +++ b/src/lib/buildPacks/python.ts @@ -0,0 +1,73 @@ +import { buildImage } from '$lib/docker'; +import { promises as fs } from 'fs'; + +const createDockerfile = async (data, image): Promise => { + const { + workdir, + port, + baseDirectory, + secrets, + pullmergeRequestId, + pythonWSGI, + pythonModule, + pythonVariable + } = data; + const Dockerfile: Array = []; + Dockerfile.push(`FROM ${image}`); + Dockerfile.push('WORKDIR /app'); + Dockerfile.push(`LABEL coolify.image=true`); + if (secrets.length > 0) { + secrets.forEach((secret) => { + if (secret.isBuildSecret) { + if (pullmergeRequestId) { + if (secret.isPRMRSecret) { + Dockerfile.push(`ARG ${secret.name}=${secret.value}`); + } + } else { + if (!secret.isPRMRSecret) { + Dockerfile.push(`ARG ${secret.name}=${secret.value}`); + } + } + } + }); + } + if (pythonWSGI?.toLowerCase() === 'gunicorn') { + Dockerfile.push(`RUN pip install gunicorn`); + } else if (pythonWSGI?.toLowerCase() === 'uwsgi') { + Dockerfile.push(`RUN apk add --no-cache uwsgi-python3`); + // Dockerfile.push(`RUN pip install --no-cache-dir uwsgi`) + } + + try { + await fs.stat(`${workdir}${baseDirectory || ''}/requirements.txt`); + Dockerfile.push(`COPY .${baseDirectory || ''}/requirements.txt ./`); + Dockerfile.push(`RUN pip install --no-cache-dir -r .${baseDirectory || ''}/requirements.txt`); + } catch (e) { + // + } + Dockerfile.push(`COPY .${baseDirectory || ''} ./`); + Dockerfile.push(`EXPOSE ${port}`); + console.log({ pythonWSGI }); + if (pythonWSGI?.toLowerCase() === 'gunicorn') { + console.log({ pythonModule, pythonVariable }); + Dockerfile.push(`CMD gunicorn -w=4 -b=0.0.0.0:8000 ${pythonModule}:${pythonVariable}`); + } else if (pythonWSGI?.toLowerCase() === 'uwsgi') { + Dockerfile.push( + `CMD uwsgi --master -p 4 --http-socket 0.0.0.0:8000 --uid uwsgi --plugins python3 --protocol uwsgi --wsgi ${pythonModule}:${pythonVariable}` + ); + } else { + Dockerfile.push(`CMD python ${pythonModule}`); + } + + await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); +}; + +export default async function (data) { + try { + const image = 'python:3-alpine'; + await createDockerfile(data, image); + await buildImage(data); + } catch (error) { + throw error; + } +} diff --git a/src/lib/components/common.ts b/src/lib/components/common.ts index b9b3ac982..96ae8fb52 100644 --- a/src/lib/components/common.ts +++ b/src/lib/components/common.ts @@ -19,7 +19,7 @@ export const staticDeployments = [ 'astro', 'eleventy' ]; -export const notNodeDeployments = ['php', 'docker', 'rust']; +export const notNodeDeployments = ['php', 'docker', 'rust', 'python']; export function getDomain(domain) { return domain?.replace('https://', '').replace('http://', ''); diff --git a/src/lib/components/templates.ts b/src/lib/components/templates.ts index 5ad62f6fa..b82b12f51 100644 --- a/src/lib/components/templates.ts +++ b/src/lib/components/templates.ts @@ -146,6 +146,13 @@ export function findBuildPack(pack, packageManager = 'npm') { port: 80 }; } + if (pack === 'python') { + return { + ...metaData, + startCommand: null, + port: 8000 + }; + } return { name: 'node', fancyName: 'Node.js', @@ -249,6 +256,12 @@ export const buildPacks = [ fancyName: 'Rust', hoverColor: 'hover:bg-pink-700', color: 'bg-pink-700' + }, + { + name: 'python', + fancyName: 'Python', + hoverColor: 'hover:bg-green-700', + color: 'bg-green-700' } ]; export const scanningTemplates = { diff --git a/src/lib/database/applications.ts b/src/lib/database/applications.ts index ad03ed7c0..1d8140144 100644 --- a/src/lib/database/applications.ts +++ b/src/lib/database/applications.ts @@ -214,11 +214,15 @@ export async function configureApplication({ buildCommand, startCommand, baseDirectory, - publishDirectory + publishDirectory, + pythonWSGI, + pythonModule, + pythonVariable }) { return await prisma.application.update({ where: { id }, data: { + name, buildPack, fqdn, port, @@ -227,7 +231,9 @@ export async function configureApplication({ startCommand, baseDirectory, publishDirectory, - name + pythonWSGI, + pythonModule, + pythonVariable } }); } diff --git a/src/lib/queues/builder.ts b/src/lib/queues/builder.ts index 3898bcdb5..be46976c5 100644 --- a/src/lib/queues/builder.ts +++ b/src/lib/queues/builder.ts @@ -51,7 +51,10 @@ export default async function (job) { pullmergeRequestId = null, sourceBranch = null, settings, - persistentStorage + persistentStorage, + pythonWSGI, + pythonModule, + pythonVariable } = job.data; const { debug } = settings; @@ -127,7 +130,7 @@ export default async function (job) { } try { - db.prisma.build.update({ where: { id: buildId }, data: { commit } }); + await db.prisma.build.update({ where: { id: buildId }, data: { commit } }); } catch (err) { console.log(err); } @@ -200,7 +203,10 @@ export default async function (job) { startCommand, baseDirectory, secrets, - phpModules + phpModules, + pythonWSGI, + pythonModule, + pythonVariable }); else { saveBuildLog({ line: `Build pack ${buildPack} not found`, buildId, applicationId }); diff --git a/src/routes/applications/[id]/index.json.ts b/src/routes/applications/[id]/index.json.ts index 3b4405706..8a67242d2 100644 --- a/src/routes/applications/[id]/index.json.ts +++ b/src/routes/applications/[id]/index.json.ts @@ -5,6 +5,7 @@ import { checkContainer } from '$lib/haproxy'; import type { RequestHandler } from '@sveltejs/kit'; import jsonwebtoken from 'jsonwebtoken'; import { get as getRequest } from '$lib/api'; +import { setDefaultConfiguration } from '$lib/buildPacks/common'; export const get: RequestHandler = async (event) => { const { teamId, status, body } = await getUserDetails(event); @@ -52,12 +53,23 @@ export const post: RequestHandler = async (event) => { buildCommand, startCommand, baseDirectory, - publishDirectory + publishDirectory, + pythonWSGI, + pythonModule, + pythonVariable } = await event.request.json(); - if (port) port = Number(port); try { + const defaultConfiguration = await setDefaultConfiguration({ + buildPack, + port, + installCommand, + startCommand, + buildCommand, + publishDirectory, + baseDirectory + }); await db.configureApplication({ id, buildPack, @@ -68,7 +80,11 @@ export const post: RequestHandler = async (event) => { buildCommand, startCommand, baseDirectory, - publishDirectory + publishDirectory, + pythonWSGI, + pythonModule, + pythonVariable, + ...defaultConfiguration }); return { status: 201 }; } catch (error) { diff --git a/src/routes/applications/[id]/index.svelte b/src/routes/applications/[id]/index.svelte index 49f800975..bcdbfc876 100644 --- a/src/routes/applications/[id]/index.svelte +++ b/src/routes/applications/[id]/index.svelte @@ -38,6 +38,7 @@ import { page, session } from '$app/stores'; import { errorNotification } from '$lib/form'; import { onMount } from 'svelte'; + import Select from 'svelte-select'; import Explainer from '$lib/components/Explainer.svelte'; import Setting from '$lib/components/Setting.svelte'; @@ -57,6 +58,23 @@ let previews = application.settings.previews; let dualCerts = application.settings.dualCerts; let autodeploy = application.settings.autodeploy; + + let wsgis = [ + { + value: 'None', + label: 'None' + }, + { + value: 'Gunicorn', + label: 'Gunicorn' + } + // }, + // { + // value: 'uWSGI', + // label: 'uWSGI' + // } + ]; + if (browser && window.location.hostname === 'demo.coolify.io' && !application.fqdn) { application.fqdn = `http://${cuid()}.demo.coolify.io`; } @@ -119,6 +137,9 @@ loading = false; } } + async function selectWSGI(event) { + application.pythonWSGI = event.detail.value; + }
@@ -315,6 +336,39 @@ on:click={() => !isRunning && changeSettings('dualCerts')} />
+ {#if application.buildPack === 'python'} +
+ +
+ +
+
+ + +
+ {/if} {#if !staticDeployments.includes(application.buildPack)}
@@ -323,7 +377,7 @@ name="port" id="port" bind:value={application.port} - placeholder="default: 3000" + placeholder={application.buildPack === 'python' ? '8000' : '3000'} />
{/if} diff --git a/src/tailwind.css b/src/tailwind.css index 1f357acfb..20f67dd73 100644 --- a/src/tailwind.css +++ b/src/tailwind.css @@ -54,10 +54,10 @@ textarea { } #svelte .item.hover { - @apply bg-coolgray-100 text-white; + @apply bg-coollabs text-white !important; } #svelte .item.active { - @apply bg-coollabs text-white; + @apply bg-coolgray-100 text-white; } select {