mirror of
https://github.com/datarhei/core.git
synced 2025-09-26 20:11:29 +08:00
Fix proper version handling for uploading a new config
This commit is contained in:
@@ -4,8 +4,16 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/datarhei/core/v16/config"
|
||||
v1config "github.com/datarhei/core/v16/config/v1"
|
||||
v2config "github.com/datarhei/core/v16/config/v2"
|
||||
)
|
||||
|
||||
// ConfigVersion is used to only unmarshal the version field in order
|
||||
// find out which SetConfig should be used.
|
||||
type ConfigVersion struct {
|
||||
Version int64 `json:"version"`
|
||||
}
|
||||
|
||||
// ConfigData embeds config.Data
|
||||
type ConfigData struct {
|
||||
config.Data
|
||||
@@ -22,11 +30,68 @@ type Config struct {
|
||||
Overrides []string `json:"overrides"`
|
||||
}
|
||||
|
||||
type SetConfigV1 struct {
|
||||
v1config.Data
|
||||
}
|
||||
|
||||
// NewSetConfigV1 creates a new SetConfigV1 based on the current
|
||||
// config with downgrading.
|
||||
func NewSetConfigV1(cfg *config.Config) SetConfigV1 {
|
||||
v2data, _ := config.DowngradeV3toV2(&cfg.Data)
|
||||
v1data, _ := v2config.DowngradeV2toV1(v2data)
|
||||
|
||||
data := SetConfigV1{
|
||||
Data: *v1data,
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// MergeTo merges the v1 config into the current config.
|
||||
func (s *SetConfigV1) MergeTo(cfg *config.Config) {
|
||||
v2data, _ := config.DowngradeV3toV2(&cfg.Data)
|
||||
|
||||
v2config.MergeV1ToV2(v2data, &s.Data)
|
||||
config.MergeV2toV3(&cfg.Data, v2data)
|
||||
}
|
||||
|
||||
type SetConfigV2 struct {
|
||||
v2config.Data
|
||||
}
|
||||
|
||||
// NewSetConfigV2 creates a new SetConfigV2 based on the current
|
||||
// config with downgrading.
|
||||
func NewSetConfigV2(cfg *config.Config) SetConfigV2 {
|
||||
v2data, _ := config.DowngradeV3toV2(&cfg.Data)
|
||||
|
||||
data := SetConfigV2{
|
||||
Data: *v2data,
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// MergeTo merges the v2 config into the current config.
|
||||
func (s *SetConfigV2) MergeTo(cfg *config.Config) {
|
||||
config.MergeV2toV3(&cfg.Data, &s.Data)
|
||||
}
|
||||
|
||||
// SetConfig embeds config.Data. It is used to send a new config to the server.
|
||||
type SetConfig struct {
|
||||
config.Data
|
||||
}
|
||||
|
||||
// NewSetConfig converts a config.Config into a SetConfig in order to prepopulate
|
||||
// a SetConfig with the current values. The uploaded config can have missing fields that
|
||||
// will be filled with the current values after unmarshalling the JSON.
|
||||
func NewSetConfig(cfg *config.Config) SetConfig {
|
||||
data := SetConfig{
|
||||
cfg.Data,
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// MergeTo merges a sent config into a config.Config
|
||||
func (rscfg *SetConfig) MergeTo(cfg *config.Config) {
|
||||
cfg.ID = rscfg.ID
|
||||
@@ -51,18 +116,7 @@ func (rscfg *SetConfig) MergeTo(cfg *config.Config) {
|
||||
cfg.Router = rscfg.Router
|
||||
}
|
||||
|
||||
// NewSetConfig converts a config.Config into a RestreamerSetConfig in order to prepopulate
|
||||
// a RestreamerSetConfig with the current values. The uploaded config can have missing fields that
|
||||
// will be filled with the current values after unmarshalling the JSON.
|
||||
func NewSetConfig(cfg *config.Config) SetConfig {
|
||||
data := SetConfig{
|
||||
cfg.Data,
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Unmarshal converts a config.Config to a RestreamerConfig.
|
||||
// Unmarshal converts a config.Config to a Config.
|
||||
func (c *Config) Unmarshal(cfg *config.Config) {
|
||||
if cfg == nil {
|
||||
return
|
||||
|
@@ -1,11 +1,13 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/datarhei/core/v16/config"
|
||||
cfgstore "github.com/datarhei/core/v16/config/store"
|
||||
cfgvars "github.com/datarhei/core/v16/config/vars"
|
||||
"github.com/datarhei/core/v16/encoding/json"
|
||||
"github.com/datarhei/core/v16/http/api"
|
||||
"github.com/datarhei/core/v16/http/handler/util"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
@@ -13,11 +15,11 @@ import (
|
||||
// The ConfigHandler type provides handler functions for reading and manipulating
|
||||
// the current config.
|
||||
type ConfigHandler struct {
|
||||
store config.Store
|
||||
store cfgstore.Store
|
||||
}
|
||||
|
||||
// NewConfig return a new Config type. You have to provide a valid config store.
|
||||
func NewConfig(store config.Store) *ConfigHandler {
|
||||
func NewConfig(store cfgstore.Store) *ConfigHandler {
|
||||
return &ConfigHandler{
|
||||
store: store,
|
||||
}
|
||||
@@ -53,25 +55,73 @@ func (p *ConfigHandler) Get(c echo.Context) error {
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v3/config [put]
|
||||
func (p *ConfigHandler) Set(c echo.Context) error {
|
||||
cfg := p.store.Get()
|
||||
version := api.ConfigVersion{}
|
||||
|
||||
// Set the current config as default config value. This will
|
||||
// allow to set a partial config without destroying the other
|
||||
// values.
|
||||
setConfig := api.NewSetConfig(cfg)
|
||||
req := c.Request()
|
||||
|
||||
if err := util.ShouldBindJSON(c, &setConfig); err != nil {
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", err)
|
||||
}
|
||||
|
||||
// Merge it into the current config
|
||||
setConfig.MergeTo(cfg)
|
||||
if err := json.Unmarshal(body, &version); err != nil {
|
||||
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", json.FormatError(body, err))
|
||||
}
|
||||
|
||||
cfg := p.store.Get()
|
||||
|
||||
// For each version, set the current config as default config value. This will
|
||||
// allow to set a partial config without destroying the other values.
|
||||
if version.Version == 1 {
|
||||
// Downgrade to v1 in order to have a populated v1 config
|
||||
v1SetConfig := api.NewSetConfigV1(cfg)
|
||||
|
||||
if err := json.Unmarshal(body, &v1SetConfig); err != nil {
|
||||
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", json.FormatError(body, err))
|
||||
}
|
||||
|
||||
if err := c.Validate(v1SetConfig); err != nil {
|
||||
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", err)
|
||||
}
|
||||
|
||||
// Merge it into the current config
|
||||
v1SetConfig.MergeTo(cfg)
|
||||
} else if version.Version == 2 {
|
||||
// Downgrade to v2 in order to have a populated v2 config
|
||||
v2SetConfig := api.NewSetConfigV2(cfg)
|
||||
|
||||
if err := json.Unmarshal(body, &v2SetConfig); err != nil {
|
||||
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", json.FormatError(body, err))
|
||||
}
|
||||
|
||||
if err := c.Validate(v2SetConfig); err != nil {
|
||||
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", err)
|
||||
}
|
||||
|
||||
// Merge it into the current config
|
||||
v2SetConfig.MergeTo(cfg)
|
||||
} else if version.Version == 3 {
|
||||
v3SetConfig := api.NewSetConfig(cfg)
|
||||
|
||||
if err := json.Unmarshal(body, &v3SetConfig); err != nil {
|
||||
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", json.FormatError(body, err))
|
||||
}
|
||||
|
||||
if err := c.Validate(v3SetConfig); err != nil {
|
||||
return api.Err(http.StatusBadRequest, "Invalid JSON", "%s", err)
|
||||
}
|
||||
|
||||
// Merge it into the current config
|
||||
v3SetConfig.MergeTo(cfg)
|
||||
} else {
|
||||
return api.Err(http.StatusBadRequest, "Invalid config version", "version %d", version.Version)
|
||||
}
|
||||
|
||||
// Now we make a copy from the config and merge it with the environment
|
||||
// variables. If this configuration is valid, we will store the un-merged
|
||||
// one to disk.
|
||||
|
||||
mergedConfig := config.NewConfigFrom(cfg)
|
||||
mergedConfig := cfg.Clone()
|
||||
mergedConfig.Merge()
|
||||
|
||||
// Validate the new merged config
|
||||
@@ -79,7 +129,7 @@ func (p *ConfigHandler) Set(c echo.Context) error {
|
||||
if mergedConfig.HasErrors() {
|
||||
errors := make(map[string][]string)
|
||||
|
||||
mergedConfig.Messages(func(level string, v config.Variable, message string) {
|
||||
mergedConfig.Messages(func(level string, v cfgvars.Variable, message string) {
|
||||
if level != "error" {
|
||||
return
|
||||
}
|
||||
|
@@ -7,25 +7,28 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/datarhei/core/v16/config"
|
||||
"github.com/datarhei/core/v16/config/store"
|
||||
v1 "github.com/datarhei/core/v16/config/v1"
|
||||
"github.com/datarhei/core/v16/http/mock"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func getDummyConfigRouter() *echo.Echo {
|
||||
func getDummyConfigRouter() (*echo.Echo, store.Store) {
|
||||
router := mock.DummyEcho()
|
||||
|
||||
config := config.NewDummyStore()
|
||||
config := store.NewDummy()
|
||||
|
||||
handler := NewConfig(config)
|
||||
|
||||
router.Add("GET", "/", handler.Get)
|
||||
router.Add("PUT", "/", handler.Set)
|
||||
|
||||
return router
|
||||
return router, config
|
||||
}
|
||||
|
||||
func TestConfigGet(t *testing.T) {
|
||||
router := getDummyConfigRouter()
|
||||
router, _ := getDummyConfigRouter()
|
||||
|
||||
mock.Request(t, http.StatusOK, router, "GET", "/", nil)
|
||||
|
||||
@@ -33,7 +36,7 @@ func TestConfigGet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConfigSetConflict(t *testing.T) {
|
||||
router := getDummyConfigRouter()
|
||||
router, _ := getDummyConfigRouter()
|
||||
|
||||
var data bytes.Buffer
|
||||
|
||||
@@ -44,18 +47,86 @@ func TestConfigSetConflict(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConfigSet(t *testing.T) {
|
||||
router := getDummyConfigRouter()
|
||||
router, store := getDummyConfigRouter()
|
||||
|
||||
storedcfg := store.Get()
|
||||
|
||||
require.Equal(t, []string{}, storedcfg.Host.Name)
|
||||
|
||||
var data bytes.Buffer
|
||||
encoder := json.NewEncoder(&data)
|
||||
|
||||
// Setting a new v3 config
|
||||
cfg := config.New()
|
||||
cfg.FFmpeg.Binary = "true"
|
||||
cfg.DB.Dir = "."
|
||||
cfg.Storage.Disk.Dir = "."
|
||||
cfg.Storage.MimeTypes = ""
|
||||
cfg.Storage.Disk.Cache.Types.Allow = []string{".aaa"}
|
||||
cfg.Storage.Disk.Cache.Types.Block = []string{".zzz"}
|
||||
cfg.Host.Name = []string{"foobar.com"}
|
||||
|
||||
encoder := json.NewEncoder(&data)
|
||||
encoder.Encode(cfg)
|
||||
|
||||
mock.Request(t, http.StatusOK, router, "PUT", "/", &data)
|
||||
|
||||
storedcfg = store.Get()
|
||||
|
||||
require.Equal(t, []string{"foobar.com"}, storedcfg.Host.Name)
|
||||
require.Equal(t, []string{".aaa"}, cfg.Storage.Disk.Cache.Types.Allow)
|
||||
require.Equal(t, []string{".zzz"}, cfg.Storage.Disk.Cache.Types.Block)
|
||||
require.Equal(t, "cert@datarhei.com", cfg.TLS.Email)
|
||||
|
||||
// Setting a complete v1 config
|
||||
cfgv1 := v1.New()
|
||||
cfgv1.FFmpeg.Binary = "true"
|
||||
cfgv1.DB.Dir = "."
|
||||
cfgv1.Storage.Disk.Dir = "."
|
||||
cfgv1.Storage.MimeTypes = ""
|
||||
cfgv1.Storage.Disk.Cache.Types = []string{".bbb"}
|
||||
cfgv1.Host.Name = []string{"foobar.com"}
|
||||
|
||||
data.Reset()
|
||||
|
||||
encoder.Encode(cfgv1)
|
||||
|
||||
mock.Request(t, http.StatusOK, router, "PUT", "/", &data)
|
||||
|
||||
storedcfg = store.Get()
|
||||
|
||||
require.Equal(t, []string{"foobar.com"}, storedcfg.Host.Name)
|
||||
require.Equal(t, []string{".bbb"}, storedcfg.Storage.Disk.Cache.Types.Allow)
|
||||
require.Equal(t, []string{".zzz"}, storedcfg.Storage.Disk.Cache.Types.Block)
|
||||
require.Equal(t, "cert@datarhei.com", cfg.TLS.Email)
|
||||
|
||||
// Setting a partial v1 config
|
||||
type customconfig struct {
|
||||
Version int `json:"version"`
|
||||
Storage struct {
|
||||
Disk struct {
|
||||
Cache struct {
|
||||
Types []string `json:"types"`
|
||||
} `json:"cache"`
|
||||
} `json:"disk"`
|
||||
} `json:"storage"`
|
||||
}
|
||||
|
||||
customcfg := customconfig{
|
||||
Version: 1,
|
||||
}
|
||||
|
||||
customcfg.Storage.Disk.Cache.Types = []string{".ccc"}
|
||||
|
||||
data.Reset()
|
||||
|
||||
encoder.Encode(customcfg)
|
||||
|
||||
mock.Request(t, http.StatusOK, router, "PUT", "/", &data)
|
||||
|
||||
storedcfg = store.Get()
|
||||
|
||||
require.Equal(t, []string{"foobar.com"}, storedcfg.Host.Name)
|
||||
require.Equal(t, []string{".ccc"}, storedcfg.Storage.Disk.Cache.Types.Allow)
|
||||
require.Equal(t, []string{".zzz"}, storedcfg.Storage.Disk.Cache.Types.Block)
|
||||
require.Equal(t, "cert@datarhei.com", cfg.TLS.Email)
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/datarhei/core/v16/config"
|
||||
cfgstore "github.com/datarhei/core/v16/config/store"
|
||||
"github.com/datarhei/core/v16/http/cache"
|
||||
"github.com/datarhei/core/v16/http/errorhandler"
|
||||
"github.com/datarhei/core/v16/http/graph/resolver"
|
||||
@@ -87,7 +87,7 @@ type Config struct {
|
||||
RTMP rtmp.Server
|
||||
SRT srt.Server
|
||||
JWT jwt.JWT
|
||||
Config config.Store
|
||||
Config cfgstore.Store
|
||||
Cache cache.Cacher
|
||||
Sessions session.RegistryReader
|
||||
Router router.Router
|
||||
|
Reference in New Issue
Block a user