diff --git a/cmd/livekit-cli/main.go b/cmd/livekit-cli/main.go index 00398056..43870492 100644 --- a/cmd/livekit-cli/main.go +++ b/cmd/livekit-cli/main.go @@ -63,6 +63,7 @@ func main() { app.Commands = append(app.Commands, IngressCommands...) app.Commands = append(app.Commands, LoadTestCommands...) app.Commands = append(app.Commands, ProjectCommands...) + app.Commands = append(app.Commands, SIPCommands...) if err := app.Run(os.Args); err != nil { fmt.Println(err) diff --git a/cmd/livekit-cli/sip.go b/cmd/livekit-cli/sip.go new file mode 100644 index 00000000..20bbfa7f --- /dev/null +++ b/cmd/livekit-cli/sip.go @@ -0,0 +1,390 @@ +// Copyright 2023 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 ( + "context" + "fmt" + "os" + "strconv" + "strings" + + "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" +) + +const sipCategory = "SIP" + +var ( + SIPCommands = []*cli.Command{ + { + 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, + }, + ), + }, + { + Name: "list-sip-trunk", + Usage: "List all SIP trunk", + Before: createSIPClient, + Action: listSipTrunk, + Category: sipCategory, + Flags: withDefaultFlags(), + }, + { + Name: "delete-sip-trunk", + Usage: "Delete SIP Trunk", + Before: createSIPClient, + Action: deleteSIPTrunk, + Category: sipCategory, + Flags: withDefaultFlags( + &cli.StringFlag{ + Name: "id", + Usage: "SIPTrunk ID", + Required: true, + }, + ), + }, + + { + 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, + }, + ), + }, + { + Name: "list-sip-dispatch-rule", + Usage: "List all SIP Dispatch Rule", + Before: createSIPClient, + Action: listSipDispatchRule, + Category: sipCategory, + Flags: withDefaultFlags(), + }, + { + Name: "delete-sip-dispatch-rule", + Usage: "Delete SIP Dispatch Rule", + Before: createSIPClient, + Action: deleteSIPDispatchRule, + Category: sipCategory, + Flags: withDefaultFlags( + &cli.StringFlag{ + Name: "id", + Usage: "SIPDispatchRule ID", + Required: true, + }, + ), + }, + + { + 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, + }, + ), + }, + { + Name: "list-sip-participant", + Usage: "List all SIP Participant", + Before: createSIPClient, + Action: listSipParticipant, + Category: sipCategory, + Flags: withDefaultFlags(), + }, + { + Name: "delete-sip-participant", + Usage: "Delete SIP Participant", + Before: createSIPClient, + Action: deleteSIPParticipant, + Category: sipCategory, + Flags: withDefaultFlags( + &cli.StringFlag{ + Name: "id", + Usage: "SIPParticipant ID", + Required: true, + }, + ), + }, + } + + sipClient *lksdk.SIPClient +) + +func createSIPClient(c *cli.Context) error { + pc, err := loadProjectDetails(c) + if err != nil { + return err + } + + sipClient = lksdk.NewSIPClient(pc.URL, pc.APIKey, pc.APISecret) + return nil +} + +func createSIPTrunk(c *cli.Context) error { + reqFile := c.String("request") + reqBytes, err := os.ReadFile(reqFile) + if err != nil { + return err + } + + req := &livekit.CreateSIPTrunkRequest{} + err = protojson.Unmarshal(reqBytes, req) + if err != nil { + return err + } + + if c.Bool("verbose") { + PrintJSON(req) + } + + info, err := sipClient.CreateSIPTrunk(context.Background(), req) + if err != nil { + return err + } + + printSIPTrunkInfo(info) + return nil +} + +func userPass(user string, hasPass bool) string { + if user == "" && !hasPass { + return "" + } + passStr := "" + if hasPass { + passStr = "****" + } + return user + " / " + passStr +} + +func listSipTrunk(c *cli.Context) error { + res, err := sipClient.ListSIPTrunk(context.Background(), &livekit.ListSIPTrunkRequest{}) + if err != nil { + return err + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{ + "SipTrunkId", + "InboundAddresses", "InboundNumbersRegex", "InboundAuth", + "OutboundAddress", "OutboundNumber", "OutboundAuth", + }) + for _, item := range res.Items { + if item == nil { + continue + } + + table.Append([]string{ + item.SipTrunkId, + strings.Join(item.InboundAddresses, ","), strings.Join(item.InboundNumbersRegex, ","), userPass(item.InboundUsername, item.InboundPassword != ""), + item.OutboundAddress, item.OutboundNumber, userPass(item.OutboundUsername, item.OutboundPassword != ""), + }) + } + table.Render() + + if c.Bool("verbose") { + PrintJSON(res) + } + + return nil +} + +func deleteSIPTrunk(c *cli.Context) error { + info, err := sipClient.DeleteSIPTrunk(context.Background(), &livekit.DeleteSIPTrunkRequest{ + SipTrunkId: c.String("id"), + }) + if err != nil { + return err + } + + printSIPTrunkInfo(info) + return nil +} + +func printSIPTrunkInfo(info *livekit.SIPTrunkInfo) { + fmt.Printf("SIPTrunkID: %v\n", info.SipTrunkId) +} + +func createSIPDispatchRule(c *cli.Context) error { + reqFile := c.String("request") + reqBytes, err := os.ReadFile(reqFile) + if err != nil { + return err + } + + req := &livekit.CreateSIPDispatchRuleRequest{} + err = protojson.Unmarshal(reqBytes, req) + if err != nil { + return err + } + + if c.Bool("verbose") { + PrintJSON(req) + } + + info, err := sipClient.CreateSIPDispatchRule(context.Background(), req) + if err != nil { + return err + } + + printSIPDispatchRuleInfo(info) + return nil +} + +func listSipDispatchRule(c *cli.Context) error { + res, err := sipClient.ListSIPDispatchRule(context.Background(), &livekit.ListSIPDispatchRuleRequest{}) + if err != nil { + return err + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"SipDispatchRuleId", "SipTrunks", "Type", "RoomName", "Pin", "HidePhone"}) + for _, item := range res.Items { + if item == nil { + continue + } + var room, typ, pin string + switch r := item.GetRule().GetRule().(type) { + case *livekit.SIPDispatchRule_DispatchRuleDirect: + room = r.DispatchRuleDirect.RoomName + pin = r.DispatchRuleDirect.Pin + typ = "Direct" + case *livekit.SIPDispatchRule_DispatchRuleIndividual: + room = r.DispatchRuleIndividual.RoomPrefix + "*" + pin = r.DispatchRuleIndividual.Pin + typ = "Individual" + } + trunks := strings.Join(item.TrunkIds, ",") + if trunks == "" { + trunks = "" + } + table.Append([]string{item.SipDispatchRuleId, trunks, typ, room, pin, strconv.FormatBool(item.HidePhoneNumber)}) + } + table.Render() + + if c.Bool("verbose") { + PrintJSON(res) + } + + return nil +} + +func deleteSIPDispatchRule(c *cli.Context) error { + info, err := sipClient.DeleteSIPDispatchRule(context.Background(), &livekit.DeleteSIPDispatchRuleRequest{ + SipDispatchRuleId: c.String("id"), + }) + if err != nil { + return err + } + + printSIPDispatchRuleInfo(info) + return nil +} + +func printSIPDispatchRuleInfo(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) + if err != nil { + return err + } + + if c.Bool("verbose") { + PrintJSON(req) + } + + info, err := sipClient.CreateSIPParticipant(context.Background(), req) + if err != nil { + return err + } + + printSIPParticipantInfo(info) + return nil +} + +func listSipParticipant(c *cli.Context) error { + res, err := sipClient.ListSIPParticipant(context.Background(), &livekit.ListSIPParticipantRequest{}) + if err != nil { + return err + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"SipParticipantId", "SipTrunkId", "CallTo", "RoomName", "Identity"}) + for _, item := range res.Items { + if item == nil { + continue + } + + table.Append([]string{item.SipParticipantId, item.SipTrunkId, item.SipCallTo, item.RoomName, item.ParticipantIdentity}) + } + table.Render() + + if c.Bool("verbose") { + PrintJSON(res) + } + + return nil +} + +func deleteSIPParticipant(c *cli.Context) error { + info, err := sipClient.DeleteSIPParticipant(context.Background(), &livekit.DeleteSIPParticipantRequest{ + SipParticipantId: c.String("id"), + }) + if err != nil { + return err + } + + printSIPParticipantInfo(info) + return nil +} + +func printSIPParticipantInfo(info *livekit.SIPParticipantInfo) { + fmt.Printf("SIPParticipantID: %v\n", info.SipParticipantId) +} diff --git a/go.mod b/go.mod index e427f422..6f6202d2 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/ggwhite/go-masker v1.0.9 github.com/go-logr/logr v1.3.0 github.com/livekit/protocol v1.9.3 - github.com/livekit/server-sdk-go v1.1.3 + github.com/livekit/server-sdk-go v1.1.4 github.com/manifoldco/promptui v0.9.0 github.com/olekukonko/tablewriter v0.0.5 github.com/pion/rtcp v1.2.12 diff --git a/go.sum b/go.sum index 436ea0b2..5b6aa62a 100644 --- a/go.sum +++ b/go.sum @@ -89,8 +89,8 @@ github.com/livekit/protocol v1.9.3 h1:quHq/9dZ60ZDLFcWaY945qznO9ftgdCpHmaLfNEoi0 github.com/livekit/protocol v1.9.3/go.mod h1:8f342d5nvfNp9YAEfJokSR+zbNFpaivgU0h6vwaYhes= github.com/livekit/psrpc v0.5.2 h1:+MvG8Otm/J6MTg2MP/uuMbrkxOWsrj2hDhu/I1VIU1U= github.com/livekit/psrpc v0.5.2/go.mod h1:cQjxg1oCxYHhxxv6KJH1gSvdtCHQoRZCHgPdm5N8v2g= -github.com/livekit/server-sdk-go v1.1.3 h1:fTHXKSTvMa9N2c/eLu8u5XObhc/PfjyIt86qF+zTGCk= -github.com/livekit/server-sdk-go v1.1.3/go.mod h1:xfNiF6LjyG4nLT58cU/K9ZPuBB6Bgtyiy6Zn+VNZY70= +github.com/livekit/server-sdk-go v1.1.4 h1:gVTIRe8BNVyBpgOFfSbM3EdgPKN1oFwWNbks1bDF8RU= +github.com/livekit/server-sdk-go v1.1.4/go.mod h1:xfNiF6LjyG4nLT58cU/K9ZPuBB6Bgtyiy6Zn+VNZY70= github.com/mackerelio/go-osstat v0.2.4 h1:qxGbdPkFo65PXOb/F/nhDKpF2nGmGaCFDLXoZjJTtUs= github.com/mackerelio/go-osstat v0.2.4/go.mod h1:Zy+qzGdZs3A9cuIqmgbJvwbmLQH9dJvtio5ZjJTbdlQ= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=