feat: specific git commit deployment

feat: revert to specific image
fix: no system wide docker registries
This commit is contained in:
Andras Bacsai
2022-11-30 15:22:07 +01:00
parent a08bb25bfa
commit 9913e7b70b
20 changed files with 494 additions and 231 deletions

View File

@@ -0,0 +1,66 @@
/*
Warnings:
- You are about to drop the column `isSystemWide` on the `DockerRegistry` table. All the data in the column will be lost.
*/
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_DockerRegistry" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"url" TEXT NOT NULL,
"username" TEXT,
"password" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"teamId" TEXT,
CONSTRAINT "DockerRegistry_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_DockerRegistry" ("createdAt", "id", "name", "password", "teamId", "updatedAt", "url", "username") SELECT "createdAt", "id", "name", "password", "teamId", "updatedAt", "url", "username" FROM "DockerRegistry";
DROP TABLE "DockerRegistry";
ALTER TABLE "new_DockerRegistry" RENAME TO "DockerRegistry";
CREATE TABLE "new_Application" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"fqdn" TEXT,
"repository" TEXT,
"configHash" TEXT,
"branch" TEXT,
"buildPack" TEXT,
"projectId" INTEGER,
"port" INTEGER,
"exposePort" INTEGER,
"installCommand" TEXT,
"buildCommand" TEXT,
"startCommand" TEXT,
"baseDirectory" TEXT,
"publishDirectory" TEXT,
"deploymentType" TEXT,
"phpModules" TEXT,
"pythonWSGI" TEXT,
"pythonModule" TEXT,
"pythonVariable" TEXT,
"dockerFileLocation" TEXT,
"denoMainFile" TEXT,
"denoOptions" TEXT,
"dockerComposeFile" TEXT,
"dockerComposeFileLocation" TEXT,
"dockerComposeConfiguration" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"destinationDockerId" TEXT,
"gitSourceId" TEXT,
"gitCommitHash" TEXT,
"baseImage" TEXT,
"baseBuildImage" TEXT,
"dockerRegistryId" TEXT,
CONSTRAINT "Application_gitSourceId_fkey" FOREIGN KEY ("gitSourceId") REFERENCES "GitSource" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "Application_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "Application_dockerRegistryId_fkey" FOREIGN KEY ("dockerRegistryId") REFERENCES "DockerRegistry" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_Application" ("baseBuildImage", "baseDirectory", "baseImage", "branch", "buildCommand", "buildPack", "configHash", "createdAt", "denoMainFile", "denoOptions", "deploymentType", "destinationDockerId", "dockerComposeConfiguration", "dockerComposeFile", "dockerComposeFileLocation", "dockerFileLocation", "dockerRegistryId", "exposePort", "fqdn", "gitCommitHash", "gitSourceId", "id", "installCommand", "name", "phpModules", "port", "projectId", "publishDirectory", "pythonModule", "pythonVariable", "pythonWSGI", "repository", "startCommand", "updatedAt") SELECT "baseBuildImage", "baseDirectory", "baseImage", "branch", "buildCommand", "buildPack", "configHash", "createdAt", "denoMainFile", "denoOptions", "deploymentType", "destinationDockerId", "dockerComposeConfiguration", "dockerComposeFile", "dockerComposeFileLocation", "dockerFileLocation", "dockerRegistryId", "exposePort", "fqdn", "gitCommitHash", "gitSourceId", "id", "installCommand", "name", "phpModules", "port", "projectId", "publishDirectory", "pythonModule", "pythonVariable", "pythonWSGI", "repository", "startCommand", "updatedAt" FROM "Application";
DROP TABLE "Application";
ALTER TABLE "new_Application" RENAME TO "Application";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -139,8 +139,8 @@ model Application {
teams Team[]
connectedDatabase ApplicationConnectedDatabase?
previewApplication PreviewApplication[]
dockerRegistryId String @default("0")
dockerRegistry DockerRegistry @relation(fields: [dockerRegistryId], references: [id])
dockerRegistryId String?
dockerRegistry DockerRegistry? @relation(fields: [dockerRegistryId], references: [id])
}
model PreviewApplication {
@@ -302,17 +302,16 @@ model SshKey {
}
model DockerRegistry {
id String @id @default(cuid())
name String
url String
username String?
password String?
isSystemWide Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
teamId String?
team Team? @relation(fields: [teamId], references: [id])
application Application[]
id String @id @default(cuid())
name String
url String
username String?
password String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
teamId String?
team Team? @relation(fields: [teamId], references: [id])
application Application[]
}
model GitSource {

View File

@@ -91,10 +91,10 @@ async function main() {
}
}
// Add default docker registry (dockerhub)
const registries = await prisma.dockerRegistry.findMany()
if (registries.length === 0) {
await prisma.dockerRegistry.create({ data: { id: "0", name: 'Docker Hub', url: 'https://index.docker.io/v1/', isSystemWide: true } })
}
// const registries = await prisma.dockerRegistry.findMany()
// if (registries.length === 0) {
// await prisma.dockerRegistry.create({ data: { id: "0", name: 'Docker Hub', url: 'https://index.docker.io/v1/', isSystemWide: true, team: { connect: { id: '0' } } } })
// }
}
main()
.catch((e) => {

View File

@@ -654,8 +654,14 @@ export async function buildImage({
}
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
const { dockerRegistry: { url, username, password } } = await prisma.application.findUnique({ where: { id: applicationId }, select: { dockerRegistry: true } })
const location = await saveDockerRegistryCredentials({ url, username, password, workdir })
let location = null
const { dockerRegistry } = await prisma.application.findUnique({ where: { id: applicationId }, select: { dockerRegistry: true } })
if (dockerRegistry) {
const { url, username, password } = dockerRegistry
location = await saveDockerRegistryCredentials({ url, username, password, workdir })
}
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker ${location ? `--config ${location}` : ''} build --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}` })

View File

@@ -37,6 +37,13 @@ export default async function ({
buildId,
applicationId
});
if (gitCommitHash) {
await saveBuildLog({
line: `Checking out ${gitCommitHash} commit.`,
buildId,
applicationId
});
}
await asyncExecShell(
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `
);
@@ -68,6 +75,13 @@ export default async function ({
buildId,
applicationId
});
if (gitCommitHash) {
await saveBuildLog({
line: `Checking out ${gitCommitHash} commit.`,
buildId,
applicationId
});
}
await asyncExecShell(
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `
);

View File

@@ -39,7 +39,13 @@ export default async function ({
buildId,
applicationId
});
if (gitCommitHash) {
await saveBuildLog({
line: `Checking out ${gitCommitHash} commit.`,
buildId,
applicationId
});
}
if (forPublic) {
await asyncExecShell(
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `
@@ -49,7 +55,7 @@ export default async function ({
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `
);
}
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
return commit.replace('\n', '');
}

View File

@@ -12,7 +12,7 @@ import { checkDomainsIsValidInDNS, checkExposedPort, createDirectories, decrypt,
import { checkContainer, formatLabelsOnDocker, removeContainer } from '../../../../lib/docker';
import type { FastifyRequest } from 'fastify';
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication, CheckDomain, StopPreviewApplication, RestartPreviewApplication, GetBuilds } from './types';
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication, CheckDomain, StopPreviewApplication, RestartPreviewApplication, GetBuilds, RestartApplication } from './types';
import { OnlyId } from '../../../../types';
function filterObject(obj, callback) {
@@ -443,9 +443,10 @@ export async function stopPreviewApplication(request: FastifyRequest<StopPreview
}
}
export async function restartApplication(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
export async function restartApplication(request: FastifyRequest<RestartApplication>, reply: FastifyReply) {
try {
const { id } = request.params
const { imageId = null } = request.body
const { teamId } = request.user
let application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId) {
@@ -475,17 +476,22 @@ export async function restartApplication(request: FastifyRequest<OnlyId>, reply:
const { workdir } = await createDirectories({ repository, buildId });
const labels = []
let image = null
const { stdout: container } = await executeDockerCmd({ dockerId, command: `docker container ls --filter 'label=com.docker.compose.service=${id}' --format '{{json .}}'` })
const containersArray = container.trim().split('\n');
for (const container of containersArray) {
const containerObj = formatLabelsOnDocker(container);
image = containerObj[0].Image
Object.keys(containerObj[0].Labels).forEach(function (key) {
if (key.startsWith('coolify')) {
labels.push(`${key}=${containerObj[0].Labels[key]}`)
}
})
if (imageId) {
image = imageId
} else {
const { stdout: container } = await executeDockerCmd({ dockerId, command: `docker container ls --filter 'label=com.docker.compose.service=${id}' --format '{{json .}}'` })
const containersArray = container.trim().split('\n');
for (const container of containersArray) {
const containerObj = formatLabelsOnDocker(container);
image = containerObj[0].Image
Object.keys(containerObj[0].Labels).forEach(function (key) {
if (key.startsWith('coolify')) {
labels.push(`${key}=${containerObj[0].Labels[key]}`)
}
})
}
}
let imageFound = false;
try {
await executeDockerCmd({
@@ -681,6 +687,38 @@ export async function getUsage(request) {
return errorHandler({ status, message })
}
}
export async function getDockerImages(request) {
try {
const { id } = request.params
const teamId = request.user?.teamId;
const application: any = await getApplicationFromDB(id, teamId);
const { stdout } = await executeDockerCmd({ dockerId: application.destinationDocker.id, command: `docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}' | grep -i ${id} | grep -v cache` });
const { stdout: runningImage } = await executeDockerCmd({ dockerId: application.destinationDocker.id, command: `docker ps -a --filter 'label=com.docker.compose.service=${id}' --format {{.Image}}` });
const images = stdout.trim().split('\n');
let imagesAvailables = [];
for (const image of images) {
const [repository, tag, createdAt] = image.split('#');
if (tag.includes('-')) {
continue;
}
const [year, time] = createdAt.split(' ');
imagesAvailables.push({
repository,
tag,
createdAt: day(year + time).unix()
})
}
imagesAvailables = imagesAvailables.sort((a, b) => b.tag - a.tag);
return {
imagesAvailables,
runningImage
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function getUsageByContainer(request) {
try {

View File

@@ -1,8 +1,8 @@
import { FastifyPluginAsync } from 'fastify';
import { OnlyId } from '../../../../types';
import { cancelDeployment, checkDNS, checkDomain, checkRepository, cleanupUnconfiguredApplications, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildPack, getBuilds, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getPreviewStatus, getSecrets, getStorages, getUsage, getUsageByContainer, listApplications, loadPreviews, newApplication, restartApplication, restartPreview, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveConnectedDatabase, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRegistry, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication, updatePreviewSecret, updateSecret } from './handlers';
import { cancelDeployment, checkDNS, checkDomain, checkRepository, cleanupUnconfiguredApplications, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildPack, getBuilds, getDockerImages, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getPreviewStatus, getSecrets, getStorages, getUsage, getUsageByContainer, listApplications, loadPreviews, newApplication, restartApplication, restartPreview, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveConnectedDatabase, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRegistry, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication, updatePreviewSecret, updateSecret } from './handlers';
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuilds, GetImages, RestartPreviewApplication, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuilds, GetImages, RestartApplication, RestartPreviewApplication, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.addHook('onRequest', async (request) => {
@@ -21,7 +21,7 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get<OnlyId>('/:id/status', async (request) => await getApplicationStatus(request));
fastify.post<OnlyId>('/:id/restart', async (request, reply) => await restartApplication(request, reply));
fastify.post<RestartApplication>('/:id/restart', async (request, reply) => await restartApplication(request, reply));
fastify.post<OnlyId>('/:id/stop', async (request, reply) => await stopApplication(request, reply));
fastify.post<StopPreviewApplication>('/:id/stop/preview', async (request, reply) => await stopPreviewApplication(request, reply));
@@ -53,6 +53,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get('/:id/usage', async (request) => await getUsage(request))
fastify.get('/:id/usage/:containerId', async (request) => await getUsageByContainer(request))
fastify.get('/:id/images', async (request) => await getDockerImages(request))
fastify.post<DeployApplication>('/:id/deploy', async (request) => await deployApplication(request))
fastify.post<CancelDeployment>('/:id/cancel', async (request, reply) => await cancelDeployment(request, reply));

View File

@@ -141,4 +141,12 @@ export interface RestartPreviewApplication {
id: string,
pullmergeRequestId: string | null,
}
}
export interface RestartApplication {
Params: {
id: string,
},
Body: {
imageId: string | null,
}
}

View File

@@ -11,15 +11,8 @@ export async function listAllSettings(request: FastifyRequest) {
const teamId = request.user.teamId;
const settings = await listSettings();
const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } })
let publicRegistries = await prisma.dockerRegistry.findMany({ where: { isSystemWide: true } })
let privateRegistries = await prisma.dockerRegistry.findMany({ where: { team: { id: teamId }, isSystemWide: false } })
publicRegistries = publicRegistries.map((registry) => {
if (registry.password) {
registry.password = decrypt(registry.password)
}
return registry
})
privateRegistries = privateRegistries.map((registry) => {
let registries = await prisma.dockerRegistry.findMany({ where: { team: { id: teamId } } })
registries = registries.map((registry) => {
if (registry.password) {
registry.password = decrypt(registry.password)
}
@@ -42,10 +35,7 @@ export async function listAllSettings(request: FastifyRequest) {
settings,
certificates: cns,
sshKeys: unencryptedKeys,
registries: {
public: publicRegistries,
private: privateRegistries
}
registries
}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -221,11 +211,11 @@ export async function setDockerRegistry(request: FastifyRequest<SetDefaultRegist
export async function addDockerRegistry(request: FastifyRequest<AddDefaultRegistry>, reply: FastifyReply) {
try {
const teamId = request.user.teamId;
const { name, url, username, password, isSystemWide } = request.body;
const { name, url, username, password } = request.body;
let encryptedPassword = ''
if (password) encryptedPassword = encrypt(password)
await prisma.dockerRegistry.create({ data: { name, url, username, password: encryptedPassword, isSystemWide, team: { connect: { id: teamId } } } })
await prisma.dockerRegistry.create({ data: { name, url, username, password: encryptedPassword, team: { connect: { id: teamId } } } })
return reply.code(201).send()
} catch ({ status, message }) {
@@ -236,7 +226,7 @@ export async function deleteDockerRegistry(request: FastifyRequest<OnlyIdInBody>
try {
const teamId = request.user.teamId;
const { id } = request.body;
await prisma.application.updateMany({ where: { dockerRegistryId: id }, data: { dockerRegistryId: '0' } })
await prisma.application.updateMany({ where: { dockerRegistryId: id }, data: { dockerRegistryId: null } })
await prisma.dockerRegistry.deleteMany({ where: { id, teamId } })
return reply.code(201).send()
} catch ({ status, message }) {

View File

@@ -23,7 +23,6 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.post<SetDefaultRegistry>('/registry', async (request, reply) => await setDockerRegistry(request, reply));
fastify.post<AddDefaultRegistry>('/registry/new', async (request, reply) => await addDockerRegistry(request, reply));
fastify.delete<OnlyIdInBody>('/registry', async (request, reply) => await deleteDockerRegistry(request, reply));
// fastify.delete<>('/registry', async (request, reply) => await deleteSSHKey(request, reply));
fastify.post('/upload', async (request) => {
try {

View File

@@ -65,6 +65,5 @@ export interface AddDefaultRegistry {
name: string
username: string
password: string
isSystemWide: boolean
}
}