Skip to content
This repository was archived by the owner on Aug 4, 2026. It is now read-only.

OSAC-1755: Add capabilities intersection to NetworkClass reconciler - #406

Open
SiddarthR56 wants to merge 1 commit into
osac-project:mainfrom
SiddarthR56:osac-1755
Open

OSAC-1755: Add capabilities intersection to NetworkClass reconciler#406
SiddarthR56 wants to merge 1 commit into
osac-project:mainfrom
SiddarthR56:osac-1755

Conversation

@SiddarthR56

@SiddarthR56 SiddarthR56 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • NetworkClass capabilities now synchronize automatically from configured network managers.
    • Capabilities refresh periodically and respond immediately to relevant configuration changes.
    • Added configurable sync interval, defaulting to 5 minutes.
  • Bug Fixes

    • Network manager registration labels now use valid, consistent Kubernetes label keys.
    • NetworkClass processing continues when individual manager resolutions fail.
  • Documentation

    • Added configuration guidance for capability synchronization and network manager labels.

@openshift-ci-robot

openshift-ci-robot commented Aug 2, 2026

Copy link
Copy Markdown

@SiddarthR56: This pull request references OSAC-1755 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Assisted-by: Cursor/Claude

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci

openshift-ci Bot commented Aug 2, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: SiddarthR56
Once this PR has been reviewed and has the lgtm label, please assign vladikr for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR adds NetworkClass capability synchronization from resolved network-manager capabilities. It introduces ConfigMap-triggered and periodic reconciliation, configurable sync timing, updated manager discovery labels, and tests for capability computation, pagination, updates, and resolution errors.

Changes

NetworkClass capability synchronization

Layer / File(s) Summary
Manager discovery labels and sync configuration
.claude/rules/configuration.md, charts/operator/..., config/samples/..., pkg/networkmanager/..., cmd/main.go
Manager registration labels use single-slash keys. Helm values and the deployment expose a configurable five-minute default synchronization interval.
Capability resolution and reconciliation
pkg/dispatcher/resolver.go, internal/controller/networkclass_capabilities_controller.go, internal/controller/*_test.go
The controller resolves manager capabilities, computes intersections, updates changed NetworkClasses, handles pagination and resolution errors, and performs periodic resynchronization.
Networking controller registration
cmd/main.go
Networking setup conditionally creates discovery, registers the capabilities reconciler, and adds the periodic runnable when the required gRPC connection and namespace are available.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ConfigMap
  participant NetworkClassCapabilitiesReconciler
  participant dispatcher.Resolver
  participant FulfillmentService
  participant NetworkClassesClient
  ConfigMap->>NetworkClassCapabilitiesReconciler: trigger resynchronization
  NetworkClassCapabilitiesReconciler->>NetworkClassesClient: list NetworkClasses
  NetworkClassCapabilitiesReconciler->>dispatcher.Resolver: resolve manager capabilities
  dispatcher.Resolver->>FulfillmentService: query manager capabilities
  FulfillmentService-->>dispatcher.Resolver: return capabilities
  dispatcher.Resolver-->>NetworkClassCapabilitiesReconciler: return resolved capabilities
  NetworkClassCapabilitiesReconciler->>NetworkClassesClient: update changed capabilities
Loading

Possibly related PRs

Suggested labels: lgtm, approved

Suggested reviewers: danmanor

🚥 Pre-merge checks | ✅ 11
✅ Passed checks (11 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding capability intersection to the NetworkClass reconciler.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
No-Hardcoded-Secrets ✅ Passed Added code contains no API keys, tokens, passwords, private keys, credential URLs, or long encoded secret literals; auth values remain Secret/token-file references.
No-Weak-Crypto ✅ Passed The PR patch adds no MD5, SHA1, DES, RC4, Blowfish, ECB, or crypto APIs. Its equality checks compare capability booleans; the existing FNV hash is unchanged and non-cryptographic.
No-Injection-Vectors ✅ Passed Changed files contain no listed injection constructs; external interval input uses time.ParseDuration, and ConfigMap capabilities use fixed-value validation.
Container-Privileges ✅ Passed PR additions contain no privileged, host namespace, SYS_ADMIN, or privilege-escalation settings; deployments use runAsNonRoot, drop ALL capabilities, and the image uses USER 1001.
No-Sensitive-Data-In-Logs ✅ Passed New logs contain system-defined NetworkClass IDs, capability booleans, and manager-resolution errors; no passwords, tokens, API keys, PII, hostnames, or customer data are logged.
Ai-Attribution ✅ Passed AI use is disclosed by the PR and HEAD commit with the valid Assisted-by: Cursor/Claude trailer; no Co-Authored-By AI marker is present.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/rules/configuration.md:
- Around line 27-28: Update the ConfigMap changes entry to state that
synchronization is active only when both the fulfillment-service gRPC connection
is configured and the networking namespace is non-empty, matching the gate in
cmd/main.go.

In `@cmd/main.go`:
- Around line 581-584: Update the GetEnvWithDefault call for
envNetworkClassSyncInterval to pass a validator that accepts only strictly
positive durations, rejecting zero and negative values before syncInterval
reaches NewNetworkClassCapabilitiesSyncRunnable. Reuse the existing duration
parsing/validation conventions or helper available in the codebase.
- Around line 572-576: Instantiate privatev1.NewNetworkClassesClient(grpcConn)
once before creating the resolver, assign it to a shared client variable, and
pass that same instance to both dispatcher.NewResolver and
controller.NewNetworkClassCapabilitiesReconciler.

In `@internal/controller/networkclass_capabilities_controller_test.go`:
- Around line 84-99: Opaque protobuf messages are being instantiated with
unsupported non-empty struct literals. In
internal/controller/networkclass_capabilities_controller_test.go at lines 84-99,
117-121, 144-148, 160-210, and 225-229, replace NetworkClassCapabilities,
NetworkClass, and NetworkClassesListResponse literals with their corresponding
_builder{...}.Build() calls; in
internal/controller/dispatcher_resolver_helpers_test.go at lines 76-92, do the
same for NetworkClassesListResponse, NetworkClassesGetResponse, and
NetworkClassesUpdateResponse. Preserve all existing field values and nesting.

In `@internal/controller/networkclass_capabilities_controller.go`:
- Around line 251-267: Prevent concurrent resyncAll executions by adding shared
synchronization between networkClassCapabilitiesSyncRunnable.Start and the
reconciler’s ConfigMap-triggered resync path. Gate each full resync with a mutex
or in-progress guard, ensuring a periodic tick skips or waits when another
resync is active while preserving existing error logging and cancellation
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 31ccf270-f2b5-4ed0-8644-224b89974ed3

📥 Commits

Reviewing files that changed from the base of the PR and between 0d91105 and 24c6657.

📒 Files selected for processing (12)
  • .claude/rules/configuration.md
  • charts/operator/templates/deployment.yaml
  • charts/operator/templates/network-managers.yaml
  • charts/operator/values.yaml
  • cmd/main.go
  • config/samples/network-manager-fabric-netris.yaml
  • internal/controller/dispatcher_resolver_helpers_test.go
  • internal/controller/networkclass_capabilities_controller.go
  • internal/controller/networkclass_capabilities_controller_test.go
  • pkg/dispatcher/resolver.go
  • pkg/networkmanager/doc.go
  • pkg/networkmanager/types.go

Comment on lines +27 to +28
ConfigMap changes (default: 5m). Only active when the fulfillment-service gRPC
connection is configured.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the namespace prerequisite too.

This entry states the sync is "Only active when the fulfillment-service gRPC connection is configured." The actual gate in cmd/main.go also requires a non-empty networking namespace (grpcConn != nil && networkingNamespace != ""). Mention both prerequisites so operators do not troubleshoot a silently inactive sync when only the gRPC connection is set.

📝 Proposed doc fix
 - `OSAC_NETWORK_CLASS_SYNC_INTERVAL` — periodic full resync interval for NetworkClass
   capabilities intersection, in addition to the immediate resync triggered by manager
-  ConfigMap changes (default: 5m). Only active when the fulfillment-service gRPC
-  connection is configured.
+  ConfigMap changes (default: 5m). Only active when the fulfillment-service gRPC
+  connection and `OSAC_NETWORKING_NAMESPACE` are both configured.
📝 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
ConfigMap changes (default: 5m). Only active when the fulfillment-service gRPC
connection is configured.
ConfigMap changes (default: 5m). Only active when the fulfillment-service gRPC
connection and `OSAC_NETWORKING_NAMESPACE` are both configured.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/rules/configuration.md around lines 27 - 28, Update the ConfigMap
changes entry to state that synchronization is active only when both the
fulfillment-service gRPC connection is configured and the networking namespace
is non-empty, matching the gate in cmd/main.go.

Comment thread cmd/main.go
Comment on lines +572 to +576
resolver := dispatcher.NewResolver(privatev1.NewNetworkClassesClient(grpcConn), disc)

ncReconciler := controller.NewNetworkClassCapabilitiesReconciler(
privatev1.NewNetworkClassesClient(grpcConn), resolver, networkingNamespace,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse a single NetworkClassesClient instance.

privatev1.NewNetworkClassesClient(grpcConn) is called twice: once for resolver and once for ncReconciler. Both wrap the same grpcConn, so a single client instance can be shared.

♻️ Proposed refactor
-	resolver := dispatcher.NewResolver(privatev1.NewNetworkClassesClient(grpcConn), disc)
+	ncClient := privatev1.NewNetworkClassesClient(grpcConn)
+	resolver := dispatcher.NewResolver(ncClient, disc)
 
 	ncReconciler := controller.NewNetworkClassCapabilitiesReconciler(
-		privatev1.NewNetworkClassesClient(grpcConn), resolver, networkingNamespace,
+		ncClient, resolver, networkingNamespace,
 	)
📝 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
resolver := dispatcher.NewResolver(privatev1.NewNetworkClassesClient(grpcConn), disc)
ncReconciler := controller.NewNetworkClassCapabilitiesReconciler(
privatev1.NewNetworkClassesClient(grpcConn), resolver, networkingNamespace,
)
ncClient := privatev1.NewNetworkClassesClient(grpcConn)
resolver := dispatcher.NewResolver(ncClient, disc)
ncReconciler := controller.NewNetworkClassCapabilitiesReconciler(
ncClient, resolver, networkingNamespace,
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/main.go` around lines 572 - 576, Instantiate
privatev1.NewNetworkClassesClient(grpcConn) once before creating the resolver,
assign it to a shared client variable, and pass that same instance to both
dispatcher.NewResolver and controller.NewNetworkClassCapabilitiesReconciler.

Comment thread cmd/main.go
Comment on lines +581 to +584
syncInterval := helpers.GetEnvWithDefault(envNetworkClassSyncInterval, defaultNetworkClassSyncInterval)
if err := localMgr.Add(controller.NewNetworkClassCapabilitiesSyncRunnable(ncReconciler, syncInterval)); err != nil {
return fmt.Errorf("networkclass capabilities sync runnable: %w", err)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate that the sync interval is positive before use.

helpers.GetEnvWithDefault(envNetworkClassSyncInterval, defaultNetworkClassSyncInterval) accepts a validator argument, but none is passed here. time.ParseDuration accepts "0s" or negative durations like "-5m" as valid parses, so an operator setting OSAC_NETWORK_CLASS_SYNC_INTERVAL to zero or negative reaches NewNetworkClassCapabilitiesSyncRunnable unchecked. time.NewTicker panics for a non-positive duration in Start() (internal/controller/networkclass_capabilities_controller.go, line 254), crashing the manager process from a single misconfigured env var.

Add a positive-duration validator to the GetEnvWithDefault call.

🛡️ Proposed fix
-	syncInterval := helpers.GetEnvWithDefault(envNetworkClassSyncInterval, defaultNetworkClassSyncInterval)
+	syncInterval := helpers.GetEnvWithDefault(
+		envNetworkClassSyncInterval, defaultNetworkClassSyncInterval,
+		func(d time.Duration) bool { return d > 0 },
+	)
📝 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
syncInterval := helpers.GetEnvWithDefault(envNetworkClassSyncInterval, defaultNetworkClassSyncInterval)
if err := localMgr.Add(controller.NewNetworkClassCapabilitiesSyncRunnable(ncReconciler, syncInterval)); err != nil {
return fmt.Errorf("networkclass capabilities sync runnable: %w", err)
}
syncInterval := helpers.GetEnvWithDefault(
envNetworkClassSyncInterval, defaultNetworkClassSyncInterval,
func(d time.Duration) bool { return d > 0 },
)
if err := localMgr.Add(controller.NewNetworkClassCapabilitiesSyncRunnable(ncReconciler, syncInterval)); err != nil {
return fmt.Errorf("networkclass capabilities sync runnable: %w", err)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/main.go` around lines 581 - 584, Update the GetEnvWithDefault call for
envNetworkClassSyncInterval to pass a validator that accepts only strictly
positive durations, rejecting zero and negative values before syncInterval
reaches NewNetworkClassCapabilitiesSyncRunnable. Reuse the existing duration
parsing/validation conventions or helper available in the codebase.

Comment on lines +84 to +99
It("treats nil as equivalent to all-false", func() {
Expect(capabilitiesEqual(nil, &privatev1.NetworkClassCapabilities{})).To(BeTrue())
})

It("returns false when any field differs", func() {
a := &privatev1.NetworkClassCapabilities{SupportsIpv4: true}
b := &privatev1.NetworkClassCapabilities{}
Expect(capabilitiesEqual(a, b)).To(BeFalse())
})

It("returns true when all fields match", func() {
a := &privatev1.NetworkClassCapabilities{SupportsIpv4: true, SupportsDualStack: true}
b := &privatev1.NetworkClassCapabilities{SupportsIpv4: true, SupportsDualStack: true}
Expect(capabilitiesEqual(a, b)).To(BeTrue())
})
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Non-empty struct literals on Opaque API messages likely fail to build.

internal/api/osac/private/v1 generates NetworkClass with the protogen:"opaque.v1" state tag and only hidden xxx_hidden_* fields (per the upstream contract snippet). The Opaque API hides struct fields and requires either the generated <Type>_builder{...}.Build() companion or setters on an empty literal — this PR's own production code follows that pattern (privatev1.NetworkClassesListRequest_builder{...}.Build(), privatev1.NetworkClassesUpdateRequest_builder{Object: nc}.Build(), and caps := &privatev1.NetworkClassCapabilities{} followed by caps.SetSupportsIpv4(...) etc.). The new test code instead constructs NetworkClass, NetworkClassCapabilities, and the NetworkClasses*Response types with non-empty struct literals, which is inconsistent with the Opaque API and, per the protobuf-go documentation, is not supported for opaque messages.

  • internal/controller/networkclass_capabilities_controller_test.go#L84-L99: replace &privatev1.NetworkClassCapabilities{SupportsIpv4: true} (and similar) with privatev1.NetworkClassCapabilities_builder{SupportsIpv4: true}.Build(), or an empty literal plus Set* calls.
  • internal/controller/networkclass_capabilities_controller_test.go#L117-L121: replace &privatev1.NetworkClass{Id: ..., FabricManager: ..., K8SManager: ...} with privatev1.NetworkClass_builder{...}.Build().
  • internal/controller/networkclass_capabilities_controller_test.go#L144-L148: same fix for the NetworkClass and nested NetworkClassCapabilities literals.
  • internal/controller/networkclass_capabilities_controller_test.go#L160-L210: same fix for &privatev1.NetworkClass{Id: "nc-caps-no-fabric"}, {Id: "nc-caps-bad-fabric", FabricManager: ...}, {Id: "nc-caps-good", FabricManager: ...}, and {Id: "nc-caps-bad", FabricManager: ...}.
  • internal/controller/networkclass_capabilities_controller_test.go#L225-L229: replace &privatev1.NetworkClassesListResponse{Items: page, Size: ..., Total: ...} with privatev1.NetworkClassesListResponse_builder{...}.Build().
  • internal/controller/dispatcher_resolver_helpers_test.go#L76-L92: replace &privatev1.NetworkClassesListResponse{Items: items}, &privatev1.NetworkClassesGetResponse{Object: item}, and &privatev1.NetworkClassesUpdateResponse{Object: in.GetObject()} with the corresponding _builder{...}.Build() calls.

Run the verification script below to confirm whether these types are opaque and whether _builder companions exist, before applying fixes.

#!/bin/bash
set -euo pipefail

# Locate the generated private API package.
fd -t d 'v1$' internal/api/osac/private

# Check whether NetworkClass, NetworkClassCapabilities, and the List/Get/Update
# response types are generated with the Opaque API (hidden fields + _builder companions).
rg -n --type=go -A2 'type (NetworkClass|NetworkClassCapabilities|NetworkClassesListResponse|NetworkClassesGetResponse|NetworkClassesUpdateResponse) struct' internal/api/osac/private/v1

rg -n --type=go '(NetworkClass|NetworkClassCapabilities|NetworkClassesListResponse|NetworkClassesGetResponse|NetworkClassesUpdateResponse)_builder' internal/api/osac/private/v1

rg -n --type=go 'protogen:"opaque' internal/api/osac/private/v1
📍 Affects 2 files
  • internal/controller/networkclass_capabilities_controller_test.go#L84-L99 (this comment)
  • internal/controller/networkclass_capabilities_controller_test.go#L117-L121
  • internal/controller/networkclass_capabilities_controller_test.go#L144-L148
  • internal/controller/networkclass_capabilities_controller_test.go#L160-L210
  • internal/controller/networkclass_capabilities_controller_test.go#L225-L229
  • internal/controller/dispatcher_resolver_helpers_test.go#L76-L92
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/networkclass_capabilities_controller_test.go` around
lines 84 - 99, Opaque protobuf messages are being instantiated with unsupported
non-empty struct literals. In
internal/controller/networkclass_capabilities_controller_test.go at lines 84-99,
117-121, 144-148, 160-210, and 225-229, replace NetworkClassCapabilities,
NetworkClass, and NetworkClassesListResponse literals with their corresponding
_builder{...}.Build() calls; in
internal/controller/dispatcher_resolver_helpers_test.go at lines 76-92, do the
same for NetworkClassesListResponse, NetworkClassesGetResponse, and
NetworkClassesUpdateResponse. Preserve all existing field values and nesting.

Comment on lines +251 to +267
// Start runs the periodic resync loop until the context is canceled.
func (s *networkClassCapabilitiesSyncRunnable) Start(ctx context.Context) error {
log := ctrllog.FromContext(ctx).WithName("networkclass-capabilities-sync")
ticker := time.NewTicker(s.interval)
defer ticker.Stop()

for {
select {
case <-ctx.Done():
return nil
case <-ticker.C:
if err := s.reconciler.resyncAll(ctx); err != nil {
log.Error(err, "periodic network class capabilities resync failed")
}
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🔵 Trivial

Overlapping resyncs are possible between the ConfigMap-triggered reconciler and the periodic runnable.

Start calls s.reconciler.resyncAll(ctx) directly from its own goroutine on every tick, independent of the controller-runtime reconcile queue (which serializes Reconcile calls via MaxConcurrentReconciles). A ConfigMap event and a tick can therefore trigger two concurrent full resyncAll passes, each listing and potentially updating every NetworkClass. The result is still correct (each pass computes the same capabilities independently), but under frequent ConfigMap churn this doubles fulfillment-service List/Update traffic.

Consider gating resyncAll behind a mutex or an "in-progress" flag shared between the reconciler and the runnable to avoid redundant concurrent passes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/controller/networkclass_capabilities_controller.go` around lines 251
- 267, Prevent concurrent resyncAll executions by adding shared synchronization
between networkClassCapabilitiesSyncRunnable.Start and the reconciler’s
ConfigMap-triggered resync path. Gate each full resync with a mutex or
in-progress guard, ensuring a periodic tick skips or waits when another resync
is active while preserving existing error logging and cancellation behavior.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants