chore: config fixes

- remove example config file
- don't set default values on read
- omit empty fields
- indent yaml 2 spaces
This commit is contained in:
Rob Watson 2025-04-01 21:06:31 +02:00
parent ba356137c3
commit 6952516204
7 changed files with 36 additions and 84 deletions

View File

@ -9,13 +9,13 @@ type Destination struct {
// LogFile holds the configuration for the log file.
type LogFile struct {
Enabled bool `yaml:"enabled"`
Path string `yaml:"path"`
Path string `yaml:"path,omitempty"`
}
// RTMPSource holds the configuration for the RTMP source.
type RTMPSource struct {
Enabled bool `yaml:"enabled"`
StreamKey string `yaml:"streamkey"`
StreamKey string `yaml:"streamkey,omitempty"`
}
// Sources holds the configuration for the sources.

View File

@ -1,24 +0,0 @@
# Octoplex is a live stream multiplexer.
---
#
sources:
# Currently the only source type is RTMP server.
rtmp:
enabled: yes
# Your local stream key. Defaults to "live".
#
# rtmp://localhost:1935/live
streamkey: live
#
logfile:
# Change to yes to log to system location.
enabled: no
# Or, log to this absolute path:
# path: octoplex.log
#
# Define your destinations here.
destinations:
# - name: YouTube
# url: rtmp://rtmp.youtube.com/myYoutubeStreamKey
# - name: Twitch
# url: rtmp://ingest.global-contribute.live-video.net/app/myTwitchStreamKey

View File

@ -1,6 +1,7 @@
package config
import (
"bytes"
_ "embed"
"errors"
"fmt"
@ -8,13 +9,9 @@ import (
"path/filepath"
"strings"
"git.netflux.io/rob/octoplex/internal/domain"
"gopkg.in/yaml.v3"
)
//go:embed data/config.example.yml
var exampleConfig []byte
// Service provides configuration services.
type Service struct {
current Config
@ -100,7 +97,7 @@ func (s *Service) SetConfig(cfg Config) error {
return fmt.Errorf("validate: %w", err)
}
cfgBytes, err := yaml.Marshal(cfg)
cfgBytes, err := marshalConfig(cfg)
if err != nil {
return fmt.Errorf("marshal: %w", err)
}
@ -130,8 +127,6 @@ func (s *Service) readConfig() (cfg Config, _ error) {
return cfg, fmt.Errorf("unmarshal: %w", err)
}
s.setDefaults(&cfg)
if err = validate(cfg); err != nil {
return cfg, err
}
@ -143,17 +138,31 @@ func (s *Service) readConfig() (cfg Config, _ error) {
func (s *Service) writeDefaultConfig() (Config, error) {
var cfg Config
if err := yaml.Unmarshal(exampleConfig, &cfg); err != nil {
return cfg, fmt.Errorf("unmarshal: %w", err)
s.setDefaults(&cfg)
cfgBytes, err := marshalConfig(cfg)
if err != nil {
return cfg, fmt.Errorf("marshal: %w", err)
}
if err := s.writeConfig(exampleConfig); err != nil {
if err := s.writeConfig(cfgBytes); err != nil {
return Config{}, fmt.Errorf("write config: %w", err)
}
return cfg, nil
}
func marshalConfig(cfg Config) ([]byte, error) {
var buf bytes.Buffer
enc := yaml.NewEncoder(&buf)
enc.SetIndent(2)
if err := enc.Encode(cfg); err != nil {
return nil, fmt.Errorf("encode: %w", err)
}
return buf.Bytes(), nil
}
func (s *Service) writeConfig(cfgBytes []byte) error {
if err := os.MkdirAll(s.appConfigDir, 0744); err != nil {
return fmt.Errorf("mkdir: %w", err)
@ -166,18 +175,10 @@ func (s *Service) writeConfig(cfgBytes []byte) error {
return nil
}
// setDefaults is called to set default values for a new, empty configuration.
func (s *Service) setDefaults(cfg *Config) {
if cfg.LogFile.Enabled && cfg.LogFile.Path == "" {
cfg.LogFile.Path = filepath.Join(s.appStateDir, domain.AppName+".log")
}
cfg.Sources.RTMP.Enabled = true
for i := range cfg.Destinations {
if strings.TrimSpace(cfg.Destinations[i].Name) == "" {
cfg.Destinations[i].Name = fmt.Sprintf("Stream %d", i+1)
}
}
cfg.Sources.RTMP.StreamKey = "live"
}
// TODO: validate URL format

View File

@ -4,27 +4,21 @@ import (
_ "embed"
"os"
"path/filepath"
"strings"
"testing"
"git.netflux.io/rob/octoplex/internal/config"
"git.netflux.io/rob/octoplex/internal/shortid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
//go:embed testdata/complete.yml
var configComplete []byte
//go:embed testdata/no-logfile.yml
var configNoLogfile []byte
//go:embed testdata/logfile.yml
var configLogfile []byte
//go:embed testdata/no-name.yml
var configNoName []byte
//go:embed testdata/invalid-destination-url.yml
var configInvalidDestinationURL []byte
@ -60,8 +54,10 @@ func TestConfigServiceCreateConfig(t *testing.T) {
p := filepath.Join(systemConfigDir, "octoplex", "config.yaml")
cfgBytes, err := os.ReadFile(p)
require.NoError(t, err, "config file was not created")
// Ensure the example config file is written:
assert.Contains(t, string(cfgBytes), "# Octoplex is a live stream multiplexer.")
var readCfg config.Config
require.NoError(t, yaml.Unmarshal(cfgBytes, &readCfg))
assert.True(t, readCfg.Sources.RTMP.Enabled, "default values not set")
}
func TestConfigServiceReadConfig(t *testing.T) {
@ -97,13 +93,6 @@ func TestConfigServiceReadConfig(t *testing.T) {
}, cfg)
},
},
{
name: "logging enabled, no logfile",
configBytes: configNoLogfile,
want: func(t *testing.T, cfg config.Config) {
assert.True(t, strings.HasSuffix(cfg.LogFile.Path, "/octoplex/octoplex.log"))
},
},
{
name: "logging enabled, logfile",
configBytes: configLogfile,
@ -111,13 +100,6 @@ func TestConfigServiceReadConfig(t *testing.T) {
assert.Equal(t, "/tmp/octoplex.log", cfg.LogFile.Path)
},
},
{
name: "no name",
configBytes: configNoName,
want: func(t *testing.T, cfg config.Config) {
assert.Equal(t, "Stream 1", cfg.Destinations[0].Name)
},
},
{
name: "invalid destination URL",
configBytes: configInvalidDestinationURL,

View File

@ -1,5 +0,0 @@
---
logfile:
enabled: true
destinations:
- url: rtmp://rtmp.example.com:1935/live

View File

@ -1,6 +0,0 @@
---
logfile:
enabled: true
path: test.log
destinations:
- url: rtmp://rtmp.example.com:1935/live

12
main.go
View File

@ -49,7 +49,7 @@ func run(ctx context.Context) error {
} else if narg == 1 {
switch flag.Arg(0) {
case "edit-config":
return editConfigFile(configService.Path())
return editConfigFile(configService)
case "print-config":
return printConfigPath(configService.Path())
case "version":
@ -108,7 +108,11 @@ func run(ctx context.Context) error {
}
// editConfigFile opens the config file in the user's editor.
func editConfigFile(configPath string) error {
func editConfigFile(configService *config.Service) error {
if _, err := configService.ReadOrCreateConfig(); err != nil {
return fmt.Errorf("read or create config: %w", err)
}
editor := os.Getenv("EDITOR")
if editor == "" {
editor = "vi"
@ -118,10 +122,10 @@ func editConfigFile(configPath string) error {
return fmt.Errorf("look path: %w", err)
}
fmt.Fprintf(os.Stderr, "Editing config file: %s\n", configPath)
fmt.Fprintf(os.Stderr, "Editing config file: %s\n", configService.Path())
fmt.Println(binary)
if err := syscall.Exec(binary, []string{"--", configPath}, os.Environ()); err != nil {
if err := syscall.Exec(binary, []string{"--", configService.Path()}, os.Environ()); err != nil {
return fmt.Errorf("exec: %w", err)
}