- Standalone SQS-service repository - Multi-tenant message queue service, AWS SQS compatible - Based on GoAws, with mutable tenants, auth, WebUI, Redis persistence - Ready for independent development and deployment - See doc/ and README.md for architecture and usage
197 lines
6.6 KiB
Go
197 lines
6.6 KiB
Go
package conf
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"shared-sqs/app/models"
|
|
"shared-sqs/app/utils"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/ghodss/yaml"
|
|
)
|
|
|
|
var envs map[string]models.Environment
|
|
|
|
func LoadYamlConfig(filename string, env string) []string {
|
|
ports := []string{"4100"}
|
|
|
|
// Гарантируем что дефолты всегда применяются, даже если конфиг не найден
|
|
defer applyEnvironmentDefaults()
|
|
|
|
if filename == "" {
|
|
root, _ := filepath.Abs(".")
|
|
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
|
if "goaws.yaml" == d.Name() {
|
|
filename = path
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil || filename == "" {
|
|
log.Warn("Failure to find default config file")
|
|
return ports
|
|
}
|
|
}
|
|
|
|
filename, _ = filepath.Abs(filename)
|
|
if _, err := os.Stat(filename); err != nil {
|
|
log.Warnf("Failure to find config file: %s", filename)
|
|
return ports
|
|
}
|
|
|
|
log.Infof("Loading config file: %s", filename)
|
|
yamlFile, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
return ports
|
|
}
|
|
|
|
err = yaml.Unmarshal(yamlFile, &envs)
|
|
if err != nil {
|
|
log.Errorf("err: %v\n", err)
|
|
return ports
|
|
}
|
|
if env == "" {
|
|
env = "Local"
|
|
}
|
|
|
|
if envs[env].Region == "" {
|
|
models.CurrentEnvironment.Region = "local"
|
|
}
|
|
|
|
models.CurrentEnvironment = envs[env]
|
|
|
|
if envs[env].Port != "" {
|
|
ports = []string{envs[env].Port}
|
|
}
|
|
|
|
models.LogMessages = false
|
|
models.LogFile = "./goaws_messages.log"
|
|
if envs[env].LogToFile == true {
|
|
models.LogMessages = true
|
|
if envs[env].LogFile != "" {
|
|
models.LogFile = envs[env].LogFile
|
|
}
|
|
}
|
|
|
|
// Дефолты применяются через defer applyEnvironmentDefaults() в начале функции
|
|
|
|
models.SyncQueues.Lock()
|
|
for _, queue := range envs[env].Queues {
|
|
queueUrl := "http://" + models.CurrentEnvironment.Host + ":" + models.CurrentEnvironment.Port +
|
|
"/" + models.CurrentEnvironment.AccountID + "/" + queue.Name
|
|
if models.CurrentEnvironment.Region != "" {
|
|
queueUrl = "http://" + models.CurrentEnvironment.Region + "." + models.CurrentEnvironment.Host + ":" +
|
|
models.CurrentEnvironment.Port + "/" + models.CurrentEnvironment.AccountID + "/" + queue.Name
|
|
}
|
|
queueArn := "arn:aws:sqs:" + models.CurrentEnvironment.Region + ":" + models.CurrentEnvironment.AccountID + ":" + queue.Name
|
|
|
|
if queue.ReceiveMessageWaitTimeSeconds == 0 {
|
|
queue.ReceiveMessageWaitTimeSeconds = models.CurrentEnvironment.QueueAttributeDefaults.ReceiveMessageWaitTimeSeconds
|
|
}
|
|
if queue.MaximumMessageSize == 0 {
|
|
queue.MaximumMessageSize = models.CurrentEnvironment.QueueAttributeDefaults.MaximumMessageSize
|
|
}
|
|
if queue.VisibilityTimeout == 0 {
|
|
queue.VisibilityTimeout = models.CurrentEnvironment.QueueAttributeDefaults.VisibilityTimeout
|
|
}
|
|
if queue.MessageRetentionPeriod == 0 {
|
|
queue.MessageRetentionPeriod = models.CurrentEnvironment.QueueAttributeDefaults.MessageRetentionPeriod
|
|
}
|
|
|
|
models.SyncQueues.Queues[queue.Name] = &models.Queue{
|
|
Name: queue.Name,
|
|
VisibilityTimeout: queue.VisibilityTimeout,
|
|
Arn: queueArn,
|
|
URL: queueUrl,
|
|
ReceiveMessageWaitTimeSeconds: queue.ReceiveMessageWaitTimeSeconds,
|
|
MaximumMessageSize: queue.MaximumMessageSize,
|
|
MessageRetentionPeriod: queue.MessageRetentionPeriod,
|
|
IsFIFO: utils.HasFIFOQueueName(queue.Name),
|
|
EnableDuplicates: models.CurrentEnvironment.EnableDuplicates,
|
|
Duplicates: make(map[string]time.Time),
|
|
}
|
|
}
|
|
|
|
// Второй проход — устанавливаем RedrivePolicy, чтобы DLQ были доступны независимо от порядка
|
|
for _, queue := range envs[env].Queues {
|
|
q := models.SyncQueues.Queues[queue.Name]
|
|
if queue.RedrivePolicy != "" {
|
|
err := setQueueRedrivePolicy(models.SyncQueues.Queues, q, queue.RedrivePolicy)
|
|
if err != nil {
|
|
log.Errorf("err: %s", err)
|
|
return ports
|
|
}
|
|
}
|
|
}
|
|
|
|
models.SyncQueues.Unlock()
|
|
|
|
return ports
|
|
}
|
|
|
|
// applyEnvironmentDefaults — применяет дефолтные значения для QueueAttributeDefaults,
|
|
// AccountID и Host. Вызывается через defer в LoadYamlConfig, чтобы дефолты
|
|
// устанавливались при любом раннем return (например, если конфиг не найден).
|
|
func applyEnvironmentDefaults() {
|
|
if models.CurrentEnvironment.QueueAttributeDefaults.VisibilityTimeout <= 0 {
|
|
models.CurrentEnvironment.QueueAttributeDefaults.VisibilityTimeout = 30
|
|
}
|
|
if models.CurrentEnvironment.QueueAttributeDefaults.MaximumMessageSize <= 0 {
|
|
models.CurrentEnvironment.QueueAttributeDefaults.MaximumMessageSize = 262144 // 256K
|
|
}
|
|
if models.CurrentEnvironment.QueueAttributeDefaults.MessageRetentionPeriod <= 0 {
|
|
models.CurrentEnvironment.QueueAttributeDefaults.MessageRetentionPeriod = 345600 // 4 days
|
|
}
|
|
if models.CurrentEnvironment.AccountID == "" {
|
|
models.CurrentEnvironment.AccountID = "queue"
|
|
}
|
|
if models.CurrentEnvironment.Host == "" {
|
|
models.CurrentEnvironment.Host = "localhost"
|
|
models.CurrentEnvironment.Port = "4100"
|
|
}
|
|
}
|
|
|
|
func setQueueRedrivePolicy(queues map[string]*models.Queue, q *models.Queue, strRedrivePolicy string) error {
|
|
// Поддерживаем maxReceiveCount как int и как string (AWS SDK использует string)
|
|
redrivePolicy1 := struct {
|
|
MaxReceiveCount int `json:"maxReceiveCount"`
|
|
DeadLetterTargetArn string `json:"deadLetterTargetArn"`
|
|
}{}
|
|
redrivePolicy2 := struct {
|
|
MaxReceiveCount string `json:"maxReceiveCount"`
|
|
DeadLetterTargetArn string `json:"deadLetterTargetArn"`
|
|
}{}
|
|
err1 := json.Unmarshal([]byte(strRedrivePolicy), &redrivePolicy1)
|
|
err2 := json.Unmarshal([]byte(strRedrivePolicy), &redrivePolicy2)
|
|
maxReceiveCount := redrivePolicy1.MaxReceiveCount
|
|
deadLetterQueueArn := redrivePolicy1.DeadLetterTargetArn
|
|
if err1 != nil && err2 != nil {
|
|
return fmt.Errorf("invalid json for queue redrive policy ")
|
|
} else if err1 != nil {
|
|
maxReceiveCount, _ = strconv.Atoi(redrivePolicy2.MaxReceiveCount)
|
|
deadLetterQueueArn = redrivePolicy2.DeadLetterTargetArn
|
|
}
|
|
|
|
if (deadLetterQueueArn != "" && maxReceiveCount == 0) ||
|
|
(deadLetterQueueArn == "" && maxReceiveCount != 0) {
|
|
return fmt.Errorf("invalid redrive policy values")
|
|
}
|
|
dlt := strings.Split(deadLetterQueueArn, ":")
|
|
deadLetterQueueName := dlt[len(dlt)-1]
|
|
deadLetterQueue, ok := queues[deadLetterQueueName]
|
|
if !ok {
|
|
return fmt.Errorf("deadletter queue not found")
|
|
}
|
|
q.DeadLetterQueue = deadLetterQueue
|
|
q.MaxReceiveCount = maxReceiveCount
|
|
|
|
return nil
|
|
}
|