2024-05-05 21:45:43 -07:00
import express , { Express , Request , Response } from "express"
2024-05-05 22:33:24 -07:00
import dotenv from "dotenv"
2024-05-05 21:45:43 -07:00
import fs from "fs"
import yaml from "yaml"
import path from "path"
import cors from "cors"
import {
KubeConfig ,
AppsV1Api ,
CoreV1Api ,
NetworkingV1Api ,
} from "@kubernetes/client-node"
import { z } from "zod"
const app = express ( )
const port = process . env . PORT || 4001
app . use ( express . json ( ) )
2024-05-05 22:33:24 -07:00
dotenv . config ( )
2024-05-05 21:45:43 -07:00
2024-05-13 13:22:11 -07:00
// const corsOptions = {
// origin: ['http://localhost:3000', 'https://s.ishaand.com', 'http://localhost:4000', /\.ws\.ishaand\.com$/],
// }
2024-05-12 22:06:11 -07:00
// app.use(cors(corsOptions))
2024-05-13 13:22:11 -07:00
2024-05-12 22:06:11 -07:00
app . use ( cors ( ) )
2024-05-05 21:45:43 -07:00
const kubeconfig = new KubeConfig ( )
2024-05-11 21:13:43 -07:00
if ( process . env . NODE_ENV === "production" ) {
kubeconfig . loadFromOptions ( {
clusters : [
{
2024-05-13 03:00:15 -07:00
name : 'gke_sylvan-epoch-422219-f9_us-central1_sandbox-cluster' ,
2024-05-11 21:13:43 -07:00
server : process.env.GKE_CLUSTER_SERVER ! ,
caData : process.env.GKE_CLUSTER_CA_DATA ,
}
] ,
2024-05-13 13:22:11 -07:00
users : [
2024-05-11 21:13:43 -07:00
{
2024-05-13 03:00:15 -07:00
name : 'gke_sylvan-epoch-422219-f9_us-central1_sandbox-cluster' ,
2024-05-11 21:13:43 -07:00
exec : {
apiVersion : 'client.authentication.k8s.io/v1beta1' ,
command : 'gke-gcloud-auth-plugin' ,
installHint : 'Install gke-gcloud-auth-plugin for use with kubectl by following https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl#install_plugin' ,
interactiveMode : 'IfAvailable' ,
provideClusterInfo : true
}
}
] ,
contexts : [
{
2024-05-13 13:22:11 -07:00
name : 'gke_sylvan-epoch-422219-f9_us-central1_sandbox-cluster' ,
cluster : 'gke_sylvan-epoch-422219-f9_us-central1_sandbox-cluster' ,
user : 'gke_sylvan-epoch-422219-f9_us-central1_sandbox-cluster'
2024-05-11 21:13:43 -07:00
}
] ,
2024-05-13 13:22:11 -07:00
currentContext : "gke_sylvan-epoch-422219-f9_us-central1_sandbox-cluster"
2024-05-11 21:13:43 -07:00
} ) ;
}
2024-05-05 21:45:43 -07:00
kubeconfig . loadFromDefault ( )
2024-05-11 21:13:43 -07:00
2024-05-05 21:45:43 -07:00
const appsV1Api = kubeconfig . makeApiClient ( AppsV1Api )
2024-05-13 03:00:15 -07:00
const coreV1Api = kubeconfig . makeApiClient ( CoreV1Api )
2024-05-05 21:45:43 -07:00
const networkingV1Api = kubeconfig . makeApiClient ( NetworkingV1Api )
2024-05-05 22:33:24 -07:00
const readAndParseKubeYaml = (
filePath : string ,
sandboxId : string
) : Array < any > = > {
2024-05-05 21:45:43 -07:00
const fileContent = fs . readFileSync ( filePath , "utf8" )
const docs = yaml . parseAllDocuments ( fileContent ) . map ( ( doc ) = > {
let docString = doc . toString ( )
2024-05-05 22:33:24 -07:00
const regex = new RegExp ( ` <SANDBOX> ` , "g" )
docString = docString . replace ( regex , sandboxId )
if ( ! process . env . CF_API_TOKEN ) {
throw new Error ( "CF_API_TOKEN is not defined" )
}
const regexEnv1 = new RegExp ( ` <CF_API_TOKEN> ` , "g" )
docString = docString . replace ( regexEnv1 , process . env . CF_API_TOKEN )
if ( ! process . env . CF_USER_ID ) {
throw new Error ( "CF_USER_ID is not defined" )
}
const regexEnv2 = new RegExp ( ` <CF_USER_ID> ` , "g" )
docString = docString . replace ( regexEnv2 , process . env . CF_USER_ID )
2024-05-05 21:45:43 -07:00
return yaml . parse ( docString )
} )
return docs
}
2024-05-06 21:29:25 -07:00
const dataSchema = z . object ( {
userId : z.string ( ) ,
sandboxId : z.string ( ) ,
} )
2024-05-12 22:06:11 -07:00
const namespace = "ingress-nginx"
2024-05-11 21:13:43 -07:00
2024-05-12 22:06:11 -07:00
app . post ( "/test" , async ( req , res ) = > {
2024-05-11 21:13:43 -07:00
res . status ( 200 ) . send ( { message : "Orchestrator is up and running." } )
} )
2024-05-12 22:06:11 -07:00
app . post ( "/start" , async ( req , res ) = > {
2024-05-11 21:13:43 -07:00
const { sandboxId } = dataSchema . parse ( req . body )
2024-05-05 21:45:43 -07:00
try {
2024-05-12 22:06:11 -07:00
console . log ( "Creating resources for sandbox" , sandboxId )
2024-05-05 21:45:43 -07:00
const kubeManifests = readAndParseKubeYaml (
path . join ( __dirname , "../service.yaml" ) ,
sandboxId
)
2024-05-11 21:13:43 -07:00
async function resourceExists ( api : any , getMethod : string , name : string ) {
try {
2024-05-13 03:00:15 -07:00
await api [ getMethod ] ( name , namespace )
2024-05-11 21:13:43 -07:00
return true
} catch ( e : any ) {
2024-05-13 03:00:15 -07:00
if ( e . response && e . response . statusCode === 404 ) {
console . log ( "Resource does not exist." , e . response . body . message , e . response . body . details )
return false
}
2024-05-11 21:13:43 -07:00
throw e
}
}
2024-05-13 13:22:11 -07:00
const createResource = async ( api : any , method : string , manifest : any ) = > {
const { kind , metadata : { name } } = manifest ;
if ( ! ( await resourceExists ( api , 'readNamespaced' + kind , name ) ) ) {
await api [ 'createNamespaced' + kind ] ( namespace , manifest ) ;
console . log ( ` Created ${ kind . toLowerCase ( ) } ` , name ) ;
} else {
console . log ( ` ${ kind } ${ name } already exists. ` ) ;
}
} ;
2024-05-12 22:06:11 -07:00
const promises = kubeManifests . map ( async ( manifest ) = > {
2024-05-11 21:13:43 -07:00
const { kind , metadata : { name } } = manifest
2024-05-13 13:22:11 -07:00
console . log ( "Kind:" , kind )
switch ( manifest . kind ) {
case 'Deployment' :
return createResource ( appsV1Api , 'Deployment' , manifest ) ;
case 'Service' :
return createResource ( coreV1Api , 'Service' , manifest ) ;
case 'Ingress' :
return createResource ( networkingV1Api , 'Ingress' , manifest ) ;
default :
console . error ( "Unsupported kind:" , manifest . kind ) ;
return Promise . reject ( "Unsupported kind: " + manifest . kind ) ;
}
2024-05-05 21:45:43 -07:00
} )
2024-05-12 22:06:11 -07:00
await Promise . all ( promises )
console . log ( "All done!" )
2024-05-05 21:45:43 -07:00
res . status ( 200 ) . send ( { message : "Resources created." } )
2024-05-12 22:06:11 -07:00
} catch ( error : any ) {
const body = error . response . body
console . log ( "Failed to create resources" , body )
if ( body . code === 409 ) {
return res . status ( 200 ) . send ( { message : "Resource already exists." } )
}
2024-05-05 21:45:43 -07:00
res . status ( 500 ) . send ( { message : "Failed to create resources." } )
}
} )
2024-05-12 22:06:11 -07:00
app . post ( "/stop" , async ( req , res ) = > {
2024-05-11 21:13:43 -07:00
const { sandboxId } = dataSchema . parse ( req . body )
2024-05-12 22:06:11 -07:00
console . log ( "Deleting resources for sandbox" , sandboxId )
2024-05-06 21:29:25 -07:00
try {
const kubeManifests = readAndParseKubeYaml (
path . join ( __dirname , "../service.yaml" ) ,
sandboxId
)
2024-05-12 22:06:11 -07:00
const promises = kubeManifests . map ( async ( manifest ) = > {
2024-05-06 21:29:25 -07:00
if ( manifest . kind === "Deployment" )
await appsV1Api . deleteNamespacedDeployment (
manifest . metadata ? . name || "" ,
namespace
)
else if ( manifest . kind === "Service" )
await coreV1Api . deleteNamespacedService (
manifest . metadata ? . name || "" ,
namespace
)
else if ( manifest . kind === "Ingress" )
await networkingV1Api . deleteNamespacedIngress (
manifest . metadata ? . name || "" ,
namespace
)
} )
2024-05-12 22:06:11 -07:00
await Promise . all ( promises )
2024-05-06 21:29:25 -07:00
res . status ( 200 ) . send ( { message : "Resources deleted." } )
} catch ( error ) {
console . log ( "Failed to delete resources" , error )
res . status ( 500 ) . send ( { message : "Failed to delete resources." } )
}
} )
2024-05-05 21:45:43 -07:00
app . listen ( port , ( ) = > {
console . log ( ` Listening on port: ${ port } ` )
} )