Semaphore/util/config.go

349 lines
8.3 KiB
Go
Raw Normal View History

2016-01-05 00:32:53 +01:00
package util
import (
"encoding/base64"
2016-01-05 00:32:53 +01:00
"encoding/json"
2020-11-28 22:49:44 +01:00
"errors"
2016-01-05 00:32:53 +01:00
"fmt"
"io"
"net/url"
2016-03-16 22:49:43 +01:00
"os"
2016-04-24 20:11:43 +02:00
"path"
"strings"
"github.com/gorilla/securecookie"
2016-01-05 00:32:53 +01:00
)
// Cookie is a runtime generated secure cookie used for authentication
var Cookie *securecookie.SecureCookie
// WebHostURL is the public route to the semaphore server
2017-05-20 16:14:36 +02:00
var WebHostURL *url.URL
2016-01-05 00:32:53 +01:00
type DbDriver string
2020-11-28 22:49:44 +01:00
const (
2021-09-22 05:43:19 +02:00
DbDriverMySQL DbDriver = "mysql"
DbDriverBolt DbDriver = "bolt"
DbDriverPostgres DbDriver = "postgres"
2020-11-28 22:49:44 +01:00
)
type DbConfig struct {
2021-09-22 05:43:19 +02:00
Dialect DbDriver `json:"-"`
Hostname string `json:"host"`
Username string `json:"user"`
Password string `json:"pass"`
DbName string `json:"name"`
Options map[string]string `json:"options"`
2016-01-05 00:32:53 +01:00
}
type ldapMappings struct {
DN string `json:"dn"`
Mail string `json:"mail"`
UID string `json:"uid"`
CN string `json:"cn"`
}
type VariablesPassingMethod string
const (
VariablesPassingNone VariablesPassingMethod = "none"
VariablesPassingEnv VariablesPassingMethod = "env_vars"
VariablesPassingExtra VariablesPassingMethod = "extra_vars"
VariablesPassingBoth VariablesPassingMethod = ""
)
//ConfigType mapping between Config and the json file that sets it
type ConfigType struct {
2021-09-22 05:43:19 +02:00
MySQL DbConfig `json:"mysql"`
BoltDb DbConfig `json:"bolt"`
Postgres DbConfig `json:"postgres"`
2020-11-28 22:49:44 +01:00
Dialect DbDriver `json:"dialect"`
2016-01-05 00:32:53 +01:00
// Format `:port_num` eg, :3000
2018-05-14 21:37:07 +02:00
// if : is missing it will be corrected
2017-05-20 16:14:36 +02:00
Port string `json:"port"`
2016-01-05 00:32:53 +01:00
2018-05-14 21:37:07 +02:00
// Interface ip, put in front of the port.
// defaults to empty
Interface string `json:"interface"`
// semaphore stores ephemeral projects here
2016-01-05 00:32:53 +01:00
TmpPath string `json:"tmp_path"`
// cookie hashing & encryption
2021-09-22 05:43:19 +02:00
CookieHash string `json:"cookie_hash"`
CookieEncryption string `json:"cookie_encryption"`
AccessKeyEncryption string `json:"access_key_encryption"`
2017-02-22 09:46:42 +01:00
2017-04-18 16:36:09 +02:00
// email alerting
2021-09-22 05:43:19 +02:00
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"`
2017-02-22 09:46:42 +01:00
2017-04-18 16:36:09 +02:00
// web host
2017-02-22 09:46:42 +01:00
WebHost string `json:"web_host"`
2017-04-18 16:36:09 +02:00
// ldap settings
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"`
LdapMappings ldapMappings `json:"ldap_mappings"`
2017-04-04 13:49:00 +02:00
2017-04-18 16:36:09 +02:00
// telegram alerting
2017-03-22 08:22:09 +01:00
TelegramChat string `json:"telegram_chat"`
TelegramToken string `json:"telegram_token"`
// task concurrency
ConcurrencyMode string `json:"concurrency_mode"`
MaxParallelTasks int `json:"max_parallel_tasks"`
// configType field ordering with bools at end reduces struct size
// (maligned check)
// feature switches
EmailAlert bool `json:"email_alert"`
2021-09-22 05:43:19 +02:00
EmailSecure bool `json:"email_secure"`
TelegramAlert bool `json:"telegram_alert"`
LdapEnable bool `json:"ldap_enable"`
LdapNeedTLS bool `json:"ldap_needtls"`
SshConfigPath string `json:"ssh_config_path"`
// VariablesPassingMethod defines how Semaphore will pass variables to Ansible.
// Default both via environment variables and via extra vars.
VariablesPassingMethod VariablesPassingMethod `json:"variables_passing_method"`
2021-10-18 14:41:54 +02:00
2016-01-05 00:32:53 +01:00
}
//Config exposes the application configuration storage for use in the application
var Config *ConfigType
// ToJSON returns a JSON string of the config
2021-08-25 22:12:19 +02:00
func (conf *ConfigType) ToJSON() ([]byte, error) {
return json.MarshalIndent(&conf, " ", "\t")
}
2016-01-05 00:32:53 +01:00
// ConfigInit reads in cli flags, and switches actions appropriately on them
2021-08-25 22:12:19 +02:00
func ConfigInit(configPath string) {
loadConfig(configPath)
validateConfig()
var encryption []byte
hash, _ := base64.StdEncoding.DecodeString(Config.CookieHash)
if len(Config.CookieEncryption) > 0 {
encryption, _ = base64.StdEncoding.DecodeString(Config.CookieEncryption)
}
Cookie = securecookie.New(hash, encryption)
2017-05-20 16:14:36 +02:00
WebHostURL, _ = url.Parse(Config.WebHost)
2017-05-20 16:25:41 +02:00
if len(WebHostURL.String()) == 0 {
WebHostURL = nil
}
2016-01-05 00:32:53 +01:00
}
2021-08-25 22:12:19 +02:00
func loadConfig(configPath string) {
if configPath == "" {
configPath = os.Getenv("SEMAPHORE_CONFIG_PATH")
}
//If the configPath option has been set try to load and decode it
//var usedPath string
if configPath == "" {
// if no configPath look in the cwd
cwd, err := os.Getwd()
exitOnConfigError(err)
defaultPath := path.Join(cwd, "config.json")
file, err := os.Open(defaultPath)
exitOnConfigError(err)
decodeConfig(file)
//usedPath = defaultPath
} else {
path := configPath
file, err := os.Open(path)
exitOnConfigError(err)
decodeConfig(file)
//usedPath = path
}
//fmt.Println("Using config file: " + usedPath)
}
func validateConfig() {
validatePort()
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 {
2021-08-26 11:39:31 +02:00
fmt.Println("Cannot Find configuration! Use --config parameter to point to a JSON file generated by `semaphore setup`.")
os.Exit(1)
}
}
func decodeConfig(file io.Reader) {
if err := json.NewDecoder(file).Decode(&Config); err != nil {
fmt.Println("Could not decode configuration!")
panic(err)
}
}
func mapToQueryString(m map[string]string) (str string) {
for option, value := range m {
if str != "" {
str += "&"
}
str += option + "=" + value
}
if str != "" {
str = "?" + str
}
return
}
2021-08-24 17:20:34 +02:00
// String returns dialect name for GORP.
2020-11-28 22:49:44 +01:00
func (d DbDriver) String() string {
return string(d)
2020-11-28 22:49:44 +01:00
}
func (d *DbConfig) IsPresent() bool {
2020-11-28 22:49:44 +01:00
return d.Hostname != ""
}
func (d *DbConfig) HasSupportMultipleDatabases() bool {
2020-12-04 09:39:56 +01:00
return true
2020-11-28 22:49:44 +01:00
}
func (d *DbConfig) GetConnectionString(includeDbName bool) (connectionString string, err error) {
switch d.Dialect {
case DbDriverBolt:
connectionString = d.Hostname
2020-11-28 22:49:44 +01:00
case DbDriverMySQL:
if includeDbName {
connectionString = fmt.Sprintf(
"%s:%s@tcp(%s)/%s",
2020-11-28 22:49:44 +01:00
d.Username,
d.Password,
d.Hostname,
d.DbName)
} else {
connectionString = fmt.Sprintf(
"%s:%s@tcp(%s)/",
2020-11-28 22:49:44 +01:00
d.Username,
d.Password,
d.Hostname)
}
options := map[string]string{
"parseTime": "true",
"interpolateParams": "true",
}
for v, k := range d.Options {
options[v] = k
}
connectionString += mapToQueryString(options)
2021-08-24 17:20:34 +02:00
case DbDriverPostgres:
if includeDbName {
connectionString = fmt.Sprintf(
"postgres://%s:%s@%s/%s",
d.Username,
2021-11-02 19:43:56 +01:00
url.QueryEscape(d.Password),
2021-08-24 17:20:34 +02:00
d.Hostname,
d.DbName)
} else {
connectionString = fmt.Sprintf(
"postgres://%s:%s@%s",
2021-08-24 17:20:34 +02:00
d.Username,
2021-11-02 19:43:56 +01:00
url.QueryEscape(d.Password),
2021-08-24 17:20:34 +02:00
d.Hostname)
}
connectionString += mapToQueryString(d.Options)
2020-11-28 22:49:44 +01:00
default:
err = fmt.Errorf("unsupported database driver: %s", d.Dialect)
}
return
}
func (conf *ConfigType) GetDialect() (dialect DbDriver, err error) {
if conf.Dialect == "" {
switch {
case conf.MySQL.IsPresent():
dialect = DbDriverMySQL
case conf.BoltDb.IsPresent():
dialect = DbDriverBolt
case conf.Postgres.IsPresent():
dialect = DbDriverPostgres
default:
err = errors.New("database configuration not found")
}
return
}
dialect = conf.Dialect
return
}
2020-11-28 22:49:44 +01:00
func (conf *ConfigType) GetDBConfig() (dbConfig DbConfig, err error) {
var dialect DbDriver
dialect, err = conf.GetDialect()
if err != nil {
return
}
switch dialect {
case DbDriverBolt:
dbConfig = conf.BoltDb
case DbDriverPostgres:
2021-08-24 17:20:34 +02:00
dbConfig = conf.Postgres
case DbDriverMySQL:
dbConfig = conf.MySQL
2020-11-28 22:49:44 +01:00
default:
err = errors.New("database configuration not found")
}
2021-08-28 18:24:54 +02:00
dbConfig.Dialect = dialect
2020-11-28 22:49:44 +01:00
return
}
2021-08-31 01:02:41 +02:00
//GenerateSecrets generates cookie secret during setup
func (conf *ConfigType) GenerateSecrets() {
hash := securecookie.GenerateRandomKey(32)
encryption := securecookie.GenerateRandomKey(32)
2021-08-31 01:02:41 +02:00
accessKeyEncryption := securecookie.GenerateRandomKey(32)
2016-01-05 00:32:53 +01:00
conf.CookieHash = base64.StdEncoding.EncodeToString(hash)
conf.CookieEncryption = base64.StdEncoding.EncodeToString(encryption)
2021-08-31 01:02:41 +02:00
conf.AccessKeyEncryption = base64.StdEncoding.EncodeToString(accessKeyEncryption)
2016-01-05 00:32:53 +01:00
}