feat(config): own PasswordFile type
its possible to unmarshal a PasswordFile path to a read string. this is nice for docker secret setups. also adds a GetPassword method which checks if both Password and PasswordFile are declared.
This commit is contained in:
parent
242ee3c0c5
commit
77ad55f1eb
@ -1,11 +1,48 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var Cfg Config //nolint:gochecknoglobals
|
||||
|
||||
type Config struct {
|
||||
Email string `env:"EMAIL,required"`
|
||||
Password string `env:"PASSWORD,expand"`
|
||||
PasswordFile string `env:"PASSWORD_FILE,expand"`
|
||||
CacheDir string `env:"CACHE_DIR,expand" envDefault:"/var/cache/glucose_exporter"`
|
||||
Debug bool `env:"DEBUG"`
|
||||
Email string `env:"EMAIL,required"`
|
||||
Password string `env:"PASSWORD,expand"`
|
||||
PasswordFile PasswordFile `env:"PASSWORD_FILE,expand"`
|
||||
CacheDir string `env:"CACHE_DIR,expand" envDefault:"/var/cache/glucose_exporter"`
|
||||
Debug bool `env:"DEBUG"`
|
||||
}
|
||||
|
||||
func (c *Config) GetPassword() (string, error) {
|
||||
if c.PasswordFile != "" && c.Password != "" {
|
||||
return "", ErrTooManyPasswords
|
||||
}
|
||||
|
||||
if c.Password != "" {
|
||||
return c.Password, nil
|
||||
}
|
||||
|
||||
if c.PasswordFile != "" {
|
||||
return string(c.PasswordFile), nil
|
||||
}
|
||||
|
||||
return "", ErrMissingPassword
|
||||
}
|
||||
|
||||
type PasswordFile string
|
||||
|
||||
var _ encoding.TextUnmarshaler = (*PasswordFile)(nil)
|
||||
|
||||
func (pf *PasswordFile) UnmarshalText(text []byte) error {
|
||||
b, err := os.ReadFile(string(text))
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading password file: %w", err)
|
||||
}
|
||||
|
||||
*pf = PasswordFile(string(b))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
84
internal/config/config_test.go
Normal file
84
internal/config/config_test.go
Normal file
@ -0,0 +1,84 @@
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/matryer/is"
|
||||
"go.xsfx.dev/glucose_exporter/internal/config"
|
||||
)
|
||||
|
||||
func TestPasswordFile(t *testing.T) {
|
||||
is := is.New(t)
|
||||
|
||||
dir := t.TempDir()
|
||||
|
||||
pfPath := path.Join(dir, "foo.txt")
|
||||
|
||||
err := os.WriteFile(pfPath, []byte("f00b4r"), 0o600)
|
||||
is.NoErr(err)
|
||||
|
||||
var pf config.PasswordFile
|
||||
|
||||
err = pf.UnmarshalText([]byte(pfPath))
|
||||
is.NoErr(err)
|
||||
|
||||
is.Equal(string(pf), "f00b4r")
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
tables := []struct {
|
||||
name string
|
||||
cfg config.Config
|
||||
expected string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "00",
|
||||
cfg: config.Config{},
|
||||
expected: "",
|
||||
err: config.ErrMissingPassword,
|
||||
},
|
||||
{
|
||||
name: "01",
|
||||
cfg: config.Config{
|
||||
Password: "foo",
|
||||
PasswordFile: "bar",
|
||||
},
|
||||
expected: "",
|
||||
err: config.ErrTooManyPasswords,
|
||||
},
|
||||
{
|
||||
name: "02",
|
||||
cfg: config.Config{
|
||||
Password: "foo",
|
||||
},
|
||||
expected: "foo",
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "03",
|
||||
cfg: config.Config{
|
||||
PasswordFile: "foo",
|
||||
},
|
||||
expected: "foo",
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
is := is.New(t)
|
||||
|
||||
for _, tt := range tables {
|
||||
t.Run(tt.name, func(_ *testing.T) {
|
||||
pass, err := tt.cfg.GetPassword()
|
||||
if tt.err == nil {
|
||||
is.NoErr(err)
|
||||
is.Equal(pass, tt.expected)
|
||||
} else {
|
||||
is.True(errors.Is(err, tt.err))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
8
internal/config/errors.go
Normal file
8
internal/config/errors.go
Normal file
@ -0,0 +1,8 @@
|
||||
package config
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrTooManyPasswords = errors.New("too many passwords")
|
||||
ErrMissingPassword = errors.New("missing password")
|
||||
)
|
@ -26,7 +26,6 @@ func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
vm.WritePrometheus(w, false)
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func glucose(ctx context.Context, w io.Writer) error {
|
||||
c, err := cache.Load()
|
||||
if err != nil {
|
||||
@ -37,22 +36,8 @@ func glucose(ctx context.Context, w io.Writer) error {
|
||||
if c.JWT == "" || time.Now().After(time.Time(c.Expires)) {
|
||||
slog.Debug("needs a fresh token")
|
||||
|
||||
token, err := api.Login(
|
||||
ctx,
|
||||
api.BaseURL,
|
||||
config.Cfg.Email,
|
||||
config.Cfg.Password,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("login: %w", err)
|
||||
}
|
||||
|
||||
slog.Debug("got token", "token", token)
|
||||
|
||||
if err := cache.Save(
|
||||
cache.Cache{JWT: token.Data.AuthTicket.Token, Expires: token.Data.AuthTicket.Expires},
|
||||
); err != nil {
|
||||
return fmt.Errorf("saving cache: %w", err)
|
||||
if err := token(ctx); err != nil {
|
||||
return fmt.Errorf("refreshing token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,3 +86,30 @@ func glucose(ctx context.Context, w io.Writer) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func token(ctx context.Context) error {
|
||||
pass, err := config.Cfg.GetPassword()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting password from config: %w", err)
|
||||
}
|
||||
|
||||
token, err := api.Login(
|
||||
ctx,
|
||||
api.BaseURL,
|
||||
config.Cfg.Email,
|
||||
pass,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("login: %w", err)
|
||||
}
|
||||
|
||||
slog.Debug("got token", "token", token)
|
||||
|
||||
if err := cache.Save(
|
||||
cache.Cache{JWT: token.Data.AuthTicket.Token, Expires: token.Data.AuthTicket.Expires},
|
||||
); err != nil {
|
||||
return fmt.Errorf("saving cache: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user