refactor: client/server
This commit is contained in:
parent
d4e136c8d6
commit
c556a30317
10
go.mod
10
go.mod
@ -11,7 +11,11 @@ require (
|
|||||||
github.com/rivo/tview v0.0.0-20250330220935-949945f8d922
|
github.com/rivo/tview v0.0.0-20250330220935-949945f8d922
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/testcontainers/testcontainers-go v0.35.0
|
github.com/testcontainers/testcontainers-go v0.35.0
|
||||||
|
github.com/urfave/cli/v2 v2.27.6
|
||||||
golang.design/x/clipboard v0.7.0
|
golang.design/x/clipboard v0.7.0
|
||||||
|
golang.org/x/sync v0.13.0
|
||||||
|
google.golang.org/grpc v1.69.4
|
||||||
|
google.golang.org/protobuf v1.36.6
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,6 +28,7 @@ require (
|
|||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
github.com/containerd/platforms v0.2.1 // indirect
|
github.com/containerd/platforms v0.2.1 // indirect
|
||||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/distribution/reference v0.6.0 // indirect
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
@ -65,6 +70,7 @@ require (
|
|||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/rs/zerolog v1.33.0 // indirect
|
github.com/rs/zerolog v1.33.0 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
|
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
|
||||||
@ -81,6 +87,7 @@ require (
|
|||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
github.com/vektra/mockery/v2 v2.52.2 // indirect
|
github.com/vektra/mockery/v2 v2.52.2 // indirect
|
||||||
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||||
@ -95,12 +102,13 @@ require (
|
|||||||
golang.org/x/image v0.26.0 // indirect
|
golang.org/x/image v0.26.0 // indirect
|
||||||
golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 // indirect
|
golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 // indirect
|
||||||
golang.org/x/mod v0.24.0 // indirect
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
golang.org/x/sync v0.13.0 // indirect
|
golang.org/x/net v0.39.0 // indirect
|
||||||
golang.org/x/sys v0.32.0 // indirect
|
golang.org/x/sys v0.32.0 // indirect
|
||||||
golang.org/x/term v0.31.0 // indirect
|
golang.org/x/term v0.31.0 // indirect
|
||||||
golang.org/x/text v0.24.0 // indirect
|
golang.org/x/text v0.24.0 // indirect
|
||||||
golang.org/x/time v0.9.0 // indirect
|
golang.org/x/time v0.9.0 // indirect
|
||||||
golang.org/x/tools v0.32.0 // indirect
|
golang.org/x/tools v0.32.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
13
go.sum
13
go.sum
@ -18,6 +18,8 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
|||||||
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -52,6 +54,8 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU
|
|||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
@ -140,6 +144,7 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN
|
|||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
|
||||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||||
@ -185,8 +190,12 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA
|
|||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
|
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
|
||||||
|
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||||
github.com/vektra/mockery/v2 v2.52.2 h1:8QfPKUIrq8P3Cs7G79Iu4Byd5wdhGCE0quIS27x7rQo=
|
github.com/vektra/mockery/v2 v2.52.2 h1:8QfPKUIrq8P3Cs7G79Iu4Byd5wdhGCE0quIS27x7rQo=
|
||||||
github.com/vektra/mockery/v2 v2.52.2/go.mod h1:zGDY/f6bip0Yh13GQ5j7xa43fuEoYBa4ICHEaihisHw=
|
github.com/vektra/mockery/v2 v2.52.2/go.mod h1:zGDY/f6bip0Yh13GQ5j7xa43fuEoYBa4ICHEaihisHw=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
@ -334,8 +343,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:
|
|||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
||||||
google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
|
google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
|
||||||
google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||||
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
131
internal/client/clientapp.go
Normal file
131
internal/client/clientapp.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"git.netflux.io/rob/octoplex/internal/domain"
|
||||||
|
"git.netflux.io/rob/octoplex/internal/event"
|
||||||
|
pb "git.netflux.io/rob/octoplex/internal/generated/grpc"
|
||||||
|
"git.netflux.io/rob/octoplex/internal/protocol"
|
||||||
|
"git.netflux.io/rob/octoplex/internal/terminal"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
// App is the client application.
|
||||||
|
type App struct {
|
||||||
|
bus *event.Bus
|
||||||
|
clipboardAvailable bool
|
||||||
|
buildInfo domain.BuildInfo
|
||||||
|
screen *terminal.Screen
|
||||||
|
logger *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParams contains the parameters for the App.
|
||||||
|
type NewParams struct {
|
||||||
|
ClipboardAvailable bool
|
||||||
|
BuildInfo domain.BuildInfo
|
||||||
|
Screen *terminal.Screen
|
||||||
|
Logger *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new App instance.
|
||||||
|
func New(params NewParams) *App {
|
||||||
|
return &App{
|
||||||
|
bus: event.NewBus(params.Logger),
|
||||||
|
clipboardAvailable: params.ClipboardAvailable,
|
||||||
|
buildInfo: params.BuildInfo,
|
||||||
|
screen: params.Screen,
|
||||||
|
logger: params.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run starts the application, and blocks until it is closed.
|
||||||
|
//
|
||||||
|
// It returns nil if the application was closed by the user, or an error if it
|
||||||
|
// closed for any other reason.
|
||||||
|
func (a *App) Run(ctx context.Context) error {
|
||||||
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
|
conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("connect to gRPC server: %w", err)
|
||||||
|
}
|
||||||
|
apiClient := pb.NewInternalAPIClient(conn)
|
||||||
|
stream, err := apiClient.Communicate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create gRPC stream: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ui, err := terminal.NewUI(ctx, terminal.Params{
|
||||||
|
EventBus: a.bus,
|
||||||
|
Dispatcher: func(cmd event.Command) {
|
||||||
|
a.logger.Debug("Command dispatched to gRPC stream", "cmd", cmd.Name())
|
||||||
|
if sendErr := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Command{Command: protocol.CommandToProto(cmd)}}); sendErr != nil {
|
||||||
|
a.logger.Error("Error dispatching command to gRPC stream", "err", sendErr)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ClipboardAvailable: a.clipboardAvailable,
|
||||||
|
BuildInfo: a.buildInfo,
|
||||||
|
Screen: a.screen,
|
||||||
|
Logger: a.logger.With("component", "ui"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("start terminal user interface: %w", err)
|
||||||
|
}
|
||||||
|
defer ui.Close()
|
||||||
|
|
||||||
|
g.Go(func() error { return ui.Run(ctx) })
|
||||||
|
|
||||||
|
// After the UI is available, perform a handshake with the server.
|
||||||
|
// Ordering is important here. We want to ensure that the UI is ready to
|
||||||
|
// react to events received from the server. Performing the handshake ensures
|
||||||
|
// the client has received at least one event.
|
||||||
|
if err := a.doHandshake(stream); err != nil {
|
||||||
|
return fmt.Errorf("do handshake: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
for {
|
||||||
|
envelope, recErr := stream.Recv()
|
||||||
|
if recErr != nil {
|
||||||
|
return fmt.Errorf("receive envelope: %w", recErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pbEvt := envelope.GetEvent()
|
||||||
|
if pbEvt == nil {
|
||||||
|
a.logger.Error("Received envelope without event")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
evt := protocol.EventFromProto(pbEvt)
|
||||||
|
a.logger.Debug("Received event from gRPC stream", "event", evt.EventName(), "payload", evt)
|
||||||
|
a.bus.Send(evt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := g.Wait(); err == terminal.ErrUserClosed {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("errgroup.Wait: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) doHandshake(stream pb.InternalAPI_CommunicateClient) error {
|
||||||
|
if err := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Command{Command: &pb.Command{CommandType: &pb.Command_StartHandshake{}}}}); err != nil {
|
||||||
|
return fmt.Errorf("send start handshake command: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
env, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("receive handshake completed event: %w", err)
|
||||||
|
}
|
||||||
|
if evt := env.GetEvent(); evt == nil || evt.GetHandshakeCompleted() == nil {
|
||||||
|
return fmt.Errorf("expected handshake completed event but got: %T", env)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
//go:build integration
|
//go:build integration
|
||||||
|
|
||||||
package app_test
|
package client_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@ -14,39 +16,79 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.netflux.io/rob/octoplex/internal/app"
|
"git.netflux.io/rob/octoplex/internal/client"
|
||||||
"git.netflux.io/rob/octoplex/internal/config"
|
"git.netflux.io/rob/octoplex/internal/config"
|
||||||
"git.netflux.io/rob/octoplex/internal/container"
|
"git.netflux.io/rob/octoplex/internal/container"
|
||||||
"git.netflux.io/rob/octoplex/internal/domain"
|
"git.netflux.io/rob/octoplex/internal/domain"
|
||||||
|
"git.netflux.io/rob/octoplex/internal/server"
|
||||||
"git.netflux.io/rob/octoplex/internal/terminal"
|
"git.netflux.io/rob/octoplex/internal/terminal"
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/testcontainers/testcontainers-go"
|
"github.com/testcontainers/testcontainers-go"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
func buildAppParams(
|
func buildClientServer(
|
||||||
t *testing.T,
|
|
||||||
configService *config.Service,
|
configService *config.Service,
|
||||||
dockerClient container.DockerClient,
|
dockerClient container.DockerClient,
|
||||||
screen tcell.SimulationScreen,
|
screen tcell.SimulationScreen,
|
||||||
screenCaptureC chan<- terminal.ScreenCapture,
|
screenCaptureC chan<- terminal.ScreenCapture,
|
||||||
logger *slog.Logger,
|
logger *slog.Logger,
|
||||||
) app.Params {
|
) (*client.App, *server.App) {
|
||||||
t.Helper()
|
client := client.New(client.NewParams{
|
||||||
|
BuildInfo: domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"},
|
||||||
return app.Params{
|
|
||||||
ConfigService: configService,
|
|
||||||
DockerClient: dockerClient,
|
|
||||||
Screen: &terminal.Screen{
|
Screen: &terminal.Screen{
|
||||||
Screen: screen,
|
Screen: screen,
|
||||||
Width: 180,
|
Width: 180,
|
||||||
Height: 25,
|
Height: 25,
|
||||||
CaptureC: screenCaptureC,
|
CaptureC: screenCaptureC,
|
||||||
},
|
},
|
||||||
ClipboardAvailable: false,
|
|
||||||
BuildInfo: domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"},
|
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
|
})
|
||||||
|
|
||||||
|
server := server.New(server.Params{
|
||||||
|
ConfigService: configService,
|
||||||
|
DockerClient: dockerClient,
|
||||||
|
WaitForClient: true,
|
||||||
|
Logger: logger,
|
||||||
|
})
|
||||||
|
|
||||||
|
return client, server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type clientServerResult struct {
|
||||||
|
errClient error
|
||||||
|
errServer error
|
||||||
|
}
|
||||||
|
|
||||||
|
func runClientServer(
|
||||||
|
ctx context.Context,
|
||||||
|
_ *testing.T,
|
||||||
|
clientApp *client.App,
|
||||||
|
serverApp *server.App,
|
||||||
|
) <-chan clientServerResult {
|
||||||
|
ch := make(chan clientServerResult, 1)
|
||||||
|
|
||||||
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
|
var clientErr, srvErr error
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
srvErr = serverApp.Run(ctx)
|
||||||
|
return errors.New("server closed")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
clientErr = clientApp.Run(ctx)
|
||||||
|
return errors.New("client closed")
|
||||||
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_ = g.Wait()
|
||||||
|
|
||||||
|
ch <- clientServerResult{errClient: clientErr, errServer: srvErr}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupSimulationScreen(t *testing.T) (tcell.SimulationScreen, chan<- terminal.ScreenCapture, func() []string) {
|
func setupSimulationScreen(t *testing.T) (tcell.SimulationScreen, chan<- terminal.ScreenCapture, func() []string) {
|
@ -1,6 +1,6 @@
|
|||||||
//go:build integration
|
//go:build integration
|
||||||
|
|
||||||
package app_test
|
package client_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
@ -15,12 +15,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.netflux.io/rob/octoplex/internal/app"
|
|
||||||
"git.netflux.io/rob/octoplex/internal/config"
|
"git.netflux.io/rob/octoplex/internal/config"
|
||||||
"git.netflux.io/rob/octoplex/internal/container"
|
"git.netflux.io/rob/octoplex/internal/container"
|
||||||
"git.netflux.io/rob/octoplex/internal/container/mocks"
|
"git.netflux.io/rob/octoplex/internal/container/mocks"
|
||||||
"git.netflux.io/rob/octoplex/internal/domain"
|
"git.netflux.io/rob/octoplex/internal/domain"
|
||||||
"git.netflux.io/rob/octoplex/internal/terminal"
|
|
||||||
"git.netflux.io/rob/octoplex/internal/testhelpers"
|
"git.netflux.io/rob/octoplex/internal/testhelpers"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
dockerclient "github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
@ -126,26 +124,8 @@ func testIntegration(t *testing.T, mediaServerConfig config.MediaServerSource) {
|
|||||||
Destinations: []config.Destination{{Name: "Local server 1", URL: destURL1}},
|
Destinations: []config.Destination{{Name: "Local server 1", URL: destURL1}},
|
||||||
})
|
})
|
||||||
|
|
||||||
done := make(chan struct{})
|
client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger)
|
||||||
go func() {
|
ch := runClientServer(ctx, t, client, server)
|
||||||
defer func() {
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.Equal(t, context.Canceled, app.New(app.Params{
|
|
||||||
ConfigService: configService,
|
|
||||||
DockerClient: dockerClient,
|
|
||||||
Screen: &terminal.Screen{
|
|
||||||
Screen: screen,
|
|
||||||
Width: 160,
|
|
||||||
Height: 25,
|
|
||||||
CaptureC: screenCaptureC,
|
|
||||||
},
|
|
||||||
ClipboardAvailable: false,
|
|
||||||
BuildInfo: domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"},
|
|
||||||
Logger: logger,
|
|
||||||
}).Run(ctx))
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
t,
|
t,
|
||||||
@ -286,13 +266,12 @@ func testIntegration(t *testing.T, mediaServerConfig config.MediaServerSource) {
|
|||||||
|
|
||||||
printScreen(t, getContents, "After stopping the first destination")
|
printScreen(t, getContents, "After stopping the first destination")
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// - Source error
|
|
||||||
// - Additional features (copy URL, etc.)
|
|
||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
<-done
|
result := <-ch
|
||||||
|
// May be a gRPC error, not context.Canceled:
|
||||||
|
assert.ErrorContains(t, result.errClient, "context canceled")
|
||||||
|
assert.ErrorIs(t, result.errServer, context.Canceled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationCustomHost(t *testing.T) {
|
func TestIntegrationCustomHost(t *testing.T) {
|
||||||
@ -313,14 +292,8 @@ func TestIntegrationCustomHost(t *testing.T) {
|
|||||||
})
|
})
|
||||||
screen, screenCaptureC, getContents := setupSimulationScreen(t)
|
screen, screenCaptureC, getContents := setupSimulationScreen(t)
|
||||||
|
|
||||||
done := make(chan struct{})
|
client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger)
|
||||||
go func() {
|
ch := runClientServer(ctx, t, client, server)
|
||||||
defer func() {
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.Equal(t, context.Canceled, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx))
|
|
||||||
}()
|
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
sendKey(t, screen, tcell.KeyF1, ' ')
|
sendKey(t, screen, tcell.KeyF1, ' ')
|
||||||
@ -360,7 +333,10 @@ func TestIntegrationCustomHost(t *testing.T) {
|
|||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
<-done
|
result := <-ch
|
||||||
|
// May be a gRPC error, not context.Canceled:
|
||||||
|
assert.ErrorContains(t, result.errClient, "context canceled")
|
||||||
|
assert.ErrorIs(t, result.errServer, context.Canceled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationCustomTLSCerts(t *testing.T) {
|
func TestIntegrationCustomTLSCerts(t *testing.T) {
|
||||||
@ -384,14 +360,8 @@ func TestIntegrationCustomTLSCerts(t *testing.T) {
|
|||||||
})
|
})
|
||||||
screen, screenCaptureC, getContents := setupSimulationScreen(t)
|
screen, screenCaptureC, getContents := setupSimulationScreen(t)
|
||||||
|
|
||||||
done := make(chan struct{})
|
client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger)
|
||||||
go func() {
|
ch := runClientServer(ctx, t, client, server)
|
||||||
defer func() {
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.Equal(t, context.Canceled, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx))
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
t,
|
t,
|
||||||
@ -426,7 +396,9 @@ func TestIntegrationCustomTLSCerts(t *testing.T) {
|
|||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
<-done
|
result := <-ch
|
||||||
|
assert.ErrorContains(t, result.errClient, "context canceled")
|
||||||
|
assert.ErrorIs(t, result.errServer, context.Canceled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationRestartDestination(t *testing.T) {
|
func TestIntegrationRestartDestination(t *testing.T) {
|
||||||
@ -465,14 +437,8 @@ func TestIntegrationRestartDestination(t *testing.T) {
|
|||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
|
|
||||||
done := make(chan struct{})
|
client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger)
|
||||||
go func() {
|
ch := runClientServer(ctx, t, client, server)
|
||||||
defer func() {
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.Equal(t, context.Canceled, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx))
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
t,
|
t,
|
||||||
@ -584,7 +550,9 @@ func TestIntegrationRestartDestination(t *testing.T) {
|
|||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
<-done
|
result := <-ch
|
||||||
|
assert.ErrorContains(t, result.errClient, "context canceled")
|
||||||
|
assert.ErrorIs(t, result.errServer, context.Canceled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationStartDestinationFailed(t *testing.T) {
|
func TestIntegrationStartDestinationFailed(t *testing.T) {
|
||||||
@ -602,14 +570,8 @@ func TestIntegrationStartDestinationFailed(t *testing.T) {
|
|||||||
Destinations: []config.Destination{{Name: "Example server", URL: "rtmp://rtmp.example.com/live"}},
|
Destinations: []config.Destination{{Name: "Example server", URL: "rtmp://rtmp.example.com/live"}},
|
||||||
})
|
})
|
||||||
|
|
||||||
done := make(chan struct{})
|
client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger)
|
||||||
go func() {
|
ch := runClientServer(ctx, t, client, server)
|
||||||
defer func() {
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.Equal(t, context.Canceled, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx))
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
t,
|
t,
|
||||||
@ -658,7 +620,9 @@ func TestIntegrationStartDestinationFailed(t *testing.T) {
|
|||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
<-done
|
result := <-ch
|
||||||
|
assert.ErrorContains(t, result.errClient, "context canceled")
|
||||||
|
assert.ErrorIs(t, result.errServer, context.Canceled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationDestinationValidations(t *testing.T) {
|
func TestIntegrationDestinationValidations(t *testing.T) {
|
||||||
@ -675,14 +639,8 @@ func TestIntegrationDestinationValidations(t *testing.T) {
|
|||||||
Sources: config.Sources{MediaServer: config.MediaServerSource{StreamKey: "live", RTMP: config.RTMPSource{Enabled: true}}},
|
Sources: config.Sources{MediaServer: config.MediaServerSource{StreamKey: "live", RTMP: config.RTMPSource{Enabled: true}}},
|
||||||
})
|
})
|
||||||
|
|
||||||
done := make(chan struct{})
|
client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger)
|
||||||
go func() {
|
ch := runClientServer(ctx, t, client, server)
|
||||||
defer func() {
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.Equal(t, context.Canceled, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx))
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
t,
|
t,
|
||||||
@ -786,7 +744,9 @@ func TestIntegrationDestinationValidations(t *testing.T) {
|
|||||||
printScreen(t, getContents, "After entering a duplicate destination URL")
|
printScreen(t, getContents, "After entering a duplicate destination URL")
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
<-done
|
result := <-ch
|
||||||
|
assert.ErrorContains(t, result.errClient, "context canceled")
|
||||||
|
assert.ErrorIs(t, result.errServer, context.Canceled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationStartupCheck(t *testing.T) {
|
func TestIntegrationStartupCheck(t *testing.T) {
|
||||||
@ -817,14 +777,8 @@ func TestIntegrationStartupCheck(t *testing.T) {
|
|||||||
configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: config.MediaServerSource{RTMP: config.RTMPSource{Enabled: true}}}})
|
configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: config.MediaServerSource{RTMP: config.RTMPSource{Enabled: true}}}})
|
||||||
screen, screenCaptureC, getContents := setupSimulationScreen(t)
|
screen, screenCaptureC, getContents := setupSimulationScreen(t)
|
||||||
|
|
||||||
done := make(chan struct{})
|
client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger)
|
||||||
go func() {
|
ch := runClientServer(ctx, t, client, server)
|
||||||
defer func() {
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.Equal(t, context.Canceled, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx))
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
t,
|
t,
|
||||||
@ -868,7 +822,9 @@ func TestIntegrationStartupCheck(t *testing.T) {
|
|||||||
printScreen(t, getContents, "After starting the mediaserver")
|
printScreen(t, getContents, "After starting the mediaserver")
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
<-done
|
result := <-ch
|
||||||
|
assert.ErrorContains(t, result.errClient, "context canceled")
|
||||||
|
assert.ErrorIs(t, result.errServer, context.Canceled)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationMediaServerError(t *testing.T) {
|
func TestIntegrationMediaServerError(t *testing.T) {
|
||||||
@ -886,18 +842,8 @@ func TestIntegrationMediaServerError(t *testing.T) {
|
|||||||
configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: config.MediaServerSource{RTMP: config.RTMPSource{Enabled: true}}}})
|
configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: config.MediaServerSource{RTMP: config.RTMPSource{Enabled: true}}}})
|
||||||
screen, screenCaptureC, getContents := setupSimulationScreen(t)
|
screen, screenCaptureC, getContents := setupSimulationScreen(t)
|
||||||
|
|
||||||
done := make(chan struct{})
|
client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger)
|
||||||
go func() {
|
ch := runClientServer(ctx, t, client, server)
|
||||||
defer func() {
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.EqualError(
|
|
||||||
t,
|
|
||||||
app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx),
|
|
||||||
"media server exited",
|
|
||||||
)
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
t,
|
t,
|
||||||
@ -911,10 +857,12 @@ func TestIntegrationMediaServerError(t *testing.T) {
|
|||||||
)
|
)
|
||||||
printScreen(t, getContents, "Ater displaying the media server error modal")
|
printScreen(t, getContents, "Ater displaying the media server error modal")
|
||||||
|
|
||||||
// Quit the app, this should cause the done channel to receive.
|
// Quit the app:
|
||||||
sendKey(t, screen, tcell.KeyEnter, ' ')
|
sendKey(t, screen, tcell.KeyEnter, ' ')
|
||||||
|
|
||||||
<-done
|
result := <-ch
|
||||||
|
assert.ErrorContains(t, result.errClient, "context canceled")
|
||||||
|
assert.ErrorContains(t, result.errServer, "media server exited")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationDockerClientError(t *testing.T) {
|
func TestIntegrationDockerClientError(t *testing.T) {
|
||||||
@ -929,18 +877,8 @@ func TestIntegrationDockerClientError(t *testing.T) {
|
|||||||
configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: config.MediaServerSource{RTMP: config.RTMPSource{Enabled: true}}}})
|
configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: config.MediaServerSource{RTMP: config.RTMPSource{Enabled: true}}}})
|
||||||
screen, screenCaptureC, getContents := setupSimulationScreen(t)
|
screen, screenCaptureC, getContents := setupSimulationScreen(t)
|
||||||
|
|
||||||
done := make(chan struct{})
|
client, server := buildClientServer(configService, &dockerClient, screen, screenCaptureC, logger)
|
||||||
go func() {
|
ch := runClientServer(ctx, t, client, server)
|
||||||
defer func() {
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.EqualError(
|
|
||||||
t,
|
|
||||||
app.New(buildAppParams(t, configService, &dockerClient, screen, screenCaptureC, logger)).Run(ctx),
|
|
||||||
"create container client: network create: boom",
|
|
||||||
)
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
t,
|
t,
|
||||||
@ -954,10 +892,12 @@ func TestIntegrationDockerClientError(t *testing.T) {
|
|||||||
)
|
)
|
||||||
printScreen(t, getContents, "Ater displaying the fatal error modal")
|
printScreen(t, getContents, "Ater displaying the fatal error modal")
|
||||||
|
|
||||||
// Quit the app, this should cause the done channel to receive.
|
// Quit the app:
|
||||||
sendKey(t, screen, tcell.KeyEnter, ' ')
|
sendKey(t, screen, tcell.KeyEnter, ' ')
|
||||||
|
|
||||||
<-done
|
result := <-ch
|
||||||
|
assert.ErrorContains(t, result.errClient, "context canceled")
|
||||||
|
assert.EqualError(t, result.errServer, "create container client: network create: boom")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationDockerConnectionError(t *testing.T) {
|
func TestIntegrationDockerConnectionError(t *testing.T) {
|
||||||
@ -971,16 +911,8 @@ func TestIntegrationDockerConnectionError(t *testing.T) {
|
|||||||
configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: config.MediaServerSource{RTMP: config.RTMPSource{Enabled: true}}}})
|
configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: config.MediaServerSource{RTMP: config.RTMPSource{Enabled: true}}}})
|
||||||
screen, screenCaptureC, getContents := setupSimulationScreen(t)
|
screen, screenCaptureC, getContents := setupSimulationScreen(t)
|
||||||
|
|
||||||
done := make(chan struct{})
|
client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger)
|
||||||
go func() {
|
ch := runClientServer(ctx, t, client, server)
|
||||||
defer func() {
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err := app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx)
|
|
||||||
require.ErrorContains(t, err, "dial tcp: lookup docker.example.com")
|
|
||||||
require.ErrorContains(t, err, "no such host")
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.EventuallyWithT(
|
require.EventuallyWithT(
|
||||||
t,
|
t,
|
||||||
@ -994,10 +926,13 @@ func TestIntegrationDockerConnectionError(t *testing.T) {
|
|||||||
)
|
)
|
||||||
printScreen(t, getContents, "Ater displaying the fatal error modal")
|
printScreen(t, getContents, "Ater displaying the fatal error modal")
|
||||||
|
|
||||||
// Quit the app, this should cause the done channel to receive.
|
// Quit the app:
|
||||||
sendKey(t, screen, tcell.KeyEnter, ' ')
|
sendKey(t, screen, tcell.KeyEnter, ' ')
|
||||||
|
|
||||||
<-done
|
result := <-ch
|
||||||
|
assert.ErrorContains(t, result.errClient, "context canceled")
|
||||||
|
assert.ErrorContains(t, result.errServer, "dial tcp: lookup docker.example.com")
|
||||||
|
assert.ErrorContains(t, result.errServer, "no such host")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationCopyURLs(t *testing.T) {
|
func TestIntegrationCopyURLs(t *testing.T) {
|
||||||
@ -1067,14 +1002,8 @@ func TestIntegrationCopyURLs(t *testing.T) {
|
|||||||
configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: tc.mediaServerConfig}})
|
configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: tc.mediaServerConfig}})
|
||||||
screen, screenCaptureC, getContents := setupSimulationScreen(t)
|
screen, screenCaptureC, getContents := setupSimulationScreen(t)
|
||||||
|
|
||||||
done := make(chan struct{})
|
client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger)
|
||||||
go func() {
|
ch := runClientServer(ctx, t, client, server)
|
||||||
defer func() {
|
|
||||||
done <- struct{}{}
|
|
||||||
}()
|
|
||||||
|
|
||||||
require.Equal(t, context.Canceled, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx))
|
|
||||||
}()
|
|
||||||
|
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
printScreen(t, getContents, "Ater loading the app")
|
printScreen(t, getContents, "Ater loading the app")
|
||||||
@ -1095,7 +1024,9 @@ func TestIntegrationCopyURLs(t *testing.T) {
|
|||||||
|
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
<-done
|
result := <-ch
|
||||||
|
assert.ErrorContains(t, result.errClient, "context canceled")
|
||||||
|
assert.ErrorIs(t, result.errServer, context.Canceled)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ package event
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,6 +32,21 @@ func (b *Bus) Register() <-chan Event {
|
|||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deregister deregisters a consumer for all events.
|
||||||
|
func (b *Bus) Deregister(ch <-chan Event) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
b.consumers = slices.DeleteFunc(b.consumers, func(other chan Event) bool {
|
||||||
|
if ch == other {
|
||||||
|
close(other)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Send sends an event to all registered consumers.
|
// Send sends an event to all registered consumers.
|
||||||
func (b *Bus) Send(evt Event) {
|
func (b *Bus) Send(evt Event) {
|
||||||
// The mutex is needed to ensure the backing array of b.consumers cannot be
|
// The mutex is needed to ensure the backing array of b.consumers cannot be
|
||||||
@ -43,7 +59,7 @@ func (b *Bus) Send(evt Event) {
|
|||||||
select {
|
select {
|
||||||
case ch <- evt:
|
case ch <- evt:
|
||||||
default:
|
default:
|
||||||
b.logger.Warn("Event dropped", "name", evt.name())
|
b.logger.Warn("Event dropped", "name", evt.EventName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"git.netflux.io/rob/octoplex/internal/event"
|
"git.netflux.io/rob/octoplex/internal/event"
|
||||||
"git.netflux.io/rob/octoplex/internal/testhelpers"
|
"git.netflux.io/rob/octoplex/internal/testhelpers"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBus(t *testing.T) {
|
func TestBus(t *testing.T) {
|
||||||
@ -25,5 +26,19 @@ func TestBus(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
assert.Equal(t, evt, (<-ch1).(event.MediaServerStartedEvent))
|
assert.Equal(t, evt, (<-ch1).(event.MediaServerStartedEvent))
|
||||||
|
assert.Equal(t, evt, (<-ch1).(event.MediaServerStartedEvent))
|
||||||
|
|
||||||
assert.Equal(t, evt, (<-ch2).(event.MediaServerStartedEvent))
|
assert.Equal(t, evt, (<-ch2).(event.MediaServerStartedEvent))
|
||||||
|
assert.Equal(t, evt, (<-ch2).(event.MediaServerStartedEvent))
|
||||||
|
|
||||||
|
bus.Deregister(ch1)
|
||||||
|
|
||||||
|
_, ok := <-ch1
|
||||||
|
assert.False(t, ok)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ch2:
|
||||||
|
require.Fail(t, "ch2 should be blocking")
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,12 +49,12 @@ func (c CommandCloseOtherInstance) Name() string {
|
|||||||
return "close_other_instance"
|
return "close_other_instance"
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommandQuit quits the app.
|
// CommandKillServer kills the server.
|
||||||
type CommandQuit struct{}
|
type CommandKillServer struct{}
|
||||||
|
|
||||||
// Name implements the Command interface.
|
// Name implements the Command interface.
|
||||||
func (c CommandQuit) Name() string {
|
func (c CommandKillServer) Name() string {
|
||||||
return "quit"
|
return "kill_server"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command is an interface for commands that can be triggered by the terminal
|
// Command is an interface for commands that can be triggered by the terminal
|
||||||
|
@ -19,7 +19,7 @@ const (
|
|||||||
|
|
||||||
// Event represents something which happened in the appllication.
|
// Event represents something which happened in the appllication.
|
||||||
type Event interface {
|
type Event interface {
|
||||||
name() Name
|
EventName() Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppStateChangedEvent is emitted when the application state changes.
|
// AppStateChangedEvent is emitted when the application state changes.
|
||||||
@ -27,7 +27,7 @@ type AppStateChangedEvent struct {
|
|||||||
State domain.AppState
|
State domain.AppState
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e AppStateChangedEvent) name() Name {
|
func (e AppStateChangedEvent) EventName() Name {
|
||||||
return EventNameAppStateChanged
|
return EventNameAppStateChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,16 +36,17 @@ type DestinationAddedEvent struct {
|
|||||||
URL string
|
URL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e DestinationAddedEvent) name() Name {
|
func (e DestinationAddedEvent) EventName() Name {
|
||||||
return EventNameDestinationAdded
|
return EventNameDestinationAdded
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddDestinationFailedEvent is emitted when a destination fails to be added.
|
// AddDestinationFailedEvent is emitted when a destination fails to be added.
|
||||||
type AddDestinationFailedEvent struct {
|
type AddDestinationFailedEvent struct {
|
||||||
|
URL string
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e AddDestinationFailedEvent) name() Name {
|
func (e AddDestinationFailedEvent) EventName() Name {
|
||||||
return EventNameAddDestinationFailed
|
return EventNameAddDestinationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,14 +56,17 @@ type DestinationStreamExitedEvent struct {
|
|||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e DestinationStreamExitedEvent) name() Name {
|
func (e DestinationStreamExitedEvent) EventName() Name {
|
||||||
return EventNameDestinationStreamExited
|
return EventNameDestinationStreamExited
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartDestinationFailedEvent is emitted when a destination fails to start.
|
// StartDestinationFailedEvent is emitted when a destination fails to start.
|
||||||
type StartDestinationFailedEvent struct{}
|
type StartDestinationFailedEvent struct {
|
||||||
|
URL string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
func (e StartDestinationFailedEvent) name() Name {
|
func (e StartDestinationFailedEvent) EventName() Name {
|
||||||
return EventNameStartDestinationFailed
|
return EventNameStartDestinationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,17 +76,18 @@ type DestinationRemovedEvent struct {
|
|||||||
URL string
|
URL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e DestinationRemovedEvent) name() Name {
|
func (e DestinationRemovedEvent) EventName() Name {
|
||||||
return EventNameDestinationRemoved
|
return EventNameDestinationRemoved
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveDestinationFailedEvent is emitted when a destination fails to be
|
// RemoveDestinationFailedEvent is emitted when a destination fails to be
|
||||||
// removed.
|
// removed.
|
||||||
type RemoveDestinationFailedEvent struct {
|
type RemoveDestinationFailedEvent struct {
|
||||||
|
URL string
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e RemoveDestinationFailedEvent) name() Name {
|
func (e RemoveDestinationFailedEvent) EventName() Name {
|
||||||
return EventNameRemoveDestinationFailed
|
return EventNameRemoveDestinationFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,11 +100,11 @@ type FatalErrorOccurredEvent struct {
|
|||||||
// OtherInstanceDetectedEvent is emitted when the app launches and detects another instance.
|
// OtherInstanceDetectedEvent is emitted when the app launches and detects another instance.
|
||||||
type OtherInstanceDetectedEvent struct{}
|
type OtherInstanceDetectedEvent struct{}
|
||||||
|
|
||||||
func (e OtherInstanceDetectedEvent) name() Name {
|
func (e OtherInstanceDetectedEvent) EventName() Name {
|
||||||
return EventNameOtherInstanceDetected
|
return EventNameOtherInstanceDetected
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e FatalErrorOccurredEvent) name() Name {
|
func (e FatalErrorOccurredEvent) EventName() Name {
|
||||||
return "fatal_error_occurred"
|
return "fatal_error_occurred"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +114,6 @@ type MediaServerStartedEvent struct {
|
|||||||
RTMPSURL string
|
RTMPSURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e MediaServerStartedEvent) name() Name {
|
func (e MediaServerStartedEvent) EventName() Name {
|
||||||
return "media_server_started"
|
return "media_server_started"
|
||||||
}
|
}
|
||||||
|
0
internal/generated/grpc/.gitkeep
Normal file
0
internal/generated/grpc/.gitkeep
Normal file
199
internal/generated/grpc/api.pb.go
Normal file
199
internal/generated/grpc/api.pb.go
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.28.1
|
||||||
|
// protoc v6.30.1
|
||||||
|
// source: api.proto
|
||||||
|
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Envelope struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// Types that are assignable to Payload:
|
||||||
|
//
|
||||||
|
// *Envelope_Command
|
||||||
|
// *Envelope_Event
|
||||||
|
Payload isEnvelope_Payload `protobuf_oneof:"payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Envelope) Reset() {
|
||||||
|
*x = Envelope{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_api_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Envelope) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Envelope) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Envelope) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_api_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Envelope.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Envelope) Descriptor() ([]byte, []int) {
|
||||||
|
return file_api_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Envelope) GetPayload() isEnvelope_Payload {
|
||||||
|
if m != nil {
|
||||||
|
return m.Payload
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Envelope) GetCommand() *Command {
|
||||||
|
if x, ok := x.GetPayload().(*Envelope_Command); ok {
|
||||||
|
return x.Command
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Envelope) GetEvent() *Event {
|
||||||
|
if x, ok := x.GetPayload().(*Envelope_Event); ok {
|
||||||
|
return x.Event
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type isEnvelope_Payload interface {
|
||||||
|
isEnvelope_Payload()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Envelope_Command struct {
|
||||||
|
Command *Command `protobuf:"bytes,1,opt,name=command,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Envelope_Event struct {
|
||||||
|
Event *Event `protobuf:"bytes,2,opt,name=event,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Envelope_Command) isEnvelope_Payload() {}
|
||||||
|
|
||||||
|
func (*Envelope_Event) isEnvelope_Payload() {}
|
||||||
|
|
||||||
|
var File_api_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_api_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69,
|
||||||
|
0x1a, 0x0b, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0d, 0x63,
|
||||||
|
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x63, 0x0a, 0x08,
|
||||||
|
0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d,
|
||||||
|
0x61, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||||
|
0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61,
|
||||||
|
0x6e, 0x64, 0x12, 0x22, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||||
|
0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52,
|
||||||
|
0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61,
|
||||||
|
0x64, 0x32, 0x3e, 0x0a, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x50, 0x49,
|
||||||
|
0x12, 0x2f, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12,
|
||||||
|
0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x1a, 0x0d,
|
||||||
|
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x28, 0x01, 0x30,
|
||||||
|
0x01, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x66, 0x6c, 0x75, 0x78,
|
||||||
|
0x2e, 0x69, 0x6f, 0x2f, 0x72, 0x6f, 0x62, 0x2f, 0x6f, 0x63, 0x74, 0x6f, 0x70, 0x6c, 0x65, 0x78,
|
||||||
|
0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61,
|
||||||
|
0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_api_proto_rawDescOnce sync.Once
|
||||||
|
file_api_proto_rawDescData = file_api_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_api_proto_rawDescGZIP() []byte {
|
||||||
|
file_api_proto_rawDescOnce.Do(func() {
|
||||||
|
file_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_api_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||||
|
var file_api_proto_goTypes = []interface{}{
|
||||||
|
(*Envelope)(nil), // 0: api.Envelope
|
||||||
|
(*Command)(nil), // 1: api.Command
|
||||||
|
(*Event)(nil), // 2: api.Event
|
||||||
|
}
|
||||||
|
var file_api_proto_depIdxs = []int32{
|
||||||
|
1, // 0: api.Envelope.command:type_name -> api.Command
|
||||||
|
2, // 1: api.Envelope.event:type_name -> api.Event
|
||||||
|
0, // 2: api.InternalAPI.Communicate:input_type -> api.Envelope
|
||||||
|
0, // 3: api.InternalAPI.Communicate:output_type -> api.Envelope
|
||||||
|
3, // [3:4] is the sub-list for method output_type
|
||||||
|
2, // [2:3] is the sub-list for method input_type
|
||||||
|
2, // [2:2] is the sub-list for extension type_name
|
||||||
|
2, // [2:2] is the sub-list for extension extendee
|
||||||
|
0, // [0:2] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_api_proto_init() }
|
||||||
|
func file_api_proto_init() {
|
||||||
|
if File_api_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file_event_proto_init()
|
||||||
|
file_command_proto_init()
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Envelope); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_api_proto_msgTypes[0].OneofWrappers = []interface{}{
|
||||||
|
(*Envelope_Command)(nil),
|
||||||
|
(*Envelope_Event)(nil),
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_api_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 1,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 1,
|
||||||
|
},
|
||||||
|
GoTypes: file_api_proto_goTypes,
|
||||||
|
DependencyIndexes: file_api_proto_depIdxs,
|
||||||
|
MessageInfos: file_api_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_api_proto = out.File
|
||||||
|
file_api_proto_rawDesc = nil
|
||||||
|
file_api_proto_goTypes = nil
|
||||||
|
file_api_proto_depIdxs = nil
|
||||||
|
}
|
137
internal/generated/grpc/api_grpc.pb.go
Normal file
137
internal/generated/grpc/api_grpc.pb.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-grpc v1.2.0
|
||||||
|
// - protoc v6.30.1
|
||||||
|
// source: api.proto
|
||||||
|
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
codes "google.golang.org/grpc/codes"
|
||||||
|
status "google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.32.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion7
|
||||||
|
|
||||||
|
// InternalAPIClient is the client API for InternalAPI service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
type InternalAPIClient interface {
|
||||||
|
Communicate(ctx context.Context, opts ...grpc.CallOption) (InternalAPI_CommunicateClient, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type internalAPIClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInternalAPIClient(cc grpc.ClientConnInterface) InternalAPIClient {
|
||||||
|
return &internalAPIClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *internalAPIClient) Communicate(ctx context.Context, opts ...grpc.CallOption) (InternalAPI_CommunicateClient, error) {
|
||||||
|
stream, err := c.cc.NewStream(ctx, &InternalAPI_ServiceDesc.Streams[0], "/api.InternalAPI/Communicate", opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &internalAPICommunicateClient{stream}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type InternalAPI_CommunicateClient interface {
|
||||||
|
Send(*Envelope) error
|
||||||
|
Recv() (*Envelope, error)
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type internalAPICommunicateClient struct {
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *internalAPICommunicateClient) Send(m *Envelope) error {
|
||||||
|
return x.ClientStream.SendMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *internalAPICommunicateClient) Recv() (*Envelope, error) {
|
||||||
|
m := new(Envelope)
|
||||||
|
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InternalAPIServer is the server API for InternalAPI service.
|
||||||
|
// All implementations must embed UnimplementedInternalAPIServer
|
||||||
|
// for forward compatibility
|
||||||
|
type InternalAPIServer interface {
|
||||||
|
Communicate(InternalAPI_CommunicateServer) error
|
||||||
|
mustEmbedUnimplementedInternalAPIServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedInternalAPIServer must be embedded to have forward compatible implementations.
|
||||||
|
type UnimplementedInternalAPIServer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedInternalAPIServer) Communicate(InternalAPI_CommunicateServer) error {
|
||||||
|
return status.Errorf(codes.Unimplemented, "method Communicate not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedInternalAPIServer) mustEmbedUnimplementedInternalAPIServer() {}
|
||||||
|
|
||||||
|
// UnsafeInternalAPIServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to InternalAPIServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeInternalAPIServer interface {
|
||||||
|
mustEmbedUnimplementedInternalAPIServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterInternalAPIServer(s grpc.ServiceRegistrar, srv InternalAPIServer) {
|
||||||
|
s.RegisterService(&InternalAPI_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _InternalAPI_Communicate_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
return srv.(InternalAPIServer).Communicate(&internalAPICommunicateServer{stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
type InternalAPI_CommunicateServer interface {
|
||||||
|
Send(*Envelope) error
|
||||||
|
Recv() (*Envelope, error)
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type internalAPICommunicateServer struct {
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *internalAPICommunicateServer) Send(m *Envelope) error {
|
||||||
|
return x.ServerStream.SendMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *internalAPICommunicateServer) Recv() (*Envelope, error) {
|
||||||
|
m := new(Envelope)
|
||||||
|
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InternalAPI_ServiceDesc is the grpc.ServiceDesc for InternalAPI service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var InternalAPI_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "api.InternalAPI",
|
||||||
|
HandlerType: (*InternalAPIServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{},
|
||||||
|
Streams: []grpc.StreamDesc{
|
||||||
|
{
|
||||||
|
StreamName: "Communicate",
|
||||||
|
Handler: _InternalAPI_Communicate_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
ClientStreams: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Metadata: "api.proto",
|
||||||
|
}
|
714
internal/generated/grpc/command.pb.go
Normal file
714
internal/generated/grpc/command.pb.go
Normal file
@ -0,0 +1,714 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.28.1
|
||||||
|
// protoc v6.30.1
|
||||||
|
// source: command.proto
|
||||||
|
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Command struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
// Types that are assignable to CommandType:
|
||||||
|
//
|
||||||
|
// *Command_AddDestination
|
||||||
|
// *Command_RemoveDestination
|
||||||
|
// *Command_StartDestination
|
||||||
|
// *Command_StopDestination
|
||||||
|
// *Command_CloseOtherInstances
|
||||||
|
// *Command_KillServer
|
||||||
|
// *Command_StartHandshake
|
||||||
|
CommandType isCommand_CommandType `protobuf_oneof:"command_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Command) Reset() {
|
||||||
|
*x = Command{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_command_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Command) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Command) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Command) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_command_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Command.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Command) Descriptor() ([]byte, []int) {
|
||||||
|
return file_command_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Command) GetCommandType() isCommand_CommandType {
|
||||||
|
if m != nil {
|
||||||
|
return m.CommandType
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Command) GetAddDestination() *AddDestinationCommand {
|
||||||
|
if x, ok := x.GetCommandType().(*Command_AddDestination); ok {
|
||||||
|
return x.AddDestination
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Command) GetRemoveDestination() *RemoveDestinationCommand {
|
||||||
|
if x, ok := x.GetCommandType().(*Command_RemoveDestination); ok {
|
||||||
|
return x.RemoveDestination
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Command) GetStartDestination() *StartDestinationCommand {
|
||||||
|
if x, ok := x.GetCommandType().(*Command_StartDestination); ok {
|
||||||
|
return x.StartDestination
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Command) GetStopDestination() *StopDestinationCommand {
|
||||||
|
if x, ok := x.GetCommandType().(*Command_StopDestination); ok {
|
||||||
|
return x.StopDestination
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Command) GetCloseOtherInstances() *CloseOtherInstancesCommand {
|
||||||
|
if x, ok := x.GetCommandType().(*Command_CloseOtherInstances); ok {
|
||||||
|
return x.CloseOtherInstances
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Command) GetKillServer() *KillServerCommand {
|
||||||
|
if x, ok := x.GetCommandType().(*Command_KillServer); ok {
|
||||||
|
return x.KillServer
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Command) GetStartHandshake() *StartHandshakeCommand {
|
||||||
|
if x, ok := x.GetCommandType().(*Command_StartHandshake); ok {
|
||||||
|
return x.StartHandshake
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type isCommand_CommandType interface {
|
||||||
|
isCommand_CommandType()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Command_AddDestination struct {
|
||||||
|
AddDestination *AddDestinationCommand `protobuf:"bytes,1,opt,name=add_destination,json=addDestination,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Command_RemoveDestination struct {
|
||||||
|
RemoveDestination *RemoveDestinationCommand `protobuf:"bytes,2,opt,name=remove_destination,json=removeDestination,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Command_StartDestination struct {
|
||||||
|
StartDestination *StartDestinationCommand `protobuf:"bytes,3,opt,name=start_destination,json=startDestination,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Command_StopDestination struct {
|
||||||
|
StopDestination *StopDestinationCommand `protobuf:"bytes,4,opt,name=stop_destination,json=stopDestination,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Command_CloseOtherInstances struct {
|
||||||
|
CloseOtherInstances *CloseOtherInstancesCommand `protobuf:"bytes,5,opt,name=close_other_instances,json=closeOtherInstances,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Command_KillServer struct {
|
||||||
|
KillServer *KillServerCommand `protobuf:"bytes,6,opt,name=kill_server,json=killServer,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Command_StartHandshake struct {
|
||||||
|
StartHandshake *StartHandshakeCommand `protobuf:"bytes,7,opt,name=start_handshake,json=startHandshake,proto3,oneof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Command_AddDestination) isCommand_CommandType() {}
|
||||||
|
|
||||||
|
func (*Command_RemoveDestination) isCommand_CommandType() {}
|
||||||
|
|
||||||
|
func (*Command_StartDestination) isCommand_CommandType() {}
|
||||||
|
|
||||||
|
func (*Command_StopDestination) isCommand_CommandType() {}
|
||||||
|
|
||||||
|
func (*Command_CloseOtherInstances) isCommand_CommandType() {}
|
||||||
|
|
||||||
|
func (*Command_KillServer) isCommand_CommandType() {}
|
||||||
|
|
||||||
|
func (*Command_StartHandshake) isCommand_CommandType() {}
|
||||||
|
|
||||||
|
type AddDestinationCommand struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AddDestinationCommand) Reset() {
|
||||||
|
*x = AddDestinationCommand{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_command_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AddDestinationCommand) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*AddDestinationCommand) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *AddDestinationCommand) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_command_proto_msgTypes[1]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use AddDestinationCommand.ProtoReflect.Descriptor instead.
|
||||||
|
func (*AddDestinationCommand) Descriptor() ([]byte, []int) {
|
||||||
|
return file_command_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AddDestinationCommand) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AddDestinationCommand) GetUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Url
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemoveDestinationCommand struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RemoveDestinationCommand) Reset() {
|
||||||
|
*x = RemoveDestinationCommand{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_command_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RemoveDestinationCommand) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RemoveDestinationCommand) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *RemoveDestinationCommand) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_command_proto_msgTypes[2]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RemoveDestinationCommand.ProtoReflect.Descriptor instead.
|
||||||
|
func (*RemoveDestinationCommand) Descriptor() ([]byte, []int) {
|
||||||
|
return file_command_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RemoveDestinationCommand) GetUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Url
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartDestinationCommand struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StartDestinationCommand) Reset() {
|
||||||
|
*x = StartDestinationCommand{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_command_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StartDestinationCommand) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StartDestinationCommand) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StartDestinationCommand) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_command_proto_msgTypes[3]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StartDestinationCommand.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StartDestinationCommand) Descriptor() ([]byte, []int) {
|
||||||
|
return file_command_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StartDestinationCommand) GetUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Url
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type StopDestinationCommand struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopDestinationCommand) Reset() {
|
||||||
|
*x = StopDestinationCommand{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_command_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopDestinationCommand) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StopDestinationCommand) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StopDestinationCommand) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_command_proto_msgTypes[4]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StopDestinationCommand.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StopDestinationCommand) Descriptor() ([]byte, []int) {
|
||||||
|
return file_command_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StopDestinationCommand) GetUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Url
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloseOtherInstancesCommand struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CloseOtherInstancesCommand) Reset() {
|
||||||
|
*x = CloseOtherInstancesCommand{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_command_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CloseOtherInstancesCommand) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CloseOtherInstancesCommand) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *CloseOtherInstancesCommand) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_command_proto_msgTypes[5]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use CloseOtherInstancesCommand.ProtoReflect.Descriptor instead.
|
||||||
|
func (*CloseOtherInstancesCommand) Descriptor() ([]byte, []int) {
|
||||||
|
return file_command_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
type KillServerCommand struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *KillServerCommand) Reset() {
|
||||||
|
*x = KillServerCommand{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_command_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *KillServerCommand) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*KillServerCommand) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *KillServerCommand) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_command_proto_msgTypes[6]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use KillServerCommand.ProtoReflect.Descriptor instead.
|
||||||
|
func (*KillServerCommand) Descriptor() ([]byte, []int) {
|
||||||
|
return file_command_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartHandshakeCommand struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StartHandshakeCommand) Reset() {
|
||||||
|
*x = StartHandshakeCommand{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_command_proto_msgTypes[7]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *StartHandshakeCommand) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StartHandshakeCommand) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *StartHandshakeCommand) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_command_proto_msgTypes[7]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use StartHandshakeCommand.ProtoReflect.Descriptor instead.
|
||||||
|
func (*StartHandshakeCommand) Descriptor() ([]byte, []int) {
|
||||||
|
return file_command_proto_rawDescGZIP(), []int{7}
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_command_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_command_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
||||||
|
0x03, 0x61, 0x70, 0x69, 0x22, 0xa0, 0x04, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
|
||||||
|
0x12, 0x45, 0x0a, 0x0f, 0x61, 0x64, 0x64, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74,
|
||||||
|
0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||||
|
0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f,
|
||||||
|
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x61, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74,
|
||||||
|
0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x12, 0x72, 0x65, 0x6d, 0x6f, 0x76,
|
||||||
|
0x65, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20,
|
||||||
|
0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
|
||||||
|
0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61,
|
||||||
|
0x6e, 0x64, 0x48, 0x00, 0x52, 0x11, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, 0x74,
|
||||||
|
0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4b, 0x0a, 0x11, 0x73, 0x74, 0x61, 0x72, 0x74,
|
||||||
|
0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01,
|
||||||
|
0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65,
|
||||||
|
0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
|
||||||
|
0x48, 0x00, 0x52, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61,
|
||||||
|
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x10, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x64, 0x65, 0x73,
|
||||||
|
0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b,
|
||||||
|
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61,
|
||||||
|
0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x0f, 0x73,
|
||||||
|
0x74, 0x6f, 0x70, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x55,
|
||||||
|
0x0a, 0x15, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x69, 0x6e,
|
||||||
|
0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e,
|
||||||
|
0x61, 0x70, 0x69, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e,
|
||||||
|
0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00,
|
||||||
|
0x52, 0x13, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74,
|
||||||
|
0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x0b, 0x6b, 0x69, 0x6c, 0x6c, 0x5f, 0x73, 0x65,
|
||||||
|
0x72, 0x76, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x70, 0x69,
|
||||||
|
0x2e, 0x4b, 0x69, 0x6c, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6d, 0x6d, 0x61,
|
||||||
|
0x6e, 0x64, 0x48, 0x00, 0x52, 0x0a, 0x6b, 0x69, 0x6c, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||||
|
0x12, 0x45, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68,
|
||||||
|
0x61, 0x6b, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||||
|
0x53, 0x74, 0x61, 0x72, 0x74, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x43, 0x6f,
|
||||||
|
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x61,
|
||||||
|
0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x61,
|
||||||
|
0x6e, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3d, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x44, 0x65,
|
||||||
|
0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
|
||||||
|
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
||||||
|
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||||
|
0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x2c, 0x0a, 0x18, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
|
||||||
|
0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61,
|
||||||
|
0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
|
0x03, 0x75, 0x72, 0x6c, 0x22, 0x2b, 0x0a, 0x17, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73,
|
||||||
|
0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12,
|
||||||
|
0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72,
|
||||||
|
0x6c, 0x22, 0x2a, 0x0a, 0x16, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61,
|
||||||
|
0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75,
|
||||||
|
0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x1c, 0x0a,
|
||||||
|
0x1a, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61,
|
||||||
|
0x6e, 0x63, 0x65, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x13, 0x0a, 0x11, 0x4b,
|
||||||
|
0x69, 0x6c, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
|
||||||
|
0x22, 0x17, 0x0a, 0x15, 0x53, 0x74, 0x61, 0x72, 0x74, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61,
|
||||||
|
0x6b, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74,
|
||||||
|
0x2e, 0x6e, 0x65, 0x74, 0x66, 0x6c, 0x75, 0x78, 0x2e, 0x69, 0x6f, 0x2f, 0x72, 0x6f, 0x62, 0x2f,
|
||||||
|
0x6f, 0x63, 0x74, 0x6f, 0x70, 0x6c, 0x65, 0x78, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
|
||||||
|
0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63,
|
||||||
|
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_command_proto_rawDescOnce sync.Once
|
||||||
|
file_command_proto_rawDescData = file_command_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_command_proto_rawDescGZIP() []byte {
|
||||||
|
file_command_proto_rawDescOnce.Do(func() {
|
||||||
|
file_command_proto_rawDescData = protoimpl.X.CompressGZIP(file_command_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_command_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_command_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||||
|
var file_command_proto_goTypes = []interface{}{
|
||||||
|
(*Command)(nil), // 0: api.Command
|
||||||
|
(*AddDestinationCommand)(nil), // 1: api.AddDestinationCommand
|
||||||
|
(*RemoveDestinationCommand)(nil), // 2: api.RemoveDestinationCommand
|
||||||
|
(*StartDestinationCommand)(nil), // 3: api.StartDestinationCommand
|
||||||
|
(*StopDestinationCommand)(nil), // 4: api.StopDestinationCommand
|
||||||
|
(*CloseOtherInstancesCommand)(nil), // 5: api.CloseOtherInstancesCommand
|
||||||
|
(*KillServerCommand)(nil), // 6: api.KillServerCommand
|
||||||
|
(*StartHandshakeCommand)(nil), // 7: api.StartHandshakeCommand
|
||||||
|
}
|
||||||
|
var file_command_proto_depIdxs = []int32{
|
||||||
|
1, // 0: api.Command.add_destination:type_name -> api.AddDestinationCommand
|
||||||
|
2, // 1: api.Command.remove_destination:type_name -> api.RemoveDestinationCommand
|
||||||
|
3, // 2: api.Command.start_destination:type_name -> api.StartDestinationCommand
|
||||||
|
4, // 3: api.Command.stop_destination:type_name -> api.StopDestinationCommand
|
||||||
|
5, // 4: api.Command.close_other_instances:type_name -> api.CloseOtherInstancesCommand
|
||||||
|
6, // 5: api.Command.kill_server:type_name -> api.KillServerCommand
|
||||||
|
7, // 6: api.Command.start_handshake:type_name -> api.StartHandshakeCommand
|
||||||
|
7, // [7:7] is the sub-list for method output_type
|
||||||
|
7, // [7:7] is the sub-list for method input_type
|
||||||
|
7, // [7:7] is the sub-list for extension type_name
|
||||||
|
7, // [7:7] is the sub-list for extension extendee
|
||||||
|
0, // [0:7] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_command_proto_init() }
|
||||||
|
func file_command_proto_init() {
|
||||||
|
if File_command_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_command_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Command); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_command_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*AddDestinationCommand); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_command_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*RemoveDestinationCommand); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_command_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*StartDestinationCommand); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_command_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*StopDestinationCommand); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_command_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*CloseOtherInstancesCommand); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_command_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*KillServerCommand); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_command_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*StartHandshakeCommand); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_command_proto_msgTypes[0].OneofWrappers = []interface{}{
|
||||||
|
(*Command_AddDestination)(nil),
|
||||||
|
(*Command_RemoveDestination)(nil),
|
||||||
|
(*Command_StartDestination)(nil),
|
||||||
|
(*Command_StopDestination)(nil),
|
||||||
|
(*Command_CloseOtherInstances)(nil),
|
||||||
|
(*Command_KillServer)(nil),
|
||||||
|
(*Command_StartHandshake)(nil),
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_command_proto_rawDesc,
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 8,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_command_proto_goTypes,
|
||||||
|
DependencyIndexes: file_command_proto_depIdxs,
|
||||||
|
MessageInfos: file_command_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_command_proto = out.File
|
||||||
|
file_command_proto_rawDesc = nil
|
||||||
|
file_command_proto_goTypes = nil
|
||||||
|
file_command_proto_depIdxs = nil
|
||||||
|
}
|
729
internal/generated/grpc/domain.pb.go
Normal file
729
internal/generated/grpc/domain.pb.go
Normal file
@ -0,0 +1,729 @@
|
|||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.28.1
|
||||||
|
// protoc v6.30.1
|
||||||
|
// source: domain.proto
|
||||||
|
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Destination_Status int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
Destination_STATUS_OFF_AIR Destination_Status = 0
|
||||||
|
Destination_STATUS_STARTING Destination_Status = 1
|
||||||
|
Destination_STATUS_LIVE Destination_Status = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enum value maps for Destination_Status.
|
||||||
|
var (
|
||||||
|
Destination_Status_name = map[int32]string{
|
||||||
|
0: "STATUS_OFF_AIR",
|
||||||
|
1: "STATUS_STARTING",
|
||||||
|
2: "STATUS_LIVE",
|
||||||
|
}
|
||||||
|
Destination_Status_value = map[string]int32{
|
||||||
|
"STATUS_OFF_AIR": 0,
|
||||||
|
"STATUS_STARTING": 1,
|
||||||
|
"STATUS_LIVE": 2,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x Destination_Status) Enum() *Destination_Status {
|
||||||
|
p := new(Destination_Status)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x Destination_Status) String() string {
|
||||||
|
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Destination_Status) Descriptor() protoreflect.EnumDescriptor {
|
||||||
|
return file_domain_proto_enumTypes[0].Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Destination_Status) Type() protoreflect.EnumType {
|
||||||
|
return &file_domain_proto_enumTypes[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x Destination_Status) Number() protoreflect.EnumNumber {
|
||||||
|
return protoreflect.EnumNumber(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Destination_Status.Descriptor instead.
|
||||||
|
func (Destination_Status) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return file_domain_proto_rawDescGZIP(), []int{2, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Container struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"`
|
||||||
|
HealthState string `protobuf:"bytes,3,opt,name=health_state,json=healthState,proto3" json:"health_state,omitempty"`
|
||||||
|
CpuPercent float64 `protobuf:"fixed64,4,opt,name=cpu_percent,json=cpuPercent,proto3" json:"cpu_percent,omitempty"`
|
||||||
|
MemoryUsageBytes uint64 `protobuf:"varint,5,opt,name=memory_usage_bytes,json=memoryUsageBytes,proto3" json:"memory_usage_bytes,omitempty"`
|
||||||
|
RxRate int32 `protobuf:"varint,6,opt,name=rx_rate,json=rxRate,proto3" json:"rx_rate,omitempty"`
|
||||||
|
TxRate int32 `protobuf:"varint,7,opt,name=tx_rate,json=txRate,proto3" json:"tx_rate,omitempty"`
|
||||||
|
RxSince *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=rx_since,json=rxSince,proto3" json:"rx_since,omitempty"`
|
||||||
|
ImageName string `protobuf:"bytes,9,opt,name=image_name,json=imageName,proto3" json:"image_name,omitempty"`
|
||||||
|
PullStatus string `protobuf:"bytes,10,opt,name=pull_status,json=pullStatus,proto3" json:"pull_status,omitempty"`
|
||||||
|
PullProgress string `protobuf:"bytes,11,opt,name=pull_progress,json=pullProgress,proto3" json:"pull_progress,omitempty"`
|
||||||
|
PullPercent int32 `protobuf:"varint,12,opt,name=pull_percent,json=pullPercent,proto3" json:"pull_percent,omitempty"`
|
||||||
|
RestartCount int32 `protobuf:"varint,13,opt,name=restart_count,json=restartCount,proto3" json:"restart_count,omitempty"`
|
||||||
|
ExitCode *int32 `protobuf:"varint,14,opt,name=exit_code,json=exitCode,proto3,oneof" json:"exit_code,omitempty"`
|
||||||
|
Err string `protobuf:"bytes,15,opt,name=err,proto3" json:"err,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) Reset() {
|
||||||
|
*x = Container{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_domain_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Container) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Container) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_domain_proto_msgTypes[0]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Container.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Container) Descriptor() ([]byte, []int) {
|
||||||
|
return file_domain_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) GetId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) GetStatus() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Status
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) GetHealthState() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.HealthState
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) GetCpuPercent() float64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.CpuPercent
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) GetMemoryUsageBytes() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.MemoryUsageBytes
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) GetRxRate() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.RxRate
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) GetTxRate() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.TxRate
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) GetRxSince() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.RxSince
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) GetImageName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ImageName
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) GetPullStatus() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.PullStatus
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) GetPullProgress() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.PullProgress
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) GetPullPercent() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.PullPercent
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) GetRestartCount() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.RestartCount
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) GetExitCode() int32 {
|
||||||
|
if x != nil && x.ExitCode != nil {
|
||||||
|
return *x.ExitCode
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Container) GetErr() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Err
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type Source struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
|
||||||
|
Live bool `protobuf:"varint,2,opt,name=live,proto3" json:"live,omitempty"`
|
||||||
|
LiveChangedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=live_changed_at,json=liveChangedAt,proto3" json:"live_changed_at,omitempty"`
|
||||||
|
Tracks []string `protobuf:"bytes,4,rep,name=tracks,proto3" json:"tracks,omitempty"`
|
||||||
|
ExitReason string `protobuf:"bytes,5,opt,name=exit_reason,json=exitReason,proto3" json:"exit_reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Source) Reset() {
|
||||||
|
*x = Source{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_domain_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Source) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Source) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Source) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_domain_proto_msgTypes[1]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Source.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Source) Descriptor() ([]byte, []int) {
|
||||||
|
return file_domain_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Source) GetContainer() *Container {
|
||||||
|
if x != nil {
|
||||||
|
return x.Container
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Source) GetLive() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Live
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Source) GetLiveChangedAt() *timestamppb.Timestamp {
|
||||||
|
if x != nil {
|
||||||
|
return x.LiveChangedAt
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Source) GetTracks() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Tracks
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Source) GetExitReason() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ExitReason
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type Destination struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"`
|
||||||
|
Status Destination_Status `protobuf:"varint,2,opt,name=status,proto3,enum=api.Destination_Status" json:"status,omitempty"`
|
||||||
|
Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Destination) Reset() {
|
||||||
|
*x = Destination{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_domain_proto_msgTypes[2]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Destination) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Destination) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *Destination) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_domain_proto_msgTypes[2]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use Destination.ProtoReflect.Descriptor instead.
|
||||||
|
func (*Destination) Descriptor() ([]byte, []int) {
|
||||||
|
return file_domain_proto_rawDescGZIP(), []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Destination) GetContainer() *Container {
|
||||||
|
if x != nil {
|
||||||
|
return x.Container
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Destination) GetStatus() Destination_Status {
|
||||||
|
if x != nil {
|
||||||
|
return x.Status
|
||||||
|
}
|
||||||
|
return Destination_STATUS_OFF_AIR
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Destination) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Destination) GetUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Url
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type BuildInfo struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
GoVersion string `protobuf:"bytes,1,opt,name=go_version,json=goVersion,proto3" json:"go_version,omitempty"`
|
||||||
|
Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"`
|
||||||
|
Commit string `protobuf:"bytes,3,opt,name=commit,proto3" json:"commit,omitempty"`
|
||||||
|
Date string `protobuf:"bytes,4,opt,name=date,proto3" json:"date,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *BuildInfo) Reset() {
|
||||||
|
*x = BuildInfo{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_domain_proto_msgTypes[3]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *BuildInfo) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*BuildInfo) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *BuildInfo) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_domain_proto_msgTypes[3]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use BuildInfo.ProtoReflect.Descriptor instead.
|
||||||
|
func (*BuildInfo) Descriptor() ([]byte, []int) {
|
||||||
|
return file_domain_proto_rawDescGZIP(), []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *BuildInfo) GetGoVersion() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.GoVersion
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *BuildInfo) GetVersion() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Version
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *BuildInfo) GetCommit() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Commit
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *BuildInfo) GetDate() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Date
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppState struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Source *Source `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"`
|
||||||
|
Destinations []*Destination `protobuf:"bytes,2,rep,name=destinations,proto3" json:"destinations,omitempty"`
|
||||||
|
BuildInfo *BuildInfo `protobuf:"bytes,3,opt,name=build_info,json=buildInfo,proto3" json:"build_info,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AppState) Reset() {
|
||||||
|
*x = AppState{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_domain_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AppState) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*AppState) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *AppState) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_domain_proto_msgTypes[4]
|
||||||
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use AppState.ProtoReflect.Descriptor instead.
|
||||||
|
func (*AppState) Descriptor() ([]byte, []int) {
|
||||||
|
return file_domain_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AppState) GetSource() *Source {
|
||||||
|
if x != nil {
|
||||||
|
return x.Source
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AppState) GetDestinations() []*Destination {
|
||||||
|
if x != nil {
|
||||||
|
return x.Destinations
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AppState) GetBuildInfo() *BuildInfo {
|
||||||
|
if x != nil {
|
||||||
|
return x.BuildInfo
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_domain_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
var file_domain_proto_rawDesc = []byte{
|
||||||
|
0x0a, 0x0c, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03,
|
||||||
|
0x61, 0x70, 0x69, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||||
|
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfd, 0x03, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e,
|
||||||
|
0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
|
||||||
|
0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01,
|
||||||
|
0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x68, 0x65,
|
||||||
|
0x61, 0x6c, 0x74, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||||
|
0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a,
|
||||||
|
0x0b, 0x63, 0x70, 0x75, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01,
|
||||||
|
0x28, 0x01, 0x52, 0x0a, 0x63, 0x70, 0x75, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x2c,
|
||||||
|
0x0a, 0x12, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x62,
|
||||||
|
0x79, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x6d, 0x65, 0x6d, 0x6f,
|
||||||
|
0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x17, 0x0a, 0x07,
|
||||||
|
0x72, 0x78, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x72,
|
||||||
|
0x78, 0x52, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x72, 0x61, 0x74, 0x65,
|
||||||
|
0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x74, 0x78, 0x52, 0x61, 0x74, 0x65, 0x12, 0x35,
|
||||||
|
0x0a, 0x08, 0x72, 0x78, 0x5f, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b,
|
||||||
|
0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||||
|
0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x72, 0x78,
|
||||||
|
0x53, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x6e,
|
||||||
|
0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65,
|
||||||
|
0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x74, 0x61,
|
||||||
|
0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x75, 0x6c, 0x6c, 0x53,
|
||||||
|
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x70, 0x72,
|
||||||
|
0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x75,
|
||||||
|
0x6c, 0x6c, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x75,
|
||||||
|
0x6c, 0x6c, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05,
|
||||||
|
0x52, 0x0b, 0x70, 0x75, 0x6c, 0x6c, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a,
|
||||||
|
0x0d, 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d,
|
||||||
|
0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x75,
|
||||||
|
0x6e, 0x74, 0x12, 0x20, 0x0a, 0x09, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18,
|
||||||
|
0x0e, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x08, 0x65, 0x78, 0x69, 0x74, 0x43, 0x6f, 0x64,
|
||||||
|
0x65, 0x88, 0x01, 0x01, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28,
|
||||||
|
0x09, 0x52, 0x03, 0x65, 0x72, 0x72, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x65, 0x78, 0x69, 0x74, 0x5f,
|
||||||
|
0x63, 0x6f, 0x64, 0x65, 0x22, 0xc7, 0x01, 0x0a, 0x06, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12,
|
||||||
|
0x2c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01,
|
||||||
|
0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e,
|
||||||
|
0x65, 0x72, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x12, 0x0a,
|
||||||
|
0x04, 0x6c, 0x69, 0x76, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x6c, 0x69, 0x76,
|
||||||
|
0x65, 0x12, 0x42, 0x0a, 0x0f, 0x6c, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65,
|
||||||
|
0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
|
||||||
|
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
|
||||||
|
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0d, 0x6c, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e,
|
||||||
|
0x67, 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x18,
|
||||||
|
0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x12, 0x1f, 0x0a,
|
||||||
|
0x0b, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01,
|
||||||
|
0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x69, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xd6,
|
||||||
|
0x01, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c,
|
||||||
|
0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
|
0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
|
||||||
|
0x72, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x06,
|
||||||
|
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x61,
|
||||||
|
0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53,
|
||||||
|
0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a,
|
||||||
|
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
|
||||||
|
0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
|
||||||
|
0x75, 0x72, 0x6c, 0x22, 0x42, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a,
|
||||||
|
0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4f, 0x46, 0x46, 0x5f, 0x41, 0x49, 0x52, 0x10,
|
||||||
|
0x00, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x54, 0x41, 0x52,
|
||||||
|
0x54, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53,
|
||||||
|
0x5f, 0x4c, 0x49, 0x56, 0x45, 0x10, 0x02, 0x22, 0x70, 0x0a, 0x09, 0x42, 0x75, 0x69, 0x6c, 0x64,
|
||||||
|
0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x6f, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69,
|
||||||
|
0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x6f, 0x56, 0x65, 0x72, 0x73,
|
||||||
|
0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02,
|
||||||
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a,
|
||||||
|
0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63,
|
||||||
|
0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20,
|
||||||
|
0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x65, 0x22, 0x94, 0x01, 0x0a, 0x08, 0x41, 0x70,
|
||||||
|
0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
|
||||||
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x6f, 0x75,
|
||||||
|
0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0c, 0x64,
|
||||||
|
0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
|
||||||
|
0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74,
|
||||||
|
0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||||
|
0x73, 0x12, 0x2d, 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18,
|
||||||
|
0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x75, 0x69, 0x6c,
|
||||||
|
0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f,
|
||||||
|
0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x66, 0x6c, 0x75, 0x78, 0x2e,
|
||||||
|
0x69, 0x6f, 0x2f, 0x72, 0x6f, 0x62, 0x2f, 0x6f, 0x63, 0x74, 0x6f, 0x70, 0x6c, 0x65, 0x78, 0x2f,
|
||||||
|
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74,
|
||||||
|
0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_domain_proto_rawDescOnce sync.Once
|
||||||
|
file_domain_proto_rawDescData = file_domain_proto_rawDesc
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_domain_proto_rawDescGZIP() []byte {
|
||||||
|
file_domain_proto_rawDescOnce.Do(func() {
|
||||||
|
file_domain_proto_rawDescData = protoimpl.X.CompressGZIP(file_domain_proto_rawDescData)
|
||||||
|
})
|
||||||
|
return file_domain_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_domain_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
|
var file_domain_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
|
||||||
|
var file_domain_proto_goTypes = []interface{}{
|
||||||
|
(Destination_Status)(0), // 0: api.Destination.Status
|
||||||
|
(*Container)(nil), // 1: api.Container
|
||||||
|
(*Source)(nil), // 2: api.Source
|
||||||
|
(*Destination)(nil), // 3: api.Destination
|
||||||
|
(*BuildInfo)(nil), // 4: api.BuildInfo
|
||||||
|
(*AppState)(nil), // 5: api.AppState
|
||||||
|
(*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp
|
||||||
|
}
|
||||||
|
var file_domain_proto_depIdxs = []int32{
|
||||||
|
6, // 0: api.Container.rx_since:type_name -> google.protobuf.Timestamp
|
||||||
|
1, // 1: api.Source.container:type_name -> api.Container
|
||||||
|
6, // 2: api.Source.live_changed_at:type_name -> google.protobuf.Timestamp
|
||||||
|
1, // 3: api.Destination.container:type_name -> api.Container
|
||||||
|
0, // 4: api.Destination.status:type_name -> api.Destination.Status
|
||||||
|
2, // 5: api.AppState.source:type_name -> api.Source
|
||||||
|
3, // 6: api.AppState.destinations:type_name -> api.Destination
|
||||||
|
4, // 7: api.AppState.build_info:type_name -> api.BuildInfo
|
||||||
|
8, // [8:8] is the sub-list for method output_type
|
||||||
|
8, // [8:8] is the sub-list for method input_type
|
||||||
|
8, // [8:8] is the sub-list for extension type_name
|
||||||
|
8, // [8:8] is the sub-list for extension extendee
|
||||||
|
0, // [0:8] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_domain_proto_init() }
|
||||||
|
func file_domain_proto_init() {
|
||||||
|
if File_domain_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !protoimpl.UnsafeEnabled {
|
||||||
|
file_domain_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Container); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_domain_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Source); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_domain_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*Destination); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_domain_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*BuildInfo); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_domain_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*AppState); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_domain_proto_msgTypes[0].OneofWrappers = []interface{}{}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: file_domain_proto_rawDesc,
|
||||||
|
NumEnums: 1,
|
||||||
|
NumMessages: 5,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 0,
|
||||||
|
},
|
||||||
|
GoTypes: file_domain_proto_goTypes,
|
||||||
|
DependencyIndexes: file_domain_proto_depIdxs,
|
||||||
|
EnumInfos: file_domain_proto_enumTypes,
|
||||||
|
MessageInfos: file_domain_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_domain_proto = out.File
|
||||||
|
file_domain_proto_rawDesc = nil
|
||||||
|
file_domain_proto_goTypes = nil
|
||||||
|
file_domain_proto_depIdxs = nil
|
||||||
|
}
|
1110
internal/generated/grpc/event.pb.go
Normal file
1110
internal/generated/grpc/event.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
110
internal/protocol/command.go
Normal file
110
internal/protocol/command.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.netflux.io/rob/octoplex/internal/event"
|
||||||
|
pb "git.netflux.io/rob/octoplex/internal/generated/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CommandToProto converts a command to a protobuf message.
|
||||||
|
func CommandToProto(command event.Command) *pb.Command {
|
||||||
|
switch evt := command.(type) {
|
||||||
|
case event.CommandAddDestination:
|
||||||
|
return buildAddDestinationCommand(evt)
|
||||||
|
case event.CommandRemoveDestination:
|
||||||
|
return buildRemoveDestinationCommand(evt)
|
||||||
|
case event.CommandStartDestination:
|
||||||
|
return buildStartDestinationCommand(evt)
|
||||||
|
case event.CommandStopDestination:
|
||||||
|
return buildStopDestinationCommand(evt)
|
||||||
|
case event.CommandCloseOtherInstance:
|
||||||
|
return buildCloseOtherInstanceCommand(evt)
|
||||||
|
case event.CommandKillServer:
|
||||||
|
return buildKillServerCommand(evt)
|
||||||
|
default:
|
||||||
|
panic("unknown command type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAddDestinationCommand(cmd event.CommandAddDestination) *pb.Command {
|
||||||
|
return &pb.Command{CommandType: &pb.Command_AddDestination{AddDestination: &pb.AddDestinationCommand{Name: cmd.DestinationName, Url: cmd.URL}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRemoveDestinationCommand(cmd event.CommandRemoveDestination) *pb.Command {
|
||||||
|
return &pb.Command{CommandType: &pb.Command_RemoveDestination{RemoveDestination: &pb.RemoveDestinationCommand{Url: cmd.URL}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildStartDestinationCommand(cmd event.CommandStartDestination) *pb.Command {
|
||||||
|
return &pb.Command{CommandType: &pb.Command_StartDestination{StartDestination: &pb.StartDestinationCommand{Url: cmd.URL}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildStopDestinationCommand(cmd event.CommandStopDestination) *pb.Command {
|
||||||
|
return &pb.Command{CommandType: &pb.Command_StopDestination{StopDestination: &pb.StopDestinationCommand{Url: cmd.URL}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCloseOtherInstanceCommand(event.CommandCloseOtherInstance) *pb.Command {
|
||||||
|
return &pb.Command{CommandType: &pb.Command_CloseOtherInstances{CloseOtherInstances: &pb.CloseOtherInstancesCommand{}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildKillServerCommand(event.CommandKillServer) *pb.Command {
|
||||||
|
return &pb.Command{CommandType: &pb.Command_KillServer{KillServer: &pb.KillServerCommand{}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandFromProto converts a protobuf message to a command.
|
||||||
|
func CommandFromProto(pbCmd *pb.Command) event.Command {
|
||||||
|
if pbCmd == nil || pbCmd.CommandType == nil {
|
||||||
|
panic("invalid or nil pb.Command")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cmd := pbCmd.CommandType.(type) {
|
||||||
|
case *pb.Command_AddDestination:
|
||||||
|
return parseAddDestinationCommand(cmd.AddDestination)
|
||||||
|
case *pb.Command_RemoveDestination:
|
||||||
|
return parseRemoveDestinationCommand(cmd.RemoveDestination)
|
||||||
|
case *pb.Command_StartDestination:
|
||||||
|
return parseStartDestinationCommand(cmd.StartDestination)
|
||||||
|
case *pb.Command_StopDestination:
|
||||||
|
return parseStopDestinationCommand(cmd.StopDestination)
|
||||||
|
case *pb.Command_CloseOtherInstances:
|
||||||
|
return parseCloseOtherInstanceCommand(cmd.CloseOtherInstances)
|
||||||
|
case *pb.Command_KillServer:
|
||||||
|
return parseKillServerCommand(cmd.KillServer)
|
||||||
|
default:
|
||||||
|
panic("unknown pb.Command type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAddDestinationCommand(cmd *pb.AddDestinationCommand) event.Command {
|
||||||
|
if cmd == nil {
|
||||||
|
panic("nil AddDestinationCommand")
|
||||||
|
}
|
||||||
|
return event.CommandAddDestination{DestinationName: cmd.Name, URL: cmd.Url}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRemoveDestinationCommand(cmd *pb.RemoveDestinationCommand) event.Command {
|
||||||
|
if cmd == nil {
|
||||||
|
panic("nil RemoveDestinationCommand")
|
||||||
|
}
|
||||||
|
return event.CommandRemoveDestination{URL: cmd.Url}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseStartDestinationCommand(cmd *pb.StartDestinationCommand) event.Command {
|
||||||
|
if cmd == nil {
|
||||||
|
panic("nil StartDestinationCommand")
|
||||||
|
}
|
||||||
|
return event.CommandStartDestination{URL: cmd.Url}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseStopDestinationCommand(cmd *pb.StopDestinationCommand) event.Command {
|
||||||
|
if cmd == nil {
|
||||||
|
panic("nil StopDestinationCommand")
|
||||||
|
}
|
||||||
|
return event.CommandStopDestination{URL: cmd.Url}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCloseOtherInstanceCommand(_ *pb.CloseOtherInstancesCommand) event.Command {
|
||||||
|
return event.CommandCloseOtherInstance{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseKillServerCommand(_ *pb.KillServerCommand) event.Command {
|
||||||
|
return event.CommandKillServer{}
|
||||||
|
}
|
180
internal/protocol/command_test.go
Normal file
180
internal/protocol/command_test.go
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
package protocol_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.netflux.io/rob/octoplex/internal/event"
|
||||||
|
pb "git.netflux.io/rob/octoplex/internal/generated/grpc"
|
||||||
|
"git.netflux.io/rob/octoplex/internal/protocol"
|
||||||
|
gocmp "github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/protobuf/testing/protocmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommandToProto(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
in event.Command
|
||||||
|
want *pb.Command
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "AddDestination",
|
||||||
|
in: event.CommandAddDestination{
|
||||||
|
DestinationName: "test",
|
||||||
|
URL: "rtmp://rtmp.example.com",
|
||||||
|
},
|
||||||
|
want: &pb.Command{
|
||||||
|
CommandType: &pb.Command_AddDestination{
|
||||||
|
AddDestination: &pb.AddDestinationCommand{
|
||||||
|
Name: "test",
|
||||||
|
Url: "rtmp://rtmp.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "RemoveDestination",
|
||||||
|
in: event.CommandRemoveDestination{
|
||||||
|
URL: "rtmp://remove.example.com",
|
||||||
|
},
|
||||||
|
want: &pb.Command{
|
||||||
|
CommandType: &pb.Command_RemoveDestination{
|
||||||
|
RemoveDestination: &pb.RemoveDestinationCommand{
|
||||||
|
Url: "rtmp://remove.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "StartDestination",
|
||||||
|
in: event.CommandStartDestination{
|
||||||
|
URL: "rtmp://start.example.com",
|
||||||
|
},
|
||||||
|
want: &pb.Command{
|
||||||
|
CommandType: &pb.Command_StartDestination{
|
||||||
|
StartDestination: &pb.StartDestinationCommand{
|
||||||
|
Url: "rtmp://start.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "StopDestination",
|
||||||
|
in: event.CommandStopDestination{
|
||||||
|
URL: "rtmp://stop.example.com",
|
||||||
|
},
|
||||||
|
want: &pb.Command{
|
||||||
|
CommandType: &pb.Command_StopDestination{
|
||||||
|
StopDestination: &pb.StopDestinationCommand{
|
||||||
|
Url: "rtmp://stop.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CloseOtherInstance",
|
||||||
|
in: event.CommandCloseOtherInstance{},
|
||||||
|
want: &pb.Command{
|
||||||
|
CommandType: &pb.Command_CloseOtherInstances{
|
||||||
|
CloseOtherInstances: &pb.CloseOtherInstancesCommand{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "KillServer",
|
||||||
|
in: event.CommandKillServer{},
|
||||||
|
want: &pb.Command{
|
||||||
|
CommandType: &pb.Command_KillServer{
|
||||||
|
KillServer: &pb.KillServerCommand{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Empty(t, gocmp.Diff(tc.want, protocol.CommandToProto(tc.in), protocmp.Transform()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandFromProto(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
in *pb.Command
|
||||||
|
want event.Command
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "AddDestination",
|
||||||
|
in: &pb.Command{
|
||||||
|
CommandType: &pb.Command_AddDestination{
|
||||||
|
AddDestination: &pb.AddDestinationCommand{
|
||||||
|
Name: "test",
|
||||||
|
Url: "rtmp://rtmp.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: event.CommandAddDestination{
|
||||||
|
DestinationName: "test",
|
||||||
|
URL: "rtmp://rtmp.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "RemoveDestination",
|
||||||
|
in: &pb.Command{
|
||||||
|
CommandType: &pb.Command_RemoveDestination{
|
||||||
|
RemoveDestination: &pb.RemoveDestinationCommand{
|
||||||
|
Url: "rtmp://remove.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: event.CommandRemoveDestination{URL: "rtmp://remove.example.com"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "StartDestination",
|
||||||
|
in: &pb.Command{
|
||||||
|
CommandType: &pb.Command_StartDestination{
|
||||||
|
StartDestination: &pb.StartDestinationCommand{
|
||||||
|
Url: "rtmp://start.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: event.CommandStartDestination{URL: "rtmp://start.example.com"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "StopDestination",
|
||||||
|
in: &pb.Command{
|
||||||
|
CommandType: &pb.Command_StopDestination{
|
||||||
|
StopDestination: &pb.StopDestinationCommand{
|
||||||
|
Url: "rtmp://stop.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: event.CommandStopDestination{URL: "rtmp://stop.example.com"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "CloseOtherInstance",
|
||||||
|
in: &pb.Command{
|
||||||
|
CommandType: &pb.Command_CloseOtherInstances{
|
||||||
|
CloseOtherInstances: &pb.CloseOtherInstancesCommand{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: event.CommandCloseOtherInstance{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "KillServer",
|
||||||
|
in: &pb.Command{
|
||||||
|
CommandType: &pb.Command_KillServer{
|
||||||
|
KillServer: &pb.KillServerCommand{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: event.CommandKillServer{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Empty(t, gocmp.Diff(tc.want, protocol.CommandFromProto(tc.in)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
143
internal/protocol/domain.go
Normal file
143
internal/protocol/domain.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.netflux.io/rob/octoplex/internal/domain"
|
||||||
|
pb "git.netflux.io/rob/octoplex/internal/generated/grpc"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ContainerToProto converts a domain.Container to a protobuf Container.
|
||||||
|
func ContainerToProto(c domain.Container) *pb.Container {
|
||||||
|
var errString string
|
||||||
|
if c.Err != nil {
|
||||||
|
errString = c.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
var exitCode *int32
|
||||||
|
if c.ExitCode != nil {
|
||||||
|
code := int32(*c.ExitCode)
|
||||||
|
exitCode = &code
|
||||||
|
}
|
||||||
|
|
||||||
|
var rxSince *timestamppb.Timestamp
|
||||||
|
if !c.RxSince.IsZero() {
|
||||||
|
rxSince = timestamppb.New(c.RxSince)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.Container{
|
||||||
|
Id: c.ID,
|
||||||
|
Status: c.Status,
|
||||||
|
HealthState: c.HealthState,
|
||||||
|
CpuPercent: c.CPUPercent,
|
||||||
|
MemoryUsageBytes: c.MemoryUsageBytes,
|
||||||
|
RxRate: int32(c.RxRate),
|
||||||
|
TxRate: int32(c.TxRate),
|
||||||
|
RxSince: rxSince,
|
||||||
|
ImageName: c.ImageName,
|
||||||
|
PullStatus: c.PullStatus,
|
||||||
|
PullProgress: c.PullProgress,
|
||||||
|
PullPercent: int32(c.PullPercent),
|
||||||
|
RestartCount: int32(c.RestartCount),
|
||||||
|
ExitCode: exitCode,
|
||||||
|
Err: errString,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainerFromProto converts a protobuf Container to a domain.Container.
|
||||||
|
func ContainerFromProto(pbContainer *pb.Container) domain.Container {
|
||||||
|
if pbContainer == nil {
|
||||||
|
return domain.Container{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var exitCode *int
|
||||||
|
if pbContainer.ExitCode != nil {
|
||||||
|
val := int(*pbContainer.ExitCode)
|
||||||
|
exitCode = &val
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if pbContainer.Err != "" {
|
||||||
|
err = errors.New(pbContainer.Err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var rxSince time.Time
|
||||||
|
if pbContainer.RxSince != nil {
|
||||||
|
rxSince = pbContainer.RxSince.AsTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.Container{
|
||||||
|
ID: pbContainer.Id,
|
||||||
|
Status: pbContainer.Status,
|
||||||
|
HealthState: pbContainer.HealthState,
|
||||||
|
CPUPercent: pbContainer.CpuPercent,
|
||||||
|
MemoryUsageBytes: pbContainer.MemoryUsageBytes,
|
||||||
|
RxRate: int(pbContainer.RxRate),
|
||||||
|
TxRate: int(pbContainer.TxRate),
|
||||||
|
RxSince: rxSince,
|
||||||
|
ImageName: pbContainer.ImageName,
|
||||||
|
PullStatus: pbContainer.PullStatus,
|
||||||
|
PullProgress: pbContainer.PullProgress,
|
||||||
|
PullPercent: int(pbContainer.PullPercent),
|
||||||
|
RestartCount: int(pbContainer.RestartCount),
|
||||||
|
ExitCode: exitCode,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestinationsToProto converts a slice of domain.Destinations to a slice of
|
||||||
|
// protobuf Destinations.
|
||||||
|
func DestinationsToProto(inDests []domain.Destination) []*pb.Destination {
|
||||||
|
destinations := make([]*pb.Destination, 0, len(inDests))
|
||||||
|
for _, d := range inDests {
|
||||||
|
destinations = append(destinations, DestinationToProto(d))
|
||||||
|
}
|
||||||
|
return destinations
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestinationToProto converts a domain.Destination to a protobuf Destination.
|
||||||
|
func DestinationToProto(d domain.Destination) *pb.Destination {
|
||||||
|
return &pb.Destination{
|
||||||
|
Container: ContainerToProto(d.Container),
|
||||||
|
Status: DestinationStatusToProto(d.Status),
|
||||||
|
Name: d.Name,
|
||||||
|
Url: d.URL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProtoToDestinations converts a slice of protobuf Destinations to a slice of
|
||||||
|
// domain.Destinations.
|
||||||
|
func ProtoToDestinations(pbDests []*pb.Destination) []domain.Destination {
|
||||||
|
if pbDests == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dests := make([]domain.Destination, 0, len(pbDests))
|
||||||
|
for _, pbDest := range pbDests {
|
||||||
|
if pbDest == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dests = append(dests, domain.Destination{
|
||||||
|
Container: ContainerFromProto(pbDest.Container),
|
||||||
|
Status: domain.DestinationStatus(pbDest.Status),
|
||||||
|
Name: pbDest.Name,
|
||||||
|
URL: pbDest.Url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return dests
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestinationStatusToProto converts a domain.DestinationStatus to a
|
||||||
|
// pb.Destination_Status.
|
||||||
|
func DestinationStatusToProto(s domain.DestinationStatus) pb.Destination_Status {
|
||||||
|
switch s {
|
||||||
|
case domain.DestinationStatusStarting:
|
||||||
|
return pb.Destination_STATUS_STARTING
|
||||||
|
case domain.DestinationStatusLive:
|
||||||
|
return pb.Destination_STATUS_LIVE
|
||||||
|
default:
|
||||||
|
return pb.Destination_STATUS_OFF_AIR
|
||||||
|
}
|
||||||
|
}
|
222
internal/protocol/domain_test.go
Normal file
222
internal/protocol/domain_test.go
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
package protocol_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.netflux.io/rob/octoplex/internal/domain"
|
||||||
|
pb "git.netflux.io/rob/octoplex/internal/generated/grpc"
|
||||||
|
"git.netflux.io/rob/octoplex/internal/protocol"
|
||||||
|
gocmp "github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/protobuf/testing/protocmp"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContainerToProto(t *testing.T) {
|
||||||
|
exitCode := 1
|
||||||
|
ts := time.Unix(1234567890, 0)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
in domain.Container
|
||||||
|
want *pb.Container
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "complete",
|
||||||
|
in: domain.Container{
|
||||||
|
ID: "abc123",
|
||||||
|
Status: "running",
|
||||||
|
HealthState: "healthy",
|
||||||
|
CPUPercent: 12.5,
|
||||||
|
MemoryUsageBytes: 2048,
|
||||||
|
RxRate: 100,
|
||||||
|
TxRate: 200,
|
||||||
|
RxSince: ts,
|
||||||
|
ImageName: "nginx",
|
||||||
|
PullStatus: "pulling",
|
||||||
|
PullProgress: "50%",
|
||||||
|
PullPercent: 50,
|
||||||
|
RestartCount: 3,
|
||||||
|
ExitCode: &exitCode,
|
||||||
|
Err: errors.New("container error"),
|
||||||
|
},
|
||||||
|
want: &pb.Container{
|
||||||
|
Id: "abc123",
|
||||||
|
Status: "running",
|
||||||
|
HealthState: "healthy",
|
||||||
|
CpuPercent: 12.5,
|
||||||
|
MemoryUsageBytes: 2048,
|
||||||
|
RxRate: 100,
|
||||||
|
TxRate: 200,
|
||||||
|
RxSince: timestamppb.New(ts),
|
||||||
|
ImageName: "nginx",
|
||||||
|
PullStatus: "pulling",
|
||||||
|
PullProgress: "50%",
|
||||||
|
PullPercent: 50,
|
||||||
|
RestartCount: 3,
|
||||||
|
ExitCode: protoInt32(1),
|
||||||
|
Err: "container error",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero values",
|
||||||
|
in: domain.Container{},
|
||||||
|
want: &pb.Container{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Empty(t, gocmp.Diff(tc.want, protocol.ContainerToProto(tc.in), protocmp.Transform()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerFromProto(t *testing.T) {
|
||||||
|
ts := timestamppb.New(time.Now())
|
||||||
|
exitCode := int32(2)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
in *pb.Container
|
||||||
|
want domain.Container
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "complete",
|
||||||
|
in: &pb.Container{
|
||||||
|
Id: "xyz789",
|
||||||
|
Status: "exited",
|
||||||
|
HealthState: "unhealthy",
|
||||||
|
CpuPercent: 42.0,
|
||||||
|
MemoryUsageBytes: 4096,
|
||||||
|
RxRate: 300,
|
||||||
|
TxRate: 400,
|
||||||
|
RxSince: ts,
|
||||||
|
ImageName: "redis",
|
||||||
|
PullStatus: "complete",
|
||||||
|
PullProgress: "100%",
|
||||||
|
PullPercent: 100,
|
||||||
|
RestartCount: 1,
|
||||||
|
ExitCode: &exitCode,
|
||||||
|
Err: "crash error",
|
||||||
|
},
|
||||||
|
want: domain.Container{
|
||||||
|
ID: "xyz789",
|
||||||
|
Status: "exited",
|
||||||
|
HealthState: "unhealthy",
|
||||||
|
CPUPercent: 42.0,
|
||||||
|
MemoryUsageBytes: 4096,
|
||||||
|
RxRate: 300,
|
||||||
|
TxRate: 400,
|
||||||
|
RxSince: ts.AsTime(),
|
||||||
|
ImageName: "redis",
|
||||||
|
PullStatus: "complete",
|
||||||
|
PullProgress: "100%",
|
||||||
|
PullPercent: 100,
|
||||||
|
RestartCount: 1,
|
||||||
|
ExitCode: protoInt(2),
|
||||||
|
Err: errors.New("crash error"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil proto",
|
||||||
|
in: nil,
|
||||||
|
want: domain.Container{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero values",
|
||||||
|
in: &pb.Container{},
|
||||||
|
want: domain.Container{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
got := protocol.ContainerFromProto(tc.in)
|
||||||
|
assert.Empty(
|
||||||
|
t,
|
||||||
|
gocmp.Diff(
|
||||||
|
tc.want,
|
||||||
|
got,
|
||||||
|
gocmp.Comparer(compareErrorMessages),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDestinationConversions(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
in domain.Destination
|
||||||
|
want *pb.Destination
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic destination",
|
||||||
|
in: domain.Destination{
|
||||||
|
Name: "dest1",
|
||||||
|
URL: "rtmp://dest1",
|
||||||
|
Status: domain.DestinationStatusLive,
|
||||||
|
Container: domain.Container{ID: "c1"},
|
||||||
|
},
|
||||||
|
want: &pb.Destination{
|
||||||
|
Name: "dest1",
|
||||||
|
Url: "rtmp://dest1",
|
||||||
|
Status: pb.Destination_STATUS_LIVE,
|
||||||
|
Container: &pb.Container{Id: "c1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
proto := protocol.DestinationToProto(tc.in)
|
||||||
|
assert.Equal(t, tc.want.Name, proto.Name)
|
||||||
|
assert.Equal(t, tc.want.Url, proto.Url)
|
||||||
|
assert.Equal(t, tc.want.Status, proto.Status)
|
||||||
|
require.NotNil(t, proto.Container)
|
||||||
|
assert.Equal(t, tc.want.Container.Id, proto.Container.Id)
|
||||||
|
|
||||||
|
dests := protocol.ProtoToDestinations([]*pb.Destination{proto})
|
||||||
|
assert.Len(t, dests, 1)
|
||||||
|
assert.Equal(t, tc.in.Name, dests[0].Name)
|
||||||
|
assert.Equal(t, tc.in.URL, dests[0].URL)
|
||||||
|
assert.Equal(t, tc.in.Status, dests[0].Status)
|
||||||
|
assert.Equal(t, tc.in.Container.ID, dests[0].Container.ID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDestinationStatusToProto(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
in domain.DestinationStatus
|
||||||
|
want pb.Destination_Status
|
||||||
|
}{
|
||||||
|
{"Starting", domain.DestinationStatusStarting, pb.Destination_STATUS_STARTING},
|
||||||
|
{"Live", domain.DestinationStatusLive, pb.Destination_STATUS_LIVE},
|
||||||
|
{"Off-air", domain.DestinationStatusOffAir, pb.Destination_STATUS_OFF_AIR},
|
||||||
|
{"Unknown", domain.DestinationStatus(999), pb.Destination_STATUS_OFF_AIR},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.want, protocol.DestinationStatusToProto(tc.in))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func protoInt32(v int32) *int32 { return &v }
|
||||||
|
func protoInt(v int) *int { return &v }
|
||||||
|
|
||||||
|
// compareErrorMessages compares two error messages for equality using only the
|
||||||
|
// error message string.
|
||||||
|
func compareErrorMessages(x, y error) bool {
|
||||||
|
if x == nil || y == nil {
|
||||||
|
return x == y
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.Error() == y.Error()
|
||||||
|
}
|
263
internal/protocol/event.go
Normal file
263
internal/protocol/event.go
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.netflux.io/rob/octoplex/internal/domain"
|
||||||
|
"git.netflux.io/rob/octoplex/internal/event"
|
||||||
|
pb "git.netflux.io/rob/octoplex/internal/generated/grpc"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EventToProto converts an event to a protobuf message.
|
||||||
|
func EventToProto(ev event.Event) *pb.Event {
|
||||||
|
switch evt := ev.(type) {
|
||||||
|
case event.AppStateChangedEvent:
|
||||||
|
return buildAppStateChangeEvent(evt)
|
||||||
|
case event.DestinationAddedEvent:
|
||||||
|
return buildDestinationAddedEvent(evt)
|
||||||
|
case event.AddDestinationFailedEvent:
|
||||||
|
return buildAddDestinationFailedEvent(evt)
|
||||||
|
case event.DestinationStreamExitedEvent:
|
||||||
|
return buildDestinationStreamExitedEvent(evt)
|
||||||
|
case event.StartDestinationFailedEvent:
|
||||||
|
return buildStartDestinationFailedEvent(evt)
|
||||||
|
case event.DestinationRemovedEvent:
|
||||||
|
return buildDestinationRemovedEvent(evt)
|
||||||
|
case event.RemoveDestinationFailedEvent:
|
||||||
|
return buildRemoveDestinationFailedEvent(evt)
|
||||||
|
case event.FatalErrorOccurredEvent:
|
||||||
|
return buildFatalErrorOccurredEvent(evt)
|
||||||
|
case event.OtherInstanceDetectedEvent:
|
||||||
|
return buildOtherInstanceDetectedEvent(evt)
|
||||||
|
case event.MediaServerStartedEvent:
|
||||||
|
return buildMediaServerStartedEvent(evt)
|
||||||
|
default:
|
||||||
|
panic("unknown event type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAppStateChangeEvent(evt event.AppStateChangedEvent) *pb.Event {
|
||||||
|
var liveChangedAt *timestamppb.Timestamp
|
||||||
|
if !evt.State.Source.LiveChangedAt.IsZero() {
|
||||||
|
liveChangedAt = timestamppb.New(evt.State.Source.LiveChangedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.Event{
|
||||||
|
EventType: &pb.Event_AppStateChanged{
|
||||||
|
AppStateChanged: &pb.AppStateChangedEvent{
|
||||||
|
AppState: &pb.AppState{
|
||||||
|
Source: &pb.Source{
|
||||||
|
Container: ContainerToProto(evt.State.Source.Container),
|
||||||
|
Live: evt.State.Source.Live,
|
||||||
|
LiveChangedAt: liveChangedAt,
|
||||||
|
Tracks: evt.State.Source.Tracks,
|
||||||
|
ExitReason: evt.State.Source.ExitReason,
|
||||||
|
},
|
||||||
|
Destinations: DestinationsToProto(evt.State.Destinations),
|
||||||
|
BuildInfo: &pb.BuildInfo{
|
||||||
|
GoVersion: evt.State.BuildInfo.GoVersion,
|
||||||
|
Version: evt.State.BuildInfo.Version,
|
||||||
|
Commit: evt.State.BuildInfo.Commit,
|
||||||
|
Date: evt.State.BuildInfo.Date,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDestinationAddedEvent(evt event.DestinationAddedEvent) *pb.Event {
|
||||||
|
return &pb.Event{
|
||||||
|
EventType: &pb.Event_DestinationAdded{
|
||||||
|
DestinationAdded: &pb.DestinationAddedEvent{Url: evt.URL},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAddDestinationFailedEvent(evt event.AddDestinationFailedEvent) *pb.Event {
|
||||||
|
return &pb.Event{
|
||||||
|
EventType: &pb.Event_AddDestinationFailed{
|
||||||
|
AddDestinationFailed: &pb.AddDestinationFailedEvent{Url: evt.URL, Error: evt.Err.Error()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDestinationStreamExitedEvent(evt event.DestinationStreamExitedEvent) *pb.Event {
|
||||||
|
return &pb.Event{
|
||||||
|
EventType: &pb.Event_DestinationStreamExited{
|
||||||
|
DestinationStreamExited: &pb.DestinationStreamExitedEvent{Name: evt.Name, Error: evt.Err.Error()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildStartDestinationFailedEvent(evt event.StartDestinationFailedEvent) *pb.Event {
|
||||||
|
return &pb.Event{
|
||||||
|
EventType: &pb.Event_StartDestinationFailed{
|
||||||
|
StartDestinationFailed: &pb.StartDestinationFailedEvent{Url: evt.URL, Message: evt.Message},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDestinationRemovedEvent(evt event.DestinationRemovedEvent) *pb.Event {
|
||||||
|
return &pb.Event{
|
||||||
|
EventType: &pb.Event_DestinationRemoved{
|
||||||
|
DestinationRemoved: &pb.DestinationRemovedEvent{Url: evt.URL},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRemoveDestinationFailedEvent(evt event.RemoveDestinationFailedEvent) *pb.Event {
|
||||||
|
return &pb.Event{
|
||||||
|
EventType: &pb.Event_RemoveDestinationFailed{
|
||||||
|
RemoveDestinationFailed: &pb.RemoveDestinationFailedEvent{Url: evt.URL, Error: evt.Err.Error()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildFatalErrorOccurredEvent(evt event.FatalErrorOccurredEvent) *pb.Event {
|
||||||
|
return &pb.Event{
|
||||||
|
EventType: &pb.Event_FatalError{
|
||||||
|
FatalError: &pb.FatalErrorEvent{Message: evt.Message},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildOtherInstanceDetectedEvent(_ event.OtherInstanceDetectedEvent) *pb.Event {
|
||||||
|
return &pb.Event{
|
||||||
|
EventType: &pb.Event_OtherInstanceDetected{
|
||||||
|
OtherInstanceDetected: &pb.OtherInstanceDetectedEvent{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMediaServerStartedEvent(evt event.MediaServerStartedEvent) *pb.Event {
|
||||||
|
return &pb.Event{
|
||||||
|
EventType: &pb.Event_MediaServerStarted{
|
||||||
|
MediaServerStarted: &pb.MediaServerStartedEvent{RtmpUrl: evt.RTMPURL, RtmpsUrl: evt.RTMPSURL},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventFromProto converts a protobuf message to an event.
|
||||||
|
func EventFromProto(pbEv *pb.Event) event.Event {
|
||||||
|
if pbEv == nil || pbEv.EventType == nil {
|
||||||
|
panic("invalid or nil pb.Event")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch evt := pbEv.EventType.(type) {
|
||||||
|
case *pb.Event_AppStateChanged:
|
||||||
|
return parseAppStateChangedEvent(evt.AppStateChanged)
|
||||||
|
case *pb.Event_DestinationAdded:
|
||||||
|
return parseDestinationAddedEvent(evt.DestinationAdded)
|
||||||
|
case *pb.Event_AddDestinationFailed:
|
||||||
|
return parseAddDestinationFailedEvent(evt.AddDestinationFailed)
|
||||||
|
case *pb.Event_DestinationStreamExited:
|
||||||
|
return parseDestinationStreamExitedEvent(evt.DestinationStreamExited)
|
||||||
|
case *pb.Event_StartDestinationFailed:
|
||||||
|
return parseStartDestinationFailedEvent(evt.StartDestinationFailed)
|
||||||
|
case *pb.Event_DestinationRemoved:
|
||||||
|
return parseDestinationRemovedEvent(evt.DestinationRemoved)
|
||||||
|
case *pb.Event_RemoveDestinationFailed:
|
||||||
|
return parseRemoveDestinationFailedEvent(evt.RemoveDestinationFailed)
|
||||||
|
case *pb.Event_FatalError:
|
||||||
|
return parseFatalErrorOccurredEvent(evt.FatalError)
|
||||||
|
case *pb.Event_OtherInstanceDetected:
|
||||||
|
return parseOtherInstanceDetectedEvent(evt.OtherInstanceDetected)
|
||||||
|
case *pb.Event_MediaServerStarted:
|
||||||
|
return parseMediaServerStartedEvent(evt.MediaServerStarted)
|
||||||
|
default:
|
||||||
|
panic("unknown pb.Event type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAppStateChangedEvent(evt *pb.AppStateChangedEvent) event.Event {
|
||||||
|
if evt == nil || evt.AppState == nil || evt.AppState.Source == nil {
|
||||||
|
panic("invalid AppStateChangedEvent")
|
||||||
|
}
|
||||||
|
|
||||||
|
var liveChangedAt time.Time
|
||||||
|
if evt.AppState.Source.LiveChangedAt != nil {
|
||||||
|
liveChangedAt = evt.AppState.Source.LiveChangedAt.AsTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
return event.AppStateChangedEvent{
|
||||||
|
State: domain.AppState{
|
||||||
|
Source: domain.Source{
|
||||||
|
Container: ContainerFromProto(evt.AppState.Source.Container),
|
||||||
|
Live: evt.AppState.Source.Live,
|
||||||
|
LiveChangedAt: liveChangedAt,
|
||||||
|
Tracks: evt.AppState.Source.Tracks,
|
||||||
|
ExitReason: evt.AppState.Source.ExitReason,
|
||||||
|
},
|
||||||
|
Destinations: ProtoToDestinations(evt.AppState.Destinations),
|
||||||
|
BuildInfo: domain.BuildInfo{
|
||||||
|
GoVersion: evt.AppState.BuildInfo.GoVersion,
|
||||||
|
Version: evt.AppState.BuildInfo.Version,
|
||||||
|
Commit: evt.AppState.BuildInfo.Commit,
|
||||||
|
Date: evt.AppState.BuildInfo.Date,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDestinationAddedEvent(evt *pb.DestinationAddedEvent) event.Event {
|
||||||
|
if evt == nil {
|
||||||
|
panic("nil DestinationAddedEvent")
|
||||||
|
}
|
||||||
|
return event.DestinationAddedEvent{URL: evt.Url}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAddDestinationFailedEvent(evt *pb.AddDestinationFailedEvent) event.Event {
|
||||||
|
if evt == nil {
|
||||||
|
panic("nil AddDestinationFailedEvent")
|
||||||
|
}
|
||||||
|
return event.AddDestinationFailedEvent{URL: evt.Url, Err: errors.New(evt.Error)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDestinationStreamExitedEvent(evt *pb.DestinationStreamExitedEvent) event.Event {
|
||||||
|
if evt == nil {
|
||||||
|
panic("nil DestinationStreamExitedEvent")
|
||||||
|
}
|
||||||
|
return event.DestinationStreamExitedEvent{Name: evt.Name, Err: errors.New(evt.Error)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseStartDestinationFailedEvent(evt *pb.StartDestinationFailedEvent) event.Event {
|
||||||
|
if evt == nil {
|
||||||
|
panic("nil StartDestinationFailedEvent")
|
||||||
|
}
|
||||||
|
return event.StartDestinationFailedEvent{URL: evt.Url, Message: evt.Message}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDestinationRemovedEvent(evt *pb.DestinationRemovedEvent) event.Event {
|
||||||
|
if evt == nil {
|
||||||
|
panic("nil DestinationRemovedEvent")
|
||||||
|
}
|
||||||
|
return event.DestinationRemovedEvent{URL: evt.Url}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRemoveDestinationFailedEvent(evt *pb.RemoveDestinationFailedEvent) event.Event {
|
||||||
|
if evt == nil {
|
||||||
|
panic("nil RemoveDestinationFailedEvent")
|
||||||
|
}
|
||||||
|
return event.RemoveDestinationFailedEvent{URL: evt.Url, Err: errors.New(evt.Error)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFatalErrorOccurredEvent(evt *pb.FatalErrorEvent) event.Event {
|
||||||
|
if evt == nil {
|
||||||
|
panic("nil FatalErrorEvent")
|
||||||
|
}
|
||||||
|
return event.FatalErrorOccurredEvent{Message: evt.Message}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseOtherInstanceDetectedEvent(_ *pb.OtherInstanceDetectedEvent) event.Event {
|
||||||
|
return event.OtherInstanceDetectedEvent{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMediaServerStartedEvent(evt *pb.MediaServerStartedEvent) event.Event {
|
||||||
|
if evt == nil {
|
||||||
|
panic("nil MediaServerStartedEvent")
|
||||||
|
}
|
||||||
|
return event.MediaServerStartedEvent{RTMPURL: evt.RtmpUrl, RTMPSURL: evt.RtmpsUrl}
|
||||||
|
}
|
267
internal/protocol/event_test.go
Normal file
267
internal/protocol/event_test.go
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
package protocol_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.netflux.io/rob/octoplex/internal/domain"
|
||||||
|
"git.netflux.io/rob/octoplex/internal/event"
|
||||||
|
pb "git.netflux.io/rob/octoplex/internal/generated/grpc"
|
||||||
|
"git.netflux.io/rob/octoplex/internal/protocol"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
gocmp "github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/protobuf/testing/protocmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEventToProto(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
in event.Event
|
||||||
|
want *pb.Event
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "AppStateChanged",
|
||||||
|
in: event.AppStateChangedEvent{
|
||||||
|
State: domain.AppState{
|
||||||
|
Source: domain.Source{
|
||||||
|
Container: domain.Container{
|
||||||
|
ID: "abc123",
|
||||||
|
},
|
||||||
|
Live: true,
|
||||||
|
},
|
||||||
|
Destinations: []domain.Destination{
|
||||||
|
{
|
||||||
|
Name: "dest1",
|
||||||
|
URL: "rtmp://dest1.example.com",
|
||||||
|
Container: domain.Container{
|
||||||
|
ID: "bcd456",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
BuildInfo: domain.BuildInfo{GoVersion: "go1.16", Version: "v1.0.0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &pb.Event{
|
||||||
|
EventType: &pb.Event_AppStateChanged{
|
||||||
|
AppStateChanged: &pb.AppStateChangedEvent{
|
||||||
|
AppState: &pb.AppState{
|
||||||
|
Source: &pb.Source{
|
||||||
|
Container: &pb.Container{
|
||||||
|
Id: "abc123",
|
||||||
|
},
|
||||||
|
Live: true,
|
||||||
|
},
|
||||||
|
Destinations: []*pb.Destination{
|
||||||
|
{
|
||||||
|
Name: "dest1",
|
||||||
|
Url: "rtmp://dest1.example.com",
|
||||||
|
Container: &pb.Container{
|
||||||
|
Id: "bcd456",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
BuildInfo: &pb.BuildInfo{GoVersion: "go1.16", Version: "v1.0.0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DestinationAdded",
|
||||||
|
in: event.DestinationAddedEvent{URL: "rtmp://dest.example.com"},
|
||||||
|
want: &pb.Event{
|
||||||
|
EventType: &pb.Event_DestinationAdded{
|
||||||
|
DestinationAdded: &pb.DestinationAddedEvent{
|
||||||
|
Url: "rtmp://dest.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AddDestinationFailed",
|
||||||
|
in: event.AddDestinationFailedEvent{URL: "rtmp://fail.example.com", Err: errors.New("failed")},
|
||||||
|
want: &pb.Event{
|
||||||
|
EventType: &pb.Event_AddDestinationFailed{
|
||||||
|
AddDestinationFailed: &pb.AddDestinationFailedEvent{
|
||||||
|
Url: "rtmp://fail.example.com",
|
||||||
|
Error: "failed",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DestinationStreamExited",
|
||||||
|
in: event.DestinationStreamExitedEvent{Name: "stream1", Err: errors.New("exit reason")},
|
||||||
|
want: &pb.Event{
|
||||||
|
EventType: &pb.Event_DestinationStreamExited{
|
||||||
|
DestinationStreamExited: &pb.DestinationStreamExitedEvent{
|
||||||
|
Name: "stream1",
|
||||||
|
Error: "exit reason",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "FatalErrorOccurred",
|
||||||
|
in: event.FatalErrorOccurredEvent{Message: "fatal error"},
|
||||||
|
want: &pb.Event{
|
||||||
|
EventType: &pb.Event_FatalError{
|
||||||
|
FatalError: &pb.FatalErrorEvent{Message: "fatal error"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OtherInstanceDetected",
|
||||||
|
in: event.OtherInstanceDetectedEvent{},
|
||||||
|
want: &pb.Event{
|
||||||
|
EventType: &pb.Event_OtherInstanceDetected{
|
||||||
|
OtherInstanceDetected: &pb.OtherInstanceDetectedEvent{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MediaServerStarted",
|
||||||
|
in: event.MediaServerStartedEvent{RTMPURL: "rtmp://media", RTMPSURL: "rtmps://media"},
|
||||||
|
want: &pb.Event{
|
||||||
|
EventType: &pb.Event_MediaServerStarted{
|
||||||
|
MediaServerStarted: &pb.MediaServerStartedEvent{
|
||||||
|
RtmpUrl: "rtmp://media",
|
||||||
|
RtmpsUrl: "rtmps://media",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Empty(t, gocmp.Diff(tc.want, protocol.EventToProto(tc.in), protocmp.Transform()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventFromProto(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
in *pb.Event
|
||||||
|
want event.Event
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "AppStateChanged",
|
||||||
|
in: &pb.Event{
|
||||||
|
EventType: &pb.Event_AppStateChanged{
|
||||||
|
AppStateChanged: &pb.AppStateChangedEvent{
|
||||||
|
AppState: &pb.AppState{
|
||||||
|
Source: &pb.Source{
|
||||||
|
Container: &pb.Container{Id: "abc123"},
|
||||||
|
Live: true,
|
||||||
|
},
|
||||||
|
Destinations: []*pb.Destination{
|
||||||
|
{
|
||||||
|
Name: "dest1",
|
||||||
|
Url: "rtmp://dest1.example.com",
|
||||||
|
Container: &pb.Container{Id: "bcd456"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
BuildInfo: &pb.BuildInfo{
|
||||||
|
GoVersion: "go1.16",
|
||||||
|
Version: "v1.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: event.AppStateChangedEvent{
|
||||||
|
State: domain.AppState{
|
||||||
|
Source: domain.Source{
|
||||||
|
Container: domain.Container{ID: "abc123"},
|
||||||
|
Live: true,
|
||||||
|
},
|
||||||
|
Destinations: []domain.Destination{
|
||||||
|
{
|
||||||
|
Name: "dest1",
|
||||||
|
URL: "rtmp://dest1.example.com",
|
||||||
|
Container: domain.Container{ID: "bcd456"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
BuildInfo: domain.BuildInfo{
|
||||||
|
GoVersion: "go1.16",
|
||||||
|
Version: "v1.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DestinationAdded",
|
||||||
|
in: &pb.Event{
|
||||||
|
EventType: &pb.Event_DestinationAdded{
|
||||||
|
DestinationAdded: &pb.DestinationAddedEvent{
|
||||||
|
Url: "rtmp://dest.example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: event.DestinationAddedEvent{URL: "rtmp://dest.example.com"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AddDestinationFailed",
|
||||||
|
in: &pb.Event{
|
||||||
|
EventType: &pb.Event_AddDestinationFailed{
|
||||||
|
AddDestinationFailed: &pb.AddDestinationFailedEvent{
|
||||||
|
Url: "rtmp://fail.example.com",
|
||||||
|
Error: "failed",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: event.AddDestinationFailedEvent{URL: "rtmp://fail.example.com", Err: errors.New("failed")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DestinationStreamExited",
|
||||||
|
in: &pb.Event{
|
||||||
|
EventType: &pb.Event_DestinationStreamExited{
|
||||||
|
DestinationStreamExited: &pb.DestinationStreamExitedEvent{
|
||||||
|
Name: "stream1",
|
||||||
|
Error: "exit reason",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: event.DestinationStreamExitedEvent{Name: "stream1", Err: errors.New("exit reason")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "FatalErrorOccurred",
|
||||||
|
in: &pb.Event{
|
||||||
|
EventType: &pb.Event_FatalError{
|
||||||
|
FatalError: &pb.FatalErrorEvent{Message: "fatal error"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: event.FatalErrorOccurredEvent{Message: "fatal error"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OtherInstanceDetected",
|
||||||
|
in: &pb.Event{
|
||||||
|
EventType: &pb.Event_OtherInstanceDetected{
|
||||||
|
OtherInstanceDetected: &pb.OtherInstanceDetectedEvent{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: event.OtherInstanceDetectedEvent{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MediaServerStarted",
|
||||||
|
in: &pb.Event{
|
||||||
|
EventType: &pb.Event_MediaServerStarted{
|
||||||
|
MediaServerStarted: &pb.MediaServerStartedEvent{
|
||||||
|
RtmpUrl: "rtmp://media",
|
||||||
|
RtmpsUrl: "rtmps://media",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: event.MediaServerStartedEvent{RTMPURL: "rtmp://media", RTMPSURL: "rtmps://media"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert.Empty(t, cmp.Diff(tc.want, protocol.EventFromProto(tc.in), gocmp.Comparer(compareErrorMessages)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
147
internal/server/grpc.go
Normal file
147
internal/server/grpc.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.netflux.io/rob/octoplex/internal/event"
|
||||||
|
pb "git.netflux.io/rob/octoplex/internal/generated/grpc"
|
||||||
|
"git.netflux.io/rob/octoplex/internal/protocol"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server is the gRPC server that handles incoming commands and outgoing
|
||||||
|
// events.
|
||||||
|
type Server struct {
|
||||||
|
pb.UnimplementedInternalAPIServer
|
||||||
|
|
||||||
|
dispatcher func(event.Command)
|
||||||
|
bus *event.Bus
|
||||||
|
logger *slog.Logger
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
clientCount int
|
||||||
|
clientC chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newServer creates a new gRPC server.
|
||||||
|
func newServer(
|
||||||
|
dispatcher func(event.Command),
|
||||||
|
bus *event.Bus,
|
||||||
|
logger *slog.Logger,
|
||||||
|
) *Server {
|
||||||
|
return &Server{
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
bus: bus,
|
||||||
|
clientC: make(chan struct{}, 1),
|
||||||
|
logger: logger.With("component", "server"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Communicate(stream pb.InternalAPI_CommunicateServer) error {
|
||||||
|
g, ctx := errgroup.WithContext(stream.Context())
|
||||||
|
|
||||||
|
// perform handshake:
|
||||||
|
startHandshakeCmd, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("receive start handshake command: %w", err)
|
||||||
|
}
|
||||||
|
if startHandshakeCmd.GetCommand() == nil || startHandshakeCmd.GetCommand().GetStartHandshake() == nil {
|
||||||
|
return fmt.Errorf("expected start handshake command but got: %T", startHandshakeCmd)
|
||||||
|
}
|
||||||
|
if err := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Event{Event: &pb.Event{EventType: &pb.Event_HandshakeCompleted{}}}}); err != nil {
|
||||||
|
return fmt.Errorf("send handshake completed event: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify that a client has connected and completed the handshake.
|
||||||
|
select {
|
||||||
|
case s.clientC <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
eventsC := s.bus.Register()
|
||||||
|
defer s.bus.Deregister(eventsC)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case evt := <-eventsC:
|
||||||
|
if err := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Event{Event: protocol.EventToProto(evt)}}); err != nil {
|
||||||
|
return fmt.Errorf("send event: %w", err)
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.clientCount++
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.clientCount--
|
||||||
|
s.mu.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
in, err := stream.Recv()
|
||||||
|
if err == io.EOF {
|
||||||
|
s.logger.Info("Client disconnected")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("receive message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch pbCmd := in.Payload.(type) {
|
||||||
|
case *pb.Envelope_Command:
|
||||||
|
cmd := protocol.CommandFromProto(pbCmd.Command)
|
||||||
|
s.logger.Debug("Received command from gRPC stream", "command", cmd.Name())
|
||||||
|
s.dispatcher(cmd)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("expected command but got: %T", pbCmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := g.Wait(); err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, context.Canceled) {
|
||||||
|
s.logger.Error("Client stream closed with error", "err", err)
|
||||||
|
return fmt.Errorf("errgroup.Wait: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("Client stream closed")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClientCount returns the number of connected clients.
|
||||||
|
func (s *Server) GetClientCount() int {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
return s.clientCount
|
||||||
|
}
|
||||||
|
|
||||||
|
const waitForClientTimeout = 10 * time.Second
|
||||||
|
|
||||||
|
// WaitForClient waits for _any_ client to connect and complete the handshake.
|
||||||
|
// It times out if no client has connected after 10 seconds.
|
||||||
|
func (s *Server) WaitForClient(ctx context.Context) error {
|
||||||
|
select {
|
||||||
|
case <-s.clientC:
|
||||||
|
return nil
|
||||||
|
case <-time.After(waitForClientTimeout):
|
||||||
|
return errors.New("timeout")
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"net"
|
||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -13,10 +14,11 @@ import (
|
|||||||
"git.netflux.io/rob/octoplex/internal/container"
|
"git.netflux.io/rob/octoplex/internal/container"
|
||||||
"git.netflux.io/rob/octoplex/internal/domain"
|
"git.netflux.io/rob/octoplex/internal/domain"
|
||||||
"git.netflux.io/rob/octoplex/internal/event"
|
"git.netflux.io/rob/octoplex/internal/event"
|
||||||
|
pb "git.netflux.io/rob/octoplex/internal/generated/grpc"
|
||||||
"git.netflux.io/rob/octoplex/internal/mediaserver"
|
"git.netflux.io/rob/octoplex/internal/mediaserver"
|
||||||
"git.netflux.io/rob/octoplex/internal/replicator"
|
"git.netflux.io/rob/octoplex/internal/replicator"
|
||||||
"git.netflux.io/rob/octoplex/internal/terminal"
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// App is an instance of the app.
|
// App is an instance of the app.
|
||||||
@ -26,11 +28,7 @@ type App struct {
|
|||||||
eventBus *event.Bus
|
eventBus *event.Bus
|
||||||
dispatchC chan event.Command
|
dispatchC chan event.Command
|
||||||
dockerClient container.DockerClient
|
dockerClient container.DockerClient
|
||||||
screen *terminal.Screen // Screen may be nil.
|
waitForClient bool
|
||||||
headless bool
|
|
||||||
clipboardAvailable bool
|
|
||||||
configFilePath string
|
|
||||||
buildInfo domain.BuildInfo
|
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,11 +37,8 @@ type Params struct {
|
|||||||
ConfigService *config.Service
|
ConfigService *config.Service
|
||||||
DockerClient container.DockerClient
|
DockerClient container.DockerClient
|
||||||
ChanSize int
|
ChanSize int
|
||||||
Screen *terminal.Screen // Screen may be nil.
|
|
||||||
Headless bool
|
|
||||||
ClipboardAvailable bool
|
|
||||||
ConfigFilePath string
|
ConfigFilePath string
|
||||||
BuildInfo domain.BuildInfo
|
WaitForClient bool
|
||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,11 +53,7 @@ func New(params Params) *App {
|
|||||||
eventBus: event.NewBus(params.Logger.With("component", "event_bus")),
|
eventBus: event.NewBus(params.Logger.With("component", "event_bus")),
|
||||||
dispatchC: make(chan event.Command, cmp.Or(params.ChanSize, defaultChanSize)),
|
dispatchC: make(chan event.Command, cmp.Or(params.ChanSize, defaultChanSize)),
|
||||||
dockerClient: params.DockerClient,
|
dockerClient: params.DockerClient,
|
||||||
screen: params.Screen,
|
waitForClient: params.WaitForClient,
|
||||||
headless: params.Headless,
|
|
||||||
clipboardAvailable: params.ClipboardAvailable,
|
|
||||||
configFilePath: params.ConfigFilePath,
|
|
||||||
buildInfo: params.BuildInfo,
|
|
||||||
logger: params.Logger,
|
logger: params.Logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,20 +69,26 @@ func (a *App) Run(ctx context.Context) error {
|
|||||||
return errors.New("config: either sources.mediaServer.rtmp.enabled or sources.mediaServer.rtmps.enabled must be set")
|
return errors.New("config: either sources.mediaServer.rtmp.enabled or sources.mediaServer.rtmps.enabled must be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !a.headless {
|
const grpcAddr = ":50051"
|
||||||
ui, err := terminal.StartUI(ctx, terminal.StartParams{
|
lis, err := net.Listen("tcp", grpcAddr)
|
||||||
EventBus: a.eventBus,
|
|
||||||
Dispatcher: func(cmd event.Command) { a.dispatchC <- cmd },
|
|
||||||
Screen: a.screen,
|
|
||||||
ClipboardAvailable: a.clipboardAvailable,
|
|
||||||
ConfigFilePath: a.configFilePath,
|
|
||||||
BuildInfo: a.buildInfo,
|
|
||||||
Logger: a.logger.With("component", "ui"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("start terminal user interface: %w", err)
|
return fmt.Errorf("listen: %w", err)
|
||||||
|
}
|
||||||
|
defer lis.Close()
|
||||||
|
|
||||||
|
grpcServer := grpc.NewServer()
|
||||||
|
grpcDone := make(chan error, 1)
|
||||||
|
internalAPI := newServer(a.DispatchAsync, a.eventBus, a.logger)
|
||||||
|
pb.RegisterInternalAPIServer(grpcServer, internalAPI)
|
||||||
|
go func() {
|
||||||
|
a.logger.Info("gRPC server started", "addr", grpcAddr)
|
||||||
|
grpcDone <- grpcServer.Serve(lis)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if a.waitForClient {
|
||||||
|
if err = internalAPI.WaitForClient(ctx); err != nil {
|
||||||
|
return fmt.Errorf("wait for client: %w", err)
|
||||||
}
|
}
|
||||||
defer ui.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// emptyUI is a dummy function that sets the UI state to an empty state, and
|
// emptyUI is a dummy function that sets the UI state to an empty state, and
|
||||||
@ -107,12 +104,13 @@ func (a *App) Run(ctx context.Context) error {
|
|||||||
a.eventBus.Send(event.AppStateChangedEvent{State: domain.AppState{}})
|
a.eventBus.Send(event.AppStateChangedEvent{State: domain.AppState{}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// doFatalError publishes a fatal error to the event bus, waiting for the
|
// doFatalError publishes a fatal error to the event bus. It will block until
|
||||||
// user to acknowledge it if not in headless mode.
|
// the user acknowledges it if there is 1 or more clients connected to the
|
||||||
|
// internal API.
|
||||||
doFatalError := func(msg string) {
|
doFatalError := func(msg string) {
|
||||||
a.eventBus.Send(event.FatalErrorOccurredEvent{Message: msg})
|
a.eventBus.Send(event.FatalErrorOccurredEvent{Message: msg})
|
||||||
|
|
||||||
if a.headless {
|
if internalAPI.GetClientCount() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,21 +175,20 @@ func (a *App) Run(ctx context.Context) error {
|
|||||||
defer uiUpdateT.Stop()
|
defer uiUpdateT.Stop()
|
||||||
|
|
||||||
startMediaServerC := make(chan struct{}, 1)
|
startMediaServerC := make(chan struct{}, 1)
|
||||||
if a.headless { // disable startup check in headless mode for now
|
|
||||||
startMediaServerC <- struct{}{}
|
|
||||||
} else {
|
|
||||||
if ok, startupErr := doStartupCheck(ctx, containerClient, a.eventBus); startupErr != nil {
|
if ok, startupErr := doStartupCheck(ctx, containerClient, a.eventBus); startupErr != nil {
|
||||||
doFatalError(startupErr.Error())
|
doFatalError(startupErr.Error())
|
||||||
return startupErr
|
return startupErr
|
||||||
} else if ok {
|
} else if ok {
|
||||||
startMediaServerC <- struct{}{}
|
startMediaServerC <- struct{}{}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
|
case grpcErr := <-grpcDone:
|
||||||
|
a.logger.Error("gRPC server exited", "err", grpcErr)
|
||||||
|
return grpcErr
|
||||||
case <-startMediaServerC:
|
case <-startMediaServerC:
|
||||||
if err = srv.Start(ctx); err != nil {
|
if err = srv.Start(ctx); err != nil {
|
||||||
return fmt.Errorf("start mediaserver: %w", err)
|
return fmt.Errorf("start mediaserver: %w", err)
|
||||||
@ -245,7 +242,12 @@ func (a *App) Dispatch(cmd event.Command) event.Event {
|
|||||||
return <-ch
|
return <-ch
|
||||||
}
|
}
|
||||||
|
|
||||||
// errExit is an error that indicates the app should exit.
|
// DispatchAsync dispatches a command to be executed synchronously.
|
||||||
|
func (a *App) DispatchAsync(cmd event.Command) {
|
||||||
|
a.dispatchC <- cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// errExit is an error that indicates the server should exit.
|
||||||
var errExit = errors.New("exit")
|
var errExit = errors.New("exit")
|
||||||
|
|
||||||
// handleCommand handles an incoming command. It may return an Event which will
|
// handleCommand handles an incoming command. It may return an Event which will
|
||||||
@ -253,7 +255,7 @@ var errExit = errors.New("exit")
|
|||||||
// benefit of synchronous callers. The event may be nil. It may also publish
|
// benefit of synchronous callers. The event may be nil. It may also publish
|
||||||
// other events to the event bus which are not returned. Currently the only
|
// other events to the event bus which are not returned. Currently the only
|
||||||
// error that may be returned is [errExit], which indicates to the main event
|
// error that may be returned is [errExit], which indicates to the main event
|
||||||
// loop that the app should exit.
|
// loop that the server should exit.
|
||||||
func (a *App) handleCommand(
|
func (a *App) handleCommand(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cmd event.Command,
|
cmd event.Command,
|
||||||
@ -262,7 +264,7 @@ func (a *App) handleCommand(
|
|||||||
containerClient *container.Client,
|
containerClient *container.Client,
|
||||||
startMediaServerC chan struct{},
|
startMediaServerC chan struct{},
|
||||||
) (evt event.Event, _ error) {
|
) (evt event.Event, _ error) {
|
||||||
a.logger.Debug("Command received", "cmd", cmd.Name())
|
a.logger.Debug("Command received in handler", "cmd", cmd.Name())
|
||||||
defer func() {
|
defer func() {
|
||||||
if evt != nil {
|
if evt != nil {
|
||||||
a.eventBus.Send(evt)
|
a.eventBus.Send(evt)
|
||||||
@ -272,6 +274,12 @@ func (a *App) handleCommand(
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// If the command is a syncCommand, we need to extract the command from it so
|
||||||
|
// it can be type-switched against.
|
||||||
|
if c, ok := cmd.(syncCommand); ok {
|
||||||
|
cmd = c.Command
|
||||||
|
}
|
||||||
|
|
||||||
switch c := cmd.(type) {
|
switch c := cmd.(type) {
|
||||||
case event.CommandAddDestination:
|
case event.CommandAddDestination:
|
||||||
newCfg := a.cfg
|
newCfg := a.cfg
|
||||||
@ -281,10 +289,11 @@ func (a *App) handleCommand(
|
|||||||
})
|
})
|
||||||
if err := a.configService.SetConfig(newCfg); err != nil {
|
if err := a.configService.SetConfig(newCfg); err != nil {
|
||||||
a.logger.Error("Add destination failed", "err", err)
|
a.logger.Error("Add destination failed", "err", err)
|
||||||
return event.AddDestinationFailedEvent{Err: err}, nil
|
return event.AddDestinationFailedEvent{URL: c.URL, Err: err}, nil
|
||||||
}
|
}
|
||||||
a.cfg = newCfg
|
a.cfg = newCfg
|
||||||
a.handleConfigUpdate(state)
|
a.handleConfigUpdate(state)
|
||||||
|
a.logger.Info("Destination added", "url", c.URL)
|
||||||
a.eventBus.Send(event.DestinationAddedEvent{URL: c.URL})
|
a.eventBus.Send(event.DestinationAddedEvent{URL: c.URL})
|
||||||
case event.CommandRemoveDestination:
|
case event.CommandRemoveDestination:
|
||||||
repl.StopDestination(c.URL) // no-op if not live
|
repl.StopDestination(c.URL) // no-op if not live
|
||||||
@ -294,7 +303,7 @@ func (a *App) handleCommand(
|
|||||||
})
|
})
|
||||||
if err := a.configService.SetConfig(newCfg); err != nil {
|
if err := a.configService.SetConfig(newCfg); err != nil {
|
||||||
a.logger.Error("Remove destination failed", "err", err)
|
a.logger.Error("Remove destination failed", "err", err)
|
||||||
a.eventBus.Send(event.RemoveDestinationFailedEvent{Err: err})
|
a.eventBus.Send(event.RemoveDestinationFailedEvent{URL: c.URL, Err: err})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
a.cfg = newCfg
|
a.cfg = newCfg
|
||||||
@ -302,7 +311,7 @@ func (a *App) handleCommand(
|
|||||||
a.eventBus.Send(event.DestinationRemovedEvent{URL: c.URL}) //nolint:gosimple
|
a.eventBus.Send(event.DestinationRemovedEvent{URL: c.URL}) //nolint:gosimple
|
||||||
case event.CommandStartDestination:
|
case event.CommandStartDestination:
|
||||||
if !state.Source.Live {
|
if !state.Source.Live {
|
||||||
a.eventBus.Send(event.StartDestinationFailedEvent{})
|
a.eventBus.Send(event.StartDestinationFailedEvent{URL: c.URL, Message: "source not live"})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,7 +324,7 @@ func (a *App) handleCommand(
|
|||||||
}
|
}
|
||||||
|
|
||||||
startMediaServerC <- struct{}{}
|
startMediaServerC <- struct{}{}
|
||||||
case event.CommandQuit:
|
case event.CommandKillServer:
|
||||||
return nil, errExit
|
return nil, errExit
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package app
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -3,6 +3,7 @@ package terminal
|
|||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"maps"
|
"maps"
|
||||||
@ -44,9 +45,9 @@ type UI struct {
|
|||||||
eventBus *event.Bus
|
eventBus *event.Bus
|
||||||
dispatch func(event.Command)
|
dispatch func(event.Command)
|
||||||
clipboardAvailable bool
|
clipboardAvailable bool
|
||||||
configFilePath string
|
|
||||||
rtmpURL, rtmpsURL string
|
rtmpURL, rtmpsURL string
|
||||||
buildInfo domain.BuildInfo
|
buildInfo domain.BuildInfo
|
||||||
|
appExitC chan error
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
|
|
||||||
// tview state
|
// tview state
|
||||||
@ -92,20 +93,20 @@ type ScreenCapture struct {
|
|||||||
Width, Height int
|
Width, Height int
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartParams contains the parameters for starting a new terminal user
|
// Params contains the parameters for starting a new terminal user
|
||||||
// interface.
|
// interface.
|
||||||
type StartParams struct {
|
type Params struct {
|
||||||
EventBus *event.Bus
|
EventBus *event.Bus
|
||||||
Dispatcher func(event.Command)
|
Dispatcher func(event.Command)
|
||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
ClipboardAvailable bool
|
ClipboardAvailable bool
|
||||||
ConfigFilePath string
|
|
||||||
BuildInfo domain.BuildInfo
|
BuildInfo domain.BuildInfo
|
||||||
Screen *Screen // Screen may be nil.
|
Screen *Screen // Screen may be nil.
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartUI starts the terminal user interface.
|
// NewUI creates the user interface. Call [Run] on the *UI instance to block
|
||||||
func StartUI(ctx context.Context, params StartParams) (*UI, error) {
|
// until it is completed.
|
||||||
|
func NewUI(ctx context.Context, params Params) (*UI, error) {
|
||||||
app := tview.NewApplication()
|
app := tview.NewApplication()
|
||||||
|
|
||||||
var screen tcell.Screen
|
var screen tcell.Screen
|
||||||
@ -211,7 +212,7 @@ func StartUI(ctx context.Context, params StartParams) (*UI, error) {
|
|||||||
eventBus: params.EventBus,
|
eventBus: params.EventBus,
|
||||||
dispatch: params.Dispatcher,
|
dispatch: params.Dispatcher,
|
||||||
clipboardAvailable: params.ClipboardAvailable,
|
clipboardAvailable: params.ClipboardAvailable,
|
||||||
configFilePath: params.ConfigFilePath,
|
appExitC: make(chan error, 1),
|
||||||
buildInfo: params.BuildInfo,
|
buildInfo: params.BuildInfo,
|
||||||
logger: params.Logger,
|
logger: params.Logger,
|
||||||
app: app,
|
app: app,
|
||||||
@ -237,8 +238,6 @@ func StartUI(ctx context.Context, params StartParams) (*UI, error) {
|
|||||||
app.SetInputCapture(ui.inputCaptureHandler)
|
app.SetInputCapture(ui.inputCaptureHandler)
|
||||||
app.SetAfterDrawFunc(ui.afterDrawHandler)
|
app.SetAfterDrawFunc(ui.afterDrawHandler)
|
||||||
|
|
||||||
go ui.run(ctx)
|
|
||||||
|
|
||||||
return ui, nil
|
return ui, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,32 +261,32 @@ func (ui *UI) renderAboutView() {
|
|||||||
ui.aboutView.AddItem(rtmpsURLView, 1, 0, false)
|
ui.aboutView.AddItem(rtmpsURLView, 1, 0, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.aboutView.AddItem(tview.NewTextView().SetDynamicColors(true).SetText("[grey]c[-] Copy config file path"), 1, 0, false)
|
|
||||||
ui.aboutView.AddItem(tview.NewTextView().SetDynamicColors(true).SetText("[grey]?[-] About"), 1, 0, false)
|
ui.aboutView.AddItem(tview.NewTextView().SetDynamicColors(true).SetText("[grey]?[-] About"), 1, 0, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *UI) run(ctx context.Context) {
|
var ErrUserClosed = errors.New("user closed UI")
|
||||||
defer func() {
|
|
||||||
// Ensure the application is stopped when the UI is closed.
|
|
||||||
ui.dispatch(event.CommandQuit{})
|
|
||||||
}()
|
|
||||||
|
|
||||||
|
// Run runs the user interface. It always returns a non-nil error, which will
|
||||||
|
// be [ErrUserClosed] if the user voluntarily closed the UI.
|
||||||
|
func (ui *UI) Run(ctx context.Context) error {
|
||||||
eventC := ui.eventBus.Register()
|
eventC := ui.eventBus.Register()
|
||||||
|
defer ui.eventBus.Deregister(eventC)
|
||||||
|
|
||||||
uiDone := make(chan struct{})
|
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
err := ui.app.Run()
|
||||||
uiDone <- struct{}{}
|
if err != nil {
|
||||||
}()
|
ui.logger.Error("Error in UI run loop, exiting", "err", err)
|
||||||
|
|
||||||
if err := ui.app.Run(); err != nil {
|
|
||||||
ui.logger.Error("tui application error", "err", err)
|
|
||||||
}
|
}
|
||||||
|
ui.appExitC <- err
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case evt := <-eventC:
|
case evt, ok := <-eventC:
|
||||||
|
if !ok {
|
||||||
|
// should never happen
|
||||||
|
return errors.New("event channel closed")
|
||||||
|
}
|
||||||
ui.app.QueueUpdateDraw(func() {
|
ui.app.QueueUpdateDraw(func() {
|
||||||
switch evt := evt.(type) {
|
switch evt := evt.(type) {
|
||||||
case event.AppStateChangedEvent:
|
case event.AppStateChangedEvent:
|
||||||
@ -313,12 +312,11 @@ func (ui *UI) run(ctx context.Context) {
|
|||||||
default:
|
default:
|
||||||
ui.logger.Warn("unhandled event", "event", evt)
|
ui.logger.Warn("unhandled event", "event", evt)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return ctx.Err()
|
||||||
case <-uiDone:
|
case err := <-ui.appExitC:
|
||||||
return
|
return cmp.Or(err, ErrUserClosed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -358,8 +356,6 @@ func (ui *UI) inputCaptureHandler(event *tcell.EventKey) *tcell.EventKey {
|
|||||||
return nil
|
return nil
|
||||||
case ' ':
|
case ' ':
|
||||||
ui.toggleDestination()
|
ui.toggleDestination()
|
||||||
case 'c', 'C':
|
|
||||||
ui.copyConfigFilePathToClipboard(ui.clipboardAvailable, ui.configFilePath)
|
|
||||||
case '?':
|
case '?':
|
||||||
ui.showAbout()
|
ui.showAbout()
|
||||||
case 'k': // tview vim bindings
|
case 'k': // tview vim bindings
|
||||||
@ -420,7 +416,7 @@ func (ui *UI) handleOtherInstanceDetected(event.OtherInstanceDetectedEvent) {
|
|||||||
if buttonIndex == 0 {
|
if buttonIndex == 0 {
|
||||||
ui.dispatch(event.CommandCloseOtherInstance{})
|
ui.dispatch(event.CommandCloseOtherInstance{})
|
||||||
} else {
|
} else {
|
||||||
ui.dispatch(event.CommandQuit{})
|
ui.dispatch(event.CommandKillServer{})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -450,7 +446,7 @@ func (ui *UI) handleFatalErrorOccurred(evt event.FatalErrorOccurredEvent) {
|
|||||||
[]string{"Quit"},
|
[]string{"Quit"},
|
||||||
false,
|
false,
|
||||||
func(int, string) {
|
func(int, string) {
|
||||||
ui.dispatch(event.CommandQuit{})
|
ui.dispatch(event.CommandKillServer{})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -825,6 +821,11 @@ func (ui *UI) Close() {
|
|||||||
ui.app.Stop()
|
ui.app.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait waits for the terminal user interface to finish.
|
||||||
|
func (ui *UI) Wait() {
|
||||||
|
<-ui.appExitC
|
||||||
|
}
|
||||||
|
|
||||||
func (ui *UI) addDestination() {
|
func (ui *UI) addDestination() {
|
||||||
const (
|
const (
|
||||||
inputLen = 60
|
inputLen = 60
|
||||||
@ -1006,28 +1007,6 @@ func (ui *UI) copySourceURLToClipboard(url string) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *UI) copyConfigFilePathToClipboard(clipboardAvailable bool, configFilePath string) {
|
|
||||||
var text string
|
|
||||||
if clipboardAvailable {
|
|
||||||
if configFilePath != "" {
|
|
||||||
clipboard.Write(clipboard.FmtText, []byte(configFilePath))
|
|
||||||
text = "Configuration file path copied to clipboard:\n\n" + configFilePath
|
|
||||||
} else {
|
|
||||||
text = "Configuration file path not set"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
text = "Copy to clipboard not available"
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.showModal(
|
|
||||||
pageNameModalClipboard,
|
|
||||||
text,
|
|
||||||
[]string{"Ok"},
|
|
||||||
false,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *UI) confirmQuit() {
|
func (ui *UI) confirmQuit() {
|
||||||
ui.showModal(
|
ui.showModal(
|
||||||
pageNameModalQuit,
|
pageNameModalQuit,
|
||||||
@ -1036,7 +1015,7 @@ func (ui *UI) confirmQuit() {
|
|||||||
false,
|
false,
|
||||||
func(buttonIndex int, _ string) {
|
func(buttonIndex int, _ string) {
|
||||||
if buttonIndex == 0 {
|
if buttonIndex == 0 {
|
||||||
ui.dispatch(event.CommandQuit{})
|
ui.app.Stop()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
304
main.go
304
main.go
@ -4,21 +4,23 @@ import (
|
|||||||
"cmp"
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.netflux.io/rob/octoplex/internal/app"
|
"git.netflux.io/rob/octoplex/internal/client"
|
||||||
"git.netflux.io/rob/octoplex/internal/config"
|
"git.netflux.io/rob/octoplex/internal/config"
|
||||||
"git.netflux.io/rob/octoplex/internal/domain"
|
"git.netflux.io/rob/octoplex/internal/domain"
|
||||||
|
"git.netflux.io/rob/octoplex/internal/server"
|
||||||
dockerclient "github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
"golang.design/x/clipboard"
|
"golang.design/x/clipboard"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -30,23 +32,108 @@ var (
|
|||||||
date string
|
date string
|
||||||
)
|
)
|
||||||
|
|
||||||
var errShutdown = errors.New("shutdown")
|
// errInterrupt is an error type that indicates an interrupt signal was
|
||||||
|
// received.
|
||||||
|
type errInterrupt struct{}
|
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (e errInterrupt) Error() string {
|
||||||
|
return "interrupt signal received"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitCode implements the ExitCoder interface.
|
||||||
|
func (e errInterrupt) ExitCode() int {
|
||||||
|
return 130
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var exitStatus int
|
app := &cli.App{
|
||||||
|
Name: "Octoplex",
|
||||||
if err := run(); errors.Is(err, errShutdown) {
|
Usage: "Octoplex is a live video restreamer for Docker.",
|
||||||
exitStatus = 130
|
Commands: []*cli.Command{
|
||||||
} else if err != nil {
|
{
|
||||||
exitStatus = 1
|
Name: "client",
|
||||||
_, _ = os.Stderr.WriteString("Error: " + err.Error() + "\n")
|
Usage: "Run the client",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
return runClient(c.Context, c)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "server",
|
||||||
|
Usage: "Run the server",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
return runServer(c.Context, c, serverConfig{
|
||||||
|
stderrAvailable: true,
|
||||||
|
handleSigInt: true,
|
||||||
|
waitForClient: false,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "run",
|
||||||
|
Usage: "Run server and client together (testing)",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
return runClientAndServer(c)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(exitStatus)
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func run() error {
|
func runClient(ctx context.Context, _ *cli.Context) error {
|
||||||
ctx, cancel := context.WithCancelCause(context.Background())
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// TODO: logger from config
|
||||||
|
fptr, err := os.OpenFile("octoplex.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("open log file: %w", err)
|
||||||
|
}
|
||||||
|
logger := slog.New(slog.NewTextHandler(fptr, nil))
|
||||||
|
logger.Info("Starting client", "version", cmp.Or(version, "devel"), "commit", cmp.Or(commit, "unknown"), "date", cmp.Or(date, "unknown"), "go_version", runtime.Version())
|
||||||
|
|
||||||
|
var clipboardAvailable bool
|
||||||
|
if err = clipboard.Init(); err != nil {
|
||||||
|
logger.Warn("Clipboard not available", "err", err)
|
||||||
|
} else {
|
||||||
|
clipboardAvailable = true
|
||||||
|
}
|
||||||
|
|
||||||
|
buildInfo, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("read build info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app := client.New(client.NewParams{
|
||||||
|
ClipboardAvailable: clipboardAvailable,
|
||||||
|
BuildInfo: domain.BuildInfo{
|
||||||
|
GoVersion: buildInfo.GoVersion,
|
||||||
|
Version: version,
|
||||||
|
Commit: commit,
|
||||||
|
Date: date,
|
||||||
|
},
|
||||||
|
Logger: logger,
|
||||||
|
})
|
||||||
|
if err := app.Run(ctx); err != nil {
|
||||||
|
return fmt.Errorf("run app: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverConfig struct {
|
||||||
|
stderrAvailable bool
|
||||||
|
handleSigInt bool
|
||||||
|
waitForClient bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func runServer(ctx context.Context, _ *cli.Context, serverCfg serverConfig) error {
|
||||||
|
ctx, cancel := context.WithCancelCause(ctx)
|
||||||
defer cancel(nil)
|
defer cancel(nil)
|
||||||
|
|
||||||
configService, err := config.NewDefaultService()
|
configService, err := config.NewDefaultService()
|
||||||
@ -54,45 +141,35 @@ func run() error {
|
|||||||
return fmt.Errorf("build config service: %w", err)
|
return fmt.Errorf("build config service: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
help := flag.Bool("h", false, "Show help")
|
|
||||||
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *help {
|
|
||||||
printUsage()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if narg := flag.NArg(); narg > 1 {
|
|
||||||
printUsage()
|
|
||||||
return fmt.Errorf("too many arguments")
|
|
||||||
} else if narg == 1 {
|
|
||||||
switch flag.Arg(0) {
|
|
||||||
case "edit-config":
|
|
||||||
return editConfigFile(configService)
|
|
||||||
case "print-config":
|
|
||||||
return printConfigPath(configService.Path())
|
|
||||||
case "version":
|
|
||||||
return printVersion()
|
|
||||||
case "help":
|
|
||||||
printUsage()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := configService.ReadOrCreateConfig()
|
cfg, err := configService.ReadOrCreateConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("read or create config: %w", err)
|
return fmt.Errorf("read or create config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
headless := os.Getenv("OCTO_HEADLESS") != ""
|
// TODO: improve logger API
|
||||||
logger, err := buildLogger(cfg.LogFile, headless)
|
// Currently it's a bit complicated because we can only use stdout - the
|
||||||
|
// preferred destination - if the client is not running. Otherwise we
|
||||||
|
// fallback to the legacy configuration but this should be bought more
|
||||||
|
// in-line with the client/server split.
|
||||||
|
var w io.Writer
|
||||||
|
if serverCfg.stderrAvailable {
|
||||||
|
w = os.Stdout
|
||||||
|
} else if !cfg.LogFile.Enabled {
|
||||||
|
w = io.Discard
|
||||||
|
} else {
|
||||||
|
w, err = os.OpenFile(cfg.LogFile.GetPath(), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("build logger: %w", err)
|
return fmt.Errorf("error opening log file: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if headless {
|
var handlerOpts slog.HandlerOptions
|
||||||
// When running in headless mode tview doesn't handle SIGINT for us.
|
if os.Getenv("OCTO_DEBUG") != "" {
|
||||||
|
handlerOpts.Level = slog.LevelDebug
|
||||||
|
}
|
||||||
|
logger := slog.New(slog.NewTextHandler(w, &handlerOpts))
|
||||||
|
|
||||||
|
if serverCfg.handleSigInt {
|
||||||
ch := make(chan os.Signal, 1)
|
ch := make(chan os.Signal, 1)
|
||||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
@ -100,17 +177,10 @@ func run() error {
|
|||||||
<-ch
|
<-ch
|
||||||
logger.Info("Received interrupt signal, exiting")
|
logger.Info("Received interrupt signal, exiting")
|
||||||
signal.Stop(ch)
|
signal.Stop(ch)
|
||||||
cancel(errShutdown)
|
cancel(errInterrupt{})
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
var clipboardAvailable bool
|
|
||||||
if err = clipboard.Init(); err != nil {
|
|
||||||
logger.Warn("Clipboard not available", "err", err)
|
|
||||||
} else {
|
|
||||||
clipboardAvailable = true
|
|
||||||
}
|
|
||||||
|
|
||||||
dockerClient, err := dockerclient.NewClientWithOpts(
|
dockerClient, err := dockerclient.NewClientWithOpts(
|
||||||
dockerclient.FromEnv,
|
dockerclient.FromEnv,
|
||||||
dockerclient.WithAPIVersionNegotiation(),
|
dockerclient.WithAPIVersionNegotiation(),
|
||||||
@ -119,102 +189,64 @@ func run() error {
|
|||||||
return fmt.Errorf("new docker client: %w", err)
|
return fmt.Errorf("new docker client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildInfo, ok := debug.ReadBuildInfo()
|
app := server.New(server.Params{
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("read build info: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
app := app.New(app.Params{
|
|
||||||
ConfigService: configService,
|
ConfigService: configService,
|
||||||
DockerClient: dockerClient,
|
DockerClient: dockerClient,
|
||||||
Headless: headless,
|
|
||||||
ClipboardAvailable: clipboardAvailable,
|
|
||||||
ConfigFilePath: configService.Path(),
|
ConfigFilePath: configService.Path(),
|
||||||
BuildInfo: domain.BuildInfo{
|
WaitForClient: serverCfg.waitForClient,
|
||||||
GoVersion: buildInfo.GoVersion,
|
|
||||||
Version: version,
|
|
||||||
Commit: commit,
|
|
||||||
Date: date,
|
|
||||||
},
|
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
})
|
})
|
||||||
|
|
||||||
return app.Run(ctx)
|
logger.Info(
|
||||||
}
|
"Starting server",
|
||||||
|
"version",
|
||||||
|
cmp.Or(version, "devel"),
|
||||||
|
"commit",
|
||||||
|
cmp.Or(commit, "unknown"),
|
||||||
|
"date",
|
||||||
|
cmp.Or(date, "unknown"),
|
||||||
|
"go_version",
|
||||||
|
runtime.Version(),
|
||||||
|
)
|
||||||
|
|
||||||
// editConfigFile opens the config file in the user's editor.
|
if err := app.Run(ctx); err != nil {
|
||||||
func editConfigFile(configService *config.Service) error {
|
if errors.Is(err, context.Canceled) && errors.Is(context.Cause(ctx), errInterrupt{}) {
|
||||||
if _, err := configService.ReadOrCreateConfig(); err != nil {
|
return context.Cause(ctx)
|
||||||
return fmt.Errorf("read or create config: %w", err)
|
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
editor := os.Getenv("EDITOR")
|
|
||||||
if editor == "" {
|
|
||||||
editor = "vi"
|
|
||||||
}
|
|
||||||
binary, err := exec.LookPath(editor)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("look path: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "Editing config file: %s\n", configService.Path())
|
|
||||||
fmt.Println(binary)
|
|
||||||
|
|
||||||
if err := syscall.Exec(binary, []string{"--", configService.Path()}, os.Environ()); err != nil {
|
|
||||||
return fmt.Errorf("exec: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// printConfigPath prints the path to the config file to stderr.
|
func runClientAndServer(c *cli.Context) error {
|
||||||
func printConfigPath(configPath string) error {
|
errNoErr := errors.New("no error")
|
||||||
fmt.Fprintln(os.Stderr, configPath)
|
|
||||||
|
g, ctx := errgroup.WithContext(c.Context)
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
if err := runClient(ctx, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errNoErr
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
if err := runServer(ctx, c, serverConfig{
|
||||||
|
stderrAvailable: false,
|
||||||
|
handleSigInt: false,
|
||||||
|
waitForClient: true,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return errNoErr
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := g.Wait(); err == errNoErr {
|
||||||
return nil
|
return nil
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// printVersion prints the version of the application to stderr.
|
|
||||||
func printVersion() error {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s version %s\n", domain.AppName, cmp.Or(version, "0.0.0-dev"))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func printUsage() {
|
|
||||||
os.Stderr.WriteString("Usage: octoplex [command]\n\n")
|
|
||||||
os.Stderr.WriteString("Commands:\n\n")
|
|
||||||
os.Stderr.WriteString(" edit-config Edit the config file\n")
|
|
||||||
os.Stderr.WriteString(" print-config Print the path to the config file\n")
|
|
||||||
os.Stderr.WriteString(" version Print the version of the application\n")
|
|
||||||
os.Stderr.WriteString(" help Print this help message\n")
|
|
||||||
os.Stderr.WriteString("\n")
|
|
||||||
os.Stderr.WriteString("Additionally, Octoplex can be configured with the following environment variables:\n\n")
|
|
||||||
os.Stderr.WriteString(" OCTO_DEBUG Enables debug logging if set\n")
|
|
||||||
os.Stderr.WriteString(" OCTO_HEADLESS Enables headless mode if set (experimental)\n\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildLogger builds the logger, which may be a no-op logger.
|
|
||||||
func buildLogger(cfg config.LogFile, headless bool) (*slog.Logger, error) {
|
|
||||||
build := func(w io.Writer) *slog.Logger {
|
|
||||||
var handlerOpts slog.HandlerOptions
|
|
||||||
if os.Getenv("OCTO_DEBUG") != "" {
|
|
||||||
handlerOpts.Level = slog.LevelDebug
|
|
||||||
}
|
|
||||||
return slog.New(slog.NewTextHandler(w, &handlerOpts))
|
|
||||||
}
|
|
||||||
|
|
||||||
// In headless mode, always log to stderr.
|
|
||||||
if headless {
|
|
||||||
return build(os.Stderr), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cfg.Enabled {
|
|
||||||
return slog.New(slog.DiscardHandler), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fptr, err := os.OpenFile(cfg.GetPath(), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error opening log file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return build(fptr), nil
|
|
||||||
}
|
}
|
||||||
|
@ -40,3 +40,9 @@ description = "Generate mocks"
|
|||||||
dir = "{{cwd}}"
|
dir = "{{cwd}}"
|
||||||
run = "go tool mockery"
|
run = "go tool mockery"
|
||||||
alias = "m"
|
alias = "m"
|
||||||
|
|
||||||
|
[tasks.generate_proto]
|
||||||
|
description = "Generate gRPC files from proto"
|
||||||
|
dir = "{{cwd}}"
|
||||||
|
run = "protoc -I proto --go_out=paths=source_relative:internal/generated/grpc --go-grpc_out=paths=source_relative:internal/generated/grpc proto/api.proto proto/domain.proto proto/event.proto proto/command.proto"
|
||||||
|
alias = "p"
|
||||||
|
18
proto/api.proto
Normal file
18
proto/api.proto
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option go_package = "git.netflux.io/rob/octoplex/internal/generated/grpc";
|
||||||
|
package api;
|
||||||
|
|
||||||
|
import "event.proto";
|
||||||
|
import "command.proto";
|
||||||
|
|
||||||
|
service InternalAPI {
|
||||||
|
rpc Communicate(stream Envelope) returns (stream Envelope);
|
||||||
|
}
|
||||||
|
|
||||||
|
message Envelope {
|
||||||
|
oneof payload {
|
||||||
|
Command command = 1;
|
||||||
|
Event event = 2;
|
||||||
|
}
|
||||||
|
}
|
33
proto/command.proto
Normal file
33
proto/command.proto
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
package api;
|
||||||
|
|
||||||
|
option go_package = "git.netflux.io/rob/octoplex/internal/generated/grpc";
|
||||||
|
|
||||||
|
message Command {
|
||||||
|
oneof command_type {
|
||||||
|
AddDestinationCommand add_destination = 1;
|
||||||
|
RemoveDestinationCommand remove_destination = 2;
|
||||||
|
StartDestinationCommand start_destination = 3;
|
||||||
|
StopDestinationCommand stop_destination = 4;
|
||||||
|
CloseOtherInstancesCommand close_other_instances = 5;
|
||||||
|
KillServerCommand kill_server = 6;
|
||||||
|
StartHandshakeCommand start_handshake = 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message AddDestinationCommand {
|
||||||
|
string name = 1;
|
||||||
|
string url = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveDestinationCommand { string url = 1; }
|
||||||
|
|
||||||
|
message StartDestinationCommand { string url = 1; }
|
||||||
|
|
||||||
|
message StopDestinationCommand { string url = 1; }
|
||||||
|
|
||||||
|
message CloseOtherInstancesCommand {}
|
||||||
|
|
||||||
|
message KillServerCommand {}
|
||||||
|
|
||||||
|
message StartHandshakeCommand {};
|
58
proto/domain.proto
Normal file
58
proto/domain.proto
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package api;
|
||||||
|
option go_package = "git.netflux.io/rob/octoplex/internal/generated/grpc";
|
||||||
|
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
|
message Container {
|
||||||
|
string id = 1;
|
||||||
|
string status = 2;
|
||||||
|
string health_state = 3;
|
||||||
|
double cpu_percent = 4;
|
||||||
|
uint64 memory_usage_bytes = 5;
|
||||||
|
int32 rx_rate = 6;
|
||||||
|
int32 tx_rate = 7;
|
||||||
|
google.protobuf.Timestamp rx_since = 8;
|
||||||
|
string image_name = 9;
|
||||||
|
string pull_status = 10;
|
||||||
|
string pull_progress = 11;
|
||||||
|
int32 pull_percent = 12;
|
||||||
|
int32 restart_count = 13;
|
||||||
|
optional int32 exit_code = 14;
|
||||||
|
string err = 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Source {
|
||||||
|
Container container = 1;
|
||||||
|
bool live = 2;
|
||||||
|
google.protobuf.Timestamp live_changed_at = 3;
|
||||||
|
repeated string tracks = 4;
|
||||||
|
string exit_reason = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Destination {
|
||||||
|
enum Status {
|
||||||
|
STATUS_OFF_AIR = 0;
|
||||||
|
STATUS_STARTING = 1;
|
||||||
|
STATUS_LIVE = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
Container container = 1;
|
||||||
|
Status status = 2;
|
||||||
|
string name = 3;
|
||||||
|
string url = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BuildInfo {
|
||||||
|
string go_version = 1;
|
||||||
|
string version = 2;
|
||||||
|
string commit = 3;
|
||||||
|
string date = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AppState {
|
||||||
|
Source source = 1;
|
||||||
|
repeated Destination destinations = 2;
|
||||||
|
BuildInfo build_info = 3;
|
||||||
|
}
|
59
proto/event.proto
Normal file
59
proto/event.proto
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package api;
|
||||||
|
option go_package = "git.netflux.io/rob/octoplex/internal/generated/grpc";
|
||||||
|
|
||||||
|
import "domain.proto";
|
||||||
|
|
||||||
|
message Event {
|
||||||
|
oneof event_type {
|
||||||
|
AppStateChangedEvent app_state_changed = 1;
|
||||||
|
DestinationStreamExitedEvent destination_stream_exited = 2;
|
||||||
|
DestinationAddedEvent destination_added = 3;
|
||||||
|
AddDestinationFailedEvent add_destination_failed = 4;
|
||||||
|
DestinationRemovedEvent destination_removed = 5;
|
||||||
|
RemoveDestinationFailedEvent remove_destination_failed = 6;
|
||||||
|
StartDestinationFailedEvent start_destination_failed = 7;
|
||||||
|
MediaServerStartedEvent media_server_started = 8;
|
||||||
|
OtherInstanceDetectedEvent other_instance_detected = 9;
|
||||||
|
FatalErrorEvent fatal_error = 10;
|
||||||
|
HandshakeCompletedEvent handshake_completed = 11;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message AppStateChangedEvent { AppState app_state = 1; }
|
||||||
|
|
||||||
|
message DestinationStreamExitedEvent {
|
||||||
|
string name = 1;
|
||||||
|
string error = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DestinationAddedEvent { string url = 1; }
|
||||||
|
|
||||||
|
message AddDestinationFailedEvent {
|
||||||
|
string url = 1;
|
||||||
|
string error = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DestinationRemovedEvent { string url = 1; }
|
||||||
|
|
||||||
|
message RemoveDestinationFailedEvent {
|
||||||
|
string url = 1;
|
||||||
|
string error = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StartDestinationFailedEvent {
|
||||||
|
string url = 1;
|
||||||
|
string message = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message MediaServerStartedEvent {
|
||||||
|
string rtmp_url = 1;
|
||||||
|
string rtmps_url = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OtherInstanceDetectedEvent {}
|
||||||
|
|
||||||
|
message FatalErrorEvent { string message = 1; }
|
||||||
|
|
||||||
|
message HandshakeCompletedEvent {}
|
Loading…
x
Reference in New Issue
Block a user