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
+3
View File
@@ -59,6 +59,9 @@ func CreateQueueV1(req *http.Request) (int, interfaces.AbstractResponseBody) {
IsFIFO: utils.HasFIFOQueueName(queueName),
EnableDuplicates: models.CurrentEnvironment.EnableDuplicates,
Duplicates: make(map[string]time.Time),
// CreatedTimestamp/LastModifiedTimestamp в GetQueueAttributes (AWS их отдаёт).
CreatedAt: time.Now().UTC(),
LastModifiedAt: time.Now().UTC(),
}
// Список имён переданных атрибутов — для whitelist-валидации
// (Query-протокол: Attribute.N.Name; JSON: nil — проверка пропускается).
+23 -2
View File
@@ -7,6 +7,7 @@ import (
"net/http"
"strconv"
"strings"
"time"
"shared-sqs/app/interfaces"
"shared-sqs/app/models"
@@ -86,6 +87,13 @@ func GetQueueAttributesV1(req *http.Request) (int, interfaces.AbstractResponseBo
if shouldInclude("VisibilityTimeout") {
queueAttributes = append(queueAttributes, models.Attribute{Name: "VisibilityTimeout", Value: strconv.Itoa(queue.VisibilityTimeout)})
}
// FIFO-атрибуты: читаются Terraform (aws_sqs_queue) и aws cli; без них — drift.
if shouldInclude("FifoQueue") {
queueAttributes = append(queueAttributes, models.Attribute{Name: "FifoQueue", Value: strconv.FormatBool(queue.IsFIFO)})
}
if shouldInclude("ContentBasedDeduplication") {
queueAttributes = append(queueAttributes, models.Attribute{Name: "ContentBasedDeduplication", Value: strconv.FormatBool(queue.ContentBasedDeduplication)})
}
if shouldInclude("ApproximateNumberOfMessages") {
queueAttributes = append(queueAttributes, models.Attribute{Name: "ApproximateNumberOfMessages", Value: strconv.Itoa(queue.LiveCount())})
}
@@ -93,10 +101,10 @@ func GetQueueAttributesV1(req *http.Request) (int, interfaces.AbstractResponseBo
queueAttributes = append(queueAttributes, models.Attribute{Name: "ApproximateNumberOfMessagesNotVisible", Value: strconv.Itoa(numberOfHiddenMessagesInQueue(queue))})
}
if shouldInclude("CreatedTimestamp") {
queueAttributes = append(queueAttributes, models.Attribute{Name: "CreatedTimestamp", Value: "0000000000"})
queueAttributes = append(queueAttributes, models.Attribute{Name: "CreatedTimestamp", Value: epochSeconds(queue.CreatedAt)})
}
if shouldInclude("LastModifiedTimestamp") {
queueAttributes = append(queueAttributes, models.Attribute{Name: "LastModifiedTimestamp", Value: "0000000000"})
queueAttributes = append(queueAttributes, models.Attribute{Name: "LastModifiedTimestamp", Value: epochSeconds(queue.LastModifiedAt)})
}
if shouldInclude("QueueArn") {
queueAttributes = append(queueAttributes, models.Attribute{Name: "QueueArn", Value: queue.Arn})
@@ -107,6 +115,10 @@ func GetQueueAttributesV1(req *http.Request) (int, interfaces.AbstractResponseBo
Value: fmt.Sprintf(`{"maxReceiveCount":"%d", "deadLetterTargetArn":"%s"}`, queue.MaxReceiveCount, queue.DeadLetterQueue.Arn),
})
}
// Policy — отдаём сохранённую JSON-строку как есть (совместимость с Terraform).
if shouldInclude("Policy") && queue.Policy != "" {
queueAttributes = append(queueAttributes, models.Attribute{Name: "Policy", Value: queue.Policy})
}
respStruct := models.GetQueueAttributesResponse{
Xmlns: models.BaseXmlns,
@@ -115,3 +127,12 @@ func GetQueueAttributesV1(req *http.Request) (int, interfaces.AbstractResponseBo
}
return http.StatusOK, respStruct
}
// epochSeconds — timestamp в формате AWS (unix-секунды строкой).
// Нулевое время отдаём как "0" (стабильно, без drift при чтении).
func epochSeconds(t time.Time) string {
if t.IsZero() {
return "0"
}
return strconv.FormatInt(t.Unix(), 10)
}
+21
View File
@@ -1,8 +1,10 @@
package gosqs
import (
"encoding/json"
"fmt"
"strings"
"time"
log "github.com/sirupsen/logrus"
@@ -85,5 +87,24 @@ func setQueueAttributesV1(q *models.Queue, attr models.QueueAttributes, provided
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
}