[Fleet] Add ability to specify which integration variables should be configurable (#97163)
This commit is contained in:
parent
d72a7afbf4
commit
deaa7794d5
|
@ -14,6 +14,7 @@ export interface PackagePolicyPackage {
|
|||
export interface PackagePolicyConfigRecordEntry {
|
||||
type?: string;
|
||||
value?: any;
|
||||
frozen?: boolean;
|
||||
}
|
||||
|
||||
export type PackagePolicyConfigRecord = Record<string, PackagePolicyConfigRecordEntry>;
|
||||
|
|
|
@ -105,12 +105,13 @@ export const PackagePolicyInputConfig: React.FunctionComponent<{
|
|||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
{requiredVars.map((varDef) => {
|
||||
const { name: varName, type: varType } = varDef;
|
||||
const value = packagePolicyInput.vars![varName].value;
|
||||
const { value, frozen } = packagePolicyInput.vars![varName];
|
||||
return (
|
||||
<EuiFlexItem key={varName}>
|
||||
<PackagePolicyInputVarField
|
||||
varDef={varDef}
|
||||
value={value}
|
||||
frozen={frozen}
|
||||
onChange={(newValue: any) => {
|
||||
updatePackagePolicyInput({
|
||||
vars: {
|
||||
|
|
|
@ -106,12 +106,13 @@ export const PackagePolicyInputStreamConfig: React.FunctionComponent<{
|
|||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
{requiredVars.map((varDef) => {
|
||||
const { name: varName, type: varType } = varDef;
|
||||
const value = packagePolicyInputStream.vars![varName].value;
|
||||
const { value, frozen } = packagePolicyInputStream.vars![varName];
|
||||
return (
|
||||
<EuiFlexItem key={varName}>
|
||||
<PackagePolicyInputVarField
|
||||
varDef={varDef}
|
||||
value={value}
|
||||
frozen={frozen}
|
||||
onChange={(newValue: any) => {
|
||||
updatePackagePolicyInputStream({
|
||||
vars: {
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
EuiComboBox,
|
||||
EuiText,
|
||||
EuiCodeEditor,
|
||||
EuiTextArea,
|
||||
EuiFieldPassword,
|
||||
} from '@elastic/eui';
|
||||
|
||||
|
@ -29,7 +30,8 @@ export const PackagePolicyInputVarField: React.FunctionComponent<{
|
|||
onChange: (newValue: any) => void;
|
||||
errors?: string[] | null;
|
||||
forceShowErrors?: boolean;
|
||||
}> = memo(({ varDef, value, onChange, errors: varErrors, forceShowErrors }) => {
|
||||
frozen?: boolean;
|
||||
}> = memo(({ varDef, value, onChange, errors: varErrors, forceShowErrors, frozen }) => {
|
||||
const [isDirty, setIsDirty] = useState<boolean>(false);
|
||||
const { multi, required, type, title, name, description } = varDef;
|
||||
const isInvalid = (isDirty || forceShowErrors) && !!varErrors;
|
||||
|
@ -50,12 +52,20 @@ export const PackagePolicyInputVarField: React.FunctionComponent<{
|
|||
onChange(newVals.map((val) => val.label));
|
||||
}}
|
||||
onBlur={() => setIsDirty(true)}
|
||||
isDisabled={frozen}
|
||||
/>
|
||||
);
|
||||
}
|
||||
switch (type) {
|
||||
case 'yaml':
|
||||
return (
|
||||
return frozen ? (
|
||||
<EuiTextArea
|
||||
className="ace_editor"
|
||||
disabled
|
||||
value={value}
|
||||
style={{ height: '175px', padding: '4px', whiteSpace: 'pre', resize: 'none' }}
|
||||
/>
|
||||
) : (
|
||||
<EuiCodeEditor
|
||||
width="100%"
|
||||
mode="yaml"
|
||||
|
@ -79,6 +89,7 @@ export const PackagePolicyInputVarField: React.FunctionComponent<{
|
|||
showLabel={false}
|
||||
onChange={(e) => onChange(e.target.checked)}
|
||||
onBlur={() => setIsDirty(true)}
|
||||
disabled={frozen}
|
||||
/>
|
||||
);
|
||||
case 'password':
|
||||
|
@ -89,6 +100,7 @@ export const PackagePolicyInputVarField: React.FunctionComponent<{
|
|||
value={value === undefined ? '' : value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
onBlur={() => setIsDirty(true)}
|
||||
disabled={frozen}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
|
@ -98,10 +110,11 @@ export const PackagePolicyInputVarField: React.FunctionComponent<{
|
|||
value={value === undefined ? '' : value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
onBlur={() => setIsDirty(true)}
|
||||
disabled={frozen}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}, [isInvalid, multi, onChange, type, value, fieldLabel]);
|
||||
}, [isInvalid, multi, onChange, type, value, fieldLabel, frozen]);
|
||||
|
||||
// Boolean cannot be optional by default set to false
|
||||
const isOptional = useMemo(() => type !== 'bool' && !required, [required, type]);
|
||||
|
|
|
@ -11,10 +11,10 @@ import {
|
|||
httpServerMock,
|
||||
} from 'src/core/server/mocks';
|
||||
|
||||
import type { SavedObjectsUpdateResponse } from 'src/core/server';
|
||||
import type { SavedObjectsClient, SavedObjectsUpdateResponse } from 'src/core/server';
|
||||
import type { KibanaRequest } from 'kibana/server';
|
||||
|
||||
import type { PackageInfo, PackagePolicySOAttributes } from '../types';
|
||||
import type { PackageInfo, PackagePolicySOAttributes, AgentPolicySOAttributes } from '../types';
|
||||
import { createPackagePolicyMock } from '../../common/mocks';
|
||||
import type { ExternalCallback } from '..';
|
||||
|
||||
|
@ -68,6 +68,26 @@ jest.mock('./epm/registry', () => {
|
|||
};
|
||||
});
|
||||
|
||||
jest.mock('./agent_policy', () => {
|
||||
return {
|
||||
agentPolicyService: {
|
||||
get: async (soClient: SavedObjectsClient, id: string) => {
|
||||
const agentPolicySO = await soClient.get<AgentPolicySOAttributes>(
|
||||
'ingest-agent-policies',
|
||||
id
|
||||
);
|
||||
if (!agentPolicySO) {
|
||||
return null;
|
||||
}
|
||||
const agentPolicy = { id: agentPolicySO.id, ...agentPolicySO.attributes };
|
||||
agentPolicy.package_policies = [];
|
||||
return agentPolicy;
|
||||
},
|
||||
bumpRevision: () => {},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('Package policy service', () => {
|
||||
describe('compilePackagePolicyInputs', () => {
|
||||
it('should work with config variables from the stream', async () => {
|
||||
|
@ -346,8 +366,8 @@ describe('Package policy service', () => {
|
|||
});
|
||||
savedObjectsClient.update.mockImplementation(
|
||||
async (
|
||||
type: string,
|
||||
id: string
|
||||
_type: string,
|
||||
_id: string
|
||||
): Promise<SavedObjectsUpdateResponse<PackagePolicySOAttributes>> => {
|
||||
throw savedObjectsClient.errors.createConflictError('abc', '123');
|
||||
}
|
||||
|
@ -362,6 +382,131 @@ describe('Package policy service', () => {
|
|||
)
|
||||
).rejects.toThrow('Saved object [abc/123] conflict');
|
||||
});
|
||||
|
||||
it('should only update input vars that are not frozen', async () => {
|
||||
const savedObjectsClient = savedObjectsClientMock.create();
|
||||
const mockPackagePolicy = createPackagePolicyMock();
|
||||
const mockInputs = [
|
||||
{
|
||||
config: {},
|
||||
enabled: true,
|
||||
type: 'endpoint',
|
||||
vars: {
|
||||
dog: {
|
||||
type: 'text',
|
||||
value: 'dalmatian',
|
||||
},
|
||||
cat: {
|
||||
type: 'text',
|
||||
value: 'siamese',
|
||||
frozen: true,
|
||||
},
|
||||
},
|
||||
streams: [
|
||||
{
|
||||
data_stream: {
|
||||
type: 'birds',
|
||||
dataset: 'migratory.patterns',
|
||||
},
|
||||
enabled: false,
|
||||
id: `endpoint-migratory.patterns-${mockPackagePolicy.id}`,
|
||||
vars: {
|
||||
paths: {
|
||||
value: ['north', 'south'],
|
||||
type: 'text',
|
||||
frozen: true,
|
||||
},
|
||||
period: {
|
||||
value: '6mo',
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const inputsUpdate = [
|
||||
{
|
||||
config: {},
|
||||
enabled: true,
|
||||
type: 'endpoint',
|
||||
vars: {
|
||||
dog: {
|
||||
type: 'text',
|
||||
value: 'labrador',
|
||||
},
|
||||
cat: {
|
||||
type: 'text',
|
||||
value: 'tabby',
|
||||
},
|
||||
},
|
||||
streams: [
|
||||
{
|
||||
data_stream: {
|
||||
type: 'birds',
|
||||
dataset: 'migratory.patterns',
|
||||
},
|
||||
enabled: false,
|
||||
id: `endpoint-migratory.patterns-${mockPackagePolicy.id}`,
|
||||
vars: {
|
||||
paths: {
|
||||
value: ['east', 'west'],
|
||||
type: 'text',
|
||||
},
|
||||
period: {
|
||||
value: '12mo',
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const attributes = {
|
||||
...mockPackagePolicy,
|
||||
inputs: mockInputs,
|
||||
};
|
||||
|
||||
savedObjectsClient.get.mockResolvedValue({
|
||||
id: 'test',
|
||||
type: 'abcd',
|
||||
references: [],
|
||||
version: 'test',
|
||||
attributes,
|
||||
});
|
||||
|
||||
savedObjectsClient.update.mockImplementation(
|
||||
async (
|
||||
type: string,
|
||||
id: string,
|
||||
attrs: any
|
||||
): Promise<SavedObjectsUpdateResponse<PackagePolicySOAttributes>> => {
|
||||
savedObjectsClient.get.mockResolvedValue({
|
||||
id: 'test',
|
||||
type: 'abcd',
|
||||
references: [],
|
||||
version: 'test',
|
||||
attributes: attrs,
|
||||
});
|
||||
return attrs;
|
||||
}
|
||||
);
|
||||
const elasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
const result = await packagePolicyService.update(
|
||||
savedObjectsClient,
|
||||
elasticsearchClient,
|
||||
'the-package-policy-id',
|
||||
{ ...mockPackagePolicy, inputs: inputsUpdate }
|
||||
);
|
||||
|
||||
const [modifiedInput] = result.inputs;
|
||||
expect(modifiedInput.vars!.dog.value).toEqual('labrador');
|
||||
expect(modifiedInput.vars!.cat.value).toEqual('siamese');
|
||||
const [modifiedStream] = modifiedInput.streams;
|
||||
expect(modifiedStream.vars!.paths.value).toEqual(expect.arrayContaining(['north', 'south']));
|
||||
expect(modifiedStream.vars!.period.value).toEqual('12mo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('runExternalCallbacks', () => {
|
||||
|
|
|
@ -23,6 +23,7 @@ import type {
|
|||
DeletePackagePoliciesResponse,
|
||||
PackagePolicyInput,
|
||||
NewPackagePolicyInput,
|
||||
PackagePolicyConfigRecordEntry,
|
||||
PackagePolicyInputStream,
|
||||
PackageInfo,
|
||||
ListWithKuery,
|
||||
|
@ -346,6 +347,8 @@ class PackagePolicyService {
|
|||
assignStreamIdToInput(oldPackagePolicy.id, input)
|
||||
);
|
||||
|
||||
inputs = enforceFrozenInputs(oldPackagePolicy.inputs, inputs);
|
||||
|
||||
if (packagePolicy.package?.name) {
|
||||
const pkgInfo = await getPackageInfo({
|
||||
savedObjectsClient: soClient,
|
||||
|
@ -602,6 +605,42 @@ async function _compilePackageStream(
|
|||
return { ...stream };
|
||||
}
|
||||
|
||||
function enforceFrozenInputs(oldInputs: PackagePolicyInput[], newInputs: PackagePolicyInput[]) {
|
||||
const resultInputs = [...newInputs];
|
||||
|
||||
for (const input of resultInputs) {
|
||||
const oldInput = oldInputs.find((i) => i.type === input.type);
|
||||
if (input.vars && oldInput?.vars) {
|
||||
input.vars = _enforceFrozenVars(oldInput.vars, input.vars);
|
||||
}
|
||||
if (input.streams && oldInput?.streams) {
|
||||
for (const stream of input.streams) {
|
||||
const oldStream = oldInput.streams.find((s) => s.id === stream.id);
|
||||
if (stream.vars && oldStream?.vars) {
|
||||
stream.vars = _enforceFrozenVars(oldStream.vars, stream.vars);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resultInputs;
|
||||
}
|
||||
|
||||
function _enforceFrozenVars(
|
||||
oldVars: Record<string, PackagePolicyConfigRecordEntry>,
|
||||
newVars: Record<string, PackagePolicyConfigRecordEntry>
|
||||
) {
|
||||
const resultVars: Record<string, PackagePolicyConfigRecordEntry> = {};
|
||||
for (const [key, val] of Object.entries(oldVars)) {
|
||||
if (val.frozen) {
|
||||
resultVars[key] = val;
|
||||
} else {
|
||||
resultVars[key] = newVars[key];
|
||||
}
|
||||
}
|
||||
return resultVars;
|
||||
}
|
||||
|
||||
export type PackagePolicyServiceInterface = PackagePolicyService;
|
||||
export const packagePolicyService = new PackagePolicyService();
|
||||
|
||||
|
|
|
@ -16,7 +16,8 @@ const varsSchema = schema.maybe(
|
|||
schema.object({
|
||||
name: schema.string(),
|
||||
type: schema.maybe(schema.string()),
|
||||
value: schema.oneOf([schema.string(), schema.number()]),
|
||||
value: schema.maybe(schema.oneOf([schema.string(), schema.number()])),
|
||||
frozen: schema.maybe(schema.boolean()),
|
||||
})
|
||||
)
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue