Skip to content

Commit ba4ca27

Browse files
author
高魏洪
committed
fix: layer publish support code is http url, model remove error
1 parent 35a061a commit ba4ca27

File tree

5 files changed

+115
-53
lines changed

5 files changed

+115
-53
lines changed

__tests__/ut/commands/invoke_test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import logger from '../../../src/logger';
44
import { IInputs } from '../../../src/interface';
55
import fs from 'fs';
66

7+
// Mock @serverless-devs/downloads module to prevent import errors
8+
jest.mock('@serverless-devs/downloads', () => ({
9+
__esModule: true,
10+
default: jest.fn(),
11+
}));
12+
713
// Mock dependencies
814
jest.mock('../../../src/resources/fc', () => {
915
return {

__tests__/ut/utils/utils_test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ import log from '../../../src/logger';
2121
import { execSync } from 'child_process';
2222
log._set(console);
2323

24+
// Mock @serverless-devs/downloads module to prevent import errors
25+
jest.mock('@serverless-devs/downloads', () => ({
26+
__esModule: true,
27+
default: jest.fn(),
28+
}));
29+
2430
describe('isAuto', () => {
2531
test('should return true if config is string "AUTO" or "auto"', () => {
2632
expect(isAuto('AUTO')).toBe(true);

src/subCommands/deploy/impl/function.ts

Lines changed: 22 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import _ from 'lodash';
22
import { diffConvertYaml } from '@serverless-devs/diff';
33
import inquirer from 'inquirer';
44
import fs from 'fs';
5-
import os from 'os';
65
import assert from 'assert';
76
import path from 'path';
87
import { yellow } from 'chalk';
@@ -19,10 +18,15 @@ import FC, { GetApiType } from '../../../resources/fc';
1918
import VPC_NAS from '../../../resources/vpc-nas';
2019
import Base from './base';
2120
import { ICredentials } from '@serverless-devs/component-interface';
22-
import { calculateCRC64, getFileSize, parseAutoConfig, checkFcDir } from '../../../utils';
21+
import {
22+
calculateCRC64,
23+
getFileSize,
24+
parseAutoConfig,
25+
checkFcDir,
26+
_downloadFromUrl,
27+
} from '../../../utils';
2328
import OSS from '../../../resources/oss';
2429
import { setNodeModulesBinPermissions } from '../../../resources/fc/impl/utils';
25-
import downloads from '@serverless-devs/downloads';
2630

2731
type IType = 'code' | 'config' | boolean;
2832
interface IOpts {
@@ -280,11 +284,11 @@ export default class Service extends Base {
280284
}
281285

282286
let zipPath: string;
283-
let downloadedTempDir = '';
287+
let downloadedTempFile = '';
284288
// 处理不同类型的 codeUri
285289
if (codeUri.startsWith('http://') || codeUri.startsWith('https://')) {
286-
zipPath = await this._downloadFromUrl(codeUri);
287-
downloadedTempDir = path.dirname(zipPath);
290+
zipPath = await _downloadFromUrl(codeUri);
291+
downloadedTempFile = zipPath;
288292
} else {
289293
zipPath = path.isAbsolute(codeUri) ? codeUri : path.join(this.inputs.baseDir, codeUri);
290294
}
@@ -321,6 +325,14 @@ export default class Service extends Base {
321325
logger.debug(
322326
yellow(`skip uploadCode because code is no changed, codeChecksum=${crc64Value}`),
323327
);
328+
if (downloadedTempFile) {
329+
try {
330+
logger.debug(`Removing temp download dir: ${downloadedTempFile}`);
331+
fs.rmSync(downloadedTempFile, { recursive: true, force: true });
332+
} catch (ex) {
333+
logger.debug(`Unable to remove temp download dir: ${downloadedTempFile}`);
334+
}
335+
}
324336
return false;
325337
} else {
326338
logger.debug(`\x1b[33mcodeChecksum from ${this.codeChecksum} to ${crc64Value}\x1b[0m`);
@@ -338,58 +350,18 @@ export default class Service extends Base {
338350
}
339351
}
340352

341-
if (downloadedTempDir) {
353+
if (downloadedTempFile) {
342354
try {
343-
logger.debug(`Removing temp download dir: ${downloadedTempDir}`);
344-
fs.rmSync(downloadedTempDir, { recursive: true, force: true });
355+
logger.debug(`Removing temp download dir: ${downloadedTempFile}`);
356+
fs.rmSync(downloadedTempFile, { recursive: true, force: true });
345357
} catch (ex) {
346-
logger.debug(`Unable to remove temp download dir: ${downloadedTempDir}`);
358+
logger.debug(`Unable to remove temp download dir: ${downloadedTempFile}`);
347359
}
348360
}
349361

350362
return true;
351363
}
352364

353-
/**
354-
* 从URL下载文件到本地临时目录
355-
*/
356-
private async _downloadFromUrl(url: string): Promise<string> {
357-
logger.info(`Downloading code from URL: ${url}`);
358-
359-
// 创建临时目录
360-
const tempDir = path.join(os.tmpdir(), 'fc_code_download');
361-
let downloadPath: string;
362-
363-
try {
364-
// 从URL获取文件名
365-
const urlPath = new URL(url).pathname;
366-
const parsedPathName = path.parse(urlPath).name;
367-
const filename = path.basename(urlPath) || `downloaded_code_${Date.now()}`;
368-
downloadPath = path.join(tempDir, filename);
369-
370-
await downloads(url, {
371-
dest: tempDir,
372-
filename: parsedPathName,
373-
extract: false,
374-
});
375-
376-
logger.debug(`Downloaded file to: ${downloadPath}`);
377-
378-
// 返回下载文件路径,由主流程决定是否需要压缩
379-
return downloadPath;
380-
} catch (error) {
381-
// 如果下载失败,清理临时目录
382-
try {
383-
fs.rmSync(tempDir, { recursive: true, force: true });
384-
logger.debug(`Cleaned up temporary directory after error: ${tempDir}`);
385-
} catch (cleanupError) {
386-
logger.debug(`Failed to clean up temporary directory: ${cleanupError.message}`);
387-
}
388-
389-
throw new Error(`Failed to download code from URL: ${error.message}`);
390-
}
391-
}
392-
393365
/**
394366
* 生成 auto 资源,非 FC 资源,主要指 vpc、nas、log、role(oss mount 挂载点才有)
395367
*/

src/subCommands/layer/index.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
calculateCRC64,
1616
getFileSize,
1717
getUserAgent,
18+
_downloadFromUrl,
1819
} from '../../utils';
1920
import chalk from 'chalk';
2021

@@ -28,6 +29,7 @@ export default class Layer {
2829
layerName: string,
2930
compatibleRuntimeList: string[],
3031
description: string,
32+
downloadedTempFile = '',
3133
): Promise<any> {
3234
let zipPath = toZipDir;
3335
let generateZipFilePath = '';
@@ -53,6 +55,14 @@ export default class Layer {
5355
`Skip uploadCode because code is no changed, codeChecksum=${crc64Value}; Laster layerArn=${latestLayer.layerVersionArn}`,
5456
),
5557
);
58+
if (downloadedTempFile) {
59+
try {
60+
logger.debug(`Removing temp download dir: ${downloadedTempFile}`);
61+
fs.rmSync(downloadedTempFile, { recursive: true, force: true });
62+
} catch (ex) {
63+
logger.debug(`Unable to remove temp download dir: ${downloadedTempFile}`);
64+
}
65+
}
5666
return latestLayer;
5767
}
5868
}
@@ -76,6 +86,16 @@ export default class Layer {
7686
logger.debug(`Unable to remove zip file: ${zipPath}`);
7787
}
7888
}
89+
90+
if (downloadedTempFile) {
91+
try {
92+
logger.debug(`Removing temp download file: ${downloadedTempFile}`);
93+
fs.rmSync(downloadedTempFile, { recursive: true, force: true });
94+
} catch (ex) {
95+
logger.debug(`Unable to remove temp download file: ${downloadedTempFile}`);
96+
}
97+
}
98+
7999
console.log(JSON.stringify(result));
80100
return result;
81101
}
@@ -220,7 +240,16 @@ export default class Layer {
220240
);
221241
}
222242

223-
const toZipDir: string = path.isAbsolute(codeUri) ? codeUri : path.join(this.baseDir, codeUri);
243+
let toZipDir: string;
244+
let downloadedTempFile = '';
245+
// 处理不同类型的 codeUri
246+
if (codeUri.startsWith('http://') || codeUri.startsWith('https://')) {
247+
toZipDir = await _downloadFromUrl(codeUri);
248+
downloadedTempFile = toZipDir;
249+
} else {
250+
toZipDir = path.isAbsolute(codeUri) ? codeUri : path.join(this.baseDir, codeUri);
251+
}
252+
224253
const compatibleRuntimeList = compatibleRuntime.split(',');
225254
return Layer.safe_publish_layer(
226255
this.fcSdk,
@@ -229,6 +258,7 @@ export default class Layer {
229258
layerName,
230259
compatibleRuntimeList,
231260
this.opts.description || '',
261+
downloadedTempFile,
232262
);
233263
}
234264

src/utils/index.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import Table from 'tty-table';
44
import * as crc64 from 'crc64-ecma182.js';
55
import { promisify } from 'util';
66
import * as fs from 'fs';
7+
import os from 'os';
78
import logger from '../logger';
89
import { execSync } from 'child_process';
910
import axios from 'axios';
1011
import { FC_API_ERROR_CODE, isInvalidArgument } from '../resources/fc/error-code';
12+
import path from 'path';
13+
import downloads from '@serverless-devs/downloads';
1114

1215
export { default as verify } from './verify';
1316
export { default as runCommand } from './run-command';
@@ -261,8 +264,8 @@ export function getUserAgent(userAgent: string, command: string) {
261264
/**
262265
* 验证并规范化路径
263266
*/
264-
export function checkFcDir(path: string, paramName = 'path'): string {
265-
const normalizedPath = path.trim();
267+
export function checkFcDir(inputPath: string, paramName = 'path'): string {
268+
const normalizedPath = inputPath.trim();
266269

267270
if (!normalizedPath.startsWith('/')) {
268271
throw new Error(`${paramName} does not start with '/'`);
@@ -308,3 +311,48 @@ export function checkFcDir(path: string, paramName = 'path'): string {
308311

309312
return normalizedPath;
310313
}
314+
315+
/**
316+
* 从URL下载文件到本地临时目录
317+
*/
318+
export async function _downloadFromUrl(url: string): Promise<string> {
319+
// 创建临时目录
320+
const tempDir = path.join(os.tmpdir(), 'fc_code_download');
321+
let downloadPath: string;
322+
323+
try {
324+
// 确保临时目录存在
325+
if (!fs.existsSync(tempDir)) {
326+
fs.mkdirSync(tempDir, { recursive: true });
327+
}
328+
329+
// 从URL获取文件名
330+
const urlPath = new URL(url).pathname;
331+
const parsedPathName = path.parse(urlPath).name;
332+
const filename = path.basename(urlPath) || `downloaded_code_${Date.now()}`;
333+
downloadPath = path.join(tempDir, filename);
334+
335+
await downloads(url, {
336+
dest: tempDir,
337+
filename: parsedPathName,
338+
extract: false,
339+
});
340+
341+
logger.debug(`Downloaded file to: ${downloadPath}`);
342+
343+
// 返回下载文件路径,由主流程决定是否需要压缩
344+
return downloadPath;
345+
} catch (error) {
346+
// 如果下载失败,清理临时目录
347+
try {
348+
if (fs.existsSync(tempDir)) {
349+
fs.rmSync(tempDir, { recursive: true, force: true });
350+
logger.debug(`Cleaned up temporary directory after error: ${tempDir}`);
351+
}
352+
} catch (cleanupError) {
353+
logger.debug(`Failed to clean up temporary directory: ${cleanupError.message}`);
354+
}
355+
356+
throw new Error(`Failed to download code from URL: ${error.message}`);
357+
}
358+
}

0 commit comments

Comments
 (0)