api: add /info endpoint (#945) (#5045)

This commit is contained in:
Alessandro Ros
2025-09-30 10:39:27 +02:00
committed by GitHub
parent 2024783eec
commit 2690ef8e71
10 changed files with 113 additions and 33 deletions
+27
View File
@@ -21,6 +21,14 @@ components:
error:
type: string
Info:
type: object
properties:
version:
type: string
started:
type: string
AuthInternalUser:
type: object
properties:
@@ -1134,6 +1142,25 @@ components:
paths:
/v3/info:
get:
operationId: info
tags: [General]
summary: returns informations about the instance.
responses:
'200':
description: the request was successful.
content:
application/json:
schema:
$ref: '#/components/schemas/Info'
'500':
description: server error.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/v3/auth/jwks/refresh:
post:
operationId: authJwksRefresh
+1 -1
View File
@@ -1,6 +1,6 @@
# Control API
The server can be queried and controlled with an API, that can be enabled by setting the `api` parameter in the configuration:
The server can be queried and controlled with an API, that can be enabled by toggling the `api` parameter in the configuration:
```yml
api: yes
+11
View File
@@ -88,6 +88,8 @@ type apiParent interface {
// API is an API server.
type API struct {
Version string
Started time.Time
Address string
Encryption bool
ServerKey string
@@ -121,6 +123,8 @@ func (a *API) Initialize() error {
group := router.Group("/v3")
group.GET("/info", a.onInfo)
group.POST("/auth/jwks/refresh", a.onAuthJwksRefresh)
group.GET("/config/global/get", a.onConfigGlobalGet)
@@ -538,6 +542,13 @@ func (a *API) onConfigPathsDelete(ctx *gin.Context) {
ctx.Status(http.StatusOK)
}
func (a *API) onInfo(ctx *gin.Context) {
ctx.JSON(http.StatusOK, &defs.APIInfo{
Version: a.Version,
Started: a.Started,
})
}
func (a *API) onAuthJwksRefresh(ctx *gin.Context) {
a.AuthManager.RefreshJWTJWKS()
ctx.Status(http.StatusOK)
+34 -6
View File
@@ -117,6 +117,34 @@ func TestPreflightRequest(t *testing.T) {
require.Equal(t, byts, []byte{})
}
func TestInfo(t *testing.T) {
cnf := tempConf(t, "api: yes\n")
api := API{
Version: "v1.2.3",
Started: time.Date(2008, 11, 7, 11, 22, 0, 0, time.Local),
Address: "localhost:9997",
ReadTimeout: conf.Duration(10 * time.Second),
Conf: cnf,
AuthManager: test.NilAuthManager,
Parent: &testParent{},
}
err := api.Initialize()
require.NoError(t, err)
defer api.Close()
tr := &http.Transport{}
defer tr.CloseIdleConnections()
hc := &http.Client{Transport: tr}
var out map[string]interface{}
httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/info", nil, &out)
require.Equal(t, map[string]interface{}{
"started": time.Date(2008, 11, 7, 11, 22, 0, 0, time.Local).Format(time.RFC3339),
"version": "v1.2.3",
}, out)
}
func TestConfigGlobalGet(t *testing.T) {
cnf := tempConf(t, "api: yes\n")
checked := false
@@ -621,10 +649,10 @@ func TestRecordingsList(t *testing.T) {
"name": "mypath1",
"segments": []interface{}{
map[string]interface{}{
"start": time.Date(2008, 11, 0o7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
"start": time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
},
map[string]interface{}{
"start": time.Date(2009, 11, 0o7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano),
"start": time.Date(2009, 11, 7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano),
},
},
},
@@ -632,7 +660,7 @@ func TestRecordingsList(t *testing.T) {
"name": "mypath2",
"segments": []interface{}{
map[string]interface{}{
"start": time.Date(2009, 11, 0o7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano),
"start": time.Date(2009, 11, 7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano),
},
},
},
@@ -680,10 +708,10 @@ func TestRecordingsGet(t *testing.T) {
"name": "mypath1",
"segments": []interface{}{
map[string]interface{}{
"start": time.Date(2008, 11, 0o7, 11, 22, 0, 0, time.Local).Format(time.RFC3339Nano),
"start": time.Date(2008, 11, 7, 11, 22, 0, 0, time.Local).Format(time.RFC3339Nano),
},
map[string]interface{}{
"start": time.Date(2009, 11, 0o7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano),
"start": time.Date(2009, 11, 7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano),
},
},
}, out)
@@ -725,7 +753,7 @@ func TestRecordingsDeleteSegment(t *testing.T) {
v := url.Values{}
v.Set("path", "mypath1")
v.Set("start", time.Date(2008, 11, 0o7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano))
v.Set("start", time.Date(2008, 11, 7, 11, 22, 0, 900000000, time.Local).Format(time.RFC3339Nano))
u.RawQuery = v.Encode()
req, err := http.NewRequest(http.MethodDelete, u.String(), nil)
+4
View File
@@ -41,6 +41,8 @@ import (
//go:embed VERSION
var version []byte
var started = time.Now()
var defaultConfPaths = []string{
"rtsp-simple-server.yml",
"mediamtx.yml",
@@ -615,6 +617,8 @@ func (p *Core) createResources(initial bool) error {
if p.conf.API &&
p.api == nil {
i := &api.API{
Version: string(version),
Started: started,
Address: p.conf.APIAddress,
Encryption: p.conf.APIEncryption,
ServerKey: p.conf.APIServerKey,
+6
View File
@@ -55,6 +55,12 @@ type APIError struct {
Error string `json:"error"`
}
// APIInfo is a info response.
type APIInfo struct {
Version string `json:"version"`
Started time.Time `json:"started"`
}
// APIPathConfList is a list of path configurations.
type APIPathConfList struct {
ItemCount int `json:"itemCount"`
+3 -3
View File
@@ -264,7 +264,7 @@ func TestOnGet(t *testing.T) {
v := url.Values{}
v.Set("path", "mypath")
v.Set("start", time.Date(2008, 11, 0o7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano))
v.Set("start", time.Date(2008, 11, 7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano))
v.Set("duration", "3")
v.Set("format", format)
u.RawQuery = v.Encode()
@@ -488,7 +488,7 @@ func TestOnGetDifferentInit(t *testing.T) {
v := url.Values{}
v.Set("path", "mypath")
v.Set("start", time.Date(2008, 11, 0o7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano))
v.Set("start", time.Date(2008, 11, 7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano))
v.Set("duration", "2")
v.Set("format", "fmp4")
u.RawQuery = v.Encode()
@@ -566,7 +566,7 @@ func TestOnGetNTPCompensation(t *testing.T) {
v := url.Values{}
v.Set("path", "mypath")
v.Set("start", time.Date(2008, 11, 0o7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano))
v.Set("start", time.Date(2008, 11, 7, 11, 23, 1, 500000000, time.Local).Format(time.RFC3339Nano))
v.Set("duration", "3")
v.Set("format", "fmp4")
u.RawQuery = v.Encode()
+22 -22
View File
@@ -91,18 +91,18 @@ func TestOnList(t *testing.T) {
switch ca {
case "filtered":
v.Set("start", time.Date(2008, 11, 0o7, 11, 22, 1, 500000000, time.Local).Format(time.RFC3339Nano))
v.Set("end", time.Date(2009, 11, 0o7, 11, 23, 4, 500000000, time.Local).Format(time.RFC3339Nano))
v.Set("start", time.Date(2008, 11, 7, 11, 22, 1, 500000000, time.Local).Format(time.RFC3339Nano))
v.Set("end", time.Date(2009, 11, 7, 11, 23, 4, 500000000, time.Local).Format(time.RFC3339Nano))
case "filtered and gap":
v.Set("start", time.Date(2008, 11, 0o7, 11, 23, 20, 500000000, time.Local).Format(time.RFC3339Nano))
v.Set("end", time.Date(2009, 11, 0o7, 11, 23, 4, 500000000, time.Local).Format(time.RFC3339Nano))
v.Set("start", time.Date(2008, 11, 7, 11, 23, 20, 500000000, time.Local).Format(time.RFC3339Nano))
v.Set("end", time.Date(2009, 11, 7, 11, 23, 4, 500000000, time.Local).Format(time.RFC3339Nano))
case "start after duration":
v.Set("start", time.Date(2010, 11, 0o7, 11, 23, 20, 500000000, time.Local).Format(time.RFC3339Nano))
v.Set("start", time.Date(2010, 11, 7, 11, 23, 20, 500000000, time.Local).Format(time.RFC3339Nano))
case "start before first":
v.Set("start", time.Date(2007, 11, 0o7, 11, 23, 20, 500000000, time.Local).Format(time.RFC3339Nano))
v.Set("start", time.Date(2007, 11, 7, 11, 23, 20, 500000000, time.Local).Format(time.RFC3339Nano))
}
u.RawQuery = v.Encode()
@@ -130,15 +130,15 @@ func TestOnList(t *testing.T) {
require.Equal(t, []interface{}{
map[string]interface{}{
"duration": float64(66),
"start": time.Date(2008, 11, 0o7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
"start": time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
"url": "http://localhost:9996/get?duration=66&path=mypath&start=" +
url.QueryEscape(time.Date(2008, 11, 0o7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano)),
url.QueryEscape(time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano)),
},
map[string]interface{}{
"duration": float64(4),
"start": time.Date(2009, 11, 0o7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano),
"start": time.Date(2009, 11, 7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano),
"url": "http://localhost:9996/get?duration=4&path=mypath&start=" +
url.QueryEscape(time.Date(2009, 11, 0o7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
url.QueryEscape(time.Date(2009, 11, 7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
},
}, out)
@@ -146,15 +146,15 @@ func TestOnList(t *testing.T) {
require.Equal(t, []interface{}{
map[string]interface{}{
"duration": float64(65),
"start": time.Date(2008, 11, 0o7, 11, 22, 1, 500000000, time.Local).Format(time.RFC3339Nano),
"start": time.Date(2008, 11, 7, 11, 22, 1, 500000000, time.Local).Format(time.RFC3339Nano),
"url": "http://localhost:9996/get?duration=65&path=mypath&start=" +
url.QueryEscape(time.Date(2008, 11, 0o7, 11, 22, 1, 500000000, time.Local).Format(time.RFC3339Nano)),
url.QueryEscape(time.Date(2008, 11, 7, 11, 22, 1, 500000000, time.Local).Format(time.RFC3339Nano)),
},
map[string]interface{}{
"duration": float64(2),
"start": time.Date(2009, 11, 0o7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano),
"start": time.Date(2009, 11, 7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano),
"url": "http://localhost:9996/get?duration=2&path=mypath&start=" +
url.QueryEscape(time.Date(2009, 11, 0o7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
url.QueryEscape(time.Date(2009, 11, 7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
},
}, out)
@@ -162,9 +162,9 @@ func TestOnList(t *testing.T) {
require.Equal(t, []interface{}{
map[string]interface{}{
"duration": float64(4),
"start": time.Date(2008, 11, 0o7, 11, 24, 2, 500000000, time.Local).Format(time.RFC3339Nano),
"start": time.Date(2008, 11, 7, 11, 24, 2, 500000000, time.Local).Format(time.RFC3339Nano),
"url": "http://localhost:9996/get?duration=4&path=mypath&start=" +
url.QueryEscape(time.Date(2008, 11, 0o7, 11, 24, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
url.QueryEscape(time.Date(2008, 11, 7, 11, 24, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
},
}, out)
@@ -172,15 +172,15 @@ func TestOnList(t *testing.T) {
require.Equal(t, []interface{}{
map[string]interface{}{
"duration": float64(62),
"start": time.Date(2008, 11, 0o7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
"start": time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
"url": "http://localhost:9996/get?duration=62&path=mypath&start=" +
url.QueryEscape(time.Date(2008, 11, 0o7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano)),
url.QueryEscape(time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano)),
},
map[string]interface{}{
"duration": float64(1),
"start": time.Date(2008, 11, 0o7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano),
"start": time.Date(2008, 11, 7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano),
"url": "http://localhost:9996/get?duration=1&path=mypath&start=" +
url.QueryEscape(time.Date(2008, 11, 0o7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
url.QueryEscape(time.Date(2008, 11, 7, 11, 23, 2, 500000000, time.Local).Format(time.RFC3339Nano)),
},
}, out)
}
@@ -327,9 +327,9 @@ func TestOnListCachedDuration(t *testing.T) {
require.Equal(t, []interface{}{
map[string]interface{}{
"duration": float64(50),
"start": time.Date(2008, 11, 0o7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
"start": time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano),
"url": "http://localhost:9996/get?duration=50&path=mypath&start=" +
url.QueryEscape(time.Date(2008, 11, 0o7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano)),
url.QueryEscape(time.Date(2008, 11, 7, 11, 22, 0, 500000000, time.Local).Format(time.RFC3339Nano)),
},
}, out)
}
+1 -1
View File
@@ -17,7 +17,7 @@ var pathCases = []struct {
"standard",
"%path/%Y-%m-%d_%H-%M-%S-%f.mp4",
Path{
Start: time.Date(2008, 11, 0o7, 11, 22, 4, 123456000, time.Local),
Start: time.Date(2008, 11, 7, 11, 22, 4, 123456000, time.Local),
Path: "mypath",
},
"mypath/2008-11-07_11-22-04-123456.mp4",
+4
View File
@@ -44,6 +44,10 @@ func TestAPIDocs(t *testing.T) {
openAPIKey string
goStruct any
}{
{
"Info",
defs.APIInfo{},
},
{
"AuthInternalUser",
conf.AuthInternalUser{},