Skip to content

add peer groups to status JSON/YAML output#5775

Open
Paulpatou wants to merge 3 commits intonetbirdio:mainfrom
Paulpatou:feature/add-groups-to-status-json-output
Open

add peer groups to status JSON/YAML output#5775
Paulpatou wants to merge 3 commits intonetbirdio:mainfrom
Paulpatou:feature/add-groups-to-status-json-output

Conversation

@Paulpatou
Copy link
Copy Markdown

@Paulpatou Paulpatou commented Apr 2, 2026

Describe your changes

This PR adds group membership information (group names) to each peer in the netbird status --json and netbird status --yaml output.

Problem

When using netbird status --json or netbird status --yaml to inventory peers programmatically, group membership was missing from the output. This made the CLI output impractical for automation use cases where peers need to be filtered or categorized by group.

The API already exposes this information, but not everyone can use the API in their tooling architecture - they rely on the CLI status output.

Solution

Added a groups field to each peer entry in the status JSON/YAML output, containing the list of group names the peer belongs to.

Before:

{
  "peers": {
    "details": [
      {
        "fqdn": "peer.example.netbird.cloud",
        "netbirdIp": "100.64.0.1",
        "status": "Connected"
      }
    ]
  }
}

After:

{
  "peers": {
    "details": [
      {
        "fqdn": "peer.example.netbird.cloud",
        "netbirdIp": "100.64.0.1",
        "status": "Connected",
        "groups": ["All", "Production"]
      }
    ]
  }
}

Changes

The feature is implemented across the full data pipeline:

Management server → client sync

  • shared/management/proto/management.proto — add repeated string groupsNames = 6 to RemotePeerConfig
  • management/server/types/network.go — add PeerGroupsNames map[string][]string to NetworkMap
  • management/server/types/account.go — add GetPeerGroupNames(peerID string) []string helper
  • management/internals/shared/grpc/conversion.go — populate GroupsNames in appendRemotePeerConfig
  • management/internals/controllers/network_map/controller/controller.go — fill PeerGroupsNames before ToSyncResponse
  • management/internals/shared/grpc/server.go — fill PeerGroupsNames via GetAccountGroups in sendInitialSync

Client daemon → CLI

  • client/proto/daemon.proto — add repeated string groups = 20 to PeerState
  • client/internal/peer/status.go — add Groups []string to State, add UpdatePeerGroups() method, include groups in ToProto()
  • client/internal/engine.go — call UpdatePeerGroups in addNewPeer and modifyPeers

CLI output

  • client/status/status.go — add Groups []string \json:"groups" yaml:"groups"`toPeerStateDetailOutput, populate in mapPeers()`

Issue ticket number and link

#5714

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)

Documentation

  • I added/updated documentation for this change
  • Documentation is not needed for this change

This is a feature addition that exposes existing group membership data in the CLI status output. The API already provides this information; we're just making it visible in the JSON/YAML status output for automation use cases.

Docs PR URL (required if "docs added" is checked)

https://github.com/netbirdio/docs/pull/__

Summary by CodeRabbit

  • New Features

    • Peer group membership is now recorded and shown in peer status/details and network sync payloads.
    • Remote peer configurations and sync responses include per-peer group names.
    • Status output and anonymized exports now include anonymized group labels when applicable.
  • Tests

    • Added unit tests covering group tracking, status output, and label anonymization.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 2, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 2, 2026

📝 Walkthrough

Walkthrough

Adds peer group membership tracking end-to-end: introduces Groups on peer state, persists and exposes group names in status and sync payloads, populates per-peer group-name maps server-side, extends protobufs and CLI output, and adds tests and anonymization support for labels.

Changes

Cohort / File(s) Summary
Client status & engine
client/internal/engine.go, client/internal/peer/status.go, client/internal/peer/status_test.go
Added Groups []string to peer State, implemented Status.UpdatePeerGroups, wired engine to call UpdatePeerGroups when adding/modifying peers, and added tests for UpdatePeerGroups.
Client proto & status output
client/proto/daemon.proto, client/status/status.go, client/status/status_test.go
Extended daemon.PeerState with groups; populated CLI/JSON/YAML PeerStateDetailOutput.Groups (sorted/non-nil) and updated serialization tests/fixtures.
Anonymization
client/anonymize/anonymize.go, client/anonymize/anonymize_test.go
Added label anonymizer state and AnonymizeLabel method with tests to anonymize group labels in outputs.
Management network map & account
management/server/types/network.go, management/server/types/account.go, management/server/types/account_test.go
Added NetworkMap.PeerGroupsNames and PopulatePeerGroupsNames(account), plus Account.GetPeerGroupNames(peerID) and unit tests for group-name lookup.
Controller & gRPC wiring
management/internals/controllers/network_map/controller/controller.go, management/internals/shared/grpc/conversion.go, management/internals/shared/grpc/server.go
Populates NetworkMap.PeerGroupsNames in controller paths and threads peer group-name maps through ToSyncResponseappendRemotePeerConfig so RemotePeerConfig receives per-peer group names; initial sync loads account groups to build the map.
Management proto
shared/management/proto/management.proto
Added repeated string groupsNames to RemotePeerConfig to carry per-peer group names in sync responses.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Engine
    participant StatusRecorder
    participant GRPC as "gRPC/Conversion"
    participant Controller
    participant NetworkMap
    participant AccountStore as "Account/DB"

    Client->>Engine: connect / request sync
    Engine->>StatusRecorder: update peer FQDN / allowed IP
    StatusRecorder->>StatusRecorder: UpdatePeerGroups(pubKey, groups)
    Controller->>NetworkMap: build/merge network map
    Controller->>AccountStore: GetAccountGroups(account)
    AccountStore-->>Controller: groups with member lists
    Controller->>NetworkMap: PopulatePeerGroupsNames(account)
    Controller->>GRPC: ToSyncResponse(networkMap)
    GRPC->>Client: SyncResponse includes RemotePeerConfig.groupsNames
    Client->>Engine: receive RemotePeerConfig
    Engine->>StatusRecorder: UpdatePeerGroups(peerPubKey, groupsFromSync)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • #4987 — Modifies network-map sync plumbing and peer group name propagation paths (ToSyncResponse/controller/server changes).
  • #4969 — Touches NetworkMap peer-group propagation and Account/GetPeerGroupNames-related wiring.
  • #4807 — Alters controller update paths (sendUpdateAccountPeers/UpdateAccountPeer) similar to where group-name population was added.

Suggested reviewers

  • pascal-fischer
  • pappz
  • crn4

Poem

🐇 With twitching nose and springy feet,
I stitched the groups where peers shall meet.
From account maps to client view,
Names hop through sync — tidy and true. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'add peer groups to status JSON/YAML output' clearly and accurately summarizes the main change in the PR: adding group membership information to the status output.
Description check ✅ Passed The PR description is well-structured with clear sections covering the problem, solution, and implementation changes across the full pipeline, with before/after examples and proper checklist completion.

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

✨ 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 and usage tips.

Copy link
Copy Markdown
Contributor

@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 (3)
management/server/types/account.go (1)

2081-2092: Sort group names before returning for stable status output.

This method works, but sorting the result avoids non-deterministic ordering in JSON/YAML output.

♻️ Proposed change
 func (a *Account) GetPeerGroupNames(peerID string) []string {
 	var names []string
 	for _, group := range a.Groups {
 		for _, id := range group.Peers {
 			if id == peerID {
 				names = append(names, group.Name)
 				break
 			}
 		}
 	}
+	slices.Sort(names)
 	return names
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/server/types/account.go` around lines 2081 - 2092, The
GetPeerGroupNames method returns group names in iteration order which can be
non-deterministic; update the function (Account.GetPeerGroupNames) to sort the
names slice before returning so output is stable for JSON/YAML
serialization—after collecting matching group.Name values push them into names,
call sort.Strings(names) and then return names (ensure to import "sort" if not
already).
client/status/status_test.go (1)

153-175: Consider adding one non-empty groups fixture/assertion.

Current updates verify empty-slice rendering well; adding one peer with actual group values would better guard end-to-end serialization of populated data.

Also applies to: 310-310, 334-334, 443-443, 463-463

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/status/status_test.go` around lines 153 - 175, Add at least one
non-empty Groups value to one of the peer fixtures and assert it is
serialized/rendered correctly: locate the peer struct literal (the entry with IP
"192.168.178.102") in client/status/status_test.go and change Groups from
[]string{} to a populated slice (e.g. []string{"team-a"}), then update or add
the corresponding test assertion that checks the output contains that group
string; repeat the same change for the other peer fixtures referenced in the
comment locations so the tests cover both empty and populated Groups cases.
management/internals/controllers/network_map/controller/controller.go (1)

404-411: Consider extracting duplicate group name population logic.

This code block is nearly identical to lines 264-271 in sendUpdateAccountPeers. Additionally, the variable name peerGroupsNames2 is inconsistent with peerGroupsNames used in the other function.

♻️ Suggestion: Extract helper function
func buildPeerGroupsNames(account *types.Account, peers []*nbpeer.Peer, offlinePeers []*nbpeer.Peer) map[string][]string {
    peerGroupsNames := make(map[string][]string)
    for _, rp := range peers {
        peerGroupsNames[rp.ID] = account.GetPeerGroupNames(rp.ID)
    }
    for _, rp := range offlinePeers {
        peerGroupsNames[rp.ID] = account.GetPeerGroupNames(rp.ID)
    }
    return peerGroupsNames
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/internals/controllers/network_map/controller/controller.go` around
lines 404 - 411, The duplicate population of peer group names (peerGroupsNames2
in controller.go) should be extracted into a shared helper to avoid repetition
with sendUpdateAccountPeers; add a function (e.g., buildPeerGroupsNames(account
*types.Account, peers []*nbpeer.Peer, offlinePeers []*nbpeer.Peer)
map[string][]string) that calls account.GetPeerGroupNames for each rp.ID and
returns the map, then replace the inline loops in both remotePeerNetworkMap
population (where peerGroupsNames2 is set) and sendUpdateAccountPeers (where
peerGroupsNames is used) to call this helper so the variable names and logic are
consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/status/status.go`:
- Line 76: The exported Groups field in client/status/status.go (Groups []string
`json:"groups" yaml:"groups"`) is leaked in anonymized output; update the
peer-anonymization code path (where anonymization is performed — e.g., the
function handling "--anonymize" / anonymizePeerDetails / AnonymizePeer) to clear
or replace the Groups field (set to nil, empty slice, or a fixed placeholder
like "REDACTED") before marshaling, and add a unit test that validates Groups is
scrubbed in anonymized output; ensure both JSON and YAML output flows use the
same scrub logic.

---

Nitpick comments:
In `@client/status/status_test.go`:
- Around line 153-175: Add at least one non-empty Groups value to one of the
peer fixtures and assert it is serialized/rendered correctly: locate the peer
struct literal (the entry with IP "192.168.178.102") in
client/status/status_test.go and change Groups from []string{} to a populated
slice (e.g. []string{"team-a"}), then update or add the corresponding test
assertion that checks the output contains that group string; repeat the same
change for the other peer fixtures referenced in the comment locations so the
tests cover both empty and populated Groups cases.

In `@management/internals/controllers/network_map/controller/controller.go`:
- Around line 404-411: The duplicate population of peer group names
(peerGroupsNames2 in controller.go) should be extracted into a shared helper to
avoid repetition with sendUpdateAccountPeers; add a function (e.g.,
buildPeerGroupsNames(account *types.Account, peers []*nbpeer.Peer, offlinePeers
[]*nbpeer.Peer) map[string][]string) that calls account.GetPeerGroupNames for
each rp.ID and returns the map, then replace the inline loops in both
remotePeerNetworkMap population (where peerGroupsNames2 is set) and
sendUpdateAccountPeers (where peerGroupsNames is used) to call this helper so
the variable names and logic are consistent.

In `@management/server/types/account.go`:
- Around line 2081-2092: The GetPeerGroupNames method returns group names in
iteration order which can be non-deterministic; update the function
(Account.GetPeerGroupNames) to sort the names slice before returning so output
is stable for JSON/YAML serialization—after collecting matching group.Name
values push them into names, call sort.Strings(names) and then return names
(ensure to import "sort" if not already).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d5d5067-ddc6-4b93-9d1f-29ba5c79d777

📥 Commits

Reviewing files that changed from the base of the PR and between aaf813f and ef897e8.

⛔ Files ignored due to path filters (2)
  • client/proto/daemon.pb.go is excluded by !**/*.pb.go
  • shared/management/proto/management.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (13)
  • client/internal/engine.go
  • client/internal/peer/status.go
  • client/internal/peer/status_test.go
  • client/proto/daemon.proto
  • client/status/status.go
  • client/status/status_test.go
  • management/internals/controllers/network_map/controller/controller.go
  • management/internals/shared/grpc/conversion.go
  • management/internals/shared/grpc/server.go
  • management/server/types/account.go
  • management/server/types/account_test.go
  • management/server/types/network.go
  • shared/management/proto/management.proto

Contributor and others added 3 commits April 3, 2026 08:23
Add group membership (group names) for each peer in `netbird status --json`
and `netbird status --yaml` output, resolving issue netbirdio#5714.

Changes:
- shared/management/proto: add groupsNames field to RemotePeerConfig
- management/server/types: add PeerGroupsNames map to NetworkMap and
  GetPeerGroupNames helper on Account
- management/internals/grpc: populate group names in RemotePeerConfig
  when building sync response (both controller and server paths)
- client/proto: add groups field to PeerState daemon proto
- client/internal/peer: add Groups field to State, UpdatePeerGroups
  method, and include groups in ToProto()
- client/internal/engine: store peer groups on add and modify
- client/status: expose groups in PeerStateDetailOutput (json:"groups")
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use protoc-gen-go v1.36.6 + protoc v33.1 for daemon.pb.go and
protoc-gen-go v1.26.0 + protoc v34.1 for management.pb.go,
matching the versions used by the upstream project.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Paulpatou Paulpatou force-pushed the feature/add-groups-to-status-json-output branch from ef897e8 to 27cae9c Compare April 3, 2026 06:26
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 3, 2026

@Paulpatou
Copy link
Copy Markdown
Author

Rebased on latest main and addressed the review feedback.

Regenerated .pb.go files with the correct upstream protoc/protoc-gen-go versions, cleaning up the noisy diff. Added slices.Sort() in GetPeerGroupNames() for deterministic output, a dedicated AnonymizeLabel() method for group name anonymization (existing methods don't handle arbitrary labels), and extracted PopulatePeerGroupsNames() to remove duplicated logic in the controller. Also ensured group names are sorted in sendInitialSync for consistency with the other server paths. Groups are now also included in netbird status -d text output and in the ToProtoFullStatus() path.

Copy link
Copy Markdown
Contributor

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/anonymize/anonymize.go`:
- Around line 146-159: AnonymizeLabel currently may return a generated token
equal to the original label (e.g. original "anon-label-1"); change
AnonymizeLabel on type Anonymizer to ensure the generated anonymized value is
never equal to the source: when creating a new anonymized value (using
a.labelCounter and fmt.Sprintf("anon-label-%d", ...)), loop/increment the
counter until the candidate != label and is not already present in
a.labelAnonymizer, then assign it to a.labelAnonymizer[label]; also add a
regression test that calls AnonymizeLabel with inputs already shaped like
"anon-label-N" to assert the returned anonymized value differs from the original
and remains consistent across repeated calls.

In `@management/internals/shared/grpc/server.go`:
- Around line 891-902: The initial-sync path builds peerGroupsNames without
sorting, causing order drift vs the update path that uses
PopulatePeerGroupsNames/account.GetPeerGroupNames which returns sorted names;
update the block that constructs peerGroupsNames (the loop feeding
networkMap.PeerGroupsNames after calling
s.accountManager.GetStore().GetAccountGroups) to sort each []string before
assignment so peerGroupsNames[peerID] contains a deterministically ordered list
matching account.GetPeerGroupNames.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a443182c-863a-4f82-97f8-9633609043b1

📥 Commits

Reviewing files that changed from the base of the PR and between ef897e8 and 27cae9c.

⛔ Files ignored due to path filters (2)
  • client/proto/daemon.pb.go is excluded by !**/*.pb.go
  • shared/management/proto/management.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (15)
  • client/anonymize/anonymize.go
  • client/anonymize/anonymize_test.go
  • client/internal/engine.go
  • client/internal/peer/status.go
  • client/internal/peer/status_test.go
  • client/proto/daemon.proto
  • client/status/status.go
  • client/status/status_test.go
  • management/internals/controllers/network_map/controller/controller.go
  • management/internals/shared/grpc/conversion.go
  • management/internals/shared/grpc/server.go
  • management/server/types/account.go
  • management/server/types/account_test.go
  • management/server/types/network.go
  • shared/management/proto/management.proto
✅ Files skipped from review due to trivial changes (1)
  • client/status/status_test.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • client/internal/peer/status_test.go
  • client/proto/daemon.proto
  • management/internals/shared/grpc/conversion.go
  • client/internal/engine.go
  • management/internals/controllers/network_map/controller/controller.go
  • management/server/types/account_test.go
  • client/internal/peer/status.go

Comment on lines +146 to +159
// AnonymizeLabel anonymizes an arbitrary text label (e.g. group names) using a consistent mapping.
func (a *Anonymizer) AnonymizeLabel(label string) string {
if label == "" {
return label
}

anonymized, ok := a.labelAnonymizer[label]
if !ok {
a.labelCounter++
anonymized = fmt.Sprintf("anon-label-%d", a.labelCounter)
a.labelAnonymizer[label] = anonymized
}
return anonymized
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid generating an anonymized label that equals the original.

A real group name like anon-label-1 will pass through unchanged the first time it is seen, because the first generated token is also anon-label-1. That means --anonymize can still leak a user label. Please skip candidates that match the source label and add a regression test for labels already shaped like anon-label-N.

🛠️ Minimal fix
 func (a *Anonymizer) AnonymizeLabel(label string) string {
 	if label == "" {
 		return label
 	}
 
 	anonymized, ok := a.labelAnonymizer[label]
 	if !ok {
-		a.labelCounter++
-		anonymized = fmt.Sprintf("anon-label-%d", a.labelCounter)
+		for {
+			a.labelCounter++
+			anonymized = fmt.Sprintf("anon-label-%d", a.labelCounter)
+			if anonymized != label {
+				break
+			}
+		}
 		a.labelAnonymizer[label] = anonymized
 	}
 	return anonymized
 }
📝 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
// AnonymizeLabel anonymizes an arbitrary text label (e.g. group names) using a consistent mapping.
func (a *Anonymizer) AnonymizeLabel(label string) string {
if label == "" {
return label
}
anonymized, ok := a.labelAnonymizer[label]
if !ok {
a.labelCounter++
anonymized = fmt.Sprintf("anon-label-%d", a.labelCounter)
a.labelAnonymizer[label] = anonymized
}
return anonymized
}
// AnonymizeLabel anonymizes an arbitrary text label (e.g. group names) using a consistent mapping.
func (a *Anonymizer) AnonymizeLabel(label string) string {
if label == "" {
return label
}
anonymized, ok := a.labelAnonymizer[label]
if !ok {
for {
a.labelCounter++
anonymized = fmt.Sprintf("anon-label-%d", a.labelCounter)
if anonymized != label {
break
}
}
a.labelAnonymizer[label] = anonymized
}
return anonymized
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/anonymize/anonymize.go` around lines 146 - 159, AnonymizeLabel
currently may return a generated token equal to the original label (e.g.
original "anon-label-1"); change AnonymizeLabel on type Anonymizer to ensure the
generated anonymized value is never equal to the source: when creating a new
anonymized value (using a.labelCounter and fmt.Sprintf("anon-label-%d", ...)),
loop/increment the counter until the candidate != label and is not already
present in a.labelAnonymizer, then assign it to a.labelAnonymizer[label]; also
add a regression test that calls AnonymizeLabel with inputs already shaped like
"anon-label-N" to assert the returned anonymized value differs from the original
and remains consistent across repeated calls.

Comment on lines +891 to +902
allGroups, err := s.accountManager.GetStore().GetAccountGroups(ctx, store.LockingStrengthNone, peer.AccountID)
if err != nil {
log.WithContext(ctx).Warnf("failed to get account groups for peer groups names: %v", err)
} else {
peerGroupsNames := make(map[string][]string)
for _, g := range allGroups {
for _, peerID := range g.Peers {
peerGroupsNames[peerID] = append(peerGroupsNames[peerID], g.Name)
}
}
networkMap.PeerGroupsNames = peerGroupsNames
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Sorting inconsistency between initial sync and update sync paths.

This code builds peerGroupsNames by iterating groups and appending names without sorting. However, PopulatePeerGroupsNames (used in the controller's update path) calls account.GetPeerGroupNames, which sorts the names before returning.

This means initial sync may return groups in a different order than subsequent update syncs, potentially causing unnecessary state churn on clients if they compare group lists.

🔧 Proposed fix: Sort names for consistency
+	"slices"
 ...
 	allGroups, err := s.accountManager.GetStore().GetAccountGroups(ctx, store.LockingStrengthNone, peer.AccountID)
 	if err != nil {
 		log.WithContext(ctx).Warnf("failed to get account groups for peer groups names: %v", err)
 	} else {
 		peerGroupsNames := make(map[string][]string)
 		for _, g := range allGroups {
 			for _, peerID := range g.Peers {
 				peerGroupsNames[peerID] = append(peerGroupsNames[peerID], g.Name)
 			}
 		}
+		for peerID := range peerGroupsNames {
+			slices.Sort(peerGroupsNames[peerID])
+		}
 		networkMap.PeerGroupsNames = peerGroupsNames
 	}
📝 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
allGroups, err := s.accountManager.GetStore().GetAccountGroups(ctx, store.LockingStrengthNone, peer.AccountID)
if err != nil {
log.WithContext(ctx).Warnf("failed to get account groups for peer groups names: %v", err)
} else {
peerGroupsNames := make(map[string][]string)
for _, g := range allGroups {
for _, peerID := range g.Peers {
peerGroupsNames[peerID] = append(peerGroupsNames[peerID], g.Name)
}
}
networkMap.PeerGroupsNames = peerGroupsNames
}
allGroups, err := s.accountManager.GetStore().GetAccountGroups(ctx, store.LockingStrengthNone, peer.AccountID)
if err != nil {
log.WithContext(ctx).Warnf("failed to get account groups for peer groups names: %v", err)
} else {
peerGroupsNames := make(map[string][]string)
for _, g := range allGroups {
for _, peerID := range g.Peers {
peerGroupsNames[peerID] = append(peerGroupsNames[peerID], g.Name)
}
}
for peerID := range peerGroupsNames {
slices.Sort(peerGroupsNames[peerID])
}
networkMap.PeerGroupsNames = peerGroupsNames
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/internals/shared/grpc/server.go` around lines 891 - 902, The
initial-sync path builds peerGroupsNames without sorting, causing order drift vs
the update path that uses PopulatePeerGroupsNames/account.GetPeerGroupNames
which returns sorted names; update the block that constructs peerGroupsNames
(the loop feeding networkMap.PeerGroupsNames after calling
s.accountManager.GetStore().GetAccountGroups) to sort each []string before
assignment so peerGroupsNames[peerID] contains a deterministically ordered list
matching account.GetPeerGroupNames.

@mlsmaycon
Copy link
Copy Markdown
Collaborator

Hello @Paulpatou thanks for submitting the PR.

In general, we won't be sending the groups to the peers as part of the network map in this way. We have a few plans to optimize the network map and offload some of it to the peer that can allow similar information to be displayed, but current feedback from many users is that sharing internal infrastructure configuration details is not the desired mode.

With that, I think the PR is a bit early, and we should revisit the idea in a few weeks.

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