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
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
var Cfg Config //nolint:gochecknoglobals
|
var Cfg Config //nolint:gochecknoglobals
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Email string `env:"EMAIL,required"`
|
Email string `env:"EMAIL,required"`
|
||||||
Password string `env:"PASSWORD,expand"`
|
Password string `env:"PASSWORD,expand"`
|
||||||
PasswordFile string `env:"PASSWORD_FILE,expand"`
|
PasswordFile PasswordFile `env:"PASSWORD_FILE,expand"`
|
||||||
CacheDir string `env:"CACHE_DIR,expand" envDefault:"/var/cache/glucose_exporter"`
|
CacheDir string `env:"CACHE_DIR,expand" envDefault:"/var/cache/glucose_exporter"`
|
||||||
Debug bool `env:"DEBUG"`
|
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)
|
vm.WritePrometheus(w, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:funlen
|
|
||||||
func glucose(ctx context.Context, w io.Writer) error {
|
func glucose(ctx context.Context, w io.Writer) error {
|
||||||
c, err := cache.Load()
|
c, err := cache.Load()
|
||||||
if err != nil {
|
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)) {
|
if c.JWT == "" || time.Now().After(time.Time(c.Expires)) {
|
||||||
slog.Debug("needs a fresh token")
|
slog.Debug("needs a fresh token")
|
||||||
|
|
||||||
token, err := api.Login(
|
if err := token(ctx); err != nil {
|
||||||
ctx,
|
return fmt.Errorf("refreshing token: %w", err)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,3 +86,30 @@ func glucose(ctx context.Context, w io.Writer) error {
|
|||||||
|
|
||||||
return nil
|
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