feat: deploy form
parent
6ffa9ca777
commit
28b16aee7a
@ -1,36 +0,0 @@
|
|||||||
deploys:
|
|
||||||
- name: project1
|
|
||||||
url: https://github.com/username/project1
|
|
||||||
branch: main
|
|
||||||
type: docker
|
|
||||||
options:
|
|
||||||
ports:
|
|
||||||
- 80:8080
|
|
||||||
volumes:
|
|
||||||
- /var/www/html:/var/www/html
|
|
||||||
- name: project2
|
|
||||||
url: https://github.com/username/project2
|
|
||||||
type: dockerfile
|
|
||||||
options:
|
|
||||||
ports:
|
|
||||||
- 9000:8080
|
|
||||||
volumes:
|
|
||||||
- /var/www/html:/var/www/html
|
|
||||||
- name: project2
|
|
||||||
url: ssh://example.org/username/project2.git
|
|
||||||
commit: 04c540647a
|
|
||||||
type: docker-compose
|
|
||||||
options:
|
|
||||||
path: ./docker-compose.yml
|
|
||||||
- name: project3
|
|
||||||
url: https://github.com/username/project3
|
|
||||||
type: shell
|
|
||||||
options:
|
|
||||||
path: ./deploy.sh
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,107 @@
|
|||||||
|
import type { Deploy, GitRef } from './config'
|
||||||
|
|
||||||
|
function cutString(s: string, sep: string): [string, string] {
|
||||||
|
return [s.slice(0, s.indexOf(sep)), s.slice(s.indexOf(sep) + sep.length)]
|
||||||
|
}
|
||||||
|
|
||||||
|
function notEmpty(s: string, message: string) {
|
||||||
|
if (s.trim().length === 0) {
|
||||||
|
throw new Error(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseRef(form: Record<string, any>): GitRef {
|
||||||
|
const type = form['deploy-ref-type']
|
||||||
|
const value = form['deploy-ref-value']
|
||||||
|
|
||||||
|
return type === 'default' ? { type } : { type, value }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseDeploy(form: Record<string, any>): Deploy {
|
||||||
|
const name = form['deploy-name']
|
||||||
|
const type = form['deploy-type']
|
||||||
|
|
||||||
|
if (type === 'docker') {
|
||||||
|
// const url = notEmpty(form['deploy-url'], 'must provide an url')
|
||||||
|
const containerName = form['deploy-options-name'] as string
|
||||||
|
const image = notEmpty(form['deploy-options-image'], 'must provide an image name')
|
||||||
|
const ports = form['deploy-options-ports'] as string
|
||||||
|
const env = form['deploy-options-env'] as string
|
||||||
|
const volumes = form['deploy-options-volumes'] as string
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
type: 'docker',
|
||||||
|
options: {
|
||||||
|
image,
|
||||||
|
name: containerName,
|
||||||
|
env: Object.fromEntries(env.split(/\n/g).map(line => cutString(line.trim(), '='))),
|
||||||
|
ports: ports.split(/\n/g).map(line => line.trim()),
|
||||||
|
volumes: volumes.split(/\n/g).map(line => line.trim()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'shell') {
|
||||||
|
const path = form['deploy-options-path'] as string
|
||||||
|
const env = form['deploy-options-env'] as string
|
||||||
|
|
||||||
|
const url = form['deploy-url'] as string
|
||||||
|
const ref = parseRef(form)
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
ref,
|
||||||
|
type: 'shell',
|
||||||
|
options: {
|
||||||
|
path,
|
||||||
|
env: Object.fromEntries(env.split(/\n/g).map(line => cutString(line.trim(), '='))),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'dockerfile') {
|
||||||
|
const url = form['deploy-url'] as string
|
||||||
|
const ref = parseRef(form)
|
||||||
|
|
||||||
|
const path = form['deploy-options-path'] as string
|
||||||
|
const ports = form['deploy-options-ports'] as string
|
||||||
|
const env = form['deploy-options-env'] as string
|
||||||
|
const volumes = form['deploy-options-volumes'] as string
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
ref,
|
||||||
|
type: 'dockerfile',
|
||||||
|
options: {
|
||||||
|
path,
|
||||||
|
env: Object.fromEntries(env.split(/\n/g).map(line => cutString(line.trim(), '='))),
|
||||||
|
ports: ports.split(/\n/g).map(line => line.trim()),
|
||||||
|
volumes: volumes.split(/\n/g).map(line => line.trim()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'docker-compose') {
|
||||||
|
const url = form['deploy-url'] as string
|
||||||
|
const ref = parseRef(form)
|
||||||
|
|
||||||
|
const path = form['deploy-options-path'] as string
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
ref,
|
||||||
|
type: 'docker-compose',
|
||||||
|
options: {
|
||||||
|
path,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('invalid deploy type')
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
const queue: (() => Promise<void>)[] = []
|
||||||
|
|
||||||
|
// to ensure that the while loop inside triggerProcessQueue is getting executed from only one call at a time
|
||||||
|
let working = false
|
||||||
|
|
||||||
|
export function runPendingJobs() {
|
||||||
|
triggerProcessQueue()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function triggerProcessQueue() {
|
||||||
|
if (working) return
|
||||||
|
|
||||||
|
working = true
|
||||||
|
{
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const job = queue.shift()!
|
||||||
|
await job()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
working = false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addJob(job: () => Promise<void>) {
|
||||||
|
queue.push(job)
|
||||||
|
|
||||||
|
// starts concurrently a function to process jobs
|
||||||
|
triggerProcessQueue()
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
import type { APIRoute } from 'astro'
|
||||||
|
import { loadConfig } from '../../config'
|
||||||
|
|
||||||
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
|
const body = await request.json()
|
||||||
|
const { html_url: htmlUrl, clone_url: cloneUrl, ssh_url: sshUrl } = body.repository
|
||||||
|
|
||||||
|
const URLS = [htmlUrl, cloneUrl, sshUrl]
|
||||||
|
|
||||||
|
const { deploys } = await loadConfig()
|
||||||
|
|
||||||
|
for (const deploy of deploys) {
|
||||||
|
if (deploy.type !== 'docker') {
|
||||||
|
if (URLS.includes(deploy.url)) {
|
||||||
|
// TODO: trigger deploy
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response('ok')
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
import { refToString, type GitRef, type ShellDeploy, type Deploy } from './config'
|
||||||
|
|
||||||
|
import { existsSync } from 'fs'
|
||||||
|
import { promisify } from 'util'
|
||||||
|
import child_process from 'child_process'
|
||||||
|
|
||||||
|
const exec = promisify(child_process.exec)
|
||||||
|
|
||||||
|
function getDeployDirectory(deploy: Deploy & { url: string; ref: GitRef }): string {
|
||||||
|
const { url, ref } = deploy
|
||||||
|
|
||||||
|
const repoSlug = url.replace(/(^\w+:|^)\/\//, '').replace(/[^a-zA-Z]+/g, '-')
|
||||||
|
const slug =
|
||||||
|
ref.type === 'default'
|
||||||
|
? `${deploy.name}_${repoSlug}`
|
||||||
|
: `${deploy.name}_${repoSlug}@${ref.value}`
|
||||||
|
|
||||||
|
return `${process.env.CLONE_PATH ?? './data.local/repos'}/${slug}`
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cloneOrUpdateRepo(deploy: Deploy & { url: string; ref: GitRef }) {
|
||||||
|
const repoDir = getDeployDirectory(deploy)
|
||||||
|
|
||||||
|
if (existsSync(repoDir)) {
|
||||||
|
await exec(`git -C ${repoDir} pull`)
|
||||||
|
} else {
|
||||||
|
await exec(`git clone ${deploy.url} ${repoDir}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function shellRunner(deploy: ShellDeploy) {
|
||||||
|
const { url, ref, options } = deploy
|
||||||
|
const { path, env } = options
|
||||||
|
|
||||||
|
const projectName = url.split('/').slice(-1)[0].replace('.git', '')
|
||||||
|
|
||||||
|
await cloneOrUpdateRepo(deploy)
|
||||||
|
|
||||||
|
const envStr = Object.entries(env as any)
|
||||||
|
.map(([key, value]) => `${key}=${value}`)
|
||||||
|
.join(' ')
|
||||||
|
|
||||||
|
return exec(`cd ${projectName} && ${envStr} ${path}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue