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  }