Skip to content

Commit ea1433f

Browse files
committed
feat(terraform): Add support for audit comments to policies
1 parent 8d08bca commit ea1433f

34 files changed

Lines changed: 1196 additions & 80 deletions

assets/terraform/internal/manager/entry_utils_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111

1212
sdkerrors "github.com/PaloAltoNetworks/pango/errors"
13+
"github.com/PaloAltoNetworks/pango/util"
1314
"github.com/PaloAltoNetworks/pango/version"
1415
"github.com/PaloAltoNetworks/pango/xmlapi"
1516

@@ -117,6 +118,11 @@ func (o *MockEntryClient[E]) MultiConfig(ctx context.Context, updates *xmlapi.Mu
117118
return nil, nil, nil, nil
118119
}
119120

121+
func (o *MockEntryClient[E]) Communicate(ctx context.Context, cmd util.PangoCommand, strip bool, ans any) ([]byte, *http.Response, error) {
122+
// Mock implementation for Communicate API calls (audit comments, etc.)
123+
return nil, nil, nil
124+
}
125+
120126
func (o *MockEntryClient[E]) list() []E {
121127
var entries []E
122128
slog.Debug("MockEntryClient list()", "o.Current", o.Current, "o.Current.Front()", o.Current.Front())

assets/terraform/internal/manager/manager.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88
"net/url"
99

10+
"github.com/PaloAltoNetworks/pango/util"
1011
"github.com/PaloAltoNetworks/pango/version"
1112
"github.com/PaloAltoNetworks/pango/xmlapi"
1213
)
@@ -55,4 +56,5 @@ type SDKClient interface {
5556
GetTarget() string
5657
ChunkedMultiConfig(context.Context, *xmlapi.MultiConfig, bool, url.Values) ([]xmlapi.ChunkedMultiConfigResponse, error)
5758
MultiConfig(context.Context, *xmlapi.MultiConfig, bool, url.Values) ([]byte, *http.Response, *xmlapi.MultiConfigResponse, error)
59+
Communicate(context.Context, util.PangoCommand, bool, any) ([]byte, *http.Response, error)
5860
}

assets/terraform/internal/manager/uuid.go

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import (
88

99
"github.com/hashicorp/terraform-plugin-framework/diag"
1010
"github.com/hashicorp/terraform-plugin-framework/types"
11+
"github.com/hashicorp/terraform-plugin-log/tflog"
1112

13+
"github.com/PaloAltoNetworks/pango/audit"
1214
sdkerrors "github.com/PaloAltoNetworks/pango/errors"
1315
"github.com/PaloAltoNetworks/pango/movement"
1416
"github.com/PaloAltoNetworks/pango/util"
@@ -258,7 +260,7 @@ func (o *UuidObjectManager[E, L, S]) moveNonExhaustive(ctx context.Context, loca
258260
return nil
259261
}
260262

261-
func (o *UuidObjectManager[E, L, S]) CreateMany(ctx context.Context, location L, parentComponents []string, planEntries []E, exhaustive ExhaustiveType, sdkPosition movement.Position) ([]E, error) {
263+
func (o *UuidObjectManager[E, L, S]) CreateMany(ctx context.Context, location L, parentComponents []string, planEntries []E, exhaustive ExhaustiveType, sdkPosition movement.Position, auditComments map[string]string) ([]E, error) {
262264
var diags diag.Diagnostics
263265

264266
planEntriesByName := o.entriesByName(planEntries, entryUnknown)
@@ -343,6 +345,37 @@ func (o *UuidObjectManager[E, L, S]) CreateMany(ctx context.Context, location L,
343345
return nil, errors.Join(moveErr, cleanupErr)
344346
}
345347

348+
// Op API calls cannot be batched with Config operations in MultiConfig (API limitation),
349+
// so we send audit comments sequentially after the main configuration succeeds
350+
tflog.Debug(ctx, "Processing audit comments", map[string]interface{}{"count": len(auditComments), "comments": auditComments})
351+
for entryName, comment := range auditComments {
352+
components := append(parentComponents, util.AsEntryXpath(entryName))
353+
path, err := location.XpathWithComponents(o.client.Versioning(), components...)
354+
if err != nil {
355+
tflog.Error(ctx, "Failed to create xpath for audit comment", map[string]interface{}{"entry": entryName, "error": err.Error()})
356+
return nil, fmt.Errorf("failed to create xpath for audit comment: %w", err)
357+
}
358+
359+
opCmd := &xmlapi.Op{
360+
Command: audit.SetComment{
361+
Xpath: util.AsXpath(path),
362+
Comment: comment,
363+
},
364+
Target: o.client.GetTarget(),
365+
}
366+
367+
tflog.Debug(ctx, "Sending audit comment via Communicate", map[string]interface{}{"entry": entryName, "xpath": util.AsXpath(path), "comment": comment})
368+
_, _, err = o.client.Communicate(ctx, opCmd, false, nil)
369+
if err != nil {
370+
tflog.Error(ctx, "Failed to send audit comment", map[string]interface{}{"entry": entryName, "error": err.Error()})
371+
// Continue on audit comment errors - MultiConfig has already committed the rule changes,
372+
// so we cannot roll back. We must save state with the successful rule operations.
373+
continue
374+
}
375+
tflog.Debug(ctx, "Successfully sent audit comment", map[string]interface{}{"entry": entryName})
376+
}
377+
tflog.Debug(ctx, "Completed processing audit comments")
378+
346379
existing, err = o.service.List(ctx, location, "get", "", "")
347380
if err != nil && !sdkerrors.IsObjectNotFound(err) {
348381
return nil, fmt.Errorf("Failed to list remote entries: %w", err)
@@ -360,7 +393,7 @@ func (o *UuidObjectManager[E, L, S]) CreateMany(ctx context.Context, location L,
360393
return entries, nil
361394
}
362395

363-
func (o *UuidObjectManager[E, L, S]) UpdateMany(ctx context.Context, location L, parentComponents []string, stateEntries []E, planEntries []E, exhaustive ExhaustiveType, position movement.Position) ([]E, error) {
396+
func (o *UuidObjectManager[E, L, S]) UpdateMany(ctx context.Context, location L, parentComponents []string, stateEntries []E, planEntries []E, exhaustive ExhaustiveType, position movement.Position, auditComments map[string]string) ([]E, error) {
364397
stateEntriesByName := o.entriesByName(stateEntries, entryUnknown)
365398
planEntriesByName := o.entriesByName(planEntries, entryUnknown)
366399
if len(planEntriesByName) != len(planEntries) {
@@ -616,6 +649,37 @@ func (o *UuidObjectManager[E, L, S]) UpdateMany(ctx context.Context, location L,
616649
return nil, errors.Join(moveErr, cleanupErr)
617650
}
618651

652+
// Op API calls cannot be batched with Config operations in MultiConfig (API limitation),
653+
// so we send audit comments sequentially after the main configuration succeeds
654+
tflog.Debug(ctx, "Processing audit comments", map[string]interface{}{"count": len(auditComments), "comments": auditComments})
655+
for entryName, comment := range auditComments {
656+
components := append(parentComponents, util.AsEntryXpath(entryName))
657+
path, err := location.XpathWithComponents(o.client.Versioning(), components...)
658+
if err != nil {
659+
tflog.Error(ctx, "Failed to create xpath for audit comment", map[string]interface{}{"entry": entryName, "error": err.Error()})
660+
return nil, fmt.Errorf("failed to create xpath for audit comment: %w", err)
661+
}
662+
663+
opCmd := &xmlapi.Op{
664+
Command: audit.SetComment{
665+
Xpath: util.AsXpath(path),
666+
Comment: comment,
667+
},
668+
Target: o.client.GetTarget(),
669+
}
670+
671+
tflog.Debug(ctx, "Sending audit comment via Communicate", map[string]interface{}{"entry": entryName, "xpath": util.AsXpath(path), "comment": comment})
672+
_, _, err = o.client.Communicate(ctx, opCmd, false, nil)
673+
if err != nil {
674+
tflog.Error(ctx, "Failed to send audit comment", map[string]interface{}{"entry": entryName, "error": err.Error()})
675+
// Continue on audit comment errors - MultiConfig has already committed the rule changes,
676+
// so we cannot roll back. We must save state with the successful rule operations.
677+
continue
678+
}
679+
tflog.Debug(ctx, "Successfully sent audit comment", map[string]interface{}{"entry": entryName})
680+
}
681+
tflog.Debug(ctx, "Completed processing audit comments")
682+
619683
existing, err = o.service.List(ctx, location, "get", "", "")
620684
if err != nil && !sdkerrors.IsObjectNotFound(err) {
621685
return nil, fmt.Errorf("Failed to list remote entries: %w", err)

assets/terraform/internal/manager/uuid_test.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ var _ = Describe("Server", func() {
9898

9999
It("CreateMany() should create new entries on the server, and return them with uuid set", func() {
100100
entries := []*MockUuidObject{{Name: "1", Value: "A"}}
101-
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.Exhaustive, movement.PositionFirst{})
101+
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.Exhaustive, movement.PositionFirst{}, map[string]string{})
102102

103103
Expect(err).ToNot(HaveOccurred())
104104
Expect(processed).To(HaveLen(1))
@@ -114,7 +114,7 @@ var _ = Describe("Server", func() {
114114
Context("and entries with the same name are being created in NonExhaustive mode", func() {
115115
It("should not create any entries and return an error", func() {
116116
entries := []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "4", Value: "D"}}
117-
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.NonExhaustive, movement.PositionFirst{})
117+
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.NonExhaustive, movement.PositionFirst{}, map[string]string{})
118118

119119
Expect(err).To(MatchError(sdkmanager.ErrConflict))
120120
Expect(processed).To(BeNil())
@@ -126,7 +126,7 @@ var _ = Describe("Server", func() {
126126
Context("and all entries being created are new to the server", func() {
127127
It("should create those entries in the correct position", func() {
128128
entries := []*MockUuidObject{{Name: "4", Value: "D"}, {Name: "5", Value: "E"}}
129-
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.NonExhaustive, movement.PositionFirst{})
129+
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.NonExhaustive, movement.PositionFirst{}, map[string]string{})
130130

131131
Expect(err).ToNot(HaveOccurred())
132132
Expect(processed).To(HaveLen(2))
@@ -142,7 +142,7 @@ var _ = Describe("Server", func() {
142142
Context("and entries are created in Exhaustive mode", func() {
143143
It("should not return any error and overwrite all entries on the server", func() {
144144
entries := []*MockUuidObject{{Name: "1", Value: "A'"}, {Name: "3", Value: "C"}}
145-
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.Exhaustive, movement.PositionFirst{})
145+
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.Exhaustive, movement.PositionFirst{}, map[string]string{})
146146

147147
Expect(err).ToNot(HaveOccurred())
148148

@@ -174,7 +174,7 @@ var _ = Describe("Server", func() {
174174
stateEntries := []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "2", Value: "B"}, {Name: "3", Value: "C"}}
175175
planEntries := []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "3", Value: "C"}, {Name: "2", Value: "B"}}
176176

177-
processed, err := manager.UpdateMany(ctx, location, []string{}, stateEntries, planEntries, sdkmanager.NonExhaustive, movement.PositionFirst{})
177+
processed, err := manager.UpdateMany(ctx, location, []string{}, stateEntries, planEntries, sdkmanager.NonExhaustive, movement.PositionFirst{}, map[string]string{})
178178

179179
Expect(err).ToNot(HaveOccurred())
180180
Expect(processed).To(HaveLen(3))
@@ -192,7 +192,7 @@ var _ = Describe("Server", func() {
192192
stateEntries := []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "2", Value: "B"}, {Name: "3", Value: "C"}}
193193
planEntries := []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "3", Value: "C"}, {Name: "2", Value: "B"}}
194194

195-
processed, err := manager.UpdateMany(ctx, location, []string{}, stateEntries, planEntries, sdkmanager.Exhaustive, movement.PositionFirst{})
195+
processed, err := manager.UpdateMany(ctx, location, []string{}, stateEntries, planEntries, sdkmanager.Exhaustive, movement.PositionFirst{}, map[string]string{})
196196

197197
Expect(err).ToNot(HaveOccurred())
198198
Expect(processed).To(HaveLen(3))
@@ -212,7 +212,7 @@ var _ = Describe("Server", func() {
212212

213213
It("should add the entry to the server", func() {
214214
planEntries := []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "2", Value: "B"}, {Name: "3", Value: "C"}}
215-
processed, err := manager.UpdateMany(ctx, location, []string{}, initial, planEntries, sdkmanager.NonExhaustive, movement.PositionLast{})
215+
processed, err := manager.UpdateMany(ctx, location, []string{}, initial, planEntries, sdkmanager.NonExhaustive, movement.PositionLast{}, map[string]string{})
216216

217217
Expect(err).ToNot(HaveOccurred())
218218
Expect(processed).To(HaveLen(3))
@@ -224,7 +224,7 @@ var _ = Describe("Server", func() {
224224
It("should delete the entry from the server", func() {
225225
planEntries := []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "3", Value: "C"}}
226226

227-
processed, err := manager.UpdateMany(ctx, location, []string{}, initial, planEntries, sdkmanager.NonExhaustive, movement.PositionFirst{})
227+
processed, err := manager.UpdateMany(ctx, location, []string{}, initial, planEntries, sdkmanager.NonExhaustive, movement.PositionFirst{}, map[string]string{})
228228

229229
Expect(err).ToNot(HaveOccurred())
230230
Expect(processed).To(HaveLen(2))
@@ -236,7 +236,7 @@ var _ = Describe("Server", func() {
236236
It("should update the entry on the server", func() {
237237
planEntries := []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "2", Value: "B_modified"}, {Name: "3", Value: "C"}}
238238

239-
processed, err := manager.UpdateMany(ctx, location, []string{}, initial, planEntries, sdkmanager.NonExhaustive, movement.PositionFirst{})
239+
processed, err := manager.UpdateMany(ctx, location, []string{}, initial, planEntries, sdkmanager.NonExhaustive, movement.PositionFirst{}, map[string]string{})
240240

241241
Expect(err).ToNot(HaveOccurred())
242242
Expect(processed).To(HaveLen(3))
@@ -248,7 +248,7 @@ var _ = Describe("Server", func() {
248248
It("should rename the entry on the server", func() {
249249
planEntries := []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "two", Value: "B"}, {Name: "3", Value: "C"}}
250250

251-
processed, err := manager.UpdateMany(ctx, location, []string{}, initial, planEntries, sdkmanager.NonExhaustive, movement.PositionFirst{})
251+
processed, err := manager.UpdateMany(ctx, location, []string{}, initial, planEntries, sdkmanager.NonExhaustive, movement.PositionFirst{}, map[string]string{})
252252

253253
Expect(err).ToNot(HaveOccurred())
254254
Expect(processed).To(HaveLen(3))
@@ -275,7 +275,7 @@ var _ = Describe("Server", func() {
275275
stateEntries := []*MockUuidObject{}
276276
planEntries := []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "2", Value: "B"}}
277277

278-
processed, err := manager.UpdateMany(ctx, location, []string{}, stateEntries, planEntries, sdkmanager.Exhaustive, movement.PositionFirst{})
278+
processed, err := manager.UpdateMany(ctx, location, []string{}, stateEntries, planEntries, sdkmanager.Exhaustive, movement.PositionFirst{}, map[string]string{})
279279

280280
Expect(err).ToNot(HaveOccurred())
281281
Expect(processed).To(HaveLen(2))
@@ -291,7 +291,7 @@ var _ = Describe("Server", func() {
291291
It("should return a conflict error", func() {
292292
stateEntries := []*MockUuidObject{{Name: "1", Value: "A"}}
293293
planEntries := []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "conflict", Value: "new"}}
294-
_, err := manager.UpdateMany(ctx, location, []string{}, stateEntries, planEntries, sdkmanager.NonExhaustive, movement.PositionFirst{})
294+
_, err := manager.UpdateMany(ctx, location, []string{}, stateEntries, planEntries, sdkmanager.NonExhaustive, movement.PositionFirst{}, map[string]string{})
295295

296296
Expect(err).To(MatchError(sdkmanager.ErrConflict))
297297
})
@@ -333,7 +333,7 @@ var _ = Describe("Server", func() {
333333

334334
expectedFinalServerObjects := append(expectedFinalState, &MockUuidObject{Name: "99", Value: "ZZ"})
335335

336-
processed, err := manager.UpdateMany(ctx, location, []string{}, stateEntries, planEntries, sdkmanager.NonExhaustive, movement.PositionFirst{})
336+
processed, err := manager.UpdateMany(ctx, location, []string{}, stateEntries, planEntries, sdkmanager.NonExhaustive, movement.PositionFirst{}, map[string]string{})
337337

338338
Expect(err).ToNot(HaveOccurred())
339339
Expect(processed).To(HaveLen(4))
@@ -369,7 +369,7 @@ var _ = Describe("Server", func() {
369369
state := []*MockUuidObject{{Name: "1", Value: ""}, {Name: "2", Value: ""}, {Name: "3", Value: "C"}}
370370
plan := []*MockUuidObject{{Name: "1", Value: "A"}, {Name: "2", Value: "B"}, {Name: "3", Value: "C"}}
371371

372-
processed, err := manager.UpdateMany(ctx, location, []string{}, state, plan, sdkmanager.NonExhaustive, movement.PositionLast{})
372+
processed, err := manager.UpdateMany(ctx, location, []string{}, state, plan, sdkmanager.NonExhaustive, movement.PositionLast{}, map[string]string{})
373373
Expect(err).ToNot(HaveOccurred())
374374
Expect(processed).To(MatchEntries(plan))
375375
Expect(client.list()).To(MatchEntries(append([]*MockUuidObject{{Name: "4", Value: ""}}, plan...)))
@@ -437,7 +437,7 @@ var _ = Describe("Server", func() {
437437
Expect(moveRequired).To(BeFalse())
438438
Expect(processed).To(HaveLen(2))
439439

440-
processed, err = manager.UpdateMany(ctx, location, []string{}, processed, entries, sdkmanager.NonExhaustive, movement.PositionLast{})
440+
processed, err = manager.UpdateMany(ctx, location, []string{}, processed, entries, sdkmanager.NonExhaustive, movement.PositionLast{}, map[string]string{})
441441
Expect(client.list()).To(HaveLen(3))
442442
Expect(err).ToNot(HaveOccurred())
443443
Expect(processed).To(HaveLen(3))
@@ -452,7 +452,7 @@ var _ = Describe("Server", func() {
452452
It("should create new entries on the top of the list", func() {
453453
entries := []*MockUuidObject{{Name: "4", Value: "D"}, {Name: "5", Value: "E"}, {Name: "6", Value: "F"}}
454454

455-
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.NonExhaustive, movement.PositionFirst{})
455+
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.NonExhaustive, movement.PositionFirst{}, map[string]string{})
456456
Expect(err).ToNot(HaveOccurred())
457457
Expect(processed).To(HaveLen(3))
458458

@@ -470,7 +470,7 @@ var _ = Describe("Server", func() {
470470
It("should create new entries on the bottom of the list", func() {
471471
entries := []*MockUuidObject{{Name: "4", Value: "D"}, {Name: "5", Value: "E"}, {Name: "6", Value: "F"}}
472472

473-
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.NonExhaustive, movement.PositionLast{})
473+
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.NonExhaustive, movement.PositionLast{}, map[string]string{})
474474
Expect(err).ToNot(HaveOccurred())
475475
Expect(processed).To(HaveLen(3))
476476

@@ -488,7 +488,7 @@ var _ = Describe("Server", func() {
488488
It("should create new entries directly after first existing element", func() {
489489
entries := []*MockUuidObject{{Name: "4", Value: "D"}, {Name: "5", Value: "E"}, {Name: "6", Value: "F"}}
490490

491-
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.NonExhaustive, movement.PositionAfter{Directly: true, Pivot: initial[0].Name})
491+
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.NonExhaustive, movement.PositionAfter{Directly: true, Pivot: initial[0].Name}, map[string]string{})
492492

493493
Expect(err).ToNot(HaveOccurred())
494494
Expect(processed).To(HaveLen(3))
@@ -513,7 +513,7 @@ var _ = Describe("Server", func() {
513513

514514
pivot := initial[2].Name // "3"
515515
position := movement.PositionBefore{Directly: true, Pivot: pivot}
516-
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.NonExhaustive, position)
516+
processed, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.NonExhaustive, position, map[string]string{})
517517

518518
Expect(err).ToNot(HaveOccurred())
519519
Expect(processed).To(HaveLen(3))
@@ -532,7 +532,7 @@ var _ = Describe("Server", func() {
532532
Context("and there is a duplicate entry within a list", func() {
533533
It("should properly raise an error", func() {
534534
entries := []*MockUuidObject{{Name: "4", Value: "D"}, {Name: "4", Value: "D"}}
535-
_, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.NonExhaustive, movement.PositionFirst{})
535+
_, err := manager.CreateMany(ctx, location, []string{}, entries, sdkmanager.NonExhaustive, movement.PositionFirst{}, map[string]string{})
536536

537537
Expect(err).To(MatchError(sdkmanager.ErrPlanConflict))
538538
})

assets/terraform/internal/manager/uuid_utils_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ func (o *MockUuidClient[E]) MultiConfig(ctx context.Context, updates *xmlapi.Mul
122122
return nil, nil, nil, nil
123123
}
124124

125+
func (o *MockUuidClient[E]) Communicate(ctx context.Context, cmd util.PangoCommand, strip bool, ans any) ([]byte, *http.Response, error) {
126+
// Mock implementation for Communicate API calls (audit comments, etc.)
127+
return nil, nil, nil
128+
}
129+
125130
func (o *MockUuidClient[E]) list() []E {
126131
var entries []E
127132
for e := o.Current.Front(); e != nil; e = e.Next() {

assets/terraform/test/provider_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ var (
2929
func init() {
3030
sdkClient = &pango.Client{
3131
CheckEnvironment: true,
32+
Logging: pango.LoggingInfo{
33+
LogLevel: slog.LevelDebug,
34+
LogCategories: pango.LogCategorySend | pango.LogCategoryReceive,
35+
},
3236
}
3337

3438
ctx := context.Background()

0 commit comments

Comments
 (0)