Skip to content

Commit

Permalink
feat(config): support env vars in some config vars
Browse files Browse the repository at this point in the history
"${ENV_VAR}"
* deployed_version.basic_auth.password
* deployed_version.basic_auth.username
* deployed_version.headers.*.key
* deployed_version.headers.*.value
* deployed_version.url
* latest_version.access_token
* latest_version.url
* latest_version.require.docker.token
* latest_version.require.docker.username
* notify.*.options.*
* notify.*.params.*
* notify.*.url_fields.*

* -web.basic-auth.password flag
* -web.basic-auth.username flag

* ARGUS_WEB_BASIC_AUTH_USERNAME env
* ARGUS_WEB_BASIC_AUTH_PASSWORD env
  • Loading branch information
JosephKav committed Feb 16, 2024
1 parent af37e67 commit ca77bf4
Show file tree
Hide file tree
Showing 33 changed files with 2,024 additions and 1,157 deletions.
Empty file added .husky/pre-commit
Empty file.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ Usage of /usr/local/bin/argus:
Put the name of the Notify service to send a test message.
-test.service string
Put the name of the Service to test the version query.
-web.basic-auth.password string
Password for basic auth
-web.basic-auth.username string
Username for basic auth
-web.cert-file string
HTTPS certificate file path.
-web.listen-host string
Expand Down
1 change: 1 addition & 0 deletions cmd/argus/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func TestTheMain(t *testing.T) {
t.Run(name, func(t *testing.T) {

file := fmt.Sprintf("%s.yml", name)
os.Remove(tc.db)
tc.file(file, t)
defer os.Remove(tc.db)
resetFlags()
Expand Down
2 changes: 0 additions & 2 deletions commands/announce_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ func TestController_AnnounceCommand(t *testing.T) {
"not tried does delay by 15s": {
index: 2,
timeDifference: 15 * time.Second,
failed: nil,
},
"failed does delay by 15s": {
index: 0,
Expand Down Expand Up @@ -137,7 +136,6 @@ func TestController_Find(t *testing.T) {
want: nil},
"nil controller": {
command: "ls -lah /root",
want: nil,
nilController: true},
}

Expand Down
6 changes: 0 additions & 6 deletions commands/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ func TestCommand_Exec(t *testing.T) {
}{
"command that will pass": {
cmd: Command{"date", "+%m-%d-%Y"},
err: nil,
outputRegex: `[0-9]{2}-[0-9]{2}-[0-9]{4}\s+$`},
"command that will fail": {
cmd: Command{"false"},
Expand Down Expand Up @@ -144,12 +143,10 @@ func TestController_ExecIndex(t *testing.T) {
}{
"command index out of range": {
index: 2,
err: nil,
outputRegex: `^$`,
noAnnounce: true},
"command index that will pass": {
index: 0,
err: nil,
outputRegex: `[0-9]{2}-[0-9]{2}-[0-9]{4}\s+$`},
"command index that will fail": {
index: 1,
Expand Down Expand Up @@ -208,15 +205,12 @@ func TestController_Exec(t *testing.T) {
}{
"nil Controller": {
nilController: true,
err: nil,
outputRegex: `^$`,
noAnnounce: true},
"nil Command": {
err: nil,
outputRegex: `^$`,
noAnnounce: true},
"single Command": {
err: nil,
outputRegex: `[0-9]{2}-[0-9]{2}-[0-9]{4}\s+$`,
commands: &Slice{
{"date", "+%m-%d-%Y"}}},
Expand Down
37 changes: 37 additions & 0 deletions commands/help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ package command
import (
"fmt"
"os"
"strings"
"testing"

"github.com/release-argus/Argus/notifiers/shoutrrr"
svcstatus "github.com/release-argus/Argus/service/status"
"github.com/release-argus/Argus/util"
)
Expand Down Expand Up @@ -69,3 +71,38 @@ func TestMain(m *testing.M) {
// exit
os.Exit(exitCode)
}

func testShoutrrr(failing bool, selfSignedCert bool) *shoutrrr.Shoutrrr {
url := "valid.release-argus.io"
if selfSignedCert {
url = strings.Replace(url, "valid", "invalid", 1)
}
shoutrrr := shoutrrr.New(
nil, "",
&map[string]string{"max_tries": "1"},
&map[string]string{},
"gotify",
// trunk-ignore(gitleaks/generic-api-key)
&map[string]string{"host": url, "path": "/gotify", "token": "AGE-LlHU89Q56uQ"},
shoutrrr.NewDefaults(
"", nil, nil, nil),
shoutrrr.NewDefaults(
"", nil, nil, nil),
shoutrrr.NewDefaults(
"", nil, nil, nil))
shoutrrr.Main.InitMaps()
shoutrrr.Defaults.InitMaps()
shoutrrr.HardDefaults.InitMaps()

shoutrrr.ID = "test"
shoutrrr.ServiceStatus = &svcstatus.Status{
ServiceID: stringPtr("service"),
}
shoutrrr.ServiceStatus.Fails.Shoutrrr.Init(1)
shoutrrr.Failed = &shoutrrr.ServiceStatus.Fails.Shoutrrr

if failing {
shoutrrr.URLFields["token"] = "invalid"
}
return shoutrrr
}
6 changes: 2 additions & 4 deletions commands/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ func TestCommand_String(t *testing.T) {
cmd: &Command{},
want: ""},
"nil command": {
cmd: nil,
want: ""},
"command with no args": {
cmd: &Command{"ls"},
Expand Down Expand Up @@ -370,17 +369,16 @@ func TestCommand_Init(t *testing.T) {
{"false"}},
},
"nil Notifiers": {
shoutrrrNotifiers: nil,
command: &Slice{
{"date", "+%m-%d-%Y"}},
},
"non-nil Notifiers": {
shoutrrrNotifiers: nil,
command: &Slice{
{"date", "+%m-%d-%Y"}},
shoutrrrNotifiers: &shoutrrr.Slice{
"test": testShoutrrr(false, false)},
},
"nil parentInterval": {
parentInterval: nil,
command: &Slice{
{"date", "+%m-%d-%Y"}},
},
Expand Down
137 changes: 77 additions & 60 deletions config/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
package config

import (
"crypto/sha256"
"encoding/hex"
"flag"
"fmt"
"os"
Expand All @@ -28,14 +26,16 @@ import (

// Export the flags.
var (
LogLevel = flag.String("log.level", "INFO", "ERROR, WARN, INFO, VERBOSE or DEBUG")
LogTimestamps = flag.Bool("log.timestamps", false, "Enable timestamps in CLI output.")
DataDatabaseFile = flag.String("data.database-file", "data/argus.db", "Database file path.")
WebListenHost = flag.String("web.listen-host", "0.0.0.0", "IP address to listen on for UI, API, and telemetry.")
WebListenPort = flag.String("web.listen-port", "8080", "Port to listen on for UI, API, and telemetry.")
WebCertFile = flag.String("web.cert-file", "", "HTTPS certificate file path.")
WebPKeyFile = flag.String("web.pkey-file", "", "HTTPS private key file path.")
WebRoutePrefix = flag.String("web.route-prefix", "/", "Prefix for web endpoints")
LogLevel = flag.String("log.level", "INFO", "ERROR, WARN, INFO, VERBOSE or DEBUG")
LogTimestamps = flag.Bool("log.timestamps", false, "Enable timestamps in CLI output.")
DataDatabaseFile = flag.String("data.database-file", "data/argus.db", "Database file path.")
WebListenHost = flag.String("web.listen-host", "0.0.0.0", "IP address to listen on for UI, API, and telemetry.")
WebListenPort = flag.String("web.listen-port", "8080", "Port to listen on for UI, API, and telemetry.")
WebCertFile = flag.String("web.cert-file", "", "HTTPS certificate file path.")
WebPKeyFile = flag.String("web.pkey-file", "", "HTTPS private key file path.")
WebRoutePrefix = flag.String("web.route-prefix", "/", "Prefix for web endpoints")
WebBasicAuthUsername = flag.String("web.basic-auth.username", "", "Username for basic auth")
WebBasicAuthPassword = flag.String("web.basic-auth.password", "", "Password for basic auth")
)

// Settings for the binary.
Expand Down Expand Up @@ -64,6 +64,12 @@ type SettingsBase struct {
Web WebSettings `yaml:"web,omitempty"` // Web settings
}

// CheckValues of the SettingsBase.
func (s *SettingsBase) CheckValues() {
// Web
s.Web.CheckValues()
}

// MapEnvToStruct maps environment variables to this struct.
func (s *SettingsBase) MapEnvToStruct() {
err := mapEnvToStruct(s, "", nil)
Expand All @@ -73,6 +79,7 @@ func (s *SettingsBase) MapEnvToStruct() {
strings.ReplaceAll(util.ErrorToString(err), "\\", "\n"),
util.LogFrom{}, true)
}
s.CheckValues() // Set hash values and remove empty structs.
}

// LogSettings for the binary.
Expand Down Expand Up @@ -143,24 +150,11 @@ func (s *WebSettingsBasicAuth) String(prefix string) (str string) {

// CheckValues will ensure that the values are SHA256 hashed.
func (ba *WebSettingsBasicAuth) CheckValues() {
// Ensure it's hashed.
sha256Regex := "^h__[a-f0-9]{64}$"
// Username - Hash if not already hashed.
if !util.RegexCheck(sha256Regex, ba.Username) {
ba.UsernameHash = sha256.Sum256([]byte(ba.Username))
ba.Username = fmt.Sprintf("h__%x", ba.UsernameHash)
} else {
hash, _ := hex.DecodeString(ba.Username[3:])
copy(ba.UsernameHash[:], hash)
}
// Password - Hash if not already hashed.
if !util.RegexCheck(sha256Regex, ba.Password) {
ba.PasswordHash = sha256.Sum256([]byte(ba.Password))
ba.Password = fmt.Sprintf("h__%x", ba.PasswordHash)
} else {
hash, _ := hex.DecodeString(ba.Password[3:])
copy(ba.PasswordHash[:], hash)
}
// Username
ba.UsernameHash = util.GetHash(ba.Username)
// Password
ba.PasswordHash = util.GetHash(ba.Password)
ba.Password = util.FmtHash(ba.PasswordHash)
}

// FaviconSettings contains the favicon override settings.
Expand All @@ -170,29 +164,26 @@ type FaviconSettings struct {
}

func (s *Settings) NilUndefinedFlags(flagset *map[string]bool) {
if !(*flagset)["log.level"] {
LogLevel = nil
}
if !(*flagset)["log.timestamps"] {
LogTimestamps = nil
}
if !(*flagset)["data.database-file"] {
DataDatabaseFile = nil
}
if !(*flagset)["web.listen-host"] {
WebListenHost = nil
}
if !(*flagset)["web.listen-port"] {
WebListenPort = nil
}
if !(*flagset)["web.cert-file"] {
WebCertFile = nil
}
if !(*flagset)["web.pkey-file"] {
WebPKeyFile = nil
}
if !(*flagset)["web.route-prefix"] {
WebRoutePrefix = nil
for _, f := range []struct {
Flag string
Variable interface{}
}{
{"log.level", &LogLevel},
{"log.timestamps", &LogTimestamps},
{"data.database-file", &DataDatabaseFile},
{"web.listen-host", &WebListenHost},
{"web.listen-port", &WebListenPort},
{"web.cert-file", &WebCertFile},
{"web.pkey-file", &WebPKeyFile},
{"web.route-prefix", &WebRoutePrefix},
{"web.basic-auth.username", &WebBasicAuthUsername},
{"web.basic-auth.password", &WebBasicAuthPassword},
} {
if !(*flagset)[f.Flag] {
if strPtr, ok := f.Variable.(**string); ok {
*strPtr = nil
}
}
}
}

Expand Down Expand Up @@ -238,17 +229,25 @@ func (s *Settings) SetDefaults() {
webListenPort := "8080"
s.HardDefaults.Web.ListenPort = &webListenPort

// RoutePrefix
s.FromFlags.Web.RoutePrefix = WebRoutePrefix
webRoutePrefix := "/"
s.HardDefaults.Web.RoutePrefix = &webRoutePrefix

// CertFile
s.FromFlags.Web.CertFile = WebCertFile

// KeyFile
s.FromFlags.Web.KeyFile = WebPKeyFile

// RoutePrefix
s.FromFlags.Web.RoutePrefix = WebRoutePrefix
webRoutePrefix := "/"
s.HardDefaults.Web.RoutePrefix = &webRoutePrefix

// BasicAuth
if WebBasicAuthUsername != nil || WebBasicAuthPassword != nil {
s.FromFlags.Web.BasicAuth = &WebSettingsBasicAuth{}
s.FromFlags.Web.BasicAuth.Username = util.StringWithEnv(util.DefaultIfNil(WebBasicAuthUsername))
s.FromFlags.Web.BasicAuth.Password = util.StringWithEnv(util.DefaultIfNil(WebBasicAuthPassword))
s.FromFlags.Web.BasicAuth.CheckValues()
}

// Overwrite defaults with environment variables.
s.HardDefaults.MapEnvToStruct()
}
Expand Down Expand Up @@ -352,8 +351,26 @@ func (s *Settings) WebKeyFile() *string {
return keyFile
}

// CheckValues of the Settings.
func (s *Settings) CheckValues() {
// Web
s.Web.CheckValues()
// WebBasicAuthUsername.
func (s *Settings) WebBasicAuthUsernameHash() [32]byte {
// Username set through flag.
if s.FromFlags.Web.BasicAuth != nil && s.FromFlags.Web.BasicAuth.Username != "" {
return s.FromFlags.Web.BasicAuth.UsernameHash
}
// Username set through config.
if s.Web.BasicAuth != nil && s.Web.BasicAuth.Username != "" {
return s.Web.BasicAuth.UsernameHash
}
return s.HardDefaults.Web.BasicAuth.UsernameHash
}

// WebBasicAuthPassword.
func (s *Settings) WebBasicAuthPasswordHash() [32]byte {
if s.FromFlags.Web.BasicAuth != nil && s.FromFlags.Web.BasicAuth.Password != "" {
return s.FromFlags.Web.BasicAuth.PasswordHash
}
if s.Web.BasicAuth != nil {
return s.Web.BasicAuth.PasswordHash
}
return s.HardDefaults.Web.BasicAuth.PasswordHash
}
Loading

0 comments on commit ca77bf4

Please sign in to comment.