Conversation
…ed names
Following the `sangfor_sip_v92` precedent, declare the deployed product
version on every supported third-party API plugin (top-level `version:`
plus `defaults.product_version:` for backward compatibility) and rename
the plugin directories to include a `_v<dot-replaced-version>` suffix so
the on-disk layout reflects the targeted release.
Versions captured:
- qingteng -> qingteng_v3_4_1_66
- sangfor_xdr -> sangfor_xdr_v2_2
- ngtip_api -> ngtip_v5_1_5
- onesig -> onesig_v2_5_3_D20260321
- onesec -> onesec_v2_8_2
- tdp_api -> tdp_v3_3_10
- skyeye_api -> skyeye_v4_0_14_0_SP2
`service_id` (and the `provider:` references inside each tool YAML) is
intentionally left unchanged so existing `flocks.json` configs and
`{secret:*_api_key}` references keep working without migration.
Tests that hard-code the old plugin paths are updated accordingly.
Made-with: Cursor
Allow multiple versions of the same API product to coexist in flocks.json under distinct `<service_id>_v<version>` storage keys, so updating a plugin to a new version no longer overwrites the previous version's credentials. Core changes: - Add `flocks/config/api_service_versioning.py` with derive/discover helpers, bidirectional legacy<->storage_key resolution, shadowing detection, and an idempotent copy-only migration that backs up flocks.json before its first write. - Promote `info.provider` to the storage_key in `tool_loader` while preserving the unversioned `service_id` on the Tool instance for legacy lookups. - Make `ConfigWriter.get_api_service_raw` version-aware: prefer the versioned shadow when an unversioned id is requested, fall back to the legacy id when a storage_key has no entry yet (covers partially-upgraded environments and isolated tests). Warn on asymmetric writes that target a shadowed legacy id. - Run migration in the lifespan startup before `ToolRegistry.init` so freshly loaded tools observe the post-migration layout. - Hide shadowed legacy entries from the API service listing endpoint to avoid duplicate rows in the WebUI after migration. - Make the provider-route metadata loader resolve a `provider_id` against each candidate `_provider.yaml`'s derived storage_key, not just its directory name. Without this, plugins whose dir was renamed to a shorter form (e.g. `tdp_v3_3_10` for service_id `tdp_api`) returned no metadata to the WebUI. Tests: - New `tests/config/test_api_service_versioning.py` (41 cases) covers derivation, descriptor discovery, legacy resolution, shadowing, migration idempotency + backup, ConfigWriter fallback (incl. the `"api_services": null` defensive path), and the regression where the metadata loader must accept storage_keys whose directory name differs. - Existing tool-plugin tests refreshed for the new `info.provider` values (`tdp_api_v3_3_10`, `qingteng_v3_4_1_66`, etc.) and the versioned plugin directory paths. Made-with: Cursor
…versioning Centralize the storage-key resolution logic that used to be inlined in ConfigWriter and route handlers. Domain rules now live in a single module with a shorter, more intuitive name. Module rename: - flocks/config/api_service_versioning.py -> flocks/config/api_versioning.py (and the matching tests/config/test_*.py). Aligns length with sibling config.py / config_writer.py. New helpers in api_versioning: - resolve_api_service(service_id, services): three-step shadow / direct / legacy lookup; the only place this rule lives. - warn_if_shadowing_legacy(service_id, services): structured warning when a write targets a legacy id whose versioned shadow already exists. Slimmed call sites: - ConfigWriter.get_api_service_raw shrinks from ~45 lines to ~12, just reads flocks.json then delegates to resolve_api_service. Falls back to a plain dict lookup if the import fails so a versioning bug cannot break credential reads. - ConfigWriter.set_api_service uses setdefault and delegates the shadow-warning to the new helper. Log key renamed to api_service.write.shadowed_legacy. - _load_provider_yaml_metadata in server/routes/provider.py now reuses discover_api_service_descriptors instead of reimplementing the plugin directory walk. Drops ~45 lines and removes the duplicate matching logic between provider.py and api_versioning.py. Cleanups: - Drop is_legacy_shadowed (one-line wrapper around shadowed_legacy_ids with no production callers); tests use the batch API directly. - Tighten _extract_version's tail return. - Drop the verbose null-handling commentary in get_api_service_raw; a single isinstance check covers null / non-dict garbage. Net diff: +125 / -185 across 6 files, 41 versioning tests + 234 directly affected tests still pass, no lint regressions. Made-with: Cursor
xiami762
approved these changes
Apr 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
refactor(api): centralize version-aware lookup; rename module to api_versioning
Centralize the storage-key resolution logic that used to be inlined in
ConfigWriter and route handlers. Domain rules now live in a single
module with a shorter, more intuitive name.
Module rename:
(and the matching tests/config/test_*.py). Aligns length with sibling
config.py / config_writer.py.
New helpers in api_versioning:
legacy lookup; the only place this rule lives.
a write targets a legacy id whose versioned shadow already exists.
Slimmed call sites:
reads flocks.json then delegates to resolve_api_service. Falls back to
a plain dict lookup if the import fails so a versioning bug cannot
break credential reads.
shadow-warning to the new helper. Log key renamed to
api_service.write.shadowed_legacy.
discover_api_service_descriptors instead of reimplementing the plugin
directory walk. Drops ~45 lines and removes the duplicate matching
logic between provider.py and api_versioning.py.
Cleanups:
with no production callers); tests use the batch API directly.
single isinstance check covers null / non-dict garbage.