From 929be1af9db91813b89aa51e7c72f2aa46948387 Mon Sep 17 00:00:00 2001 From: Blaize M Kaye Date: Fri, 15 Dec 2023 06:21:17 +1300 Subject: [PATCH 1/4] Adds deletion endpoints --- README.md | 15 ++++++ internal/service/defs.go | 15 ++++++ internal/service/service.go | 85 +++++++++++++++++++++++++++++--- internal/service/service_test.go | 46 +++++++++++++++++ 4 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 internal/service/defs.go diff --git a/README.md b/README.md index d38516a..e3e8669 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,21 @@ There are several parts to this - but from a user perspective it is fairly strai How this all works is coordinated across a few subsystems. +#### Clearing facts and problems + +There are cases where clearing facts/problems for an environment _without_ writing a new set of insights. + +This is done by issuing a DELETE method call (with the authentication token in the header) to one of the following four endpoints + +* `http://lagoon-remote-insights-remote.lagoon.svc/facts/{SOURCE}` +* `http://lagoon-remote-insights-remote.lagoon.svc/problems/{SOURCE}` +* `http://lagoon-remote-insights-remote.lagoon.svc/facts/{SOURCE}/{SERVICE}` +* `http://lagoon-remote-insights-remote.lagoon.svc/problems/{SOURCE}/{SERVICE}` + +Where `SOURCE` and `SERVICE` target the appropriate categorizations used generating the insights. + +Essentially these calls correspond to the "deleteProblemsFromSource" and "deleteFactsFromSource" Lagoon API calls + #### Authorization Token The Authorization token is a JWT that is generated per project and environment by the insights-remote [namespace controller](controllers/namespace_controller.go) diff --git a/internal/service/defs.go b/internal/service/defs.go new file mode 100644 index 0000000..0f5e04e --- /dev/null +++ b/internal/service/defs.go @@ -0,0 +1,15 @@ +package service + +type DeleteFactsMessage struct { + Type string `json:"type"` + EnvironmentId int `json:"environmentId"` + Source string `json:"source"` + Service string `json:"service"` +} + +type DeleteProblemsMessage struct { + Type string `json:"type"` + EnvironmentId int `json:"environmentId"` + Source string `json:"source"` + Service string `json:"service"` +} diff --git a/internal/service/service.go b/internal/service/service.go index 9af6d50..a9a2a96 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -23,6 +23,9 @@ type routerInstance struct { WriteToQueue bool } +const deleteFactsType = "direct.delete.facts" +const deleteProblemsType = "direct.delete.problems" + func SetupRouter(secret string, messageQWriter func(data []byte) error, writeToQueue bool) *gin.Engine { router := gin.Default() r := routerInstance{secret: secret} @@ -30,9 +33,72 @@ func SetupRouter(secret string, messageQWriter func(data []byte) error, writeToQ r.WriteToQueue = writeToQueue router.POST("/facts", r.writeFacts) router.POST("/problems", r.writeProblems) + router.DELETE("/problems/:source", r.deleteProblems) + router.DELETE("/problems/:source/:service", r.deleteProblems) + router.DELETE("/facts/:source", r.deleteFacts) + router.DELETE("/facts/:source/:service", r.deleteFacts) return router } +func (r *routerInstance) deleteProblems(c *gin.Context) { + generateDeletionMessage(c, r, deleteProblemsType) +} + +func (r *routerInstance) deleteFacts(c *gin.Context) { + generateDeletionMessage(c, r, deleteFactsType) +} + +func generateDeletionMessage(c *gin.Context, r *routerInstance, deletionType string) { + h := &AuthHeader{} + if err := c.ShouldBindHeader(&h); err != nil { + c.JSON(http.StatusOK, err) + } + + namespace, err := tokens.ValidateAndExtractNamespaceDetailsFromToken(r.secret, h.Authorization) + + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{ + "status": "unauthorized", + "message": err.Error(), + }) + return + } + + source := c.Params.ByName("source") + service := c.Params.ByName("service") + + envid, err := strconv.ParseInt(namespace.EnvironmentId, 10, 32) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "status": "BAD REQUEST", + "message": err.Error(), + }) + return + } + + message := DeleteFactsMessage{ + Type: deletionType, + EnvironmentId: int(envid), + Source: source, + Service: service, + } + + jsonRep, err := json.Marshal(message) + if err != nil { + c.JSON(http.StatusInternalServerError, err) + return + } + + if err := r.writeToQueue(c, err, jsonRep); err != nil { + c.JSON(http.StatusInternalServerError, err) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "okay", + }) +} + func (r *routerInstance) writeProblems(c *gin.Context) { h := &AuthHeader{} @@ -95,19 +161,26 @@ func (r *routerInstance) writeProblems(c *gin.Context) { return } + if err := r.writeToQueue(c, err, jsonRep); err != nil { + c.JSON(http.StatusInternalServerError, err) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "okay", + }) +} + +func (r *routerInstance) writeToQueue(c *gin.Context, err error, jsonRep []byte) error { if r.WriteToQueue { err = r.MessageQWriter(jsonRep) if err != nil { - c.JSON(http.StatusInternalServerError, err) - return + return err } } else { fmt.Printf("Not writing to queue - would have sent these data %v\n", string(jsonRep)) } - - c.JSON(http.StatusOK, gin.H{ - "message": "okay", - }) + return nil } func (r *routerInstance) writeFacts(c *gin.Context) { diff --git a/internal/service/service_test.go b/internal/service/service_test.go index 2c25425..df490db 100644 --- a/internal/service/service_test.go +++ b/internal/service/service_test.go @@ -142,3 +142,49 @@ func TestWriteProblemsRoute(t *testing.T) { assert.Contains(t, queueWriterOutput, testProblems[0].Source) } + +func TestFactDeletionRoute(t *testing.T) { + defer resetWriterOutput() + router := SetupRouter(secretTestTokenSecret, messageQueueWriter, true) + w := httptest.NewRecorder() + + token, err := tokens.GenerateTokenForNamespace(secretTestTokenSecret, tokens.NamespaceDetails{ + Namespace: secretTestNamespace, + EnvironmentId: testEnvironmentId, + ProjectName: "Test", + EnvironmentName: "Test", + }) + + require.NoError(t, err) + + var bodyString []byte + req, _ := http.NewRequest(http.MethodDelete, "/facts/testsource", bytes.NewBuffer(bodyString)) + req.Header.Set("Authorization", token) + req.Header.Set("Content-Type", "application/json") + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestProblemDeletionRoute(t *testing.T) { + defer resetWriterOutput() + router := SetupRouter(secretTestTokenSecret, messageQueueWriter, true) + w := httptest.NewRecorder() + + token, err := tokens.GenerateTokenForNamespace(secretTestTokenSecret, tokens.NamespaceDetails{ + Namespace: secretTestNamespace, + EnvironmentId: testEnvironmentId, + ProjectName: "Test", + EnvironmentName: "Test", + }) + + require.NoError(t, err) + + var bodyString []byte + req, _ := http.NewRequest(http.MethodDelete, "/problems/testsource", bytes.NewBuffer(bodyString)) + req.Header.Set("Authorization", token) + req.Header.Set("Content-Type", "application/json") + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} From cc25d3750debe6a7870b7a4627b97d3d589b66d3 Mon Sep 17 00:00:00 2001 From: Blaize M Kaye Date: Fri, 15 Dec 2023 06:27:15 +1300 Subject: [PATCH 2/4] Consolidates message type for deletion --- internal/service/defs.go | 9 +-------- internal/service/service.go | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/internal/service/defs.go b/internal/service/defs.go index 0f5e04e..2384b2c 100644 --- a/internal/service/defs.go +++ b/internal/service/defs.go @@ -1,13 +1,6 @@ package service -type DeleteFactsMessage struct { - Type string `json:"type"` - EnvironmentId int `json:"environmentId"` - Source string `json:"source"` - Service string `json:"service"` -} - -type DeleteProblemsMessage struct { +type DirectDeleteMessage struct { Type string `json:"type"` EnvironmentId int `json:"environmentId"` Source string `json:"source"` diff --git a/internal/service/service.go b/internal/service/service.go index a9a2a96..2169f67 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -76,7 +76,7 @@ func generateDeletionMessage(c *gin.Context, r *routerInstance, deletionType str return } - message := DeleteFactsMessage{ + message := DirectDeleteMessage{ Type: deletionType, EnvironmentId: int(envid), Source: source, From e152517ad7ed4e4efee16756b32bfb2f523dd0f1 Mon Sep 17 00:00:00 2001 From: Blaize M Kaye Date: Fri, 15 Dec 2023 07:18:43 +1300 Subject: [PATCH 3/4] Moves consts to defs file --- internal/service/defs.go | 3 +++ internal/service/service.go | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/service/defs.go b/internal/service/defs.go index 2384b2c..2bfb30a 100644 --- a/internal/service/defs.go +++ b/internal/service/defs.go @@ -1,5 +1,8 @@ package service +const deleteFactsType = "direct.delete.facts" +const deleteProblemsType = "direct.delete.problems" + type DirectDeleteMessage struct { Type string `json:"type"` EnvironmentId int `json:"environmentId"` diff --git a/internal/service/service.go b/internal/service/service.go index 2169f67..61610e3 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -23,9 +23,6 @@ type routerInstance struct { WriteToQueue bool } -const deleteFactsType = "direct.delete.facts" -const deleteProblemsType = "direct.delete.problems" - func SetupRouter(secret string, messageQWriter func(data []byte) error, writeToQueue bool) *gin.Engine { router := gin.Default() r := routerInstance{secret: secret} From 6a313ef5b6074e223decebbd7e89f96f8e16c34c Mon Sep 17 00:00:00 2001 From: Blaize M Kaye Date: Fri, 15 Dec 2023 09:42:38 +1300 Subject: [PATCH 4/4] Updates environment json export to bring in line with other messages --- internal/service/defs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/defs.go b/internal/service/defs.go index 2bfb30a..2474191 100644 --- a/internal/service/defs.go +++ b/internal/service/defs.go @@ -5,7 +5,7 @@ const deleteProblemsType = "direct.delete.problems" type DirectDeleteMessage struct { Type string `json:"type"` - EnvironmentId int `json:"environmentId"` + EnvironmentId int `json:"environment"` Source string `json:"source"` Service string `json:"service"` }