feat(aws-compat): queue attributes for Terraform (FifoQueue, ContentBasedDeduplication, Policy, real timestamps)
- 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)
This commit is contained in:
@@ -34,3 +34,31 @@ func (s *StringToInt) UnmarshalJSON(data []byte) error {
|
||||
func (s *StringToInt) Int() int {
|
||||
return int(*s)
|
||||
}
|
||||
|
||||
// StringToBool — аналог StringToInt для булевых атрибутов: принимает и JSON-bool
|
||||
// (true/false), и строку ("true"/"false"). Query-протокол шлёт строки,
|
||||
// JSON-протокол — булевы значения; без этого FifoQueue/ContentBasedDeduplication
|
||||
// из JSON-запроса молча терялись бы.
|
||||
type StringToBool bool
|
||||
|
||||
func (s *StringToBool) UnmarshalJSON(data []byte) error {
|
||||
var b bool
|
||||
if err := json.Unmarshal(data, &b); err == nil {
|
||||
*s = StringToBool(b)
|
||||
return nil
|
||||
}
|
||||
var str string
|
||||
if err := json.Unmarshal(data, &str); err != nil {
|
||||
return err
|
||||
}
|
||||
tmp, err := strconv.ParseBool(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*s = StringToBool(tmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StringToBool) Bool() bool {
|
||||
return bool(*s)
|
||||
}
|
||||
|
||||
+16
-7
@@ -92,6 +92,16 @@ type Queue struct {
|
||||
// Используется для запрета повторного purge ранее чем через 60 секунд
|
||||
// (требование AWS: PurgeQueueInProgress). Нулевое значение = purge ещё не выполнялся.
|
||||
LastPurgeTime time.Time
|
||||
// CreatedAt/LastModifiedAt — для атрибутов CreatedTimestamp/LastModifiedTimestamp
|
||||
// (AWS отдаёт их в GetQueueAttributes; Terraform читает и без них видел бы drift).
|
||||
CreatedAt time.Time
|
||||
LastModifiedAt time.Time
|
||||
// Policy — resource policy (JSON-строка), храним и отдаём как есть для
|
||||
// совместимости с Terraform. НЕ исполняется (нет enforcement).
|
||||
Policy string
|
||||
// ContentBasedDeduplication — FIFO-атрибут. Храним и отдаём значение,
|
||||
// переданное клиентом (для совместимости), см. раздел про dedup.
|
||||
ContentBasedDeduplication bool
|
||||
}
|
||||
|
||||
// LockMu/UnlockMu/RLockMu/RUnlockMu — доступ к per-queue блокировке извне пакета models
|
||||
@@ -150,13 +160,12 @@ func (q *Queue) UnlockGroup(groupId string) {
|
||||
}
|
||||
|
||||
// IsDuplicate — проверяет, было ли сообщение с таким deduplicationId уже
|
||||
// добавлено в очередь в пределах deduplication-окна.
|
||||
//
|
||||
// Логическая схема: дубликаты отслеживаются ТОЛЬКО для FIFO-очередей с
|
||||
// включённой дупликацией (EnableDuplicates) и непустым deduplicationId.
|
||||
// Для остальных случаев дубликаты не отслеживаются (AWS-семантика).
|
||||
// добавлено в очередь в пределах deduplication-окна (5 минут).
|
||||
// Дедупликация — НЕОТЪЕМЛЕМОЕ свойство FIFO-очереди (AWS): раньше она зависела
|
||||
// от env-флага EnableDuplicates=false по умолчанию, и FIFO-очереди не
|
||||
// дедуплицировали вообще. Теперь — только по IsFIFO и непустому id.
|
||||
func (q *Queue) IsDuplicate(deduplicationId string) bool {
|
||||
if !q.EnableDuplicates || !q.IsFIFO || deduplicationId == "" {
|
||||
if !q.IsFIFO || deduplicationId == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -170,7 +179,7 @@ func (q *Queue) IsDuplicate(deduplicationId string) bool {
|
||||
// фоновой задачей PeriodicTasks (deduplication period expired).
|
||||
// Для не-FIFO очередей или пустого deduplicationId — no-op.
|
||||
func (q *Queue) InitDuplicatation(deduplicationId string) {
|
||||
if !q.EnableDuplicates || !q.IsFIFO || deduplicationId == "" {
|
||||
if !q.IsFIFO || deduplicationId == "" {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+61
-7
@@ -113,6 +113,20 @@ func (r *CreateQueueRequest) SetAttributesFromForm(values url.Values) {
|
||||
continue
|
||||
}
|
||||
r.Attributes.RedriveAllowPolicy = tmp
|
||||
case "FifoQueue":
|
||||
tmp, err := strconv.ParseBool(attrValue)
|
||||
if err != nil {
|
||||
log.Debugf("Failed to parse form attribute - %s: %s", attrName, attrValue)
|
||||
continue
|
||||
}
|
||||
r.Attributes.FifoQueue = StringToBool(tmp)
|
||||
case "ContentBasedDeduplication":
|
||||
tmp, err := strconv.ParseBool(attrValue)
|
||||
if err != nil {
|
||||
log.Debugf("Failed to parse form attribute - %s: %s", attrName, attrValue)
|
||||
continue
|
||||
}
|
||||
r.Attributes.ContentBasedDeduplication = StringToBool(tmp)
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -381,6 +395,20 @@ func (r *SetQueueAttributesRequest) SetAttributesFromForm(values url.Values) {
|
||||
continue
|
||||
}
|
||||
r.Attributes.RedriveAllowPolicy = tmp
|
||||
case "FifoQueue":
|
||||
tmp, err := strconv.ParseBool(attrValue)
|
||||
if err != nil {
|
||||
log.Debugf("Failed to parse form attribute - %s: %s", attrName, attrValue)
|
||||
continue
|
||||
}
|
||||
r.Attributes.FifoQueue = StringToBool(tmp)
|
||||
case "ContentBasedDeduplication":
|
||||
tmp, err := strconv.ParseBool(attrValue)
|
||||
if err != nil {
|
||||
log.Debugf("Failed to parse form attribute - %s: %s", attrName, attrValue)
|
||||
continue
|
||||
}
|
||||
r.Attributes.ContentBasedDeduplication = StringToBool(tmp)
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -393,12 +421,17 @@ type QueueAttributes struct {
|
||||
DelaySeconds StringToInt `json:"DelaySeconds"`
|
||||
MaximumMessageSize StringToInt `json:"MaximumMessageSize"`
|
||||
MessageRetentionPeriod StringToInt `json:"MessageRetentionPeriod"` // NOTE: not implemented
|
||||
Policy map[string]interface{} `json:"Policy"` // NOTE: not implemented
|
||||
ReceiveMessageWaitTimeSeconds StringToInt `json:"ReceiveMessageWaitTimeSeconds"`
|
||||
VisibilityTimeout StringToInt `json:"VisibilityTimeout"`
|
||||
// Dead Letter Queues Only
|
||||
RedrivePolicy RedrivePolicy `json:"RedrivePolicy"`
|
||||
RedriveAllowPolicy map[string]interface{} `json:"RedriveAllowPolicy"` // NOTE: not implemented
|
||||
Policy map[string]interface{} `json:"Policy"` // хранится, не исполняется
|
||||
ReceiveMessageWaitTimeSeconds StringToInt `json:"ReceiveMessageWaitTimeSeconds"`
|
||||
VisibilityTimeout StringToInt `json:"VisibilityTimeout"`
|
||||
// Dead Letter Queues Only
|
||||
RedrivePolicy RedrivePolicy `json:"RedrivePolicy"`
|
||||
RedriveAllowPolicy map[string]interface{} `json:"RedriveAllowPolicy"` // NOTE: not implemented
|
||||
// FIFO-атрибуты. FifoQueue у нас также выводится из суффикса .fifo имени,
|
||||
// но значение от клиента нужно СОХРАНИТЬ и вернуть в GetQueueAttributes —
|
||||
// иначе Terraform (aws_sqs_queue) видел бы постоянный drift.
|
||||
FifoQueue StringToBool `json:"FifoQueue"`
|
||||
ContentBasedDeduplication StringToBool `json:"ContentBasedDeduplication"`
|
||||
}
|
||||
|
||||
// AttrNameWhitelist — имена атрибутов, допустимые в SetQueueAttributes/CreateQueue.
|
||||
@@ -444,8 +477,29 @@ func (q *QueueAttributes) UnmarshalJSON(data []byte) error {
|
||||
return fmt.Errorf("invalid attribute name: %s", name)
|
||||
}
|
||||
}
|
||||
// Policy/RedriveAllowPolicy в SQS API — СТРОКА с JSON внутри, а поля у нас —
|
||||
// map. JSON-протокол присылает их именно строкой, и unmarshal в map падал бы
|
||||
// (SetQueueAttributes → InvalidParameterValue). Разворачиваем строку в объект.
|
||||
// Также принимаем вариант, где уже пришёл объект — он и так валиден.
|
||||
for _, name := range []string{"Policy", "RedriveAllowPolicy"} {
|
||||
rv, ok := raw[name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var s string
|
||||
if err := json.Unmarshal(rv, &s); err == nil {
|
||||
if !json.Valid([]byte(s)) {
|
||||
return fmt.Errorf("invalid JSON in attribute %s", name)
|
||||
}
|
||||
raw[name] = json.RawMessage(s)
|
||||
}
|
||||
}
|
||||
normalized, err := json.Marshal(raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
type plain QueueAttributes
|
||||
return json.Unmarshal(data, (*plain)(q))
|
||||
return json.Unmarshal(normalized, (*plain)(q))
|
||||
}
|
||||
|
||||
type RedrivePolicy struct {
|
||||
|
||||
Reference in New Issue
Block a user