feat: able to push image to docker registry

This commit is contained in:
Andras Bacsai 2022-12-01 14:39:02 +01:00
parent 127880cf8d
commit 8ccb0c88db
9 changed files with 102 additions and 20 deletions

View File

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

View File

@ -133,6 +133,7 @@ model Application {
baseBuildImage String? baseBuildImage String?
settings ApplicationSettings? settings ApplicationSettings?
dockerRegistryId String? dockerRegistryId String?
dockerRegistryImageName String?
simpleDockerfile String? simpleDockerfile String?
persistentStorage ApplicationPersistentStorage[] persistentStorage ApplicationPersistentStorage[]

View File

@ -3,8 +3,8 @@ 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, makeLabelForSimpleDockerfile, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common'; import { copyBaseConfigurationFiles, makeLabelForSimpleDockerfile, makeLabelForStandaloneApplication, saveBuildLog, saveDockerRegistryCredentials, 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, pushToRegistry } 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';
@ -37,7 +37,7 @@ import * as buildpacks from '../lib/buildPacks';
for (const queueBuild of queuedBuilds) { for (const queueBuild of queuedBuilds) {
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: { dockerRegistry: true, destinationDocker: true, gitSource: { include: { githubApp: true, gitlabApp: true } }, persistentStorage: true, secrets: true, settings: true, teams: true } })
let { id: buildId, type, gitSourceId, 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)
@ -51,7 +51,8 @@ import * as buildpacks from '../lib/buildPacks';
port, port,
persistentStorage, persistentStorage,
exposePort, exposePort,
simpleDockerfile simpleDockerfile,
dockerRegistry
} = application } = application
const { workdir } = await createDirectories({ repository: applicationId, buildId }); const { workdir } = await createDirectories({ repository: applicationId, buildId });
try { try {
@ -109,6 +110,11 @@ import * as buildpacks from '../lib/buildPacks';
} }
await fs.writeFile(`${workdir}/Dockerfile`, simpleDockerfile); await fs.writeFile(`${workdir}/Dockerfile`, simpleDockerfile);
if (dockerRegistry) {
const { url, username, password } = dockerRegistry
await saveDockerRegistryCredentials({ url, username, password, workdir })
}
const labels = makeLabelForSimpleDockerfile({ const labels = makeLabelForSimpleDockerfile({
applicationId, applicationId,
type, type,
@ -164,7 +170,7 @@ import * as buildpacks from '../lib/buildPacks';
} }
throw new Error(error); throw new Error(error);
} }
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
} }
} catch (error) { } catch (error) {
const foundBuild = await prisma.build.findUnique({ where: { id: buildId } }) const foundBuild = await prisma.build.findUnique({ where: { id: buildId } })
@ -179,10 +185,27 @@ import * as buildpacks from '../lib/buildPacks';
if (error !== 1) { if (error !== 1) {
await saveBuildLog({ line: error, buildId, applicationId: application.id }); await saveBuildLog({ line: error, buildId, applicationId: application.id });
} }
}
try {
if (application.dockerRegistryImageName) {
const customTag = application.dockerRegistryImageName.split(':')[1] || buildId;
const imageName = application.dockerRegistryImageName.split(':')[0];
await saveBuildLog({ line: `Pushing ${imageName}:${customTag} to Docker Registry... It could take a while...`, buildId, applicationId: application.id });
await pushToRegistry(application, workdir, buildId, imageName, customTag)
await saveBuildLog({ line: "Success 🎉", buildId, applicationId: application.id });
}
} catch (error) {
if (error.stdout) {
await saveBuildLog({ line: error.stdout, buildId, applicationId });
}
if (error.stderr) {
await saveBuildLog({ line: error.stderr, buildId, applicationId });
}
} finally { } finally {
if (!isDev) { if (!isDev) {
await fs.rm(workdir, { recursive: true, force: true }); await fs.rm(workdir, { recursive: true, force: true });
} }
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
} }
return; return;
} }
@ -211,6 +234,7 @@ import * as buildpacks from '../lib/buildPacks';
baseBuildImage, baseBuildImage,
deploymentType, deploymentType,
gitCommitHash, gitCommitHash,
dockerRegistry
} = application } = application
let { let {
@ -231,7 +255,7 @@ import * as buildpacks from '../lib/buildPacks';
let imageId = applicationId; let imageId = applicationId;
let domain = getDomain(fqdn); let domain = getDomain(fqdn);
let tag = null
if (pullmergeRequestId) { if (pullmergeRequestId) {
const previewApplications = await prisma.previewApplication.findMany({ where: { applicationId: originalApplicationId, pullmergeRequestId } }) const previewApplications = await prisma.previewApplication.findMany({ where: { applicationId: originalApplicationId, pullmergeRequestId } })
if (previewApplications.length > 0) { if (previewApplications.length > 0) {
@ -330,7 +354,7 @@ import * as buildpacks from '../lib/buildPacks';
if (!commit) { if (!commit) {
throw new Error('No commit found?'); throw new Error('No commit found?');
} }
let tag = commit.slice(0, 7); tag = commit.slice(0, 7);
if (pullmergeRequestId) { if (pullmergeRequestId) {
tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`; tag = `${commit.slice(0, 7)}-${pullmergeRequestId}`;
} }
@ -500,6 +524,9 @@ import * as buildpacks from '../lib/buildPacks';
} }
await fs.writeFile(`${workdir}/.env`, envs.join('\n')); await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
const { url, username, password } = dockerRegistry
await saveDockerRegistryCredentials({ url, username, password, workdir })
let envFound = false; let envFound = false;
try { try {
envFound = !!(await fs.stat(`${workdir}/.env`)); envFound = !!(await fs.stat(`${workdir}/.env`));
@ -553,7 +580,7 @@ import * as buildpacks from '../lib/buildPacks';
} }
throw new Error(error); throw new Error(error);
} }
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
if (!pullmergeRequestId) await prisma.application.update({ if (!pullmergeRequestId) await prisma.application.update({
where: { id: applicationId }, where: { id: applicationId },
data: { configHash: currentHash } data: { configHash: currentHash }
@ -573,10 +600,28 @@ import * as buildpacks from '../lib/buildPacks';
if (error !== 1) { if (error !== 1) {
await saveBuildLog({ line: error, buildId, applicationId: application.id }); await saveBuildLog({ line: error, buildId, applicationId: application.id });
} }
}
try {
if (application.dockerRegistryImageName) {
const customTag = application.dockerRegistryImageName.split(':')[1] || tag;
const imageName = application.dockerRegistryImageName.split(':')[0];
await saveBuildLog({ line: `Pushing ${imageName}:${customTag} to Docker Registry... It could take a while...`, buildId, applicationId: application.id });
await pushToRegistry(application, workdir, tag, imageName, customTag)
await saveBuildLog({ line: "Success 🎉", buildId, applicationId: application.id });
}
} catch (error) {
if (error.stdout) {
await saveBuildLog({ line: error.stdout, buildId, applicationId });
}
if (error.stderr) {
await saveBuildLog({ line: error.stderr, buildId, applicationId });
}
} finally { } finally {
if (!isDev) { if (!isDev) {
await fs.rm(workdir, { recursive: true, force: true }); await fs.rm(workdir, { recursive: true, force: true });
} }
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
} }
}); });
} }

View File

@ -607,13 +607,13 @@ export function checkPnpm(installCommand = null, buildCommand = null, startComma
} }
export async function saveDockerRegistryCredentials({ url, username, password, workdir }) { export async function saveDockerRegistryCredentials({ url, username, password, workdir }) {
let decryptedPassword = decrypt(password);
const location = `${workdir}/.docker`;
if (!username || !password) { if (!username || !password) {
return null return null
} }
let decryptedPassword = decrypt(password);
const location = `${workdir}/.docker`;
try { try {
await fs.mkdir(`${workdir}/.docker`); await fs.mkdir(`${workdir}/.docker`);
} catch (error) { } catch (error) {

View File

@ -15,7 +15,7 @@ import sshConfig from 'ssh-config';
import jsonwebtoken from 'jsonwebtoken'; import jsonwebtoken from 'jsonwebtoken';
import { checkContainer, removeContainer } from './docker'; import { checkContainer, removeContainer } from './docker';
import { day } from './dayjs'; import { day } from './dayjs';
import { saveBuildLog } from './buildPacks/common'; import { saveBuildLog, saveDockerRegistryCredentials } from './buildPacks/common';
import { scheduler } from './scheduler'; import { scheduler } from './scheduler';
export const version = '3.12.0'; export const version = '3.12.0';
@ -1713,3 +1713,17 @@ export function decryptApplication(application: any) {
return application; return application;
} }
} }
export async function pushToRegistry(application: any, workdir: string, tag: string, imageName: string, customTag: string) {
const location = `${workdir}/.docker`
const tagCommand = `docker tag ${application.id}:${tag} ${imageName}:${customTag}`
const pushCommand = `docker --config ${location} push ${imageName}:${customTag}`
await executeDockerCmd({
dockerId: application.destinationDockerId,
command: tagCommand
})
await executeDockerCmd({
dockerId: application.destinationDockerId,
command: pushCommand
})
}

View File

@ -335,7 +335,8 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
dockerComposeFile, dockerComposeFile,
dockerComposeFileLocation, dockerComposeFileLocation,
dockerComposeConfiguration, dockerComposeConfiguration,
simpleDockerfile simpleDockerfile,
dockerRegistryImageName
} = request.body } = request.body
if (port) port = Number(port); if (port) port = Number(port);
if (exposePort) { if (exposePort) {
@ -375,6 +376,7 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
dockerComposeFileLocation, dockerComposeFileLocation,
dockerComposeConfiguration, dockerComposeConfiguration,
simpleDockerfile, simpleDockerfile,
dockerRegistryImageName,
...defaultConfiguration, ...defaultConfiguration,
connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } } connectedDatabase: { update: { hostedDatabaseDBName: baseDatabaseBranch } }
} }
@ -398,6 +400,7 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
dockerComposeFileLocation, dockerComposeFileLocation,
dockerComposeConfiguration, dockerComposeConfiguration,
simpleDockerfile, simpleDockerfile,
dockerRegistryImageName,
...defaultConfiguration ...defaultConfiguration
} }
}); });

View File

@ -26,7 +26,8 @@ export interface SaveApplication extends OnlyId {
dockerComposeFile: string, dockerComposeFile: string,
dockerComposeFileLocation: string, dockerComposeFileLocation: string,
dockerComposeConfiguration: string, dockerComposeConfiguration: string,
simpleDockerfile: string simpleDockerfile: string,
dockerRegistryImageName: string
} }
} }
export interface SaveApplicationSettings extends OnlyId { export interface SaveApplicationSettings extends OnlyId {

View File

@ -218,7 +218,7 @@
<li class="menu-title"> <li class="menu-title">
<span>Advanced</span> <span>Advanced</span>
</li> </li>
{#if !application.simpleDockerfile} {#if application.gitSourceId}
<li <li
class="rounded" class="rounded"
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/revert`} class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/revert`}
@ -267,7 +267,7 @@
</svg>Monitoring</a </svg>Monitoring</a
> >
</li> </li>
{#if !application.settings.isBot && !application.simpleDockerfile} {#if !application.settings.isBot && application.gitSourceId}
<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

@ -568,8 +568,6 @@
> >
{/if} {/if}
</div> </div>
{:else}
{/if} {/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>
@ -592,6 +590,24 @@
> >
{/if} {/if}
</div> </div>
{#if application.dockerRegistry?.id && application.gitSourceId}
<div class="grid grid-cols-2 items-center">
<label for="registry"
>Push Image to Registry <Explainer
explanation="Push the build image to the specific Docker Registry.<br><br>This is useful if you want to use the image in other places. If you don't fill this the image will be only available on the server.<br><br>Tag is optional. If you don't fill it, the tag will be the same as the git commit hash."
/></label
>
<input
name="dockerRegistryImageName"
id="dockerRegistryImageName"
readonly={isDisabled}
disabled={isDisabled}
class="w-full"
placeholder="e.g. coollabsio/myimage (tag will be commit sha) or coollabsio/myimage:tag"
bind:value={application.dockerRegistryImageName}
/>
</div>
{/if}
{#if !isSimpleDockerfile} {#if !isSimpleDockerfile}
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<label for="buildPack">{$t('application.build_pack')} </label> <label for="buildPack">{$t('application.build_pack')} </label>
@ -728,13 +744,13 @@
<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>
<div class="grid grid-flow-row gap-2 px-4 pr-5"> <div class="grid grid-flow-row gap-2 px-4 pr-5">
<div class="grid grid-cols-2 items-center pt-4"> <div class="grid grid-cols-2 items-center pt-4">
<label for="simpleDockerfile">Dockerfile</label> <label for="simpleDockerfile">Dockerfile</label>
<div class="flex gap-2"> <div class="flex gap-2">
<textarea <textarea
rows=10 rows="10"
id="simpleDockerfile" id="simpleDockerfile"
name="simpleDockerfile" name="simpleDockerfile"
class="w-full" class="w-full"