github.com/sharovik/devbot@v1.0.1-0.20240308094637-4a0387c40516/internal/config/config.go (about) 1 package config 2 3 import ( 4 "flag" 5 "fmt" 6 "os" 7 "strconv" 8 "strings" 9 "time" 10 _ "time/tzdata" 11 12 "github.com/joho/godotenv" 13 "github.com/sharovik/devbot/internal/log" 14 "github.com/sharovik/orm/clients" 15 ) 16 17 // MessagesAPIConfig struct object 18 type MessagesAPIConfig struct { 19 BaseURL string 20 OAuthToken string 21 WebAPIOAuthToken string 22 BotUserID string 23 BotName string 24 MainChannelAlias string 25 MainChannelID string 26 Type string 27 } 28 29 // BitBucketConfig struct for bitbucket config 30 type BitBucketConfig struct { 31 ClientID string 32 ClientSecret string 33 ReleaseChannelMessageEnabled bool 34 ReleaseChannel string 35 CurrentUserUUID string 36 DefaultWorkspace string 37 DefaultMainBranch string 38 RequiredReviewers []BitBucketReviewer 39 } 40 41 // BitBucketReviewer is used for identifying of the reviewer user 42 type BitBucketReviewer struct { 43 UUID string 44 SlackUID string 45 } 46 47 // HTTPClient the configuration for the http client 48 type HTTPClient struct { 49 RequestTimeout int64 50 TLSHandshakeTimeout int64 51 InsecureSkipVerify bool 52 } 53 54 // Config configuration object 55 type Config struct { 56 appEnv string 57 Timezone *time.Location 58 LearningEnabled bool 59 MessagesAPIConfig MessagesAPIConfig 60 BitBucketConfig BitBucketConfig 61 initialised bool 62 Database clients.DatabaseConfig 63 HTTPClient HTTPClient 64 LogConfig log.Config 65 } 66 67 // cfg variable which contains initialised Config 68 var ( 69 cfg Config 70 envPath string 71 ) 72 73 const ( 74 //EnvironmentTesting constant for testing environment 75 EnvironmentTesting = "testing" 76 77 envAppEnv = "APP_ENV" 78 envMessagesAPIType = "MESSAGES_API_TYPE" 79 80 //EnvUserID env variable for user ID 81 EnvUserID = "MESSAGES_API_USER_ID" 82 83 //EnvMainChannelID env variable for main channel ID 84 EnvMainChannelID = "MESSAGES_API_MAIN_CHANNEL_ID" 85 86 //EnvMainChannelAlias env variable for main channel alias 87 EnvMainChannelAlias = "MESSAGES_API_MAIN_CHANNEL_ALIAS" 88 89 //EnvBotName env variable for bot name 90 EnvBotName = "MESSAGES_API_BOT_NAME" 91 92 //EnvBaseURL env variable for base url 93 EnvBaseURL = "MESSAGES_API_BASE_URL" 94 95 //EnvOAuthToken env variable for message oauth token 96 EnvOAuthToken = "MESSAGES_API_OAUTH_TOKEN" 97 98 //EnvWebAPIOAuthToken env variable for message web api oauth token. 99 EnvWebAPIOAuthToken = "MESSAGES_API_WEB_API_OAUTH_TOKEN" 100 101 //DatabaseConnection env variable for database connection type 102 DatabaseConnection = "DATABASE_CONNECTION" 103 104 //DatabaseHost env variable for database host 105 DatabaseHost = "DATABASE_HOST" 106 107 //DatabaseUsername env variable for database username 108 DatabaseUsername = "DATABASE_USERNAME" 109 110 //DatabasePassword env variable for database password 111 DatabasePassword = "DATABASE_PASSWORD" 112 113 //DatabaseName env variable for database name 114 DatabaseName = "DATABASE_NAME" 115 116 //BitBucketClientID the client id which is used fo oauth token generation 117 BitBucketClientID = "BITBUCKET_CLIENT_ID" 118 119 //BitBucketClientSecret the client secret which is used fo oauth token generation 120 BitBucketClientSecret = "BITBUCKET_CLIENT_SECRET" 121 122 //BitBucketRequiredReviewers the required reviewers list separated by comma 123 BitBucketRequiredReviewers = "BITBUCKET_REQUIRED_REVIEWERS" 124 125 //BitBucketReleaseChannel the release channel ID. To that channel bot will publish the result of the release 126 BitBucketReleaseChannel = "BITBUCKET_RELEASE_CHANNEL" 127 128 //BitBucketReleaseChannelMessageEnabled the release channel ID. To that channel bot will publish the result of the release 129 BitBucketReleaseChannelMessageEnabled = "BITBUCKET_RELEASE_CHANNEL_MESSAGE_ENABLE" 130 131 //BitBucketCurrentUserUUID the current BitBucket user UUID the client credentials of which we are using in BITBUCKET_CLIENT_ID and BITBUCKET_CLIENT_SECRET 132 BitBucketCurrentUserUUID = "BITBUCKET_USER_UUID" 133 134 //BitBucketDefaultWorkspace the default workspace which can be used in the functionality, once you don't have PR link, from where to get this information 135 BitBucketDefaultWorkspace = "BITBUCKET_DEFAULT_WORKSPACE" 136 137 //BitBucketDefaultMainBranch the default main branch which can be used in cases, when you can't get the information from the PR link 138 BitBucketDefaultMainBranch = "BITBUCKET_DEFAULT_MAIN_BRANCH" 139 140 httpClientRequestTimeout = "HTTP_CLIENT_REQUEST_TIMEOUT" 141 httpClientTLSHandshakeTimeout = "HTTP_CLIENT_TLS_HANDSHAKE_TIMEOUT" 142 httpClientInsecureSkipVerify = "HTTP_CLIENT_INSECURE_SKIP_VERIFY" 143 144 //learningEnabled enables or disables the learning mode. If enabled, the bot will try to ask in the main channel, how to react on that message. 145 learningEnabled = "LEARNING_MODE_ENABLED" 146 147 envLogOutput = "LOG_OUTPUT" 148 envLogLevel = "LOG_LEVEL" 149 envLogFieldContext = "LOG_FIELD_CONTEXT" 150 envLogFieldLevelName = "LOG_FIELD_LEVEL_NAME" 151 envLogFieldErrorMessage = "LOG_FIELD_ERROR_MESSAGE" 152 153 //MessagesAPITypeSlack message messages API type 154 MessagesAPITypeSlack = "slack" 155 156 defaultMainChannelAlias = "general" 157 defaultBotName = "devbot" 158 defaultMessagesAPIType = MessagesAPITypeSlack 159 defaultDatabaseConnection = "sqlite" 160 defaultEnvFilePath = "./.env" 161 defaultEnvFileRootProjectPath = "./../../.env" 162 163 //AWSSecretsBucket the bucket for secrets manager. If it's specified then the secret values will be loaded 164 AWSSecretsBucket = "AWS_SECRETS_BUCKET" 165 166 //AWSSecretsRegion the region selected for the aws session 167 AWSSecretsRegion = "AWS_REGION" 168 169 envTimezone = "APP_TIMEZONE" 170 ) 171 172 var DefaultTimezone = time.UTC 173 174 // Init initialise configuration for this project 175 func Init() (Config, error) { 176 if !cfg.IsInitialised() { 177 178 envPath = defaultEnvFilePath 179 if _, err := os.Stat(envPath); err != nil { 180 envPath = defaultEnvFileRootProjectPath 181 } 182 183 if err := godotenv.Load(envPath); err != nil { 184 return Config{}, err 185 } 186 187 httpC, err := initHTTPClientConfig() 188 if err != nil { 189 return Config{}, err 190 } 191 192 cfg = Config{ 193 appEnv: os.Getenv(envAppEnv), 194 LearningEnabled: getBoolValue(learningEnabled), 195 MessagesAPIConfig: initMessagesAPIConfig(), 196 BitBucketConfig: initBitbucketConfig(), 197 initialised: true, 198 HTTPClient: httpC, 199 Database: initDatabaseConfig(), 200 LogConfig: initLogConfig(), 201 } 202 203 cfg.loadTimezone() 204 205 cfg = loadSecrets(cfg) 206 207 return cfg, nil 208 } 209 210 return cfg, nil 211 } 212 213 func loadSecrets(cfg Config) Config { 214 if os.Getenv(AWSSecretsBucket) != "" { 215 secrets, _ := GetSecret(os.Getenv(AWSSecretsBucket), os.Getenv(AWSSecretsRegion)) 216 cfg.BitBucketConfig.ClientID = secrets.BitBucketClientID 217 cfg.BitBucketConfig.ClientSecret = secrets.BitBucketClientSecret 218 cfg.MessagesAPIConfig.OAuthToken = secrets.MessagesAPIOAuthToken 219 cfg.MessagesAPIConfig.WebAPIOAuthToken = secrets.MessagesAPIWebAPIOAuthToken 220 } 221 222 return cfg 223 } 224 225 func initHTTPClientConfig() (c HTTPClient, err error) { 226 var requestTimeout, tLSHandshakeTimeout int64 227 if os.Getenv(httpClientRequestTimeout) != "" { 228 requestTimeout, err = strconv.ParseInt(os.Getenv(httpClientRequestTimeout), 10, 64) 229 if err != nil { 230 return HTTPClient{}, err 231 } 232 } 233 234 if os.Getenv(httpClientTLSHandshakeTimeout) != "" { 235 tLSHandshakeTimeout, _ = strconv.ParseInt(os.Getenv(httpClientTLSHandshakeTimeout), 10, 64) 236 if err != nil { 237 return HTTPClient{}, err 238 } 239 } 240 241 return HTTPClient{ 242 RequestTimeout: requestTimeout, 243 TLSHandshakeTimeout: tLSHandshakeTimeout, 244 InsecureSkipVerify: getBoolValue(httpClientInsecureSkipVerify), 245 }, nil 246 } 247 248 func initBitbucketConfig() BitBucketConfig { 249 return BitBucketConfig{ 250 ClientID: os.Getenv(BitBucketClientID), 251 ClientSecret: os.Getenv(BitBucketClientSecret), 252 ReleaseChannel: os.Getenv(BitBucketReleaseChannel), 253 CurrentUserUUID: os.Getenv(BitBucketCurrentUserUUID), 254 DefaultWorkspace: os.Getenv(BitBucketDefaultWorkspace), 255 DefaultMainBranch: os.Getenv(BitBucketDefaultMainBranch), 256 ReleaseChannelMessageEnabled: getBoolValue(BitBucketReleaseChannelMessageEnabled), 257 RequiredReviewers: PrepareBitBucketReviewers(os.Getenv(BitBucketRequiredReviewers)), 258 } 259 } 260 261 func initMessagesAPIConfig() MessagesAPIConfig { 262 oAuthToken := os.Getenv(EnvOAuthToken) 263 webAPIOAuthToken := os.Getenv(EnvWebAPIOAuthToken) 264 mainChannelAlias := defaultMainChannelAlias 265 if os.Getenv(EnvMainChannelAlias) != "" { 266 mainChannelAlias = os.Getenv(EnvMainChannelAlias) 267 } 268 269 botName := defaultBotName 270 if os.Getenv(EnvBotName) != "" { 271 botName = os.Getenv(EnvBotName) 272 } 273 274 messagesAPIType := defaultMessagesAPIType 275 if os.Getenv(envMessagesAPIType) != "" { 276 messagesAPIType = os.Getenv(envMessagesAPIType) 277 } 278 279 return MessagesAPIConfig{ 280 BaseURL: os.Getenv(EnvBaseURL), 281 OAuthToken: oAuthToken, 282 WebAPIOAuthToken: webAPIOAuthToken, 283 MainChannelAlias: mainChannelAlias, 284 MainChannelID: os.Getenv(EnvMainChannelID), 285 BotUserID: os.Getenv(EnvUserID), 286 BotName: botName, 287 Type: messagesAPIType, 288 } 289 } 290 291 func initDatabaseConfig() clients.DatabaseConfig { 292 dbConnection := defaultDatabaseConnection 293 if os.Getenv(DatabaseConnection) != "" { 294 dbConnection = os.Getenv(DatabaseConnection) 295 } 296 297 return clients.DatabaseConfig{ 298 Type: dbConnection, 299 Host: os.Getenv(DatabaseHost), 300 Username: os.Getenv(DatabaseUsername), 301 Password: os.Getenv(DatabasePassword), 302 Database: os.Getenv(DatabaseName), 303 } 304 } 305 306 func initLogConfig() log.Config { 307 fieldContext := log.FieldContext 308 if os.Getenv(envLogFieldContext) != "" { 309 fieldContext = os.Getenv(envLogFieldContext) 310 } 311 312 fieldLevelName := log.FieldLevelName 313 if os.Getenv(envLogFieldLevelName) != "" { 314 fieldLevelName = os.Getenv(envLogFieldLevelName) 315 } 316 317 fieldErrorMessage := log.FieldErrorMessage 318 if os.Getenv(envLogFieldErrorMessage) != "" { 319 fieldErrorMessage = os.Getenv(envLogFieldErrorMessage) 320 } 321 322 return log.Config{ 323 Env: os.Getenv(envAppEnv), 324 Level: os.Getenv(envLogLevel), 325 Output: os.Getenv(envLogOutput), 326 FieldContext: fieldContext, 327 FieldLevelName: fieldLevelName, 328 FieldErrorMessage: fieldErrorMessage, 329 } 330 } 331 332 // IsInitialised method which retrieves current status of object 333 func (c Config) IsInitialised() bool { 334 return c.initialised 335 } 336 337 // GetAppEnv retrieve current environment 338 func (c Config) GetAppEnv() string { 339 if flag.Lookup("test.v") != nil { 340 return EnvironmentTesting 341 } 342 343 return c.appEnv 344 } 345 346 // SetToEnv method for saving env variable into memory + into .env file 347 func (c Config) SetToEnv(field string, value string, writeToEnvFile bool) error { 348 349 if writeToEnvFile { 350 f, err := os.OpenFile(envPath, 351 os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 352 if err != nil { 353 return err 354 } 355 356 defer f.Close() 357 if _, err = f.WriteString(fmt.Sprintf("\n%s=%s", field, value)); err != nil { 358 return err 359 } 360 } 361 362 if err := os.Setenv(field, value); err != nil { 363 return err 364 } 365 366 return nil 367 } 368 369 // PrepareBitBucketReviewers method retrieves the list of bitbucket reviewers 370 func PrepareBitBucketReviewers(reviewers string) []BitBucketReviewer { 371 entries := strings.Split(reviewers, ",") 372 373 var result []BitBucketReviewer 374 for _, value := range entries { 375 userInfo := strings.Split(value, ":") 376 377 if len(userInfo) != 0 && userInfo[0] != "" { 378 result = append(result, BitBucketReviewer{ 379 SlackUID: userInfo[0], 380 UUID: userInfo[1], 381 }) 382 } 383 } 384 385 return result 386 } 387 388 func getBoolValue(field string) bool { 389 res := false 390 if os.Getenv(field) == "true" || os.Getenv(field) == "1" { 391 res = true 392 } 393 394 return res 395 } 396 397 func (c *Config) loadTimezone() { 398 if os.Getenv(envTimezone) != "" { 399 c.Timezone, _ = time.LoadLocation(os.Getenv(envTimezone)) 400 } 401 402 if c.Timezone == nil { 403 c.Timezone = DefaultTimezone 404 } 405 } 406 407 func (c Config) GetTimezone() (timeZone *time.Location) { 408 if c.Timezone == nil { 409 c.Timezone = DefaultTimezone 410 } 411 412 return c.Timezone 413 }