Merge pull request #47 from serhii-vasylkiv/mqtt-password-file
Some checks failed
build / inverter_gui_pipeline (push) Has been cancelled
Some checks failed
build / inverter_gui_pipeline (push) Has been cancelled
Add support for reading MQTT password from file
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
)
|
||||
|
||||
@@ -21,6 +25,7 @@ type config struct {
|
||||
Topic string `long:"mqtt.topic" env:"MQTT_TOPIC" default:"invertergui/updates" description:"Set the MQTT topic updates published to."`
|
||||
Username string `long:"mqtt.username" env:"MQTT_USERNAME" default:"" description:"Set the MQTT username"`
|
||||
Password string `long:"mqtt.password" env:"MQTT_PASSWORD" default:"" description:"Set the MQTT password"`
|
||||
PasswordFile string `long:"mqtt.password-file" env:"MQTT_PASSWORD_FILE" default:"" description:"Path to a file containing the MQTT password"`
|
||||
}
|
||||
Loglevel string `long:"loglevel" env:"LOGLEVEL" default:"info" description:"The log level to generate logs at. (\"panic\", \"fatal\", \"error\", \"warn\", \"info\", \"debug\", \"trace\")"`
|
||||
}
|
||||
@@ -31,5 +36,30 @@ func parseConfig() (*config, error) {
|
||||
if _, err := parser.Parse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := resolvePasswordFile(conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func resolvePasswordFile(conf *config) error {
|
||||
if conf.MQTT.PasswordFile != "" && conf.MQTT.Password != "" {
|
||||
return fmt.Errorf("mqtt.password and mqtt.password-file are mutually exclusive")
|
||||
}
|
||||
if conf.MQTT.PasswordFile != "" {
|
||||
password, err := readPasswordFile(conf.MQTT.PasswordFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conf.MQTT.Password = password
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readPasswordFile(path string) (string, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read MQTT password file: %w", err)
|
||||
}
|
||||
return strings.TrimRight(string(data), "\n\r"), nil
|
||||
}
|
||||
|
||||
107
cmd/invertergui/config_test.go
Normal file
107
cmd/invertergui/config_test.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const testInlineSecret = "inline-secret"
|
||||
|
||||
func TestReadPasswordFile(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
content string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "plain password",
|
||||
content: "secret",
|
||||
expected: "secret",
|
||||
},
|
||||
{
|
||||
name: "password with trailing newline",
|
||||
content: "secret\n",
|
||||
expected: "secret",
|
||||
},
|
||||
{
|
||||
name: "password with trailing carriage return and newline",
|
||||
content: "secret\r\n",
|
||||
expected: "secret",
|
||||
},
|
||||
{
|
||||
name: "empty file",
|
||||
content: "",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "password")
|
||||
if err := os.WriteFile(path, []byte(tt.content), 0o600); err != nil {
|
||||
t.Fatalf("failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
got, err := readPasswordFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != tt.expected {
|
||||
t.Errorf("got %q, want %q", got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadPasswordFile_NotFound(t *testing.T) {
|
||||
_, err := readPasswordFile("/nonexistent/path/password")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for missing file, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvePassword_MutuallyExclusive(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "password")
|
||||
if err := os.WriteFile(path, []byte("secret"), 0o600); err != nil {
|
||||
t.Fatalf("failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
conf := &config{}
|
||||
conf.MQTT.Password = testInlineSecret
|
||||
conf.MQTT.PasswordFile = path
|
||||
|
||||
err := resolvePasswordFile(conf)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when both mqtt.password and mqtt.password-file are set, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvePassword_FromFile(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "password")
|
||||
if err := os.WriteFile(path, []byte("file-secret\n"), 0o600); err != nil {
|
||||
t.Fatalf("failed to write test file: %v", err)
|
||||
}
|
||||
|
||||
conf := &config{}
|
||||
conf.MQTT.PasswordFile = path
|
||||
|
||||
if err := resolvePasswordFile(conf); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if conf.MQTT.Password != "file-secret" {
|
||||
t.Errorf("got %q, want %q", conf.MQTT.Password, "file-secret")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvePassword_NoFile(t *testing.T) {
|
||||
conf := &config{}
|
||||
conf.MQTT.Password = testInlineSecret
|
||||
|
||||
if err := resolvePasswordFile(conf); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if conf.MQTT.Password != testInlineSecret {
|
||||
t.Errorf("got %q, want %q", conf.MQTT.Password, testInlineSecret)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user