Respect domain in cluster DB, allow metadata in process config for cluster

This commit is contained in:
Ingo Oppermann 2023-06-05 21:19:11 +02:00
parent 1689f3f7db
commit 8829b8fff0
No known key found for this signature in database
GPG Key ID: 2AB32426E9DD229E
55 changed files with 2061 additions and 1111 deletions

View File

@ -1493,7 +1493,7 @@ func probeInput(binary string, config app.Config) app.Probe {
rs.AddProcess(&config)
id := restream.TaskID{ID: config.ID}
id := config.ProcessID()
probe := rs.Probe(id)
rs.DeleteProcess(id)

View File

@ -13,6 +13,7 @@ import (
mwlog "github.com/datarhei/core/v16/http/middleware/log"
"github.com/datarhei/core/v16/http/validator"
"github.com/datarhei/core/v16/log"
"github.com/datarhei/core/v16/restream/app"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
@ -161,6 +162,7 @@ func NewAPI(config APIConfig) (API, error) {
a.router.DELETE("/v1/process/:id", func(c echo.Context) error {
id := util.PathParam(c, "id")
domain := util.DefaultQuery(c, "domain", "")
origin := c.Request().Header.Get("X-Cluster-Origin")
@ -168,11 +170,13 @@ func NewAPI(config APIConfig) (API, error) {
return httpapi.Err(http.StatusLoopDetected, "", "breaking circuit")
}
a.logger.Debug().WithField("id", id).Log("Remove process request")
pid := app.ProcessID{ID: id, Domain: domain}
err := a.cluster.RemoveProcess(origin, id)
a.logger.Debug().WithField("id", pid).Log("Remove process request")
err := a.cluster.RemoveProcess(origin, pid)
if err != nil {
a.logger.Debug().WithError(err).WithField("id", id).Log("Unable to remove process")
a.logger.Debug().WithError(err).WithField("id", pid).Log("Unable to remove process")
return httpapi.Err(http.StatusInternalServerError, "unable to remove process", "%s", err)
}
@ -181,6 +185,7 @@ func NewAPI(config APIConfig) (API, error) {
a.router.PUT("/v1/process/:id", func(c echo.Context) error {
id := util.PathParam(c, "id")
domain := util.DefaultQuery(c, "domain", "")
r := client.UpdateProcessRequest{}
@ -194,13 +199,15 @@ func NewAPI(config APIConfig) (API, error) {
return httpapi.Err(http.StatusLoopDetected, "", "breaking circuit")
}
if id != r.ID {
pid := app.ProcessID{ID: id, Domain: domain}
if !pid.Equals(r.ID) {
return httpapi.Err(http.StatusBadRequest, "Invalid data", "the ID in the path and the request do not match")
}
a.logger.Debug().WithFields(log.Fields{
"old_id": r.ID,
"new_id": r.Config.ID,
"new_id": r.Config.ProcessID(),
}).Log("Update process request")
err := a.cluster.UpdateProcess(origin, r.ID, &r.Config)
@ -215,6 +222,7 @@ func NewAPI(config APIConfig) (API, error) {
a.router.PUT("/v1/process/:id/metadata/:key", func(c echo.Context) error {
id := util.PathParam(c, "id")
key := util.PathParam(c, "key")
domain := util.DefaultQuery(c, "domain", "")
r := client.SetProcessMetadataRequest{}
@ -228,7 +236,9 @@ func NewAPI(config APIConfig) (API, error) {
return httpapi.Err(http.StatusLoopDetected, "", "breaking circuit")
}
err := a.cluster.SetProcessMetadata(origin, id, key, r.Metadata)
pid := app.ProcessID{ID: id, Domain: domain}
err := a.cluster.SetProcessMetadata(origin, pid, key, r.Metadata)
if err != nil {
a.logger.Debug().WithError(err).WithField("id", r.ID).Log("Unable to update metadata")
return httpapi.Err(http.StatusInternalServerError, "unable to update metadata", "%s", err)

View File

@ -28,14 +28,14 @@ type AddProcessRequest struct {
}
type UpdateProcessRequest struct {
ID string `json:"id"`
Config app.Config `json:"config"`
ID app.ProcessID `json:"id"`
Config app.Config `json:"config"`
}
type SetProcessMetadataRequest struct {
ID string `json:"id"`
Key string `json:"key"`
Metadata interface{} `json:"metadata"`
ID app.ProcessID `json:"id"`
Key string `json:"key"`
Metadata interface{} `json:"metadata"`
}
type AddIdentityRequest struct {
@ -100,8 +100,8 @@ func (c *APIClient) AddProcess(origin string, r AddProcessRequest) error {
return err
}
func (c *APIClient) RemoveProcess(origin string, id string) error {
_, err := c.call(http.MethodDelete, "/process/"+id, "application/json", nil, origin)
func (c *APIClient) RemoveProcess(origin string, id app.ProcessID) error {
_, err := c.call(http.MethodDelete, "/process/"+id.ID+"?domain="+id.Domain, "application/json", nil, origin)
return err
}
@ -112,7 +112,7 @@ func (c *APIClient) UpdateProcess(origin string, r UpdateProcessRequest) error {
return err
}
_, err = c.call(http.MethodPut, "/process/"+r.ID, "application/json", bytes.NewReader(data), origin)
_, err = c.call(http.MethodPut, "/process/"+r.ID.ID+"?domain="+r.ID.Domain, "application/json", bytes.NewReader(data), origin)
return err
}
@ -123,7 +123,7 @@ func (c *APIClient) SetProcessMetadata(origin string, r SetProcessMetadataReques
return err
}
_, err = c.call(http.MethodPut, "/process/"+r.ID+"/metadata/"+r.Key, "application/json", bytes.NewReader(data), origin)
_, err = c.call(http.MethodPut, "/process/"+r.ID.ID+"/metadata/"+r.Key+"?domain="+r.ID.Domain, "application/json", bytes.NewReader(data), origin)
return err
}

View File

@ -66,11 +66,11 @@ type Cluster interface {
Shutdown() error
ListProcesses() []store.Process
GetProcess(id string) (store.Process, error)
GetProcess(id app.ProcessID) (store.Process, error)
AddProcess(origin string, config *app.Config) error
RemoveProcess(origin, id string) error
UpdateProcess(origin, id string, config *app.Config) error
SetProcessMetadata(origin, id, key string, data interface{}) error
RemoveProcess(origin string, id app.ProcessID) error
UpdateProcess(origin string, id app.ProcessID, config *app.Config) error
SetProcessMetadata(origin string, id app.ProcessID, key string, data interface{}) error
IAM(superuser iamidentity.User, jwtRealm, jwtSecret string) (iam.IAM, error)
ListIdentities() (time.Time, []iamidentity.User)
@ -707,7 +707,7 @@ func (c *cluster) ListProcesses() []store.Process {
return c.store.ProcessList()
}
func (c *cluster) GetProcess(id string) (store.Process, error) {
func (c *cluster) GetProcess(id app.ProcessID) (store.Process, error) {
return c.store.GetProcess(id)
}
@ -726,7 +726,7 @@ func (c *cluster) AddProcess(origin string, config *app.Config) error {
return c.applyCommand(cmd)
}
func (c *cluster) RemoveProcess(origin, id string) error {
func (c *cluster) RemoveProcess(origin string, id app.ProcessID) error {
if !c.IsRaftLeader() {
return c.forwarder.RemoveProcess(origin, id)
}
@ -741,7 +741,7 @@ func (c *cluster) RemoveProcess(origin, id string) error {
return c.applyCommand(cmd)
}
func (c *cluster) UpdateProcess(origin, id string, config *app.Config) error {
func (c *cluster) UpdateProcess(origin string, id app.ProcessID, config *app.Config) error {
if !c.IsRaftLeader() {
return c.forwarder.UpdateProcess(origin, id, config)
}
@ -757,7 +757,7 @@ func (c *cluster) UpdateProcess(origin, id string, config *app.Config) error {
return c.applyCommand(cmd)
}
func (c *cluster) SetProcessMetadata(origin, id, key string, data interface{}) error {
func (c *cluster) SetProcessMetadata(origin string, id app.ProcessID, key string, data interface{}) error {
if !c.IsRaftLeader() {
return c.forwarder.SetProcessMetadata(origin, id, key, data)
}

View File

@ -23,9 +23,9 @@ type Forwarder interface {
Snapshot() (io.ReadCloser, error)
AddProcess(origin string, config *app.Config) error
UpdateProcess(origin, id string, config *app.Config) error
SetProcessMetadata(origin, id, key string, data interface{}) error
RemoveProcess(origin, id string) error
UpdateProcess(origin string, id app.ProcessID, config *app.Config) error
SetProcessMetadata(origin string, id app.ProcessID, key string, data interface{}) error
RemoveProcess(origin string, id app.ProcessID) error
AddIdentity(origin string, identity iamidentity.User) error
UpdateIdentity(origin, name string, identity iamidentity.User) error
@ -155,7 +155,7 @@ func (f *forwarder) AddProcess(origin string, config *app.Config) error {
return client.AddProcess(origin, r)
}
func (f *forwarder) UpdateProcess(origin, id string, config *app.Config) error {
func (f *forwarder) UpdateProcess(origin string, id app.ProcessID, config *app.Config) error {
if origin == "" {
origin = f.id
}
@ -172,7 +172,7 @@ func (f *forwarder) UpdateProcess(origin, id string, config *app.Config) error {
return client.UpdateProcess(origin, r)
}
func (f *forwarder) SetProcessMetadata(origin, id, key string, data interface{}) error {
func (f *forwarder) SetProcessMetadata(origin string, id app.ProcessID, key string, data interface{}) error {
if origin == "" {
origin = f.id
}
@ -190,7 +190,7 @@ func (f *forwarder) SetProcessMetadata(origin, id, key string, data interface{})
return client.SetProcessMetadata(origin, r)
}
func (f *forwarder) RemoveProcess(origin, id string) error {
func (f *forwarder) RemoveProcess(origin string, id app.ProcessID) error {
if origin == "" {
origin = f.id
}

View File

@ -289,6 +289,8 @@ WAIT:
func (c *cluster) establishLeadership(ctx context.Context) error {
c.logger.Debug().Log("Establishing leadership")
// creating a map of which process runs where
ctx, cancel := context.WithCancel(ctx)
c.cancelLeaderShip = cancel
@ -324,7 +326,7 @@ var errNoLimitsDefined = errors.New("no process limits are defined")
type processOpDelete struct {
nodeid string
processid string
processid app.ProcessID
}
type processOpMove struct {
@ -336,7 +338,7 @@ type processOpMove struct {
type processOpStart struct {
nodeid string
processid string
processid app.ProcessID
}
type processOpAdd struct {
@ -347,19 +349,19 @@ type processOpAdd struct {
type processOpUpdate struct {
nodeid string
processid string
processid app.ProcessID
config *app.Config
metadata map[string]interface{}
}
type processOpReject struct {
processid string
processid app.ProcessID
err error
}
type processOpSkip struct {
nodeid string
processid string
processid app.ProcessID
err error
}
@ -370,12 +372,12 @@ func (c *cluster) applyOpStack(stack []interface{}) {
err := c.proxy.ProcessAdd(v.nodeid, v.config, v.metadata)
if err != nil {
c.logger.Info().WithError(err).WithFields(log.Fields{
"processid": v.config.ID,
"processid": v.config.ProcessID(),
"nodeid": v.nodeid,
}).Log("Adding process")
break
}
err = c.proxy.ProcessStart(v.nodeid, v.config.ID)
err = c.proxy.ProcessStart(v.nodeid, v.config.ProcessID())
if err != nil {
c.logger.Info().WithError(err).WithFields(log.Fields{
"processid": v.config.ID,
@ -423,7 +425,7 @@ func (c *cluster) applyOpStack(stack []interface{}) {
}).Log("Moving process, adding process")
break
}
err = c.proxy.ProcessDelete(v.fromNodeid, v.config.ID)
err = c.proxy.ProcessDelete(v.fromNodeid, v.config.ProcessID())
if err != nil {
c.logger.Info().WithError(err).WithFields(log.Fields{
"processid": v.config.ID,
@ -432,7 +434,7 @@ func (c *cluster) applyOpStack(stack []interface{}) {
}).Log("Moving process, removing process")
break
}
err = c.proxy.ProcessStart(v.toNodeid, v.config.ID)
err = c.proxy.ProcessStart(v.toNodeid, v.config.ProcessID())
if err != nil {
c.logger.Info().WithError(err).WithFields(log.Fields{
"processid": v.config.ID,
@ -509,7 +511,8 @@ func synchronize(want []store.Process, have []proxy.Process, resources map[strin
// we want to be running on the nodes.
wantMap := map[string]store.Process{}
for _, process := range want {
wantMap[process.Config.ID] = process
pid := process.Config.ProcessID().String()
wantMap[pid] = process
}
opStack := []interface{}{}
@ -520,10 +523,11 @@ func synchronize(want []store.Process, have []proxy.Process, resources map[strin
haveAfterRemove := []proxy.Process{}
for _, p := range have {
if wantP, ok := wantMap[p.Config.ID]; !ok {
pid := p.Config.ProcessID().String()
if wantP, ok := wantMap[pid]; !ok {
opStack = append(opStack, processOpDelete{
nodeid: p.NodeID,
processid: p.Config.ID,
processid: p.Config.ProcessID(),
})
// Adjust the resources
@ -539,19 +543,19 @@ func synchronize(want []store.Process, have []proxy.Process, resources map[strin
if wantP.UpdatedAt.After(p.UpdatedAt) {
opStack = append(opStack, processOpUpdate{
nodeid: p.NodeID,
processid: p.Config.ID,
processid: p.Config.ProcessID(),
config: wantP.Config,
metadata: wantP.Metadata,
})
}
}
delete(wantMap, p.Config.ID)
delete(wantMap, pid)
if p.Order != "start" {
opStack = append(opStack, processOpStart{
nodeid: p.NodeID,
processid: p.Config.ID,
processid: p.Config.ProcessID(),
})
}
@ -568,7 +572,7 @@ func synchronize(want []store.Process, have []proxy.Process, resources map[strin
// If a process doesn't have any limits defined, reject that process
if process.Config.LimitCPU <= 0 || process.Config.LimitMemory <= 0 {
opStack = append(opStack, processOpReject{
processid: process.Config.ID,
processid: process.Config.ProcessID(),
err: errNoLimitsDefined,
})
@ -631,7 +635,7 @@ func synchronize(want []store.Process, have []proxy.Process, resources map[strin
}
} else {
opStack = append(opStack, processOpReject{
processid: process.Config.ID,
processid: process.Config.ProcessID(),
err: errNotEnoughResources,
})
}
@ -780,7 +784,7 @@ func rebalance(have []proxy.Process, resources map[string]proxy.NodeResources) [
// There's no other node with enough resources to take over this process
opStack = append(opStack, processOpSkip{
nodeid: overloadedNodeid,
processid: p.Config.ID,
processid: p.Config.ProcessID(),
err: errNotEnoughResourcesForRebalancing,
})
continue

View File

@ -242,7 +242,7 @@ func TestSynchronizeAddNoResourcesCPU(t *testing.T) {
require.Equal(t, []interface{}{
processOpReject{
processid: "foobar",
processid: app.ProcessID{ID: "foobar"},
err: errNotEnoughResources,
},
}, stack)
@ -283,7 +283,7 @@ func TestSynchronizeAddNoResourcesMemory(t *testing.T) {
require.Equal(t, []interface{}{
processOpReject{
processid: "foobar",
processid: app.ProcessID{ID: "foobar"},
err: errNotEnoughResources,
},
}, stack)
@ -322,7 +322,7 @@ func TestSynchronizeAddNoLimits(t *testing.T) {
require.Equal(t, []interface{}{
processOpReject{
processid: "foobar",
processid: app.ProcessID{ID: "foobar"},
err: errNoLimitsDefined,
},
}, stack)
@ -367,7 +367,7 @@ func TestSynchronizeRemove(t *testing.T) {
require.Equal(t, []interface{}{
processOpDelete{
nodeid: "node2",
processid: "foobar",
processid: app.ProcessID{ID: "foobar"},
},
}, stack)
@ -437,7 +437,7 @@ func TestSynchronizeAddRemove(t *testing.T) {
require.Equal(t, []interface{}{
processOpDelete{
nodeid: "node2",
processid: "foobar2",
processid: app.ProcessID{ID: "foobar2"},
},
processOpAdd{
nodeid: "node1",
@ -662,17 +662,17 @@ func TestRebalanceSkip(t *testing.T) {
require.ElementsMatch(t, []interface{}{
processOpSkip{
nodeid: "node1",
processid: "foobar3",
processid: app.ProcessID{ID: "foobar3"},
err: errNotEnoughResourcesForRebalancing,
},
processOpSkip{
nodeid: "node1",
processid: "foobar1",
processid: app.ProcessID{ID: "foobar1"},
err: errNotEnoughResourcesForRebalancing,
},
processOpSkip{
nodeid: "node2",
processid: "foobar2",
processid: app.ProcessID{ID: "foobar2"},
err: errNotEnoughResourcesForRebalancing,
},
}, opStack)

View File

@ -11,9 +11,10 @@ import (
"sync"
"time"
"github.com/datarhei/core/v16/restream/app"
client "github.com/datarhei/core-client-go/v16"
clientapi "github.com/datarhei/core-client-go/v16/api"
"github.com/datarhei/core/v16/restream/app"
)
type Node interface {
@ -27,10 +28,10 @@ type Node interface {
GetFile(prefix, path string) (io.ReadCloser, error)
ProcessAdd(config *app.Config, metadata map[string]interface{}) error
ProcessStart(id string) error
ProcessStop(id string) error
ProcessDelete(id string) error
ProcessUpdate(id string, config *app.Config, metadata map[string]interface{}) error
ProcessStart(id app.ProcessID) error
ProcessStop(id app.ProcessID) error
ProcessDelete(id app.ProcessID) error
ProcessUpdate(id app.ProcessID, config *app.Config, metadata map[string]interface{}) error
NodeReader
}
@ -51,11 +52,12 @@ type NodeFiles struct {
}
type NodeResources struct {
NCPU float64 // Number of CPU on this node
CPU float64 // Current CPU load, 0-100*ncpu
CPULimit float64 // Defined CPU load limit, 0-100*ncpu
Mem uint64 // Currently used memory in bytes
MemLimit uint64 // Defined memory limit in bytes
IsThrottling bool // Whether this core is currently throttling
NCPU float64 // Number of CPU on this node
CPU float64 // Current CPU load, 0-100*ncpu
CPULimit float64 // Defined CPU load limit, 0-100*ncpu
Mem uint64 // Currently used memory in bytes
MemLimit uint64 // Defined memory limit in bytes
}
type NodeAbout struct {
@ -719,6 +721,8 @@ func (n *node) ProcessList() ([]Process, error) {
cfg := &app.Config{
ID: p.Config.ID,
Owner: p.Config.Owner,
Domain: p.Config.Domain,
Reference: p.Config.Reference,
Input: []app.ConfigIO{},
Output: []app.ConfigIO{},
@ -727,6 +731,9 @@ func (n *node) ProcessList() ([]Process, error) {
ReconnectDelay: p.Config.ReconnectDelay,
Autostart: p.Config.Autostart,
StaleTimeout: p.Config.StaleTimeout,
Timeout: p.Config.Timeout,
Scheduler: p.Config.Scheduler,
LogPatterns: p.Config.LogPatterns,
LimitCPU: p.Config.Limits.CPU,
LimitMemory: p.Config.Limits.Memory * 1024 * 1024,
LimitWaitFor: p.Config.Limits.WaitFor,
@ -784,6 +791,8 @@ func (n *node) ProcessAdd(config *app.Config, metadata map[string]interface{}) e
func convertConfig(config *app.Config, metadata map[string]interface{}) clientapi.ProcessConfig {
cfg := clientapi.ProcessConfig{
ID: config.ID,
Owner: config.Owner,
Domain: config.Domain,
Type: "ffmpeg",
Reference: config.Reference,
Input: []clientapi.ProcessConfigIO{},
@ -793,6 +802,9 @@ func convertConfig(config *app.Config, metadata map[string]interface{}) clientap
ReconnectDelay: config.ReconnectDelay,
Autostart: config.Autostart,
StaleTimeout: config.StaleTimeout,
Timeout: config.Timeout,
Scheduler: config.Scheduler,
LogPatterns: config.LogPatterns,
Limits: clientapi.ProcessConfigLimits{
CPU: config.LimitCPU,
Memory: config.LimitMemory / 1024 / 1024,
@ -832,7 +844,7 @@ func convertConfig(config *app.Config, metadata map[string]interface{}) clientap
return cfg
}
func (n *node) ProcessStart(id string) error {
func (n *node) ProcessStart(id app.ProcessID) error {
n.peerLock.RLock()
defer n.peerLock.RUnlock()
@ -840,10 +852,10 @@ func (n *node) ProcessStart(id string) error {
return fmt.Errorf("not connected")
}
return n.peer.ProcessCommand(id, "start")
return n.peer.ProcessCommand(client.NewProcessID(id.ID, id.Domain), "start")
}
func (n *node) ProcessStop(id string) error {
func (n *node) ProcessStop(id app.ProcessID) error {
n.peerLock.RLock()
defer n.peerLock.RUnlock()
@ -851,10 +863,10 @@ func (n *node) ProcessStop(id string) error {
return fmt.Errorf("not connected")
}
return n.peer.ProcessCommand(id, "stop")
return n.peer.ProcessCommand(client.NewProcessID(id.ID, id.Domain), "stop")
}
func (n *node) ProcessDelete(id string) error {
func (n *node) ProcessDelete(id app.ProcessID) error {
n.peerLock.RLock()
defer n.peerLock.RUnlock()
@ -862,10 +874,10 @@ func (n *node) ProcessDelete(id string) error {
return fmt.Errorf("not connected")
}
return n.peer.ProcessDelete(id)
return n.peer.ProcessDelete(client.NewProcessID(id.ID, id.Domain))
}
func (n *node) ProcessUpdate(id string, config *app.Config, metadata map[string]interface{}) error {
func (n *node) ProcessUpdate(id app.ProcessID, config *app.Config, metadata map[string]interface{}) error {
n.peerLock.RLock()
defer n.peerLock.RUnlock()
@ -875,5 +887,5 @@ func (n *node) ProcessUpdate(id string, config *app.Config, metadata map[string]
cfg := convertConfig(config, metadata)
return n.peer.ProcessUpdate(id, cfg)
return n.peer.ProcessUpdate(client.NewProcessID(id.ID, id.Domain), cfg)
}

View File

@ -25,9 +25,9 @@ type Proxy interface {
Reader() ProxyReader
ProcessAdd(nodeid string, config *app.Config, metadata map[string]interface{}) error
ProcessDelete(nodeid string, id string) error
ProcessStart(nodeid string, id string) error
ProcessUpdate(nodeid string, id string, config *app.Config, metadata map[string]interface{}) error
ProcessDelete(nodeid string, id app.ProcessID) error
ProcessStart(nodeid string, id app.ProcessID) error
ProcessUpdate(nodeid string, id app.ProcessID, config *app.Config, metadata map[string]interface{}) error
}
type ProxyReader interface {
@ -495,7 +495,7 @@ func (p *proxy) ProcessAdd(nodeid string, config *app.Config, metadata map[strin
return err
}
err = node.ProcessStart(config.ID)
err = node.ProcessStart(config.ProcessID())
if err != nil {
return err
}
@ -503,7 +503,7 @@ func (p *proxy) ProcessAdd(nodeid string, config *app.Config, metadata map[strin
return nil
}
func (p *proxy) ProcessDelete(nodeid string, id string) error {
func (p *proxy) ProcessDelete(nodeid string, id app.ProcessID) error {
p.lock.RLock()
defer p.lock.RUnlock()
@ -520,7 +520,7 @@ func (p *proxy) ProcessDelete(nodeid string, id string) error {
return nil
}
func (p *proxy) ProcessStart(nodeid string, id string) error {
func (p *proxy) ProcessStart(nodeid string, id app.ProcessID) error {
p.lock.RLock()
defer p.lock.RUnlock()
@ -537,7 +537,7 @@ func (p *proxy) ProcessStart(nodeid string, id string) error {
return nil
}
func (p *proxy) ProcessUpdate(nodeid string, id string, config *app.Config, metadata map[string]interface{}) error {
func (p *proxy) ProcessUpdate(nodeid string, id app.ProcessID, config *app.Config, metadata map[string]interface{}) error {
p.lock.RLock()
defer p.lock.RUnlock()

View File

@ -22,7 +22,7 @@ type Store interface {
OnApply(func(op Operation))
ProcessList() []Process
GetProcess(id string) (Process, error)
GetProcess(id app.ProcessID) (Process, error)
UserList() Users
GetUser(name string) Users
@ -80,16 +80,16 @@ type CommandAddProcess struct {
}
type CommandUpdateProcess struct {
ID string
ID app.ProcessID
Config *app.Config
}
type CommandRemoveProcess struct {
ID string
ID app.ProcessID
}
type CommandSetProcessMetadata struct {
ID string
ID app.ProcessID
Key string
Data interface{}
}
@ -242,13 +242,15 @@ func (s *store) addProcess(cmd CommandAddProcess) error {
s.lock.Lock()
defer s.lock.Unlock()
_, ok := s.Process[cmd.Config.ID]
id := cmd.Config.ProcessID().String()
_, ok := s.Process[id]
if ok {
return NewStoreError("the process with the ID '%s' already exists", cmd.Config.ID)
return NewStoreError("the process with the ID '%s' already exists", id)
}
now := time.Now()
s.Process[cmd.Config.ID] = Process{
s.Process[id] = Process{
CreatedAt: now,
UpdatedAt: now,
Config: cmd.Config,
@ -262,7 +264,14 @@ func (s *store) removeProcess(cmd CommandRemoveProcess) error {
s.lock.Lock()
defer s.lock.Unlock()
delete(s.Process, cmd.ID)
id := cmd.ID.String()
_, ok := s.Process[id]
if !ok {
return NewStoreError("the process with the ID '%s' doesn't exist", id)
}
delete(s.Process, id)
return nil
}
@ -271,9 +280,12 @@ func (s *store) updateProcess(cmd CommandUpdateProcess) error {
s.lock.Lock()
defer s.lock.Unlock()
p, ok := s.Process[cmd.ID]
srcid := cmd.ID.String()
dstid := cmd.Config.ProcessID().String()
p, ok := s.Process[srcid]
if !ok {
return NewStoreError("the process with the ID '%s' doesn't exists", cmd.ID)
return NewStoreError("the process with the ID '%s' doesn't exists", srcid)
}
currentHash := p.Config.Hash()
@ -283,19 +295,19 @@ func (s *store) updateProcess(cmd CommandUpdateProcess) error {
return nil
}
if cmd.ID == cmd.Config.ID {
s.Process[cmd.ID] = Process{
if srcid == dstid {
s.Process[srcid] = Process{
UpdatedAt: time.Now(),
Config: cmd.Config,
}
} else {
_, ok := s.Process[cmd.Config.ID]
_, ok := s.Process[dstid]
if ok {
return NewStoreError("the process with the ID '%s' already exists", cmd.Config.ID)
return NewStoreError("the process with the ID '%s' already exists", dstid)
}
delete(s.Process, cmd.ID)
s.Process[cmd.Config.ID] = Process{
delete(s.Process, srcid)
s.Process[dstid] = Process{
UpdatedAt: time.Now(),
Config: cmd.Config,
}
@ -308,7 +320,9 @@ func (s *store) setProcessMetadata(cmd CommandSetProcessMetadata) error {
s.lock.Lock()
defer s.lock.Unlock()
p, ok := s.Process[cmd.ID]
id := cmd.ID.String()
p, ok := s.Process[id]
if !ok {
return NewStoreError("the process with the ID '%s' doesn't exists", cmd.ID)
}
@ -324,7 +338,7 @@ func (s *store) setProcessMetadata(cmd CommandSetProcessMetadata) error {
}
p.UpdatedAt = time.Now()
s.Process[cmd.ID] = p
s.Process[id] = p
return nil
}
@ -456,11 +470,11 @@ func (s *store) ProcessList() []Process {
return processes
}
func (s *store) GetProcess(id string) (Process, error) {
func (s *store) GetProcess(id app.ProcessID) (Process, error) {
s.lock.RLock()
defer s.lock.RUnlock()
process, ok := s.Process[id]
process, ok := s.Process[id.String()]
if !ok {
return Process{}, fmt.Errorf("not found")
}

View File

@ -1071,6 +1071,77 @@ const docTemplate = `{
}
}
},
"/api/v3/cluster/process/{id}/metadata/{key}": {
"put": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Add arbitrary JSON metadata under the given key. If the key exists, all already stored metadata with this key will be overwritten. If the key doesn't exist, it will be created.",
"produces": [
"application/json"
],
"tags": [
"v16.?.?"
],
"summary": "Add JSON metadata with a process under the given key",
"operationId": "cluster-3-set-process-metadata",
"parameters": [
{
"type": "string",
"description": "Process ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Key for data store",
"name": "key",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Domain to act on",
"name": "domain",
"in": "query"
},
{
"description": "Arbitrary JSON data. The null value will remove the key and its contents",
"name": "data",
"in": "body",
"required": true,
"schema": {}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.Error"
}
},
"403": {
"description": "Forbidden",
"schema": {
"$ref": "#/definitions/api.Error"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/api.Error"
}
}
}
}
},
"/api/v3/config": {
"get": {
"security": [
@ -3773,6 +3844,9 @@ const docTemplate = `{
"cpu": {
"type": "number"
},
"domain": {
"type": "string"
},
"id": {
"type": "string"
},
@ -3785,6 +3859,9 @@ const docTemplate = `{
"order": {
"type": "string"
},
"owner": {
"type": "string"
},
"reference": {
"type": "string"
},
@ -4982,10 +5059,16 @@ const docTemplate = `{
"type": "integer",
"format": "int64"
},
"domain": {
"type": "string"
},
"id": {
"type": "string"
},
"metadata": {},
"owner": {
"type": "string"
},
"reference": {
"type": "string"
},
@ -5035,6 +5118,10 @@ const docTemplate = `{
"type": "string"
}
},
"metadata": {
"type": "object",
"additionalProperties": true
},
"options": {
"type": "array",
"items": {

View File

@ -1063,6 +1063,77 @@
}
}
},
"/api/v3/cluster/process/{id}/metadata/{key}": {
"put": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "Add arbitrary JSON metadata under the given key. If the key exists, all already stored metadata with this key will be overwritten. If the key doesn't exist, it will be created.",
"produces": [
"application/json"
],
"tags": [
"v16.?.?"
],
"summary": "Add JSON metadata with a process under the given key",
"operationId": "cluster-3-set-process-metadata",
"parameters": [
{
"type": "string",
"description": "Process ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Key for data store",
"name": "key",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Domain to act on",
"name": "domain",
"in": "query"
},
{
"description": "Arbitrary JSON data. The null value will remove the key and its contents",
"name": "data",
"in": "body",
"required": true,
"schema": {}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/api.Error"
}
},
"403": {
"description": "Forbidden",
"schema": {
"$ref": "#/definitions/api.Error"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/api.Error"
}
}
}
}
},
"/api/v3/config": {
"get": {
"security": [
@ -3765,6 +3836,9 @@
"cpu": {
"type": "number"
},
"domain": {
"type": "string"
},
"id": {
"type": "string"
},
@ -3777,6 +3851,9 @@
"order": {
"type": "string"
},
"owner": {
"type": "string"
},
"reference": {
"type": "string"
},
@ -4974,10 +5051,16 @@
"type": "integer",
"format": "int64"
},
"domain": {
"type": "string"
},
"id": {
"type": "string"
},
"metadata": {},
"owner": {
"type": "string"
},
"reference": {
"type": "string"
},
@ -5027,6 +5110,10 @@
"type": "string"
}
},
"metadata": {
"type": "object",
"additionalProperties": true
},
"options": {
"type": "array",
"items": {

View File

@ -136,6 +136,8 @@ definitions:
properties:
cpu:
type: number
domain:
type: string
id:
type: string
memory_bytes:
@ -144,6 +146,8 @@ definitions:
type: string
order:
type: string
owner:
type: string
reference:
type: string
runtime_seconds:
@ -944,9 +948,13 @@ definitions:
created_at:
format: int64
type: integer
domain:
type: string
id:
type: string
metadata: {}
owner:
type: string
reference:
type: string
report:
@ -977,6 +985,9 @@ definitions:
items:
type: string
type: array
metadata:
additionalProperties: true
type: object
options:
items:
type: string
@ -2985,6 +2996,56 @@ paths:
summary: Replace an existing process
tags:
- v16.?.?
/api/v3/cluster/process/{id}/metadata/{key}:
put:
description: Add arbitrary JSON metadata under the given key. If the key exists,
all already stored metadata with this key will be overwritten. If the key
doesn't exist, it will be created.
operationId: cluster-3-set-process-metadata
parameters:
- description: Process ID
in: path
name: id
required: true
type: string
- description: Key for data store
in: path
name: key
required: true
type: string
- description: Domain to act on
in: query
name: domain
type: string
- description: Arbitrary JSON data. The null value will remove the key and its
contents
in: body
name: data
required: true
schema: {}
produces:
- application/json
responses:
"200":
description: OK
schema: {}
"400":
description: Bad Request
schema:
$ref: '#/definitions/api.Error'
"403":
description: Forbidden
schema:
$ref: '#/definitions/api.Error'
"404":
description: Not Found
schema:
$ref: '#/definitions/api.Error'
security:
- ApiKeyAuth: []
summary: Add JSON metadata with a process under the given key
tags:
- v16.?.?
/api/v3/config:
get:
description: Retrieve the currently active Restreamer configuration

4
go.mod
View File

@ -9,7 +9,7 @@ require (
github.com/atrox/haikunatorgo/v2 v2.0.1
github.com/caddyserver/certmagic v0.17.2
github.com/casbin/casbin/v2 v2.69.1
github.com/datarhei/core-client-go/v16 v16.11.1-0.20230602102832-3d80767a2208
github.com/datarhei/core-client-go/v16 v16.11.1-0.20230605095314-42546fbbbece
github.com/datarhei/gosrt v0.4.1
github.com/datarhei/joy4 v0.0.0-20230505074825-fde05957445a
github.com/fujiwara/shapeio v1.0.0
@ -30,7 +30,7 @@ require (
github.com/prep/average v0.0.0-20200506183628-d26c465f48c3
github.com/prometheus/client_golang v1.15.1
github.com/shirou/gopsutil/v3 v3.23.4
github.com/stretchr/testify v1.8.2
github.com/stretchr/testify v1.8.4
github.com/swaggo/echo-swagger v1.4.0
github.com/swaggo/swag v1.16.1
github.com/vektah/gqlparser/v2 v2.5.1

7
go.sum
View File

@ -47,8 +47,8 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/datarhei/core-client-go/v16 v16.11.1-0.20230602102832-3d80767a2208 h1:voT+m+r0r112S0BIbQDvW9S4BGBv2JXGW/1L5Cmmvq4=
github.com/datarhei/core-client-go/v16 v16.11.1-0.20230602102832-3d80767a2208/go.mod h1:2eAeJtBPTyiI+9uhGcCEHZqATBt9J06Bb7Fbxj07lw4=
github.com/datarhei/core-client-go/v16 v16.11.1-0.20230605095314-42546fbbbece h1:Gv+W986jLcMa/TOKg5YF3RMDlNDDyj7uHuH+mHP7xq8=
github.com/datarhei/core-client-go/v16 v16.11.1-0.20230605095314-42546fbbbece/go.mod h1:6L0zr/NUwvaPsCTK/IL17m8JUEtgLp3BDtlsBREwacg=
github.com/datarhei/gosrt v0.4.1 h1:08km3wKy72jOdC+JzBDWN57H7xST4mz5lFeJQHuWmMs=
github.com/datarhei/gosrt v0.4.1/go.mod h1:FtsulRiUc67Oi3Ii9JH9aQkpO+ZfgeauRAtIE40mIVA=
github.com/datarhei/joy4 v0.0.0-20230505074825-fde05957445a h1:Tf4DSHY1xruBglr+yYP5Wct7czM86GKMYgbXH8a7OFo=
@ -298,8 +298,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/swaggo/echo-swagger v1.4.0 h1:RCxLKySw1SceHLqnmc41pKyiIeE+OiD7NSI7FUOBlLo=
github.com/swaggo/echo-swagger v1.4.0/go.mod h1:Wh3VlwjZGZf/LH0s81tz916JokuPG7y/ZqaqnckYqoQ=
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=

View File

@ -15,11 +15,12 @@ type ClusterNode struct {
}
type ClusterNodeResources struct {
NCPU float64 `json:"ncpu"`
CPU float64 `json:"cpu_used"` // percent 0-100*npcu
CPULimit float64 `json:"cpu_limit"` // percent 0-100*npcu
Mem uint64 `json:"memory_used_bytes"`
MemLimit uint64 `json:"memory_limit_bytes"`
IsThrottling bool `json:"is_throttling"`
NCPU float64 `json:"ncpu"`
CPU float64 `json:"cpu_used"` // percent 0-100*npcu
CPULimit float64 `json:"cpu_limit"` // percent 0-100*npcu
Mem uint64 `json:"memory_used_bytes"`
MemLimit uint64 `json:"memory_limit_bytes"`
}
type ClusterNodeFiles struct {
@ -35,7 +36,9 @@ type ClusterServer struct {
}
type ClusterProcess struct {
ProcessID string `json:"id"`
ID string `json:"id"`
Owner string `json:"owner"`
Domain string `json:"domain"`
NodeID string `json:"node_id"`
Reference string `json:"reference"`
Order string `json:"order"`

View File

@ -11,10 +11,12 @@ import (
// Process represents all information on a process
type Process struct {
ID string `json:"id" jsonschema:"minLength=1"`
Owner string `json:"owner"`
Domain string `json:"domain"`
Type string `json:"type" jsonschema:"enum=ffmpeg"`
Reference string `json:"reference"`
CreatedAt int64 `json:"created_at" jsonschema:"minimum=0" format:"int64"`
UpdatedAt int64 `json:"updated_at" jsonschema:"minimum=0" format:"int64"`
CreatedAt int64 `json:"created_at" format:"int64"`
UpdatedAt int64 `json:"updated_at" format:"int64"`
Config *ProcessConfig `json:"config,omitempty"`
State *ProcessState `json:"state,omitempty"`
Report *ProcessReport `json:"report,omitempty"`
@ -207,6 +209,13 @@ func (cfg *ProcessConfig) Unmarshal(c *app.Config) {
copy(cfg.LogPatterns, c.LogPatterns)
}
func (p *ProcessConfig) ProcessID() app.ProcessID {
return app.ProcessID{
ID: p.ID,
Domain: p.Domain,
}
}
// ProcessState represents the current state of an ffmpeg process
type ProcessState struct {
Order string `json:"order" jsonschema:"enum=start,enum=stop"`

View File

@ -11,7 +11,7 @@ import (
"github.com/datarhei/core/v16/http/graph/models"
"github.com/datarhei/core/v16/playout"
"github.com/datarhei/core/v16/restream"
"github.com/datarhei/core/v16/restream/app"
)
// PlayoutStatus is the resolver for the playoutStatus field.
@ -22,7 +22,7 @@ func (r *queryResolver) PlayoutStatus(ctx context.Context, id string, domain str
return nil, fmt.Errorf("forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}

View File

@ -8,7 +8,7 @@ import (
"fmt"
"github.com/datarhei/core/v16/http/graph/models"
"github.com/datarhei/core/v16/restream"
"github.com/datarhei/core/v16/restream/app"
)
// Processes is the resolver for the processes field.
@ -42,7 +42,7 @@ func (r *queryResolver) Process(ctx context.Context, id string, domain string) (
return nil, fmt.Errorf("forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -58,7 +58,7 @@ func (r *queryResolver) Probe(ctx context.Context, id string, domain string) (*m
return nil, fmt.Errorf("forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}

View File

@ -11,6 +11,7 @@ import (
"github.com/datarhei/core/v16/log"
"github.com/datarhei/core/v16/monitor"
"github.com/datarhei/core/v16/restream"
"github.com/datarhei/core/v16/restream/app"
)
// This file will not be regenerated automatically.
@ -24,7 +25,7 @@ type Resolver struct {
IAM iam.IAM
}
func (r *queryResolver) getProcess(id restream.TaskID) (*models.Process, error) {
func (r *queryResolver) getProcess(id app.ProcessID) (*models.Process, error) {
process, err := r.Restream.GetProcess(id)
if err != nil {
return nil, err

View File

@ -16,6 +16,7 @@ import (
"github.com/datarhei/core/v16/iam/access"
"github.com/datarhei/core/v16/iam/identity"
"github.com/datarhei/core/v16/restream"
"github.com/datarhei/core/v16/restream/app"
"github.com/labstack/echo/v4"
"github.com/lithammer/shortuuid/v4"
@ -108,7 +109,9 @@ func (h *ClusterHandler) ListAllNodeProcesses(c echo.Context) error {
}
processes = append(processes, api.ClusterProcess{
ProcessID: p.Config.ID,
ID: p.Config.ID,
Owner: p.Config.Owner,
Domain: p.Config.Domain,
NodeID: p.NodeID,
Reference: p.Config.Reference,
Order: p.Order,
@ -303,7 +306,9 @@ func (h *ClusterHandler) ListNodeProcesses(c echo.Context) error {
}
processes = append(processes, api.ClusterProcess{
ProcessID: p.Config.ID,
ID: p.Config.ID,
Owner: p.Config.Owner,
Domain: p.Config.Domain,
NodeID: p.NodeID,
Reference: p.Config.Reference,
Order: p.Order,
@ -341,9 +346,11 @@ func (h *ClusterHandler) ListStoreProcesses(c echo.Context) error {
process := api.Process{
ID: p.Config.ID,
Owner: p.Config.Owner,
Domain: p.Config.Domain,
Type: "ffmpeg",
Reference: p.Config.Reference,
CreatedAt: 0,
CreatedAt: p.CreatedAt.Unix(),
UpdatedAt: p.UpdatedAt.Unix(),
Metadata: p.Metadata,
}
@ -412,7 +419,7 @@ func (h *ClusterHandler) AddProcess(c echo.Context) error {
}
for key, value := range metadata {
h.cluster.SetProcessMetadata("", config.ID, key, value)
h.cluster.SetProcessMetadata("", config.ProcessID(), key, value)
}
return c.JSON(http.StatusOK, process)
@ -436,7 +443,7 @@ func (h *ClusterHandler) AddProcess(c echo.Context) error {
func (h *ClusterHandler) UpdateProcess(c echo.Context) error {
ctxuser := util.DefaultContext(c, "user", "")
superuser := util.DefaultContext(c, "superuser", false)
domain := util.DefaultQuery(c, "domain", "$none")
domain := util.DefaultQuery(c, "domain", "")
id := util.PathParam(c, "id")
process := api.ProcessConfig{
@ -451,7 +458,9 @@ func (h *ClusterHandler) UpdateProcess(c echo.Context) error {
return api.Err(http.StatusForbidden, "Forbidden")
}
current, err := h.cluster.GetProcess(id)
pid := process.ProcessID()
current, err := h.cluster.GetProcess(pid)
if err != nil {
return api.Err(http.StatusNotFound, "Process not found", "%s", id)
}
@ -475,7 +484,7 @@ func (h *ClusterHandler) UpdateProcess(c echo.Context) error {
config, metadata := process.Marshal()
if err := h.cluster.UpdateProcess("", id, config); err != nil {
if err := h.cluster.UpdateProcess("", pid, config); err != nil {
if err == restream.ErrUnknownProcess {
return api.Err(http.StatusNotFound, "Process not found", "%s", id)
}
@ -483,8 +492,10 @@ func (h *ClusterHandler) UpdateProcess(c echo.Context) error {
return api.Err(http.StatusBadRequest, "Process can't be updated", "%s", err)
}
pid = process.ProcessID()
for key, value := range metadata {
h.cluster.SetProcessMetadata("", id, key, value)
h.cluster.SetProcessMetadata("", pid, key, value)
}
return c.JSON(http.StatusOK, process)
@ -525,13 +536,13 @@ func (h *ClusterHandler) SetProcessMetadata(c echo.Context) error {
if err := util.ShouldBindJSONValidation(c, &data, false); err != nil {
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", err)
}
/*
tid := restream.TaskID{
ID: id,
Domain: domain,
}
*/
if err := h.cluster.SetProcessMetadata("", id, key, data); err != nil {
pid := app.ProcessID{
ID: id,
Domain: domain,
}
if err := h.cluster.SetProcessMetadata("", pid, key, data); err != nil {
return api.Err(http.StatusNotFound, "Unknown process ID", "%s", err)
}
@ -546,21 +557,26 @@ func (h *ClusterHandler) SetProcessMetadata(c echo.Context) error {
// @Produce json
// @Param id path string true "Process ID"
// @Success 200 {string} string
// @Failure 400 {object} api.Error
// @Failure 403 {object} api.Error
// @Failure 500 {object} api.Error
// @Security ApiKeyAuth
// @Router /api/v3/cluster/process/{id} [delete]
func (h *ClusterHandler) DeleteProcess(c echo.Context) error {
ctxuser := util.DefaultContext(c, "user", "")
domain := util.DefaultQuery(c, "domain", "$none")
domain := util.DefaultQuery(c, "domain", "")
id := util.PathParam(c, "id")
if !h.iam.Enforce(ctxuser, domain, "process:"+id, "write") {
return api.Err(http.StatusForbidden, "", "Not allowed to delete process")
return api.Err(http.StatusForbidden, "", "Not allowed to delete this process")
}
if err := h.cluster.RemoveProcess("", id); err != nil {
return api.Err(http.StatusInternalServerError, "Process can't be deleted", "%s", err)
pid := app.ProcessID{
ID: id,
Domain: domain,
}
if err := h.cluster.RemoveProcess("", pid); err != nil {
return api.Err(http.StatusBadRequest, "", "%s", err)
}
return c.JSON(http.StatusOK, "OK")

View File

@ -22,12 +22,12 @@ func NewJWT(iam iam.IAM) *JWTHandler {
func (j *JWTHandler) Login(c echo.Context) error {
subject, ok := c.Get("user").(string)
if !ok {
return api.Err(http.StatusForbidden, "Invalid token")
return api.Err(http.StatusForbidden, "Invalid user")
}
at, rt, err := j.iam.CreateJWT(subject)
if err != nil {
return api.Err(http.StatusInternalServerError, "Failed to create JWT", "%s", err)
return api.Err(http.StatusForbidden, "Failed to create JWT", "%s", err)
}
return c.JSON(http.StatusOK, api.JWT{
@ -44,7 +44,7 @@ func (j *JWTHandler) Refresh(c echo.Context) error {
at, _, err := j.iam.CreateJWT(subject)
if err != nil {
return api.Err(http.StatusInternalServerError, "Failed to create JWT", "%s", err)
return api.Err(http.StatusForbidden, "Failed to create JWT", "%s", err)
}
return c.JSON(http.StatusOK, api.JWTRefresh{

View File

@ -11,7 +11,7 @@ import (
"github.com/datarhei/core/v16/http/api"
"github.com/datarhei/core/v16/http/handler/util"
"github.com/datarhei/core/v16/playout"
"github.com/datarhei/core/v16/restream"
"github.com/datarhei/core/v16/restream/app"
"github.com/labstack/echo/v4"
)
@ -39,7 +39,7 @@ func (h *RestreamHandler) PlayoutStatus(c echo.Context) error {
return api.Err(http.StatusForbidden, "Forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -108,7 +108,7 @@ func (h *RestreamHandler) PlayoutKeyframe(c echo.Context) error {
return api.Err(http.StatusForbidden, "Forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -166,7 +166,7 @@ func (h *RestreamHandler) PlayoutEncodeErrorframe(c echo.Context) error {
return api.Err(http.StatusForbidden, "Forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -221,7 +221,7 @@ func (h *RestreamHandler) PlayoutSetErrorframe(c echo.Context) error {
return api.Err(http.StatusForbidden, "Forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -277,7 +277,7 @@ func (h *RestreamHandler) PlayoutReopenInput(c echo.Context) error {
return api.Err(http.StatusForbidden, "Forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -331,7 +331,7 @@ func (h *RestreamHandler) PlayoutSetStream(c echo.Context) error {
return api.Err(http.StatusForbidden, "Forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}

View File

@ -11,6 +11,7 @@ import (
"github.com/datarhei/core/v16/http/handler/util"
"github.com/datarhei/core/v16/iam"
"github.com/datarhei/core/v16/restream"
"github.com/datarhei/core/v16/restream/app"
"github.com/labstack/echo/v4"
"github.com/lithammer/shortuuid/v4"
@ -82,7 +83,7 @@ func (h *RestreamHandler) Add(c echo.Context) error {
return api.Err(http.StatusBadRequest, "Invalid process config", "%s", err.Error())
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: config.ID,
Domain: config.Domain,
}
@ -127,7 +128,7 @@ func (h *RestreamHandler) GetAll(c echo.Context) error {
domainpattern := util.DefaultQuery(c, "domainpattern", "")
preids := h.restream.GetProcessIDs(idpattern, refpattern, ownerpattern, domainpattern)
ids := []restream.TaskID{}
ids := []app.ProcessID{}
for _, id := range preids {
if !h.iam.Enforce(ctxuser, domain, "process:"+id.ID, "read") {
@ -187,7 +188,7 @@ func (h *RestreamHandler) Get(c echo.Context) error {
return api.Err(http.StatusForbidden, "Forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -219,7 +220,7 @@ func (h *RestreamHandler) Delete(c echo.Context) error {
id := util.PathParam(c, "id")
domain := util.DefaultQuery(c, "domain", "")
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -274,7 +275,7 @@ func (h *RestreamHandler) Update(c echo.Context) error {
return api.Err(http.StatusForbidden, "Forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -303,7 +304,7 @@ func (h *RestreamHandler) Update(c echo.Context) error {
config, metadata := process.Marshal()
tid = restream.TaskID{
tid = app.ProcessID{
ID: id,
Domain: domain,
}
@ -316,7 +317,7 @@ func (h *RestreamHandler) Update(c echo.Context) error {
return api.Err(http.StatusBadRequest, "Process can't be updated", "%s", err)
}
tid = restream.TaskID{
tid = app.ProcessID{
ID: config.ID,
Domain: config.Domain,
}
@ -361,7 +362,7 @@ func (h *RestreamHandler) Command(c echo.Context) error {
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", err)
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -409,7 +410,7 @@ func (h *RestreamHandler) GetConfig(c echo.Context) error {
return api.Err(http.StatusForbidden, "Forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -448,7 +449,7 @@ func (h *RestreamHandler) GetState(c echo.Context) error {
return api.Err(http.StatusForbidden, "Forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -510,7 +511,7 @@ func (h *RestreamHandler) GetReport(c echo.Context) error {
return api.Err(http.StatusForbidden, "Forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -653,7 +654,7 @@ func (h *RestreamHandler) Probe(c echo.Context) error {
return api.Err(http.StatusForbidden, "Forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -728,7 +729,7 @@ func (h *RestreamHandler) GetProcessMetadata(c echo.Context) error {
return api.Err(http.StatusForbidden, "Forbidden")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -777,7 +778,7 @@ func (h *RestreamHandler) SetProcessMetadata(c echo.Context) error {
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", err)
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}
@ -844,7 +845,7 @@ func (h *RestreamHandler) SetMetadata(c echo.Context) error {
return c.JSON(http.StatusOK, data)
}
func (h *RestreamHandler) getProcess(id restream.TaskID, filterString string) (api.Process, error) {
func (h *RestreamHandler) getProcess(id app.ProcessID, filterString string) (api.Process, error) {
filter := strings.FieldsFunc(filterString, func(r rune) bool {
return r == rune(',')
})
@ -875,6 +876,8 @@ func (h *RestreamHandler) getProcess(id restream.TaskID, filterString string) (a
info := api.Process{
ID: process.ID,
Owner: process.Owner,
Domain: process.Domain,
Reference: process.Reference,
Type: "ffmpeg",
CreatedAt: process.CreatedAt,

View File

@ -7,6 +7,7 @@ import (
"github.com/datarhei/core/v16/http/api"
"github.com/datarhei/core/v16/http/handler/util"
"github.com/datarhei/core/v16/restream"
"github.com/datarhei/core/v16/restream/app"
"github.com/datarhei/core/v16/session"
"github.com/labstack/echo/v4"
@ -49,7 +50,7 @@ func (w *WidgetHandler) Get(c echo.Context) error {
return api.Err(http.StatusNotFound, "Unknown process ID")
}
tid := restream.TaskID{
tid := app.ProcessID{
ID: id,
Domain: domain,
}

View File

@ -125,7 +125,10 @@ func NewWithConfig(config Config) echo.MiddlewareFunc {
resource := c.Request().URL.Path
var domain string
if resource == "/ping" || resource == "/profiling" {
c.Set("user", username)
c.Set("superuser", false)
if resource == "/ping" {
return next(c)
}
@ -151,7 +154,7 @@ func NewWithConfig(config Config) echo.MiddlewareFunc {
} else {
identity, err = mw.findIdentityFromJWT(c)
if err != nil {
return api.Err(http.StatusForbidden, "Forbidden", "%s", err)
return api.Err(http.StatusUnauthorized, "Unauthorized", "%s", err)
}
if identity != nil {
@ -223,6 +226,10 @@ func NewWithConfig(config Config) echo.MiddlewareFunc {
action := c.Request().Method
if username == "$anon" && resource == "api:/api" {
return next(c)
}
if !config.IAM.Enforce(username, domain, resource, action) {
return api.Err(http.StatusForbidden, "Forbidden", "access denied")
}

View File

@ -176,6 +176,13 @@ func (config *Config) Hash() []byte {
return sum[:]
}
func (c *Config) ProcessID() ProcessID {
return ProcessID{
ID: c.ID,
Domain: c.Domain,
}
}
type Process struct {
ID string
Owner string
@ -202,6 +209,13 @@ func (process *Process) Clone() *Process {
return clone
}
func (process *Process) ProcessID() ProcessID {
return ProcessID{
ID: process.ID,
Domain: process.Domain,
}
}
type ProcessStates struct {
Finished uint64
Starting uint64
@ -254,3 +268,46 @@ type ProcessUsage struct {
CPU ProcessUsageCPU
Memory ProcessUsageMemory
}
type ProcessID struct {
ID string
Domain string
}
func NewProcessID(id, domain string) ProcessID {
return ProcessID{
ID: id,
Domain: domain,
}
}
func ParseProcessID(pid string) ProcessID {
p := ProcessID{}
p.Parse(pid)
return p
}
func (p ProcessID) String() string {
return p.ID + "@" + p.Domain
}
func (p ProcessID) Equals(b ProcessID) bool {
if p.ID == b.ID && p.Domain == b.Domain {
return true
}
return false
}
func (p *ProcessID) Parse(pid string) {
i := strings.LastIndex(pid, "@")
if i == -1 {
p.ID = pid
p.Domain = ""
}
p.ID = pid[:i]
p.Domain = pid[i+1:]
}

View File

@ -48,22 +48,22 @@ type Restreamer interface {
GetMetadata(key string) (interface{}, error) // Get previously set general metadata
AddProcess(config *app.Config) error // Add a new process
GetProcessIDs(idpattern, refpattern, ownerpattern, domainpattern string) []TaskID // Get a list of process IDs based on patterns for ID and reference
DeleteProcess(id TaskID) error // Delete a process
UpdateProcess(id TaskID, config *app.Config) error // Update a process
StartProcess(id TaskID) error // Start a process
StopProcess(id TaskID) error // Stop a process
RestartProcess(id TaskID) error // Restart a process
ReloadProcess(id TaskID) error // Reload a process
GetProcess(id TaskID) (*app.Process, error) // Get a process
GetProcessState(id TaskID) (*app.State, error) // Get the state of a process
GetProcessLog(id TaskID) (*app.Log, error) // Get the logs of a process
GetProcessIDs(idpattern, refpattern, ownerpattern, domainpattern string) []app.ProcessID // Get a list of process IDs based on patterns for ID and reference
DeleteProcess(id app.ProcessID) error // Delete a process
UpdateProcess(id app.ProcessID, config *app.Config) error // Update a process
StartProcess(id app.ProcessID) error // Start a process
StopProcess(id app.ProcessID) error // Stop a process
RestartProcess(id app.ProcessID) error // Restart a process
ReloadProcess(id app.ProcessID) error // Reload a process
GetProcess(id app.ProcessID) (*app.Process, error) // Get a process
GetProcessState(id app.ProcessID) (*app.State, error) // Get the state of a process
GetProcessLog(id app.ProcessID) (*app.Log, error) // Get the logs of a process
SearchProcessLogHistory(idpattern, refpattern, state string, from, to *time.Time) []app.LogHistorySearchResult // Search the log history of all processes
GetPlayout(id TaskID, inputid string) (string, error) // Get the URL of the playout API for a process
Probe(id TaskID) app.Probe // Probe a process
ProbeWithTimeout(id TaskID, timeout time.Duration) app.Probe // Probe a process with specific timeout
SetProcessMetadata(id TaskID, key string, data interface{}) error // Set metatdata to a process
GetProcessMetadata(id TaskID, key string) (interface{}, error) // Get previously set metadata from a process
GetPlayout(id app.ProcessID, inputid string) (string, error) // Get the URL of the playout API for a process
Probe(id app.ProcessID) app.Probe // Probe a process
ProbeWithTimeout(id app.ProcessID, timeout time.Duration) app.Probe // Probe a process with specific timeout
SetProcessMetadata(id app.ProcessID, key string, data interface{}) error // Set metatdata to a process
GetProcessMetadata(id app.ProcessID, key string) (interface{}, error) // Get previously set metadata from a process
}
// Config is the required configuration for a new restreamer instance.
@ -99,8 +99,8 @@ type task struct {
metadata map[string]interface{}
}
func (t *task) ID() TaskID {
return TaskID{
func (t *task) ID() app.ProcessID {
return app.ProcessID{
ID: t.id,
Domain: t.domain,
}
@ -110,23 +110,6 @@ func (t *task) String() string {
return t.ID().String()
}
type TaskID struct {
ID string
Domain string
}
func (t TaskID) String() string {
return t.ID + "@" + t.Domain
}
func (t TaskID) Equals(b TaskID) bool {
if t.ID == b.ID && t.Domain == b.Domain {
return true
}
return false
}
type restream struct {
id string
name string
@ -140,8 +123,8 @@ type restream struct {
}
replace replace.Replacer
rewrite rewrite.Rewriter
tasks map[TaskID]*task // domain:processid
metadata map[string]interface{} // global metadata
tasks map[app.ProcessID]*task // domain:ProcessID
metadata map[string]interface{} // global metadata
logger log.Logger
resources resources.Resources
@ -386,7 +369,7 @@ func (r *restream) load() error {
return err
}
tasks := make(map[TaskID]*task)
tasks := make(map[app.ProcessID]*task)
skills := r.ffmpeg.Skills()
ffversion := skills.FFmpeg.Version
@ -409,9 +392,10 @@ func (r *restream) load() error {
process: p.Process,
config: p.Process.Config.Clone(),
logger: r.logger.WithFields(log.Fields{
"id": p.Process.ID,
"owner": p.Process.Owner,
"domain": p.Process.Domain,
"id": p.Process.ID,
"owner": p.Process.Owner,
"domain": p.Process.Domain,
"reference": p.Process.Reference,
}),
}
@ -609,6 +593,7 @@ func (r *restream) createTask(config *app.Config) (*task, error) {
process := &app.Process{
ID: config.ID,
Owner: config.Owner,
Domain: config.Domain,
Reference: config.Reference,
Config: config.Clone(),
@ -624,12 +609,14 @@ func (r *restream) createTask(config *app.Config) (*task, error) {
t := &task{
id: config.ID,
owner: config.Owner,
domain: config.Domain,
reference: process.Reference,
process: process,
config: process.Config.Clone(),
logger: r.logger.WithFields(log.Fields{
"id": process.ID,
"owner": process.Owner,
"reference": process.Reference,
"domain": process.Domain,
}),
@ -722,7 +709,7 @@ func (r *restream) onArgs(cfg *app.Config) func([]string) []string {
}
}
func (r *restream) setCleanup(id TaskID, config *app.Config) {
func (r *restream) setCleanup(id app.ProcessID, config *app.Config) {
rePrefix := regexp.MustCompile(`^([a-z]+):`)
for _, output := range config.Output {
@ -764,7 +751,7 @@ func (r *restream) setCleanup(id TaskID, config *app.Config) {
}
}
func (r *restream) unsetCleanup(id TaskID) {
func (r *restream) unsetCleanup(id app.ProcessID) {
for _, fs := range r.fs.list {
fs.UnsetCleanup(id.String())
}
@ -1024,7 +1011,7 @@ func validateOutputAddress(address, basedir string, ffmpeg ffmpeg.FFmpeg) (strin
}
// resolveAddresses replaces the addresse reference from each input in a config with the actual address.
func (r *restream) resolveAddresses(tasks map[TaskID]*task, config *app.Config) error {
func (r *restream) resolveAddresses(tasks map[app.ProcessID]*task, config *app.Config) error {
for i, input := range config.Input {
// Resolve any references
address, err := r.resolveAddress(tasks, config.ID, input.Address)
@ -1041,7 +1028,7 @@ func (r *restream) resolveAddresses(tasks map[TaskID]*task, config *app.Config)
}
// resolveAddress replaces the address reference with the actual address.
func (r *restream) resolveAddress(tasks map[TaskID]*task, id, address string) (string, error) {
func (r *restream) resolveAddress(tasks map[app.ProcessID]*task, id, address string) (string, error) {
matches, err := parseAddressReference(address)
if err != nil {
return address, err
@ -1176,7 +1163,7 @@ func parseAddressReference(address string) (map[string]string, error) {
return results, nil
}
func (r *restream) UpdateProcess(id TaskID, config *app.Config) error {
func (r *restream) UpdateProcess(id app.ProcessID, config *app.Config) error {
r.lock.Lock()
defer r.lock.Unlock()
@ -1241,11 +1228,11 @@ func (r *restream) UpdateProcess(id TaskID, config *app.Config) error {
return nil
}
func (r *restream) GetProcessIDs(idpattern, refpattern, ownerpattern, domainpattern string) []TaskID {
func (r *restream) GetProcessIDs(idpattern, refpattern, ownerpattern, domainpattern string) []app.ProcessID {
r.lock.RLock()
defer r.lock.RUnlock()
ids := []TaskID{}
ids := []app.ProcessID{}
for _, t := range r.tasks {
count := 0
@ -1302,7 +1289,7 @@ func (r *restream) GetProcessIDs(idpattern, refpattern, ownerpattern, domainpatt
continue
}
tid := TaskID{
tid := app.ProcessID{
ID: t.id,
Domain: t.domain,
}
@ -1313,7 +1300,7 @@ func (r *restream) GetProcessIDs(idpattern, refpattern, ownerpattern, domainpatt
return ids
}
func (r *restream) GetProcess(id TaskID) (*app.Process, error) {
func (r *restream) GetProcess(id app.ProcessID) (*app.Process, error) {
r.lock.RLock()
defer r.lock.RUnlock()
@ -1327,7 +1314,7 @@ func (r *restream) GetProcess(id TaskID) (*app.Process, error) {
return process, nil
}
func (r *restream) DeleteProcess(id TaskID) error {
func (r *restream) DeleteProcess(id app.ProcessID) error {
r.lock.Lock()
defer r.lock.Unlock()
@ -1341,7 +1328,7 @@ func (r *restream) DeleteProcess(id TaskID) error {
return nil
}
func (r *restream) deleteProcess(tid TaskID) error {
func (r *restream) deleteProcess(tid app.ProcessID) error {
task, ok := r.tasks[tid]
if !ok {
return ErrUnknownProcess
@ -1359,7 +1346,7 @@ func (r *restream) deleteProcess(tid TaskID) error {
return nil
}
func (r *restream) StartProcess(id TaskID) error {
func (r *restream) StartProcess(id app.ProcessID) error {
r.lock.Lock()
defer r.lock.Unlock()
@ -1373,7 +1360,7 @@ func (r *restream) StartProcess(id TaskID) error {
return nil
}
func (r *restream) startProcess(tid TaskID) error {
func (r *restream) startProcess(tid app.ProcessID) error {
task, ok := r.tasks[tid]
if !ok {
return ErrUnknownProcess
@ -1404,7 +1391,7 @@ func (r *restream) startProcess(tid TaskID) error {
return nil
}
func (r *restream) StopProcess(id TaskID) error {
func (r *restream) StopProcess(id app.ProcessID) error {
r.lock.Lock()
defer r.lock.Unlock()
@ -1418,7 +1405,7 @@ func (r *restream) StopProcess(id TaskID) error {
return nil
}
func (r *restream) stopProcess(tid TaskID) error {
func (r *restream) stopProcess(tid app.ProcessID) error {
task, ok := r.tasks[tid]
if !ok {
return ErrUnknownProcess
@ -1443,14 +1430,14 @@ func (r *restream) stopProcess(tid TaskID) error {
return nil
}
func (r *restream) RestartProcess(id TaskID) error {
func (r *restream) RestartProcess(id app.ProcessID) error {
r.lock.RLock()
defer r.lock.RUnlock()
return r.restartProcess(id)
}
func (r *restream) restartProcess(tid TaskID) error {
func (r *restream) restartProcess(tid app.ProcessID) error {
task, ok := r.tasks[tid]
if !ok {
return ErrUnknownProcess
@ -1472,7 +1459,7 @@ func (r *restream) restartProcess(tid TaskID) error {
return nil
}
func (r *restream) ReloadProcess(id TaskID) error {
func (r *restream) ReloadProcess(id app.ProcessID) error {
r.lock.Lock()
defer r.lock.Unlock()
@ -1486,7 +1473,7 @@ func (r *restream) ReloadProcess(id TaskID) error {
return nil
}
func (r *restream) reloadProcess(tid TaskID) error {
func (r *restream) reloadProcess(tid app.ProcessID) error {
t, ok := r.tasks[tid]
if !ok {
return ErrUnknownProcess
@ -1575,7 +1562,7 @@ func (r *restream) reloadProcess(tid TaskID) error {
return nil
}
func (r *restream) GetProcessState(id TaskID) (*app.State, error) {
func (r *restream) GetProcessState(id app.ProcessID) (*app.State, error) {
state := &app.State{}
r.lock.RLock()
@ -1735,7 +1722,7 @@ func convertProgressFromParser(progress *app.Progress, pprogress parse.Progress)
}
}
func (r *restream) GetProcessLog(id TaskID) (*app.Log, error) {
func (r *restream) GetProcessLog(id app.ProcessID) (*app.Log, error) {
log := &app.Log{}
r.lock.RLock()
@ -1851,11 +1838,11 @@ func (r *restream) SearchProcessLogHistory(idpattern, refpattern, state string,
return result
}
func (r *restream) Probe(id TaskID) app.Probe {
func (r *restream) Probe(id app.ProcessID) app.Probe {
return r.ProbeWithTimeout(id, 20*time.Second)
}
func (r *restream) ProbeWithTimeout(id TaskID, timeout time.Duration) app.Probe {
func (r *restream) ProbeWithTimeout(id app.ProcessID, timeout time.Duration) app.Probe {
appprobe := app.Probe{}
r.lock.RLock()
@ -1954,7 +1941,7 @@ func (r *restream) ReloadSkills() error {
return r.ffmpeg.ReloadSkills()
}
func (r *restream) GetPlayout(id TaskID, inputid string) (string, error) {
func (r *restream) GetPlayout(id app.ProcessID, inputid string) (string, error) {
r.lock.RLock()
defer r.lock.RUnlock()
@ -1977,7 +1964,7 @@ func (r *restream) GetPlayout(id TaskID, inputid string) (string, error) {
var ErrMetadataKeyNotFound = errors.New("unknown key")
func (r *restream) SetProcessMetadata(id TaskID, key string, data interface{}) error {
func (r *restream) SetProcessMetadata(id app.ProcessID, key string, data interface{}) error {
if len(key) == 0 {
return fmt.Errorf("a key for storing the data has to be provided")
}
@ -2009,7 +1996,7 @@ func (r *restream) SetProcessMetadata(id TaskID, key string, data interface{}) e
return nil
}
func (r *restream) GetProcessMetadata(id TaskID, key string) (interface{}, error) {
func (r *restream) GetProcessMetadata(id app.ProcessID, key string) (interface{}, error) {
r.lock.RLock()
defer r.lock.RUnlock()
@ -2083,7 +2070,7 @@ func resolveStaticPlaceholders(config *app.Config, r replace.Replacer) {
"processid": config.ID,
"owner": config.Owner,
"reference": config.Reference,
"group": config.Domain,
"domain": config.Domain,
}
for i, option := range config.Options {
@ -2098,12 +2085,14 @@ func resolveStaticPlaceholders(config *app.Config, r replace.Replacer) {
for i, input := range config.Input {
// Replace any known placeholders
input.ID = r.Replace(input.ID, "processid", config.ID, vars, config, "input")
input.ID = r.Replace(input.ID, "domain", config.Domain, vars, config, "input")
input.ID = r.Replace(input.ID, "reference", config.Reference, vars, config, "input")
vars["inputid"] = input.ID
input.Address = r.Replace(input.Address, "inputid", input.ID, vars, config, "input")
input.Address = r.Replace(input.Address, "processid", config.ID, vars, config, "input")
input.Address = r.Replace(input.Address, "domain", config.Domain, vars, config, "input")
input.Address = r.Replace(input.Address, "reference", config.Reference, vars, config, "input")
input.Address = r.Replace(input.Address, "diskfs", "", vars, config, "input")
input.Address = r.Replace(input.Address, "memfs", "", vars, config, "input")
@ -2115,6 +2104,7 @@ func resolveStaticPlaceholders(config *app.Config, r replace.Replacer) {
// Replace any known placeholders
option = r.Replace(option, "inputid", input.ID, vars, config, "input")
option = r.Replace(option, "processid", config.ID, vars, config, "input")
option = r.Replace(option, "domain", config.Domain, vars, config, "input")
option = r.Replace(option, "reference", config.Reference, vars, config, "input")
option = r.Replace(option, "diskfs", "", vars, config, "input")
option = r.Replace(option, "memfs", "", vars, config, "input")
@ -2132,12 +2122,14 @@ func resolveStaticPlaceholders(config *app.Config, r replace.Replacer) {
for i, output := range config.Output {
// Replace any known placeholders
output.ID = r.Replace(output.ID, "processid", config.ID, vars, config, "output")
output.ID = r.Replace(output.ID, "domain", config.Domain, vars, config, "output")
output.ID = r.Replace(output.ID, "reference", config.Reference, vars, config, "output")
vars["outputid"] = output.ID
output.Address = r.Replace(output.Address, "outputid", output.ID, vars, config, "output")
output.Address = r.Replace(output.Address, "processid", config.ID, vars, config, "output")
output.Address = r.Replace(output.Address, "domain", config.Domain, vars, config, "output")
output.Address = r.Replace(output.Address, "reference", config.Reference, vars, config, "output")
output.Address = r.Replace(output.Address, "diskfs", "", vars, config, "output")
output.Address = r.Replace(output.Address, "memfs", "", vars, config, "output")
@ -2149,6 +2141,7 @@ func resolveStaticPlaceholders(config *app.Config, r replace.Replacer) {
// Replace any known placeholders
option = r.Replace(option, "outputid", output.ID, vars, config, "output")
option = r.Replace(option, "processid", config.ID, vars, config, "output")
option = r.Replace(option, "domain", config.Domain, vars, config, "output")
option = r.Replace(option, "reference", config.Reference, vars, config, "output")
option = r.Replace(option, "diskfs", "", vars, config, "output")
option = r.Replace(option, "memfs", "", vars, config, "output")
@ -2161,6 +2154,7 @@ func resolveStaticPlaceholders(config *app.Config, r replace.Replacer) {
// Replace any known placeholders
cleanup.Pattern = r.Replace(cleanup.Pattern, "outputid", output.ID, vars, config, "output")
cleanup.Pattern = r.Replace(cleanup.Pattern, "processid", config.ID, vars, config, "output")
cleanup.Pattern = r.Replace(cleanup.Pattern, "domain", config.Domain, vars, config, "output")
cleanup.Pattern = r.Replace(cleanup.Pattern, "reference", config.Reference, vars, config, "output")
output.Cleanup[j] = cleanup

View File

@ -134,7 +134,7 @@ func TestAddProcess(t *testing.T) {
process := getDummyProcess()
require.NotNil(t, process)
tid := TaskID{ID: process.ID}
tid := app.ProcessID{ID: process.ID}
_, err = rs.GetProcess(tid)
require.Equal(t, ErrUnknownProcess, err)
@ -156,7 +156,7 @@ func TestAutostartProcess(t *testing.T) {
process := getDummyProcess()
process.Autostart = true
tid := TaskID{ID: process.ID}
tid := app.ProcessID{ID: process.ID}
rs.AddProcess(process)
@ -239,7 +239,7 @@ func TestRemoveProcess(t *testing.T) {
require.NoError(t, err)
process := getDummyProcess()
tid := TaskID{ID: process.ID}
tid := app.ProcessID{ID: process.ID}
err = rs.AddProcess(process)
require.Equal(t, nil, err, "Failed to add process (%s)", err)
@ -258,12 +258,12 @@ func TestUpdateProcess(t *testing.T) {
process1 := getDummyProcess()
require.NotNil(t, process1)
process1.ID = "process1"
tid1 := TaskID{ID: process1.ID}
tid1 := app.ProcessID{ID: process1.ID}
process2 := getDummyProcess()
require.NotNil(t, process2)
process2.ID = "process2"
tid2 := TaskID{ID: process2.ID}
tid2 := app.ProcessID{ID: process2.ID}
err = rs.AddProcess(process1)
require.Equal(t, nil, err)
@ -282,7 +282,7 @@ func TestUpdateProcess(t *testing.T) {
process3 := getDummyProcess()
require.NotNil(t, process3)
process3.ID = "process2"
tid3 := TaskID{ID: process3.ID}
tid3 := app.ProcessID{ID: process3.ID}
err = rs.UpdateProcess(tid1, process3)
require.Error(t, err)
@ -308,7 +308,7 @@ func TestUpdateSameHashProcess(t *testing.T) {
config := getDummyProcess()
require.NotNil(t, config)
tid := TaskID{ID: config.ID}
tid := app.ProcessID{ID: config.ID}
err = rs.AddProcess(config)
require.Equal(t, nil, err)
@ -339,7 +339,7 @@ func TestUpdateProcessLogHistoryTransfer(t *testing.T) {
require.NotNil(t, p)
p.ID = "process1"
tid1 := TaskID{ID: p.ID}
tid1 := app.ProcessID{ID: p.ID}
err = rs.AddProcess(p)
require.Equal(t, nil, err)
@ -363,7 +363,7 @@ func TestUpdateProcessLogHistoryTransfer(t *testing.T) {
err = rs.UpdateProcess(tid1, p)
require.NoError(t, err)
tid2 := TaskID{ID: p.ID}
tid2 := app.ProcessID{ID: p.ID}
_, err = rs.GetProcess(tid2)
require.NoError(t, err)
@ -393,7 +393,7 @@ func TestUpdateProcessMetadataTransfer(t *testing.T) {
require.NotNil(t, p)
p.ID = "process1"
tid1 := TaskID{ID: p.ID}
tid1 := app.ProcessID{ID: p.ID}
err = rs.AddProcess(p)
require.Equal(t, nil, err)
@ -408,7 +408,7 @@ func TestUpdateProcessMetadataTransfer(t *testing.T) {
err = rs.UpdateProcess(tid1, p)
require.NoError(t, err)
tid2 := TaskID{ID: p.ID}
tid2 := app.ProcessID{ID: p.ID}
_, err = rs.GetProcess(tid2)
require.NoError(t, err)
@ -427,19 +427,19 @@ func TestGetProcess(t *testing.T) {
process1 := getDummyProcess()
process1.ID = "foo_aaa_1"
process1.Reference = "foo_aaa_1"
tid1 := TaskID{ID: process1.ID}
tid1 := app.ProcessID{ID: process1.ID}
process2 := getDummyProcess()
process2.ID = "bar_bbb_2"
process2.Reference = "bar_bbb_2"
tid2 := TaskID{ID: process2.ID}
tid2 := app.ProcessID{ID: process2.ID}
process3 := getDummyProcess()
process3.ID = "foo_ccc_3"
process3.Reference = "foo_ccc_3"
tid3 := TaskID{ID: process3.ID}
tid3 := app.ProcessID{ID: process3.ID}
process4 := getDummyProcess()
process4.ID = "bar_ddd_4"
process4.Reference = "bar_ddd_4"
tid4 := TaskID{ID: process4.ID}
tid4 := app.ProcessID{ID: process4.ID}
rs.AddProcess(process1)
rs.AddProcess(process2)
@ -460,31 +460,31 @@ func TestGetProcess(t *testing.T) {
list := rs.GetProcessIDs("", "", "", "")
require.Len(t, list, 4)
require.ElementsMatch(t, []TaskID{{ID: "foo_aaa_1"}, {ID: "bar_bbb_2"}, {ID: "foo_ccc_3"}, {ID: "bar_ddd_4"}}, list)
require.ElementsMatch(t, []app.ProcessID{{ID: "foo_aaa_1"}, {ID: "bar_bbb_2"}, {ID: "foo_ccc_3"}, {ID: "bar_ddd_4"}}, list)
list = rs.GetProcessIDs("foo_*", "", "", "")
require.Len(t, list, 2)
require.ElementsMatch(t, []TaskID{{ID: "foo_aaa_1"}, {ID: "foo_ccc_3"}}, list)
require.ElementsMatch(t, []app.ProcessID{{ID: "foo_aaa_1"}, {ID: "foo_ccc_3"}}, list)
list = rs.GetProcessIDs("bar_*", "", "", "")
require.Len(t, list, 2)
require.ElementsMatch(t, []TaskID{{ID: "bar_bbb_2"}, {ID: "bar_ddd_4"}}, list)
require.ElementsMatch(t, []app.ProcessID{{ID: "bar_bbb_2"}, {ID: "bar_ddd_4"}}, list)
list = rs.GetProcessIDs("*_bbb_*", "", "", "")
require.Len(t, list, 1)
require.ElementsMatch(t, []TaskID{{ID: "bar_bbb_2"}}, list)
require.ElementsMatch(t, []app.ProcessID{{ID: "bar_bbb_2"}}, list)
list = rs.GetProcessIDs("", "foo_*", "", "")
require.Len(t, list, 2)
require.ElementsMatch(t, []TaskID{{ID: "foo_aaa_1"}, {ID: "foo_ccc_3"}}, list)
require.ElementsMatch(t, []app.ProcessID{{ID: "foo_aaa_1"}, {ID: "foo_ccc_3"}}, list)
list = rs.GetProcessIDs("", "bar_*", "", "")
require.Len(t, list, 2)
require.ElementsMatch(t, []TaskID{{ID: "bar_bbb_2"}, {ID: "bar_ddd_4"}}, list)
require.ElementsMatch(t, []app.ProcessID{{ID: "bar_bbb_2"}, {ID: "bar_ddd_4"}}, list)
list = rs.GetProcessIDs("", "*_bbb_*", "", "")
require.Len(t, list, 1)
require.ElementsMatch(t, []TaskID{{ID: "bar_bbb_2"}}, list)
require.ElementsMatch(t, []app.ProcessID{{ID: "bar_bbb_2"}}, list)
}
func TestStartProcess(t *testing.T) {
@ -492,11 +492,11 @@ func TestStartProcess(t *testing.T) {
require.NoError(t, err)
process := getDummyProcess()
tid := TaskID{ID: process.ID}
tid := app.ProcessID{ID: process.ID}
rs.AddProcess(process)
err = rs.StartProcess(TaskID{ID: "foobar"})
err = rs.StartProcess(app.ProcessID{ID: "foobar"})
require.NotEqual(t, nil, err, "shouldn't be able to start non-existing process")
err = rs.StartProcess(tid)
@ -519,12 +519,12 @@ func TestStopProcess(t *testing.T) {
require.NoError(t, err)
process := getDummyProcess()
tid := TaskID{ID: process.ID}
tid := app.ProcessID{ID: process.ID}
rs.AddProcess(process)
rs.StartProcess(tid)
err = rs.StopProcess(TaskID{ID: "foobar"})
err = rs.StopProcess(app.ProcessID{ID: "foobar"})
require.NotEqual(t, nil, err, "shouldn't be able to stop non-existing process")
err = rs.StopProcess(tid)
@ -545,11 +545,11 @@ func TestRestartProcess(t *testing.T) {
require.NoError(t, err)
process := getDummyProcess()
tid := TaskID{ID: process.ID}
tid := app.ProcessID{ID: process.ID}
rs.AddProcess(process)
err = rs.RestartProcess(TaskID{ID: "foobar"})
err = rs.RestartProcess(app.ProcessID{ID: "foobar"})
require.NotEqual(t, nil, err, "shouldn't be able to restart non-existing process")
err = rs.RestartProcess(tid)
@ -571,11 +571,11 @@ func TestReloadProcess(t *testing.T) {
require.NoError(t, err)
process := getDummyProcess()
tid := TaskID{ID: process.ID}
tid := app.ProcessID{ID: process.ID}
rs.AddProcess(process)
err = rs.ReloadProcess(TaskID{ID: "foobar"})
err = rs.ReloadProcess(app.ProcessID{ID: "foobar"})
require.NotEqual(t, nil, err, "shouldn't be able to reload non-existing process")
err = rs.ReloadProcess(tid)
@ -605,7 +605,7 @@ func TestParseProcessPattern(t *testing.T) {
process := getDummyProcess()
process.LogPatterns = []string{"libx264"}
tid := TaskID{ID: process.ID}
tid := app.ProcessID{ID: process.ID}
rs.AddProcess(process)
rs.StartProcess(tid)
@ -626,7 +626,7 @@ func TestProbeProcess(t *testing.T) {
require.NoError(t, err)
process := getDummyProcess()
tid := TaskID{ID: process.ID}
tid := app.ProcessID{ID: process.ID}
rs.AddProcess(process)
@ -640,7 +640,7 @@ func TestProcessMetadata(t *testing.T) {
require.NoError(t, err)
process := getDummyProcess()
tid := TaskID{ID: process.ID}
tid := app.ProcessID{ID: process.ID}
rs.AddProcess(process)
@ -665,11 +665,11 @@ func TestLog(t *testing.T) {
require.NoError(t, err)
process := getDummyProcess()
tid := TaskID{ID: process.ID}
tid := app.ProcessID{ID: process.ID}
rs.AddProcess(process)
_, err = rs.GetProcessLog(TaskID{ID: "foobar"})
_, err = rs.GetProcessLog(app.ProcessID{ID: "foobar"})
require.Error(t, err)
log, err := rs.GetProcessLog(tid)
@ -704,7 +704,7 @@ func TestLogTransfer(t *testing.T) {
require.NoError(t, err)
process := getDummyProcess()
tid := TaskID{ID: process.ID}
tid := app.ProcessID{ID: process.ID}
err = rs.AddProcess(process)
require.NoError(t, err)
@ -730,13 +730,13 @@ func TestPlayoutNoRange(t *testing.T) {
require.NoError(t, err)
process := getDummyProcess()
tid := TaskID{ID: process.ID}
tid := app.ProcessID{ID: process.ID}
process.Input[0].Address = "playout:" + process.Input[0].Address
rs.AddProcess(process)
_, err = rs.GetPlayout(TaskID{ID: "foobar"}, process.Input[0].ID)
_, err = rs.GetPlayout(app.ProcessID{ID: "foobar"}, process.Input[0].ID)
require.Equal(t, ErrUnknownProcess, err)
_, err = rs.GetPlayout(tid, "foobar")
@ -754,13 +754,13 @@ func TestPlayoutRange(t *testing.T) {
require.NoError(t, err)
process := getDummyProcess()
tid := TaskID{ID: process.ID}
tid := app.ProcessID{ID: process.ID}
process.Input[0].Address = "playout:" + process.Input[0].Address
rs.AddProcess(process)
_, err = rs.GetPlayout(TaskID{ID: "foobar"}, process.Input[0].ID)
_, err = rs.GetPlayout(app.ProcessID{ID: "foobar"}, process.Input[0].ID)
require.Equal(t, ErrUnknownProcess, err)
_, err = rs.GetPlayout(tid, "foobar")
@ -869,9 +869,9 @@ func TestTeeAddressReference(t *testing.T) {
r := rs.(*restream)
require.Equal(t, "http://example.com/live.m3u8", r.tasks[TaskID{ID: "process2"}].config.Input[0].Address)
require.Equal(t, "http://example.com/live.m3u8", r.tasks[TaskID{ID: "process3"}].config.Input[0].Address)
require.Equal(t, "rtmp://example.com/live.stream?token=123", r.tasks[TaskID{ID: "process4"}].config.Input[0].Address)
require.Equal(t, "http://example.com/live.m3u8", r.tasks[app.ProcessID{ID: "process2"}].config.Input[0].Address)
require.Equal(t, "http://example.com/live.m3u8", r.tasks[app.ProcessID{ID: "process3"}].config.Input[0].Address)
require.Equal(t, "rtmp://example.com/live.stream?token=123", r.tasks[app.ProcessID{ID: "process4"}].config.Input[0].Address)
}
func TestConfigValidation(t *testing.T) {
@ -1449,7 +1449,7 @@ func TestProcessReplacer(t *testing.T) {
LogPatterns: []string{},
}
task, ok := rs.tasks[TaskID{ID: "314159265359"}]
task, ok := rs.tasks[app.ProcessID{ID: "314159265359"}]
require.True(t, ok)
require.Equal(t, process, task.config)
@ -1466,7 +1466,7 @@ func TestProcessLogPattern(t *testing.T) {
process.Autostart = false
process.Reconnect = true
tid := TaskID{ID: process.ID}
tid := app.ProcessID{ID: process.ID}
err = rs.AddProcess(process)
require.NoError(t, err)
@ -1500,7 +1500,7 @@ func TestProcessLimit(t *testing.T) {
rs := rsi.(*restream)
task, ok := rs.tasks[TaskID{ID: process.ID}]
task, ok := rs.tasks[app.ProcessID{ID: process.ID}]
require.True(t, ok)
status := task.ffmpeg.Status()

View File

@ -1,6 +1,8 @@
package json
import "github.com/datarhei/core/v16/restream/app"
import (
"github.com/datarhei/core/v16/restream/app"
)
type ProcessConfigIOCleanup struct {
Pattern string `json:"pattern"`

View File

@ -3,10 +3,12 @@ package api
// Process represents all information on a process
type Process struct {
ID string `json:"id" jsonschema:"minLength=1"`
Owner string `json:"owner"`
Domain string `json:"domain"`
Type string `json:"type" jsonschema:"enum=ffmpeg"`
Reference string `json:"reference"`
CreatedAt int64 `json:"created_at" jsonschema:"minimum=0" format:"int64"`
UpdatedAt int64 `json:"updated_at" jsonschema:"minimum=0" format:"int64"`
CreatedAt int64 `json:"created_at" jsonschema:"minimum=0" format:"int64"` // Unix timestamp
UpdatedAt int64 `json:"updated_at" jsonschema:"minimum=0" format:"int64"` // Unix timestamp
Config *ProcessConfig `json:"config,omitempty"`
State *ProcessState `json:"state,omitempty"`
Report *ProcessReport `json:"report,omitempty"`
@ -24,29 +26,31 @@ type ProcessConfigIO struct {
type ProcessConfigIOCleanup struct {
Pattern string `json:"pattern" validate:"required"`
MaxFiles uint `json:"max_files" format:"uint"`
MaxFileAge uint `json:"max_file_age_seconds" format:"uint"`
MaxFileAge uint `json:"max_file_age_seconds" format:"uint"` // seconds
PurgeOnDelete bool `json:"purge_on_delete"`
}
type ProcessConfigLimits struct {
CPU float64 `json:"cpu_usage" jsonschema:"minimum=0,maximum=100"`
Memory uint64 `json:"memory_mbytes" jsonschema:"minimum=0" format:"uint64"`
WaitFor uint64 `json:"waitfor_seconds" jsonschema:"minimum=0" format:"uint64"`
CPU float64 `json:"cpu_usage" jsonschema:"minimum=0"` // percent 0-100*ncpu
Memory uint64 `json:"memory_mbytes" jsonschema:"minimum=0" format:"uint64"` // megabytes
WaitFor uint64 `json:"waitfor_seconds" jsonschema:"minimum=0" format:"uint64"` // seconds
}
// ProcessConfig represents the configuration of an ffmpeg process
type ProcessConfig struct {
ID string `json:"id"`
Owner string `json:"owner"`
Domain string `json:"domain"`
Type string `json:"type" validate:"oneof='ffmpeg' ''" jsonschema:"enum=ffmpeg,enum="`
Reference string `json:"reference"`
Input []ProcessConfigIO `json:"input" validate:"required"`
Output []ProcessConfigIO `json:"output" validate:"required"`
Options []string `json:"options"`
Reconnect bool `json:"reconnect"`
ReconnectDelay uint64 `json:"reconnect_delay_seconds" format:"uint64"`
ReconnectDelay uint64 `json:"reconnect_delay_seconds" format:"uint64"` // seconds
Autostart bool `json:"autostart"`
StaleTimeout uint64 `json:"stale_timeout_seconds" format:"uint64"`
Timeout uint64 `json:"runtime_duration_seconds" format:"uint64"`
StaleTimeout uint64 `json:"stale_timeout_seconds" format:"uint64"` // seconds
Timeout uint64 `json:"runtime_duration_seconds" format:"uint64"` // seconds
Scheduler string `json:"scheduler"`
LogPatterns []string `json:"log_patterns"`
Limits ProcessConfigLimits `json:"limits"`
@ -57,29 +61,29 @@ type ProcessConfig struct {
type ProcessState struct {
Order string `json:"order" jsonschema:"enum=start,enum=stop"`
State string `json:"exec" jsonschema:"enum=finished,enum=starting,enum=running,enum=finishing,enum=killed,enum=failed"`
Runtime int64 `json:"runtime_seconds" jsonschema:"minimum=0" format:"int64"`
Reconnect int64 `json:"reconnect_seconds" format:"int64"`
Runtime int64 `json:"runtime_seconds" jsonschema:"minimum=0" format:"int64"` // seconds
Reconnect int64 `json:"reconnect_seconds" format:"int64"` // seconds
LastLog string `json:"last_logline"`
Progress *Progress `json:"progress"`
Memory uint64 `json:"memory_bytes" format:"uint64"`
CPU float64 `json:"cpu_usage" swaggertype:"number" jsonschema:"type=number"`
Memory uint64 `json:"memory_bytes" format:"uint64"` // bytes
CPU float64 `json:"cpu_usage" swaggertype:"number" jsonschema:"type=number"` // percent 0-100*ncpu
Resources ProcessUsage `json:"resources"`
Command []string `json:"command"`
}
type ProcessUsageCPU struct {
NCPU float64 `json:"ncpu" swaggertype:"number" jsonschema:"type=number"`
Current float64 `json:"cur" swaggertype:"number" jsonschema:"type=number"`
Average float64 `json:"avg" swaggertype:"number" jsonschema:"type=number"`
Max float64 `json:"max" swaggertype:"number" jsonschema:"type=number"`
Limit float64 `json:"limit" swaggertype:"number" jsonschema:"type=number"`
Current float64 `json:"cur" swaggertype:"number" jsonschema:"type=number"` // percent 0-100*ncpu
Average float64 `json:"avg" swaggertype:"number" jsonschema:"type=number"` // percent 0-100*ncpu
Max float64 `json:"max" swaggertype:"number" jsonschema:"type=number"` // percent 0-100*ncpu
Limit float64 `json:"limit" swaggertype:"number" jsonschema:"type=number"` // percent 0-100*ncpu
}
type ProcessUsageMemory struct {
Current uint64 `json:"cur" format:"uint64"`
Average float64 `json:"avg" swaggertype:"number" jsonschema:"type=number"`
Max uint64 `json:"max" format:"uint64"`
Limit uint64 `json:"limit" format:"uint64"`
Current uint64 `json:"cur" format:"uint64"` // bytes
Average float64 `json:"avg" swaggertype:"number" jsonschema:"type=number"` // bytes
Max uint64 `json:"max" format:"uint64"` // bytes
Limit uint64 `json:"limit" format:"uint64"` // bytes
}
type ProcessUsage struct {

View File

@ -74,18 +74,18 @@ type RestClient interface {
MetricsList() ([]api.MetricsDescription, error) // GET /v3/metrics
Metrics(query api.MetricsQuery) (api.MetricsResponse, error) // POST /v3/metrics
ProcessList(opts ProcessListOptions) ([]api.Process, error) // GET /v3/process
ProcessAdd(p api.ProcessConfig) error // POST /v3/process
Process(id string, filter []string) (api.Process, error) // GET /v3/process/{id}
ProcessUpdate(id string, p api.ProcessConfig) error // PUT /v3/process/{id}
ProcessDelete(id string) error // DELETE /v3/process/{id}
ProcessCommand(id, command string) error // PUT /v3/process/{id}/command
ProcessProbe(id string) (api.Probe, error) // GET /v3/process/{id}/probe
ProcessConfig(id string) (api.ProcessConfig, error) // GET /v3/process/{id}/config
ProcessReport(id string) (api.ProcessReport, error) // GET /v3/process/{id}/report
ProcessState(id string) (api.ProcessState, error) // GET /v3/process/{id}/state
ProcessMetadata(id, key string) (api.Metadata, error) // GET /v3/process/{id}/metadata/{key}
ProcessMetadataSet(id, key string, metadata api.Metadata) error // PUT /v3/process/{id}/metadata/{key}
ProcessList(opts ProcessListOptions) ([]api.Process, error) // GET /v3/process
ProcessAdd(p api.ProcessConfig) error // POST /v3/process
Process(id ProcessID, filter []string) (api.Process, error) // GET /v3/process/{id}
ProcessUpdate(id ProcessID, p api.ProcessConfig) error // PUT /v3/process/{id}
ProcessDelete(id ProcessID) error // DELETE /v3/process/{id}
ProcessCommand(id ProcessID, command string) error // PUT /v3/process/{id}/command
ProcessProbe(id ProcessID) (api.Probe, error) // GET /v3/process/{id}/probe
ProcessConfig(id ProcessID) (api.ProcessConfig, error) // GET /v3/process/{id}/config
ProcessReport(id ProcessID) (api.ProcessReport, error) // GET /v3/process/{id}/report
ProcessState(id ProcessID) (api.ProcessState, error) // GET /v3/process/{id}/state
ProcessMetadata(id ProcessID, key string) (api.Metadata, error) // GET /v3/process/{id}/metadata/{key}
ProcessMetadataSet(id ProcessID, key string, metadata api.Metadata) error // PUT /v3/process/{id}/metadata/{key}
RTMPChannels() ([]api.RTMPChannel, error) // GET /v3/rtmp
SRTChannels() ([]api.SRTChannel, error) // GET /v3/srt
@ -96,7 +96,7 @@ type RestClient interface {
Skills() (api.Skills, error) // GET /v3/skills
SkillsReload() error // GET /v3/skills/reload
WidgetProcess(id string) (api.WidgetProcess, error) // GET /v3/widget/process/{id}
WidgetProcess(id ProcessID) (api.WidgetProcess, error) // GET /v3/widget/process/{id}
}
// Config is the configuration for a new REST API client.
@ -451,12 +451,17 @@ func (r *restclient) request(req *http.Request) (int, io.ReadCloser, error) {
return resp.StatusCode, resp.Body, nil
}
func (r *restclient) stream(method, path, contentType string, data io.Reader) (io.ReadCloser, error) {
func (r *restclient) stream(method, path string, query *url.Values, contentType string, data io.Reader) (io.ReadCloser, error) {
if err := r.checkVersion(method, r.prefix+path); err != nil {
return nil, err
}
req, err := http.NewRequest(method, r.address+r.prefix+path, data)
u := r.address + r.prefix + path
if query != nil {
u += "?" + query.Encode()
}
req, err := http.NewRequest(method, u, data)
if err != nil {
return nil, err
}
@ -516,8 +521,8 @@ func (r *restclient) stream(method, path, contentType string, data io.Reader) (i
return body, nil
}
func (r *restclient) call(method, path, contentType string, data io.Reader) ([]byte, error) {
body, err := r.stream(method, path, contentType, data)
func (r *restclient) call(method, path string, query *url.Values, contentType string, data io.Reader) ([]byte, error) {
body, err := r.stream(method, path, query, contentType, data)
if err != nil {
return nil, err
}

View File

@ -16,7 +16,7 @@ type configVersion struct {
func (r *restclient) Config() (int64, api.Config, error) {
version := configVersion{}
data, err := r.call("GET", "/v3/config", "", nil)
data, err := r.call("GET", "/v3/config", nil, "", nil)
if err != nil {
return 0, api.Config{}, err
}
@ -69,7 +69,7 @@ func (r *restclient) ConfigSet(config interface{}) error {
e := json.NewEncoder(&buf)
e.Encode(config)
_, err := r.call("PUT", "/v3/config", "application/json", &buf)
_, err := r.call("PUT", "/v3/config", nil, "application/json", &buf)
if e, ok := err.(api.Error); ok {
if e.Code == 409 {
@ -85,7 +85,7 @@ func (r *restclient) ConfigSet(config interface{}) error {
}
func (r *restclient) ConfigReload() error {
_, err := r.call("GET", "/v3/config/reload", "", nil)
_, err := r.call("GET", "/v3/config/reload", nil, "", nil)
return err
}

View File

@ -23,12 +23,12 @@ const (
func (r *restclient) FilesystemList(name, pattern, sort, order string) ([]api.FileInfo, error) {
var files []api.FileInfo
values := url.Values{}
values.Set("glob", pattern)
values.Set("sort", sort)
values.Set("order", order)
query := &url.Values{}
query.Set("glob", pattern)
query.Set("sort", sort)
query.Set("order", order)
data, err := r.call("GET", "/v3/fs/"+url.PathEscape(name)+"?"+values.Encode(), "", nil)
data, err := r.call("GET", "/v3/fs/"+url.PathEscape(name), query, "", nil)
if err != nil {
return files, err
}
@ -43,7 +43,7 @@ func (r *restclient) FilesystemHasFile(name, path string) bool {
path = "/" + path
}
_, err := r.call("HEAD", "/v3/fs/"+url.PathEscape(name)+path, "", nil)
_, err := r.call("HEAD", "/v3/fs/"+url.PathEscape(name)+path, nil, "", nil)
return err == nil
}
@ -53,7 +53,7 @@ func (r *restclient) FilesystemGetFile(name, path string) (io.ReadCloser, error)
path = "/" + path
}
return r.stream("GET", "/v3/fs/"+url.PathEscape(name)+path, "", nil)
return r.stream("GET", "/v3/fs/"+url.PathEscape(name)+path, nil, "", nil)
}
func (r *restclient) FilesystemDeleteFile(name, path string) error {
@ -61,7 +61,7 @@ func (r *restclient) FilesystemDeleteFile(name, path string) error {
path = "/" + path
}
_, err := r.call("DELETE", "/v3/fs/"+url.PathEscape(name)+path, "", nil)
_, err := r.call("DELETE", "/v3/fs/"+url.PathEscape(name)+path, nil, "", nil)
return err
}
@ -71,7 +71,7 @@ func (r *restclient) FilesystemAddFile(name, path string, data io.Reader) error
path = "/" + path
}
_, err := r.call("PUT", "/v3/fs/"+url.PathEscape(name)+path, "application/data", data)
_, err := r.call("PUT", "/v3/fs/"+url.PathEscape(name)+path, nil, "application/data", data)
return err
}

View File

@ -14,7 +14,7 @@ func (r *restclient) Graph(query api.GraphQuery) (api.GraphResponse, error) {
e := json.NewEncoder(&buf)
e.Encode(query)
data, err := r.call("PUT", "/v3/graph", "application/json", &buf)
data, err := r.call("PUT", "/v3/graph", nil, "application/json", &buf)
if err != nil {
return resp, err
}

View File

@ -2,6 +2,7 @@ package coreclient
import (
"encoding/json"
"net/url"
"github.com/datarhei/core-client-go/v16/api"
)
@ -9,7 +10,10 @@ import (
func (r *restclient) Log() ([]api.LogEvent, error) {
var log []api.LogEvent
data, err := r.call("GET", "/v3/log?format=raw", "", nil)
query := &url.Values{}
query.Set("format", "raw")
data, err := r.call("GET", "/v3/log", query, "", nil)
if err != nil {
return log, err
}

View File

@ -16,7 +16,7 @@ func (r *restclient) Metadata(key string) (api.Metadata, error) {
path += "/" + url.PathEscape(key)
}
data, err := r.call("GET", path, "", nil)
data, err := r.call("GET", path, nil, "", nil)
if err != nil {
return m, err
}
@ -37,7 +37,7 @@ func (r *restclient) MetadataSet(key string, metadata api.Metadata) error {
path += "/" + url.PathEscape(key)
}
_, err := r.call("PUT", path, "application/json", &buf)
_, err := r.call("PUT", path, nil, "application/json", &buf)
if err != nil {
return err
}

View File

@ -10,7 +10,7 @@ import (
func (r *restclient) MetricsList() ([]api.MetricsDescription, error) {
descriptions := []api.MetricsDescription{}
data, err := r.call("GET", "/v3/metrics", "application/json", nil)
data, err := r.call("GET", "/v3/metrics", nil, "application/json", nil)
if err != nil {
return descriptions, err
}
@ -27,7 +27,7 @@ func (r *restclient) Metrics(query api.MetricsQuery) (api.MetricsResponse, error
e := json.NewEncoder(&buf)
e.Encode(query)
data, err := r.call("POST", "/v3/metrics", "application/json", &buf)
data, err := r.call("POST", "/v3/metrics", nil, "application/json", &buf)
if err != nil {
return m, err
}

View File

@ -9,29 +9,60 @@ import (
"github.com/datarhei/core-client-go/v16/api"
)
type ProcessID struct {
ID string
Domain string
}
func NewProcessID(id, domain string) ProcessID {
return ProcessID{
ID: id,
Domain: domain,
}
}
func ParseProcessID(pid string) ProcessID {
i := strings.LastIndex(pid, "@")
if i == -1 {
return NewProcessID(pid, "")
}
return NewProcessID(pid[:i], pid[i+1:])
}
func ProcessIDFromProcess(p api.Process) ProcessID {
return NewProcessID(p.ID, p.Config.Domain)
}
func (p ProcessID) String() string {
return p.ID + "@" + p.Domain
}
type ProcessListOptions struct {
ID []string
Filter []string
Domain string
Reference string
IDPattern string
RefPattern string
}
func (p *ProcessListOptions) Query() string {
values := url.Values{}
func (p *ProcessListOptions) Query() *url.Values {
values := &url.Values{}
values.Set("id", strings.Join(p.ID, ","))
values.Set("filter", strings.Join(p.Filter, ","))
values.Set("domain", p.Domain)
values.Set("reference", p.Reference)
values.Set("idpattern", p.IDPattern)
values.Set("refpattern", p.RefPattern)
return values.Encode()
return values
}
func (r *restclient) ProcessList(opts ProcessListOptions) ([]api.Process, error) {
var processes []api.Process
data, err := r.call("GET", "/v3/process?"+opts.Query(), "", nil)
data, err := r.call("GET", "/v3/process", opts.Query(), "", nil)
if err != nil {
return processes, err
}
@ -41,13 +72,14 @@ func (r *restclient) ProcessList(opts ProcessListOptions) ([]api.Process, error)
return processes, err
}
func (r *restclient) Process(id string, filter []string) (api.Process, error) {
func (r *restclient) Process(id ProcessID, filter []string) (api.Process, error) {
var info api.Process
values := url.Values{}
values := &url.Values{}
values.Set("filter", strings.Join(filter, ","))
values.Set("domain", id.Domain)
data, err := r.call("GET", "/v3/process/"+url.PathEscape(id)+"?"+values.Encode(), "", nil)
data, err := r.call("GET", "/v3/process/"+url.PathEscape(id.ID), values, "", nil)
if err != nil {
return info, err
}
@ -63,7 +95,7 @@ func (r *restclient) ProcessAdd(p api.ProcessConfig) error {
e := json.NewEncoder(&buf)
e.Encode(p)
_, err := r.call("POST", "/v3/process", "application/json", &buf)
_, err := r.call("POST", "/v3/process", nil, "application/json", &buf)
if err != nil {
return err
}
@ -71,13 +103,16 @@ func (r *restclient) ProcessAdd(p api.ProcessConfig) error {
return nil
}
func (r *restclient) ProcessUpdate(id string, p api.ProcessConfig) error {
func (r *restclient) ProcessUpdate(id ProcessID, p api.ProcessConfig) error {
var buf bytes.Buffer
e := json.NewEncoder(&buf)
e.Encode(p)
_, err := r.call("PUT", "/v3/process/"+url.PathEscape(id)+"", "application/json", &buf)
query := &url.Values{}
query.Set("domain", id.Domain)
_, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID), query, "application/json", &buf)
if err != nil {
return err
}
@ -85,13 +120,16 @@ func (r *restclient) ProcessUpdate(id string, p api.ProcessConfig) error {
return nil
}
func (r *restclient) ProcessDelete(id string) error {
r.call("DELETE", "/v3/process/"+url.PathEscape(id), "", nil)
func (r *restclient) ProcessDelete(id ProcessID) error {
query := &url.Values{}
query.Set("domain", id.Domain)
r.call("DELETE", "/v3/process/"+url.PathEscape(id.ID), query, "", nil)
return nil
}
func (r *restclient) ProcessCommand(id, command string) error {
func (r *restclient) ProcessCommand(id ProcessID, command string) error {
var buf bytes.Buffer
e := json.NewEncoder(&buf)
@ -99,7 +137,10 @@ func (r *restclient) ProcessCommand(id, command string) error {
Command: command,
})
_, err := r.call("PUT", "/v3/process/"+url.PathEscape(id)+"/command", "application/json", &buf)
query := &url.Values{}
query.Set("domain", id.Domain)
_, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/command", query, "application/json", &buf)
if err != nil {
return err
}
@ -107,10 +148,13 @@ func (r *restclient) ProcessCommand(id, command string) error {
return nil
}
func (r *restclient) ProcessProbe(id string) (api.Probe, error) {
func (r *restclient) ProcessProbe(id ProcessID) (api.Probe, error) {
var p api.Probe
data, err := r.call("GET", "/v3/process/"+url.PathEscape(id)+"/probe", "", nil)
query := &url.Values{}
query.Set("domain", id.Domain)
data, err := r.call("GET", "/v3/process/"+url.PathEscape(id.ID)+"/probe", query, "", nil)
if err != nil {
return p, err
}
@ -120,10 +164,13 @@ func (r *restclient) ProcessProbe(id string) (api.Probe, error) {
return p, err
}
func (r *restclient) ProcessConfig(id string) (api.ProcessConfig, error) {
func (r *restclient) ProcessConfig(id ProcessID) (api.ProcessConfig, error) {
var p api.ProcessConfig
data, err := r.call("GET", "/v3/process/"+url.PathEscape(id)+"/config", "", nil)
query := &url.Values{}
query.Set("domain", id.Domain)
data, err := r.call("GET", "/v3/process/"+url.PathEscape(id.ID)+"/config", query, "", nil)
if err != nil {
return p, err
}
@ -133,10 +180,13 @@ func (r *restclient) ProcessConfig(id string) (api.ProcessConfig, error) {
return p, err
}
func (r *restclient) ProcessReport(id string) (api.ProcessReport, error) {
func (r *restclient) ProcessReport(id ProcessID) (api.ProcessReport, error) {
var p api.ProcessReport
data, err := r.call("GET", "/v3/process/"+url.PathEscape(id)+"/report", "", nil)
query := &url.Values{}
query.Set("domain", id.Domain)
data, err := r.call("GET", "/v3/process/"+url.PathEscape(id.ID)+"/report", query, "", nil)
if err != nil {
return p, err
}
@ -146,10 +196,13 @@ func (r *restclient) ProcessReport(id string) (api.ProcessReport, error) {
return p, err
}
func (r *restclient) ProcessState(id string) (api.ProcessState, error) {
func (r *restclient) ProcessState(id ProcessID) (api.ProcessState, error) {
var p api.ProcessState
data, err := r.call("GET", "/v3/process/"+url.PathEscape(id)+"/state", "", nil)
query := &url.Values{}
query.Set("domain", id.Domain)
data, err := r.call("GET", "/v3/process/"+url.PathEscape(id.ID)+"/state", query, "", nil)
if err != nil {
return p, err
}
@ -159,15 +212,18 @@ func (r *restclient) ProcessState(id string) (api.ProcessState, error) {
return p, err
}
func (r *restclient) ProcessMetadata(id, key string) (api.Metadata, error) {
func (r *restclient) ProcessMetadata(id ProcessID, key string) (api.Metadata, error) {
var m api.Metadata
path := "/v3/process/" + url.PathEscape(id) + "/metadata"
query := &url.Values{}
query.Set("domain", id.Domain)
path := "/v3/process/" + url.PathEscape(id.ID) + "/metadata"
if len(key) != 0 {
path += "/" + url.PathEscape(key)
}
data, err := r.call("GET", path, "", nil)
data, err := r.call("GET", path, query, "", nil)
if err != nil {
return m, err
}
@ -177,13 +233,16 @@ func (r *restclient) ProcessMetadata(id, key string) (api.Metadata, error) {
return m, err
}
func (r *restclient) ProcessMetadataSet(id, key string, metadata api.Metadata) error {
func (r *restclient) ProcessMetadataSet(id ProcessID, key string, metadata api.Metadata) error {
var buf bytes.Buffer
e := json.NewEncoder(&buf)
e.Encode(metadata)
_, err := r.call("PUT", "/v3/process/"+url.PathEscape(id)+"/metadata/"+url.PathEscape(key), "application/json", &buf)
query := &url.Values{}
query.Set("domain", id.Domain)
_, err := r.call("PUT", "/v3/process/"+url.PathEscape(id.ID)+"/metadata/"+url.PathEscape(key), query, "application/json", &buf)
if err != nil {
return err
}

View File

@ -9,7 +9,7 @@ import (
func (r *restclient) RTMPChannels() ([]api.RTMPChannel, error) {
var m []api.RTMPChannel
data, err := r.call("GET", "/v3/rtmp", "", nil)
data, err := r.call("GET", "/v3/rtmp", nil, "", nil)
if err != nil {
return m, err
}

View File

@ -11,10 +11,10 @@ import (
func (r *restclient) Sessions(collectors []string) (api.SessionsSummary, error) {
var sessions api.SessionsSummary
values := url.Values{}
values.Set("collectors", strings.Join(collectors, ","))
query := &url.Values{}
query.Set("collectors", strings.Join(collectors, ","))
data, err := r.call("GET", "/v3/sessions?"+values.Encode(), "", nil)
data, err := r.call("GET", "/v3/sessions", query, "", nil)
if err != nil {
return sessions, err
}
@ -27,10 +27,10 @@ func (r *restclient) Sessions(collectors []string) (api.SessionsSummary, error)
func (r *restclient) SessionsActive(collectors []string) (api.SessionsActive, error) {
var sessions api.SessionsActive
values := url.Values{}
values.Set("collectors", strings.Join(collectors, ","))
query := &url.Values{}
query.Set("collectors", strings.Join(collectors, ","))
data, err := r.call("GET", "/v3/sessions/active?"+values.Encode(), "", nil)
data, err := r.call("GET", "/v3/sessions/active", query, "", nil)
if err != nil {
return sessions, err
}

View File

@ -9,7 +9,7 @@ import (
func (r *restclient) Skills() (api.Skills, error) {
var skills api.Skills
data, err := r.call("GET", "/v3/skills", "", nil)
data, err := r.call("GET", "/v3/skills", nil, "", nil)
if err != nil {
return skills, err
}
@ -20,7 +20,7 @@ func (r *restclient) Skills() (api.Skills, error) {
}
func (r *restclient) SkillsReload() error {
_, err := r.call("GET", "/v3/skills/reload", "", nil)
_, err := r.call("GET", "/v3/skills/reload", nil, "", nil)
return err
}

View File

@ -9,7 +9,7 @@ import (
func (r *restclient) SRTChannels() ([]api.SRTChannel, error) {
var m []api.SRTChannel
data, err := r.call("GET", "/v3/srt", "", nil)
data, err := r.call("GET", "/v3/srt", nil, "", nil)
if err != nil {
return nil, err
}

View File

@ -7,10 +7,13 @@ import (
"github.com/datarhei/core-client-go/v16/api"
)
func (r *restclient) WidgetProcess(id string) (api.WidgetProcess, error) {
func (r *restclient) WidgetProcess(id ProcessID) (api.WidgetProcess, error) {
var w api.WidgetProcess
data, err := r.call("GET", "/v3/widget/process"+url.PathEscape(id), "", nil)
query := &url.Values{}
query.Set("domain", id.Domain)
data, err := r.call("GET", "/v3/widget/process"+url.PathEscape(id.ID), query, "", nil)
if err != nil {
return w, err
}

View File

@ -352,9 +352,9 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
// Greater asserts that the first element is greater than the second
//
// assert.Greater(t, 2, 1)
// assert.Greater(t, float64(2), float64(1))
// assert.Greater(t, "b", "a")
// assert.Greater(t, 2, 1)
// assert.Greater(t, float64(2), float64(1))
// assert.Greater(t, "b", "a")
func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -364,10 +364,10 @@ func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface
// GreaterOrEqual asserts that the first element is greater than or equal to the second
//
// assert.GreaterOrEqual(t, 2, 1)
// assert.GreaterOrEqual(t, 2, 2)
// assert.GreaterOrEqual(t, "b", "a")
// assert.GreaterOrEqual(t, "b", "b")
// assert.GreaterOrEqual(t, 2, 1)
// assert.GreaterOrEqual(t, 2, 2)
// assert.GreaterOrEqual(t, "b", "a")
// assert.GreaterOrEqual(t, "b", "b")
func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -377,9 +377,9 @@ func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...in
// Less asserts that the first element is less than the second
//
// assert.Less(t, 1, 2)
// assert.Less(t, float64(1), float64(2))
// assert.Less(t, "a", "b")
// assert.Less(t, 1, 2)
// assert.Less(t, float64(1), float64(2))
// assert.Less(t, "a", "b")
func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -389,10 +389,10 @@ func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{})
// LessOrEqual asserts that the first element is less than or equal to the second
//
// assert.LessOrEqual(t, 1, 2)
// assert.LessOrEqual(t, 2, 2)
// assert.LessOrEqual(t, "a", "b")
// assert.LessOrEqual(t, "b", "b")
// assert.LessOrEqual(t, 1, 2)
// assert.LessOrEqual(t, 2, 2)
// assert.LessOrEqual(t, "a", "b")
// assert.LessOrEqual(t, "b", "b")
func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -402,8 +402,8 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter
// Positive asserts that the specified element is positive
//
// assert.Positive(t, 1)
// assert.Positive(t, 1.23)
// assert.Positive(t, 1)
// assert.Positive(t, 1.23)
func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@ -414,8 +414,8 @@ func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
// Negative asserts that the specified element is negative
//
// assert.Negative(t, -1)
// assert.Negative(t, -1.23)
// assert.Negative(t, -1)
// assert.Negative(t, -1.23)
func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -46,36 +46,36 @@ func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareT
// IsIncreasing asserts that the collection is increasing
//
// assert.IsIncreasing(t, []int{1, 2, 3})
// assert.IsIncreasing(t, []float{1, 2})
// assert.IsIncreasing(t, []string{"a", "b"})
// assert.IsIncreasing(t, []int{1, 2, 3})
// assert.IsIncreasing(t, []float{1, 2})
// assert.IsIncreasing(t, []string{"a", "b"})
func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...)
}
// IsNonIncreasing asserts that the collection is not increasing
//
// assert.IsNonIncreasing(t, []int{2, 1, 1})
// assert.IsNonIncreasing(t, []float{2, 1})
// assert.IsNonIncreasing(t, []string{"b", "a"})
// assert.IsNonIncreasing(t, []int{2, 1, 1})
// assert.IsNonIncreasing(t, []float{2, 1})
// assert.IsNonIncreasing(t, []string{"b", "a"})
func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...)
}
// IsDecreasing asserts that the collection is decreasing
//
// assert.IsDecreasing(t, []int{2, 1, 0})
// assert.IsDecreasing(t, []float{2, 1})
// assert.IsDecreasing(t, []string{"b", "a"})
// assert.IsDecreasing(t, []int{2, 1, 0})
// assert.IsDecreasing(t, []float{2, 1})
// assert.IsDecreasing(t, []string{"b", "a"})
func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...)
}
// IsNonDecreasing asserts that the collection is not decreasing
//
// assert.IsNonDecreasing(t, []int{1, 1, 2})
// assert.IsNonDecreasing(t, []float{1, 2})
// assert.IsNonDecreasing(t, []string{"a", "b"})
// assert.IsNonDecreasing(t, []int{1, 1, 2})
// assert.IsNonDecreasing(t, []float{1, 2})
// assert.IsNonDecreasing(t, []string{"a", "b"})
func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +1,40 @@
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
//
// Example Usage
// # Example Usage
//
// The following is a complete example using assert in a standard test function:
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
//
// func TestSomething(t *testing.T) {
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
//
// var a string = "Hello"
// var b string = "Hello"
// func TestSomething(t *testing.T) {
//
// assert.Equal(t, a, b, "The two words should be the same.")
// var a string = "Hello"
// var b string = "Hello"
//
// }
// assert.Equal(t, a, b, "The two words should be the same.")
//
// }
//
// if you assert many times, use the format below:
//
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
//
// func TestSomething(t *testing.T) {
// assert := assert.New(t)
// func TestSomething(t *testing.T) {
// assert := assert.New(t)
//
// var a string = "Hello"
// var b string = "Hello"
// var a string = "Hello"
// var b string = "Hello"
//
// assert.Equal(a, b, "The two words should be the same.")
// }
// assert.Equal(a, b, "The two words should be the same.")
// }
//
// Assertions
// # Assertions
//
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
// All assertion functions take, as the first argument, the `*testing.T` object provided by the

View File

@ -23,7 +23,7 @@ func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (
// HTTPSuccess asserts that a specified handler returns a success status code.
//
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
@ -45,7 +45,7 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value
// HTTPRedirect asserts that a specified handler returns a redirect status code.
//
// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
@ -67,7 +67,7 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu
// HTTPError asserts that a specified handler returns an error status code.
//
// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
@ -89,7 +89,7 @@ func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values
// HTTPStatusCode asserts that a specified handler returns a specified status code.
//
// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501)
// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501)
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) bool {
@ -124,7 +124,7 @@ func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) s
// HTTPBodyContains asserts that a specified handler returns a
// body that contains a string.
//
// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
@ -144,7 +144,7 @@ func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string,
// HTTPBodyNotContains asserts that a specified handler returns a
// body that does not contain a string.
//
// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
//
// Returns whether the assertion was successful (true) or not (false).
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {

View File

@ -1,24 +1,25 @@
// Package require implements the same assertions as the `assert` package but
// stops test execution when a test fails.
//
// Example Usage
// # Example Usage
//
// The following is a complete example using require in a standard test function:
// import (
// "testing"
// "github.com/stretchr/testify/require"
// )
//
// func TestSomething(t *testing.T) {
// import (
// "testing"
// "github.com/stretchr/testify/require"
// )
//
// var a string = "Hello"
// var b string = "Hello"
// func TestSomething(t *testing.T) {
//
// require.Equal(t, a, b, "The two words should be the same.")
// var a string = "Hello"
// var b string = "Hello"
//
// }
// require.Equal(t, a, b, "The two words should be the same.")
//
// Assertions
// }
//
// # Assertions
//
// The `require` package have same global functions as in the `assert` package,
// but instead of returning a boolean result they call `t.FailNow()`.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

6
vendor/modules.txt vendored
View File

@ -78,7 +78,7 @@ github.com/cespare/xxhash/v2
# github.com/cpuguy83/go-md2man/v2 v2.0.2
## explicit; go 1.11
github.com/cpuguy83/go-md2man/v2/md2man
# github.com/datarhei/core-client-go/v16 v16.11.1-0.20230602102832-3d80767a2208
# github.com/datarhei/core-client-go/v16 v16.11.1-0.20230605095314-42546fbbbece
## explicit; go 1.18
github.com/datarhei/core-client-go/v16
github.com/datarhei/core-client-go/v16/api
@ -352,8 +352,8 @@ github.com/shoenig/go-m1cpu
# github.com/sirupsen/logrus v1.9.2
## explicit; go 1.13
github.com/sirupsen/logrus
# github.com/stretchr/testify v1.8.2
## explicit; go 1.13
# github.com/stretchr/testify v1.8.4
## explicit; go 1.20
github.com/stretchr/testify/assert
github.com/stretchr/testify/require
# github.com/swaggo/echo-swagger v1.4.0