Skip to content

[TT-17786] Backport multi-bundle plugin composition (gRPC + Go) to release-5.13#8505

Open
sedkis wants to merge 2 commits into
release-5.13from
TT-17786/multi-bundle-grpc-go-backport
Open

[TT-17786] Backport multi-bundle plugin composition (gRPC + Go) to release-5.13#8505
sedkis wants to merge 2 commits into
release-5.13from
TT-17786/multi-bundle-grpc-go-backport

Conversation

@sedkis

@sedkis sedkis commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Backports multi-bundle plugin composition to the 5.13 LTS line, scoped to the gRPC and native Go (goplugin) drivers. A custom_middleware_bundle value may now be a comma-separated list of bundle filenames; each bundle is fetched into its own subdirectory and their middleware manifests are merged in declaration order.

The merge machinery is engine-agnostic (it operates on spec.CustomMiddleware hook slices and the filesystem). gRPC dispatches each merged hook to the remote server by hook name and native Go resolves each symbol from its own .so, so neither needs shared-runtime isolation. The goja/JavaScript handler-isolation work from TT-16948 is not included, and the Python and Lua drivers are out of scope (they are broken on the merge path on master).

Ticket: https://tyktech.atlassian.net/browse/TT-17786

What's ported (from master, into gateway/coprocess_bundle.go)

  • parseBundleNames - split on commas, trim, drop empties
  • FetchBundle / FetchBundleByName split
  • loadBundleWithFs dispatcher: 1 name -> legacy loadSingleBundle (5.13 body unchanged); >= 2 names -> merge path
  • loadOneBundleForMerge, loadBundleManifestNamed
  • mergeBundleManifest - concat Pre/Post/PostKeyAuth/Response, single AuthCheck, uniform Driver, first-wins IdExtractor, subdir-prefixed paths
  • bundleSubdirName
  • doc comment on CustomMiddlewareBundle in apidef/api_definitions.go (no new schema field)

Backward compatibility

  • Single-bundle behavior is byte-identical (legacy code path, gated behind >= 2 comma-separated names).
  • The MD5-hashed on-disk cache layout for existing single bundles is unchanged - no re-download or migration on upgrade.
  • No goja, JavaScript, Python, or Lua multi-bundle paths introduced.

Test plan

go test ./gateway/ -run 'Bundle|MultiBundle|Merge' -count=1

  • TestParseBundleNames - trimming, empty/comma/whitespace handling (incl. the untrimmed-single-name edge case)
  • TestMergeBundleManifestAppendsHooks - cross-bundle + within-bundle ordering, subdir path prefixing
  • TestMergeBundleManifestRejectsDuplicateAuthCheck, ...RejectsDriverMismatch, ...EmptyPathNotPrefixed
  • TestBundleSubdirNameStripsExtAndCollapsesSlashes
  • TestMultiBundleMerge_GRPC, TestMultiBundleMerge_GoPlugin - real fetch -> unzip -> DeepVerify -> merge against two registered bundles, asserting both bundles' hooks land with subdir-prefixed paths
  • Existing single-bundle regression (TestBundleLoader, TestLoadBundleWithFs_*) passes unchanged

All pass locally against Redis. Note: the integration tests verify spec-composition of the merged manifest; request-time dispatch through a live gRPC server / real .so is unchanged by this backport and not exercised end-to-end here.

🤖 Generated with Claude Code

Ticket Details

TT-17786
Status In design review
Summary Backport multi-bundle plugin composition (gRPC + Go drivers) to release-5.13

Generated at: 2026-07-21 15:19:46

…rivers

Ports the engine-agnostic bundle merge layer from master onto release-5.13:
parseBundleNames, FetchBundleByName, the loadBundleWithFs dispatcher,
loadSingleBundle (5.13's existing single-bundle body, unchanged),
loadOneBundleForMerge, loadBundleManifestNamed, mergeBundleManifest and
bundleSubdirName. A comma-separated custom_middleware_bundle now fetches each
bundle into its own subdirectory and merges their manifests in order.

Scoped to the gRPC and native Go drivers, which dispatch by remote hook name
and per-.so symbol respectively and need no shared-runtime isolation. The
goja/JavaScript handler-isolation work is deliberately excluded, as are the
Python and Lua drivers (broken on the merge path on master). Single-bundle
behavior and the MD5 on-disk cache layout are unchanged; the merge path is
gated behind two or more comma-separated names.

Co-Authored-By: Claude <noreply@anthropic.com>
@probelabs

probelabs Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

This PR backports the multi-bundle plugin composition feature to the release-5.13 branch, specifically for gRPC and native Go plugins. It enables developers to specify a comma-separated list of bundle filenames in the custom_middleware_bundle field of an API definition. The gateway then fetches each bundle into a separate subdirectory and merges their middleware manifests, allowing for more modular and reusable plugin configurations.

The implementation is fully backward-compatible. When only a single bundle is specified, the existing legacy logic is used, ensuring no change in behavior or on-disk cache layout for current setups. The changes are primarily in gateway/coprocess_bundle.go, with a new comprehensive test suite in gateway/multi_bundle_backport_test.go to validate the new functionality.

Files Changed Analysis

  • apidef/api_definitions.go: A documentation comment for the CustomMiddlewareBundle field was updated to inform users about the new capability of accepting a comma-separated list of bundle names.
  • gateway/coprocess_bundle.go: This file contains the core logic. The main function loadBundleWithFs now acts as a dispatcher. If a single bundle is detected, it calls the original loadSingleBundle function. For multiple bundles, it iterates through them, fetching each into a unique subdirectory and merging their manifests using new helper functions like parseBundleNames, loadOneBundleForMerge, and mergeBundleManifest.
  • gateway/multi_bundle_backport_test.go: A new file containing an extensive test suite for the multi-bundle feature. It includes unit tests for parsing bundle names, manifest merging logic (e.g., hook concatenation, duplicate auth check rejection), and integration tests that simulate fetching and merging bundles for both gRPC and Go plugins.

Architecture & Impact Assessment

  • What this PR accomplishes: It introduces the ability to compose multiple middleware plugins (gRPC and Go) for a single API endpoint. This enhances modularity by allowing teams to combine different functionalities (e.g., a logging plugin and a custom auth plugin) without creating a single monolithic bundle.

  • Key technical changes:

    • The CustomMiddlewareBundle field is now parsed as a potential comma-separated list.
    • A new code path is introduced for multi-bundle processing that fetches each bundle into a dedicated, human-readable subdirectory (e.g., .../api-id/bundle-name-1/, .../api-id/bundle-name-2/).
    • A manifest merging mechanism combines hooks from all specified bundles. It appends list-based hooks (e.g., pre, post), enforces single-owner rules for hooks like auth_check, and ensures driver uniformity.
    • Middleware paths within manifests are automatically prefixed with their bundle's subdirectory to prevent file collisions and ensure correct resolution.
  • Affected system components: The primary impact is on the Gateway's API loading and middleware processing logic. The on-disk layout for cached bundles is also affected when an API uses the multi-bundle feature, changing from a single hashed directory to multiple named subdirectories.

  • Visualization: The new control flow in loadBundleWithFs can be visualized as follows:

graph TD
    A["Start: loadBundleWithFs(spec)"] --> B{Parse spec.CustomMiddlewareBundle};
    B --> C{"Is it a multi-bundle list?"};
    C -- "No (single bundle)" --> D["loadSingleBundle (legacy path)"];
    C -- "Yes (multiple bundles)" --> E[Loop through bundleNames];
    E --> F["loadOneBundleForMerge(name)"];
    F --> G[Fetch & Unpack bundle to subdir];
    G --> H[Load manifest.json];
    H --> I["mergeBundleManifest(spec, manifest)"];
    I --> E;
    E -- "all bundles processed" --> J[Initialize driver with merged middleware];
    J --> K[End];
    D --> K[End];
Loading

Scope Discovery & Context Expansion

  • Broader Scope: This change is confined to the Gateway's plugin loader during the API definition loading phase. While the implementation is scoped to gRPC and Go plugins, the core manifest merging logic is driver-agnostic, which could facilitate future expansion to other plugin types. The PR explicitly excludes Goja/JavaScript, Python, and Lua drivers.
  • Context from Related Files: The entry point for this functionality is within the API loading sequence. The changes manipulate the apidef.MiddlewareSection within the APISpec struct, which holds the final middleware configuration used by the gateway's request processor. The change to the on-disk caching behavior is a key operational difference: for multi-bundle APIs, the cache path becomes more transparent and debuggable (.../<api_id>/<bundle_name_1>/) compared to the previous hashed path (.../<api_id>/<bundle_hash>/).
Metadata
  • Review Effort: 3 / 5
  • Primary Label: feature

Powered by Visor from Probelabs

Last updated: 2026-07-21T15:22:19.929Z | Triggered by: pr_updated | Commit: 42353f8

💡 TIP: You can chat with Visor using /visor ask <your question>

@probelabs

probelabs Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

✅ Architecture Check Passed

No architecture issues found – changes LGTM.

Performance Issues (1)

Severity Location Issue
🟡 Warning gateway/coprocess_bundle.go:521-527
The new multi-bundle loading logic fetches and processes each bundle sequentially within a loop. When an API definition includes multiple bundles that are not yet cached on disk, this results in a series of blocking network and I/O operations. This can significantly increase the time it takes for an API to load or reload, especially with a slow network or a large number of bundles.
💡 SuggestionTo improve performance, consider parallelizing the fetching and unpacking of the bundles. You could use a `sync.WaitGroup` and goroutines to process each bundle concurrently. Once all bundles are fetched and their manifests are loaded into memory, the final step of merging them into the `spec.CustomMiddleware` should remain sequential to prevent race conditions.
\n\n

✅ Architecture Check Passed

No architecture issues found – changes LGTM.

\n\n

Performance Issues (1)

Severity Location Issue
🟡 Warning gateway/coprocess_bundle.go:521-527
The new multi-bundle loading logic fetches and processes each bundle sequentially within a loop. When an API definition includes multiple bundles that are not yet cached on disk, this results in a series of blocking network and I/O operations. This can significantly increase the time it takes for an API to load or reload, especially with a slow network or a large number of bundles.
💡 SuggestionTo improve performance, consider parallelizing the fetching and unpacking of the bundles. You could use a `sync.WaitGroup` and goroutines to process each bundle concurrently. Once all bundles are fetched and their manifests are loaded into memory, the final step of merging them into the `spec.CustomMiddleware` should remain sequential to prevent race conditions.
\n\n ### Quality Issues (1)
Severity Location Issue
🟡 Warning gateway/coprocess_bundle.go:522-524
The check `if name == ""` is unreachable. The `parseBundleNames` function, which produces the `bundleNames` slice, explicitly filters out empty strings, so this condition will never be met. This constitutes dead code.
💡 SuggestionRemove the redundant `if name == ""` block to eliminate the dead code and simplify the loop.

Powered by Visor from Probelabs

Last updated: 2026-07-21T15:21:51.196Z | Triggered by: pr_updated | Commit: 42353f8

💡 TIP: You can chat with Visor using /visor ask <your question>

@sentinelone-cnapp-eu1

Copy link
Copy Markdown

SentinelOne CNS Hardcoded Secret Detector
✅ Congratulations, your code is safe

SentinelOne CNS is a cloud-agnostic, agentless CSPM & CWPP solution that continuously detects and prevents vulnerabilities that have the highest probability of being exploited in Azure, AWS, Google Cloud, and Kubernetes.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Passed Quality Gate passed

Issues
1 New issue
0 Accepted issues

Measures
0 Security Hotspots
93.7% Coverage on New Code
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

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.

1 participant