Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion cmd/lk/proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"google.golang.org/protobuf/proto"

"github.com/livekit/livekit-cli/v2/pkg/util"
"github.com/livekit/protocol/livekit"
)

const flagRequest = "request"
Expand All @@ -44,6 +45,16 @@ type protoTypeValidator[T any] interface {
Validate() error
}

type paginatedType[T any] interface {
protoType[T]
GetPage() *livekit.Pagination
Copy link
Contributor

Choose a reason for hiding this comment

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

We plan to use this for phone numbers
https://github.com/livekit/protocol/blob/main/protobufs/livekit_models.proto#L31
Should we account for this as well ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, I'll take a look

}

type paginatedResponseType[T any, D any] interface {
protoType[T]
GetItems() []*D
}

func ReadRequest[T any, P protoType[T]](cmd *cli.Command) (*T, error) {
return ReadRequestFileOrLiteral[T, P](cmd.String(flagRequest))
}
Expand Down Expand Up @@ -99,6 +110,35 @@ func RequestDesc[T any, _ protoType[T]]() string {
return typ + " as JSON file"
}

// Repeatedly calls a paginated list function until all items are retrieved,
// requiring the caller to update the request's pagination parameters as needed.
func ExhaustivePaginatedList[
ReqT any, Req paginatedType[ReqT],
ResT any, ResD any, Res paginatedResponseType[ResT, ResD],
](
ctx context.Context,
req Req,
list func(context.Context, Req) (Res, error),
iter func(items []*ResD),
page *livekit.Pagination,
) error {
for {
res, err := list(ctx, req)
if err != nil {
return err
}
resultItems := res.GetItems()
if len(resultItems) > 0 {
iter(resultItems)
}
// List exhausted
if len(resultItems) < int(page.Limit) {
break
}
}
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to worry about local memory if the api returns a large amount say thousands of phone numbers ?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't know any modern machine that couldn't handle a few MB or even tens of MB or allocation, and this is a short-lived object. I would suggest we impose limit if and only if we get issues? This worked fine with 22K trunks in testing.


func createAndPrintFile[T any, P protoTypeValidator[T], R any](
ctx context.Context,
cmd *cli.Command, file string,
Expand Down Expand Up @@ -206,7 +246,7 @@ func listAndPrint[
](
ctx context.Context,
cmd *cli.Command,
getList func(ctx context.Context, req Req) (Resp, error), req Req,
getList func(context.Context, Req) (Resp, error), req Req,
header []string, tableRow func(item *T) []string,
) error {
res, err := getList(ctx, req)
Expand Down
54 changes: 51 additions & 3 deletions cmd/lk/sip.go
Original file line number Diff line number Diff line change
Expand Up @@ -790,9 +790,33 @@ func listSipTrunk(ctx context.Context, cmd *cli.Command) error {
func listSipInboundTrunk(ctx context.Context, cmd *cli.Command) error {
cli, err := createSIPClient(ctx, cmd)
if err != nil {
return err
return fmt.Errorf("could not create SIP client: %w", err)
}

// NOTE: twirp has a maximum payload size of 4MB, which some customer data may exceed.
// We implement pagination here behind the scenes to split requests into manageable chunks
// unlikely to exceed the limit. This should be used on all listing commands that may
// return a large number of items.
page := &livekit.Pagination{Limit: 500}
req := &livekit.ListSIPInboundTrunkRequest{Page: page}
list := func(ctx context.Context, req *livekit.ListSIPInboundTrunkRequest) (*livekit.ListSIPInboundTrunkResponse, error) {
res := &livekit.ListSIPInboundTrunkResponse{}
if err := ExhaustivePaginatedList(
ctx,
req,
cli.ListSIPInboundTrunk,
func(items []*livekit.SIPInboundTrunkInfo) {
res.Items = append(res.Items, items...)
page.AfterId = items[len(items)-1].SipTrunkId
},
page,
); err != nil {
return nil, fmt.Errorf("could not list SIP inbound trunks: %w", err)
}
return res, nil
}
return listAndPrint(ctx, cmd, cli.ListSIPInboundTrunk, &livekit.ListSIPInboundTrunkRequest{}, []string{

return listAndPrint(ctx, cmd, list, req, []string{
"SipTrunkID", "Name", "Numbers",
"AllowedAddresses", "AllowedNumbers",
"Authentication",
Expand All @@ -816,7 +840,31 @@ func listSipOutboundTrunk(ctx context.Context, cmd *cli.Command) error {
if err != nil {
return err
}
return listAndPrint(ctx, cmd, cli.ListSIPOutboundTrunk, &livekit.ListSIPOutboundTrunkRequest{}, []string{

// NOTE: twirp has a maximum payload size of 4MB, which some customer data may exceed.
// We implement pagination here behind the scenes to split requests into manageable chunks
// unlikely to exceed the limit. This should be used on all listing commands that may
// return a large number of items.
page := &livekit.Pagination{Limit: 500}
req := &livekit.ListSIPOutboundTrunkRequest{Page: page}
list := func(ctx context.Context, req *livekit.ListSIPOutboundTrunkRequest) (*livekit.ListSIPOutboundTrunkResponse, error) {
res := &livekit.ListSIPOutboundTrunkResponse{}
if err := ExhaustivePaginatedList(
ctx,
req,
cli.ListSIPOutboundTrunk,
func(items []*livekit.SIPOutboundTrunkInfo) {
res.Items = append(res.Items, items...)
page.AfterId = items[len(items)-1].SipTrunkId
},
page,
); err != nil {
return nil, fmt.Errorf("could not list SIP outbound trunks: %w", err)
}
return res, nil
}

return listAndPrint(ctx, cmd, list, req, []string{
"SipTrunkID", "Name",
"Address", "Transport",
"Numbers",
Expand Down
Loading