diff --git a/deployment-service/dockerfile b/deployment-service/dockerfile index efd442f..22aa1d8 100644 --- a/deployment-service/dockerfile +++ b/deployment-service/dockerfile @@ -7,5 +7,7 @@ RUN npm ci --only=production COPY src ./src COPY snakeapi_service ./snakeapi_service +ENV FLY_ACCESS_TOKEN="FlyV1 fm2_lJPECAAAAAAACJJHxBByW6wRXXxQ17OD8xlRRR5cwrVodHRwczovL2FwaS5mbHkuaW8vdjGUAJLOAA//nh8Lk7lodHRwczovL2FwaS5mbHkuaW8vYWFhL3YxxDwQBQg0Vif1OLMYOJOtNVokX+9SIVL2E8QoNub0JDBE4wNh97aUAPiiNvpAAMhM/eO7SWUVAx5rcDTBjf7ETtdnvXtcHaqOnK2HmNSV9K9UVy5Or3Sd+0+kxqDoWRXGE0y5pdb8+HNqwMcryszYvAv8HVcoKFgF4qd7GmzniNvZETOkrbsjMsU1+mVXTMQgh7H9z6IcGVjJozV92cDsSn91USqxOmBdwFQAkFGwPV0=,fm2_lJPETtdnvXtcHaqOnK2HmNSV9K9UVy5Or3Sd+0+kxqDoWRXGE0y5pdb8+HNqwMcryszYvAv8HVcoKFgF4qd7GmzniNvZETOkrbsjMsU1+mVXTMQQqnJP464DwxC6D4e3p9THZMO5aHR0cHM6Ly9hcGkuZmx5LmlvL2FhYS92MZgEks5oESI9zwAAAAEkCUBbF84AD2FZCpHOAA9hWQzEEL6gO8olFxMOq1uFxP1yJavEIBDKb7RuqVr/sFQniKl0S2HMM6+AQJH3940ly0mufbYx" + EXPOSE 3006 CMD ["node", "src/index.js"] diff --git a/deployment-service/fly.toml b/deployment-service/fly.toml index 7938a3b..6c0c77d 100644 --- a/deployment-service/fly.toml +++ b/deployment-service/fly.toml @@ -5,10 +5,12 @@ primary_region = 'sea' dockerfile = "Dockerfile" [env] - FLY_ORG = "personal" - COMMON_BUCKET = "snakeapi-deployment-test-bucket" - AWS_ENDPOINT_URL_S3 = "https://fly.storage.tigris.dev" - AWS_REGION = "auto" + FLY_ORG="personal" + COMMON_BUCKET="snakeapi-deployment-test-bucket" + AWS_ACCESS_KEY_ID="tid__NSmOVaGknqitaCySppZjqVTgJSdDFnFbWcQllkC_juHwkbQZO" + AWS_SECRET_ACCESS_KEY="tsec_6Bz1aMbfYQftuq5WfIVEDZkHwskU4MMjVywdtxSP6uxetEBvkSC2VHI9HfTeDgHr4D6kiz" + AWS_ENDPOINT_URL_S3="https://fly.storage.tigris.dev" + AWS_REGION="auto" [http_service] internal_port = 3006 diff --git a/deployment-service/snakeapi_service/dockerfile b/deployment-service/snakeapi_service/dockerfile index 80c866a..8095003 100644 --- a/deployment-service/snakeapi_service/dockerfile +++ b/deployment-service/snakeapi_service/dockerfile @@ -4,7 +4,7 @@ WORKDIR /app RUN pip install --no-cache-dir jupyter flask awscli flask_cors nbconvert nbformat COPY entrypoint.sh . -COPY notebook.ipynb . +COPY notebooks ./notebooks RUN chmod +x entrypoint.sh diff --git a/deployment-service/snakeapi_service/entrypoint.sh b/deployment-service/snakeapi_service/entrypoint.sh index 6f48c9b..0b5fea6 100644 --- a/deployment-service/snakeapi_service/entrypoint.sh +++ b/deployment-service/snakeapi_service/entrypoint.sh @@ -15,7 +15,6 @@ while true; do if [ "$new_mod" -ne "$last_mod" ]; then last_mod=$new_mod jupyter nbconvert --to notebook --execute --inplace --ExecutePreprocessor.timeout=0 notebook.ipynb - echo "Notebook executed; restarting..." fi sleep 1 diff --git a/deployment-service/snakeapi_service/fly.toml b/deployment-service/snakeapi_service/fly.toml index 47bb81d..0a473f9 100644 --- a/deployment-service/snakeapi_service/fly.toml +++ b/deployment-service/snakeapi_service/fly.toml @@ -6,7 +6,7 @@ kill_timeout = 5 dockerfile = "Dockerfile" [env] - PORT = "3006" +PORT = "3006" [[services]] internal_port = 3006 diff --git a/deployment-service/snakeapi_service/notebook.ipynb b/deployment-service/snakeapi_service/notebooks/notebook.ipynb similarity index 100% rename from deployment-service/snakeapi_service/notebook.ipynb rename to deployment-service/snakeapi_service/notebooks/notebook.ipynb diff --git a/deployment-service/src/index.js b/deployment-service/src/index.js index 5e08139..1402747 100644 --- a/deployment-service/src/index.js +++ b/deployment-service/src/index.js @@ -3,6 +3,7 @@ const fs = require('fs'); const path = require('path'); const AWS = require('aws-sdk'); const axios = require('axios'); +const tar = require('tar'); const { FLY_ORG, @@ -32,15 +33,23 @@ function createFlyClient() { } const app = express(); -app.use(express.json()); +app.use(express.json({ limit: '10mb' })); app.post('/deploy', async (req, res) => { const { appName, region = 'sea', notebookName } = req.body; - if (!appName || !notebookName) return res.status(400).json({ error: 'appName and notebookName are required.' }); + if (!appName || !notebookName) { + return res.status(400).json({ error: 'appName and notebookName required' }); + } try { const fly = createFlyClient(); - await fly.post('/apps', { name: appName, org_slug: FLY_ORG, primary_region: region }); + + await fly.post('/apps', { + name: appName, + org_slug: FLY_ORG, + primary_region: region + }); + await fly.post(`/apps/${appName}/secrets`, { secrets: { INSTANCE_PREFIX: appName, @@ -52,18 +61,83 @@ app.post('/deploy', async (req, res) => { } }); - const tpl = fs.readFileSync(path.join(__dirname, '../snakeapi_service', notebookName)); + const notebookFile = path.join(__dirname, '../snakeapi_service/notebooks', notebookName); + const notebookData = fs.readFileSync(notebookFile); await s3.putObject({ Bucket: COMMON_BUCKET, - Key: `${appName}/${notebookName}`, - Body: tpl, + Key: `${appName}/notebook.ipynb`, + Body: notebookData, ContentType: 'application/json' }).promise(); - await fly.post(`/apps/${appName}/deploys`); - res.json({ status: 'created', app: appName, url: `https://${appName}.fly.dev` }); + const tarFilePath = `/tmp/${appName}.tar.gz`; + await tar.c( + { + gzip: true, + file: tarFilePath, + cwd: path.join(__dirname, '../snakeapi_service') + }, + ['.'] + ); + + const tarData = fs.readFileSync(tarFilePath); + await fly.post(`/apps/${appName}/deploys`, tarData, { + headers: { 'Content-Type': 'application/gzip' } + }); + + res.json({ + status: 'created', + app: appName, + url: `https://${appName}.fly.dev` + }); + } catch (error) { + res.status(500).json({ error: error.response?.data || error.message }); + } +}); + +app.post('/upload/:appName', async (req, res) => { + const { appName } = req.params; + const notebookContent = req.body; + + if (!notebookContent) { + return res.status(400).json({ error: 'Notebook content required.' }); + } + + try { + const notebookBuffer = Buffer.from(JSON.stringify(notebookContent)); + await s3.putObject({ + Bucket: COMMON_BUCKET, + Key: `${appName}/notebook.ipynb`, + Body: notebookBuffer, + ContentType: 'application/json' + }).promise(); + + const fly = createFlyClient(); + + const tempNotebookPath = path.join(__dirname, '../snakeapi_service/notebooks/notebook.ipynb'); + fs.writeFileSync(tempNotebookPath, notebookBuffer); + + const tarFilePath = `/tmp/${appName}-redeploy.tar.gz`; + await tar.c( + { + gzip: true, + file: tarFilePath, + cwd: path.join(__dirname, '../snakeapi_service') + }, + ['.'] + ); + + const tarData = fs.readFileSync(tarFilePath); + await fly.post(`/apps/${appName}/deploys`, tarData, { + headers: { 'Content-Type': 'application/gzip' } + }); + + res.json({ + status: 'updated', + app: appName, + message: 'Notebook updated and application redeployed.' + }); } catch (error) { - console.error('Deploy endpoint failed:', error); res.status(500).json({ error: error.response?.data || error.message }); } });