- 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)
139 lines
5.6 KiB
Go
139 lines
5.6 KiB
Go
// Изменено: 2026-04-11 — убрана зависимость от copystructure (лишняя аллокация)
|
|
// GetQueueAttributesV1 — возвращает атрибуты очереди тенанта.
|
|
package gosqs
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"shared-sqs/app/interfaces"
|
|
"shared-sqs/app/models"
|
|
"shared-sqs/app/utils"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func GetQueueAttributesV1(req *http.Request) (int, interfaces.AbstractResponseBody) {
|
|
requestBody := models.NewGetQueueAttributesRequest()
|
|
ok := utils.REQUEST_TRANSFORMER(requestBody, req, false)
|
|
if !ok {
|
|
log.Error("Invalid Request - GetQueueAttributesV1")
|
|
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
|
}
|
|
if requestBody.QueueUrl == "" {
|
|
log.Error("Missing QueueUrl - GetQueueAttributesV1")
|
|
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
|
}
|
|
|
|
t := getTenantFromContext(req)
|
|
if t == nil {
|
|
return utils.CreateErrorResponseV1("InvalidClientTokenId", true)
|
|
}
|
|
|
|
// Определяем набор запрошенных атрибутов (или All)
|
|
requestedAttributes := func() map[string]bool {
|
|
attrs := map[string]bool{}
|
|
if len(requestBody.AttributeNames) == 0 {
|
|
return map[string]bool{"All": true}
|
|
}
|
|
for _, attr := range requestBody.AttributeNames {
|
|
if "All" == attr {
|
|
return map[string]bool{"All": true}
|
|
}
|
|
attrs[attr] = true
|
|
}
|
|
return attrs
|
|
}()
|
|
|
|
// Фильтруем атрибуты без deep copy — простая проверка через map lookup
|
|
_, wantAll := requestedAttributes["All"]
|
|
shouldInclude := func(attr string) bool {
|
|
if wantAll {
|
|
return true
|
|
}
|
|
_, ok := requestedAttributes[attr]
|
|
return ok
|
|
}
|
|
|
|
uriSegments := strings.Split(requestBody.QueueUrl, "/")
|
|
queueName := uriSegments[len(uriSegments)-1]
|
|
key := tenantQueueKey(t.AccessKey, queueName)
|
|
|
|
log.Infof("Get Queue Attributes: %s (tenant: %s)", queueName, t.ID)
|
|
queueAttributes := make([]models.Attribute, 0)
|
|
|
|
queue := models.SyncQueues.GetRLocked(key)
|
|
if queue == nil {
|
|
log.Errorf("Get Queue Attributes: %s queue does not exist for tenant %s", queueName, t.ID)
|
|
return utils.CreateErrorResponseV1("QueueNotFound", true)
|
|
}
|
|
defer queue.RUnlockMu()
|
|
|
|
if shouldInclude("DelaySeconds") {
|
|
queueAttributes = append(queueAttributes, models.Attribute{Name: "DelaySeconds", Value: strconv.Itoa(queue.DelaySeconds)})
|
|
}
|
|
if shouldInclude("MaximumMessageSize") {
|
|
queueAttributes = append(queueAttributes, models.Attribute{Name: "MaximumMessageSize", Value: strconv.Itoa(queue.MaximumMessageSize)})
|
|
}
|
|
if shouldInclude("MessageRetentionPeriod") {
|
|
queueAttributes = append(queueAttributes, models.Attribute{Name: "MessageRetentionPeriod", Value: strconv.Itoa(queue.MessageRetentionPeriod)})
|
|
}
|
|
if shouldInclude("ReceiveMessageWaitTimeSeconds") {
|
|
queueAttributes = append(queueAttributes, models.Attribute{Name: "ReceiveMessageWaitTimeSeconds", Value: strconv.Itoa(queue.ReceiveMessageWaitTimeSeconds)})
|
|
}
|
|
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())})
|
|
}
|
|
if shouldInclude("ApproximateNumberOfMessagesNotVisible") {
|
|
queueAttributes = append(queueAttributes, models.Attribute{Name: "ApproximateNumberOfMessagesNotVisible", Value: strconv.Itoa(numberOfHiddenMessagesInQueue(queue))})
|
|
}
|
|
if shouldInclude("CreatedTimestamp") {
|
|
queueAttributes = append(queueAttributes, models.Attribute{Name: "CreatedTimestamp", Value: epochSeconds(queue.CreatedAt)})
|
|
}
|
|
if shouldInclude("LastModifiedTimestamp") {
|
|
queueAttributes = append(queueAttributes, models.Attribute{Name: "LastModifiedTimestamp", Value: epochSeconds(queue.LastModifiedAt)})
|
|
}
|
|
if shouldInclude("QueueArn") {
|
|
queueAttributes = append(queueAttributes, models.Attribute{Name: "QueueArn", Value: queue.Arn})
|
|
}
|
|
if shouldInclude("RedrivePolicy") && queue.DeadLetterQueue != nil {
|
|
queueAttributes = append(queueAttributes, models.Attribute{
|
|
Name: "RedrivePolicy",
|
|
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,
|
|
Result: models.GetQueueAttributesResult{Attrs: queueAttributes},
|
|
Metadata: models.BaseResponseMetadata,
|
|
}
|
|
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)
|
|
}
|