Przeglądaj źródła

Home: handling of all 3 simple cases.

Frederic G. MARAND 2 lat temu
rodzic
commit
5608c32a27

+ 54 - 44
back/services/redriver/redriver.go

@@ -28,10 +28,15 @@ type ItemsKeys struct {
 	ReceiptHandle string
 }
 
+type QueueRedrivePolicies struct {
+	*QueueInfoAttributesRedrivePolicy
+	*QueueInfoAttributesRedriveAllowPolicy
+}
+
 // Redriver is a redrive-oriented facade in front of the sqs.Client API.
 type Redriver interface {
 	ListQueues(ctx context.Context, prefix string) (QueueUrls []string, err error)
-	GetDLQ(ctx context.Context, qName string) (QueueUrls string, err error)
+	GetRedrivePolicies(ctx context.Context, qName string) (*QueueRedrivePolicies, error)
 	GetQueueInfo(ctx context.Context, qName string) (*QueueInfo, error)
 	GetQueueItems(ctx context.Context, qName string) ([]Message, error)
 	DeleteItems(ctx context.Context, qName string, itemsIDs []ItemsKeys) error
@@ -47,6 +52,7 @@ type redriver struct {
 }
 
 func (r *redriver) ListQueues(ctx context.Context, prefix string) (QueueUrls []string, err error) {
+	// TODO implement pagination for the day we need more than 1000 queues to be reported.
 	lqi := &sqs.ListQueuesInput{
 		MaxResults:      aws.Int32(1000),
 		NextToken:       nil,
@@ -59,62 +65,70 @@ func (r *redriver) ListQueues(ctx context.Context, prefix string) (QueueUrls []s
 	return lqo.QueueUrls, nil
 }
 
-func (*redriver) parseQueueInfoRedriveAllowPolicy(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 (*redriver) parseQueueInfoRedrivePolicies(qName string, qao sqs.GetQueueAttributesOutput) (*QueueRedrivePolicies, error) {
+	var qrp QueueRedrivePolicies
 
-func (*redriver) parseQueueInfoRedrivePolicy(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)
+	srp := qao.Attributes[string(types.QueueAttributeNameRedrivePolicy)]
+	if srp != "" {
+		rp := QueueInfoAttributesRedrivePolicy{}
+		if err := json.Unmarshal([]byte(srp), &rp); err != nil {
+			return nil, fmt.Errorf(
+				"failed parsing redrive policy for queue %q %w", qName, err)
+		}
+		if _, err := URLFromARNString(rp.DeadLetterTargetARN); err != nil {
+			return nil, fmt.Errorf(
+				"failed converting queue %q ARN to URL: %w", qName, err)
+		}
+		qrp.QueueInfoAttributesRedrivePolicy = &rp
 	}
-	if _, err = URLFromARNString(qrp.DeadLetterTargetARN); err != nil {
-		return qrp, fmt.Errorf(
-			"failed converting queue %q ARN to URL: %w", qName, err)
+
+	srap := qao.Attributes[string(types.QueueAttributeNameRedriveAllowPolicy)]
+	if srap != "" {
+		rap := QueueInfoAttributesRedriveAllowPolicy{}
+		if err := json.Unmarshal([]byte(srap), &rap); err != nil {
+			return nil, fmt.Errorf(
+				"failed parsing redrive allow policy for queue %q %w", qName, err)
+		}
+		for _, src := range rap.SourceQueueARNs {
+			if _, err := URLFromARNString(src); err != nil {
+				return nil, fmt.Errorf(
+					"failed converting queue %q ARN to URL: %w", qName, err)
+			}
+		}
+		qrp.QueueInfoAttributesRedriveAllowPolicy = &rap
 	}
-	return qrp, nil
+
+	return &qrp, nil
 }
 
-func (r *redriver) GetDLQ(ctx context.Context, qName string) (QueueUrl string, err error) {
+func (r *redriver) GetRedrivePolicies(ctx context.Context, qName string) (policies *QueueRedrivePolicies, err error) {
 	qui := &sqs.GetQueueUrlInput{QueueName: &qName}
 	qu, err := r.GetQueueUrl(ctx, qui)
 	if err != nil {
-		return "", fmt.Errorf("failed getting URL for queue %q: %w", qName, err)
+		return nil, fmt.Errorf("failed getting URL for queue %q: %w", qName, err)
 	}
 	qai := &sqs.GetQueueAttributesInput{
-		QueueUrl:       qu.QueueUrl,
-		AttributeNames: []types.QueueAttributeName{types.QueueAttributeNameRedrivePolicy},
+		QueueUrl: qu.QueueUrl,
+		AttributeNames: []types.QueueAttributeName{
+			types.QueueAttributeNameRedrivePolicy,
+			types.QueueAttributeNameRedriveAllowPolicy,
+		},
 	}
 	qao, err := r.Client.GetQueueAttributes(ctx, qai)
 	if err != nil {
-		return "", fmt.Errorf("failed getting DLQ for queue %q: %w", qName, err)
+		return nil, fmt.Errorf("failed getting DLQ policies for queue %q: %w", qName, err)
 	}
 	if qao == nil {
-		return "", fmt.Errorf("redrive policy for queue %q is empty", qName)
+		return nil, fmt.Errorf("redrive policy info for queue %q is empty", qName)
 	}
-	qrp, err := r.parseQueueInfoRedrivePolicy(qName, *qao)
+	qrp, err := r.parseQueueInfoRedrivePolicies(qName, *qao)
 	if err != nil {
-		return "", fmt.Errorf("failed parsing redrive policy for queue %q: %w", qName, err)
+		return nil, 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
+		return nil, nil // Queue has no DLQ: this is not an error
 	}
-	qURL, _ := URLFromARNString(qrp.DeadLetterTargetARN) // Already checked in parseQueueInfoRedrivePolicy
-	return qURL, nil
+	return qrp, nil
 }
 
 func (r *redriver) GetQueueInfo(ctx context.Context, qName string) (*QueueInfo, error) {
@@ -187,14 +201,10 @@ func (r *redriver) parseQueueInfoAttributes(qName string, qu *sqs.GetQueueUrlOut
 		}
 		*field.value = int64(n)
 	}
-	qrp, err := r.parseQueueInfoRedrivePolicy(qName, *qao)
+	qrp, err := r.parseQueueInfoRedrivePolicies(qName, *qao)
 	if err != nil {
 		return nil, fmt.Errorf("failed parsing redrive policy for queue %q: %w", qName, err)
 	}
-	qrap, err := r.parseQueueInfoRedriveAllowPolicy(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,
@@ -210,8 +220,8 @@ func (r *redriver) parseQueueInfoAttributes(qName string, qu *sqs.GetQueueUrlOut
 			QueueARN:                              a.String(),
 			ReceiveMessageWaitTimeSeconds:         wait,
 			VisibilityTimeout:                     vto,
-			RedrivePolicy:                         qrp,
-			RedriveAllowPolicy:                    qrap,
+			RedrivePolicy:                         qrp.QueueInfoAttributesRedrivePolicy,
+			RedriveAllowPolicy:                    qrp.QueueInfoAttributesRedriveAllowPolicy,
 		},
 	}
 	return qi, nil

+ 162 - 11
back/web/home.go

@@ -1,37 +1,188 @@
 package web
 
 import (
+	"context"
 	"log"
 	"net/http"
+	"time"
 
+	"github.com/gin-contrib/sessions"
 	"github.com/gin-gonic/gin"
 
 	"code.osinet.fr/fgm/sqs_demo/back/services/redriver"
 )
 
+// classifyPolicies distributes queue policies across five bins:
+//   - wild: policies describing a DLQ with an allowAll policy
+//   - closed: policies describing a DLQ with an denyAll policy
+//   - byQueue: policies describing a DLQ with an byQueue policy
+//   - sources: policies describing a queue with a DLQ
+//   - notSources: policies describing a queue without a DLQ
+//
+// Note that dead-letter queues may also by source queues, for multi-level processing, and vice-versa.
+//
+// For easier reading, DLQs are not listed as part of the non-source list.
+func classifyPolicies(policies map[string]*redriver.QueueRedrivePolicies) (byQueue, wild, closed, sources map[string]redriver.QueueRedrivePolicies, notSources []string) {
+	DLQs := make(map[string]redriver.QueueRedrivePolicies)
+	sources = make(map[string]redriver.QueueRedrivePolicies)
+	wild = make(map[string]redriver.QueueRedrivePolicies)
+	closed = make(map[string]redriver.QueueRedrivePolicies)
+	byQueue = make(map[string]redriver.QueueRedrivePolicies)
+	nsTemp := make([]string, 0, len(notSources))
+
+	for qURL, qrp := range policies {
+		if qrp.QueueInfoAttributesRedriveAllowPolicy != nil {
+			DLQs[qURL] = *qrp
+		}
+		if qrp.QueueInfoAttributesRedrivePolicy != nil {
+			sources[qURL] = *qrp
+		} else {
+			nsTemp = append(nsTemp, qURL)
+		}
+	}
+	for _, qURL := range nsTemp {
+		if _, ok := DLQs[qURL]; !ok {
+			notSources = append(notSources, qURL)
+		}
+	}
+	for qURL, qrp := range DLQs {
+		rap := qrp.QueueInfoAttributesRedriveAllowPolicy
+		switch rap.RedrivePermission {
+		case "allowAll":
+			wild[qURL] = qrp
+		case "denyAll":
+			closed[qURL] = qrp
+		case "byQueue":
+			byQueue[qURL] = qrp
+		}
+	}
+
+	return
+}
+
+func policiesByURL(c *gin.Context, qURLs []string, rd redriver.Redriver, ctx context.Context) map[string]*redriver.QueueRedrivePolicies {
+	qMap := make(map[string]*redriver.QueueRedrivePolicies, len(qURLs))
+	for _, qURL := range qURLs {
+		name, err := redriver.NameFromURL(qURL)
+		if err != nil {
+			log.Println(http.StatusInternalServerError)
+			c.HTML(http.StatusInternalServerError, "500", nil)
+		}
+		qrp, err := rd.GetRedrivePolicies(ctx, name)
+		if err != nil || qrp == nil {
+			log.Println(err)
+			c.HTML(http.StatusInternalServerError, "500", nil)
+		}
+		qMap[qURL] = qrp
+	}
+	return qMap
+}
+
+type link struct{ URL, Text string }
+
+// queueRow provides templates with a representation for all kinds of table cells
+type queueCell struct {
+	Message string
+	Link    *link
+	Links   []link
+}
+
+type QueueRow [2]queueCell
+
 func makeHomeHandler(rd redriver.Redriver) gin.HandlerFunc {
 	return func(c *gin.Context) {
 		ctx := c.Request.Context()
-		qURLs, err := rd.ListQueues(ctx, "test")
+		sess := sessions.Default(c)
+		flashes := sess.Flashes()
+		defer func() { _ = sess.Save() }()
+
+		t0 := time.Now()
+		qURLs, err := rd.ListQueues(ctx, "")
+		latency := time.Since(t0)
 		if err != nil {
 			log.Printf("failed listing queues: %v", err)
-			c.JSON(http.StatusInternalServerError, nil)
+			c.HTML(http.StatusInternalServerError, "500", nil)
 			return
 		}
-		qMap := map[string]string{}
-		for _, qURL := range qURLs {
+		policies := policiesByURL(c, qURLs, rd, ctx)
+		byqueue, wild, closed, sources, notSources := classifyPolicies(policies)
+		// log.Printf("DLQs: %#v\nSRCs: %#v\nNoSRCs: %#v\n", maps.Keys(DLQs), maps.Keys(SRCs), NoSRCs)
+		l := len(byqueue)
+		if len(wild) > 0 {
+			l++
+		}
+		if len(closed) > 0 {
+			l++
+		}
+		if len(notSources) > 0 {
+			l++
+		}
+		rows := make([]QueueRow, 0, l) // Sources appears within a byQueue row
+
+		rows, err = prepareSingleLeft(wild, rows, "All source queues allowed")
+		if err != nil {
+			log.Printf("failed converting queue URL %q: %v", qURLs, err)
+			c.HTML(http.StatusInternalServerError, "500", nil)
+			return
+		}
+		rows, err = prepareSingleLeft(closed, rows, "No source queue allowed")
+		if err != nil {
+			log.Printf("failed converting queue URL %q: %v", qURLs, err)
+			c.HTML(http.StatusInternalServerError, "500", nil)
+			return
+		}
+		rows, err = prepareSingleRight(notSources, rows, "No associated DLQ")
+		if err != nil {
+			log.Printf("failed converting queue URL %q: %v", qURLs, err)
+			c.HTML(http.StatusInternalServerError, "500", nil)
+			return
+		}
+
+		// for qURLs := range closed {
+		//
+		// }
+		log.Println(sources)
+		c.HTML(http.StatusOK, "home", gin.H{
+			"flashes": flashes,
+			"latency": latency,
+			"rows":    rows,
+		})
+
+	}
+}
+
+func prepareSingleLeft(list map[string]redriver.QueueRedrivePolicies, rows []QueueRow, msg string) ([]QueueRow, error) {
+	if len(list) > 0 {
+		row := QueueRow{
+			{Message: msg},
+			{Links: make([]link, 0, len(list))},
+		}
+		for qURL := range list {
 			name, err := redriver.NameFromURL(qURL)
 			if err != nil {
-				log.Println(http.StatusInternalServerError)
-				c.JSON(http.StatusInternalServerError, nil)
+				return nil, err
 			}
-			dlqURL, err := rd.GetDLQ(ctx, name)
+			row[1].Links = append(row[1].Links, link{URL: "/queue/" + name, Text: name})
+		}
+		rows = append(rows, row)
+	}
+	return rows, nil
+}
+
+func prepareSingleRight(list []string, rows []QueueRow, msg string) ([]QueueRow, error) {
+	if len(list) > 0 {
+		row := QueueRow{
+			{Links: make([]link, 0, len(list))},
+			{Message: msg},
+		}
+		for _, qURL := range list {
+			name, err := redriver.NameFromURL(qURL)
 			if err != nil {
-				log.Println(err)
-				c.JSON(http.StatusInternalServerError, nil)
+				return nil, err
 			}
-			qMap[qURL] = dlqURL
+			row[0].Links = append(row[0].Links, link{URL: "/queue/" + name, Text: name})
 		}
-		c.JSON(http.StatusOK, qMap)
+		rows = append(rows, row)
 	}
+	return rows, nil
 }

+ 4 - 4
back/web/queue.go

@@ -22,20 +22,20 @@ func makeQueueHandler(rd redriver.Redriver) gin.HandlerFunc {
 
 		t0 := time.Now()
 		qi, err := rd.GetQueueInfo(ctx, qName)
+		infoLatency := time.Since(t0)
 		if err != nil {
 			log.Printf("failed getting info for queue %q: %v", qName, err)
-			c.JSON(http.StatusInternalServerError, nil)
+			c.HTML(http.StatusInternalServerError, "500", nil)
 			return
 		}
-		infoLatency := time.Since(t0)
 		items, err := rd.GetQueueItems(ctx, qName)
 		if err != nil {
 			log.Printf("failed getting items for queue %q: %v", qName, err)
-			c.JSON(http.StatusInternalServerError, nil)
+			c.HTML(http.StatusInternalServerError, "500", nil)
 			return
 		}
 		itemsLatency := time.Since(t0) - infoLatency
-		c.HTML(http.StatusOK, "queue-get", map[string]any{
+		c.HTML(http.StatusOK, "queue-get", gin.H{
 			"flashes": flashes,
 			"info":    qi,
 			"items":   items,

+ 1 - 0
back/web/routes.go

@@ -79,6 +79,7 @@ func RendererService(_ *izidic.Container) (any, error) {
 	}{
 		{"confirm", front.Confirm},
 		{"flashes", front.Flashes},
+		{"home", front.Home},
 		{"queue-get", front.QueueGet}, // Includes queue-item.
 		{"500", front.Err500},
 	} {

+ 4 - 3
front/assets/styles.css

@@ -1,8 +1,9 @@
+#home-table caption {
+  caption-side: top;
+}
 
-/******* CUSTOM STYLES *******/
+/* See flashes.gohtml */
 #warning-emj {
   font-size: 1.7rem;
   font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
 }
-
-/*** END CUSTOM STYLES **

+ 3 - 0
front/handlers.go

@@ -13,6 +13,9 @@ var Confirm string
 //go:embed templates/flashes.gohtml
 var Flashes string
 
+//go:embed templates/home.gohtml
+var Home string
+
 //go:embed templates/queue.gohtml
 var QueueGet string
 

+ 87 - 0
front/templates/home.gohtml

@@ -0,0 +1,87 @@
+{{ define "home" -}}
+    <!DOCTYPE html>
+    <html lang="en">
+    <head>
+        <meta charset="UTF-8">
+        <title>SQS Redriver</title>
+        <link rel="stylesheet" href="/assets/styles.css"/>
+        <link rel="stylesheet" href="/assets/bootstrap.min.css">
+    </head>
+    <body>
+    <main class="container">
+        <div class="container mt-5">
+            <nav aria-label="breadcrumb">
+                <ol class="breadcrumb">
+                    <li class="breadcrumb-item">Home</li>
+                </ol>
+            </nav>
+        </div>
+
+        <h1 class="text-3xl font-bold underline">SQS Queues
+            {{ if .prefix }}
+                matching prefix: {{ .prefix }}
+            {{ end}}
+        </h1>
+        <table id="home-table" class="table">
+            <caption>Grayed out queues in the left column are accepted by the given DLQ,
+                but are not configured to use a DLQ. This is a configuration error in SQS.
+            </caption>
+            <thead>
+            <th scope="col">Queue name</th>
+            <th scope="col">DLQ name</th>
+            </thead>
+            <tbody>
+            {{ range .rows }}
+                {{ template "home-row" . }}
+            {{ end }}
+            <tr>
+                <td>
+                    <ul>
+                        <li><a class="text-black-50 bg-light" href="/queue/acquisition-reviews-sender-submit-qa3">acquisition-reviews-sender-submit-qa3</a>
+                        </li>
+                        <li>
+                            <a href="/queue/acquisition-reviews-sender-submit-stg">acquisition-reviews-sender-submit-stg</a>
+                        </li>
+                    </ul>
+                </td>
+                <td><a href="/queue/acquisition-reviews-sender-submit-dlq">acquisition-reviews-sender-submit-dlq</a>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <ul>
+                        <li><a href="/queue/payment-payin-work-dlq-qa3">payment-payin-work-dlq-qa3</a></li>
+                        <li><a class="text-black-50 bg-light" href="/queue/payment-payin-work-dlq-stg">payment-payin-work-dlq-std</a>
+                        </li>
+                    </ul>
+                </td>
+                <td><a href="/queue/payment-payin-work-dlq-dlq">payment-payin-work-dlq</a></td>
+            </tr>
+            </tbody>
+        </table>
+    </main>
+    </body>
+    </html>
+{{ end }}
+
+{{ define "home-row" }}
+    <tr>
+        <td>{{ template "home-cell" (index . 0) }}</td>
+        <td>{{ template "home-cell" (index . 1) }}</td>
+    </tr>
+{{ end }}
+
+{{ define "home-cell" }}
+    {{ if .Message }}
+        <em>{{ .Message }}</em>
+    {{else if .Link }}
+        <a href="{{ .Link.URL }}">{{ .Link.Text }}</a>
+    {{ else }}
+
+        <ul>
+            {{ range .Links }}
+                <li><a href="{{ .URL }}">{{ .Text }}</a></li>
+            {{ end }}
+        </ul>
+    {{end}}
+{{ end }}