home.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. package web
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "net/http"
  7. "time"
  8. "github.com/aws/aws-sdk-go-v2/aws/arn"
  9. "github.com/gin-contrib/sessions"
  10. "github.com/gin-gonic/gin"
  11. "code.osinet.fr/fgm/sqs_demo/back/services/redriver"
  12. )
  13. // classifyPolicies distributes queue policies across five bins:
  14. // - wild: policies describing a DLQ with an allowAll policy
  15. // - closed: policies describing a DLQ with an denyAll policy
  16. // - byQueue: policies describing a DLQ with an byQueue policy
  17. // - sources: policies describing a queue with a DLQ
  18. // - notSources: policies describing a queue without a DLQ
  19. //
  20. // Note that dead-letter queues may also be source queues, for multi-level processing, and vice-versa.
  21. //
  22. // For easier reading, DLQs are not listed as part of the non-source list.
  23. func classifyPolicies(policies map[string]*redriver.QueueRedrivePolicies) (byQueue, wild, closed, sources map[string]redriver.QueueRedrivePolicies, notSources []string) {
  24. DLQs := make(map[string]redriver.QueueRedrivePolicies)
  25. sources = make(map[string]redriver.QueueRedrivePolicies)
  26. wild = make(map[string]redriver.QueueRedrivePolicies)
  27. closed = make(map[string]redriver.QueueRedrivePolicies)
  28. byQueue = make(map[string]redriver.QueueRedrivePolicies)
  29. nsTemp := make([]string, 0, len(notSources))
  30. for qURL, qrp := range policies {
  31. if qrp.QueueInfoAttributesRedriveAllowPolicy != nil {
  32. DLQs[qURL] = *qrp
  33. }
  34. if qrp.QueueInfoAttributesRedrivePolicy != nil {
  35. sources[qURL] = *qrp
  36. } else {
  37. nsTemp = append(nsTemp, qURL)
  38. }
  39. }
  40. for _, qURL := range nsTemp {
  41. if _, ok := DLQs[qURL]; !ok {
  42. notSources = append(notSources, qURL)
  43. }
  44. }
  45. for qURL, qrp := range DLQs {
  46. rap := qrp.QueueInfoAttributesRedriveAllowPolicy
  47. switch rap.RedrivePermission {
  48. case "allowAll":
  49. wild[qURL] = qrp
  50. case "denyAll":
  51. closed[qURL] = qrp
  52. case "byQueue":
  53. byQueue[qURL] = qrp
  54. }
  55. }
  56. return
  57. }
  58. func policiesByURL(c *gin.Context, qURLs []string, rd redriver.Redriver, ctx context.Context) map[string]*redriver.QueueRedrivePolicies {
  59. qMap := make(map[string]*redriver.QueueRedrivePolicies, len(qURLs))
  60. for _, qURL := range qURLs {
  61. name, err := redriver.NameFromURL(qURL)
  62. if err != nil {
  63. log.Println(http.StatusInternalServerError)
  64. c.HTML(http.StatusInternalServerError, "500", nil)
  65. }
  66. qrp, err := rd.GetRedrivePolicies(ctx, name)
  67. if err != nil || qrp == nil {
  68. log.Println(err)
  69. c.HTML(http.StatusInternalServerError, "500", nil)
  70. }
  71. qMap[qURL] = qrp
  72. }
  73. return qMap
  74. }
  75. type link struct{ URL, Text string }
  76. // queueRow provides templates with a representation for all kinds of table cells
  77. type queueCell struct {
  78. Message string
  79. Link *link
  80. Links [][]link
  81. }
  82. type QueueRow [2]queueCell
  83. func makeHomeHandler(rd redriver.Redriver) gin.HandlerFunc {
  84. return func(c *gin.Context) {
  85. ctx := c.Request.Context()
  86. sess := sessions.Default(c)
  87. flashes := sess.Flashes()
  88. defer func() { _ = sess.Save() }()
  89. t0 := time.Now()
  90. qURLs, err := rd.ListQueues(ctx, "")
  91. latency := time.Since(t0)
  92. if err != nil {
  93. log.Printf("failed listing queues: %v", err)
  94. c.HTML(http.StatusInternalServerError, "500", nil)
  95. return
  96. }
  97. policies := policiesByURL(c, qURLs, rd, ctx)
  98. byQueue, wild, closed, sources, notSources := classifyPolicies(policies)
  99. // log.Printf("DLQs: %#v\nSRCs: %#v\nNoSRCs: %#v\n", maps.Keys(DLQs), maps.Keys(SRCs), NoSRCs)
  100. rows := make([]QueueRow, 0, len(byQueue)+3) // wild, closed, notSources: sources appear within a byQueue row
  101. rows, err = prepareSingleLeft(rows, wild, "All source queues allowed")
  102. if err != nil {
  103. log.Printf("failed preparing wild DLQs: %v", err)
  104. c.HTML(http.StatusInternalServerError, "500", nil)
  105. return
  106. }
  107. rows, err = prepareSingleLeft(rows, closed, "No source queue allowed")
  108. if err != nil {
  109. log.Printf("failed preparing closed DLQs: %v", err)
  110. c.HTML(http.StatusInternalServerError, "500", nil)
  111. return
  112. }
  113. rows, err = prepareSingleRight(rows, notSources, "No associated DLQ")
  114. if err != nil {
  115. log.Printf("failed converting isolated queues: %v", err)
  116. c.HTML(http.StatusInternalServerError, "500", nil)
  117. return
  118. }
  119. rows, err = prepareBindings(rows, byQueue, sources)
  120. if err != nil {
  121. log.Printf("failed preparing queue bindings: %v", err)
  122. c.HTML(http.StatusInternalServerError, "500", nil)
  123. return
  124. }
  125. c.HTML(http.StatusOK, "home", gin.H{
  126. "flashes": flashes,
  127. "latency": latency,
  128. "rows": rows,
  129. })
  130. }
  131. }
  132. func prepareSingleLeft(rows []QueueRow, list map[string]redriver.QueueRedrivePolicies, msg string) ([]QueueRow, error) {
  133. if len(list) > 0 {
  134. row := QueueRow{
  135. {Message: msg},
  136. {Links: make([][]link, 1)},
  137. }
  138. row[1].Links[0] = make([]link, 0, len(list))
  139. for qURL := range list {
  140. name, err := redriver.NameFromURL(qURL)
  141. if err != nil {
  142. return nil, err
  143. }
  144. row[1].Links[0] = append(row[1].Links[0], link{URL: "/queue/" + name, Text: name})
  145. }
  146. rows = append(rows, row)
  147. }
  148. return rows, nil
  149. }
  150. func prepareSingleRight(rows []QueueRow, list []string, msg string) ([]QueueRow, error) {
  151. if len(list) > 0 {
  152. row := QueueRow{
  153. {Links: make([][]link, 1)},
  154. {Message: msg},
  155. }
  156. row[0].Links[0] = make([]link, 0, len(list))
  157. for _, qURL := range list {
  158. name, err := redriver.NameFromURL(qURL)
  159. if err != nil {
  160. return nil, err
  161. }
  162. row[0].Links[0] = append(row[0].Links[0], link{URL: "/queue/" + name, Text: name})
  163. }
  164. rows = append(rows, row)
  165. }
  166. return rows, nil
  167. }
  168. func prepareBinding(dlqURL string, DLQrp redriver.QueueRedrivePolicies, sources map[string]redriver.QueueRedrivePolicies) (*QueueRow, error) {
  169. var left, right queueCell
  170. dlqName, err := redriver.NameFromURL(dlqURL)
  171. if err != nil {
  172. return nil, fmt.Errorf("failed parsing DLQ URL %q: %w", dlqURL, err)
  173. }
  174. right = queueCell{Link: &link{URL: "/queue/" + dlqName, Text: dlqName}}
  175. a, err := redriver.ARNFromURL(dlqURL)
  176. if err != nil {
  177. return nil, fmt.Errorf("failed converting URL %q to ARN: %w", dlqURL, err)
  178. }
  179. dlqARN := a.String()
  180. var bound, unbound, elsewhere []link
  181. for _, sourceARN := range DLQrp.SourceQueueARNs {
  182. sa, err := arn.Parse(sourceARN)
  183. if err != nil {
  184. return nil, fmt.Errorf("failed parsing %q as an ARN: %w", sourceARN, err)
  185. }
  186. sourceURL, err := redriver.URLFromARN(sa)
  187. if err != nil {
  188. return nil, fmt.Errorf("failed converting ARN %q for URL: %w", sa.String(), err)
  189. }
  190. sourceName := sa.Resource
  191. sourceQRP, ok := sources[sourceURL]
  192. link := link{
  193. URL: "/queue/" + sourceName,
  194. Text: sourceName,
  195. }
  196. if ok {
  197. switch sourceQRP.DeadLetterTargetARN {
  198. case "":
  199. unbound = append(unbound, link)
  200. case dlqARN:
  201. bound = append(bound, link)
  202. default:
  203. elsewhere = append(elsewhere, link)
  204. }
  205. } else {
  206. unbound = append(unbound, link)
  207. }
  208. }
  209. left.Links = [][]link{bound, elsewhere, unbound}
  210. return &QueueRow{left, right}, nil
  211. }
  212. func prepareBindings(rows []QueueRow, DLQs, sources map[string]redriver.QueueRedrivePolicies) ([]QueueRow, error) {
  213. for dlqURL, DLQrp := range DLQs {
  214. row, err := prepareBinding(dlqURL, DLQrp, sources)
  215. if err != nil || row == nil {
  216. return nil, fmt.Errorf("failed binding DLQ %s: %w", dlqURL, err)
  217. }
  218. rows = append(rows, *row)
  219. }
  220. return rows, nil
  221. }