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