-
-
Notifications
You must be signed in to change notification settings - Fork 668
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(runtime): add feature support runing in docker env #1930
base: main
Are you sure you want to change the base?
Changes from all commits
43aae4e
793f2c9
6308f1b
9eb395d
5e209f7
455bcb7
6ed298d
90b24cd
cbd3bb8
ec3972e
ed7ebd6
e343d50
ac779aa
c8c07e8
309f3f9
db3e471
5f260c9
03547f0
9daea7f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import { Response } from 'express' | ||
import { createReadStream } from 'fs' | ||
import fs from 'fs/promises' | ||
import fsPromises from 'fs/promises' | ||
import os from 'os' | ||
import path from 'path' | ||
import tar from 'tar' | ||
import { pipeline } from 'stream/promises' | ||
import { DatabaseAgent } from '../db' | ||
import { CLOUD_FUNCTION_COLLECTION, CONFIG_COLLECTION } from '../constants' | ||
import { ICloudFunctionData } from '../support/engine/types' | ||
import { IRequest } from '../support/types' | ||
|
||
// Constants for file paths | ||
const WORKSPACE_PATH = path.join(__dirname, '../../cloud_functions') | ||
|
||
const TEMP_DIR = path.join(os.tmpdir(), 'cloud_functions') | ||
const TAR_FILE_NAME = 'cloud_functions.tar' | ||
const TAR_FILE_PATH = path.join(TEMP_DIR, TAR_FILE_NAME) | ||
|
||
// Function to write cloud function data to the workspace as JSON files | ||
async function saveFunctionsToWorkspace(): Promise<void> { | ||
// Check if the workspace directory exists and delete it if it does | ||
try { | ||
await fs.rm(WORKSPACE_PATH, { recursive: true, force: true }) | ||
} catch (err) { | ||
console.error('Error removing the workspace directory:', err) | ||
} | ||
|
||
await fs.mkdir(WORKSPACE_PATH, { recursive: true }) | ||
|
||
const funcs = await DatabaseAgent.db | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FunctionCache.getAll |
||
.collection<ICloudFunctionData>(CLOUD_FUNCTION_COLLECTION) | ||
.find() | ||
.toArray() | ||
|
||
for (const func of funcs) { | ||
const dirPath = path.join(WORKSPACE_PATH, path.dirname(func.name)) | ||
await fs.mkdir(dirPath, { recursive: true }) | ||
const filePath = path.join(WORKSPACE_PATH, func.name) | ||
await fs.writeFile(filePath, JSON.stringify(func), 'utf8') | ||
} | ||
} | ||
|
||
// Function to create a tarball of the workspace directory | ||
async function createTarPackage(): Promise<void> { | ||
// Check if the tar file exists and delete it if it does | ||
try { | ||
await fs.rm(TAR_FILE_PATH, { force: true }) | ||
} catch (err) { | ||
console.error('Error removing the tar file:', err) | ||
} | ||
|
||
await fs.mkdir(TEMP_DIR, { recursive: true }) | ||
|
||
await tar.create( | ||
{ | ||
gzip: false, | ||
file: TAR_FILE_PATH, | ||
cwd: WORKSPACE_PATH, | ||
}, | ||
['.'], | ||
) | ||
} | ||
|
||
// /docker-file | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove |
||
export async function handleCloudFunctionTarPackage( | ||
_: IRequest, | ||
res: Response, | ||
) { | ||
try { | ||
// Save them to the workspace directory | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 上下注释的大小写对齐 |
||
await saveFunctionsToWorkspace() | ||
// Create a tarball of the workspace | ||
await createTarPackage() | ||
|
||
// Set headers to serve the tarball as a download | ||
res.writeHead(200, { | ||
'Content-Type': 'application/x-tar', | ||
'Content-Disposition': `attachment; filename="${TAR_FILE_NAME}"`, | ||
}) | ||
// create file stream | ||
const readStream = createReadStream(TAR_FILE_PATH) | ||
|
||
// use pipeline pipe the file stream to response | ||
await pipeline(readStream, res).then(() => { | ||
// after pipe the file stream, remove the tar file | ||
fsPromises.unlink(TAR_FILE_PATH).catch((unlinkError) => { | ||
console.error('Error removing file:', unlinkError) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logger.error |
||
}) | ||
}) | ||
} catch (error) { | ||
console.error('Error:', error) | ||
if (!res.headersSent) { | ||
return res.status(500).send('Internal Server Error') | ||
} | ||
} | ||
} | ||
|
||
export async function handleDockerFile(_: IRequest, res: Response) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dockerfile |
||
try { | ||
const ENV = process.env | ||
const keysToInclude = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 去掉不需要的 |
||
'DB_URI', | ||
'APP_ID', | ||
'APPID', | ||
'RUNTIME_DOMAIN', | ||
'RUNTIME_MAIN_IMAGE', | ||
|
||
'OSS_ACCESS_KEY', | ||
'OSS_ACCESS_SECRET', | ||
'OSS_INTERNAL_ENDPOINT', | ||
'OSS_EXTERNAL_ENDPOINT', | ||
'OSS_REGION', | ||
|
||
'DEPENDENCIES', | ||
|
||
'NODE_MODULES_PUSH_URL', | ||
'NODE_MODULES_PULL_URL', | ||
|
||
'NPM_INSTALL_FLAGS', | ||
'CUSTOM_DEPENDENCY_BASE_PATH', | ||
'RESTART_AT', | ||
'SERVER_SECRET', | ||
] | ||
|
||
let envVariablesString = 'LOG_LEVEL=debug \\\n FORCE_COLOR=1 \\\n ' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 不要直接拼,数组操作 |
||
|
||
envVariablesString += 'DOCKER_PRODUCT=true \\\n ' | ||
envVariablesString += 'DOCKER_PRODUCT_MONGO=false \\\n ' | ||
|
||
const conf = await DatabaseAgent.db | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 从已有的方法拿 |
||
.collection(CONFIG_COLLECTION) | ||
.findOne({}) | ||
|
||
if (conf && conf.environments) { | ||
for (const env of conf.environments) { | ||
envVariablesString += `${env.name}=${env.value} \\\n ` | ||
} | ||
} | ||
|
||
for (const [key, value] of Object.entries(ENV)) { | ||
if (keysToInclude.includes(key)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 放到上面的conf.environments |
||
envVariablesString += `${key}=${value} \\\n ` | ||
} | ||
} | ||
|
||
envVariablesString = envVariablesString.replace(/\\\n\s*$/, '') | ||
|
||
// version from env todo | ||
const DOCKER_FILE = ` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DOCKERFILE |
||
|
||
FROM ${ENV.RUNTIME_MAIN_IMAGE} as builder | ||
USER root | ||
|
||
WORKDIR ${ENV.CUSTOM_DEPENDENCY_BASE_PATH} | ||
RUN mkdir -p /app/cloud_functions && chown -R node:node /app/cloud_functions ${ENV.CUSTOM_DEPENDENCY_BASE_PATH} | ||
|
||
USER node | ||
RUN npm install ${ENV.DEPENDENCIES} || true | ||
|
||
RUN curl -o /tmp/cloud_functions.tar https://${ENV.RUNTIME_DOMAIN}/_/${process.env.SERVER_SECRET}/cloud_functions/tar && tar -xf /tmp/cloud_functions.tar -C /app/cloud_functions && rm /tmp/cloud_functions.tar | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ${process.env.SERVER_SECRET} 放在 tar后面,最好用jwt There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. http的兼容 |
||
|
||
FROM node:20.10.0 | ||
USER root | ||
|
||
EXPOSE 8000 | ||
EXPOSE 9000 | ||
|
||
USER node | ||
|
||
COPY --from=builder ${ENV.CUSTOM_DEPENDENCY_BASE_PATH} ${ENV.CUSTOM_DEPENDENCY_BASE_PATH} | ||
COPY --from=builder /app /app | ||
|
||
RUN ln -s ${ENV.CUSTOM_DEPENDENCY_BASE_PATH} /app/functions/node_modules | ||
|
||
ENV ${envVariablesString} | ||
|
||
WORKDIR /app | ||
|
||
CMD node $FLAGS --experimental-vm-modules --experimental-fetch ./dist/index.js | ||
` | ||
// 设置响应头,返回纯文本内容 | ||
res.writeHead(200, { | ||
'Content-Type': 'text/plain', | ||
'Content-Disposition': 'attachment; filename="Dockerfile"', | ||
}) | ||
|
||
res.end(DOCKER_FILE) | ||
} catch (error) { | ||
// Handle errors by sending an internal server error status and the error message | ||
res.status(500).send(`Error generating Dockerfile: ${error.message}`) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
不是错误