mirror of
https://github.com/semaphoreui/semaphore.git
synced 2025-01-20 23:39:56 +01:00
Merge pull request #1472 from ansible-semaphore/config-validation
Config validation
This commit is contained in:
commit
9af6aa504f
@ -198,7 +198,7 @@ func (key *AccessKey) SerializeSecret() error {
|
||||
return fmt.Errorf("invalid access token type")
|
||||
}
|
||||
|
||||
encryptionString := util.Config.GetAccessKeyEncryption()
|
||||
encryptionString := util.Config.AccessKeyEncryption
|
||||
|
||||
if encryptionString == "" {
|
||||
secret := base64.StdEncoding.EncodeToString(plaintext)
|
||||
@ -258,7 +258,7 @@ func (key *AccessKey) unmarshalAppropriateField(secret []byte) (err error) {
|
||||
//}
|
||||
|
||||
func (key *AccessKey) DeserializeSecret() error {
|
||||
return key.DeserializeSecret2(util.Config.GetAccessKeyEncryption())
|
||||
return key.DeserializeSecret2(util.Config.AccessKeyEncryption)
|
||||
}
|
||||
|
||||
func (key *AccessKey) DeserializeSecret2(encryptionString string) error {
|
||||
|
@ -150,7 +150,7 @@ func connect() (*sql.DB, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dialect := cfg.Dialect.String()
|
||||
dialect := cfg.Dialect
|
||||
return sql.Open(dialect, connectionString)
|
||||
}
|
||||
|
||||
@ -169,7 +169,7 @@ func createDb() error {
|
||||
return err
|
||||
}
|
||||
|
||||
conn, err := sql.Open(cfg.Dialect.String(), connectionString)
|
||||
conn, err := sql.Open(cfg.Dialect, connectionString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
4
go.mod
4
go.mod
@ -21,6 +21,7 @@ require (
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
go.etcd.io/bbolt v1.3.2
|
||||
golang.org/x/crypto v0.3.0
|
||||
golang.org/x/oauth2 v0.7.0
|
||||
@ -32,6 +33,7 @@ require (
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||
github.com/cloudflare/circl v1.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.0 // indirect
|
||||
@ -47,6 +49,7 @@ require (
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
@ -58,4 +61,5 @@ require (
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0 // indirect
|
||||
)
|
||||
|
7
go.sum
7
go.sum
@ -168,7 +168,6 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
@ -449,8 +448,6 @@ golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5o
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
@ -535,14 +532,11 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
@ -555,7 +549,6 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
372
util/config.go
372
util/config.go
@ -6,15 +6,18 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/google/go-github/github"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-github/github"
|
||||
"github.com/gorilla/securecookie"
|
||||
)
|
||||
|
||||
@ -24,21 +27,19 @@ var Cookie *securecookie.SecureCookie
|
||||
// WebHostURL is the public route to the semaphore server
|
||||
var WebHostURL *url.URL
|
||||
|
||||
type DbDriver string
|
||||
|
||||
const (
|
||||
DbDriverMySQL DbDriver = "mysql"
|
||||
DbDriverBolt DbDriver = "bolt"
|
||||
DbDriverPostgres DbDriver = "postgres"
|
||||
DbDriverMySQL = "mysql"
|
||||
DbDriverBolt = "bolt"
|
||||
DbDriverPostgres = "postgres"
|
||||
)
|
||||
|
||||
type DbConfig struct {
|
||||
Dialect DbDriver `json:"-"`
|
||||
Dialect string `json:"-"`
|
||||
|
||||
Hostname string `json:"host"`
|
||||
Username string `json:"user"`
|
||||
Password string `json:"pass"`
|
||||
DbName string `json:"name"`
|
||||
Hostname string `json:"host" env:"SEMAPHORE_DB_HOST"`
|
||||
Username string `json:"user" env:"SEMAPHORE_DB_USER"`
|
||||
Password string `json:"pass" env:"SEMAPHORE_DB_PASS"`
|
||||
DbName string `json:"name" env:"SEMAPHORE_DB"`
|
||||
Options map[string]string `json:"options"`
|
||||
}
|
||||
|
||||
@ -71,23 +72,32 @@ type oidcProvider struct {
|
||||
EmailClaim string `json:"email_claim"`
|
||||
}
|
||||
|
||||
type GitClientId string
|
||||
|
||||
const (
|
||||
// GoGitClientId is builtin Git client. It is not require external dependencies and is preferred.
|
||||
// Use it if you don't need external SSH authorization.
|
||||
GoGitClientId GitClientId = "go_git"
|
||||
GoGitClientId = "go_git"
|
||||
// CmdGitClientId is external Git client.
|
||||
// Default Git client. It is use external Git binary to clone repositories.
|
||||
CmdGitClientId GitClientId = "cmd_git"
|
||||
CmdGitClientId = "cmd_git"
|
||||
)
|
||||
|
||||
// // basic config validation using regex
|
||||
// /* NOTE: other basic regex could be used:
|
||||
//
|
||||
// ipv4: ^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$
|
||||
// ipv6: ^(?:[A-Fa-f0-9]{1,4}:|:){3,7}[A-Fa-f0-9]{1,4}$
|
||||
// domain: ^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.)+[a-zA-Z]{2,}$
|
||||
// path+filename: ^([\\/[a-zA-Z0-9_\\-${}:~]*]*\\/)?[a-zA-Z0-9\\.~_${}\\-:]*$
|
||||
// email address: ^(|.*@[A-Za-z0-9-\\.]*)$
|
||||
//
|
||||
// */
|
||||
|
||||
type RunnerSettings struct {
|
||||
ApiURL string `json:"api_url"`
|
||||
RegistrationToken string `json:"registration_token"`
|
||||
ConfigFile string `json:"config_file"`
|
||||
ApiURL string `json:"api_url" env:"SEMAPHORE_RUNNER_API_URL"`
|
||||
RegistrationToken string `json:"registration_token" env:"SEMAPHORE_RUNNER_REGISTRATION_TOKEN"`
|
||||
ConfigFile string `json:"config_file" env:"SEMAPHORE_RUNNER_CONFIG_FILE"`
|
||||
// OneOff indicates than runner runs only one job and exit
|
||||
OneOff bool `json:"one_off"`
|
||||
OneOff bool `json:"one_off" env:"SEMAPHORE_RUNNER_ONE_OFF"`
|
||||
}
|
||||
|
||||
// ConfigType mapping between Config and the json file that sets it
|
||||
@ -96,75 +106,74 @@ type ConfigType struct {
|
||||
BoltDb DbConfig `json:"bolt"`
|
||||
Postgres DbConfig `json:"postgres"`
|
||||
|
||||
Dialect DbDriver `json:"dialect"`
|
||||
Dialect string `json:"dialect" rule:"^mysql|bolt|postgres$" env:"SEMAPHORE_DB_DIALECT"`
|
||||
|
||||
// Format `:port_num` eg, :3000
|
||||
// if : is missing it will be corrected
|
||||
Port string `json:"port"`
|
||||
Port string `json:"port" default:":3000" rule:"^:([0-9]{1,5})$" env:"SEMAPHORE_PORT"`
|
||||
|
||||
// Interface ip, put in front of the port.
|
||||
// defaults to empty
|
||||
Interface string `json:"interface"`
|
||||
Interface string `json:"interface" env:"SEMAPHORE_INTERFACE"`
|
||||
|
||||
// semaphore stores ephemeral projects here
|
||||
TmpPath string `json:"tmp_path"`
|
||||
TmpPath string `json:"tmp_path" default:"/tmp/semaphore" env:"SEMAPHORE_TMP_PATH"`
|
||||
|
||||
// SshConfigPath is a path to the custom SSH config file.
|
||||
// Default path is ~/.ssh/config.
|
||||
SshConfigPath string `json:"ssh_config_path"`
|
||||
SshConfigPath string `json:"ssh_config_path" env:"SEMAPHORE_TMP_PATH"`
|
||||
|
||||
GitClientId GitClientId `json:"git_client"`
|
||||
GitClientId string `json:"git_client" rule:"^go_git|cmd_git$" env:"SEMAPHORE_GIT_CLIENT" default:"cmd_git"`
|
||||
|
||||
// web host
|
||||
WebHost string `json:"web_host"`
|
||||
WebHost string `json:"web_host" env:"SEMAPHORE_WEB_ROOT"`
|
||||
|
||||
// cookie hashing & encryption
|
||||
CookieHash string `json:"cookie_hash"`
|
||||
CookieEncryption string `json:"cookie_encryption"`
|
||||
CookieHash string `json:"cookie_hash" env:"SEMAPHORE_COOKIE_HASH"`
|
||||
CookieEncryption string `json:"cookie_encryption" env:"SEMAPHORE_COOKIE_ENCRYPTION"`
|
||||
// AccessKeyEncryption is BASE64 encoded byte array used
|
||||
// for encrypting and decrypting access keys stored in database.
|
||||
// Do not use it! Use method GetAccessKeyEncryption instead of it.
|
||||
AccessKeyEncryption string `json:"access_key_encryption"`
|
||||
AccessKeyEncryption string `json:"access_key_encryption" env:"SEMAPHORE_ACCESS_KEY_ENCRYPTION"`
|
||||
|
||||
// email alerting
|
||||
EmailAlert bool `json:"email_alert"`
|
||||
EmailSender string `json:"email_sender"`
|
||||
EmailHost string `json:"email_host"`
|
||||
EmailPort string `json:"email_port"`
|
||||
EmailUsername string `json:"email_username"`
|
||||
EmailPassword string `json:"email_password"`
|
||||
EmailSecure bool `json:"email_secure"`
|
||||
EmailAlert bool `json:"email_alert" env:"SEMAPHORE_EMAIL_ALERT"`
|
||||
EmailSender string `json:"email_sender" env:"SEMAPHORE_EMAIL_SENDER"`
|
||||
EmailHost string `json:"email_host" env:"SEMAPHORE_EMAIL_HOST"`
|
||||
EmailPort string `json:"email_port" rule:"^(|[0-9]{1,5})$" env:"SEMAPHORE_EMAIL_PORT"`
|
||||
EmailUsername string `json:"email_username" env:"SEMAPHORE_EMAIL_USERNAME"`
|
||||
EmailPassword string `json:"email_password" env:"SEMAPHORE_EMAIL_PASSWORD"`
|
||||
EmailSecure bool `json:"email_secure" env:"SEMAPHORE_EMAIL_SECURE"`
|
||||
|
||||
// ldap settings
|
||||
LdapEnable bool `json:"ldap_enable"`
|
||||
LdapBindDN string `json:"ldap_binddn"`
|
||||
LdapBindPassword string `json:"ldap_bindpassword"`
|
||||
LdapServer string `json:"ldap_server"`
|
||||
LdapSearchDN string `json:"ldap_searchdn"`
|
||||
LdapSearchFilter string `json:"ldap_searchfilter"`
|
||||
LdapEnable bool `json:"ldap_enable" env:"SEMAPHORE_LDAP_ENABLE"`
|
||||
LdapBindDN string `json:"ldap_binddn" env:"SEMAPHORE_LDAP_BIND_DN"`
|
||||
LdapBindPassword string `json:"ldap_bindpassword" env:"SEMAPHORE_LDAP_BIND_PASSWORD"`
|
||||
LdapServer string `json:"ldap_server" env:"SEMAPHORE_LDAP_SERVER"`
|
||||
LdapSearchDN string `json:"ldap_searchdn" env:"SEMAPHORE_LDAP_SEARCH_DN"`
|
||||
LdapSearchFilter string `json:"ldap_searchfilter" env:"SEMAPHORE_LDAP_SEARCH_FILTER"`
|
||||
LdapMappings ldapMappings `json:"ldap_mappings"`
|
||||
LdapNeedTLS bool `json:"ldap_needtls"`
|
||||
LdapNeedTLS bool `json:"ldap_needtls" env:"SEMAPHORE_LDAP_NEEDTLS"`
|
||||
|
||||
// telegram and slack alerting
|
||||
TelegramAlert bool `json:"telegram_alert"`
|
||||
TelegramChat string `json:"telegram_chat"`
|
||||
TelegramToken string `json:"telegram_token"`
|
||||
SlackAlert bool `json:"slack_alert"`
|
||||
SlackUrl string `json:"slack_url"`
|
||||
TelegramAlert bool `json:"telegram_alert" env:"SEMAPHORE_TELEGRAM_ALERT"`
|
||||
TelegramChat string `json:"telegram_chat" env:"SEMAPHORE_TELEGRAM_CHAT"`
|
||||
TelegramToken string `json:"telegram_token" env:"SEMAPHORE_TELEGRAM_TOKEN"`
|
||||
SlackAlert bool `json:"slack_alert" env:"SEMAPHORE_SLACK_ALERT"`
|
||||
SlackUrl string `json:"slack_url" env:"SEMAPHORE_SLACK_URL"`
|
||||
|
||||
// oidc settings
|
||||
OidcProviders map[string]oidcProvider `json:"oidc_providers"`
|
||||
|
||||
// task concurrency
|
||||
MaxParallelTasks int `json:"max_parallel_tasks"`
|
||||
MaxParallelTasks int `json:"max_parallel_tasks" rule:"^[0-9]{1,10}$" env:"SEMAPHORE_MAX_PARALLEL_TASKS"`
|
||||
|
||||
RunnerRegistrationToken string `json:"runner_registration_token"`
|
||||
RunnerRegistrationToken string `json:"runner_registration_token" env:"SEMAPHORE_RUNNER_REGISTRATION_TOKEN"`
|
||||
|
||||
// feature switches
|
||||
PasswordLoginDisable bool `json:"password_login_disable"`
|
||||
NonAdminCanCreateProject bool `json:"non_admin_can_create_project"`
|
||||
PasswordLoginDisable bool `json:"password_login_disable" env:"SEMAPHORE_PASSWORD_LOGIN_DISABLED"`
|
||||
NonAdminCanCreateProject bool `json:"non_admin_can_create_project" env:"SEMAPHORE_NON_ADMIN_CAN_CREATE_PROJECT"`
|
||||
|
||||
UseRemoteRunner bool `json:"use_remote_runner"`
|
||||
UseRemoteRunner bool `json:"use_remote_runner" env:"SEMAPHORE_USE_REMOTE_RUNNER"`
|
||||
|
||||
Runner RunnerSettings `json:"runner"`
|
||||
}
|
||||
@ -177,19 +186,14 @@ func (conf *ConfigType) ToJSON() ([]byte, error) {
|
||||
return json.MarshalIndent(&conf, " ", "\t")
|
||||
}
|
||||
|
||||
func (conf *ConfigType) GetAccessKeyEncryption() string {
|
||||
ret := os.Getenv("SEMAPHORE_ACCESS_KEY_ENCRYPTION")
|
||||
|
||||
if ret == "" {
|
||||
ret = conf.AccessKeyEncryption
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// ConfigInit reads in cli flags, and switches actions appropriately on them
|
||||
func ConfigInit(configPath string) {
|
||||
loadConfig(configPath)
|
||||
fmt.Println("Loading config")
|
||||
loadConfigFile(configPath)
|
||||
loadConfigEnvironment()
|
||||
loadConfigDefaults()
|
||||
|
||||
fmt.Println("Validating config")
|
||||
validateConfig()
|
||||
|
||||
var encryption []byte
|
||||
@ -206,7 +210,7 @@ func ConfigInit(configPath string) {
|
||||
}
|
||||
}
|
||||
|
||||
func loadConfig(configPath string) {
|
||||
func loadConfigFile(configPath string) {
|
||||
if configPath == "" {
|
||||
configPath = os.Getenv("SEMAPHORE_CONFIG_PATH")
|
||||
}
|
||||
@ -216,7 +220,7 @@ func loadConfig(configPath string) {
|
||||
|
||||
if configPath == "" {
|
||||
cwd, err := os.Getwd()
|
||||
exitOnConfigError(err)
|
||||
exitOnConfigFileError(err)
|
||||
paths := []string{
|
||||
path.Join(cwd, "config.json"),
|
||||
"/usr/local/etc/semaphore/config.json",
|
||||
@ -234,46 +238,215 @@ func loadConfig(configPath string) {
|
||||
decodeConfig(file)
|
||||
break
|
||||
}
|
||||
exitOnConfigError(err)
|
||||
exitOnConfigFileError(err)
|
||||
} else {
|
||||
p := configPath
|
||||
file, err := os.Open(p)
|
||||
exitOnConfigError(err)
|
||||
exitOnConfigFileError(err)
|
||||
decodeConfig(file)
|
||||
}
|
||||
}
|
||||
|
||||
func loadDefaultsToObject(obj interface{}) error {
|
||||
var t = reflect.TypeOf(obj)
|
||||
var v = reflect.ValueOf(obj)
|
||||
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
v = reflect.Indirect(v)
|
||||
}
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
fieldType := t.Field(i)
|
||||
fieldValue := v.Field(i)
|
||||
|
||||
if fieldType.Type.Kind() == reflect.Struct {
|
||||
err := loadDefaultsToObject(fieldValue.Addr())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
defaultVar := fieldType.Tag.Get("default")
|
||||
if defaultVar == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
setConfigValue(fieldValue, defaultVar)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadConfigDefaults() {
|
||||
|
||||
err := loadDefaultsToObject(Config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func castStringToInt(value string) int {
|
||||
|
||||
valueInt, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return valueInt
|
||||
|
||||
}
|
||||
|
||||
func castStringToBool(value string) bool {
|
||||
|
||||
var valueBool bool
|
||||
if value == "1" || strings.ToLower(value) == "true" {
|
||||
valueBool = true
|
||||
} else {
|
||||
valueBool = false
|
||||
}
|
||||
return valueBool
|
||||
|
||||
}
|
||||
|
||||
func setConfigValue(attribute reflect.Value, value interface{}) {
|
||||
|
||||
if attribute.IsValid() {
|
||||
switch attribute.Kind() {
|
||||
case reflect.Int:
|
||||
if reflect.ValueOf(value).Kind() != reflect.Int {
|
||||
value = castStringToInt(fmt.Sprintf("%v", reflect.ValueOf(value)))
|
||||
}
|
||||
case reflect.Bool:
|
||||
if reflect.ValueOf(value).Kind() != reflect.Bool {
|
||||
value = castStringToBool(fmt.Sprintf("%v", reflect.ValueOf(value)))
|
||||
}
|
||||
}
|
||||
attribute.Set(reflect.ValueOf(value))
|
||||
} else {
|
||||
panic(fmt.Errorf("got non-existent config attribute"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getConfigValue(path string) string {
|
||||
|
||||
attribute := reflect.ValueOf(Config)
|
||||
nested_path := strings.Split(path, ".")
|
||||
|
||||
for i, nested := range nested_path {
|
||||
attribute = reflect.Indirect(attribute).FieldByName(nested)
|
||||
lastDepth := len(nested_path) == i+1
|
||||
if !lastDepth && attribute.Kind() != reflect.Struct || lastDepth && attribute.Kind() == reflect.Invalid {
|
||||
panic(fmt.Errorf("got non-existent config attribute '%v'", path))
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v", attribute)
|
||||
}
|
||||
|
||||
func validate(value interface{}) error {
|
||||
var t = reflect.TypeOf(value)
|
||||
var v = reflect.ValueOf(value)
|
||||
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
v = reflect.Indirect(v)
|
||||
}
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
fieldType := t.Field(i)
|
||||
fieldValue := v.Field(i)
|
||||
|
||||
rule := fieldType.Tag.Get("rule")
|
||||
if rule == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var value string
|
||||
|
||||
if fieldType.Type.Kind() == reflect.Int {
|
||||
value = strconv.FormatInt(fieldValue.Int(), 10)
|
||||
} else if fieldType.Type.Kind() == reflect.Uint {
|
||||
value = strconv.FormatUint(fieldValue.Uint(), 10)
|
||||
} else {
|
||||
value = fieldValue.String()
|
||||
}
|
||||
|
||||
match, _ := regexp.MatchString(rule, value)
|
||||
if !match {
|
||||
return fmt.Errorf(
|
||||
"value of field '%v' is not valid! (Must match regex: '%v')",
|
||||
fieldType.Name, rule,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateConfig() {
|
||||
|
||||
validatePort()
|
||||
err := validate(Config)
|
||||
|
||||
if len(Config.TmpPath) == 0 {
|
||||
Config.TmpPath = "/tmp/semaphore"
|
||||
}
|
||||
|
||||
if Config.MaxParallelTasks < 1 {
|
||||
Config.MaxParallelTasks = 10
|
||||
}
|
||||
}
|
||||
|
||||
func validatePort() {
|
||||
|
||||
//TODO - why do we do this only with this variable?
|
||||
if len(os.Getenv("PORT")) > 0 {
|
||||
Config.Port = ":" + os.Getenv("PORT")
|
||||
}
|
||||
if len(Config.Port) == 0 {
|
||||
Config.Port = ":3000"
|
||||
}
|
||||
if !strings.HasPrefix(Config.Port, ":") {
|
||||
Config.Port = ":" + Config.Port
|
||||
}
|
||||
}
|
||||
|
||||
func exitOnConfigError(err error) {
|
||||
if err != nil {
|
||||
fmt.Println("Cannot Find configuration! Use --config parameter to point to a JSON file generated by `semaphore setup`.")
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func loadEnvironmentToObject(obj interface{}) error {
|
||||
var t = reflect.TypeOf(obj)
|
||||
var v = reflect.ValueOf(obj)
|
||||
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
v = reflect.Indirect(v)
|
||||
}
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
fieldType := t.Field(i)
|
||||
fieldValue := v.Field(i)
|
||||
|
||||
if fieldType.Type.Kind() == reflect.Struct {
|
||||
err := loadEnvironmentToObject(fieldValue.Addr().Interface())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
envVar := fieldType.Tag.Get("env")
|
||||
if envVar == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
envValue, exists := os.LookupEnv(envVar)
|
||||
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
setConfigValue(fieldValue, envValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadConfigEnvironment() {
|
||||
err := loadEnvironmentToObject(Config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func exitOnConfigError(msg string) {
|
||||
fmt.Println(msg)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func exitOnConfigFileError(err error) {
|
||||
if err != nil {
|
||||
exitOnConfigError("Cannot Find configuration! Use --config parameter to point to a JSON file generated by `semaphore setup`.")
|
||||
}
|
||||
}
|
||||
|
||||
@ -335,11 +508,6 @@ func CheckUpdate() (updateAvailable *github.RepositoryRelease, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// String returns dialect name for GORP.
|
||||
func (d DbDriver) String() string {
|
||||
return string(d)
|
||||
}
|
||||
|
||||
func (d *DbConfig) IsPresent() bool {
|
||||
return d.GetHostname() != ""
|
||||
}
|
||||
@ -451,7 +619,7 @@ func (conf *ConfigType) PrintDbInfo() {
|
||||
}
|
||||
}
|
||||
|
||||
func (conf *ConfigType) GetDialect() (dialect DbDriver, err error) {
|
||||
func (conf *ConfigType) GetDialect() (dialect string, err error) {
|
||||
if conf.Dialect == "" {
|
||||
switch {
|
||||
case conf.MySQL.IsPresent():
|
||||
@ -471,7 +639,7 @@ func (conf *ConfigType) GetDialect() (dialect DbDriver, err error) {
|
||||
}
|
||||
|
||||
func (conf *ConfigType) GetDBConfig() (dbConfig DbConfig, err error) {
|
||||
var dialect DbDriver
|
||||
var dialect string
|
||||
dialect, err = conf.GetDialect()
|
||||
|
||||
if err != nil {
|
||||
|
@ -1,28 +1,340 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidatePort(t *testing.T) {
|
||||
func mockError(msg string) {
|
||||
panic(msg)
|
||||
}
|
||||
|
||||
Config = new(ConfigType)
|
||||
Config.Port = ""
|
||||
validatePort()
|
||||
if Config.Port != ":3000" {
|
||||
t.Error("no port should get set to default")
|
||||
func TestValidate(t *testing.T) {
|
||||
var val struct {
|
||||
Test string `rule:"^\\d+$"`
|
||||
}
|
||||
val.Test = "45243524"
|
||||
|
||||
Config.Port = "4000"
|
||||
validatePort()
|
||||
if Config.Port != ":4000" {
|
||||
t.Error("Port without : suffix should have it added")
|
||||
}
|
||||
|
||||
os.Setenv("PORT", "5000")
|
||||
validatePort()
|
||||
if Config.Port != ":5000" {
|
||||
t.Error("Port value should be overwritten by env var, and it should be prefixed appropriately")
|
||||
err := validate(val)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadEnvironmentToObject(t *testing.T) {
|
||||
var val struct {
|
||||
Test string `env:"TEST_ENV_VAR"`
|
||||
Subfield struct {
|
||||
Value string `env:"TEST_VALUE_ENV_VAR"`
|
||||
}
|
||||
}
|
||||
|
||||
err := os.Setenv("TEST_ENV_VAR", "758478")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.Setenv("TEST_VALUE_ENV_VAR", "test_value")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = loadEnvironmentToObject(&val)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if val.Test != "758478" {
|
||||
t.Error("Invalid value")
|
||||
}
|
||||
|
||||
if val.Subfield.Value != "test_value" {
|
||||
t.Error("Invalid value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCastStringToInt(t *testing.T) {
|
||||
|
||||
var errMsg string = "Cast string => int failed"
|
||||
|
||||
if castStringToInt("5") != 5 {
|
||||
t.Error(errMsg)
|
||||
}
|
||||
if castStringToInt("0") != 0 {
|
||||
t.Error(errMsg)
|
||||
}
|
||||
if castStringToInt("-1") != -1 {
|
||||
t.Error(errMsg)
|
||||
}
|
||||
if castStringToInt("999") != 999 {
|
||||
t.Error(errMsg)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Errorf("Cast string => int did not panic on invalid input")
|
||||
}
|
||||
}()
|
||||
castStringToInt("xxx")
|
||||
|
||||
}
|
||||
|
||||
func TestCastStringToBool(t *testing.T) {
|
||||
|
||||
var errMsg string = "Cast string => bool failed"
|
||||
|
||||
if castStringToBool("1") != true {
|
||||
t.Error(errMsg)
|
||||
}
|
||||
if castStringToBool("0") != false {
|
||||
t.Error(errMsg)
|
||||
}
|
||||
if castStringToBool("true") != true {
|
||||
t.Error(errMsg)
|
||||
}
|
||||
if castStringToBool("false") != false {
|
||||
t.Error(errMsg)
|
||||
}
|
||||
if castStringToBool("xxx") != false {
|
||||
t.Error(errMsg)
|
||||
}
|
||||
if castStringToBool("") != false {
|
||||
t.Error(errMsg)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetConfigValue(t *testing.T) {
|
||||
|
||||
Config = new(ConfigType)
|
||||
|
||||
var testPort string = "1337"
|
||||
var testCookieHash string = "0Sn+edH3doJ4EO4Rl49Y0KrxjUkXuVtR5zKHGGWerxQ="
|
||||
var testMaxParallelTasks int = 5
|
||||
var testLdapNeedTls bool = true
|
||||
var testDbHost string = "192.168.0.1"
|
||||
|
||||
Config.Port = testPort
|
||||
Config.CookieHash = testCookieHash
|
||||
Config.MaxParallelTasks = testMaxParallelTasks
|
||||
Config.LdapNeedTLS = testLdapNeedTls
|
||||
Config.BoltDb.Hostname = testDbHost
|
||||
|
||||
if getConfigValue("Port") != testPort {
|
||||
t.Error("Could not get value for config attribute 'Port'!")
|
||||
}
|
||||
if getConfigValue("CookieHash") != testCookieHash {
|
||||
t.Error("Could not get value for config attribute 'CookieHash'!")
|
||||
}
|
||||
if getConfigValue("MaxParallelTasks") != fmt.Sprintf("%v", testMaxParallelTasks) {
|
||||
t.Error("Could not get value for config attribute 'MaxParallelTasks'!")
|
||||
}
|
||||
if getConfigValue("LdapNeedTLS") != fmt.Sprintf("%v", testLdapNeedTls) {
|
||||
t.Error("Could not get value for config attribute 'LdapNeedTLS'!")
|
||||
}
|
||||
if getConfigValue("BoltDb.Hostname") != fmt.Sprintf("%v", testDbHost) {
|
||||
t.Error("Could not get value for config attribute 'BoltDb.Hostname'!")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Error("Did not fail on non-existent config attribute!")
|
||||
}
|
||||
}()
|
||||
getConfigValue("NotExistent")
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Error("Did not fail on non-existent config attribute!")
|
||||
}
|
||||
}()
|
||||
getConfigValue("Not.Existent")
|
||||
|
||||
}
|
||||
|
||||
func TestSetConfigValue(t *testing.T) {
|
||||
|
||||
Config = new(ConfigType)
|
||||
|
||||
configValue := reflect.ValueOf(Config).Elem()
|
||||
|
||||
var testPort string = "1337"
|
||||
var testCookieHash string = "0Sn+edH3doJ4EO4Rl49Y0KrxjUkXuVtR5zKHGGWerxQ="
|
||||
var testMaxParallelTasks int = 5
|
||||
var testLdapNeedTls bool = true
|
||||
//var testDbHost string = "192.168.0.1"
|
||||
var testEmailSecure string = "1"
|
||||
var expectEmailSecure bool = true
|
||||
|
||||
setConfigValue(configValue.FieldByName("Port"), testPort)
|
||||
setConfigValue(configValue.FieldByName("CookieHash"), testCookieHash)
|
||||
setConfigValue(configValue.FieldByName("MaxParallelTasks"), testMaxParallelTasks)
|
||||
setConfigValue(configValue.FieldByName("LdapNeedTLS"), testLdapNeedTls)
|
||||
//setConfigValue(configValue.FieldByName("BoltDb.Hostname"), testDbHost)
|
||||
setConfigValue(configValue.FieldByName("EmailSecure"), testEmailSecure)
|
||||
|
||||
if Config.Port != testPort {
|
||||
t.Error("Could not set value for config attribute 'Port'!")
|
||||
}
|
||||
if Config.CookieHash != testCookieHash {
|
||||
t.Error("Could not set value for config attribute 'CookieHash'!")
|
||||
}
|
||||
if Config.MaxParallelTasks != testMaxParallelTasks {
|
||||
t.Error("Could not set value for config attribute 'MaxParallelTasks'!")
|
||||
}
|
||||
if Config.LdapNeedTLS != testLdapNeedTls {
|
||||
t.Error("Could not set value for config attribute 'LdapNeedTls'!")
|
||||
}
|
||||
//if Config.BoltDb.Hostname != testDbHost {
|
||||
// t.Error("Could not set value for config attribute 'BoltDb.Hostname'!")
|
||||
//}
|
||||
if Config.EmailSecure != expectEmailSecure {
|
||||
t.Error("Could not set value for config attribute 'EmailSecure'!")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Error("Did not fail on non-existent config attribute!")
|
||||
}
|
||||
}()
|
||||
setConfigValue(configValue.FieldByName("NotExistent"), "someValue")
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Error("Did not fail on non-existent config attribute!")
|
||||
}
|
||||
}()
|
||||
//setConfigValue(configValue.FieldByName("Not.Existent"), "someValue")
|
||||
|
||||
}
|
||||
|
||||
func TestLoadConfigEnvironmet(t *testing.T) {
|
||||
|
||||
Config = new(ConfigType)
|
||||
Config.Dialect = DbDriverBolt
|
||||
|
||||
var envPort string = "1337"
|
||||
var envCookieHash string = "0Sn+edH3doJ4EO4Rl49Y0KrxjUkXuVtR5zKHGGWerxQ="
|
||||
var envAccessKeyEncryption string = "1/wRYXQltDGwbzNZRP9ZfJb2IoWcn1hYrxA0vOdvVos="
|
||||
var envMaxParallelTasks string = "5"
|
||||
var expectMaxParallelTasks int = 5
|
||||
var expectLdapNeedTls bool = true
|
||||
var envLdapNeedTls string = "1"
|
||||
var envDbHost string = "192.168.0.1"
|
||||
|
||||
os.Setenv("SEMAPHORE_PORT", envPort)
|
||||
os.Setenv("SEMAPHORE_COOKIE_HASH", envCookieHash)
|
||||
os.Setenv("SEMAPHORE_ACCESS_KEY_ENCRYPTION", envAccessKeyEncryption)
|
||||
os.Setenv("SEMAPHORE_MAX_PARALLEL_TASKS", envMaxParallelTasks)
|
||||
os.Setenv("SEMAPHORE_LDAP_NEEDTLS", envLdapNeedTls)
|
||||
os.Setenv("SEMAPHORE_DB_HOST", envDbHost)
|
||||
|
||||
loadConfigEnvironment()
|
||||
|
||||
if Config.Port != envPort {
|
||||
t.Error("Setting 'Port' was not loaded from environment-vars!")
|
||||
}
|
||||
if Config.CookieHash != envCookieHash {
|
||||
t.Error("Setting 'CookieHash' was not loaded from environment-vars!")
|
||||
}
|
||||
if Config.AccessKeyEncryption != envAccessKeyEncryption {
|
||||
t.Error("Setting 'AccessKeyEncryption' was not loaded from environment-vars!")
|
||||
}
|
||||
if Config.MaxParallelTasks != expectMaxParallelTasks {
|
||||
t.Error("Setting 'MaxParallelTasks' was not loaded from environment-vars!")
|
||||
}
|
||||
if Config.LdapNeedTLS != expectLdapNeedTls {
|
||||
t.Error("Setting 'LdapNeedTLS' was not loaded from environment-vars!")
|
||||
}
|
||||
if Config.BoltDb.Hostname != envDbHost {
|
||||
t.Error("Setting 'BoltDb.Hostname' was not loaded from environment-vars!")
|
||||
}
|
||||
|
||||
//if Config.MySQL.Hostname == envDbHost || Config.Postgres.Hostname == envDbHost {
|
||||
// // inactive db-dialects could be set as they share the same env-vars; but should be ignored
|
||||
// t.Error("DB-Hostname was loaded for inactive DB-dialects!")
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
func TestLoadConfigDefaults(t *testing.T) {
|
||||
|
||||
Config = new(ConfigType)
|
||||
var errMsg string = "Failed to load config-default"
|
||||
|
||||
loadConfigDefaults()
|
||||
|
||||
if Config.Port != ":3000" {
|
||||
t.Error(errMsg)
|
||||
}
|
||||
if Config.TmpPath != "/tmp/semaphore" {
|
||||
t.Error(errMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func ensureConfigValidationFailure(t *testing.T, attribute string, value interface{}) {
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Errorf(
|
||||
"Config validation for attribute '%v' did not fail! (value '%v')",
|
||||
attribute, value,
|
||||
)
|
||||
}
|
||||
}()
|
||||
validateConfig()
|
||||
|
||||
}
|
||||
|
||||
func TestValidateConfig(t *testing.T) {
|
||||
//assert := assert.New(t)
|
||||
|
||||
Config = new(ConfigType)
|
||||
|
||||
var testPort string = ":3000"
|
||||
var testDbDialect = DbDriverBolt
|
||||
var testCookieHash string = "0Sn+edH3doJ4EO4Rl49Y0KrxjUkXuVtR5zKHGGWerxQ="
|
||||
var testMaxParallelTasks int = 0
|
||||
|
||||
Config.Port = testPort
|
||||
Config.Dialect = testDbDialect
|
||||
Config.CookieHash = testCookieHash
|
||||
Config.MaxParallelTasks = testMaxParallelTasks
|
||||
Config.GitClientId = GoGitClientId
|
||||
Config.CookieEncryption = testCookieHash
|
||||
Config.AccessKeyEncryption = testCookieHash
|
||||
validateConfig()
|
||||
|
||||
Config.Port = "INVALID"
|
||||
ensureConfigValidationFailure(t, "Port", Config.Port)
|
||||
|
||||
Config.Port = ":100000"
|
||||
ensureConfigValidationFailure(t, "Port", Config.Port)
|
||||
Config.Port = testPort
|
||||
|
||||
Config.MaxParallelTasks = -1
|
||||
ensureConfigValidationFailure(t, "MaxParallelTasks", Config.MaxParallelTasks)
|
||||
|
||||
ensureConfigValidationFailure(t, "MaxParallelTasks", Config.MaxParallelTasks)
|
||||
Config.MaxParallelTasks = testMaxParallelTasks
|
||||
|
||||
//Config.CookieHash = "\"0Sn+edH3doJ4EO4Rl49Y0KrxjUkXuVtR5zKHGGWerxQ=\"" // invalid with quotes (can happen when supplied as env-var)
|
||||
//ensureConfigValidationFailure(t, "CookieHash", Config.CookieHash)
|
||||
|
||||
//Config.CookieHash = "!)394340"
|
||||
//ensureConfigValidationFailure(t, "CookieHash", Config.CookieHash)
|
||||
|
||||
//Config.CookieHash = ""
|
||||
//ensureConfigValidationFailure(t, "CookieHash", Config.CookieHash)
|
||||
|
||||
//Config.CookieHash = "TQwjDZ5fIQtaIw==" // valid b64, but too small
|
||||
//ensureConfigValidationFailure(t, "CookieHash", Config.CookieHash)
|
||||
Config.CookieHash = testCookieHash
|
||||
|
||||
Config.Dialect = "someOtherDB"
|
||||
ensureConfigValidationFailure(t, "Dialect", Config.Dialect)
|
||||
Config.Dialect = testDbDialect
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user