Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
"@alicloud/fc2": "^2.6.6",
"@alicloud/fc20230330": "4.6.7",
"@alicloud/pop-core": "^1.8.0",
"@serverless-cd/srm-aliyun-oss": "^0.0.1-beta.8",
"@serverless-cd/srm-aliyun-pop-core": "^0.0.8-beta.1",
"@serverless-cd/srm-aliyun-ram20150501": "^0.0.2-beta.9",
"@serverless-cd/srm-aliyun-sls20201230": "0.0.5-beta.3",
"@serverless-cd/srm-aliyun-oss": "^0.0.1-beta.8",
"@serverless-devs/diff": "^0.0.3-beta.6",
"@serverless-devs/downloads": "^0.0.7",
"@serverless-devs/load-component": "^0.0.9",
Expand All @@ -38,6 +38,7 @@
"ajv": "^8.17.1",
"ali-oss": "6.18.1",
"aliyun-sdk": "^1.12.10",
"axios": "^1.13.5",
"chalk": "^4.1.0",
"crc64-ecma182.js": "^2.0.2",
"decompress": "^4.2.1",
Expand Down
84 changes: 80 additions & 4 deletions src/subCommands/deploy/impl/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import _ from 'lodash';
import { diffConvertYaml } from '@serverless-devs/diff';
import inquirer from 'inquirer';
import fs from 'fs';
import os from 'os';
import axios from 'axios';
import assert from 'assert';
import path from 'path';
import { yellow } from 'chalk';
Expand Down Expand Up @@ -277,12 +279,16 @@ export default class Service extends Base {
return true;
}

let zipPath: string = path.isAbsolute(codeUri)
? codeUri
: path.join(this.inputs.baseDir, codeUri);
let zipPath: string;
// 处理不同类型的 codeUri
if (codeUri.startsWith('http://') || codeUri.startsWith('https://')) {
zipPath = await this._downloadFromUrl(codeUri);
} else {
zipPath = path.isAbsolute(codeUri) ? codeUri : path.join(this.inputs.baseDir, codeUri);
}
logger.debug(`Code path absolute path: ${zipPath}`);
Comment on lines +282 to 289
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Downloaded temp artifacts are not cleaned up, causing disk leaks.

When codeUri is HTTP(S), the downloaded file/temp dir is never removed on either success or failure paths. Over repeated deploys, this will leak files under OS temp.

Proposed fix
   private async _uploadCode(): Promise<boolean> {
     const codeUri = this.local.code;
@@
-    let zipPath: string;
+    let zipPath: string;
+    let downloadedTempDir = '';
@@
     if (codeUri.startsWith('http://') || codeUri.startsWith('https://')) {
       zipPath = await this._downloadFromUrl(codeUri);
+      downloadedTempDir = path.dirname(zipPath);
     } else {
       zipPath = path.isAbsolute(codeUri) ? codeUri : path.join(this.inputs.baseDir, codeUri);
     }
@@
-    if (generateZipFilePath) {
-      try {
-        fs.rmSync(generateZipFilePath);
-      } catch (ex) {
-        logger.debug(`Unable to remove zip file: ${zipPath}`);
-      }
-    }
+    if (generateZipFilePath) {
+      try {
+        fs.rmSync(generateZipFilePath, { force: true });
+      } catch (ex) {
+        logger.debug(`Unable to remove zip file: ${zipPath}`);
+      }
+    }
+    if (downloadedTempDir) {
+      try {
+        fs.rmSync(downloadedTempDir, { recursive: true, force: true });
+      } catch (ex) {
+        logger.debug(`Unable to remove temp download dir: ${downloadedTempDir}`);
+      }
+    }

Also applies to: 322-346, 354-389

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/subCommands/deploy/impl/function.ts` around lines 282 - 289, The
downloaded temp artifact assigned to zipPath when codeUri is HTTP(S) is never
removed; update the function (the block that calls this._downloadFromUrl to
produce zipPath) to ensure cleanup of the downloaded file/dir in all cases by
wrapping usage in a try/finally (or equivalent) and deleting the temp artifact
in the finally block; apply the same pattern to the other similar blocks
mentioned (lines around the other occurrences) so that any temp path created by
_downloadFromUrl is removed via fs.unlink/fs.rm or a recursive remover before
returning or throwing.


const needZip = this._assertNeedZip(codeUri);
const needZip = this._assertNeedZip(zipPath);
logger.debug(`Need zip file: ${needZip}`);

let generateZipFilePath = '';
Expand Down Expand Up @@ -313,6 +319,15 @@ export default class Service extends Base {
logger.debug(
yellow(`skip uploadCode because code is no changed, codeChecksum=${crc64Value}`),
);
// 清理临时文件
if (generateZipFilePath) {
try {
fs.rmSync(generateZipFilePath);
} catch (ex) {
logger.debug(`Unable to remove zip file: ${zipPath}`);
}
}

return false;
} else {
logger.debug(`\x1b[33mcodeChecksum from ${this.codeChecksum} to ${crc64Value}\x1b[0m`);
Expand All @@ -333,6 +348,67 @@ export default class Service extends Base {
return true;
}

/**
* 从URL下载文件到本地临时目录
*/
private async _downloadFromUrl(url: string): Promise<string> {
logger.warn(`Downloading code from URL: ${url}`);

// 创建临时目录
const tempDir = path.join(os.tmpdir(), 'fc_code_download', Date.now().toString());
fs.mkdirSync(tempDir, { recursive: true });
let downloadPath: string;

try {
// 从URL获取文件名
const urlPath = new URL(url).pathname;
const filename = path.basename(urlPath) || `downloaded_code_${Date.now()}`;
downloadPath = path.join(tempDir, filename);

// 下载文件
const response = await axios({
method: 'GET',
url,
responseType: 'stream',
});

const writer = fs.createWriteStream(downloadPath);

// 创建一个 Promise 来处理流操作
await new Promise<void>((resolve, reject) => {
response.data.on('error', (err) => {
reject(new Error(`Source stream error: ${err.message}`));
// 确保 writer 被关闭
writer.destroy(err);
});

writer.on('finish', resolve);

writer.on('error', (err) => {
reject(new Error(`Write stream error: ${err.message}`));
response.data.destroy();
});

response.data.pipe(writer);
});

logger.debug(`Downloaded file to: ${downloadPath}`);

// 返回下载文件路径,由主流程决定是否需要压缩
return downloadPath;
} catch (error) {
// 如果下载失败,清理临时目录
try {
fs.rmSync(tempDir, { recursive: true, force: true });
logger.debug(`Cleaned up temporary directory after error: ${tempDir}`);
} catch (cleanupError) {
logger.debug(`Failed to clean up temporary directory: ${cleanupError.message}`);
}

throw new Error(`Failed to download code from URL: ${error.message}`);
}
}

/**
* 生成 auto 资源,非 FC 资源,主要指 vpc、nas、log、role(oss mount 挂载点才有)
*/
Expand Down
Loading