Skip to content
Merged
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
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ See `docs/architecture.md` for detailed diagrams.

Frozen scope (read-only for all subsequent work):

| Path | Contents |
| ---- | -------- |
| `src/subCommands/model/` | `model.ts`, `index.ts`, `fileManager.ts`, `constants.ts`, `utils/` |
| `src/commands-help/model.ts` | `model` command help text |
| Path | Contents |
| ---------------------------- | ------------------------------------------------------------------ |
| `src/subCommands/model/` | `model.ts`, `index.ts`, `fileManager.ts`, `constants.ts`, `utils/` |
| `src/commands-help/model.ts` | `model` command help text |

Rules:

Expand Down
2 changes: 2 additions & 0 deletions __tests__/ut/commands/build/impl/baseBuilder_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ describe('Builder', () => {
const builderWithCustomContainer = new TestBuilder(inputsWithCustomContainer);

(FC.isCustomContainerRuntime as jest.Mock).mockReturnValue(true);
(FC.getContainerImage as jest.Mock).mockReturnValue('custom-image:latest');

const image = await builderWithCustomContainer.getRuntimeBuildImage();
expect(image).toBe('custom-image:latest');
Expand All @@ -300,6 +301,7 @@ describe('Builder', () => {
const builderWithCustomContainer = new TestBuilder(inputsWithCustomContainer);

(FC.isCustomContainerRuntime as jest.Mock).mockReturnValue(true);
(FC.getContainerImage as jest.Mock).mockReturnValue('');
(_.isEmpty as any).mockReturnValue(true);

await expect(builderWithCustomContainer.getRuntimeBuildImage()).rejects.toThrow(
Expand Down
11 changes: 11 additions & 0 deletions __tests__/ut/commands/deploy/impl/function_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,9 @@ describe('Service', () => {
customContainerConfig: {},
} as IFunction;

// Mock FC.getContainerImage
(FC.getContainerImage as jest.Mock).mockReturnValue(undefined);

await expect((service as any)._pushImage()).rejects.toThrow(
'CustomContainerRuntime must have a valid image URL',
);
Expand All @@ -455,6 +458,11 @@ describe('Service', () => {
customContainerConfig: { image: 'registry.cn-hangzhou.aliyuncs.com/test/image' },
} as IFunction;

// Mock FC.getContainerImage
(FC.getContainerImage as jest.Mock).mockReturnValue(
'registry.cn-hangzhou.aliyuncs.com/test/image',
);

// Mock Acr.isAcrRegistry
Acr.isAcrRegistry = jest.fn().mockReturnValue(true);

Expand All @@ -475,6 +483,9 @@ describe('Service', () => {
customContainerConfig: { image: 'docker.io/test/image' },
} as IFunction;

// Mock FC.getContainerImage
(FC.getContainerImage as jest.Mock).mockReturnValue('docker.io/test/image');

// Mock Acr.isAcrRegistry
Acr.isAcrRegistry = jest.fn().mockReturnValue(false);

Expand Down
30 changes: 30 additions & 0 deletions __tests__/ut/local/impl/baseLocal_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import path from 'path';
import * as fs from 'fs-extra';
import { v4 as uuidV4 } from 'uuid';
import { getTempDir } from '../../../../src/utils';
import FC from '../../../../src/resources/fc';

// Mock external dependencies
jest.mock('../../../../src/logger', () => ({
Expand Down Expand Up @@ -210,6 +211,16 @@ describe('BaseLocal', () => {
expect(result).toBe(true);
});

it('should return true for micro-sandbox runtime', () => {
const inputsWithMicroSandbox = JSON.parse(JSON.stringify(mockInputs));
inputsWithMicroSandbox.props.runtime = 'micro-sandbox';

const instance = new BaseLocal(inputsWithMicroSandbox);
const result = instance.isCustomContainerRuntime();

expect(result).toBe(true);
});

it('should return false for non custom-container runtime', () => {
const instance = new BaseLocal(mockInputs);
const result = instance.isCustomContainerRuntime();
Expand All @@ -218,6 +229,25 @@ describe('BaseLocal', () => {
});
});

describe('getRuntimeRunImage', () => {
it('should use microSandboxConfig.image for micro-sandbox runtime', async () => {
const inputsWithMicroSandbox = JSON.parse(JSON.stringify(mockInputs));
inputsWithMicroSandbox.props.runtime = 'micro-sandbox';
inputsWithMicroSandbox.props.microSandboxConfig = { image: 'registry/sandbox:v1' };

const instance = new BaseLocal(inputsWithMicroSandbox);
(FC.getContainerImage as jest.Mock).mockReturnValue('registry/sandbox:v1');

const image = await instance.getRuntimeRunImage();

expect(image).toBe('registry/sandbox:v1');
expect(FC.getContainerImage).toHaveBeenCalledWith(inputsWithMicroSandbox.props);
expect(logger.debug).toHaveBeenCalledWith(
'use fc docker CustomContainer image: registry/sandbox:v1',
);
});
});

describe('checkCodeUri', () => {
it('should return true when codeUri is valid', () => {
const instance = new BaseLocal(mockInputs);
Expand Down
23 changes: 23 additions & 0 deletions __tests__/ut/resources/fc/impl/utils_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getRemoteResourceConfig,
computeLocalAuto,
getCustomEndpoint,
getContainerImage,
} from '../../../../../src/resources/fc/impl/utils';
import { INasConfig, IVpcConfig, ILogConfig, IOssMountConfig } from '../../../../../src/interface';
import * as utils from '../../../../../src/utils';
Expand Down Expand Up @@ -68,6 +69,28 @@ describe('utils', () => {
});
});

describe('getContainerImage', () => {
it('should prefer microSandboxConfig.image over customContainerConfig.image', () => {
const result = getContainerImage({
microSandboxConfig: { image: 'registry/sandbox:v1' },
customContainerConfig: { image: 'registry/container:v1' },
});
expect(result).toBe('registry/sandbox:v1');
});

it('should fall back to customContainerConfig.image', () => {
const result = getContainerImage({
customContainerConfig: { image: 'registry/container:v1' },
});
expect(result).toBe('registry/container:v1');
});

it('should return undefined when no image is configured', () => {
expect(getContainerImage({})).toBeUndefined();
expect(getContainerImage({ customContainerConfig: { image: '' } })).toBeUndefined();
});
});

describe('getRemoteResourceConfig', () => {
it('should extract remote resource configurations correctly', () => {
const mockRemote = {
Expand Down
2 changes: 1 addition & 1 deletion publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Type: Component
Name: fc3
Provider:
- 阿里云
Version: 0.1.24
Version: 0.1.25
Description: 阿里云函数计算全生命周期管理
HomePage: https://github.com/devsapp/fc3
Organization: 阿里云函数计算(FC)
Expand Down
11 changes: 8 additions & 3 deletions src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,14 @@ export default class Base {
_.set(inputs, 'props.endpoint', argvEndpoint);
}
// fc组件镜像 trim 左右空格
const image = _.get(inputs, 'props.customContainerConfig.image');
if (!_.isEmpty(image)) {
_.set(inputs, 'props.customContainerConfig.image', _.trim(image));
for (const imagePath of [
'props.microSandboxConfig.image',
'props.customContainerConfig.image',
]) {
const image = _.get(inputs, imagePath);
if (!_.isEmpty(image)) {
_.set(inputs, imagePath, _.trim(image));
}
}

const role = _.get(inputs, 'props.role');
Expand Down
1 change: 1 addition & 0 deletions src/interface/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export interface ILogConfig {
}

export interface IMicroSandboxConfig {
image?: string;
osType?: string;
readyCommand?: string;
startCommand?: string;
Expand Down
19 changes: 18 additions & 1 deletion src/resources/fc/impl/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import _ from 'lodash';
import { INasConfig, IVpcConfig, ILogConfig, Runtime, IOssMountConfig } from '../../../interface';
import {
INasConfig,
IVpcConfig,
ILogConfig,
Runtime,
IOssMountConfig,
IFunction,
} from '../../../interface';
import { isAuto, isAutoVpcConfig } from '../../../utils';
import logger from '../../../logger';
import * as fs from 'fs';
Expand All @@ -10,6 +17,16 @@
return runtime === Runtime['custom-container'] || runtime === Runtime['micro-sandbox'];
}

/**
* microSandboxConfig.image 优先级高于 customContainerConfig.image
*/
export function getContainerImage(
props: Pick<IFunction, 'microSandboxConfig' | 'customContainerConfig'>,
): string | undefined {
const image = props?.microSandboxConfig?.image || props?.customContainerConfig?.image;
return _.isEmpty(image) ? undefined : image;
}

export function isCustomRuntime(runtime: string): boolean {
return (
runtime === Runtime.custom ||
Expand Down Expand Up @@ -45,7 +62,7 @@
if (stat.isDirectory()) {
continue;
}
const newMode = stat.mode | 0o111;

Check warning on line 65 in src/resources/fc/impl/utils.ts

View workflow job for this annotation

GitHub Actions / check-format

Unexpected use of '|'
fs.chmodSync(filePath, newMode);
logger.info(`Set executable permission for: ${filePath}`);
} catch (fileError) {
Expand Down
34 changes: 18 additions & 16 deletions src/resources/fc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ import {
isFunctionStateWaitTimedOut,
isFunctionScalingConfigError,
} from './error-code';
import { isCustomContainerRuntime, isCustomRuntime, computeLocalAuto } from './impl/utils';
import {
isCustomContainerRuntime,
isCustomRuntime,
computeLocalAuto,
getContainerImage,
} from './impl/utils';
import replaceFunctionConfig from './impl/replace-function-config';
import { IAlias } from '../../interface/cli-config/alias';
import { TriggerType } from '../../interface/base';
Expand All @@ -79,6 +84,7 @@ export default class FC extends FC_Client {
static computeLocalAuto = computeLocalAuto;
static isCustomContainerRuntime = isCustomContainerRuntime;
static isCustomRuntime = isCustomRuntime;
static getContainerImage = getContainerImage;
static replaceFunctionConfig = replaceFunctionConfig;

async untilFunctionStateOK(config: IFunction, reason: string, skipAccelerationWait?: boolean) {
Expand All @@ -93,34 +99,35 @@ export default class FC extends FC_Client {
const retryContainerAccelerated = FC.isCustomContainerRuntime(config.runtime);
// 部署镜像需要重试 3min, 直到达到!(State == Pending || LastUpdateStatus == InProgress)
if (retryContainerAccelerated) {
const image = getContainerImage(config);
if (skipAccelerationWait) {
logger.info(
`Skip waiting for ${config.customContainerConfig.image} optimization. The function will be available for invocation once the image acceleration process is complete.`,
`Skip waiting for ${image} optimization. The function will be available for invocation once the image acceleration process is complete.`,
);
return;
}
console.log('');
if (reason === 'CREATE') {
if (isAppCenter()) {
logger.info(
`${config.customContainerConfig.image} optimization to be ready, the function will be available for invocation once this process is complete`,
`${image} optimization to be ready, the function will be available for invocation once this process is complete`,
);
} else {
logger.spin(
'checking',
`${config.customContainerConfig.image} `,
`${image} `,
`optimization to be ready, the function will be available for invocation once this process is complete ...`,
);
}
} else if (reason === 'UPDATE') {
if (isAppCenter()) {
logger.info(
`${config.customContainerConfig.image} optimization to be ready, function calls will be updated to the latest deployed version once the image optimization process is complete ...`,
`${image} optimization to be ready, function calls will be updated to the latest deployed version once the image optimization process is complete ...`,
);
} else {
logger.spin(
'checking',
`${config.customContainerConfig.image}`,
`${image}`,
`optimization to be ready, function calls will be updated to the latest deployed version once the image optimization process is complete ...`,
);
}
Expand All @@ -145,16 +152,14 @@ export default class FC extends FC_Client {
await sleep(retryInterval);
if (isAppCenter()) {
logger.info(
`${
config.customContainerConfig.image
} optimization is not ready, function state=${state}, lastUpdateStatus=${lastUpdateStatus}, waiting ${
`${image} optimization is not ready, function state=${state}, lastUpdateStatus=${lastUpdateStatus}, waiting ${
(new Date().getTime() - startTime) / 1000
} seconds...`,
);
} else {
logger.spin(
'checking',
`${config.customContainerConfig.image}`,
`${image}`,
`optimization is not ready, function state=${state}, lastUpdateStatus=${lastUpdateStatus}, waiting ${
(new Date().getTime() - startTime) / 1000
} seconds...`,
Expand All @@ -172,13 +177,9 @@ export default class FC extends FC_Client {
await sleep(retryInterval);
} else {
if (isAppCenter()) {
logger.info(`${config.customContainerConfig.image} optimization is ready`);
logger.info(`${image} optimization is ready`);
} else {
logger.spin(
'checked',
`${config.customContainerConfig.image}`,
`optimization is ready`,
);
logger.spin('checked', `${image}`, `optimization is ready`);
}
break;
}
Expand Down Expand Up @@ -290,6 +291,7 @@ export default class FC extends FC_Client {
functionName: config.functionName,
code: config.code,
customContainerConfig: config.customContainerConfig,
microSandboxConfig: config.microSandboxConfig,
} as any;
} else if (type === 'config') {
_.unset(config, 'code');
Expand Down
18 changes: 16 additions & 2 deletions src/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,9 @@
},
"IMicroSandboxConfig": {
"properties": {
"image": {
"type": "string"
},
"osType": {
"type": "string"
},
Expand Down Expand Up @@ -1454,8 +1457,19 @@
"required": [
"region",
"functionName",
"runtime",
"customContainerConfig"
"runtime"
],
"anyOf": [
{
"required": [
"microSandboxConfig"
]
},
{
"required": [
"customContainerConfig"
]
}
]
},
"else": {
Expand Down
2 changes: 1 addition & 1 deletion src/subCommands/build/impl/baseBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export abstract class Builder {
async getRuntimeBuildImage(): Promise<string> {
let image: string;
if (FC.isCustomContainerRuntime(this.getRuntime())) {
image = this.getProps().customContainerConfig?.image;
image = FC.getContainerImage(this.getProps());
if (_.isEmpty(image)) {
throw new Error('image must be set in custom-container runtime');
}
Expand Down
4 changes: 2 additions & 2 deletions src/subCommands/deploy/impl/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ export default class Service extends Base {
// custom-container 检查 s.yaml 中 image 是否存在 acr 中, 如果存在, 则弹出交互提示
// --skip-push 则不用提示
if (FC.isCustomContainerRuntime(this.local.runtime)) {
const { image } = this.local.customContainerConfig || {};
const image = FC.getContainerImage(this.local);
if (_.isNil(image)) {
throw new Error('CustomContainerRuntime must have a valid image URL');
}
Expand Down Expand Up @@ -256,7 +256,7 @@ export default class Service extends Base {
logger.debug(`skip push is ${this.skipPush}`);
return;
}
const { image } = this.local.customContainerConfig || {};
const image = FC.getContainerImage(this.local);
if (_.isNil(image)) {
throw new Error('CustomContainerRuntime must have a valid image URL');
}
Expand Down
Loading
Loading