2023-09-18 13:59:28 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2023-09-18 16:52:28 +02:00
|
|
|
"fmt"
|
2023-09-19 19:29:35 +02:00
|
|
|
ipcRpc "net/rpc"
|
2023-09-18 16:52:28 +02:00
|
|
|
"os"
|
|
|
|
|
|
|
|
"github.com/akamensky/argparse"
|
2024-01-02 00:55:50 +01:00
|
|
|
"github.com/tim-beatham/smegmesh/pkg/ctrlserver"
|
|
|
|
graph "github.com/tim-beatham/smegmesh/pkg/dot"
|
|
|
|
"github.com/tim-beatham/smegmesh/pkg/ipc"
|
|
|
|
logging "github.com/tim-beatham/smegmesh/pkg/log"
|
2023-09-18 13:59:28 +02:00
|
|
|
)
|
|
|
|
|
2023-09-18 18:00:43 +02:00
|
|
|
const SockAddr = "/tmp/wgmesh_ipc.sock"
|
|
|
|
|
2023-10-28 17:38:25 +02:00
|
|
|
type CreateMeshParams struct {
|
2023-12-12 12:58:47 +01:00
|
|
|
Client *ipcRpc.Client
|
|
|
|
Endpoint string
|
|
|
|
WgArgs ipc.WireGuardArgs
|
|
|
|
AdvertiseRoutes bool
|
|
|
|
AdvertiseDefault bool
|
2023-10-28 17:38:25 +02:00
|
|
|
}
|
|
|
|
|
2024-01-02 00:55:50 +01:00
|
|
|
func createMesh(client *ipc.SmegmeshIpc, args *ipc.NewMeshArgs) {
|
2023-09-18 18:00:43 +02:00
|
|
|
var reply string
|
2023-12-31 15:25:06 +01:00
|
|
|
err := client.CreateMesh(args, &reply)
|
2023-09-18 18:00:43 +02:00
|
|
|
|
|
|
|
if err != nil {
|
2023-12-31 15:25:06 +01:00
|
|
|
fmt.Println(err.Error())
|
|
|
|
return
|
2023-09-18 18:00:43 +02:00
|
|
|
}
|
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
fmt.Println(reply)
|
2023-09-18 18:00:43 +02:00
|
|
|
}
|
|
|
|
|
2024-01-02 00:55:50 +01:00
|
|
|
func listMeshes(client *ipc.SmegmeshIpc) {
|
2023-10-06 19:25:38 +02:00
|
|
|
reply := new(ipc.ListMeshReply)
|
2023-09-19 14:45:49 +02:00
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
err := client.ListMeshes(reply)
|
2023-09-19 14:45:49 +02:00
|
|
|
|
|
|
|
if err != nil {
|
2023-10-24 01:12:38 +02:00
|
|
|
logging.Log.WriteErrorf(err.Error())
|
2023-09-19 14:45:49 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-10-06 19:25:38 +02:00
|
|
|
for _, meshId := range reply.Meshes {
|
|
|
|
fmt.Println(meshId)
|
2023-09-19 14:45:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-02 00:55:50 +01:00
|
|
|
func joinMesh(client *ipc.SmegmeshIpc, args ipc.JoinMeshArgs) {
|
2023-09-19 19:29:35 +02:00
|
|
|
var reply string
|
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
err := client.JoinMesh(args, &reply)
|
2023-09-19 19:29:35 +02:00
|
|
|
|
2023-09-20 00:50:44 +02:00
|
|
|
if err != nil {
|
2023-12-31 15:25:06 +01:00
|
|
|
fmt.Println(err.Error())
|
2023-09-20 00:50:44 +02:00
|
|
|
}
|
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
fmt.Println(reply)
|
2023-09-20 00:50:44 +02:00
|
|
|
}
|
|
|
|
|
2024-01-02 00:55:50 +01:00
|
|
|
func leaveMesh(client *ipc.SmegmeshIpc, meshId string) {
|
2023-10-28 17:38:25 +02:00
|
|
|
var reply string
|
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
err := client.LeaveMesh(meshId, &reply)
|
2023-10-28 17:38:25 +02:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Println(reply)
|
|
|
|
}
|
|
|
|
|
2024-01-02 00:55:50 +01:00
|
|
|
func getGraph(client *ipc.SmegmeshIpc) {
|
2023-12-25 02:25:20 +01:00
|
|
|
listMeshesReply := new(ipc.ListMeshReply)
|
2023-10-22 14:34:49 +02:00
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
err := client.ListMeshes(listMeshesReply)
|
2023-10-22 14:34:49 +02:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-12-25 02:25:20 +01:00
|
|
|
meshes := make(map[string][]ctrlserver.MeshNode)
|
|
|
|
|
|
|
|
for _, meshId := range listMeshesReply.Meshes {
|
|
|
|
var meshReply ipc.GetMeshReply
|
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
err := client.GetMesh(meshId, &meshReply)
|
2023-12-25 02:25:20 +01:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
meshes[meshId] = meshReply.Nodes
|
|
|
|
}
|
|
|
|
|
|
|
|
dotGenerator := graph.NewMeshGraphConverter(meshes)
|
|
|
|
dot, err := dotGenerator.Generate()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Println(dot)
|
2023-10-22 14:34:49 +02:00
|
|
|
}
|
|
|
|
|
2024-01-02 00:55:50 +01:00
|
|
|
func queryMesh(client *ipc.SmegmeshIpc, meshId, query string) {
|
2023-10-30 20:02:28 +01:00
|
|
|
var reply string
|
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
args := ipc.QueryMesh{
|
|
|
|
MeshId: meshId,
|
|
|
|
Query: query,
|
|
|
|
}
|
|
|
|
|
|
|
|
err := client.Query(args, &reply)
|
2023-10-30 20:02:28 +01:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Println(reply)
|
|
|
|
}
|
|
|
|
|
2024-01-02 00:55:50 +01:00
|
|
|
func putDescription(client *ipc.SmegmeshIpc, meshId, description string) {
|
2023-11-01 12:58:10 +01:00
|
|
|
var reply string
|
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
err := client.PutDescription(ipc.PutDescriptionArgs{
|
|
|
|
MeshId: meshId,
|
|
|
|
Description: description,
|
|
|
|
}, &reply)
|
2023-11-01 12:58:10 +01:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Println(reply)
|
|
|
|
}
|
|
|
|
|
2023-11-17 20:05:21 +01:00
|
|
|
// putAlias: puts an alias for the node
|
2024-01-02 00:55:50 +01:00
|
|
|
func putAlias(client *ipc.SmegmeshIpc, meshid, alias string) {
|
2023-11-17 20:05:21 +01:00
|
|
|
var reply string
|
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
err := client.PutAlias(ipc.PutAliasArgs{
|
|
|
|
MeshId: meshid,
|
|
|
|
Alias: alias,
|
|
|
|
}, &reply)
|
2023-11-17 20:05:21 +01:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Println(reply)
|
|
|
|
}
|
|
|
|
|
2024-01-02 00:55:50 +01:00
|
|
|
func setService(client *ipc.SmegmeshIpc, meshId, service, value string) {
|
2023-11-17 23:13:51 +01:00
|
|
|
var reply string
|
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
err := client.PutService(ipc.PutServiceArgs{
|
|
|
|
MeshId: meshId,
|
2023-11-17 23:13:51 +01:00
|
|
|
Service: service,
|
|
|
|
Value: value,
|
2023-12-31 15:25:06 +01:00
|
|
|
}, &reply)
|
2023-11-17 23:13:51 +01:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Println(reply)
|
|
|
|
}
|
|
|
|
|
2024-01-02 00:55:50 +01:00
|
|
|
func deleteService(client *ipc.SmegmeshIpc, meshId, service string) {
|
2023-11-17 23:13:51 +01:00
|
|
|
var reply string
|
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
err := client.DeleteService(ipc.DeleteServiceArgs{
|
|
|
|
MeshId: meshId,
|
|
|
|
Service: service,
|
|
|
|
}, &reply)
|
2023-11-17 23:13:51 +01:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Println(reply)
|
|
|
|
}
|
|
|
|
|
2023-09-18 16:52:28 +02:00
|
|
|
func main() {
|
2024-01-02 00:55:50 +01:00
|
|
|
parser := argparse.NewParser("smgctl",
|
|
|
|
"smegctl Manipulate WireGuard mesh networks")
|
2023-09-18 16:52:28 +02:00
|
|
|
|
|
|
|
newMeshCmd := parser.NewCommand("new-mesh", "Create a new mesh")
|
2023-09-19 14:45:49 +02:00
|
|
|
listMeshCmd := parser.NewCommand("list-meshes", "List meshes the node is connected to")
|
|
|
|
joinMeshCmd := parser.NewCommand("join-mesh", "Join a mesh network")
|
2023-10-22 14:34:49 +02:00
|
|
|
getGraphCmd := parser.NewCommand("get-graph", "Convert a mesh into DOT format")
|
2023-10-28 17:38:25 +02:00
|
|
|
leaveMeshCmd := parser.NewCommand("leave-mesh", "Leave a mesh network")
|
2023-10-30 20:02:28 +01:00
|
|
|
queryMeshCmd := parser.NewCommand("query-mesh", "Query a mesh network using JMESPath")
|
2023-11-01 12:58:10 +01:00
|
|
|
putDescriptionCmd := parser.NewCommand("put-description", "Place a description for the node")
|
2023-11-17 20:05:21 +01:00
|
|
|
putAliasCmd := parser.NewCommand("put-alias", "Place an alias for the node")
|
2023-11-17 23:13:51 +01:00
|
|
|
setServiceCmd := parser.NewCommand("set-service", "Place a service into your advertisements")
|
|
|
|
deleteServiceCmd := parser.NewCommand("delete-service", "Remove a service from your advertisements")
|
2023-10-28 17:38:25 +02:00
|
|
|
|
2023-12-12 12:58:47 +01:00
|
|
|
var newMeshPort *int = newMeshCmd.Int("p", "wgport", &argparse.Options{
|
|
|
|
Default: 0,
|
|
|
|
Help: "WireGuard port to use to the interface. A default of 0 uses an unused ephmeral port.",
|
|
|
|
})
|
|
|
|
|
|
|
|
var newMeshEndpoint *string = newMeshCmd.String("e", "endpoint", &argparse.Options{
|
|
|
|
Help: "Publicly routeable endpoint to advertise within the mesh",
|
|
|
|
})
|
|
|
|
|
|
|
|
var newMeshRole *string = newMeshCmd.Selector("r", "role", []string{"peer", "client"}, &argparse.Options{
|
|
|
|
Help: "Role in the mesh network. A value of peer means that the node is publicly routeable and thus considered" +
|
|
|
|
" in the gossip protocol. Client means that the node is not publicly routeable and is not a candidate in the gossip" +
|
|
|
|
" protocol",
|
|
|
|
})
|
|
|
|
var newMeshKeepAliveWg *int = newMeshCmd.Int("k", "KeepAliveWg", &argparse.Options{
|
|
|
|
Default: 0,
|
|
|
|
Help: "WireGuard KeepAlive value for NAT traversal and firewall holepunching",
|
|
|
|
})
|
|
|
|
|
|
|
|
var newMeshAdvertiseRoutes *bool = newMeshCmd.Flag("a", "advertise", &argparse.Options{
|
|
|
|
Help: "Advertise routes to other mesh network into the mesh",
|
|
|
|
})
|
|
|
|
|
|
|
|
var newMeshAdvertiseDefaults *bool = newMeshCmd.Flag("d", "defaults", &argparse.Options{
|
|
|
|
Help: "Advertise ::/0 into the mesh network",
|
|
|
|
})
|
|
|
|
|
|
|
|
var joinMeshId *string = joinMeshCmd.String("m", "meshid", &argparse.Options{
|
|
|
|
Required: true,
|
|
|
|
Help: "MeshID of the mesh network to join",
|
|
|
|
})
|
|
|
|
|
|
|
|
var joinMeshIpAddress *string = joinMeshCmd.String("i", "ip", &argparse.Options{
|
|
|
|
Required: true,
|
|
|
|
Help: "IP address of the bootstrapping node to join through",
|
|
|
|
})
|
|
|
|
|
|
|
|
var joinMeshEndpoint *string = joinMeshCmd.String("e", "endpoint", &argparse.Options{
|
|
|
|
Help: "Publicly routeable endpoint to advertise within the mesh",
|
|
|
|
})
|
|
|
|
|
|
|
|
var joinMeshRole *string = joinMeshCmd.Selector("r", "role", []string{"peer", "client"}, &argparse.Options{
|
|
|
|
Help: "Role in the mesh network. A value of peer means that the node is publicly routeable and thus considered" +
|
|
|
|
" in the gossip protocol. Client means that the node is not publicly routeable and is not a candidate in the gossip" +
|
|
|
|
" protocol",
|
|
|
|
})
|
|
|
|
|
|
|
|
var joinMeshPort *int = joinMeshCmd.Int("p", "wgport", &argparse.Options{
|
|
|
|
Default: 0,
|
|
|
|
Help: "WireGuard port to use to the interface. A default of 0 uses an unused ephmeral port.",
|
|
|
|
})
|
|
|
|
|
|
|
|
var joinMeshKeepAliveWg *int = joinMeshCmd.Int("k", "KeepAliveWg", &argparse.Options{
|
|
|
|
Default: 0,
|
|
|
|
Help: "WireGuard KeepAlive value for NAT traversal and firewall ho;lepunching",
|
|
|
|
})
|
|
|
|
|
|
|
|
var joinMeshAdvertiseRoutes *bool = joinMeshCmd.Flag("a", "advertise", &argparse.Options{
|
|
|
|
Help: "Advertise routes to other mesh network into the mesh",
|
|
|
|
})
|
|
|
|
|
|
|
|
var joinMeshAdvertiseDefaults *bool = joinMeshCmd.Flag("d", "defaults", &argparse.Options{
|
|
|
|
Help: "Advertise ::/0 into the mesh network",
|
|
|
|
})
|
|
|
|
|
|
|
|
var leaveMeshMeshId *string = leaveMeshCmd.String("m", "mesh", &argparse.Options{
|
|
|
|
Required: true,
|
|
|
|
Help: "MeshID of the mesh to leave",
|
|
|
|
})
|
|
|
|
|
|
|
|
var queryMeshMeshId *string = queryMeshCmd.String("m", "mesh", &argparse.Options{
|
|
|
|
Required: true,
|
|
|
|
Help: "MeshID of the mesh to query",
|
|
|
|
})
|
|
|
|
var queryMeshQuery *string = queryMeshCmd.String("q", "query", &argparse.Options{
|
|
|
|
Required: true,
|
|
|
|
Help: "JMESPath Query Of The Mesh Network To Query",
|
|
|
|
})
|
|
|
|
|
|
|
|
var description *string = putDescriptionCmd.String("d", "description", &argparse.Options{
|
|
|
|
Required: true,
|
|
|
|
Help: "Description of the node in the mesh",
|
|
|
|
})
|
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
var descriptionMeshId *string = putDescriptionCmd.String("m", "meshid", &argparse.Options{
|
|
|
|
Required: true,
|
|
|
|
Help: "MeshID of the mesh network to join",
|
|
|
|
})
|
|
|
|
|
|
|
|
var aliasMeshId *string = putAliasCmd.String("m", "meshid", &argparse.Options{
|
|
|
|
Required: true,
|
|
|
|
Help: "MeshID of the mesh network to join",
|
|
|
|
})
|
|
|
|
|
2023-12-12 12:58:47 +01:00
|
|
|
var alias *string = putAliasCmd.String("a", "alias", &argparse.Options{
|
|
|
|
Required: true,
|
|
|
|
Help: "Alias of the node to set can be used in DNS to lookup an IP address",
|
|
|
|
})
|
|
|
|
|
|
|
|
var serviceKey *string = setServiceCmd.String("s", "service", &argparse.Options{
|
|
|
|
Required: true,
|
|
|
|
Help: "Key of the service to advertise in the mesh network",
|
|
|
|
})
|
|
|
|
var serviceValue *string = setServiceCmd.String("v", "value", &argparse.Options{
|
|
|
|
Required: true,
|
|
|
|
Help: "Value of the service to advertise in the mesh network",
|
|
|
|
})
|
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
var serviceMeshId *string = setServiceCmd.String("m", "meshid", &argparse.Options{
|
|
|
|
Required: true,
|
|
|
|
Help: "MeshID of the mesh network to join",
|
|
|
|
})
|
|
|
|
|
2023-12-12 12:58:47 +01:00
|
|
|
var deleteServiceKey *string = deleteServiceCmd.String("s", "service", &argparse.Options{
|
|
|
|
Required: true,
|
|
|
|
Help: "Key of the service to remove",
|
|
|
|
})
|
2023-11-20 12:28:12 +01:00
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
var deleteServiceMeshid *string = deleteServiceCmd.String("m", "meshid", &argparse.Options{
|
|
|
|
Required: true,
|
|
|
|
Help: "MeshID of the mesh network to join",
|
|
|
|
})
|
|
|
|
|
2023-09-18 16:52:28 +02:00
|
|
|
err := parser.Parse(os.Args)
|
2023-09-18 13:59:28 +02:00
|
|
|
|
2023-09-18 16:52:28 +02:00
|
|
|
if err != nil {
|
|
|
|
fmt.Print(parser.Usage(err))
|
|
|
|
return
|
|
|
|
}
|
2023-09-18 13:59:28 +02:00
|
|
|
|
2023-12-31 15:25:06 +01:00
|
|
|
client, err := ipc.NewClientIpc()
|
2023-09-18 18:00:43 +02:00
|
|
|
if err != nil {
|
2023-12-31 15:25:06 +01:00
|
|
|
panic(err)
|
2023-09-18 18:00:43 +02:00
|
|
|
}
|
2023-09-18 13:59:28 +02:00
|
|
|
|
2023-09-18 18:00:43 +02:00
|
|
|
if newMeshCmd.Happened() {
|
2023-12-31 15:25:06 +01:00
|
|
|
args := &ipc.NewMeshArgs{
|
2023-12-12 12:58:47 +01:00
|
|
|
WgArgs: ipc.WireGuardArgs{
|
|
|
|
Endpoint: *newMeshEndpoint,
|
|
|
|
Role: *newMeshRole,
|
|
|
|
WgPort: *newMeshPort,
|
|
|
|
KeepAliveWg: *newMeshKeepAliveWg,
|
|
|
|
AdvertiseDefaultRoute: *newMeshAdvertiseDefaults,
|
|
|
|
AdvertiseRoutes: *newMeshAdvertiseRoutes,
|
|
|
|
},
|
2023-12-31 15:25:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
createMesh(client, args)
|
2023-09-18 16:52:28 +02:00
|
|
|
}
|
2023-09-19 14:45:49 +02:00
|
|
|
|
|
|
|
if listMeshCmd.Happened() {
|
|
|
|
listMeshes(client)
|
|
|
|
}
|
|
|
|
|
|
|
|
if joinMeshCmd.Happened() {
|
2023-12-31 15:25:06 +01:00
|
|
|
args := ipc.JoinMeshArgs{
|
2023-10-28 17:38:25 +02:00
|
|
|
IpAddress: *joinMeshIpAddress,
|
|
|
|
MeshId: *joinMeshId,
|
2023-12-12 12:58:47 +01:00
|
|
|
WgArgs: ipc.WireGuardArgs{
|
|
|
|
Endpoint: *joinMeshEndpoint,
|
|
|
|
Role: *joinMeshRole,
|
|
|
|
WgPort: *joinMeshPort,
|
|
|
|
KeepAliveWg: *joinMeshKeepAliveWg,
|
|
|
|
AdvertiseDefaultRoute: *joinMeshAdvertiseDefaults,
|
|
|
|
AdvertiseRoutes: *joinMeshAdvertiseRoutes,
|
|
|
|
},
|
2023-12-31 15:25:06 +01:00
|
|
|
}
|
|
|
|
joinMesh(client, args)
|
2023-09-20 00:50:44 +02:00
|
|
|
}
|
|
|
|
|
2023-10-22 14:34:49 +02:00
|
|
|
if getGraphCmd.Happened() {
|
2023-12-25 02:25:20 +01:00
|
|
|
getGraph(client)
|
2023-10-22 14:34:49 +02:00
|
|
|
}
|
|
|
|
|
2023-10-28 17:38:25 +02:00
|
|
|
if leaveMeshCmd.Happened() {
|
|
|
|
leaveMesh(client, *leaveMeshMeshId)
|
|
|
|
}
|
2023-10-30 20:02:28 +01:00
|
|
|
|
|
|
|
if queryMeshCmd.Happened() {
|
|
|
|
queryMesh(client, *queryMeshMeshId, *queryMeshQuery)
|
|
|
|
}
|
2023-11-01 12:58:10 +01:00
|
|
|
|
|
|
|
if putDescriptionCmd.Happened() {
|
2023-12-31 15:25:06 +01:00
|
|
|
putDescription(client, *descriptionMeshId, *description)
|
2023-11-01 12:58:10 +01:00
|
|
|
}
|
2023-11-17 20:05:21 +01:00
|
|
|
|
|
|
|
if putAliasCmd.Happened() {
|
2023-12-31 15:25:06 +01:00
|
|
|
putAlias(client, *aliasMeshId, *alias)
|
2023-11-17 20:05:21 +01:00
|
|
|
}
|
2023-11-17 23:13:51 +01:00
|
|
|
|
|
|
|
if setServiceCmd.Happened() {
|
2023-12-31 15:25:06 +01:00
|
|
|
setService(client, *serviceMeshId, *serviceKey, *serviceValue)
|
2023-11-17 23:13:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if deleteServiceCmd.Happened() {
|
2023-12-31 15:25:06 +01:00
|
|
|
deleteService(client, *deleteServiceMeshid, *deleteServiceKey)
|
2023-11-17 23:13:51 +01:00
|
|
|
}
|
2023-09-18 16:52:28 +02:00
|
|
|
}
|