-
Notifications
You must be signed in to change notification settings - Fork 12
fix(cli): enforce exact label selector matching #418
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,7 +7,6 @@ import { omit, toString } from 'lodash'; | |||||||||||||||||||||
| import { lastValueFrom, toArray } from 'rxjs'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import { | ||||||||||||||||||||||
| fillLabels, | ||||||||||||||||||||||
| filterConfiguration, | ||||||||||||||||||||||
| filterResourceType, | ||||||||||||||||||||||
| loadBackend, | ||||||||||||||||||||||
|
|
@@ -52,11 +51,15 @@ export const syncHandler: RequestHandler< | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| // load local configuration and validate it | ||||||||||||||||||||||
| //TODO: merged with the listr task | ||||||||||||||||||||||
| const local = filterResourceType( | ||||||||||||||||||||||
| let local = filterResourceType( | ||||||||||||||||||||||
| task.config, | ||||||||||||||||||||||
| task.opts.includeResourceType, | ||||||||||||||||||||||
| task.opts.excludeResourceType, | ||||||||||||||||||||||
| ) as ADCSDK.InternalConfiguration; | ||||||||||||||||||||||
| [local] = filterConfiguration( | ||||||||||||||||||||||
| local, | ||||||||||||||||||||||
| task.opts.labelSelector, | ||||||||||||||||||||||
| ) as [ADCSDK.InternalConfiguration, ADCSDK.Configuration]; | ||||||||||||||||||||||
|
Comment on lines
+59
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check if filterConfiguration handles undefined/empty rules gracefully
ast-grep --pattern $'export const filterConfiguration = ($_, $_, $_) => {
$$$
}'Repository: api7/adc Length of output: 34 🏁 Script executed: # Find filterConfiguration function definition
rg 'export.*filterConfiguration\s*=' -A 10Repository: api7/adc Length of output: 900 🏁 Script executed: # Read sync.ts file around the flagged lines
cat -n apps/cli/src/server/sync.ts | sed -n '50,115p'Repository: api7/adc Length of output: 2463 🏁 Script executed: # Find load_local.ts and check the guard pattern
find . -name 'load_local.ts' -type fRepository: api7/adc Length of output: 86 🏁 Script executed: # Get full filterConfiguration implementation
cat -n apps/cli/src/command/utils.ts | grep -A 30 'export const filterConfiguration'Repository: api7/adc Length of output: 1221 🏁 Script executed: # Check the labelFilter function definition and implementation
rg 'export.*labelFilter\s*=|function labelFilter' -A 15Repository: api7/adc Length of output: 34 🏁 Script executed: # Read load_local.ts to see the guard pattern mentioned in the review
cat -n apps/cli/src/tasks/load_local.tsRepository: api7/adc Length of output: 4301 Add guards before calling The code unconditionally calls Suggested fix- [local] = filterConfiguration(
- local,
- task.opts.labelSelector,
- ) as [ADCSDK.InternalConfiguration, ADCSDK.Configuration];
+ if (task.opts.labelSelector && Object.keys(task.opts.labelSelector).length > 0) {
+ [local] = filterConfiguration(
+ local,
+ task.opts.labelSelector,
+ ) as [ADCSDK.InternalConfiguration, ADCSDK.Configuration];
+ }Apply the same guard to line 107: - [remote] = filterConfiguration(remote, task.opts.labelSelector);
+ if (task.opts.labelSelector && Object.keys(task.opts.labelSelector).length > 0) {
+ [remote] = filterConfiguration(remote, task.opts.labelSelector);
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| if (task.opts.lint) { | ||||||||||||||||||||||
| const result = check(local); | ||||||||||||||||||||||
| if (!result.success) | ||||||||||||||||||||||
|
|
@@ -65,7 +68,6 @@ export const syncHandler: RequestHandler< | |||||||||||||||||||||
| errors: result.error.issues, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| fillLabels(local, task.opts.labelSelector); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // load and filter remote configuration | ||||||||||||||||||||||
| const backend = loadBackend(task.opts.backend, { | ||||||||||||||||||||||
|
|
@@ -134,10 +136,10 @@ export const syncHandler: RequestHandler< | |||||||||||||||||||||
| task.opts.backend !== 'apisix-standalone' | ||||||||||||||||||||||
| ? generateOutput([results, successes, faileds]) | ||||||||||||||||||||||
| : generateOutputForAPISIXStandalone(diff, [ | ||||||||||||||||||||||
| results, | ||||||||||||||||||||||
| successes, | ||||||||||||||||||||||
| faileds, | ||||||||||||||||||||||
| ]); | ||||||||||||||||||||||
| results, | ||||||||||||||||||||||
| successes, | ||||||||||||||||||||||
| faileds, | ||||||||||||||||||||||
| ]); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| logger.log({ | ||||||||||||||||||||||
| level: 'debug', | ||||||||||||||||||||||
|
|
@@ -173,7 +175,7 @@ const formatAxiosResponse = (axiosResponse: AxiosResponse) => ({ | |||||||||||||||||||||
| method: axiosResponse.config.method, | ||||||||||||||||||||||
| headers: | ||||||||||||||||||||||
| ((axiosResponse.config.headers['X-API-KEY'] = '*****'), | ||||||||||||||||||||||
| axiosResponse.config.headers), | ||||||||||||||||||||||
| axiosResponse.config.headers), | ||||||||||||||||||||||
| data: axiosResponse.config.data, | ||||||||||||||||||||||
| }, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import * as ADCSDK from '@api7/adc-sdk'; | ||
| import { dump } from 'js-yaml'; | ||
| import { Listr } from 'listr2'; | ||
| import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; | ||
| import { tmpdir } from 'node:os'; | ||
| import path from 'node:path'; | ||
|
|
||
| import { LoadLocalConfigurationTask } from './load_local'; | ||
|
|
||
| describe('LoadLocalConfigurationTask', () => { | ||
| it('filters local resources by exact label key match', async () => { | ||
| const dir = mkdtempSync(path.join(tmpdir(), 'adc-load-local-')); | ||
| const file = path.join(dir, 'adc.yaml'); | ||
|
|
||
| writeFileSync( | ||
| file, | ||
| dump({ | ||
| services: [ | ||
| { | ||
| name: 'match-by-name', | ||
| labels: { name: 'yanglao_wx_pgm' }, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this name signify? Who are you representing in submitting this PR? Please ensure you always use neutral, non-specific terminology for testing purposes.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right. I'll replace |
||
| }, | ||
| { | ||
| name: 'should-not-match-appname', | ||
| labels: { appname: 'yanglao_wx_pgm' }, | ||
| }, | ||
| ], | ||
| } satisfies ADCSDK.Configuration), | ||
| 'utf8', | ||
| ); | ||
|
|
||
| const ctx: { local: ADCSDK.Configuration } = { local: {} }; | ||
|
|
||
| try { | ||
| await new Listr( | ||
| [LoadLocalConfigurationTask([file], { name: 'yanglao_wx_pgm' })], | ||
| { ctx }, | ||
| ).run(); | ||
| } finally { | ||
| rmSync(dir, { recursive: true, force: true }); | ||
| } | ||
|
|
||
| expect(ctx.local.services).toEqual([ | ||
| { | ||
| name: 'match-by-name', | ||
| labels: { name: 'yanglao_wx_pgm' }, | ||
| }, | ||
| ]); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,7 @@ import { ListrTask } from 'listr2'; | |
| import path from 'node:path'; | ||
|
|
||
| import { | ||
| fillLabels, | ||
| filterConfiguration, | ||
| filterResourceType, | ||
| mergeKVConfigurations, | ||
| recursiveReplaceEnvVars, | ||
|
|
@@ -100,8 +100,7 @@ export const LoadLocalConfigurationTask = ( | |
| title: 'Filter configuration', | ||
| enabled: !!labelSelector && Object.keys(labelSelector).length > 0, | ||
| task: async () => { | ||
| // Merge label selectors from CLI inputs to each resource | ||
| fillLabels(ctx.local, labelSelector); | ||
| [ctx.local] = filterConfiguration(ctx.local, labelSelector); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand what you're trying to do. The original design was intentional—label filtering always occurs remotely. For local resources, the ADC will only populate the label fields you want to filter, not perform the filtering itself. I believe this stance remains unchanged. If the label filter fails to accurately search and filter resources remotely, you should improve the specific backend and require the backend API implementation to ensure its filters are precise rather than fuzzy. |
||
| }, | ||
| }, | ||
| ], | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the difference from the old implementation based on qs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main difference is that
qssupports nested object notation—for example,a[b]=cis parsed as{ a: { b: 'c' } }rather than{ 'a[b]': 'c' }.If a label value happens to contain
[or]characters,qswould producean unexpected nested structure instead of a flat string value.