v1.0.12 - Sveltekit migration (#44)
Changed the whole tech stack to SvelteKit which means: - Typescript - SSR - No fastify :( - Beta, but it's fine! Other changes: - Tailwind -> Tailwind JIT - A lot more
This commit is contained in:
29
src/lib/api/applications/buildContainer.ts
Normal file
29
src/lib/api/applications/buildContainer.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import Deployment from '$models/Logs/Deployment';
|
||||
import { saveAppLog } from './logging';
|
||||
import * as packs from './packs';
|
||||
|
||||
export default async function (configuration) {
|
||||
const { id, organization, name, branch } = configuration.repository;
|
||||
const { domain } = configuration.publish;
|
||||
const deployId = configuration.general.deployId;
|
||||
const execute = packs[configuration.build.pack];
|
||||
if (execute) {
|
||||
await Deployment.findOneAndUpdate(
|
||||
{ repoId: id, branch, deployId, organization, name, domain },
|
||||
{ repoId: id, branch, deployId, organization, name, domain, progress: 'inprogress' }
|
||||
);
|
||||
await saveAppLog('### Building application.', configuration);
|
||||
await execute(configuration);
|
||||
await saveAppLog('### Building done.', configuration);
|
||||
} else {
|
||||
try {
|
||||
await Deployment.findOneAndUpdate(
|
||||
{ repoId: id, branch, deployId, organization, name, domain },
|
||||
{ repoId: id, branch, deployId, organization, name, domain, progress: 'failed' }
|
||||
);
|
||||
} catch (error) {
|
||||
// Hmm.
|
||||
}
|
||||
throw new Error('No buildpack found.');
|
||||
}
|
||||
}
|
45
src/lib/api/applications/cleanup.ts
Normal file
45
src/lib/api/applications/cleanup.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { docker } from '$lib/api/docker';
|
||||
import { execShellAsync } from '../common';
|
||||
|
||||
export async function deleteSameDeployments(configuration) {
|
||||
await (
|
||||
await docker.engine.listServices()
|
||||
)
|
||||
.filter((r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application')
|
||||
.map(async (s) => {
|
||||
const running = JSON.parse(s.Spec.Labels.configuration);
|
||||
if (
|
||||
running.repository.id === configuration.repository.id &&
|
||||
running.repository.branch === configuration.repository.branch
|
||||
) {
|
||||
await execShellAsync(`docker stack rm ${s.Spec.Labels['com.docker.stack.namespace']}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function purgeImagesContainers(configuration, deleteAll = false) {
|
||||
const { name, tag } = configuration.build.container;
|
||||
await execShellAsync('docker container prune -f');
|
||||
if (deleteAll) {
|
||||
const IDsToDelete = (
|
||||
await execShellAsync(`docker images ls --filter=reference='${name}' --format '{{json .ID }}'`)
|
||||
)
|
||||
.trim()
|
||||
.replace(/"/g, '')
|
||||
.split('\n');
|
||||
if (IDsToDelete.length > 0)
|
||||
await execShellAsync(`docker rmi -f ${IDsToDelete.toString().replace(',', ' ')}`);
|
||||
} else {
|
||||
const IDsToDelete = (
|
||||
await execShellAsync(
|
||||
`docker images ls --filter=reference='${name}' --filter=before='${name}:${tag}' --format '{{json .ID }}'`
|
||||
)
|
||||
)
|
||||
.trim()
|
||||
.replace(/"/g, '')
|
||||
.split('\n');
|
||||
if (IDsToDelete.length > 1)
|
||||
await execShellAsync(`docker rmi -f ${IDsToDelete.toString().replace(',', ' ')}`);
|
||||
}
|
||||
await execShellAsync('docker image prune -f');
|
||||
}
|
48
src/lib/api/applications/cloneRepository.ts
Normal file
48
src/lib/api/applications/cloneRepository.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
import { execShellAsync } from '../common';
|
||||
|
||||
export default async function (configuration) {
|
||||
try {
|
||||
const { GITHUB_APP_PRIVATE_KEY } = process.env;
|
||||
const { workdir } = configuration.general;
|
||||
const { organization, name, branch } = configuration.repository;
|
||||
const github = configuration.github;
|
||||
if (!github.installation.id || !github.app.id) {
|
||||
throw new Error('Github installation ID is invalid.');
|
||||
}
|
||||
const githubPrivateKey = GITHUB_APP_PRIVATE_KEY.replace(/\\n/g, '\n').replace(/"/g, '');
|
||||
|
||||
const payload = {
|
||||
iat: Math.round(new Date().getTime() / 1000),
|
||||
exp: Math.round(new Date().getTime() / 1000 + 60),
|
||||
iss: parseInt(github.app.id)
|
||||
};
|
||||
|
||||
const jwtToken = jsonwebtoken.sign(payload, githubPrivateKey, {
|
||||
algorithm: 'RS256'
|
||||
});
|
||||
|
||||
const { token } = await (
|
||||
await fetch(
|
||||
`https://api.github.com/app/installations/${github.installation.id}/access_tokens`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + jwtToken,
|
||||
Accept: 'application/vnd.github.machine-man-preview+json'
|
||||
}
|
||||
}
|
||||
)
|
||||
).json();
|
||||
await execShellAsync(
|
||||
`mkdir -p ${workdir} && git clone -q -b ${branch} https://x-access-token:${token}@github.com/${organization}/${name}.git ${workdir}/`
|
||||
);
|
||||
configuration.build.container.tag = (
|
||||
await execShellAsync(`cd ${configuration.general.workdir}/ && git rev-parse HEAD`)
|
||||
)
|
||||
.replace('\n', '')
|
||||
.slice(0, 7);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
18
src/lib/api/applications/common.ts
Normal file
18
src/lib/api/applications/common.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export const baseServiceConfiguration = {
|
||||
replicas: 1,
|
||||
restart_policy: {
|
||||
condition: 'any',
|
||||
max_attempts: 6
|
||||
},
|
||||
update_config: {
|
||||
parallelism: 1,
|
||||
delay: '10s',
|
||||
order: 'start-first'
|
||||
},
|
||||
rollback_config: {
|
||||
parallelism: 1,
|
||||
delay: '10s',
|
||||
order: 'start-first',
|
||||
failure_action: 'rollback'
|
||||
}
|
||||
};
|
156
src/lib/api/applications/configuration.ts
Normal file
156
src/lib/api/applications/configuration.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import cuid from 'cuid';
|
||||
import crypto from 'crypto';
|
||||
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
|
||||
import { docker } from '$lib/api/docker';
|
||||
import { baseServiceConfiguration } from './common';
|
||||
import { execShellAsync } from '../common';
|
||||
|
||||
function getUniq() {
|
||||
return uniqueNamesGenerator({ dictionaries: [adjectives, animals, colors], length: 2 });
|
||||
}
|
||||
|
||||
export function setDefaultConfiguration(configuration) {
|
||||
const nickname = getUniq();
|
||||
const deployId = cuid();
|
||||
|
||||
const shaBase = JSON.stringify({ repository: configuration.repository });
|
||||
const sha256 = crypto.createHash('sha256').update(shaBase).digest('hex');
|
||||
|
||||
configuration.build.container.name = sha256.slice(0, 15);
|
||||
|
||||
configuration.general.nickname = nickname;
|
||||
configuration.general.deployId = deployId;
|
||||
configuration.general.workdir = `/tmp/${deployId}`;
|
||||
|
||||
if (!configuration.publish.path) configuration.publish.path = '/';
|
||||
if (!configuration.publish.port) {
|
||||
if (
|
||||
configuration.build.pack === 'nodejs' ||
|
||||
configuration.build.pack === 'vuejs' ||
|
||||
configuration.build.pack === 'nuxtjs' ||
|
||||
configuration.build.pack === 'rust' ||
|
||||
configuration.build.pack === 'nextjs'
|
||||
) {
|
||||
configuration.publish.port = 3000;
|
||||
} else {
|
||||
configuration.publish.port = 80;
|
||||
}
|
||||
}
|
||||
if (!configuration.build.directory) configuration.build.directory = '';
|
||||
if (configuration.build.directory.startsWith('/'))
|
||||
configuration.build.directory = configuration.build.directory.replace('/', '');
|
||||
|
||||
if (!configuration.publish.directory) configuration.publish.directory = '';
|
||||
if (configuration.publish.directory.startsWith('/'))
|
||||
configuration.publish.directory = configuration.publish.directory.replace('/', '');
|
||||
|
||||
if (configuration.build.pack === 'static' || configuration.build.pack === 'nodejs') {
|
||||
if (!configuration.build.command.installation)
|
||||
configuration.build.command.installation = 'yarn install';
|
||||
}
|
||||
|
||||
configuration.build.container.baseSHA = crypto
|
||||
.createHash('sha256')
|
||||
.update(JSON.stringify(baseServiceConfiguration))
|
||||
.digest('hex');
|
||||
configuration.baseServiceConfiguration = baseServiceConfiguration;
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
export async function precheckDeployment({ services, configuration }) {
|
||||
let foundService = false;
|
||||
let configChanged = false;
|
||||
let imageChanged = false;
|
||||
|
||||
let forceUpdate = false;
|
||||
|
||||
for (const service of services) {
|
||||
const running = JSON.parse(service.Spec.Labels.configuration);
|
||||
if (running) {
|
||||
if (
|
||||
running.repository.id === configuration.repository.id &&
|
||||
running.repository.branch === configuration.repository.branch
|
||||
) {
|
||||
// Base service configuration changed
|
||||
if (
|
||||
!running.build.container.baseSHA ||
|
||||
running.build.container.baseSHA !== configuration.build.container.baseSHA
|
||||
) {
|
||||
forceUpdate = true;
|
||||
}
|
||||
// If the deployment is in error state, forceUpdate
|
||||
const state = await execShellAsync(
|
||||
`docker stack ps ${running.build.container.name} --format '{{ json . }}'`
|
||||
);
|
||||
const isError = state
|
||||
.split('\n')
|
||||
.filter((n) => n)
|
||||
.map((s) => JSON.parse(s))
|
||||
.filter(
|
||||
(n) =>
|
||||
n.DesiredState !== 'Running' && n.Image.split(':')[1] === running.build.container.tag
|
||||
);
|
||||
if (isError.length > 0) forceUpdate = true;
|
||||
foundService = true;
|
||||
|
||||
const runningWithoutContainer = JSON.parse(JSON.stringify(running));
|
||||
delete runningWithoutContainer.build.container;
|
||||
|
||||
const configurationWithoutContainer = JSON.parse(JSON.stringify(configuration));
|
||||
delete configurationWithoutContainer.build.container;
|
||||
|
||||
// If only the configuration changed
|
||||
if (
|
||||
JSON.stringify(runningWithoutContainer.build) !==
|
||||
JSON.stringify(configurationWithoutContainer.build) ||
|
||||
JSON.stringify(runningWithoutContainer.publish) !==
|
||||
JSON.stringify(configurationWithoutContainer.publish)
|
||||
)
|
||||
configChanged = true;
|
||||
// If only the image changed
|
||||
if (running.build.container.tag !== configuration.build.container.tag) imageChanged = true;
|
||||
// If build pack changed, forceUpdate the service
|
||||
if (running.build.pack !== configuration.build.pack) forceUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (forceUpdate) {
|
||||
imageChanged = false;
|
||||
configChanged = false;
|
||||
}
|
||||
return {
|
||||
foundService,
|
||||
imageChanged,
|
||||
configChanged,
|
||||
forceUpdate
|
||||
};
|
||||
}
|
||||
|
||||
export async function updateServiceLabels(configuration) {
|
||||
// In case of any failure during deployment, still update the current configuration.
|
||||
const services = (await docker.engine.listServices()).filter(
|
||||
(r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application'
|
||||
);
|
||||
const found = services.find((s) => {
|
||||
const config = JSON.parse(s.Spec.Labels.configuration);
|
||||
if (
|
||||
config.repository.id === configuration.repository.id &&
|
||||
config.repository.branch === configuration.repository.branch
|
||||
) {
|
||||
return config;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
if (found) {
|
||||
const { ID } = found;
|
||||
const Labels = { ...JSON.parse(found.Spec.Labels.configuration), ...configuration };
|
||||
await execShellAsync(
|
||||
`docker service update --label-add configuration='${JSON.stringify(
|
||||
Labels
|
||||
)}' --label-add com.docker.stack.image='${configuration.build.container.name}:${
|
||||
configuration.build.container.tag
|
||||
}' ${ID}`
|
||||
);
|
||||
}
|
||||
}
|
69
src/lib/api/applications/copyFiles.ts
Normal file
69
src/lib/api/applications/copyFiles.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { promises as fs } from 'fs';
|
||||
export default async function (configuration) {
|
||||
const staticDeployments = ['react', 'vuejs', 'static', 'svelte', 'gatsby'];
|
||||
try {
|
||||
// TODO: Write full .dockerignore for all deployments!!
|
||||
if (configuration.build.pack === 'php') {
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/.htaccess`,
|
||||
`
|
||||
RewriteEngine On
|
||||
RewriteBase /
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^(.+)$ index.php [QSA,L]
|
||||
`
|
||||
);
|
||||
}
|
||||
// await fs.writeFile(`${configuration.general.workdir}/.dockerignore`, 'node_modules')
|
||||
if (staticDeployments.includes(configuration.build.pack)) {
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/nginx.conf`,
|
||||
`user nginx;
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
|
||||
access_log off;
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files $uri $uri/index.html $uri/ /index.html =404;
|
||||
}
|
||||
|
||||
error_page 404 /50x.html;
|
||||
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
76
src/lib/api/applications/deploy.ts
Normal file
76
src/lib/api/applications/deploy.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { docker } from '$lib/api/docker';
|
||||
import { saveAppLog } from './logging';
|
||||
import { promises as fs } from 'fs';
|
||||
import { deleteSameDeployments } from './cleanup';
|
||||
import yaml from 'js-yaml';
|
||||
import { execShellAsync } from '../common';
|
||||
|
||||
export default async function (configuration, imageChanged) {
|
||||
const generateEnvs = {};
|
||||
for (const secret of configuration.publish.secrets) {
|
||||
generateEnvs[secret.name] = secret.value;
|
||||
}
|
||||
const containerName = configuration.build.container.name;
|
||||
|
||||
// Only save SHA256 of it in the configuration label
|
||||
const baseServiceConfiguration = configuration.baseServiceConfiguration;
|
||||
delete configuration.baseServiceConfiguration;
|
||||
|
||||
const stack = {
|
||||
version: '3.8',
|
||||
services: {
|
||||
[containerName]: {
|
||||
image: `${configuration.build.container.name}:${configuration.build.container.tag}`,
|
||||
networks: [`${docker.network}`],
|
||||
environment: generateEnvs,
|
||||
deploy: {
|
||||
...baseServiceConfiguration,
|
||||
labels: [
|
||||
'managedBy=coolify',
|
||||
'type=application',
|
||||
'configuration=' + JSON.stringify(configuration),
|
||||
'traefik.enable=true',
|
||||
'traefik.http.services.' +
|
||||
configuration.build.container.name +
|
||||
`.loadbalancer.server.port=${configuration.publish.port}`,
|
||||
'traefik.http.routers.' + configuration.build.container.name + '.entrypoints=websecure',
|
||||
'traefik.http.routers.' +
|
||||
configuration.build.container.name +
|
||||
'.rule=Host(`' +
|
||||
configuration.publish.domain +
|
||||
'`) && PathPrefix(`' +
|
||||
configuration.publish.path +
|
||||
'`)',
|
||||
'traefik.http.routers.' +
|
||||
configuration.build.container.name +
|
||||
'.tls.certresolver=letsencrypt',
|
||||
'traefik.http.routers.' +
|
||||
configuration.build.container.name +
|
||||
'.middlewares=global-compress'
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
networks: {
|
||||
[`${docker.network}`]: {
|
||||
external: true
|
||||
}
|
||||
}
|
||||
};
|
||||
await saveAppLog('### Publishing.', configuration);
|
||||
await fs.writeFile(`${configuration.general.workdir}/stack.yml`, yaml.dump(stack));
|
||||
if (imageChanged) {
|
||||
// console.log('image changed')
|
||||
await execShellAsync(
|
||||
`docker service update --image ${configuration.build.container.name}:${configuration.build.container.tag} ${configuration.build.container.name}_${configuration.build.container.name}`
|
||||
);
|
||||
} else {
|
||||
// console.log('new deployment or force deployment or config changed')
|
||||
await deleteSameDeployments(configuration);
|
||||
await execShellAsync(
|
||||
`cat ${configuration.general.workdir}/stack.yml | docker stack deploy --prune -c - ${containerName}`
|
||||
);
|
||||
}
|
||||
|
||||
await saveAppLog('### Published done!', configuration);
|
||||
}
|
58
src/lib/api/applications/logging.ts
Normal file
58
src/lib/api/applications/logging.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import Settings from '$models/Settings';
|
||||
import ServerLog from '$models/Logs/Server';
|
||||
import ApplicationLog from '$models/Logs/Application';
|
||||
import dayjs from 'dayjs';
|
||||
import { version } from '../../../../package.json';
|
||||
|
||||
function generateTimestamp() {
|
||||
return `${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} `;
|
||||
}
|
||||
const patterns = [
|
||||
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
||||
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'
|
||||
].join('|');
|
||||
|
||||
export async function saveAppLog(event, configuration, isError?: boolean) {
|
||||
try {
|
||||
const deployId = configuration.general.deployId;
|
||||
const repoId = configuration.repository.id;
|
||||
const branch = configuration.repository.branch;
|
||||
if (isError) {
|
||||
const clearedEvent =
|
||||
'[ERROR 😱] ' +
|
||||
generateTimestamp() +
|
||||
event.replace(new RegExp(patterns, 'g'), '').replace(/(\r\n|\n|\r)/gm, '');
|
||||
await new ApplicationLog({ repoId, branch, deployId, event: clearedEvent }).save();
|
||||
} else {
|
||||
if (event && event !== '\n') {
|
||||
const clearedEvent =
|
||||
'[INFO] ' +
|
||||
generateTimestamp() +
|
||||
event.replace(new RegExp(patterns, 'g'), '').replace(/(\r\n|\n|\r)/gm, '');
|
||||
await new ApplicationLog({ repoId, branch, deployId, event: clearedEvent }).save();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveServerLog(error) {
|
||||
const settings = await Settings.findOne({ applicationName: 'coolify' });
|
||||
const payload = {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
type: error.type || 'spaghetticode',
|
||||
version
|
||||
};
|
||||
|
||||
const found = await ServerLog.find(payload);
|
||||
if (found.length === 0 && error.message) await new ServerLog(payload).save();
|
||||
if (settings && settings.sendErrors && process.env.NODE_ENV === 'production') {
|
||||
await fetch('https://errors.coollabs.io/api/error', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ...payload })
|
||||
});
|
||||
}
|
||||
}
|
17
src/lib/api/applications/packs/docker/index.ts
Normal file
17
src/lib/api/applications/packs/docker/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
export default async function (configuration) {
|
||||
const path = `${configuration.general.workdir}/${
|
||||
configuration.build.directory ? configuration.build.directory : ''
|
||||
}`;
|
||||
if (fs.stat(`${path}/Dockerfile`)) {
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: path },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
} else {
|
||||
throw new Error('No custom dockerfile found.');
|
||||
}
|
||||
}
|
28
src/lib/api/applications/packs/gatsby/index.ts
Normal file
28
src/lib/api/applications/packs/gatsby/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
|
||||
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
|
||||
const publishStaticDocker = (configuration) => {
|
||||
return [
|
||||
'FROM nginx:stable-alpine',
|
||||
'COPY nginx.conf /etc/nginx/nginx.conf',
|
||||
'WORKDIR /usr/share/nginx/html',
|
||||
`COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`,
|
||||
'EXPOSE 80',
|
||||
'CMD ["nginx", "-g", "daemon off;"]'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
await buildImage(configuration, true);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishStaticDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
30
src/lib/api/applications/packs/helpers.ts
Normal file
30
src/lib/api/applications/packs/helpers.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const buildImageNodeDocker = (configuration) => {
|
||||
return [
|
||||
'FROM node:lts',
|
||||
'WORKDIR /usr/src/app',
|
||||
`COPY ${configuration.build.directory}/package*.json ./`,
|
||||
configuration.build.command.installation && `RUN ${configuration.build.command.installation}`,
|
||||
`COPY ./${configuration.build.directory} ./`,
|
||||
`RUN ${configuration.build.command.build}`
|
||||
].join('\n');
|
||||
};
|
||||
export async function buildImage(configuration, cacheBuild?: boolean) {
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
buildImageNodeDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{
|
||||
t: `${configuration.build.container.name}:${
|
||||
cacheBuild
|
||||
? `${configuration.build.container.tag}-cache`
|
||||
: configuration.build.container.tag
|
||||
}`
|
||||
}
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
25
src/lib/api/applications/packs/index.ts
Normal file
25
src/lib/api/applications/packs/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import vuejs from './vuejs';
|
||||
import svelte from './svelte';
|
||||
import Static from './static';
|
||||
import rust from './rust';
|
||||
import react from './react';
|
||||
import php from './php';
|
||||
import nuxtjs from './nuxtjs';
|
||||
import nodejs from './nodejs';
|
||||
import nextjs from './nextjs';
|
||||
import gatsby from './gatsby';
|
||||
import docker from './docker';
|
||||
|
||||
export {
|
||||
vuejs,
|
||||
svelte,
|
||||
Static as static,
|
||||
rust,
|
||||
react,
|
||||
php,
|
||||
nuxtjs,
|
||||
nodejs,
|
||||
nextjs,
|
||||
gatsby,
|
||||
docker
|
||||
};
|
30
src/lib/api/applications/packs/nextjs/index.ts
Normal file
30
src/lib/api/applications/packs/nextjs/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`,
|
||||
const publishNodejsDocker = (configuration) => {
|
||||
return [
|
||||
'FROM node:lts',
|
||||
'WORKDIR /usr/src/app',
|
||||
configuration.build.command.build
|
||||
? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./`
|
||||
: `
|
||||
COPY ${configuration.build.directory}/package*.json ./
|
||||
RUN ${configuration.build.command.installation}
|
||||
COPY ./${configuration.build.directory} ./`,
|
||||
`EXPOSE ${configuration.publish.port}`,
|
||||
'CMD [ "yarn", "start" ]'
|
||||
].join('\n');
|
||||
};
|
||||
export default async function (configuration) {
|
||||
await buildImage(configuration);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishNodejsDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
31
src/lib/api/applications/packs/nodejs/index.ts
Normal file
31
src/lib/api/applications/packs/nodejs/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`,
|
||||
const publishNodejsDocker = (configuration) => {
|
||||
return [
|
||||
'FROM node:lts',
|
||||
'WORKDIR /usr/src/app',
|
||||
configuration.build.command.build
|
||||
? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./`
|
||||
: `
|
||||
COPY ${configuration.build.directory}/package*.json ./
|
||||
RUN ${configuration.build.command.installation}
|
||||
COPY ./${configuration.build.directory} ./`,
|
||||
`EXPOSE ${configuration.publish.port}`,
|
||||
'CMD [ "yarn", "start" ]'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
if (configuration.build.command.build) await buildImage(configuration);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishNodejsDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
31
src/lib/api/applications/packs/nuxtjs/index.ts
Normal file
31
src/lib/api/applications/packs/nuxtjs/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`,
|
||||
const publishNodejsDocker = (configuration) => {
|
||||
return [
|
||||
'FROM node:lts',
|
||||
'WORKDIR /usr/src/app',
|
||||
configuration.build.command.build
|
||||
? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./`
|
||||
: `
|
||||
COPY ${configuration.build.directory}/package*.json ./
|
||||
RUN ${configuration.build.command.installation}
|
||||
COPY ./${configuration.build.directory} ./`,
|
||||
`EXPOSE ${configuration.publish.port}`,
|
||||
'CMD [ "yarn", "start" ]'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
await buildImage(configuration);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishNodejsDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
25
src/lib/api/applications/packs/php/index.ts
Normal file
25
src/lib/api/applications/packs/php/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
|
||||
const publishPHPDocker = (configuration) => {
|
||||
return [
|
||||
'FROM php:apache',
|
||||
'RUN a2enmod rewrite',
|
||||
'WORKDIR /usr/src/app',
|
||||
`COPY ./${configuration.build.directory} /var/www/html`,
|
||||
'EXPOSE 80',
|
||||
' CMD ["apache2-foreground"]'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishPHPDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
28
src/lib/api/applications/packs/react/index.ts
Normal file
28
src/lib/api/applications/packs/react/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
|
||||
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
|
||||
const publishStaticDocker = (configuration) => {
|
||||
return [
|
||||
'FROM nginx:stable-alpine',
|
||||
'COPY nginx.conf /etc/nginx/nginx.conf',
|
||||
'WORKDIR /usr/share/nginx/html',
|
||||
`COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`,
|
||||
'EXPOSE 80',
|
||||
'CMD ["nginx", "-g", "daemon off;"]'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
await buildImage(configuration, true);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishStaticDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
66
src/lib/api/applications/packs/rust/index.ts
Normal file
66
src/lib/api/applications/packs/rust/index.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import TOML from '@iarna/toml';
|
||||
import { execShellAsync } from '$lib/api/common';
|
||||
|
||||
const publishRustDocker = (configuration, custom) => {
|
||||
return [
|
||||
'FROM rust:latest',
|
||||
'WORKDIR /app',
|
||||
`COPY --from=${configuration.build.container.name}:cache /app/target target`,
|
||||
`COPY --from=${configuration.build.container.name}:cache /usr/local/cargo /usr/local/cargo`,
|
||||
'COPY . .',
|
||||
`RUN cargo build --release --bin ${custom.name}`,
|
||||
'FROM debian:buster-slim',
|
||||
'WORKDIR /app',
|
||||
'RUN apt-get update -y && apt-get install -y --no-install-recommends openssl libcurl4 ca-certificates && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*',
|
||||
'RUN update-ca-certificates',
|
||||
`COPY --from=${configuration.build.container.name}:cache /app/target/release/${custom.name} ${custom.name}`,
|
||||
`EXPOSE ${configuration.publish.port}`,
|
||||
`CMD ["/app/${custom.name}"]`
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
const cacheRustDocker = (configuration, custom) => {
|
||||
return [
|
||||
`FROM rust:latest AS planner-${configuration.build.container.name}`,
|
||||
'WORKDIR /app',
|
||||
'RUN cargo install cargo-chef',
|
||||
'COPY . .',
|
||||
'RUN cargo chef prepare --recipe-path recipe.json',
|
||||
'FROM rust:latest',
|
||||
'WORKDIR /app',
|
||||
'RUN cargo install cargo-chef',
|
||||
`COPY --from=planner-${configuration.build.container.name} /app/recipe.json recipe.json`,
|
||||
'RUN cargo chef cook --release --recipe-path recipe.json'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
const cargoToml = await execShellAsync(`cat ${configuration.general.workdir}/Cargo.toml`);
|
||||
const parsedToml = TOML.parse(cargoToml);
|
||||
const custom = {
|
||||
name: parsedToml.package.name
|
||||
};
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
cacheRustDocker(configuration, custom)
|
||||
);
|
||||
|
||||
let stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:cache` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishRustDocker(configuration, custom)
|
||||
);
|
||||
|
||||
stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
30
src/lib/api/applications/packs/static/index.ts
Normal file
30
src/lib/api/applications/packs/static/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
|
||||
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
|
||||
const publishStaticDocker = (configuration) => {
|
||||
return [
|
||||
'FROM nginx:stable-alpine',
|
||||
'COPY nginx.conf /etc/nginx/nginx.conf',
|
||||
'WORKDIR /usr/share/nginx/html',
|
||||
configuration.build.command.build
|
||||
? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`
|
||||
: `COPY ./${configuration.build.directory} ./`,
|
||||
'EXPOSE 80',
|
||||
'CMD ["nginx", "-g", "daemon off;"]'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
if (configuration.build.command.build) await buildImage(configuration, true);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishStaticDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
28
src/lib/api/applications/packs/svelte/index.ts
Normal file
28
src/lib/api/applications/packs/svelte/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
|
||||
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
|
||||
const publishStaticDocker = (configuration) => {
|
||||
return [
|
||||
'FROM nginx:stable-alpine',
|
||||
'COPY nginx.conf /etc/nginx/nginx.conf',
|
||||
'WORKDIR /usr/share/nginx/html',
|
||||
`COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`,
|
||||
'EXPOSE 80',
|
||||
'CMD ["nginx", "-g", "daemon off;"]'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
await buildImage(configuration, true);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishStaticDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
28
src/lib/api/applications/packs/vuejs/index.ts
Normal file
28
src/lib/api/applications/packs/vuejs/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { docker, streamEvents } from '$lib/api/docker';
|
||||
import { promises as fs } from 'fs';
|
||||
import { buildImage } from '../helpers';
|
||||
|
||||
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
|
||||
const publishStaticDocker = (configuration) => {
|
||||
return [
|
||||
'FROM nginx:stable-alpine',
|
||||
'COPY nginx.conf /etc/nginx/nginx.conf',
|
||||
'WORKDIR /usr/share/nginx/html',
|
||||
`COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`,
|
||||
'EXPOSE 80',
|
||||
'CMD ["nginx", "-g", "daemon off;"]'
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
export default async function (configuration) {
|
||||
await buildImage(configuration, true);
|
||||
await fs.writeFile(
|
||||
`${configuration.general.workdir}/Dockerfile`,
|
||||
publishStaticDocker(configuration)
|
||||
);
|
||||
const stream = await docker.engine.buildImage(
|
||||
{ src: ['.'], context: configuration.general.workdir },
|
||||
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
|
||||
);
|
||||
await streamEvents(stream, configuration);
|
||||
}
|
31
src/lib/api/applications/queueAndBuild.ts
Normal file
31
src/lib/api/applications/queueAndBuild.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import Deployment from '$models/Logs/Deployment';
|
||||
import dayjs from 'dayjs';
|
||||
import buildContainer from './buildContainer';
|
||||
import { updateServiceLabels } from './configuration';
|
||||
import copyFiles from './copyFiles';
|
||||
import deploy from './deploy';
|
||||
import { saveAppLog } from './logging';
|
||||
|
||||
export default async function (configuration, imageChanged) {
|
||||
const { id, organization, name, branch } = configuration.repository;
|
||||
const { domain } = configuration.publish;
|
||||
const { deployId, nickname } = configuration.general;
|
||||
await new Deployment({
|
||||
repoId: id,
|
||||
branch,
|
||||
deployId,
|
||||
domain,
|
||||
organization,
|
||||
name,
|
||||
nickname
|
||||
}).save();
|
||||
await saveAppLog(`${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} Queued.`, configuration);
|
||||
await copyFiles(configuration);
|
||||
await buildContainer(configuration);
|
||||
await deploy(configuration, imageChanged);
|
||||
await Deployment.findOneAndUpdate(
|
||||
{ repoId: id, branch, deployId, organization, name, domain },
|
||||
{ repoId: id, branch, deployId, organization, name, domain, progress: 'done' }
|
||||
);
|
||||
await updateServiceLabels(configuration);
|
||||
}
|
57
src/lib/api/applications/templates.ts
Normal file
57
src/lib/api/applications/templates.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
const defaultBuildAndDeploy = {
|
||||
installation: 'yarn install',
|
||||
build: 'yarn build'
|
||||
};
|
||||
|
||||
const templates = {
|
||||
svelte: {
|
||||
pack: 'svelte',
|
||||
...defaultBuildAndDeploy,
|
||||
directory: 'public',
|
||||
name: 'Svelte'
|
||||
},
|
||||
next: {
|
||||
pack: 'nextjs',
|
||||
...defaultBuildAndDeploy,
|
||||
port: 3000,
|
||||
name: 'NextJS'
|
||||
},
|
||||
nuxt: {
|
||||
pack: 'nuxtjs',
|
||||
...defaultBuildAndDeploy,
|
||||
port: 3000,
|
||||
name: 'NuxtJS'
|
||||
},
|
||||
'react-scripts': {
|
||||
pack: 'react',
|
||||
...defaultBuildAndDeploy,
|
||||
directory: 'build',
|
||||
name: 'React'
|
||||
},
|
||||
'parcel-bundler': {
|
||||
pack: 'static',
|
||||
...defaultBuildAndDeploy,
|
||||
directory: 'dist',
|
||||
name: 'Parcel'
|
||||
},
|
||||
'@vue/cli-service': {
|
||||
pack: 'vuejs',
|
||||
...defaultBuildAndDeploy,
|
||||
directory: 'dist',
|
||||
name: 'Vue'
|
||||
},
|
||||
gatsby: {
|
||||
pack: 'gatsby',
|
||||
...defaultBuildAndDeploy,
|
||||
directory: 'public',
|
||||
name: 'Gatsby'
|
||||
},
|
||||
'preact-cli': {
|
||||
pack: 'react',
|
||||
...defaultBuildAndDeploy,
|
||||
directory: 'build',
|
||||
name: 'Preact'
|
||||
}
|
||||
};
|
||||
|
||||
export default templates;
|
44
src/lib/api/common.ts
Normal file
44
src/lib/api/common.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import shell from 'shelljs';
|
||||
import User from '$models/User';
|
||||
import jsonwebtoken from 'jsonwebtoken';
|
||||
|
||||
export function execShellAsync(cmd, opts = {}) {
|
||||
try {
|
||||
return new Promise(function (resolve, reject) {
|
||||
shell.config.silent = true;
|
||||
shell.exec(cmd, opts, function (code, stdout, stderr) {
|
||||
if (code !== 0) return reject(new Error(stderr));
|
||||
return resolve(stdout);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
return new Error('Oops');
|
||||
}
|
||||
}
|
||||
export function cleanupTmp(dir) {
|
||||
if (dir !== '/') shell.rm('-fr', dir);
|
||||
}
|
||||
|
||||
export async function verifyUserId(token) {
|
||||
const { JWT_SIGN_KEY } = process.env;
|
||||
try {
|
||||
const verify = jsonwebtoken.verify(token, JWT_SIGN_KEY);
|
||||
const found = await User.findOne({ uid: verify.jti });
|
||||
if (found) {
|
||||
return Promise.resolve(true);
|
||||
} else {
|
||||
return Promise.reject(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return Promise.reject(false);
|
||||
}
|
||||
}
|
||||
|
||||
export function delay(t) {
|
||||
return new Promise(function (resolve) {
|
||||
setTimeout(function () {
|
||||
resolve('OK');
|
||||
}, t);
|
||||
});
|
||||
}
|
27
src/lib/api/docker.ts
Normal file
27
src/lib/api/docker.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import Dockerode from 'dockerode';
|
||||
import { saveAppLog } from './applications/logging';
|
||||
|
||||
const { DOCKER_ENGINE, DOCKER_NETWORK } = process.env;
|
||||
export const docker = {
|
||||
engine: new Dockerode({
|
||||
socketPath: DOCKER_ENGINE
|
||||
}),
|
||||
network: DOCKER_NETWORK
|
||||
};
|
||||
export async function streamEvents(stream, configuration) {
|
||||
await new Promise((resolve, reject) => {
|
||||
docker.engine.modem.followProgress(stream, onFinished, onProgress);
|
||||
function onFinished(err, res) {
|
||||
if (err) reject(err);
|
||||
resolve(res);
|
||||
}
|
||||
function onProgress(event) {
|
||||
if (event.error) {
|
||||
saveAppLog(event.error, configuration, true);
|
||||
reject(event.error);
|
||||
} else if (event.stream) {
|
||||
saveAppLog(event.stream, configuration);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
102
src/lib/api/request.ts
Normal file
102
src/lib/api/request.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { toast } from '@zerodevx/svelte-toast';
|
||||
import { browser } from '$app/env';
|
||||
|
||||
export async function request(
|
||||
url,
|
||||
session,
|
||||
{
|
||||
method,
|
||||
body,
|
||||
customHeaders
|
||||
}: {
|
||||
url?: string;
|
||||
session?: any;
|
||||
fetch?: any;
|
||||
method?: string;
|
||||
body?: any;
|
||||
customHeaders?: Record<string, unknown>;
|
||||
} = {}
|
||||
) {
|
||||
let fetch;
|
||||
if (browser) {
|
||||
fetch = window.fetch;
|
||||
} else {
|
||||
fetch = session.fetch;
|
||||
}
|
||||
let headers = { 'content-type': 'application/json; charset=UTF-8' };
|
||||
if (method === 'DELETE') {
|
||||
delete headers['content-type'];
|
||||
}
|
||||
const isGithub = url.match(/api.github.com/);
|
||||
if (isGithub) {
|
||||
headers = Object.assign(headers, {
|
||||
Authorization: `token ${session.ghToken}`
|
||||
});
|
||||
}
|
||||
const config: any = {
|
||||
method: method || (body ? 'POST' : 'GET'),
|
||||
cache: isGithub ? 'no-cache' : 'default',
|
||||
headers: {
|
||||
...headers,
|
||||
...customHeaders
|
||||
}
|
||||
};
|
||||
if (body) {
|
||||
config.body = JSON.stringify(body);
|
||||
}
|
||||
const response = await fetch(url, config);
|
||||
if (response.status >= 200 && response.status <= 299) {
|
||||
if (response.headers.get('content-type').match(/application\/json/)) {
|
||||
const json = await response.json();
|
||||
if (json?.success === false) {
|
||||
browser && json.showToast !== false && toast.push(json.message);
|
||||
return Promise.reject({
|
||||
status: response.status,
|
||||
error: json.message
|
||||
});
|
||||
}
|
||||
return json;
|
||||
} else if (response.headers.get('content-type').match(/text\/plain/)) {
|
||||
return await response.text();
|
||||
} else if (response.headers.get('content-type').match(/multipart\/form-data/)) {
|
||||
return await response.formData();
|
||||
} else {
|
||||
console.log(response);
|
||||
if (response.headers.get('content-disposition')) {
|
||||
const blob = await response.blob();
|
||||
console.log(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = response.headers.get('content-disposition').split('=')[1] || 'backup.gz';
|
||||
link.target = '_blank';
|
||||
link.setAttribute('type', 'hidden');
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
return;
|
||||
}
|
||||
return await response.blob();
|
||||
}
|
||||
} else {
|
||||
if (response.status === 401) {
|
||||
browser && toast.push('Unauthorized');
|
||||
return Promise.reject({
|
||||
status: response.status,
|
||||
error: 'Unauthorized'
|
||||
});
|
||||
} else if (response.status >= 500) {
|
||||
const error = (await response.json()).error;
|
||||
browser && toast.push(error);
|
||||
return Promise.reject({
|
||||
status: response.status,
|
||||
error: error || 'Oops, something is not okay. Are you okay?'
|
||||
});
|
||||
} else {
|
||||
browser && toast.push(response.statusText);
|
||||
return Promise.reject({
|
||||
status: response.status,
|
||||
error: response.statusText
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user