diff --git a/.gitignore b/.gitignore index e888b6c..218124b 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,6 @@ wrangler.toml backend/server/dist backend/server/projects -backend/database/drizzle \ No newline at end of file +backend/database/drizzle + +app.yaml \ No newline at end of file diff --git a/backend/orchestrator/.gcloudignore b/backend/orchestrator/.gcloudignore new file mode 100644 index 0000000..8067b59 --- /dev/null +++ b/backend/orchestrator/.gcloudignore @@ -0,0 +1,19 @@ +# This file specifies files that are *not* uploaded to Google Cloud +# using gcloud. It follows the same syntax as .gitignore, with the addition of +# "#!include" directives (which insert the entries of the given .gitignore-style +# file at that point). +# +# For more information, run: +# $ gcloud topic gcloudignore +# +.gcloudignore +# If you would like to upload your .git directory, .gitignore file or files +# from your .gitignore file, remove the corresponding line +# below: +.git +.gitignore + +# Node.js dependencies: +node_modules/ + +.env \ No newline at end of file diff --git a/backend/orchestrator/src/index.ts b/backend/orchestrator/src/index.ts index 113a2d9..9237271 100644 --- a/backend/orchestrator/src/index.ts +++ b/backend/orchestrator/src/index.ts @@ -16,11 +16,64 @@ import { z } from "zod" const app = express() const port = process.env.PORT || 4001 app.use(express.json()) -app.use(cors()) dotenv.config() +const corsOptions = { + origin: 'http://localhost:3000', + // origin: 'https://s.ishaand.com', +} + const kubeconfig = new KubeConfig() +if (process.env.NODE_ENV === "production") { + kubeconfig.loadFromOptions({ + clusters: [ + { + name: 'docker-desktop', + server: process.env.DOCKER_DESKTOP_SERVER!, + caData: process.env.DOCKER_DESKTOP_CA_DATA, + }, + { + name: 'gke_sylvan-epoch-422219-f9_us-central1_sandbox', + server: process.env.GKE_CLUSTER_SERVER!, + caData: process.env.GKE_CLUSTER_CA_DATA, + } + ], + users: [ + { + name: 'docker-desktop', + certData: process.env.DOCKER_DESKTOP_CLIENT_CERTIFICATE_DATA, + keyData: process.env.DOCKER_DESKTOP_CLIENT_KEY_DATA, + }, + { + name: 'gke_sylvan-epoch-422219-f9_us-central1_sandbox', + exec: { + apiVersion: 'client.authentication.k8s.io/v1beta1', + command: 'gke-gcloud-auth-plugin', + args: [], + env: null, + 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: [ + { + name: 'docker-desktop', + cluster: 'docker-desktop', + user: 'docker-desktop' + }, + { + name: 'gke_sylvan-epoch-422219-f9_us-central1_sandbox', + cluster: 'gke_sylvan-epoch-422219-f9_us-central1_sandbox', + user: 'gke_sylvan-epoch-422219-f9_us-central1_sandbox' + } + ], + currentContext: "gke_sylvan-epoch-422219-f9_us-central1_sandbox", + }); +} kubeconfig.loadFromDefault() + const coreV1Api = kubeconfig.makeApiClient(CoreV1Api) const appsV1Api = kubeconfig.makeApiClient(AppsV1Api) const networkingV1Api = kubeconfig.makeApiClient(NetworkingV1Api) @@ -58,22 +111,56 @@ const dataSchema = z.object({ sandboxId: z.string(), }) -app.post("/start", async (req, res) => { - const { userId, sandboxId } = dataSchema.parse(req.body) - const namespace = "default" +const namespace = "sandbox" + +app.get("/test", cors(), async (req, res) => { + res.status(200).send({ message: "Orchestrator is up and running." }) +}) + +app.get("/test/cors", cors(corsOptions), async (req, res) => { + res.status(200).send({ message: "With CORS, Orchestrator is up and running." }) +}) + +app.post("/start", cors(corsOptions), async (req, res) => { + const { sandboxId } = dataSchema.parse(req.body) try { const kubeManifests = readAndParseKubeYaml( path.join(__dirname, "../service.yaml"), sandboxId ) + + async function resourceExists(api: any, getMethod: string, name: string) { + try { + await api[getMethod](namespace, name) + return true + } catch (e: any) { + if (e.response && e.response.statusCode === 404) return false + throw e + } + } + kubeManifests.forEach(async (manifest) => { - if (manifest.kind === "Deployment") - await appsV1Api.createNamespacedDeployment(namespace, manifest) - else if (manifest.kind === "Service") - await coreV1Api.createNamespacedService(namespace, manifest) - else if (manifest.kind === "Ingress") - await networkingV1Api.createNamespacedIngress(namespace, manifest) + const { kind, metadata: { name } } = manifest + + if (kind === "Deployment") + if (!(await resourceExists(appsV1Api, 'readNamespacedDeployment', name))) { + await appsV1Api.createNamespacedDeployment(namespace, manifest) + } else { + return res.status(200).send({ message: "Resource deployment already exists." }) + } + else if (kind === "Service") + if (!(await resourceExists(coreV1Api, 'readNamespacedService', name))) { + await coreV1Api.createNamespacedService(namespace, manifest) + } else { + return res.status(200).send({ message: "Resource service already exists." }) + } + else if (kind === "Ingress") + if (!(await resourceExists(networkingV1Api, 'readNamespacedIngress', name))) { + await networkingV1Api.createNamespacedIngress(namespace, manifest) + } else { + return res.status(200).send({ message: "Resource ingress already exists." }) + } }) res.status(200).send({ message: "Resources created." }) } catch (error) { @@ -82,9 +169,8 @@ app.post("/start", async (req, res) => { } }) -app.post("/stop", async (req, res) => { - const { userId, sandboxId } = dataSchema.parse(req.body) - const namespace = "default" +app.post("/stop", cors(corsOptions), async (req, res) => { + const { sandboxId } = dataSchema.parse(req.body) try { const kubeManifests = readAndParseKubeYaml(