From 0c717b3e411c6fd013cdf66a5e8b8e2181a1fbcd Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Mon, 17 Jun 2024 20:35:28 +0300 Subject: [PATCH 1/2] Support new SIP Trunk API. Move to subcommands. --- cmd/livekit-cli/proto.go | 55 +++++++ cmd/livekit-cli/sip.go | 346 ++++++++++++++++++++++++++++++++------- 2 files changed, 341 insertions(+), 60 deletions(-) create mode 100644 cmd/livekit-cli/proto.go diff --git a/cmd/livekit-cli/proto.go b/cmd/livekit-cli/proto.go new file mode 100644 index 00000000..719fe048 --- /dev/null +++ b/cmd/livekit-cli/proto.go @@ -0,0 +1,55 @@ +// Copyright 2024 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "os" + "reflect" + + "github.com/urfave/cli/v2" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +const flagRequest = "request" + +func ReadRequest[T any, P interface { + *T + proto.Message +}](c *cli.Context) (*T, error) { + reqBytes, err := os.ReadFile(c.String(flagRequest)) + if err != nil { + return nil, err + } + + var req P = new(T) + err = protojson.Unmarshal(reqBytes, req) + if err != nil { + return nil, err + } + return req, nil +} + +func RequestFlag[T any, _ interface { + *T + proto.Message +}]() *cli.StringFlag { + typ := reflect.TypeFor[T]().Name() + return &cli.StringFlag{ + Name: flagRequest, + Usage: typ + " as JSON file (see livekit-cli/examples)", + Required: true, + } +} diff --git a/cmd/livekit-cli/sip.go b/cmd/livekit-cli/sip.go index 43de3af1..ec175936 100644 --- a/cmd/livekit-cli/sip.go +++ b/cmd/livekit-cli/sip.go @@ -22,12 +22,10 @@ import ( "strings" "time" - "github.com/olekukonko/tablewriter" - "github.com/urfave/cli/v2" - "google.golang.org/protobuf/encoding/protojson" - "github.com/livekit/protocol/livekit" lksdk "github.com/livekit/server-sdk-go/v2" + "github.com/olekukonko/tablewriter" + "github.com/urfave/cli/v2" ) //lint:file-ignore SA1019 we still support older APIs for compatibility @@ -37,20 +35,165 @@ const sipCategory = "SIP" var ( SIPCommands = []*cli.Command{ { + Name: "sip", + Usage: "SIP management", + Category: sipCategory, + Subcommands: []*cli.Command{ + { + Name: "inbound", + Aliases: []string{"in", "inbound-trunk"}, + Usage: "Inbound SIP Trunk management", + Category: sipCategory, + Subcommands: []*cli.Command{ + { + Name: "list", + Usage: "List all inbound SIP Trunk", + Before: createSIPClient, + Action: listSipInboundTrunk, + Category: sipCategory, + Flags: withDefaultFlags(), + }, + { + Name: "create", + Usage: "Create a inbound SIP Trunk", + Before: createSIPClient, + Action: createSIPInboundTrunk, + Category: sipCategory, + Flags: withDefaultFlags( + RequestFlag[livekit.CreateSIPInboundTrunkRequest](), + ), + }, + { + Name: "delete", + Usage: "Delete inbound SIP Trunk", + Before: createSIPClient, + Action: deleteSIPTrunk, + Category: sipCategory, + Flags: withDefaultFlags( + &cli.StringFlag{ + Name: "id", + Usage: "SIPTrunk ID", + Required: true, + }, + ), + }, + }, + }, + { + Name: "outbound", + Aliases: []string{"out", "outbound-trunk"}, + Usage: "Outbound SIP Trunk management", + Category: sipCategory, + Subcommands: []*cli.Command{ + { + Name: "list", + Usage: "List all outbound SIP Trunk", + Before: createSIPClient, + Action: listSipOutboundTrunk, + Category: sipCategory, + Flags: withDefaultFlags(), + }, + { + Name: "create", + Usage: "Create a outbound SIP Trunk", + Before: createSIPClient, + Action: createSIPOutboundTrunk, + Category: sipCategory, + Flags: withDefaultFlags( + RequestFlag[livekit.CreateSIPOutboundTrunkRequest](), + ), + }, + { + Name: "delete", + Usage: "Delete outbound SIP Trunk", + Before: createSIPClient, + Action: deleteSIPTrunk, + Category: sipCategory, + Flags: withDefaultFlags( + &cli.StringFlag{ + Name: "id", + Usage: "SIPTrunk ID", + Required: true, + }, + ), + }, + }, + }, + { + Name: "dispatch", + Usage: "SIP Dispatch Rule management", + Aliases: []string{"dispatch-rule"}, + Category: sipCategory, + Subcommands: []*cli.Command{ + { + Name: "create", + Usage: "Create a SIP Dispatch Rule", + Before: createSIPClient, + Action: createSIPDispatchRule, + Category: sipCategory, + Flags: withDefaultFlags( + RequestFlag[livekit.CreateSIPDispatchRuleRequest](), + ), + }, + { + Name: "list", + Usage: "List all SIP Dispatch Rule", + Before: createSIPClient, + Action: listSipDispatchRule, + Category: sipCategory, + Flags: withDefaultFlags(), + }, + { + Name: "delete", + Usage: "Delete SIP Dispatch Rule", + Before: createSIPClient, + Action: deleteSIPDispatchRule, + Category: sipCategory, + Flags: withDefaultFlags( + &cli.StringFlag{ + Name: "id", + Usage: "SIPDispatchRule ID", + Required: true, + }, + ), + }, + }, + }, + { + Name: "participant", + Usage: "SIP Participant management", + Category: sipCategory, + Subcommands: []*cli.Command{ + { + Name: "create", + Usage: "Create a SIP Participant", + Before: createSIPClient, + Action: createSIPParticipant, + Category: sipCategory, + Flags: withDefaultFlags( + RequestFlag[livekit.CreateSIPParticipantRequest](), + ), + }, + }, + }, + }, + }, + + // Deprecated commands kept for compatibility + { + Hidden: true, // deprecated: use "sip trunk create" Name: "create-sip-trunk", Usage: "Create a SIP Trunk", Before: createSIPClient, Action: createSIPTrunk, Category: sipCategory, Flags: withDefaultFlags( - &cli.StringFlag{ - Name: "request", - Usage: "CreateSIPTrunkRequest as json file (see livekit-cli/examples)", - Required: true, - }, + //lint:ignore SA1019 we still support it + RequestFlag[livekit.CreateSIPTrunkRequest](), ), }, { + Hidden: true, // deprecated: use "sip trunk list" Name: "list-sip-trunk", Usage: "List all SIP trunk", Before: createSIPClient, @@ -59,6 +202,7 @@ var ( Flags: withDefaultFlags(), }, { + Hidden: true, // deprecated: use "sip trunk delete" Name: "delete-sip-trunk", Usage: "Delete SIP Trunk", Before: createSIPClient, @@ -72,22 +216,19 @@ var ( }, ), }, - { + Hidden: true, // deprecated: use "sip dispatch create" Name: "create-sip-dispatch-rule", Usage: "Create a SIP Dispatch Rule", Before: createSIPClient, Action: createSIPDispatchRule, Category: sipCategory, Flags: withDefaultFlags( - &cli.StringFlag{ - Name: "request", - Usage: "CreateSIPDispatchRuleRequest as json file (see livekit-cli/examples)", - Required: true, - }, + RequestFlag[livekit.CreateSIPDispatchRuleRequest](), ), }, { + Hidden: true, // deprecated: use "sip dispatch list" Name: "list-sip-dispatch-rule", Usage: "List all SIP Dispatch Rule", Before: createSIPClient, @@ -96,6 +237,7 @@ var ( Flags: withDefaultFlags(), }, { + Hidden: true, // deprecated: use "sip dispatch delete" Name: "delete-sip-dispatch-rule", Usage: "Delete SIP Dispatch Rule", Before: createSIPClient, @@ -109,19 +251,15 @@ var ( }, ), }, - { + Hidden: true, // deprecated: use "sip participant create" Name: "create-sip-participant", Usage: "Create a SIP Participant", Before: createSIPClient, Action: createSIPParticipant, Category: sipCategory, Flags: withDefaultFlags( - &cli.StringFlag{ - Name: "request", - Usage: "CreateSIPParticipantRequest as json file (see livekit-cli/examples)", - Required: true, - }, + RequestFlag[livekit.CreateSIPParticipantRequest](), ), }, } @@ -140,14 +278,47 @@ func createSIPClient(c *cli.Context) error { } func createSIPTrunk(c *cli.Context) error { - reqFile := c.String("request") - reqBytes, err := os.ReadFile(reqFile) + //lint:ignore SA1019 we still support it + req, err := ReadRequest[livekit.CreateSIPTrunkRequest](c) + if err != nil { + return err + } + + if c.Bool("verbose") { + PrintJSON(req) + } + + //lint:ignore SA1019 we still support it + info, err := sipClient.CreateSIPTrunk(c.Context, req) + if err != nil { + return err + } + + printSIPTrunkID(info) + return nil +} + +func createSIPInboundTrunk(c *cli.Context) error { + req, err := ReadRequest[livekit.CreateSIPInboundTrunkRequest](c) + if err != nil { + return err + } + + if c.Bool("verbose") { + PrintJSON(req) + } + + info, err := sipClient.CreateSIPInboundTrunk(c.Context, req) if err != nil { return err } - req := &livekit.CreateSIPTrunkRequest{} - err = protojson.Unmarshal(reqBytes, req) + printSIPTrunkID(info) + return nil +} + +func createSIPOutboundTrunk(c *cli.Context) error { + req, err := ReadRequest[livekit.CreateSIPOutboundTrunkRequest](c) if err != nil { return err } @@ -156,12 +327,12 @@ func createSIPTrunk(c *cli.Context) error { PrintJSON(req) } - info, err := sipClient.CreateSIPTrunk(context.Background(), req) + info, err := sipClient.CreateSIPOutboundTrunk(c.Context, req) if err != nil { return err } - printSIPTrunkInfo(info) + printSIPTrunkID(info) return nil } @@ -177,16 +348,17 @@ func userPass(user string, hasPass bool) string { } func listSipTrunk(c *cli.Context) error { - res, err := sipClient.ListSIPTrunk(context.Background(), &livekit.ListSIPTrunkRequest{}) + //lint:ignore SA1019 we still support it + res, err := sipClient.ListSIPTrunk(c.Context, &livekit.ListSIPTrunkRequest{}) if err != nil { return err } table := tablewriter.NewWriter(os.Stdout) table.SetHeader([]string{ - "SipTrunkId", "Name", - "InboundAddresses", "InboundNumbers", "InboundAuth", - "OutboundAddress", "OutboundNumber", "OutboundAuth", + "SipTrunkId", "Name", "Kind", "Number", + "AllowAddresses", "AllowNumbers", "InboundAuth", + "OutboundAddress", "OutboundAuth", "Metadata", }) for _, item := range res.Items { @@ -199,9 +371,9 @@ func listSipTrunk(c *cli.Context) error { } table.Append([]string{ - item.SipTrunkId, item.Name, + item.SipTrunkId, item.Name, strings.TrimPrefix(item.Kind.String(), "TRUNK_"), item.OutboundNumber, strings.Join(item.InboundAddresses, ","), strings.Join(inboundNumbers, ","), userPass(item.InboundUsername, item.InboundPassword != ""), - item.OutboundAddress, item.OutboundNumber, userPass(item.OutboundUsername, item.OutboundPassword != ""), + item.OutboundAddress, userPass(item.OutboundUsername, item.OutboundPassword != ""), item.Metadata, }) } @@ -214,31 +386,92 @@ func listSipTrunk(c *cli.Context) error { return nil } -func deleteSIPTrunk(c *cli.Context) error { - info, err := sipClient.DeleteSIPTrunk(context.Background(), &livekit.DeleteSIPTrunkRequest{ - SipTrunkId: c.String("id"), - }) +func listSipInboundTrunk(c *cli.Context) error { + res, err := sipClient.ListSIPInboundTrunk(c.Context, &livekit.ListSIPInboundTrunkRequest{}) if err != nil { return err } - printSIPTrunkInfo(info) + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{ + "SipTrunkId", "Name", "Numbers", + "AllowedAddresses", "AllowedNumbers", + "Authentication", + "Metadata", + }) + for _, item := range res.Items { + if item == nil { + continue + } + table.Append([]string{ + item.SipTrunkId, item.Name, strings.Join(item.Numbers, ","), + strings.Join(item.AllowedAddresses, ","), strings.Join(item.AllowedNumbers, ","), + userPass(item.AuthUsername, item.AuthPassword != ""), + item.Metadata, + }) + } + table.Render() + + if c.Bool("verbose") { + PrintJSON(res) + } + return nil } -func printSIPTrunkInfo(info *livekit.SIPTrunkInfo) { - fmt.Printf("SIPTrunkID: %v\n", info.SipTrunkId) +func listSipOutboundTrunk(c *cli.Context) error { + res, err := sipClient.ListSIPOutboundTrunk(c.Context, &livekit.ListSIPOutboundTrunkRequest{}) + if err != nil { + return err + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{ + "SipTrunkId", "Name", + "Address", "Transport", + "Numbers", + "Authentication", + "Metadata", + }) + for _, item := range res.Items { + if item == nil { + continue + } + table.Append([]string{ + item.SipTrunkId, item.Name, + item.Address, strings.TrimPrefix(item.Transport.String(), "SIP_TRANSPORT_"), + strings.Join(item.Numbers, ","), + userPass(item.AuthUsername, item.AuthPassword != ""), + item.Metadata, + }) + } + table.Render() + + if c.Bool("verbose") { + PrintJSON(res) + } + + return nil } -func createSIPDispatchRule(c *cli.Context) error { - reqFile := c.String("request") - reqBytes, err := os.ReadFile(reqFile) +func deleteSIPTrunk(c *cli.Context) error { + info, err := sipClient.DeleteSIPTrunk(c.Context, &livekit.DeleteSIPTrunkRequest{ + SipTrunkId: c.String("id"), + }) if err != nil { return err } - req := &livekit.CreateSIPDispatchRuleRequest{} - err = protojson.Unmarshal(reqBytes, req) + printSIPTrunkID(info) + return nil +} + +func printSIPTrunkID(info interface{ GetSipTrunkId() string }) { + fmt.Printf("SIPTrunkID: %v\n", info.GetSipTrunkId()) +} + +func createSIPDispatchRule(c *cli.Context) error { + req, err := ReadRequest[livekit.CreateSIPDispatchRuleRequest](c) if err != nil { return err } @@ -247,17 +480,17 @@ func createSIPDispatchRule(c *cli.Context) error { PrintJSON(req) } - info, err := sipClient.CreateSIPDispatchRule(context.Background(), req) + info, err := sipClient.CreateSIPDispatchRule(c.Context, req) if err != nil { return err } - printSIPDispatchRuleInfo(info) + printSIPDispatchRuleID(info) return nil } func listSipDispatchRule(c *cli.Context) error { - res, err := sipClient.ListSIPDispatchRule(context.Background(), &livekit.ListSIPDispatchRuleRequest{}) + res, err := sipClient.ListSIPDispatchRule(c.Context, &livekit.ListSIPDispatchRuleRequest{}) if err != nil { return err } @@ -295,30 +528,23 @@ func listSipDispatchRule(c *cli.Context) error { } func deleteSIPDispatchRule(c *cli.Context) error { - info, err := sipClient.DeleteSIPDispatchRule(context.Background(), &livekit.DeleteSIPDispatchRuleRequest{ + info, err := sipClient.DeleteSIPDispatchRule(c.Context, &livekit.DeleteSIPDispatchRuleRequest{ SipDispatchRuleId: c.String("id"), }) if err != nil { return err } - printSIPDispatchRuleInfo(info) + printSIPDispatchRuleID(info) return nil } -func printSIPDispatchRuleInfo(info *livekit.SIPDispatchRuleInfo) { +func printSIPDispatchRuleID(info *livekit.SIPDispatchRuleInfo) { fmt.Printf("SIPDispatchRuleID: %v\n", info.SipDispatchRuleId) } func createSIPParticipant(c *cli.Context) error { - reqFile := c.String("request") - reqBytes, err := os.ReadFile(reqFile) - if err != nil { - return err - } - - req := &livekit.CreateSIPParticipantRequest{} - err = protojson.Unmarshal(reqBytes, req) + req, err := ReadRequest[livekit.CreateSIPParticipantRequest](c) if err != nil { return err } @@ -328,7 +554,7 @@ func createSIPParticipant(c *cli.Context) error { } // CreateSIPParticipant will wait for LiveKit Participant to be created and that can take some time. - // Thus, we must set a higher deadline for it. + // Default deadline is too short, thus, we must set a higher deadline for it. ctx, cancel := context.WithTimeout(c.Context, 30*time.Second) defer cancel() From ece8257f37a6ec1f554382af5be2d7596097a7a3 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Tue, 25 Jun 2024 22:30:11 +0300 Subject: [PATCH 2/2] New SIP command now accept main args. Simplify code even more. --- cmd/livekit-cli/proto.go | 134 +++++++++++- cmd/livekit-cli/sip.go | 432 ++++++++++++++++----------------------- cmd/livekit-cli/utils.go | 2 +- 3 files changed, 305 insertions(+), 263 deletions(-) diff --git a/cmd/livekit-cli/proto.go b/cmd/livekit-cli/proto.go index 719fe048..06768092 100644 --- a/cmd/livekit-cli/proto.go +++ b/cmd/livekit-cli/proto.go @@ -15,9 +15,12 @@ package main import ( + "context" + "errors" "os" "reflect" + "github.com/olekukonko/tablewriter" "github.com/urfave/cli/v2" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" @@ -25,11 +28,17 @@ import ( const flagRequest = "request" -func ReadRequest[T any, P interface { +type protoType[T any] interface { *T proto.Message -}](c *cli.Context) (*T, error) { - reqBytes, err := os.ReadFile(c.String(flagRequest)) +} + +func ReadRequest[T any, P protoType[T]](c *cli.Context) (*T, error) { + return ReadRequestFile[T, P](c.String(flagRequest)) +} + +func ReadRequestFile[T any, P protoType[T]](path string) (*T, error) { + reqBytes, err := os.ReadFile(path) if err != nil { return nil, err } @@ -42,14 +51,121 @@ func ReadRequest[T any, P interface { return req, nil } -func RequestFlag[T any, _ interface { - *T - proto.Message -}]() *cli.StringFlag { - typ := reflect.TypeFor[T]().Name() +func RequestFlag[T any, P protoType[T]]() *cli.StringFlag { return &cli.StringFlag{ Name: flagRequest, - Usage: typ + " as JSON file (see livekit-cli/examples)", + Usage: RequestDesc[T, P](), Required: true, } } + +func RequestDesc[T any, _ protoType[T]]() string { + typ := reflect.TypeFor[T]().Name() + return typ + " as JSON file (see livekit-cli/examples)" +} + +func createAndPrint[T any, P protoType[T], R any]( + c *cli.Context, file string, + create func(ctx context.Context, p P) (R, error), + print func(r R), +) error { + req, err := ReadRequestFile[T, P](file) + if err != nil { + return err + } + if c.Bool("verbose") { + PrintJSON(req) + } + info, err := create(c.Context, req) + if err != nil { + return err + } + print(info) + return nil +} + +func createAndPrintLegacy[T any, P protoType[T], R any]( + c *cli.Context, + create func(ctx context.Context, p P) (R, error), + print func(r R), +) error { + req, err := ReadRequest[T, P](c) + if err != nil { + return err + } + if c.Bool("verbose") { + PrintJSON(req) + } + info, err := create(c.Context, req) + if err != nil { + return err + } + print(info) + return nil +} + +func createAndPrintReqs[T any, P protoType[T], R any]( + c *cli.Context, + create func(ctx context.Context, p P) (R, error), + print func(r R), +) error { + args := c.Args() + if !args.Present() { + return errors.New("at least one JSON request file is required") + } + for _, file := range args.Slice() { + if err := createAndPrint(c, file, create, print); err != nil { + return err + } + } + return nil +} + +func forEachID(c *cli.Context, fnc func(ctx context.Context, id string) error) error { + args := c.Args() + if !args.Present() { + return errors.New("at least one ID is required") + } + for _, id := range args.Slice() { + if err := fnc(c.Context, id); err != nil { + return err + } + } + return nil +} + +func listAndPrint[ + ReqT any, Req protoType[ReqT], + T any, _ protoType[T], + Resp interface { + proto.Message + GetItems() []*T + }, +]( + c *cli.Context, + getList func(ctx context.Context, req Req) (Resp, error), req Req, + header []string, tableRow func(item *T) []string, +) error { + res, err := getList(c.Context, req) + if err != nil { + return err + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader(header) + for _, item := range res.GetItems() { + if item == nil { + continue + } + row := tableRow(item) + if len(row) == 0 { + continue + } + table.Append(row) + } + table.Render() + if c.Bool("verbose") { + PrintJSON(res) + } + return nil +} diff --git a/cmd/livekit-cli/sip.go b/cmd/livekit-cli/sip.go index ec175936..3bc291ac 100644 --- a/cmd/livekit-cli/sip.go +++ b/cmd/livekit-cli/sip.go @@ -17,20 +17,23 @@ package main import ( "context" "fmt" - "os" "strconv" "strings" "time" "github.com/livekit/protocol/livekit" lksdk "github.com/livekit/server-sdk-go/v2" - "github.com/olekukonko/tablewriter" "github.com/urfave/cli/v2" ) //lint:file-ignore SA1019 we still support older APIs for compatibility -const sipCategory = "SIP" +const ( + sipCategory = "SIP" + sipTrunkCategory = "Trunks" + sipDispatchCategory = "Dispatch Rules" + sipParticipantCategory = "Participants" +) var ( SIPCommands = []*cli.Command{ @@ -43,39 +46,29 @@ var ( Name: "inbound", Aliases: []string{"in", "inbound-trunk"}, Usage: "Inbound SIP Trunk management", - Category: sipCategory, + Category: sipTrunkCategory, Subcommands: []*cli.Command{ { - Name: "list", - Usage: "List all inbound SIP Trunk", - Before: createSIPClient, - Action: listSipInboundTrunk, - Category: sipCategory, - Flags: withDefaultFlags(), + Name: "list", + Usage: "List all inbound SIP Trunk", + Action: listSipInboundTrunk, + Flags: withDefaultFlags(), }, { - Name: "create", - Usage: "Create a inbound SIP Trunk", - Before: createSIPClient, - Action: createSIPInboundTrunk, - Category: sipCategory, - Flags: withDefaultFlags( - RequestFlag[livekit.CreateSIPInboundTrunkRequest](), - ), + Name: "create", + Usage: "Create a inbound SIP Trunk", + Action: createSIPInboundTrunk, + Flags: withDefaultFlags(), + Args: true, + ArgsUsage: RequestDesc[livekit.CreateSIPInboundTrunkRequest](), }, { - Name: "delete", - Usage: "Delete inbound SIP Trunk", - Before: createSIPClient, - Action: deleteSIPTrunk, - Category: sipCategory, - Flags: withDefaultFlags( - &cli.StringFlag{ - Name: "id", - Usage: "SIPTrunk ID", - Required: true, - }, - ), + Name: "delete", + Usage: "Delete SIP Trunk", + Action: deleteSIPTrunk, + Flags: withDefaultFlags(), + Args: true, + ArgsUsage: "SIPTrunk ID to delete", }, }, }, @@ -83,39 +76,29 @@ var ( Name: "outbound", Aliases: []string{"out", "outbound-trunk"}, Usage: "Outbound SIP Trunk management", - Category: sipCategory, + Category: sipTrunkCategory, Subcommands: []*cli.Command{ { - Name: "list", - Usage: "List all outbound SIP Trunk", - Before: createSIPClient, - Action: listSipOutboundTrunk, - Category: sipCategory, - Flags: withDefaultFlags(), + Name: "list", + Usage: "List all outbound SIP Trunk", + Action: listSipOutboundTrunk, + Flags: withDefaultFlags(), }, { - Name: "create", - Usage: "Create a outbound SIP Trunk", - Before: createSIPClient, - Action: createSIPOutboundTrunk, - Category: sipCategory, - Flags: withDefaultFlags( - RequestFlag[livekit.CreateSIPOutboundTrunkRequest](), - ), + Name: "create", + Usage: "Create a outbound SIP Trunk", + Action: createSIPOutboundTrunk, + Flags: withDefaultFlags(), + Args: true, + ArgsUsage: RequestDesc[livekit.CreateSIPOutboundTrunkRequest](), }, { - Name: "delete", - Usage: "Delete outbound SIP Trunk", - Before: createSIPClient, - Action: deleteSIPTrunk, - Category: sipCategory, - Flags: withDefaultFlags( - &cli.StringFlag{ - Name: "id", - Usage: "SIPTrunk ID", - Required: true, - }, - ), + Name: "delete", + Usage: "Delete SIP Trunk", + Action: deleteSIPTrunk, + Flags: withDefaultFlags(), + Args: true, + ArgsUsage: "SIPTrunk ID to delete", }, }, }, @@ -123,56 +106,44 @@ var ( Name: "dispatch", Usage: "SIP Dispatch Rule management", Aliases: []string{"dispatch-rule"}, - Category: sipCategory, + Category: sipDispatchCategory, Subcommands: []*cli.Command{ { - Name: "create", - Usage: "Create a SIP Dispatch Rule", - Before: createSIPClient, - Action: createSIPDispatchRule, - Category: sipCategory, - Flags: withDefaultFlags( - RequestFlag[livekit.CreateSIPDispatchRuleRequest](), - ), + Name: "list", + Usage: "List all SIP Dispatch Rule", + Action: listSipDispatchRule, + Flags: withDefaultFlags(), }, { - Name: "list", - Usage: "List all SIP Dispatch Rule", - Before: createSIPClient, - Action: listSipDispatchRule, - Category: sipCategory, - Flags: withDefaultFlags(), + Name: "create", + Usage: "Create a SIP Dispatch Rule", + Action: createSIPDispatchRule, + Flags: withDefaultFlags(), + Args: true, + ArgsUsage: RequestDesc[livekit.CreateSIPDispatchRuleRequest](), }, { - Name: "delete", - Usage: "Delete SIP Dispatch Rule", - Before: createSIPClient, - Action: deleteSIPDispatchRule, - Category: sipCategory, - Flags: withDefaultFlags( - &cli.StringFlag{ - Name: "id", - Usage: "SIPDispatchRule ID", - Required: true, - }, - ), + Name: "delete", + Usage: "Delete SIP Dispatch Rule", + Action: deleteSIPDispatchRule, + Flags: withDefaultFlags(), + Args: true, + ArgsUsage: "SIPTrunk ID to delete", }, }, }, { Name: "participant", Usage: "SIP Participant management", - Category: sipCategory, + Category: sipParticipantCategory, Subcommands: []*cli.Command{ { - Name: "create", - Usage: "Create a SIP Participant", - Before: createSIPClient, - Action: createSIPParticipant, - Category: sipCategory, - Flags: withDefaultFlags( - RequestFlag[livekit.CreateSIPParticipantRequest](), - ), + Name: "create", + Usage: "Create a SIP Participant", + Action: createSIPParticipant, + Flags: withDefaultFlags(), + Args: true, + ArgsUsage: RequestDesc[livekit.CreateSIPParticipantRequest](), }, }, }, @@ -184,8 +155,7 @@ var ( Hidden: true, // deprecated: use "sip trunk create" Name: "create-sip-trunk", Usage: "Create a SIP Trunk", - Before: createSIPClient, - Action: createSIPTrunk, + Action: createSIPTrunkLegacy, Category: sipCategory, Flags: withDefaultFlags( //lint:ignore SA1019 we still support it @@ -196,7 +166,6 @@ var ( Hidden: true, // deprecated: use "sip trunk list" Name: "list-sip-trunk", Usage: "List all SIP trunk", - Before: createSIPClient, Action: listSipTrunk, Category: sipCategory, Flags: withDefaultFlags(), @@ -205,8 +174,7 @@ var ( Hidden: true, // deprecated: use "sip trunk delete" Name: "delete-sip-trunk", Usage: "Delete SIP Trunk", - Before: createSIPClient, - Action: deleteSIPTrunk, + Action: deleteSIPTrunkLegacy, Category: sipCategory, Flags: withDefaultFlags( &cli.StringFlag{ @@ -220,8 +188,7 @@ var ( Hidden: true, // deprecated: use "sip dispatch create" Name: "create-sip-dispatch-rule", Usage: "Create a SIP Dispatch Rule", - Before: createSIPClient, - Action: createSIPDispatchRule, + Action: createSIPDispatchRuleLegacy, Category: sipCategory, Flags: withDefaultFlags( RequestFlag[livekit.CreateSIPDispatchRuleRequest](), @@ -231,7 +198,6 @@ var ( Hidden: true, // deprecated: use "sip dispatch list" Name: "list-sip-dispatch-rule", Usage: "List all SIP Dispatch Rule", - Before: createSIPClient, Action: listSipDispatchRule, Category: sipCategory, Flags: withDefaultFlags(), @@ -240,8 +206,7 @@ var ( Hidden: true, // deprecated: use "sip dispatch delete" Name: "delete-sip-dispatch-rule", Usage: "Delete SIP Dispatch Rule", - Before: createSIPClient, - Action: deleteSIPDispatchRule, + Action: deleteSIPDispatchRuleLegacy, Category: sipCategory, Flags: withDefaultFlags( &cli.StringFlag{ @@ -255,85 +220,46 @@ var ( Hidden: true, // deprecated: use "sip participant create" Name: "create-sip-participant", Usage: "Create a SIP Participant", - Before: createSIPClient, - Action: createSIPParticipant, + Action: createSIPParticipantLegacy, Category: sipCategory, Flags: withDefaultFlags( RequestFlag[livekit.CreateSIPParticipantRequest](), ), }, } - - sipClient *lksdk.SIPClient ) -func createSIPClient(c *cli.Context) error { +func createSIPClient(c *cli.Context) (*lksdk.SIPClient, error) { pc, err := loadProjectDetails(c) if err != nil { - return err + return nil, err } - - sipClient = lksdk.NewSIPClient(pc.URL, pc.APIKey, pc.APISecret, withDefaultClientOpts(pc)...) - return nil + return lksdk.NewSIPClient(pc.URL, pc.APIKey, pc.APISecret, withDefaultClientOpts(pc)...), nil } -func createSIPTrunk(c *cli.Context) error { - //lint:ignore SA1019 we still support it - req, err := ReadRequest[livekit.CreateSIPTrunkRequest](c) +func createSIPTrunkLegacy(c *cli.Context) error { + cli, err := createSIPClient(c) if err != nil { return err } - - if c.Bool("verbose") { - PrintJSON(req) - } - //lint:ignore SA1019 we still support it - info, err := sipClient.CreateSIPTrunk(c.Context, req) - if err != nil { - return err - } - - printSIPTrunkID(info) - return nil + return createAndPrintLegacy(c, cli.CreateSIPTrunk, printSIPTrunkID) } func createSIPInboundTrunk(c *cli.Context) error { - req, err := ReadRequest[livekit.CreateSIPInboundTrunkRequest](c) + cli, err := createSIPClient(c) if err != nil { return err } - - if c.Bool("verbose") { - PrintJSON(req) - } - - info, err := sipClient.CreateSIPInboundTrunk(c.Context, req) - if err != nil { - return err - } - - printSIPTrunkID(info) - return nil + return createAndPrintReqs(c, cli.CreateSIPInboundTrunk, printSIPInboundTrunkID) } func createSIPOutboundTrunk(c *cli.Context) error { - req, err := ReadRequest[livekit.CreateSIPOutboundTrunkRequest](c) + cli, err := createSIPClient(c) if err != nil { return err } - - if c.Bool("verbose") { - PrintJSON(req) - } - - info, err := sipClient.CreateSIPOutboundTrunk(c.Context, req) - if err != nil { - return err - } - - printSIPTrunkID(info) - return nil + return createAndPrintReqs(c, cli.CreateSIPOutboundTrunk, printSIPOutboundTrunkID) } func userPass(user string, hasPass bool) string { @@ -348,159 +274,140 @@ func userPass(user string, hasPass bool) string { } func listSipTrunk(c *cli.Context) error { - //lint:ignore SA1019 we still support it - res, err := sipClient.ListSIPTrunk(c.Context, &livekit.ListSIPTrunkRequest{}) + cli, err := createSIPClient(c) if err != nil { return err } - - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{ + //lint:ignore SA1019 we still support it + return listAndPrint(c, cli.ListSIPTrunk, &livekit.ListSIPTrunkRequest{}, []string{ "SipTrunkId", "Name", "Kind", "Number", "AllowAddresses", "AllowNumbers", "InboundAuth", "OutboundAddress", "OutboundAuth", "Metadata", - }) - for _, item := range res.Items { - if item == nil { - continue - } + }, func(item *livekit.SIPTrunkInfo) []string { inboundNumbers := item.InboundNumbers for _, re := range item.InboundNumbersRegex { inboundNumbers = append(inboundNumbers, "regexp("+re+")") } - - table.Append([]string{ + return []string{ item.SipTrunkId, item.Name, strings.TrimPrefix(item.Kind.String(), "TRUNK_"), item.OutboundNumber, strings.Join(item.InboundAddresses, ","), strings.Join(inboundNumbers, ","), userPass(item.InboundUsername, item.InboundPassword != ""), item.OutboundAddress, userPass(item.OutboundUsername, item.OutboundPassword != ""), item.Metadata, - }) - } - table.Render() - - if c.Bool("verbose") { - PrintJSON(res) - } - - return nil + } + }) } func listSipInboundTrunk(c *cli.Context) error { - res, err := sipClient.ListSIPInboundTrunk(c.Context, &livekit.ListSIPInboundTrunkRequest{}) + cli, err := createSIPClient(c) if err != nil { return err } - - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{ + return listAndPrint(c, cli.ListSIPInboundTrunk, &livekit.ListSIPInboundTrunkRequest{}, []string{ "SipTrunkId", "Name", "Numbers", "AllowedAddresses", "AllowedNumbers", "Authentication", "Metadata", - }) - for _, item := range res.Items { - if item == nil { - continue - } - table.Append([]string{ + }, func(item *livekit.SIPInboundTrunkInfo) []string { + return []string{ item.SipTrunkId, item.Name, strings.Join(item.Numbers, ","), strings.Join(item.AllowedAddresses, ","), strings.Join(item.AllowedNumbers, ","), userPass(item.AuthUsername, item.AuthPassword != ""), item.Metadata, - }) - } - table.Render() - - if c.Bool("verbose") { - PrintJSON(res) - } - - return nil + } + }) } func listSipOutboundTrunk(c *cli.Context) error { - res, err := sipClient.ListSIPOutboundTrunk(c.Context, &livekit.ListSIPOutboundTrunkRequest{}) + cli, err := createSIPClient(c) if err != nil { return err } - - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{ + return listAndPrint(c, cli.ListSIPOutboundTrunk, &livekit.ListSIPOutboundTrunkRequest{}, []string{ "SipTrunkId", "Name", "Address", "Transport", "Numbers", "Authentication", "Metadata", - }) - for _, item := range res.Items { - if item == nil { - continue - } - table.Append([]string{ + }, func(item *livekit.SIPOutboundTrunkInfo) []string { + return []string{ item.SipTrunkId, item.Name, item.Address, strings.TrimPrefix(item.Transport.String(), "SIP_TRANSPORT_"), strings.Join(item.Numbers, ","), userPass(item.AuthUsername, item.AuthPassword != ""), item.Metadata, - }) - } - table.Render() + } + }) +} - if c.Bool("verbose") { - PrintJSON(res) +func deleteSIPTrunk(c *cli.Context) error { + cli, err := createSIPClient(c) + if err != nil { + return err } - - return nil + return forEachID(c, func(ctx context.Context, id string) error { + info, err := cli.DeleteSIPTrunk(c.Context, &livekit.DeleteSIPTrunkRequest{ + SipTrunkId: id, + }) + if err != nil { + return err + } + printSIPTrunkID(info) + return nil + }) } -func deleteSIPTrunk(c *cli.Context) error { - info, err := sipClient.DeleteSIPTrunk(c.Context, &livekit.DeleteSIPTrunkRequest{ +func deleteSIPTrunkLegacy(c *cli.Context) error { + cli, err := createSIPClient(c) + if err != nil { + return err + } + info, err := cli.DeleteSIPTrunk(c.Context, &livekit.DeleteSIPTrunkRequest{ SipTrunkId: c.String("id"), }) if err != nil { return err } - printSIPTrunkID(info) return nil } -func printSIPTrunkID(info interface{ GetSipTrunkId() string }) { +func printSIPTrunkID(info *livekit.SIPTrunkInfo) { + fmt.Printf("SIPTrunkID: %v\n", info.GetSipTrunkId()) +} + +func printSIPInboundTrunkID(info *livekit.SIPInboundTrunkInfo) { + fmt.Printf("SIPTrunkID: %v\n", info.GetSipTrunkId()) +} + +func printSIPOutboundTrunkID(info *livekit.SIPOutboundTrunkInfo) { fmt.Printf("SIPTrunkID: %v\n", info.GetSipTrunkId()) } func createSIPDispatchRule(c *cli.Context) error { - req, err := ReadRequest[livekit.CreateSIPDispatchRuleRequest](c) + cli, err := createSIPClient(c) if err != nil { return err } + return createAndPrintReqs(c, cli.CreateSIPDispatchRule, printSIPDispatchRuleID) +} - if c.Bool("verbose") { - PrintJSON(req) - } - - info, err := sipClient.CreateSIPDispatchRule(c.Context, req) +func createSIPDispatchRuleLegacy(c *cli.Context) error { + cli, err := createSIPClient(c) if err != nil { return err } - - printSIPDispatchRuleID(info) - return nil + return createAndPrintLegacy(c, cli.CreateSIPDispatchRule, printSIPDispatchRuleID) } func listSipDispatchRule(c *cli.Context) error { - res, err := sipClient.ListSIPDispatchRule(c.Context, &livekit.ListSIPDispatchRuleRequest{}) + cli, err := createSIPClient(c) if err != nil { return err } - - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"SipDispatchRuleId", "Name", "SipTrunks", "Type", "RoomName", "Pin", "HidePhone", "Metadata"}) - for _, item := range res.Items { - if item == nil { - continue - } + return listAndPrint(c, cli.ListSIPDispatchRule, &livekit.ListSIPDispatchRuleRequest{}, []string{ + "SipDispatchRuleId", "Name", "SipTrunks", "Type", "RoomName", "Pin", "HidePhone", "Metadata", + }, func(item *livekit.SIPDispatchRuleInfo) []string { var room, typ, pin string switch r := item.GetRule().GetRule().(type) { case *livekit.SIPDispatchRule_DispatchRuleDirect: @@ -516,25 +423,38 @@ func listSipDispatchRule(c *cli.Context) error { if trunks == "" { trunks = "" } - table.Append([]string{item.SipDispatchRuleId, item.Name, trunks, typ, room, pin, strconv.FormatBool(item.HidePhoneNumber), item.Metadata}) - } - table.Render() + return []string{item.SipDispatchRuleId, item.Name, trunks, typ, room, pin, strconv.FormatBool(item.HidePhoneNumber), item.Metadata} + }) +} - if c.Bool("verbose") { - PrintJSON(res) +func deleteSIPDispatchRule(c *cli.Context) error { + cli, err := createSIPClient(c) + if err != nil { + return err } - - return nil + return forEachID(c, func(ctx context.Context, id string) error { + info, err := cli.DeleteSIPDispatchRule(c.Context, &livekit.DeleteSIPDispatchRuleRequest{ + SipDispatchRuleId: id, + }) + if err != nil { + return err + } + printSIPDispatchRuleID(info) + return nil + }) } -func deleteSIPDispatchRule(c *cli.Context) error { - info, err := sipClient.DeleteSIPDispatchRule(c.Context, &livekit.DeleteSIPDispatchRuleRequest{ +func deleteSIPDispatchRuleLegacy(c *cli.Context) error { + cli, err := createSIPClient(c) + if err != nil { + return err + } + info, err := cli.DeleteSIPDispatchRule(c.Context, &livekit.DeleteSIPDispatchRuleRequest{ SipDispatchRuleId: c.String("id"), }) if err != nil { return err } - printSIPDispatchRuleID(info) return nil } @@ -544,27 +464,33 @@ func printSIPDispatchRuleID(info *livekit.SIPDispatchRuleInfo) { } func createSIPParticipant(c *cli.Context) error { - req, err := ReadRequest[livekit.CreateSIPParticipantRequest](c) + cli, err := createSIPClient(c) if err != nil { return err } + return createAndPrintReqs(c, func(ctx context.Context, req *livekit.CreateSIPParticipantRequest) (*livekit.SIPParticipantInfo, error) { + // CreateSIPParticipant will wait for LiveKit Participant to be created and that can take some time. + // Default deadline is too short, thus, we must set a higher deadline for it. + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() - if c.Bool("verbose") { - PrintJSON(req) - } - - // CreateSIPParticipant will wait for LiveKit Participant to be created and that can take some time. - // Default deadline is too short, thus, we must set a higher deadline for it. - ctx, cancel := context.WithTimeout(c.Context, 30*time.Second) - defer cancel() + return cli.CreateSIPParticipant(ctx, req) + }, printSIPParticipantInfo) +} - info, err := sipClient.CreateSIPParticipant(ctx, req) +func createSIPParticipantLegacy(c *cli.Context) error { + cli, err := createSIPClient(c) if err != nil { return err } + return createAndPrintLegacy(c, func(ctx context.Context, req *livekit.CreateSIPParticipantRequest) (*livekit.SIPParticipantInfo, error) { + // CreateSIPParticipant will wait for LiveKit Participant to be created and that can take some time. + // Default deadline is too short, thus, we must set a higher deadline for it. + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() - printSIPParticipantInfo(info) - return nil + return cli.CreateSIPParticipant(ctx, req) + }, printSIPParticipantInfo) } func printSIPParticipantInfo(info *livekit.SIPParticipantInfo) { diff --git a/cmd/livekit-cli/utils.go b/cmd/livekit-cli/utils.go index 0d6bc63c..09f502cd 100644 --- a/cmd/livekit-cli/utils.go +++ b/cmd/livekit-cli/utils.go @@ -96,7 +96,7 @@ func withDefaultClientOpts(c *config.ProjectConfig) []twirp.ClientOption { return opts } -func PrintJSON(obj interface{}) { +func PrintJSON(obj any) { txt, _ := json.MarshalIndent(obj, "", " ") fmt.Println(string(txt)) }