Sybren A. Stüvel
24344e9632
The return value of `FileConfigWrangler.SetManagerURL()` was never used, so now the function doesn't return anything any more.
231 lines
6.0 KiB
Go
231 lines
6.0 KiB
Go
package worker
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
yaml "gopkg.in/yaml.v2"
|
|
)
|
|
|
|
var (
|
|
errURLWithoutHostName = errors.New("manager URL should contain a host name")
|
|
)
|
|
|
|
const (
|
|
credentialsFilename = "flamenco-worker-credentials.yaml"
|
|
configFilename = "flamenco-worker.yaml"
|
|
)
|
|
|
|
var defaultConfig = WorkerConfig{
|
|
ConfiguredManager: "", // Auto-detect by default.
|
|
TaskTypes: []string{"blender", "ffmpeg", "file-management", "misc"},
|
|
}
|
|
|
|
// WorkerConfig represents the configuration of a single worker.
|
|
// It does not include authentication credentials.
|
|
type WorkerConfig struct {
|
|
// ConfiguredManager is the Manager URL that's in the configuration file.
|
|
ConfiguredManager string `yaml:"manager_url"`
|
|
|
|
// ManagerURL is the Manager URL to use by the Worker. It could come from the
|
|
// configuration file, but also from autodiscovery via UPnP/SSDP.
|
|
ManagerURL string `yaml:"-"`
|
|
|
|
TaskTypes []string `yaml:"task_types"`
|
|
}
|
|
|
|
type WorkerCredentials struct {
|
|
WorkerID string `yaml:"worker_id"`
|
|
Secret string `yaml:"worker_secret"`
|
|
}
|
|
|
|
// FileConfigWrangler is the default config wrangler that actually reads & writes files.
|
|
type FileConfigWrangler struct {
|
|
// In-memory copy of the worker configuration.
|
|
wc *WorkerConfig
|
|
creds *WorkerCredentials
|
|
}
|
|
|
|
// NewConfigWrangler returns ConfigWrangler that reads files.
|
|
func NewConfigWrangler() FileConfigWrangler {
|
|
return FileConfigWrangler{}
|
|
}
|
|
|
|
// WorkerConfig returns the worker configuration, or the default config if
|
|
// there is no config file. Configuration is only loaded from disk once;
|
|
// subsequent calls return the same config.
|
|
func (fcw *FileConfigWrangler) WorkerConfig() (WorkerConfig, error) {
|
|
if fcw.wc != nil {
|
|
return *fcw.wc, nil
|
|
}
|
|
|
|
wc := fcw.DefaultConfig()
|
|
err := fcw.loadConfig(configFilename, &wc)
|
|
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, fs.ErrNotExist):
|
|
// The config file not existing is fine; just use the defaults.
|
|
case errors.Is(err, io.EOF):
|
|
// The config file exists but is empty; treat as non-existent.
|
|
default:
|
|
return wc, err
|
|
}
|
|
}
|
|
|
|
fcw.wc = &wc
|
|
|
|
man := strings.TrimSpace(wc.ConfiguredManager)
|
|
if man != "" {
|
|
fcw.SetManagerURL(man)
|
|
}
|
|
|
|
return wc, nil
|
|
}
|
|
|
|
func (fcw *FileConfigWrangler) SaveConfig() error {
|
|
err := fcw.writeConfig(configFilename, "Configuration", fcw.wc)
|
|
if err != nil {
|
|
return fmt.Errorf("writing to %s: %w", configFilename, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (fcw *FileConfigWrangler) WorkerCredentials() (WorkerCredentials, error) {
|
|
var creds WorkerCredentials
|
|
err := fcw.loadConfig(credentialsFilename, &creds)
|
|
if err != nil {
|
|
return WorkerCredentials{}, err
|
|
}
|
|
|
|
log.Info().
|
|
Str("filename", credentialsFilename).
|
|
Msg("loaded credentials")
|
|
return creds, nil
|
|
}
|
|
|
|
func (fcw *FileConfigWrangler) SaveCredentials(creds WorkerCredentials) error {
|
|
fcw.creds = &creds
|
|
|
|
err := fcw.writeConfig(credentialsFilename, "Credentials", creds)
|
|
if err != nil {
|
|
return fmt.Errorf("writing to %s: %w", credentialsFilename, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetManagerURL overwrites the Manager URL in the cached configuration.
|
|
// This is an in-memory change only, and will not be written to the config file.
|
|
func (fcw *FileConfigWrangler) SetManagerURL(managerURL string) {
|
|
fcw.wc.ManagerURL = managerURL
|
|
}
|
|
|
|
// DefaultConfig returns a fairly sane default configuration.
|
|
func (fcw FileConfigWrangler) DefaultConfig() WorkerConfig {
|
|
return defaultConfig
|
|
}
|
|
|
|
// WriteConfig stores a struct as YAML file.
|
|
func (fcw FileConfigWrangler) writeConfig(filename string, filetype string, config interface{}) error {
|
|
data, err := yaml.Marshal(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tempFilename := filename + "~"
|
|
f, err := os.OpenFile(tempFilename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(f, "# %s file for Flamenco Worker.\n", filetype)
|
|
fmt.Fprintln(f, "# For an explanation of the fields, refer to flamenco-worker-example.yaml")
|
|
fmt.Fprintln(f, "#")
|
|
fmt.Fprintln(f, "# NOTE: this file can be overwritten by Flamenco Worker.")
|
|
fmt.Fprintln(f, "#")
|
|
now := time.Now()
|
|
fmt.Fprintf(f, "# This file was written on %s\n\n", now.Format("2006-01-02 15:04:05 -07:00"))
|
|
|
|
n, err := f.Write(data)
|
|
if err != nil {
|
|
f.Close() // ignore errors here
|
|
return err
|
|
}
|
|
if n < len(data) {
|
|
f.Close() // ignore errors here
|
|
return io.ErrShortWrite
|
|
}
|
|
if err = f.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debug().Str("filename", tempFilename).Msg("config file written")
|
|
log.Debug().
|
|
Str("from", tempFilename).
|
|
Str("to", filename).
|
|
Msg("renaming config file")
|
|
if err := os.Rename(tempFilename, filename); err != nil {
|
|
return err
|
|
}
|
|
log.Info().Str("filename", filename).Msg("Saved configuration file")
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadConfig loads a YAML configuration file into 'config'
|
|
func (fcw FileConfigWrangler) loadConfig(filename string, config interface{}) error {
|
|
log.Debug().Str("filename", filename).Msg("loading config file")
|
|
f, err := os.OpenFile(filename, os.O_RDONLY, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
dec := yaml.NewDecoder(f)
|
|
if err = dec.Decode(config); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ParseURL allows URLs without scheme (assumes HTTP).
|
|
func ParseURL(rawURL string) (*url.URL, error) {
|
|
var err error
|
|
var parsedURL *url.URL
|
|
|
|
parsedURL, err = url.Parse(rawURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// url.Parse() is a bit weird when there is no scheme.
|
|
if parsedURL.Host == "" && parsedURL.Path != "" {
|
|
// This case happens when you just enter a hostname, like manager='thehost'
|
|
parsedURL.Host = parsedURL.Path
|
|
parsedURL.Path = "/"
|
|
}
|
|
if parsedURL.Host == "" && parsedURL.Scheme != "" && parsedURL.Opaque != "" {
|
|
// This case happens when you just enter a hostname:port, like manager='thehost:8083'
|
|
parsedURL.Host = parsedURL.Scheme + ":" + parsedURL.Opaque
|
|
parsedURL.Opaque = ""
|
|
parsedURL.Scheme = "http"
|
|
}
|
|
if parsedURL.Scheme == "" {
|
|
parsedURL.Scheme = "http"
|
|
}
|
|
if parsedURL.Host == "" {
|
|
return nil, errURLWithoutHostName
|
|
}
|
|
|
|
return parsedURL, nil
|
|
}
|