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

feat(api7): support consumer credentials #182

Merged
merged 26 commits into from
Sep 15, 2024
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
3 changes: 2 additions & 1 deletion .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ jobs:
if: contains(github.event.pull_request.labels.*.name, 'test/api7') || github.event_name == 'push'
strategy:
matrix:
version: [3.2.14.6]
version: [3.2.14.6, 3.2.15.0]
env:
BACKEND_API7_VERSION: ${{ matrix.version }}
BACKEND_API7_DOWNLOAD_URL: https://run.api7.ai/api7-ee/api7-ee-v${{ matrix.version }}.tar.gz
BACKEND_API7_LICENSE: ${{ secrets.BACKEND_API7_LICENSE }}
steps:
Expand Down
84 changes: 72 additions & 12 deletions apps/cli/src/differ/differv3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ const order = {
[`${ADCSDK.ResourceType.PLUGIN_METADATA}.${ADCSDK.EventType.DELETE}`]: 27,
[`${ADCSDK.ResourceType.PLUGIN_METADATA}.${ADCSDK.EventType.CREATE}`]: 28,
[`${ADCSDK.ResourceType.PLUGIN_METADATA}.${ADCSDK.EventType.UPDATE}`]: 29,
[`${ADCSDK.ResourceType.CONSUMER_CREDENTIAL}.${ADCSDK.EventType.DELETE}`]: 30,
[`${ADCSDK.ResourceType.CONSUMER_CREDENTIAL}.${ADCSDK.EventType.CREATE}`]: 31,
[`${ADCSDK.ResourceType.CONSUMER_CREDENTIAL}.${ADCSDK.EventType.UPDATE}`]: 32,
};

export class DifferV3 {
Expand Down Expand Up @@ -163,6 +166,19 @@ export class DifferV3 {
res,
]) ?? [],
),
...differ.diffResource(
ADCSDK.ResourceType.CONSUMER_CREDENTIAL,
local?.consumer_credentials?.map((res) => [
res.name,
ADCSDK.utils.generateId(generateResourceName(res.name)),
res,
]) ?? [],
remote?.consumer_credentials?.map((res) => [
res.name,
ADCSDK.utils.generateId(generateResourceName(res.name)),
res,
]) ?? [],
),
/* ...differ.diffResource(
ADCSDK.ResourceType.UPSTREAM,
local?.upstreams?.map((res) => [
Expand Down Expand Up @@ -262,6 +278,15 @@ export class DifferV3 {
local: Array<[ADCSDK.ResourceName, ADCSDK.ResourceId, ADCSDK.StreamRoute]>,
remote: Array<[ADCSDK.ResourceName, ADCSDK.ResourceId, ADCSDK.StreamRoute]>,
): Array<ADCSDK.Event>;
private diffResource(
resourceType: ADCSDK.ResourceType.CONSUMER_CREDENTIAL,
local: Array<
[ADCSDK.ResourceName, ADCSDK.ResourceId, ADCSDK.ConsumerCredential]
>,
remote: Array<
[ADCSDK.ResourceName, ADCSDK.ResourceId, ADCSDK.ConsumerCredential]
>,
): Array<ADCSDK.Event>;
private diffResource(
resourceType: ADCSDK.ResourceType.UPSTREAM,
local: Array<[ADCSDK.ResourceName, ADCSDK.ResourceId, ADCSDK.Upstream]>,
Expand Down Expand Up @@ -316,6 +341,8 @@ export class DifferV3 {
oldValue: remoteItem,

// Special handling of resources containing nested resources
// When a consumer is deleted, its credentials are not taken into
// consideration.
subEvents: DifferV3.diff(
{},
resourceType === ADCSDK.ResourceType.SERVICE
Expand Down Expand Up @@ -392,33 +419,55 @@ export class DifferV3 {
// Special handling of resources containing nested resources: routes, consumer_groups
const subEvents: Array<ADCSDK.Event> = [];
if (
resourceType === ADCSDK.ResourceType.SERVICE ||
resourceType === ADCSDK.ResourceType.CONSUMER_GROUP
[
ADCSDK.ResourceType.SERVICE,
ADCSDK.ResourceType.CONSUMER_GROUP,
ADCSDK.ResourceType.CONSUMER,
].includes(resourceType)
) {
subEvents.push(
...DifferV3.diff(
resourceType === ADCSDK.ResourceType.SERVICE
resourceType ===
(ADCSDK.ResourceType.SERVICE as ADCSDK.ResourceType)
? {
routes: (localItem as ADCSDK.Service).routes,
stream_routes: (localItem as ADCSDK.Service).stream_routes,
}
: resourceType === ADCSDK.ResourceType.CONSUMER_GROUP
: resourceType ===
(ADCSDK.ResourceType.CONSUMER_GROUP as ADCSDK.ResourceType)
? {
consumers: (localItem as ADCSDK.ConsumerGroup).consumers,
}
: {},
resourceType === ADCSDK.ResourceType.SERVICE
: resourceType ===
(ADCSDK.ResourceType.CONSUMER as ADCSDK.ResourceType)
? {
consumer_credentials: (localItem as ADCSDK.Consumer)
.credentials,
}
: {},
resourceType ===
(ADCSDK.ResourceType.SERVICE as ADCSDK.ResourceType)
? {
routes: (remoteItem as ADCSDK.Service).routes,
stream_routes: (remoteItem as ADCSDK.Service).stream_routes,
}
: resourceType === ADCSDK.ResourceType.CONSUMER_GROUP
: resourceType ===
(ADCSDK.ResourceType.CONSUMER_GROUP as ADCSDK.ResourceType)
? {
consumers: (remoteItem as ADCSDK.ConsumerGroup).consumers,
}
: {},
: resourceType ===
(ADCSDK.ResourceType.CONSUMER as ADCSDK.ResourceType)
? {
consumer_credentials: (remoteItem as ADCSDK.Consumer)
.credentials,
}
: {},
this.defaultValue,
resourceType === ADCSDK.ResourceType.SERVICE
[
ADCSDK.ResourceType.SERVICE,
ADCSDK.ResourceType.CONSUMER,
].includes(resourceType)
? remoteName
: undefined,
).map(this.postprocessSubEvent(remoteName, remoteId)),
Expand All @@ -434,7 +483,9 @@ export class DifferV3 {
// Remove nested resources to indeed compare the main resource itself.
(resourceType === ADCSDK.ResourceType.SERVICE
? ['routes', 'stream_routes']
: ['consumers']
: resourceType === ADCSDK.ResourceType.CONSUMER_GROUP
? ['consumers']
: ['credentials']
).map((key) => {
unset(mergedLocalItem, key);
unset(remoteItem, key);
Expand Down Expand Up @@ -555,10 +606,19 @@ export class DifferV3 {
? {
consumers: (localItem as ADCSDK.ConsumerGroup).consumers,
}
: {},
: resourceType === ADCSDK.ResourceType.CONSUMER
? {
consumer_credentials: (localItem as ADCSDK.Consumer)
.credentials,
}
: {},
{},
this.defaultValue,
resourceType === ADCSDK.ResourceType.SERVICE ? localName : undefined,
[ADCSDK.ResourceType.SERVICE, ADCSDK.ResourceType.CONSUMER].includes(
resourceType,
)
? localName
: undefined,
).map(this.postprocessSubEvent(localName, localId)),
});
});
Expand Down
119 changes: 119 additions & 0 deletions apps/cli/src/differ/specs/consumer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import * as ADCSDK from '@api7/adc-sdk';

import { DifferV3 } from '../differv3';

describe('Differ V3 - consumer', () => {
it('should create/update/delete consumer credentials', () => {
const consumerName = 'jack';
const changeme = 'changeme';
expect(
DifferV3.diff(
{
consumers: [
{
username: consumerName,
credentials: [
{
name: ADCSDK.EventType.CREATE,
type: 'key-auth',
config: {
key: consumerName,
},
},
{
name: ADCSDK.EventType.UPDATE,
type: 'basic-auth',
config: {
username: consumerName,
password: `${changeme}.new`,
},
},
],
},
],
},
{
consumers: [
{
username: consumerName,
credentials: [
{
name: ADCSDK.EventType.UPDATE,
type: 'basic-auth',
config: {
username: consumerName,
password: changeme,
},
},
{
name: ADCSDK.EventType.DELETE,
type: 'jwt-auth',
config: {
key: consumerName,
secret: changeme,
},
},
],
},
],
},
),
).toEqual([
{
oldValue: {
config: { key: consumerName, secret: changeme },
name: ADCSDK.EventType.DELETE,
type: 'jwt-auth',
},
parentId: consumerName,
resourceId: ADCSDK.utils.generateId(
`${consumerName}.${ADCSDK.EventType.DELETE}`,
),
resourceName: ADCSDK.EventType.DELETE,
resourceType: ADCSDK.ResourceType.CONSUMER_CREDENTIAL,
type: ADCSDK.EventType.DELETE,
},
{
newValue: {
config: { key: consumerName },
name: ADCSDK.EventType.CREATE,
type: 'key-auth',
},
parentId: consumerName,
resourceId: ADCSDK.utils.generateId(
`${consumerName}.${ADCSDK.EventType.CREATE}`,
),
resourceName: ADCSDK.EventType.CREATE,
resourceType: ADCSDK.ResourceType.CONSUMER_CREDENTIAL,
type: ADCSDK.EventType.CREATE,
},
{
diff: [
{
kind: 'E',
lhs: changeme,
path: ['config', 'password'],
rhs: `${changeme}.new`,
},
],
newValue: {
config: { password: `${changeme}.new`, username: consumerName },
name: ADCSDK.EventType.UPDATE,
type: 'basic-auth',
},
oldValue: {
config: { password: changeme, username: consumerName },
name: ADCSDK.EventType.UPDATE,
type: 'basic-auth',
},
parentId: consumerName,
resourceId: ADCSDK.utils.generateId(
`${consumerName}.${ADCSDK.EventType.UPDATE}`,
),
resourceName: ADCSDK.EventType.UPDATE,
resourceType: ADCSDK.ResourceType.CONSUMER_CREDENTIAL,
type: ADCSDK.EventType.UPDATE,
},
]);
});
});
23 changes: 22 additions & 1 deletion apps/cli/src/linter/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ const labelsSchema = z.record(
z.string(),
z.union([z.string(), z.array(z.string())]),
);
const pluginsSchema = z.record(z.string(), z.record(z.string(), z.any()));
const pluginSchema = z.record(z.string(), z.any());
const pluginsSchema = z.record(z.string(), pluginSchema);
const exprSchema = z.array(z.any());
const timeoutSchema = z.object({
connect: z.number().gt(0),
Expand Down Expand Up @@ -289,13 +290,33 @@ const sslSchema = z
})
.strict();

const consumerCredentialSchema = z
.object({
name: nameSchema,
description: descriptionSchema.optional(),
labels: labelsSchema.optional(),

type: z
.string()
.refine(
(type) =>
['key-auth', 'basic-auth', 'jwt-auth', 'hmac-auth'].includes(type),
{
message:
'Consumer credential only supports "key-auth", "basic-auth", "jwt-auth" and "hmac-auth" types',
},
),
config: pluginSchema,
})
.strict();
const consumerSchema = z
.object({
username: nameSchema,
description: descriptionSchema.optional(),
labels: labelsSchema.optional(),

plugins: pluginsSchema.optional(),
credentials: z.array(consumerCredentialSchema).optional(),
})
.strict();

Expand Down
Loading
Loading