Skip to content

Commit 678781b

Browse files
committed
feat(FR-1726): Auto-populate ENV variables based on selected Inference Runtime Variant
1 parent adbf7f2 commit 678781b

25 files changed

+675
-58
lines changed

react/src/components/EnvVarFormList.test.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,18 @@ describe('emptySensitiveEnv', () => {
3333

3434
expect(result).toEqual([]);
3535
});
36+
37+
it('should not sanitize NGC_API_KEY even though it matches sensitive pattern', () => {
38+
const envs = [
39+
{ variable: 'NGC_API_KEY', value: 'my-secret-ngc-key' },
40+
{ variable: 'OTHER_API_KEY', value: 'should-be-sanitized' },
41+
];
42+
43+
const result = sanitizeSensitiveEnv(envs);
44+
45+
expect(result).toEqual([
46+
{ variable: 'NGC_API_KEY', value: 'my-secret-ngc-key' },
47+
{ variable: 'OTHER_API_KEY', value: '' },
48+
]);
49+
});
3650
});

react/src/components/EnvVarFormList.tsx

Lines changed: 120 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
11
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
2-
import { Button, Form, FormItemProps, Input, InputRef } from 'antd';
2+
import {
3+
AutoComplete,
4+
Button,
5+
Form,
6+
FormItemProps,
7+
Input,
8+
InputRef,
9+
} from 'antd';
310
import { FormListProps } from 'antd/lib/form';
411
import { BAIFlex } from 'backend.ai-ui';
512
import _ from 'lodash';
613
import React, { useRef } from 'react';
714
import { useTranslation } from 'react-i18next';
815

16+
export interface EnvVarConfig {
17+
variable: string;
18+
placeholder?: string;
19+
required?: boolean;
20+
description?: string;
21+
}
22+
923
interface EnvVarFormListProps extends Omit<FormListProps, 'children'> {
1024
formItemProps?: FormItemProps;
25+
requiredEnvVars?: EnvVarConfig[];
26+
optionalEnvVars?: EnvVarConfig[];
1127
}
1228

1329
export interface EnvVarFormListValue {
@@ -17,11 +33,67 @@ export interface EnvVarFormListValue {
1733
// TODO: validation rule for duplicate variable name
1834
const EnvVarFormList: React.FC<EnvVarFormListProps> = ({
1935
formItemProps,
36+
requiredEnvVars,
37+
optionalEnvVars,
2038
...props
2139
}) => {
40+
'use memo';
2241
const inputRef = useRef<InputRef>(null);
2342
const { t } = useTranslation();
2443
const form = Form.useFormInstance();
44+
45+
const allEnvVars = [
46+
..._.filter(
47+
requiredEnvVars || [],
48+
(env): env is EnvVarConfig => env != null && !!env.variable,
49+
),
50+
..._.filter(
51+
optionalEnvVars || [],
52+
(env): env is EnvVarConfig => env != null && !!env.variable,
53+
),
54+
];
55+
56+
const getPlaceholderForVariable = (variable: string) => {
57+
if (!variable || !allEnvVars.length)
58+
return t('session.launcher.EnvironmentVariableValue');
59+
const envVarConfig = _.find(
60+
allEnvVars,
61+
(env) => env && env.variable === variable,
62+
);
63+
return (
64+
envVarConfig?.placeholder ||
65+
t('session.launcher.EnvironmentVariableValue')
66+
);
67+
};
68+
69+
const getAutoCompleteOptions = () => {
70+
const currentValues = form.getFieldValue(props.name) || [];
71+
const usedVariables = _.map(
72+
_.filter(
73+
currentValues,
74+
(item: EnvVarFormListValue) =>
75+
item != null &&
76+
typeof item.variable === 'string' &&
77+
item.variable.trim() !== '',
78+
),
79+
'variable',
80+
);
81+
82+
return _.map(
83+
_.filter(
84+
optionalEnvVars || [],
85+
(env): env is EnvVarConfig =>
86+
env != null &&
87+
!!env.variable &&
88+
!_.includes(usedVariables, env.variable),
89+
),
90+
(env) => ({
91+
value: env.variable,
92+
label: env.variable,
93+
}),
94+
);
95+
};
96+
2597
return (
2698
<Form.List {...props}>
2799
{(fields, { add, remove }) => {
@@ -34,6 +106,7 @@ const EnvVarFormList: React.FC<EnvVarFormListProps> = ({
34106
style={{ marginBottom: 0, flex: 1 }}
35107
name={[name, 'variable']}
36108
rules={[
109+
...(formItemProps?.rules || []),
37110
{
38111
required: true,
39112
message: t('session.launcher.EnterEnvironmentVariable'),
@@ -71,18 +144,38 @@ const EnvVarFormList: React.FC<EnvVarFormListProps> = ({
71144
]}
72145
{...formItemProps}
73146
>
74-
<Input
75-
ref={index === fields.length - 1 ? inputRef : null}
76-
placeholder="Variable"
77-
onChange={() => {
78-
const fieldNames = fields.map((_field, index) => [
79-
props.name,
80-
index,
81-
'variable',
82-
]);
83-
form.validateFields(fieldNames);
84-
}}
85-
/>
147+
{optionalEnvVars && getAutoCompleteOptions().length > 0 ? (
148+
<AutoComplete
149+
placeholder={t('session.launcher.EnvironmentVariable')}
150+
options={getAutoCompleteOptions()}
151+
onChange={() => {
152+
const fieldNames = fields.map((_field, index) => [
153+
props.name,
154+
index,
155+
'variable',
156+
]);
157+
form.validateFields(fieldNames);
158+
}}
159+
filterOption={(inputValue, option) =>
160+
option?.value
161+
?.toLowerCase()
162+
.indexOf(inputValue.toLowerCase()) !== -1
163+
}
164+
/>
165+
) : (
166+
<Input
167+
ref={index === fields.length - 1 ? inputRef : null}
168+
placeholder={t('session.launcher.EnvironmentVariable')}
169+
onChange={() => {
170+
const fieldNames = fields.map((_field, index) => [
171+
props.name,
172+
index,
173+
'variable',
174+
]);
175+
form.validateFields(fieldNames);
176+
}}
177+
/>
178+
)}
86179
</Form.Item>
87180
<Form.Item
88181
{...restField}
@@ -97,17 +190,12 @@ const EnvVarFormList: React.FC<EnvVarFormListProps> = ({
97190
},
98191
]}
99192
validateTrigger={['onChange', 'onBlur']}
193+
dependencies={[[props.name, name, 'variable']]}
100194
>
101195
<Input
102-
placeholder="Value"
103-
// onChange={() => {
104-
// const valueFields = fields.map((field, index) => [
105-
// props.name,
106-
// index,
107-
// 'value',
108-
// ]);
109-
// form.validateFields(valueFields);
110-
// }}
196+
placeholder={getPlaceholderForVariable(
197+
form.getFieldValue([props.name, name, 'variable']),
198+
)}
111199
/>
112200
</Form.Item>
113201
<MinusCircleOutlined onClick={() => remove(name)} />
@@ -161,12 +249,20 @@ const sensitivePatterns = [
161249
];
162250

163251
export function isSensitiveEnv(key: string) {
164-
return sensitivePatterns.some((pattern) => pattern.test(key));
252+
return _.some(sensitivePatterns, (pattern) => pattern.test(key));
165253
}
166254

255+
// Environment variables that should not be sanitized even if they match sensitive patterns
256+
const SANITIZE_EXCEPTIONS = ['NGC_API_KEY'];
257+
167258
export function sanitizeSensitiveEnv(envs: EnvVarFormListValue[]) {
168259
return _.map(envs, (env) => {
169-
if (env && isSensitiveEnv(env.variable)) {
260+
if (
261+
env &&
262+
env.variable &&
263+
!_.includes(SANITIZE_EXCEPTIONS, env.variable) &&
264+
isSensitiveEnv(env.variable)
265+
) {
170266
return { ...env, value: '' };
171267
}
172268
return env;

0 commit comments

Comments
 (0)