Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] Create and update package policy API return 409 conflict when names are not unique #153533

Merged
merged 2 commits into from
Mar 23, 2023
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
6 changes: 6 additions & 0 deletions x-pack/plugins/fleet/common/openapi/bundled.json
Original file line number Diff line number Diff line change
Expand Up @@ -3858,6 +3858,9 @@
},
"400": {
"$ref": "#/components/responses/error"
},
"409": {
"$ref": "#/components/responses/error"
}
},
"requestBody": {
Expand Down Expand Up @@ -4066,6 +4069,9 @@
},
"400": {
"$ref": "#/components/responses/error"
},
"409": {
"$ref": "#/components/responses/error"
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/fleet/common/openapi/bundled.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2406,6 +2406,8 @@ paths:
- item
'400':
$ref: '#/components/responses/error'
'409':
$ref: '#/components/responses/error'
requestBody:
description: >-
You should use inputs as an object and not use the deprecated inputs
Expand Down Expand Up @@ -2537,6 +2539,8 @@ paths:
- success
'400':
$ref: '#/components/responses/error'
'409':
$ref: '#/components/responses/error'
/package_policies/upgrade/dryrun:
post:
summary: Dry run package policy upgrade
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ post:
- item
'400':
$ref: ../components/responses/error.yaml
'409':
$ref: ../components/responses/error.yaml
requestBody:
description: You should use inputs as an object and not use the deprecated inputs array.
content:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ post:
- success
'400':
$ref: ../components/responses/error.yaml
'409':
$ref: ../components/responses/error.yaml
4 changes: 4 additions & 0 deletions x-pack/plugins/fleet/server/errors/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
PackageFailedVerificationError,
PackagePolicyNotFoundError,
FleetUnauthorizedError,
PackagePolicyNameExistsError,
} from '.';

type IngestErrorHandler = (
Expand Down Expand Up @@ -78,6 +79,9 @@ const getHTTPResponseCode = (error: FleetError): number => {
if (error instanceof FleetUnauthorizedError) {
return 403; // Unauthorized
}
if (error instanceof PackagePolicyNameExistsError) {
return 409; // Conflict
}
return 400; // Bad Request
};

Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/server/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class ConcurrentInstallOperationError extends FleetError {}
export class AgentReassignmentError extends FleetError {}
export class PackagePolicyIneligibleForUpgradeError extends FleetError {}
export class PackagePolicyValidationError extends FleetError {}
export class PackagePolicyNameExistsError extends FleetError {}
export class PackagePolicyNotFoundError extends FleetError {}
export class BundledPackageNotFoundError extends FleetError {}
export class HostedAgentPolicyRestrictionRelatedError extends FleetError {
Expand Down
47 changes: 25 additions & 22 deletions x-pack/plugins/fleet/server/services/package_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import {
PackagePolicyNotFoundError,
HostedAgentPolicyRestrictionRelatedError,
FleetUnauthorizedError,
PackagePolicyNameExistsError,
} from '../errors';
import { NewPackagePolicySchema, PackagePolicySchema, UpdatePackagePolicySchema } from '../types';
import type {
Expand Down Expand Up @@ -166,17 +167,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
// trailing whitespace causes issues creating API keys
enrichedPackagePolicy.name = enrichedPackagePolicy.name.trim();
if (!options?.skipUniqueNameVerification) {
const existingPoliciesWithName = await this.list(soClient, {
perPage: 1,
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: "${enrichedPackagePolicy.name}"`,
});

// Check that the name does not exist already
if (existingPoliciesWithName.items.length > 0) {
throw new FleetError(
`An integration policy with the name ${enrichedPackagePolicy.name} already exists. Please rename it or choose a different name.`
);
}
await requireUniqueName(soClient, enrichedPackagePolicy);
}

let elasticsearchPrivileges: NonNullable<PackagePolicy['elasticsearch']>['privileges'];
Expand Down Expand Up @@ -548,17 +539,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
packagePolicy.name !== oldPackagePolicy.name &&
!options?.skipUniqueNameVerification
) {
// Check that the name does not exist already but exclude the current package policy
const existingPoliciesWithName = await this.list(soClient, {
perPage: SO_SEARCH_LIMIT,
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name:"${packagePolicy.name}"`,
});
const filtered = (existingPoliciesWithName?.items || []).filter((p) => p.id !== id);
if (filtered.length > 0) {
throw new FleetError(
`An integration policy with the name ${packagePolicy.name} already exists. Please rename it or choose a different name.`
);
}
await requireUniqueName(soClient, enrichedPackagePolicy, id);
}

let inputs = restOfPackagePolicy.inputs.map((input) =>
Expand Down Expand Up @@ -2251,3 +2232,25 @@ function deepMergeVars(original: any, override: any, keepOriginalValue = false):

return result;
}

async function requireUniqueName(
soClient: SavedObjectsClientContract,
packagePolicy: UpdatePackagePolicy | NewPackagePolicy,
id?: string
) {
const existingPoliciesWithName = await packagePolicyService.list(soClient, {
perPage: SO_SEARCH_LIMIT,
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name:"${packagePolicy.name}"`,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: This logic could be simplified to use ES kuery to filter out the same id, and could use the total count without any hits.
Probably not a huge gain as there will be at least 1 matches, just some code simplification.

Something like:

  const existingPoliciesWithName = await packagePolicyService.list(soClient, {
    perPage: 0,
    kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name:"${packagePolicy.name}" AND NOT (${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.id:"${packagePolicy.id}")`,
  });
if (existingPoliciesWithName?.total > 0) {
  throw ...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks Julia! Since the gain it's not very big I'm going to merge as it is, but we can do this change the next time we touch this area :)

});

const policiesToCheck = id
? // Check that the name does not exist already but exclude the current package policy
(existingPoliciesWithName?.items || []).filter((p) => p.id !== id)
: existingPoliciesWithName?.items;

if (policiesToCheck.length > 0) {
throw new PackagePolicyNameExistsError(
`An integration policy with the name ${packagePolicy.name} already exists. Please rename it or choose a different name.`
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ export default function (providerContext: FtrProviderContext) {
.expect(400);
});

it('should return a 400 if there is another package policy with the same name', async function () {
it('should return a 409 if there is another package policy with the same name', async function () {
await supertest
.post(`/api/fleet/package_policies`)
.set('kbn-xsrf', 'xxxx')
Expand Down Expand Up @@ -279,10 +279,10 @@ export default function (providerContext: FtrProviderContext) {
version: '0.1.0',
},
})
.expect(400);
.expect(409);
});

it('should return a 400 if there is a package policy with the same name on a different policy', async function () {
it('should return a 409 if there is a package policy with the same name on a different policy', async function () {
const { body: agentPolicyResponse } = await supertest
.post(`/api/fleet/agent_policies`)
.set('kbn-xsrf', 'xxxx')
Expand Down Expand Up @@ -325,7 +325,7 @@ export default function (providerContext: FtrProviderContext) {
version: '0.1.0',
},
})
.expect(400);
.expect(409);
});

it('should return a 400 with required variables not provided', async function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ export default function (providerContext: FtrProviderContext) {
});
});

it('should return a 400 if there is another package policy with the same name', async function () {
it('should return a 409 if there is another package policy with the same name', async function () {
await supertest
.put(`/api/fleet/package_policies/${packagePolicyId2}`)
.set('kbn-xsrf', 'xxxx')
Expand All @@ -385,10 +385,10 @@ export default function (providerContext: FtrProviderContext) {
version: '0.1.0',
},
})
.expect(400);
.expect(409);
});

it('should return a 400 if there is another package policy with the same name on a different policy', async function () {
it('should return a 409 if there is another package policy with the same name on a different policy', async function () {
const { body: agentPolicyResponse } = await supertest
.post(`/api/fleet/agent_policies`)
.set('kbn-xsrf', 'xxxx')
Expand All @@ -414,7 +414,7 @@ export default function (providerContext: FtrProviderContext) {
version: '0.1.0',
},
})
.expect(400);
.expect(409);
});

it('should work with frozen input vars', async function () {
Expand Down