Worker: change how configuration & credentials are loaded/saved
The Worker config/credential management was a bit of a mess. It's now better structured, and also allows runtime overrides of the Manager URL, without writing that override to the config file.
This commit is contained in:
parent
c0cd3ca5ad
commit
42407865eb
@ -8,6 +8,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
@ -26,7 +27,13 @@ const (
|
|||||||
// WorkerConfig represents the configuration of a single worker.
|
// WorkerConfig represents the configuration of a single worker.
|
||||||
// It does not include authentication credentials.
|
// It does not include authentication credentials.
|
||||||
type WorkerConfig struct {
|
type WorkerConfig struct {
|
||||||
Manager string `yaml:"manager_url"`
|
// 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"`
|
TaskTypes []string `yaml:"task_types"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,42 +42,59 @@ type WorkerCredentials struct {
|
|||||||
Secret string `yaml:"worker_secret"`
|
Secret string `yaml:"worker_secret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadConfig(configWrangler FileConfigWrangler) (WorkerConfig, error) {
|
// FileConfigWrangler is the default config wrangler that actually reads & writes files.
|
||||||
logger := log.With().Str("filename", configFilename).Logger()
|
type FileConfigWrangler struct {
|
||||||
|
// In-memory copy of the worker configuration.
|
||||||
|
wc *WorkerConfig
|
||||||
|
creds *WorkerCredentials
|
||||||
|
}
|
||||||
|
|
||||||
var cfg WorkerConfig
|
// NewConfigWrangler returns ConfigWrangler that reads files.
|
||||||
|
func NewConfigWrangler() FileConfigWrangler {
|
||||||
|
return FileConfigWrangler{}
|
||||||
|
}
|
||||||
|
|
||||||
err := configWrangler.LoadConfig(configFilename, &cfg)
|
// 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 := WorkerConfig{}
|
||||||
|
err := fcw.loadConfig(configFilename, &wc)
|
||||||
|
|
||||||
// If the configuration file doesn't exist, write the defaults & retry loading them.
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
logger.Info().Msg("writing default configuration file")
|
|
||||||
cfg = configWrangler.DefaultConfig()
|
|
||||||
err = configWrangler.WriteConfig(configFilename, "Configuration", cfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cfg, fmt.Errorf("writing default config: %w", err)
|
if !os.IsNotExist(err) {
|
||||||
|
return WorkerConfig{}, err
|
||||||
}
|
}
|
||||||
err = configWrangler.LoadConfig(configFilename, &cfg)
|
|
||||||
|
// The config file not existing is fine; just use the defaults.
|
||||||
|
wc = fcw.DefaultConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return cfg, fmt.Errorf("loading config from %s: %w", configFilename, err)
|
return fmt.Errorf("writing to %s: %w", configFilename, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the manager URL.
|
func (fcw *FileConfigWrangler) WorkerCredentials() (WorkerCredentials, error) {
|
||||||
if cfg.Manager != "" {
|
|
||||||
_, err := ParseURL(cfg.Manager)
|
|
||||||
if err != nil {
|
|
||||||
return cfg, fmt.Errorf("parsing manager URL %s: %w", cfg.Manager, err)
|
|
||||||
}
|
|
||||||
logger.Debug().Str("url", cfg.Manager).Msg("parsed manager URL")
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadCredentials(configWrangler FileConfigWrangler) (WorkerCredentials, error) {
|
|
||||||
var creds WorkerCredentials
|
var creds WorkerCredentials
|
||||||
err := configWrangler.LoadConfig(credentialsFilename, &creds)
|
err := fcw.loadConfig(credentialsFilename, &creds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return WorkerCredentials{}, err
|
return WorkerCredentials{}, err
|
||||||
}
|
}
|
||||||
@ -81,24 +105,34 @@ func loadCredentials(configWrangler FileConfigWrangler) (WorkerCredentials, erro
|
|||||||
return creds, nil
|
return creds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileConfigWrangler is the default config wrangler that actually reads & writes files.
|
func (fcw *FileConfigWrangler) SaveCredentials(creds WorkerCredentials) error {
|
||||||
type FileConfigWrangler struct{}
|
fcw.creds = &creds
|
||||||
|
|
||||||
// NewConfigWrangler returns ConfigWrangler that reads files.
|
err := fcw.writeConfig(credentialsFilename, "Credentials", creds)
|
||||||
func NewConfigWrangler() FileConfigWrangler {
|
if err != nil {
|
||||||
return FileConfigWrangler{}
|
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.
|
||||||
|
// Returns a new copy of the WorkerConfig with the Manager URL updated.
|
||||||
|
func (fcw *FileConfigWrangler) SetManagerURL(managerURL string) WorkerConfig {
|
||||||
|
fcw.wc.ManagerURL = managerURL
|
||||||
|
return *fcw.wc
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConfig returns a fairly sane default configuration.
|
// DefaultConfig returns a fairly sane default configuration.
|
||||||
func (fcw FileConfigWrangler) DefaultConfig() WorkerConfig {
|
func (fcw FileConfigWrangler) DefaultConfig() WorkerConfig {
|
||||||
return WorkerConfig{
|
return WorkerConfig{
|
||||||
Manager: "",
|
ConfiguredManager: "", // Auto-detect by default.
|
||||||
TaskTypes: []string{"blender", "file-management", "exr-merge", "misc"},
|
TaskTypes: []string{"blender", "file-management", "exr-merge", "misc"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteConfig stores a struct as YAML file.
|
// WriteConfig stores a struct as YAML file.
|
||||||
func (fcw FileConfigWrangler) WriteConfig(filename string, filetype string, config interface{}) error {
|
func (fcw FileConfigWrangler) writeConfig(filename string, filetype string, config interface{}) error {
|
||||||
data, err := yaml.Marshal(config)
|
data, err := yaml.Marshal(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -144,7 +178,7 @@ func (fcw FileConfigWrangler) WriteConfig(filename string, filetype string, conf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig loads a YAML configuration file into 'config'
|
// LoadConfig loads a YAML configuration file into 'config'
|
||||||
func (fcw FileConfigWrangler) LoadConfig(filename string, config interface{}) error {
|
func (fcw FileConfigWrangler) loadConfig(filename string, config interface{}) error {
|
||||||
log.Debug().Str("filename", filename).Msg("loading config file")
|
log.Debug().Str("filename", filename).Msg("loading config file")
|
||||||
f, err := os.OpenFile(filename, os.O_RDONLY, 0)
|
f, err := os.OpenFile(filename, os.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -30,19 +30,18 @@ func RegisterOrSignOn(ctx context.Context, configWrangler FileConfigWrangler) (
|
|||||||
client FlamencoClient, startupState api.WorkerStatus,
|
client FlamencoClient, startupState api.WorkerStatus,
|
||||||
) {
|
) {
|
||||||
// Load configuration
|
// Load configuration
|
||||||
cfg, err := loadConfig(configWrangler)
|
cfg, err := configWrangler.WorkerConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("loading configuration")
|
log.Fatal().Err(err).Msg("loading configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info().Interface("config", cfg).Msg("loaded configuration")
|
log.Info().Interface("config", cfg).Msg("loaded configuration")
|
||||||
|
if cfg.ManagerURL == "" {
|
||||||
if cfg.Manager == "" {
|
log.Fatal().Msg("no Manager configured")
|
||||||
log.Fatal().Msg("no manager configured")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load credentials
|
// Load credentials
|
||||||
creds, err := loadCredentials(configWrangler)
|
creds, err := configWrangler.WorkerCredentials()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Credentials can be loaded just fine, try to sign on with them.
|
// Credentials can be loaded just fine, try to sign on with them.
|
||||||
client = authenticatedClient(cfg, creds)
|
client = authenticatedClient(cfg, creds)
|
||||||
@ -58,17 +57,16 @@ func RegisterOrSignOn(ctx context.Context, configWrangler FileConfigWrangler) (
|
|||||||
creds = register(ctx, cfg, client)
|
creds = register(ctx, cfg, client)
|
||||||
|
|
||||||
// store ID and secretKey in config file when registration is complete.
|
// store ID and secretKey in config file when registration is complete.
|
||||||
err = configWrangler.WriteConfig(credentialsFilename, "Credentials", creds)
|
err = configWrangler.SaveCredentials(creds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Str("file", credentialsFilename).
|
log.Fatal().Err(err).Msg("unable to write credentials file")
|
||||||
Msg("unable to write credentials configuration file")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign-on should work now.
|
// Sign-on should work now.
|
||||||
client = authenticatedClient(cfg, creds)
|
client = authenticatedClient(cfg, creds)
|
||||||
startupState, err = signOn(ctx, cfg, client)
|
startupState, err = signOn(ctx, cfg, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Str("manager", cfg.Manager).Msg("unable to sign on after registering")
|
log.Fatal().Err(err).Str("manager", cfg.ManagerURL).Msg("unable to sign on after registering")
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -141,7 +139,7 @@ func repeatSignOnUntilAnswer(ctx context.Context, cfg WorkerConfig, client Flame
|
|||||||
|
|
||||||
// signOn tells the Manager we're alive and returns the status the Manager tells us to go to.
|
// signOn tells the Manager we're alive and returns the status the Manager tells us to go to.
|
||||||
func signOn(ctx context.Context, cfg WorkerConfig, client FlamencoClient) (api.WorkerStatus, error) {
|
func signOn(ctx context.Context, cfg WorkerConfig, client FlamencoClient) (api.WorkerStatus, error) {
|
||||||
logger := log.With().Str("manager", cfg.Manager).Logger()
|
logger := log.With().Str("manager", cfg.ManagerURL).Logger()
|
||||||
|
|
||||||
req := api.SignOnJSONRequestBody{
|
req := api.SignOnJSONRequestBody{
|
||||||
Nickname: mustHostname(),
|
Nickname: mustHostname(),
|
||||||
@ -191,7 +189,7 @@ func mustHostname() string {
|
|||||||
// authenticatedClient constructs a Flamenco client with the given credentials.
|
// authenticatedClient constructs a Flamenco client with the given credentials.
|
||||||
func authenticatedClient(cfg WorkerConfig, creds WorkerCredentials) FlamencoClient {
|
func authenticatedClient(cfg WorkerConfig, creds WorkerCredentials) FlamencoClient {
|
||||||
flamenco, err := api.NewClientWithResponses(
|
flamenco, err := api.NewClientWithResponses(
|
||||||
cfg.Manager,
|
cfg.ManagerURL,
|
||||||
|
|
||||||
// Add a Basic HTTP authentication header to every request to Flamenco Manager.
|
// Add a Basic HTTP authentication header to every request to Flamenco Manager.
|
||||||
api.WithRequestEditorFn(func(ctx context.Context, req *http.Request) error {
|
api.WithRequestEditorFn(func(ctx context.Context, req *http.Request) error {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user