- GetQueueAttributes now returns FifoQueue, ContentBasedDeduplication, Policy and real Created/LastModifiedTimestamp (previously missing/stubbed '0000000000') - Queue: add CreatedAt, LastModifiedAt, Policy, ContentBasedDeduplication - parse FifoQueue/ContentBasedDeduplication in both protocols (StringToBool) - Policy/RedriveAllowPolicy: normalize JSON-string form to object (JSON protocol sent them as string -> SetQueueAttributes failed with InvalidParameterValue) - persistence: keep new fields in meta DTO + backfill empty timestamps on load - FIFO dedup is inherent: IsDuplicate/InitDuplicatation gate on IsFIFO only (env flag EnableDuplicates=false disabled dedup for all FIFO queues)
111 lines
5.3 KiB
Go
111 lines
5.3 KiB
Go
package gosqs
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
|
||
log "github.com/sirupsen/logrus"
|
||
|
||
"shared-sqs/app/models"
|
||
)
|
||
|
||
// setQueueAttributesV1 — применяет атрибуты очереди с валидацией по AWS-спецификации.
|
||
//
|
||
// Логическая схема:
|
||
// 1. Whitelist имён: неизвестное имя атрибута → InvalidAttributeName.
|
||
// Список имён (provided) доступен для Query-протокола (form-параметры
|
||
// Attribute.N.Name); для JSON-протокола provided == nil и проверка пропускается
|
||
// (структура уже разобрана из тела, неизвестные поля отброшены).
|
||
// 2. Применяются ТОЛЬКО явно переданные атрибуты (значение != 0).
|
||
// ВАЖНО (фикс): раньше условие `attr.X >= 0` срабатывало и для НЕ переданных
|
||
// полей (zero-value = 0) — вызов SetQueueAttributes с одним атрибутом
|
||
// обнулял все остальные атрибуты очереди.
|
||
// 3. Значения вне диапазонов AWS → ошибка InvalidParameterValue
|
||
// (раньше значения молча клэмпились в допустимый диапазон).
|
||
// 4. RedrivePolicy: ARN DLQ разбирается, DLQ ищется по TENANT-SCOPED ключу
|
||
// "{accessKey}:{queueName}" (раньше — по голому имени, а ключи в map
|
||
// tenant-scoped → DLQ никогда не находилась, RedrivePolicy не работал).
|
||
// DLQ должна принадлежать тому же тенанту.
|
||
func setQueueAttributesV1(q *models.Queue, attr models.QueueAttributes, provided map[string]string, tenantAccessKey string) error {
|
||
// Шаг 1: whitelist имён атрибутов (единый список — models.AttrNameWhitelist).
|
||
for name := range provided {
|
||
if !models.AttrNameWhitelist[name] {
|
||
return fmt.Errorf("InvalidAttributeName")
|
||
}
|
||
}
|
||
|
||
// Шаг 2: применяем только явно переданные атрибуты с проверкой диапазонов.
|
||
// Диапазоны AWS: DelaySeconds 0–900, MaximumMessageSize 1024–262144,
|
||
// MessageRetentionPeriod 60–1209600, ReceiveMessageWaitTimeSeconds 0–20,
|
||
// VisibilityTimeout 0–43200.
|
||
if attr.DelaySeconds != 0 {
|
||
if attr.DelaySeconds.Int() < 0 || attr.DelaySeconds.Int() > MaxDelaySeconds {
|
||
return fmt.Errorf("InvalidParameterValue")
|
||
}
|
||
q.DelaySeconds = attr.DelaySeconds.Int()
|
||
}
|
||
if attr.MaximumMessageSize != 0 {
|
||
if attr.MaximumMessageSize.Int() < MinMessageSizeLimit || attr.MaximumMessageSize.Int() > MaxMessageSizeLimit {
|
||
return fmt.Errorf("InvalidParameterValue")
|
||
}
|
||
q.MaximumMessageSize = attr.MaximumMessageSize.Int()
|
||
}
|
||
if attr.MessageRetentionPeriod != 0 {
|
||
if attr.MessageRetentionPeriod.Int() < MinMessageRetentionPeriod || attr.MessageRetentionPeriod.Int() > MaxMessageRetentionPeriod {
|
||
return fmt.Errorf("InvalidParameterValue")
|
||
}
|
||
q.MessageRetentionPeriod = attr.MessageRetentionPeriod.Int()
|
||
}
|
||
if attr.ReceiveMessageWaitTimeSeconds != 0 {
|
||
if attr.ReceiveMessageWaitTimeSeconds.Int() < 0 || attr.ReceiveMessageWaitTimeSeconds.Int() > MaxReceiveMessageWaitTimeSeconds {
|
||
return fmt.Errorf("InvalidParameterValue")
|
||
}
|
||
q.ReceiveMessageWaitTimeSeconds = attr.ReceiveMessageWaitTimeSeconds.Int()
|
||
}
|
||
if attr.VisibilityTimeout != 0 {
|
||
if attr.VisibilityTimeout.Int() < 0 || attr.VisibilityTimeout.Int() > MaxVisibilityTimeout {
|
||
return fmt.Errorf("InvalidParameterValue")
|
||
}
|
||
q.VisibilityTimeout = attr.VisibilityTimeout.Int()
|
||
}
|
||
|
||
// Шаг 3: RedrivePolicy (dead-letter queue).
|
||
if attr.RedrivePolicy != (models.RedrivePolicy{}) {
|
||
arnArray := strings.Split(attr.RedrivePolicy.DeadLetterTargetArn, ":")
|
||
queueName := arnArray[len(arnArray)-1]
|
||
// DLQ ищем по tenant-scoped ключу — как хранятся все очереди.
|
||
dlqKey := tenantAccessKey + ":" + queueName
|
||
models.SyncQueues.RLock()
|
||
deadLetterQueue, ok := models.SyncQueues.Queues[dlqKey]
|
||
models.SyncQueues.RUnlock()
|
||
if !ok {
|
||
log.Error("Invalid RedrivePolicy Attribute")
|
||
return fmt.Errorf("InvalidAttributeValue")
|
||
}
|
||
q.DeadLetterQueue = deadLetterQueue
|
||
q.MaxReceiveCount = attr.RedrivePolicy.MaxReceiveCount.Int()
|
||
}
|
||
|
||
// Шаг 4: FIFO-атрибуты и Policy — храним как есть (совместимость с Terraform).
|
||
// FifoQueue только включаем (выключить FIFO у существующей очереди нельзя —
|
||
// как и в AWS). ContentBasedDeduplication и Policy не исполняются,
|
||
// но должны возвращаться в GetQueueAttributes, иначе drift в Terraform.
|
||
if attr.FifoQueue.Bool() {
|
||
q.IsFIFO = true
|
||
}
|
||
if attr.ContentBasedDeduplication.Bool() {
|
||
q.ContentBasedDeduplication = true
|
||
}
|
||
if attr.Policy != nil {
|
||
// Сохраняем канонический JSON (map → marshal сортирует ключи) —
|
||
// детерминированно, без drift при чтении.
|
||
if data, err := json.Marshal(attr.Policy); err == nil {
|
||
q.Policy = string(data)
|
||
}
|
||
}
|
||
q.LastModifiedAt = time.Now().UTC()
|
||
return nil
|
||
}
|