Skip to content

fix(cli): enforce exact label selector matching#418

Closed
LiteSun wants to merge 4 commits intomainfrom
sy/fix-sync
Closed

fix(cli): enforce exact label selector matching#418
LiteSun wants to merge 4 commits intomainfrom
sy/fix-sync

Conversation

@LiteSun
Copy link
Contributor

@LiteSun LiteSun commented Mar 13, 2026

Changes

  • Add parseLabelSelector utility with strict key=value validation
  • Replace fillLabels() with filterConfiguration() in sync.ts and load_local.ts
  • Remove qs and @types/qs dependencies

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible

Note: This is a behavioral change. Previously, resources without the
selector label would still be synced (with the label injected). Now, only
resources that already carry the matching label will be synced.

Summary by CodeRabbit

Release Notes

  • Refactor

    • Removed external dependency while maintaining label selector parsing functionality.
    • Enhanced label-based filtering for local configurations with improved filtering logic.
  • Tests

    • Added unit tests to verify label-based resource filtering behavior.

@coderabbitai
Copy link

coderabbitai bot commented Mar 13, 2026

📝 Walkthrough

Walkthrough

This PR removes the qs package dependency and replaces label filtering with a new parseLabelSelector function that validates label selectors without external dependencies, along with a filterConfiguration utility for label-based config filtering.

Changes

Cohort / File(s) Summary
Package Dependencies
apps/cli/package.json
Removed qs and @types/qs packages from dependencies.
Label Selector Parsing
apps/cli/src/command/helper.ts
Introduced parseLabelSelector function to validate and parse comma-separated label selector strings (key=value format) into key-value maps, eliminating the qs dependency.
Configuration Filtering
apps/cli/src/command/utils.ts, apps/cli/src/tasks/load_local.ts, apps/cli/src/server/sync.ts
Added filterConfiguration function to filter configurations by label selectors, replacing the previous fillLabels approach with a tuple-returning design.
Test Coverage
apps/cli/src/command/utils.spec.ts, apps/cli/src/tasks/load_local.spec.ts
Added unit tests validating parseLabelSelector error handling, format validation, and filterConfiguration label-based filtering behavior with exact key matching.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 No more qs to parse the day,
New filters hop in every way,
Labels sorted, clean and tight,
Configuration feels just right!
Hops away with joy 🌿✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(cli): enforce exact label selector matching' directly reflects the main change—replacing fillLabels with filterConfiguration to enforce strict label matching, removing the qs dependency, and adding parseLabelSelector validation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sy/fix-sync
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the CLI/server label-selector behavior to require exact label key matching by filtering configurations (instead of mutating resources to “fill” labels), and tightens CLI parsing to key=value (comma-separated) only.

Changes:

  • Replace fillLabels usage with filterConfiguration when loading local config (CLI task) and when preparing local config in the sync server handler.
  • Add parseLabelSelector (Commander arg parser) to enforce key=value parsing only.
  • Add/extend tests to verify exact label key matching and selector parsing validation.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
apps/cli/src/tasks/load_local.ts Switch local label-selector handling from label injection to config filtering.
apps/cli/src/tasks/load_local.spec.ts Add integration-style test ensuring local load filters by exact label key.
apps/cli/src/server/sync.ts Filter local config by label-selector before lint/diff; remove fillLabels.
apps/cli/src/command/utils.spec.ts Add unit tests for exact key-match filtering and strict selector parsing.
apps/cli/src/command/helper.ts Replace qs parsing with strict key=value label selector parser.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@LiteSun LiteSun marked this pull request as ready for review March 13, 2026 08:56
@LiteSun LiteSun requested a review from bzp2010 as a code owner March 13, 2026 08:56
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/cli/src/tasks/load_local.spec.ts (1)

10-49: LGTM!

The test properly validates the new exact label key matching behavior with appropriate setup/teardown using try/finally. The test case clearly demonstrates that resources with different label keys (e.g., appname vs name) are correctly filtered out.

Consider adding tests for edge cases:

  • Empty label selector (should return all resources)
  • Multiple label key requirements (all must match)
  • Resources without any labels (should be filtered out)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/cli/src/tasks/load_local.spec.ts` around lines 10 - 49, Add three
additional unit tests in load_local.spec.ts alongside the existing "filters
local resources by exact label key match" test to cover edge cases: (1) an
"empty label selector" test that calls LoadLocalConfigurationTask with an empty
selector (e.g., {}) and asserts ctx.local.services returns all services from the
adc.yaml, (2) a "multiple label key requirements" test that supplies a selector
with two keys (e.g., { name: 'x', env: 'prod' }) and asserts only resources
matching both labels are returned by LoadLocalConfigurationTask, and (3) a
"resources without any labels" test that includes a service without a labels
field in the yaml and asserts it is filtered out by LoadLocalConfigurationTask;
reuse the mkdtempSync/writeFileSync/try...finally cleanup pattern and assert
against ctx.local.services as in the existing spec.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/cli/src/server/sync.ts`:
- Around line 59-62: The call site in sync.ts unconditionally invokes
filterConfiguration on local with task.opts.labelSelector; add the same guard
used in load_local.ts so you only call filterConfiguration when
task.opts.labelSelector is set/non-empty (e.g., check task.opts.labelSelector
truthiness or length) before assigning [local] = filterConfiguration(...);
update both places in sync.ts where filterConfiguration is used to match the
guarded pattern and ensure local remains unchanged if the selector is absent.

---

Nitpick comments:
In `@apps/cli/src/tasks/load_local.spec.ts`:
- Around line 10-49: Add three additional unit tests in load_local.spec.ts
alongside the existing "filters local resources by exact label key match" test
to cover edge cases: (1) an "empty label selector" test that calls
LoadLocalConfigurationTask with an empty selector (e.g., {}) and asserts
ctx.local.services returns all services from the adc.yaml, (2) a "multiple label
key requirements" test that supplies a selector with two keys (e.g., { name:
'x', env: 'prod' }) and asserts only resources matching both labels are returned
by LoadLocalConfigurationTask, and (3) a "resources without any labels" test
that includes a service without a labels field in the yaml and asserts it is
filtered out by LoadLocalConfigurationTask; reuse the
mkdtempSync/writeFileSync/try...finally cleanup pattern and assert against
ctx.local.services as in the existing spec.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d4cf46dc-c944-45c8-8773-c8b6dc9629a9

📥 Commits

Reviewing files that changed from the base of the PR and between 9615f61 and 4be87bf.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • apps/cli/package.json
  • apps/cli/src/command/helper.ts
  • apps/cli/src/command/utils.spec.ts
  • apps/cli/src/server/sync.ts
  • apps/cli/src/tasks/load_local.spec.ts
  • apps/cli/src/tasks/load_local.ts
💤 Files with no reviewable changes (1)
  • apps/cli/package.json

Comment on lines +59 to +62
[local] = filterConfiguration(
local,
task.opts.labelSelector,
) as [ADCSDK.InternalConfiguration, ADCSDK.Configuration];
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 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 10

Repository: 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 f

Repository: 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 15

Repository: 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.ts

Repository: api7/adc

Length of output: 4301


Add guards before calling filterConfiguration for consistency with load_local.ts.

The code unconditionally calls filterConfiguration even when task.opts.labelSelector is undefined or empty. While labelFilter has a default value (rules = {}), this results in unnecessary iteration and is inconsistent with the guard pattern in load_local.ts (lines 101-104). Add the same guard to both calls in sync.ts.

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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[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];
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/cli/src/server/sync.ts` around lines 59 - 62, The call site in sync.ts
unconditionally invokes filterConfiguration on local with
task.opts.labelSelector; add the same guard used in load_local.ts so you only
call filterConfiguration when task.opts.labelSelector is set/non-empty (e.g.,
check task.opts.labelSelector truthiness or length) before assigning [local] =
filterConfiguration(...); update both places in sync.ts where
filterConfiguration is used to match the guarded pattern and ensure local
remains unchanged if the selector is absent.

task: async () => {
// Merge label selectors from CLI inputs to each resource
fillLabels(ctx.local, labelSelector);
[ctx.local] = filterConfiguration(ctx.local, labelSelector);
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

services: [
{
name: 'match-by-name',
labels: { name: 'yanglao_wx_pgm' },
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right. I'll replace yanglao_wx_pgm with neutral placeholder values (e.g., test-service, foo) across all test cases.

verbose: number;
}

export const parseLabelSelector = (
Copy link
Collaborator

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?

Copy link
Contributor Author

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 qs supports nested object notation—for example, a[b]=c is parsed as { a: { b: 'c' } } rather than { 'a[b]': 'c' }.
If a label value happens to contain [ or ] characters, qs would produce
an unexpected nested structure instead of a flat string value.

@LiteSun
Copy link
Contributor Author

LiteSun commented Mar 17, 2026

After discussing with @bzp2010

This is the expected behavior.

Each label selector item is written to the local configuration and synchronized to the gateway.

The input accepted by ADC is your desired state for the remote configuration; what you input is what the remote state should be. If you expect a certain resource not to be synchronized from local to remote, you should not include it as input during the sync process.

Auto-filling filtered tag keys and values ensures that when synchronizing again using the same tag filters as the current setup, the scope of covered resources remains consistent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants