From da6a87482fad8cc4677ec8dfee5e384b14d2af3e Mon Sep 17 00:00:00 2001 From: bzp2010 Date: Tue, 22 Oct 2024 13:42:30 +0800 Subject: [PATCH 01/14] feat(apisix): support consumer credentials --- libs/backend-apisix/src/fetcher.ts | 33 +++++++++++++++++++++++++++++- libs/backend-apisix/src/index.ts | 27 +++++++++++++++++++++++- libs/backend-apisix/src/typing.ts | 7 +++++++ libs/backend-apisix/src/utils.ts | 14 +++++++++---- 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/libs/backend-apisix/src/fetcher.ts b/libs/backend-apisix/src/fetcher.ts index 5157543e..f9f462c4 100644 --- a/libs/backend-apisix/src/fetcher.ts +++ b/libs/backend-apisix/src/fetcher.ts @@ -1,6 +1,7 @@ import * as ADCSDK from '@api7/adc-sdk'; import { Axios } from 'axios'; import { ListrTask } from 'listr2'; +import { SemVer, gte as semVerGTE } from 'semver'; import { ToADC } from './transformer'; import * as typing from './typing'; @@ -8,6 +9,8 @@ import { buildReqAndRespDebugOutput, resourceTypeToAPIName } from './utils'; type FetchTask = ListrTask<{ remote: ADCSDK.Configuration; + + apisixVersion: SemVer; apisixResources?: typing.Resources; }>; @@ -65,7 +68,6 @@ export class Fetcher { ) return; - // resourceType === ADCSDK.ResourceType.GLOBAL_RULE || if (resourceType === ADCSDK.ResourceType.PLUGIN_METADATA) { ctx.apisixResources[ADCSDK.ResourceType.PLUGIN_METADATA] = Object.fromEntries( @@ -92,6 +94,35 @@ export class Fetcher { (item) => item.value, ); } + + if ( + resourceType === ADCSDK.ResourceType.CONSUMER && + semVerGTE(ctx.apisixVersion, '3.11.0') + ) { + await Promise.all( + ctx.apisixResources[resourceType].map(async (item) => { + const resp = await this.client.get<{ + list: Array<{ + key: string; + value: typing.ConsumerCredential; + createdIndex: number; + modifiedIndex: number; + }>; + total: number; + }>(`/apisix/admin/consumers/${item.username}/credentials`, { + validateStatus: () => true, + }); + task.output = buildReqAndRespDebugOutput( + resp, + `Get credentials of consumer "${item.username}"`, + ); + if (resp.status === 200) + item.credentials = resp.data.list.map( + (credential) => credential.value, + ); + }), + ); + } }, }), ); diff --git a/libs/backend-apisix/src/index.ts b/libs/backend-apisix/src/index.ts index ba8f124a..d8a8a562 100644 --- a/libs/backend-apisix/src/index.ts +++ b/libs/backend-apisix/src/index.ts @@ -3,9 +3,11 @@ import axios, { Axios, CreateAxiosDefaults } from 'axios'; import { Listr, ListrTask } from 'listr2'; import { readFileSync } from 'node:fs'; import { AgentOptions, Agent as httpsAgent } from 'node:https'; +import semver from 'semver'; import { Fetcher } from './fetcher'; import { Operator } from './operator'; +import { buildReqAndRespDebugOutput } from './utils'; export class BackendAPISIX implements ADCSDK.Backend { private readonly client: Axios; @@ -45,6 +47,24 @@ export class BackendAPISIX implements ADCSDK.Backend { await this.client.get(`/apisix/admin/routes`); } + private getAPISIXVersionTask(): ListrTask { + return { + enabled: (ctx) => !ctx.apisixVersion, + task: async (ctx, task) => { + const resp = await this.client.get<{ value: string }>( + '/apisix/admin/routes', + ); + task.output = buildReqAndRespDebugOutput(resp, `Get APISIX version`); + + ctx.apisixVersion = semver.coerce('0.0.0'); + if (resp.headers.server) { + const version = (resp.headers.server as string).match(/APISIX\/(.*)/); + if (version) ctx.apisixVersion = semver.coerce(version[1]); + } + }, + }; + } + public getResourceDefaultValueTask(): Array { return []; } @@ -52,7 +72,11 @@ export class BackendAPISIX implements ADCSDK.Backend { public async dump(): Promise> { const fetcher = new Fetcher(this.client); return new Listr( - [...this.getResourceDefaultValueTask(), ...fetcher.fetch()], + [ + this.getAPISIXVersionTask(), + ...this.getResourceDefaultValueTask(), + ...fetcher.fetch(), + ], { rendererOptions: { scope: BackendAPISIX.logScope }, }, @@ -63,6 +87,7 @@ export class BackendAPISIX implements ADCSDK.Backend { const operator = new Operator(this.client); return new Listr( [ + this.getAPISIXVersionTask(), ...this.getResourceDefaultValueTask(), { task: (ctx, task) => diff --git a/libs/backend-apisix/src/typing.ts b/libs/backend-apisix/src/typing.ts index d75122f4..c39b8984 100644 --- a/libs/backend-apisix/src/typing.ts +++ b/libs/backend-apisix/src/typing.ts @@ -59,6 +59,12 @@ export interface Service { script?: string; enable_websocket?: boolean; } +export interface ConsumerCredential { + desc?: string; + labels?: Labels; + + plugins?: Plugins; +} export interface Consumer { username: string; desc?: string; @@ -66,6 +72,7 @@ export interface Consumer { group_id?: string; plugins?: Plugins; + credentials?: Array; } export interface SSL { id: string; diff --git a/libs/backend-apisix/src/utils.ts b/libs/backend-apisix/src/utils.ts index 21410072..5a221f3c 100644 --- a/libs/backend-apisix/src/utils.ts +++ b/libs/backend-apisix/src/utils.ts @@ -1,10 +1,16 @@ import * as ADCSDK from '@api7/adc-sdk'; import axios, { AxiosResponse } from 'axios'; -export const resourceTypeToAPIName = (resourceType: ADCSDK.ResourceType) => - resourceType !== ADCSDK.ResourceType.PLUGIN_METADATA - ? `${resourceType}s` - : resourceType; +export const resourceTypeToAPIName = (resourceType: ADCSDK.ResourceType) => { + switch (resourceType) { + case ADCSDK.ResourceType.PLUGIN_METADATA: + return resourceType; + case ADCSDK.ResourceType.CONSUMER_CREDENTIAL: + return `consumers/%s/credentials`; + default: + return `${resourceType}s`; + } +}; export const capitalizeFirstLetter = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); From 0aebfc278696c4a1658cf228474b0ea40e24fbe0 Mon Sep 17 00:00:00 2001 From: bzp2010 Date: Tue, 22 Oct 2024 15:49:18 +0800 Subject: [PATCH 02/14] feat: support operate consumer credentials --- libs/backend-apisix/src/operator.ts | 85 ++++++++++++++++++-------- libs/backend-apisix/src/transformer.ts | 45 ++++++++++++-- libs/backend-apisix/src/typing.ts | 2 + 3 files changed, 104 insertions(+), 28 deletions(-) diff --git a/libs/backend-apisix/src/operator.ts b/libs/backend-apisix/src/operator.ts index 6025f480..da1d3f1d 100644 --- a/libs/backend-apisix/src/operator.ts +++ b/libs/backend-apisix/src/operator.ts @@ -1,6 +1,7 @@ import * as ADCSDK from '@api7/adc-sdk'; import { Axios } from 'axios'; import { ListrTask } from 'listr2'; +import { SemVer, lt as semVerLT } from 'semver'; import { FromADC } from './transformer'; import * as typing from './typing'; @@ -14,6 +15,8 @@ export interface OperateContext { diff: Array; gatewayGroupId: string; needPublishServices: Record; + + apisixVersion: SemVer; } type OperateTask = ListrTask; @@ -24,17 +27,31 @@ export class Operator { return { title: this.generateTaskName(event), task: async (ctx, task) => { - const resp = await this.client.put( - `/apisix/admin/${resourceTypeToAPIName(event.resourceType)}/${event.resourceId}`, - this.fromADC(event), - { - validateStatus: () => true, - }, - ); - task.output = buildReqAndRespDebugOutput(resp); + if (event.resourceType === ADCSDK.ResourceType.CONSUMER_CREDENTIAL) { + if (semVerLT(ctx.apisixVersion, '3.11.0')) return; + + const resp = await this.client.put( + `/apisix/admin/consumers/${event.parentId}/credentials/${event.resourceId}`, + this.fromADC(event), + { + validateStatus: () => true, + }, + ); + task.output = buildReqAndRespDebugOutput(resp); + + if (resp.data?.error_msg) throw new Error(resp.data.error_msg); + } else { + const resp = await this.client.put( + `/apisix/admin/${resourceTypeToAPIName(event.resourceType)}/${event.resourceId}`, + this.fromADC(event), + { + validateStatus: () => true, + }, + ); + task.output = buildReqAndRespDebugOutput(resp); - if (resp.data?.error_msg) throw new Error(resp.data.error_msg); - // [200, 201].includes(resp.status); + if (resp.data?.error_msg) throw new Error(resp.data.error_msg); + } }, }; } @@ -43,22 +60,38 @@ export class Operator { return { title: this.generateTaskName(event), task: async (ctx, task) => { - const resp = await this.client.delete( - `/apisix/admin/${resourceTypeToAPIName(event.resourceType)}/${event.resourceId}`, - { - validateStatus: () => true, - }, - ); - task.output = buildReqAndRespDebugOutput(resp); + if (event.resourceType === ADCSDK.ResourceType.CONSUMER_CREDENTIAL) { + const resp = await this.client.delete( + `/apisix/admin/consumers/${event.parentId}/credentials/${event.resourceId}`, + { + validateStatus: () => true, + }, + ); + task.output = buildReqAndRespDebugOutput(resp); - // If the resource does not exist, it is not an error for the delete operation - if (resp.status === 404) return; - if (resp.data?.error_msg) throw new Error(resp.data.error_msg); - if (resp.data?.deleted <= 0) - throw new Error( - `Unexpected number of deletions of resources: ${resp.data?.deleted}`, + if (resp.status === 404) return; + if (resp.data?.error_msg) throw new Error(resp.data.error_msg); + if (resp.data?.deleted <= 0) + throw new Error( + `Unexpected number of deletions of resources: ${resp.data?.deleted}`, + ); + } else { + const resp = await this.client.delete( + `/apisix/admin/${resourceTypeToAPIName(event.resourceType)}/${event.resourceId}`, + { + validateStatus: () => true, + }, ); - // [200, 404].includes(resp.status); + task.output = buildReqAndRespDebugOutput(resp); + + // If the resource does not exist, it is not an error for the delete operation + if (resp.status === 404) return; + if (resp.data?.error_msg) throw new Error(resp.data.error_msg); + if (resp.data?.deleted <= 0) + throw new Error( + `Unexpected number of deletions of resources: ${resp.data?.deleted}`, + ); + } }, }; } @@ -78,6 +111,10 @@ export class Operator { return fromADC.transformConsumerGroup( event.newValue as ADCSDK.ConsumerGroup, )[0]; + case ADCSDK.ResourceType.CONSUMER_CREDENTIAL: + return fromADC.transformConsumerCredential( + event.newValue as ADCSDK.ConsumerCredential, + ); case ADCSDK.ResourceType.GLOBAL_RULE: return { plugins: { diff --git a/libs/backend-apisix/src/transformer.ts b/libs/backend-apisix/src/transformer.ts index 86ab68ad..d49f24c2 100644 --- a/libs/backend-apisix/src/transformer.ts +++ b/libs/backend-apisix/src/transformer.ts @@ -63,6 +63,31 @@ export class ToADC { plugins: consumer.plugins, group_id: !removeGroupId ? consumer.group_id : undefined, + + credentials: consumer.credentials + ?.map((item) => this.transformConsumerCredential(item)) + .filter((item) => !!item), + } as ADCSDK.Consumer); + } + + public transformConsumerCredential( + credential: typing.ConsumerCredential, + ): ADCSDK.ConsumerCredential { + if (!credential.plugins || Object.keys(credential.plugins).length <= 0) + return; + + const [pluginName, config] = Object.entries(credential.plugins)[0]; + if ( + !['key-auth', 'basic-auth', 'jwt-auth', 'hmac-auth'].includes(pluginName) + ) + return; + return ADCSDK.utils.recursiveOmitUndefined({ + name: credential.name, + description: credential.desc, + labels: credential.labels, + type: pluginName as ADCSDK.ConsumerCredential['type'], + config, + metadata: { id: credential.id }, }); } @@ -258,12 +283,24 @@ export class FromADC { public transformConsumer(consumer: ADCSDK.Consumer): typing.Consumer { return ADCSDK.utils.recursiveOmitUndefined({ - ...consumer, - id: undefined, + username: consumer.username, + desc: consumer.description, labels: FromADC.transformLabels(consumer.labels), - desc: consumer.description, - description: undefined, + plugins: consumer.plugins, + } as typing.Consumer); + } + + public transformConsumerCredential( + credential: ADCSDK.ConsumerCredential, + ): typing.ConsumerCredential { + return ADCSDK.utils.recursiveOmitUndefined({ + name: credential.name, + desc: credential.description, + labels: FromADC.transformLabels(credential.labels), + plugins: { + [credential.type]: credential.config, + }, }); } diff --git a/libs/backend-apisix/src/typing.ts b/libs/backend-apisix/src/typing.ts index c39b8984..69a60675 100644 --- a/libs/backend-apisix/src/typing.ts +++ b/libs/backend-apisix/src/typing.ts @@ -60,6 +60,8 @@ export interface Service { enable_websocket?: boolean; } export interface ConsumerCredential { + id?: string; + name: string; desc?: string; labels?: Labels; From 9abf20a4381bf3fa35243c3dd4b0ea1888b49d3c Mon Sep 17 00:00:00 2001 From: bzp2010 Date: Tue, 22 Oct 2024 16:08:05 +0800 Subject: [PATCH 03/14] test: test on both apisix 3.10.0 and 3.11.0 --- .github/workflows/e2e.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 257eb56d..ec65b49c 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -10,8 +10,11 @@ on: jobs: apisix: runs-on: ubuntu-latest + strategy: + matrix: + version: [3.10.0, 3.11.0] env: - BACKEND_APISIX_VERSION: 3.9.1-debian + BACKEND_APISIX_VERSION: ${{ matrix.version }}-debian steps: - uses: actions/checkout@v4 From 50e55ece14598855ef47ae128bf1e159ad372b80 Mon Sep 17 00:00:00 2001 From: bzp2010 Date: Tue, 22 Oct 2024 16:11:00 +0800 Subject: [PATCH 04/14] test --- .github/workflows/e2e.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index ec65b49c..abc0f73d 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - version: [3.10.0, 3.11.0] + version: [3.7.0, 3.8.1, 3.9.1, 3.10.0, 3.11.0] env: BACKEND_APISIX_VERSION: ${{ matrix.version }}-debian steps: From 7092c089680a4800f17eae91889198f9aff7f66f Mon Sep 17 00:00:00 2001 From: bzp2010 Date: Tue, 22 Oct 2024 17:08:58 +0800 Subject: [PATCH 05/14] test --- .github/workflows/e2e.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index abc0f73d..29d18514 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - version: [3.7.0, 3.8.1, 3.9.1, 3.10.0, 3.11.0] + version: [3.8.1, 3.9.1, 3.10.0, 3.11.0] env: BACKEND_APISIX_VERSION: ${{ matrix.version }}-debian steps: From 224f2eec612f348a154afbc277c6eb494e9c4ca3 Mon Sep 17 00:00:00 2001 From: bzp2010 Date: Tue, 22 Oct 2024 17:10:27 +0800 Subject: [PATCH 06/14] test --- .../e2e/resources/consumer.e2e-spec.ts | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts diff --git a/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts b/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts new file mode 100644 index 00000000..c05aba74 --- /dev/null +++ b/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts @@ -0,0 +1,193 @@ +import * as ADCSDK from '@api7/adc-sdk'; +import { gte, lt } from 'semver'; + +import { BackendAPI7 } from '../../src'; +import { conditionalDescribe, semverCondition } from '../support/utils'; +import { + createEvent, + deleteEvent, + dumpConfiguration, + syncEvents, + updateEvent, +} from '../support/utils'; + +describe('Consumer E2E', () => { + let backend: BackendAPI7; + + beforeAll(() => { + backend = new BackendAPI7({ + server: process.env.SERVER, + token: process.env.TOKEN, + tlsSkipVerify: true, + gatewayGroup: 'default', + }); + }); + + conditionalDescribe(semverCondition(lt, '3.11.0'))( + 'Sync and dump consumers (without credential support)', + () => { + const consumer1Name = 'consumer1'; + const consumer1 = { + username: consumer1Name, + plugins: { + 'key-auth': { + key: consumer1Name, + }, + }, + } as ADCSDK.Consumer; + const consumer2Name = 'consumer2'; + const consumer2 = { + username: consumer2Name, + plugins: { + 'key-auth': { + key: consumer2Name, + }, + }, + } as ADCSDK.Consumer; + + it('Create consumers', async () => + syncEvents(backend, [ + createEvent(ADCSDK.ResourceType.CONSUMER, consumer1Name, consumer1), + createEvent(ADCSDK.ResourceType.CONSUMER, consumer2Name, consumer2), + ])); + + it('Dump', async () => { + const result = (await dumpConfiguration( + backend, + )) as ADCSDK.Configuration; + expect(result.consumers).toHaveLength(2); + expect(result.consumers[0]).toMatchObject(consumer2); + expect(result.consumers[1]).toMatchObject(consumer1); + }); + + it('Update consumer1', async () => { + consumer1.description = 'desc'; + await syncEvents(backend, [ + updateEvent(ADCSDK.ResourceType.CONSUMER, consumer1Name, consumer1), + ]); + }); + + it('Dump again (consumer1 updated)', async () => { + const result = (await dumpConfiguration( + backend, + )) as ADCSDK.Configuration; + expect(result.consumers[0]).toMatchObject(consumer1); + }); + + it('Delete consumer1', async () => + syncEvents(backend, [ + deleteEvent(ADCSDK.ResourceType.CONSUMER, consumer1Name), + ])); + + it('Dump again (consumer1 should not exist)', async () => { + const result = (await dumpConfiguration( + backend, + )) as ADCSDK.Configuration; + expect(result.consumers).toHaveLength(1); + expect(result.consumers[0]).toMatchObject(consumer2); + }); + + it('Delete consumer2', async () => + syncEvents(backend, [ + deleteEvent(ADCSDK.ResourceType.CONSUMER, consumer2Name), + ])); + + it('Dump again (consumer2 should not exist)', async () => { + const result = (await dumpConfiguration( + backend, + )) as ADCSDK.Configuration; + expect(result.consumers).toHaveLength(0); + }); + }, + ); + + conditionalDescribe(semverCondition(gte, '3.11.0'))( + 'Sync and dump consumers (with credential support)', + () => { + const consumer1Name = 'consumer1'; + const consumer1Key = 'consumer1-key'; + const consumer1Cred = { + name: consumer1Key, + type: 'key-auth', + config: { key: consumer1Key }, + }; + const consumer1 = { + username: consumer1Name, + credentials: [consumer1Cred], + } as ADCSDK.Consumer; + + it('Create consumers', async () => + syncEvents(backend, [ + createEvent(ADCSDK.ResourceType.CONSUMER, consumer1Name, consumer1), + createEvent( + ADCSDK.ResourceType.CONSUMER_CREDENTIAL, + consumer1Key, + consumer1Cred, + consumer1Name, + ), + ])); + + it('Dump', async () => { + const result = (await dumpConfiguration( + backend, + )) as ADCSDK.Configuration; + expect(result.consumers).toHaveLength(1); + expect(result.consumers[0]).toMatchObject(consumer1); + expect(result.consumers[0].credentials).toMatchObject( + consumer1.credentials, + ); + }); + + it('Update consumer credential', async () => { + consumer1.credentials[0].config.key = 'new-key'; + await syncEvents(backend, [ + updateEvent( + ADCSDK.ResourceType.CONSUMER_CREDENTIAL, + consumer1Key, + consumer1Cred, + consumer1Name, + ), + ]); + }); + + it('Dump again (consumer credential updated)', async () => { + const result = (await dumpConfiguration( + backend, + )) as ADCSDK.Configuration; + expect(result.consumers[0]).toMatchObject(consumer1); + expect(result.consumers[0].credentials[0].config.key).toEqual( + 'new-key', + ); + }); + + it('Delete consumer credential', async () => + syncEvents(backend, [ + deleteEvent( + ADCSDK.ResourceType.CONSUMER_CREDENTIAL, + consumer1Key, + consumer1Name, + ), + ])); + + it('Dump again (consumer credential should not exist)', async () => { + const result = (await dumpConfiguration( + backend, + )) as ADCSDK.Configuration; + expect(result.consumers).toHaveLength(1); + expect(result.consumers[0].credentials).toHaveLength(0); + }); + + it('Delete consumer', async () => + syncEvents(backend, [ + deleteEvent(ADCSDK.ResourceType.CONSUMER, consumer1Name), + ])); + + it('Dump again (consumer should not exist)', async () => { + const result = (await dumpConfiguration( + backend, + )) as ADCSDK.Configuration; + expect(result.consumers).toHaveLength(0); + }); + }, + ); +}); From f8f28275202e1e949d0d6f3b6b5d8bfcbb8fe908 Mon Sep 17 00:00:00 2001 From: bzp2010 Date: Tue, 22 Oct 2024 17:12:34 +0800 Subject: [PATCH 07/14] test: fix --- libs/backend-apisix/e2e/support/utils.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libs/backend-apisix/e2e/support/utils.ts b/libs/backend-apisix/e2e/support/utils.ts index 83100439..dd5ac803 100644 --- a/libs/backend-apisix/e2e/support/utils.ts +++ b/libs/backend-apisix/e2e/support/utils.ts @@ -1,5 +1,6 @@ import * as ADCSDK from '@api7/adc-sdk'; import { Listr, SilentRenderer } from 'listr2'; +import semver from 'semver'; import { BackendAPISIX } from '../../src'; @@ -81,3 +82,16 @@ export const deleteEvent = ( ), parentId: parentName ? ADCSDK.utils.generateId(parentName) : undefined, }); + +type cond = boolean | (() => boolean); + +export const conditionalDescribe = (cond: cond) => + cond ? describe : describe.skip; + +export const conditionalIt = (cond: cond) => (cond ? it : it.skip); + +export const semverCondition = ( + op: (v1: string | semver.SemVer, v2: string | semver.SemVer) => boolean, + base: string, + target = semver.coerce(process.env.BACKEND_API7_VERSION) ?? '0.0.0', +) => op(target, base); From 94a93381b36dcb5a5e30c1287d5d5e4cbd8af7f7 Mon Sep 17 00:00:00 2001 From: bzp2010 Date: Tue, 22 Oct 2024 17:16:03 +0800 Subject: [PATCH 08/14] test: fix --- libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts b/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts index c05aba74..67749f29 100644 --- a/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts +++ b/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts @@ -1,7 +1,7 @@ import * as ADCSDK from '@api7/adc-sdk'; import { gte, lt } from 'semver'; -import { BackendAPI7 } from '../../src'; +import { BackendAPISIX } from '../../src'; import { conditionalDescribe, semverCondition } from '../support/utils'; import { createEvent, @@ -12,10 +12,10 @@ import { } from '../support/utils'; describe('Consumer E2E', () => { - let backend: BackendAPI7; + let backend: BackendAPISIX; beforeAll(() => { - backend = new BackendAPI7({ + backend = new BackendAPISIX({ server: process.env.SERVER, token: process.env.TOKEN, tlsSkipVerify: true, From 1e5cdeded24dc876d9c23f1965aa93ee119edc75 Mon Sep 17 00:00:00 2001 From: bzp2010 Date: Tue, 22 Oct 2024 17:27:06 +0800 Subject: [PATCH 09/14] test: fix --- .../e2e/resources/consumer.e2e-spec.ts | 84 +------------------ .../e2e/sync-and-dump-1.e2e-spec.ts | 1 - 2 files changed, 3 insertions(+), 82 deletions(-) diff --git a/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts b/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts index 67749f29..c47c9da5 100644 --- a/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts +++ b/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts @@ -2,6 +2,7 @@ import * as ADCSDK from '@api7/adc-sdk'; import { gte, lt } from 'semver'; import { BackendAPISIX } from '../../src'; +import { server, token } from '../support/constants'; import { conditionalDescribe, semverCondition } from '../support/utils'; import { createEvent, @@ -16,91 +17,12 @@ describe('Consumer E2E', () => { beforeAll(() => { backend = new BackendAPISIX({ - server: process.env.SERVER, - token: process.env.TOKEN, + server, + token, tlsSkipVerify: true, - gatewayGroup: 'default', }); }); - conditionalDescribe(semverCondition(lt, '3.11.0'))( - 'Sync and dump consumers (without credential support)', - () => { - const consumer1Name = 'consumer1'; - const consumer1 = { - username: consumer1Name, - plugins: { - 'key-auth': { - key: consumer1Name, - }, - }, - } as ADCSDK.Consumer; - const consumer2Name = 'consumer2'; - const consumer2 = { - username: consumer2Name, - plugins: { - 'key-auth': { - key: consumer2Name, - }, - }, - } as ADCSDK.Consumer; - - it('Create consumers', async () => - syncEvents(backend, [ - createEvent(ADCSDK.ResourceType.CONSUMER, consumer1Name, consumer1), - createEvent(ADCSDK.ResourceType.CONSUMER, consumer2Name, consumer2), - ])); - - it('Dump', async () => { - const result = (await dumpConfiguration( - backend, - )) as ADCSDK.Configuration; - expect(result.consumers).toHaveLength(2); - expect(result.consumers[0]).toMatchObject(consumer2); - expect(result.consumers[1]).toMatchObject(consumer1); - }); - - it('Update consumer1', async () => { - consumer1.description = 'desc'; - await syncEvents(backend, [ - updateEvent(ADCSDK.ResourceType.CONSUMER, consumer1Name, consumer1), - ]); - }); - - it('Dump again (consumer1 updated)', async () => { - const result = (await dumpConfiguration( - backend, - )) as ADCSDK.Configuration; - expect(result.consumers[0]).toMatchObject(consumer1); - }); - - it('Delete consumer1', async () => - syncEvents(backend, [ - deleteEvent(ADCSDK.ResourceType.CONSUMER, consumer1Name), - ])); - - it('Dump again (consumer1 should not exist)', async () => { - const result = (await dumpConfiguration( - backend, - )) as ADCSDK.Configuration; - expect(result.consumers).toHaveLength(1); - expect(result.consumers[0]).toMatchObject(consumer2); - }); - - it('Delete consumer2', async () => - syncEvents(backend, [ - deleteEvent(ADCSDK.ResourceType.CONSUMER, consumer2Name), - ])); - - it('Dump again (consumer2 should not exist)', async () => { - const result = (await dumpConfiguration( - backend, - )) as ADCSDK.Configuration; - expect(result.consumers).toHaveLength(0); - }); - }, - ); - conditionalDescribe(semverCondition(gte, '3.11.0'))( 'Sync and dump consumers (with credential support)', () => { diff --git a/libs/backend-apisix/e2e/sync-and-dump-1.e2e-spec.ts b/libs/backend-apisix/e2e/sync-and-dump-1.e2e-spec.ts index 9f340a2c..8d854a4e 100644 --- a/libs/backend-apisix/e2e/sync-and-dump-1.e2e-spec.ts +++ b/libs/backend-apisix/e2e/sync-and-dump-1.e2e-spec.ts @@ -21,7 +21,6 @@ describe('Sync and Dump - 1', () => { server, token, tlsSkipVerify: true, - gatewayGroup: 'default', }); }); From 329f5b2561121e3c8143f891963b5d80fee92ef0 Mon Sep 17 00:00:00 2001 From: bzp2010 Date: Tue, 22 Oct 2024 17:31:18 +0800 Subject: [PATCH 10/14] test: fix --- libs/backend-apisix/e2e/assets/docker-compose.yaml | 4 ++-- libs/backend-apisix/e2e/support/utils.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/backend-apisix/e2e/assets/docker-compose.yaml b/libs/backend-apisix/e2e/assets/docker-compose.yaml index 457520a6..d6c80351 100644 --- a/libs/backend-apisix/e2e/assets/docker-compose.yaml +++ b/libs/backend-apisix/e2e/assets/docker-compose.yaml @@ -1,6 +1,6 @@ services: apisix_http: - image: apache/apisix:${BACKEND_APISIX_VERSION:-3.9.0-debian} + image: apache/apisix:${BACKEND_APISIX_IMAGE:-3.9.0-debian} restart: always volumes: - ./apisix_conf/http.yaml:/usr/local/apisix/conf/config.yaml:ro @@ -13,7 +13,7 @@ services: apisix: apisix_mtls: - image: apache/apisix:${BACKEND_APISIX_VERSION:-3.9.0-debian} + image: apache/apisix:${BACKEND_APISIX_IMAGE:-3.9.0-debian} restart: always volumes: - ./apisix_conf/mtls.yaml:/usr/local/apisix/conf/config.yaml:ro diff --git a/libs/backend-apisix/e2e/support/utils.ts b/libs/backend-apisix/e2e/support/utils.ts index dd5ac803..0c5aade4 100644 --- a/libs/backend-apisix/e2e/support/utils.ts +++ b/libs/backend-apisix/e2e/support/utils.ts @@ -93,5 +93,5 @@ export const conditionalIt = (cond: cond) => (cond ? it : it.skip); export const semverCondition = ( op: (v1: string | semver.SemVer, v2: string | semver.SemVer) => boolean, base: string, - target = semver.coerce(process.env.BACKEND_API7_VERSION) ?? '0.0.0', + target = semver.coerce(process.env.BACKEND_APISIX_VERSION) ?? '0.0.0', ) => op(target, base); From a8da5d1730f293e45e84124ca82cf37c806cda70 Mon Sep 17 00:00:00 2001 From: bzp2010 Date: Tue, 22 Oct 2024 17:32:10 +0800 Subject: [PATCH 11/14] fix: metadata remover --- apps/cli/src/command/utils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/cli/src/command/utils.ts b/apps/cli/src/command/utils.ts index ec81ed02..c1248dfb 100644 --- a/apps/cli/src/command/utils.ts +++ b/apps/cli/src/command/utils.ts @@ -253,7 +253,7 @@ export const recursiveRemoveMetadataField = (c: ADCSDK.Configuration) => { if ('metadata' in obj) delete obj.metadata; }; Object.entries(c).forEach(([key, value]) => { - if (['global_rules', 'plugin_metadata', 'consumers'].includes(key)) return; + if (['global_rules', 'plugin_metadata'].includes(key)) return; if (Array.isArray(value)) value.forEach((item) => { removeMetadata(item); @@ -265,6 +265,9 @@ export const recursiveRemoveMetadataField = (c: ADCSDK.Configuration) => { } else if (key === 'consumer_groups') { if ('consumers' in item && Array.isArray(item.consumers)) item.consumers.forEach((c) => removeMetadata(c)); + } else if (key === 'consumers') { + if ('credentials' in item && Array.isArray(item.credentials)) + item.credentials.forEach((c) => removeMetadata(c)); } }); }); From 99420d6dde0a766e85b33eacb04c141b50670c97 Mon Sep 17 00:00:00 2001 From: bzp2010 Date: Tue, 22 Oct 2024 17:32:17 +0800 Subject: [PATCH 12/14] test: fix --- .github/workflows/e2e.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 29d18514..3c68f491 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -14,7 +14,8 @@ jobs: matrix: version: [3.8.1, 3.9.1, 3.10.0, 3.11.0] env: - BACKEND_APISIX_VERSION: ${{ matrix.version }}-debian + BACKEND_APISIX_VERSION: ${{ matrix.version }} + BACKEND_APISIX_IMAGE: ${{ matrix.version }}-debian steps: - uses: actions/checkout@v4 From 2d97e4c6284c967f760f3dbee00b6076faeaf978 Mon Sep 17 00:00:00 2001 From: bzp2010 Date: Tue, 22 Oct 2024 17:52:44 +0800 Subject: [PATCH 13/14] test: fix --- .../e2e/resources/consumer.e2e-spec.ts | 5 +++-- libs/backend-apisix/e2e/support/utils.ts | 12 ++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts b/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts index c47c9da5..dd0fd039 100644 --- a/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts +++ b/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts @@ -23,7 +23,7 @@ describe('Consumer E2E', () => { }); }); - conditionalDescribe(semverCondition(gte, '3.11.0'))( + conditionalDescribe(semverCondition(gte, '0.0.0'))( 'Sync and dump consumers (with credential support)', () => { const consumer1Name = 'consumer1'; @@ -96,7 +96,8 @@ describe('Consumer E2E', () => { backend, )) as ADCSDK.Configuration; expect(result.consumers).toHaveLength(1); - expect(result.consumers[0].credentials).toHaveLength(0); + console.log(result.consumers[0]); + expect(result.consumers[0].credentials).toBeUndefined(); }); it('Delete consumer', async () => diff --git a/libs/backend-apisix/e2e/support/utils.ts b/libs/backend-apisix/e2e/support/utils.ts index 0c5aade4..24dc14ba 100644 --- a/libs/backend-apisix/e2e/support/utils.ts +++ b/libs/backend-apisix/e2e/support/utils.ts @@ -50,7 +50,11 @@ export const createEvent = ( parentName ? `${parentName}.${resourceName}` : resourceName, ), newValue: resource, - parentId: parentName ? ADCSDK.utils.generateId(parentName) : undefined, + parentId: parentName + ? resourceType === ADCSDK.ResourceType.CONSUMER_CREDENTIAL + ? parentName + : ADCSDK.utils.generateId(parentName) + : undefined, }); export const updateEvent = ( @@ -80,7 +84,11 @@ export const deleteEvent = ( : ADCSDK.utils.generateId( parentName ? `${parentName}.${resourceName}` : resourceName, ), - parentId: parentName ? ADCSDK.utils.generateId(parentName) : undefined, + parentId: parentName + ? resourceType === ADCSDK.ResourceType.CONSUMER_CREDENTIAL + ? parentName + : ADCSDK.utils.generateId(parentName) + : undefined, }); type cond = boolean | (() => boolean); From d3beb16915212b8b27132169c6a1d7599f718365 Mon Sep 17 00:00:00 2001 From: bzp2010 Date: Tue, 22 Oct 2024 17:54:50 +0800 Subject: [PATCH 14/14] test: fix --- libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts b/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts index dd0fd039..39dad100 100644 --- a/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts +++ b/libs/backend-apisix/e2e/resources/consumer.e2e-spec.ts @@ -23,7 +23,7 @@ describe('Consumer E2E', () => { }); }); - conditionalDescribe(semverCondition(gte, '0.0.0'))( + conditionalDescribe(semverCondition(gte, '3.11.0'))( 'Sync and dump consumers (with credential support)', () => { const consumer1Name = 'consumer1';