Jelajahi Sumber

Implemented redriver.QetQueueInfo.

Frédéric G. MARAND 1 tahun lalu
induk
melakukan
412536aabf

+ 12 - 0
.run/Redriver.run.xml

@@ -0,0 +1,12 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="Redriver" type="GoApplicationRunConfiguration" factoryName="Go Application">
+    <module name="sqs_demo" />
+    <working_directory value="$PROJECT_DIR$" />
+    <parameters value="-profile=sqs-tutorial -addr :81" />
+    <kind value="PACKAGE" />
+    <package value="code.osinet.fr/fgm/sqs_demo/cmd/redriver" />
+    <directory value="$PROJECT_DIR$" />
+    <filePath value="$PROJECT_DIR$/demo.go" />
+    <method v="2" />
+  </configuration>
+</component>

+ 26 - 0
back/services/redriver/converters.go

@@ -8,6 +8,32 @@ import (
 	"github.com/aws/aws-sdk-go-v2/aws/arn"
 )
 
+func ARNFromURL(qURL string) (arn.ARN, error) {
+	a := arn.ARN{}
+	u, err := url.Parse(qURL)
+	if err != nil {
+		return a, fmt.Errorf("queue URL %q is not a valid URL: %w", qURL, err)
+	}
+	path := u.EscapedPath()
+	pathParts := strings.Split(strings.Trim(path, "/"), "/")
+	if len(pathParts) != 2 {
+		return a, fmt.Errorf("queue path %q does not have exactly 2 parts", path)
+	}
+	hostParts := strings.Split(u.Host, ".")
+	if len(hostParts) != 4 { // <service>.<region>.amazonws.com
+		return a, fmt.Errorf("queue host %q does not have exactly 4 parts", u.Host)
+	}
+
+	a = arn.ARN{
+		Partition: "aws",
+		Service:   hostParts[0],
+		Region:    hostParts[1],
+		AccountID: pathParts[0],
+		Resource:  pathParts[1],
+	}
+	return a, nil
+}
+
 func NameFromURL(qURL string) (name string, err error) {
 	u, err := url.Parse(qURL)
 	if err != nil {

+ 130 - 13
back/services/redriver/redriver.go

@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
+	"strconv"
 
 	"github.com/aws/aws-sdk-go-v2/aws"
 	"github.com/aws/aws-sdk-go-v2/service/sqs"
@@ -48,6 +49,36 @@ func (r redriver) ListQueues(ctx context.Context, prefix string) (QueueUrls []st
 	return lqo.QueueUrls, nil
 }
 
+func (r redriver) parseRedriveAllowPolicy(qName string, qao sqs.GetQueueAttributesOutput) (qrap *QueueInfoAttributesRedriveAllowPolicy, err error) {
+	srap, ok := qao.Attributes[string(types.QueueAttributeNameRedriveAllowPolicy)]
+	if !ok || srap == "" {
+		return nil, nil
+	}
+	qrap = &QueueInfoAttributesRedriveAllowPolicy{}
+	if err = json.Unmarshal([]byte(srap), qrap); err != nil {
+		return nil, fmt.Errorf(
+			"failed parsing redrive policy for queue %q %w", qName, err)
+	}
+	return qrap, nil
+}
+
+func (r redriver) parseRedrivePolicy(qName string, qao sqs.GetQueueAttributesOutput) (qrp *QueueInfoAttributesRedrivePolicy, err error) {
+	srp, ok := qao.Attributes[string(types.QueueAttributeNameRedrivePolicy)]
+	if !ok || srp == "" {
+		return nil, nil
+	}
+	qrp = &QueueInfoAttributesRedrivePolicy{}
+	if err = json.Unmarshal([]byte(srp), qrp); err != nil {
+		return qrp, fmt.Errorf(
+			"failed parsing redrive policy for queue %q %w", qName, err)
+	}
+	if _, err = URLFromARNString(qrp.DeadLetterTargetARN); err != nil {
+		return qrp, fmt.Errorf(
+			"failed converting queue %q ARN to URL: %w", qName, err)
+	}
+	return qrp, nil
+}
+
 func (r redriver) GetDLQ(ctx context.Context, qName string) (QueueUrl string, err error) {
 	qui := &sqs.GetQueueUrlInput{QueueName: &qName}
 	qu, err := r.GetQueueUrl(ctx, qui)
@@ -62,26 +93,112 @@ func (r redriver) GetDLQ(ctx context.Context, qName string) (QueueUrl string, er
 	if err != nil {
 		return "", fmt.Errorf("failed getting DLQ for queue %q: %w", qName, err)
 	}
-	rp, ok := qao.Attributes[string(types.QueueAttributeNameRedrivePolicy)]
-	if !ok {
-		return "", nil
+	if qao == nil {
+		return "", fmt.Errorf("redrive policy for queue %q is empty", qName)
 	}
-	var qrp QueueInfoAttributesRedrivePolicy
-	if err = json.Unmarshal([]byte(rp), &qrp); err != nil {
-		return "", fmt.Errorf("failed parsing redrive policy for queue %q %w",
-			qName, err)
-	}
-	qURL, err := URLFromARNString(qrp.DeadLetterTargetARN)
+	qrp, err := r.parseRedrivePolicy(qName, *qao)
 	if err != nil {
-		return "", fmt.Errorf(
-			"failed converting queue %q ARN to URL: %w", qName, err)
+		return "", fmt.Errorf("failed parsing redrive policy for queue %q: %w", qName, err)
 	}
+	if qrp == nil {
+		return "", nil // Queue has no DLQ: this is not an error
+	}
+	qURL, _ := URLFromARNString(qrp.DeadLetterTargetARN) // Already checked in parseRedrivePolicy
 	return qURL, nil
 }
 
 func (r redriver) GetQueueInfo(ctx context.Context, qName string) (*QueueInfo, error) {
-	// TODO implement me
-	panic("implement me")
+	qui := &sqs.GetQueueUrlInput{QueueName: &qName}
+	qu, err := r.GetQueueUrl(ctx, qui)
+	if err != nil {
+		return nil, fmt.Errorf("failed getting URL for queue %q: %w", qName, err)
+	}
+	if qu.QueueUrl == nil {
+		return nil, fmt.Errorf("URL for queue %q is empty", qName)
+	}
+	u := *qu.QueueUrl
+	a, err := ARNFromURL(u)
+	if err != nil {
+		return nil, fmt.Errorf("ARN for queue %q cannot be parsed from URL %q: %w", qName, u, err)
+	}
+	qai := &sqs.GetQueueAttributesInput{
+		QueueUrl:       qu.QueueUrl,
+		AttributeNames: []types.QueueAttributeName{types.QueueAttributeNameAll},
+	}
+	qao, err := r.Client.GetQueueAttributes(ctx, qai)
+	if err != nil {
+		return nil, fmt.Errorf("failed getting all attributes for queue %q: %w", qName, err)
+	}
+	if qao == nil {
+		return nil, fmt.Errorf("no attributes returned for queue %q", qName)
+	}
+	var (
+		errCount = 0
+		qi       *QueueInfo
+	)
+	{
+		var (
+			anom, anomd, anomnv              int32
+			created, changed                 int32 // timestamps
+			delay, max, retention, vto, wait int32
+		)
+		for _, field := range []struct {
+			name  types.QueueAttributeName
+			value *int32
+			sDef  string
+			def   int32
+		}{
+			{types.QueueAttributeNameApproximateNumberOfMessages, &anom, "-1", -1},
+			{types.QueueAttributeNameApproximateNumberOfMessagesDelayed, &anomd, "-1", -1},
+			{types.QueueAttributeNameApproximateNumberOfMessagesNotVisible, &anomnv, "-1", -1},
+			{types.QueueAttributeNameCreatedTimestamp, &created, "0", 0},
+			{types.QueueAttributeNameDelaySeconds, &delay, "-1", -1},
+			{types.QueueAttributeNameLastModifiedTimestamp, &changed, "0", 0},
+			{types.QueueAttributeNameMaximumMessageSize, &max, "262144", 1 << (8 + 10)},          // 256ko
+			{types.QueueAttributeNameMessageRetentionPeriod, &max, "1209600", 14 * 24 * 60 * 60}, // 2 weeks
+			{types.QueueAttributeNameReceiveMessageWaitTimeSeconds, &wait, "0", 0},               // short polling
+			{types.QueueAttributeNameVisibilityTimeout, &vto, "0", 0},                            // short polling
+		} {
+			s, ok := qao.Attributes[string(field.name)]
+			if !ok {
+				errCount++
+				s = field.sDef
+			}
+			n, err := strconv.Atoi(s)
+			if err != nil {
+				errCount++
+			}
+			*field.value = int32(n)
+		}
+		qrp, err := r.parseRedrivePolicy(qName, *qao)
+		if err != nil {
+			return nil, fmt.Errorf("failed parsing redrive policy for queue %q: %w", qName, err)
+		}
+		qrap, err := r.parseRedriveAllowPolicy(qName, *qao)
+		if err != nil {
+			return nil, fmt.Errorf("failed parsing redrive allow policy for queue %q: %w", qName, err)
+		}
+		qi = &QueueInfo{
+			Name: qName,
+			Url:  u,
+			Attributes: &QueueInfoAttributes{
+				ApproximateNumberOfMessages:           anom,
+				ApproximateNumberOfMessagesDelayed:    anomd,
+				ApproximateNumberOfMessagesNotVisible: anomnv,
+				CreatedTimestamp:                      created,
+				DelaySeconds:                          delay,
+				LastModifiedTimestamp:                 changed,
+				MaximumMessageSize:                    max,
+				MessageRetentionPeriod:                retention,
+				QueueARN:                              a.String(),
+				ReceiveMessageWaitTimeSeconds:         wait,
+				VisibilityTimeout:                     vto,
+				RedrivePolicy:                         qrp,
+				RedriveAllowPolicy:                    qrap,
+			},
+		}
+	}
+	return qi, err
 }
 
 func (r redriver) GetQueueItems(ctx context.Context, qName string, max int) ([]sqs.ReceiveMessageOutput, error) {

+ 2 - 2
back/services/redriver/types.go

@@ -60,8 +60,8 @@ type QueueInfoAttributes struct {
 // QueueInfoAttributesRedriveAllowPolicy describes the permissions for the dead-letter queue redrive permission and which source queues can specify dead-letter queues.
 type QueueInfoAttributesRedriveAllowPolicy struct {
 	// RedrivePermission defines which source queues can specify the current queue as the dead-letter queue.
-	RedrivePermission string   `json:"redrive_permission,omitempty"`
-	SourceQueueARNs   []string `json:"source_queue_arns,omitempty"`
+	RedrivePermission string   `json:"redrivePermission,omitempty"`
+	SourceQueueARNs   []string `json:"sourceQueueArns,omitempty"`
 }
 
 // QueueInfoAttributesRedrivePolicy holds the parameters for the dead-letter queue functionality of the source queue

+ 3 - 0
back/web/public/redriver.css

@@ -0,0 +1,3 @@
+* {
+    color: red;
+}

+ 24 - 0
back/web/queue.go

@@ -0,0 +1,24 @@
+package web
+
+import (
+	"log"
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+
+	"code.osinet.fr/fgm/sqs_demo/services/redriver"
+)
+
+func makeQueueHandler(rd redriver.Redriver) gin.HandlerFunc {
+	return func(c *gin.Context) {
+		ctx := c.Request.Context()
+		qName := c.Param("name")
+		qi, err := rd.GetQueueInfo(ctx, qName)
+		if err != nil {
+			log.Printf("failed getting info for queue %q: %v", qName, err)
+			c.JSON(http.StatusInternalServerError, nil)
+			return
+		}
+		c.JSON(http.StatusOK, qi)
+	}
+}

+ 7 - 0
back/web/routes.go

@@ -1,6 +1,8 @@
 package web
 
 import (
+	_ "embed"
+
 	"github.com/fgm/izidic"
 	"github.com/gin-gonic/gin"
 
@@ -8,9 +10,14 @@ import (
 	"code.osinet.fr/fgm/sqs_demo/services/redriver"
 )
 
+//go:embed public/redriver.css
+var css []byte
+
 func SetupRoutes(rd redriver.Redriver) *gin.Engine {
 	r := gin.Default()
 	r.GET("/", makeHomeHandler(rd))
+	r.GET("/public/redriver.css", func(c *gin.Context) { c.Writer.Write(css) })
+	r.GET("/queue/:name", makeQueueHandler(rd))
 	return r
 }