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:
Repinoid
2026-10-10 17:29:58 +03:00
parent 22fa2b162a
commit 9f1242aca3
7 changed files with 171 additions and 16 deletions
+61 -7
View File
@@ -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 {