diff --git a/Makefile b/Makefile index e3a78fa..73d506b 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ WEBHOOK_VERSION=$(shell jq -rc ".webhook_version" versions.json) deploy: deploy-ui deploy-service deploy-tf deploy-tf: - cd infra; make deploy + cd infra; make deploy-yes deploy-ui: build-ui docker push $(BASE_URL)/frontend-service:latest diff --git a/infra/Makefile b/infra/Makefile index 0719ebb..14b6143 100644 --- a/infra/Makefile +++ b/infra/Makefile @@ -1,8 +1,15 @@ FRONTEND_VERSION=$(shell jq -rc ".frontend_version" ../versions.json) WEBHOOK_VERSION=$(shell jq -rc ".webhook_version" ../versions.json) -deploy: SHELL := /bin/bash -deploy: +replace: SHELL := /bin/bash +replace: sed -i -Ee "s/(webhook_service_tag = \").*(\")/\1$(WEBHOOK_VERSION)\2/g" terraform.tfvars sed -i -Ee "s/(frontend_service_tag = \").*(\")/\1$(FRONTEND_VERSION)\2/g" terraform.tfvars + +deploy: SHELL := /bin/bash +deploy: replace tofu apply + +deploy-yes: SHELL := /bin/bash +deploy-yes: replace + tofu apply -auto-approve diff --git a/service/controllers/controllers.go b/service/controllers/controllers.go index 55d4101..e389351 100644 --- a/service/controllers/controllers.go +++ b/service/controllers/controllers.go @@ -24,6 +24,7 @@ func BuildRouter(r *gin.Engine) { log.SetFormatter(&logrus.TextFormatter{ DisableColors: true, }) + log.SetLevel(logrus.DebugLevel) var err error mongo, err = db.NewClient(conf.Mongo.Uri) diff --git a/service/controllers/pco_auth_middleware.go b/service/controllers/pco_auth_middleware.go index 6eafe8e..b55b337 100644 --- a/service/controllers/pco_auth_middleware.go +++ b/service/controllers/pco_auth_middleware.go @@ -2,10 +2,11 @@ package controllers import ( "bytes" - "context" "crypto/hmac" "crypto/sha256" "encoding/hex" + "errors" + "fmt" "io" "git.preston-baxter.com/Preston_PLB/capstone/webhook-service/vendors/pco/webhooks" @@ -31,14 +32,16 @@ func ValidatePcoWebhook(c *gin.Context) { return } - //clone request to harmlessly inspect the body - bodyReader := c.Request.Clone(context.Background()).Body - body, err := io.ReadAll(bodyReader) + //clone request body to harmlessly inspect the body + bodyCopy := bytes.NewBuffer([]byte{}) + _, err = io.Copy(bodyCopy, c.Request.Body) if err != nil { - log.WithError(err).Error("Failed to read body while validating PCO webhook") + log.WithError(err).Error("Failed to copy body while validating PCO webhook") _ = c.AbortWithError(501, err) return } + body := bodyCopy.Bytes() + c.Request.Body = io.NopCloser(bytes.NewReader(body)) //Get secret key, err := getAuthSecret(c, body) @@ -60,16 +63,25 @@ func ValidatePcoWebhook(c *gin.Context) { func getAuthSecret(c *gin.Context, body []byte) (string, error) { userObjectId := userIdFromContext(c) + log.Debug(string(body)) - event := &webhooks.EventDelivery{} - err := jsonapi.UnmarshalPayload(bytes.NewBuffer(body), event) + //Pco is weird and sends a data array instead of an object. Yet there is only one event. Fun times + event, err := jsonapi.UnmarshalManyPayload[webhooks.EventDelivery](bytes.NewBuffer(body)) if err != nil { - return "", err + return "", errors.Join(fmt.Errorf("Failed to unmarshall event delivery from PCO"), err) } - webhook, err := mongo.FindPcoSubscriptionForUser(*userObjectId, event.Name) + if len(event) == 0 { + return "", fmt.Errorf("There are no events in the delivery. Something is wrong") + } + + webhook, err := mongo.FindPcoSubscriptionForUser(*userObjectId, event[0].Name) if err != nil { - return "", err + return "", errors.Join(fmt.Errorf("Failed to find pco subscription for user: %s and event: %s", userObjectId.Hex(), event[0].Name), err) + } + + if webhook == nil { + return "", fmt.Errorf("Could not find subscription for user: %s and name %s", userObjectId.Hex(), event[0].Name) } return webhook.Details.AuthenticitySecret, nil diff --git a/service/controllers/pco_webhook.go b/service/controllers/pco_webhook.go index d8c2b17..b77a97e 100644 --- a/service/controllers/pco_webhook.go +++ b/service/controllers/pco_webhook.go @@ -68,13 +68,16 @@ func ConsumePcoWebhook(c *gin.Context) { errs := make([]error, 2) go func(wg *sync.WaitGroup) { + defer wg.Done() actionMappings, errs[0] = mongo.FindActionMappingsByUser(*userObjectId) - wg.Done() }(wg) go func(wg *sync.WaitGroup) { - errs[1] = jsonapi.UnmarshalPayload(c.Request.Body, webhookBody) - wg.Done() + defer wg.Done() + + var payload []webhooks.EventDelivery + payload, errs[1] = jsonapi.UnmarshalManyPayload[webhooks.EventDelivery](c.Request.Body) + webhookBody = &payload[0] }(wg) wg.Wait() @@ -89,9 +92,9 @@ func ConsumePcoWebhook(c *gin.Context) { //loop through all actions a user has for _, mapping := range actionMappings { //find the ones that are runable by this function - if mapping.SourceEvent.VendorName == models.PCO_VENDOR_NAME && eventMatch(webhookBody.Name) { + if mapping.SourceEvent.VendorName == models.PCO_VENDOR_NAME && eventMatch(mapping.SourceEvent.Key, webhookBody.Name) { //generate lookup key for function - actionKey := fmt.Sprintf("%s:%s", mapping.Action.VendorName, mapping.Action.Type) + actionKey := fmt.Sprintf("%s.%s", mapping.Action.VendorName, mapping.Action.Type) //if function exists run the function if action, ok := actionFuncMap[actionKey]; ok { err := action(c, webhookBody) @@ -99,15 +102,21 @@ func ConsumePcoWebhook(c *gin.Context) { if err != nil { log.WithError(err).Errorf("Failed to execute action: %s. From event source: %s:%s", actionKey, mapping.SourceEvent.VendorName, mapping.SourceEvent.Key) _ = c.AbortWithError(501, err) + } else { + log.Infof("Succesfully proccessed: %s for %s", webhookBody.Name, userObjectId.Hex()) + c.Status(200) } + return } } } + log.Warnf("No errors, but also no work...") + c.Status(200) } -func eventMatch(event string) bool { - if regexString, ok := eventRegexKeys[event]; ok { - reg := regexp.MustCompile(regexString) +func eventMatch(key, event string) bool { + if regexString, ok := eventRegexKeys[key]; ok { + reg := regexp.MustCompile(regexString) //TODO: Make this regex cache-able return reg.MatchString(event) } else { return false @@ -204,6 +213,7 @@ func ScheduleBroadcastFromWebhook(c *gin.Context, body *webhooks.EventDelivery) VendorName: models.PCO_VENDOR_NAME, Type: body.Name, } + if err := mongo.SaveModel(eventRecievedAudit); err != nil { log.WithError(err).WithField("EventRecieved", eventRecievedAudit).Error("Failed to save audit event. Logging here and resuming") } @@ -215,6 +225,16 @@ func ScheduleBroadcastFromWebhook(c *gin.Context, body *webhooks.EventDelivery) switch body.Name { case "services.v2.events.plan.created": broadcast, err = scheduleNewBroadcastFromWebhook(c, payload, ytClient, pcoClient) + if err != nil { + log.WithError(err).Error("Failed to schedule broadcast from created event") + return err + } + case "services.v2.events.plan.updated": + log.Warn("services.v2.events.plan.updated event not implemented") + return nil + case "services.v2.events.plan.destroyed": + log.Warn("services.v2.events.plan.destroyed event not implemented") + return nil default: return fmt.Errorf("Unkown event error: %s", body.Name) } diff --git a/service/controllers/pco_webhook_test.go b/service/controllers/pco_webhook_test.go new file mode 100644 index 0000000..4528381 --- /dev/null +++ b/service/controllers/pco_webhook_test.go @@ -0,0 +1,15 @@ +package controllers + +import ( + "testing" + + "github.com/go-playground/assert/v2" +) + +func TestPlanEventMatch(t *testing.T) { + events := []string{"services.v2.events.plan.updated","services.v2.events.plan.destroyed","services.v2.events.plan.created"} + + for _, event := range events { + assert.Equal(t, eventMatch("plan", event), true) + } +} diff --git a/service/vendors/pco/webhooks/structs.go b/service/vendors/pco/webhooks/structs.go index 3980a85..3609c46 100644 --- a/service/vendors/pco/webhooks/structs.go +++ b/service/vendors/pco/webhooks/structs.go @@ -17,7 +17,7 @@ type EventDelivery struct { //number of attemts taken to deliver the event Attempt int `jsonapi:"attr,attempt"` //JSON:API string of the event - Payload string `jsonapi:"attr,attempt"` + Payload string `jsonapi:"attr,payload"` //Owner Organization of the event Organization *services.Organization `jsonapi:"relation,organization"` } diff --git a/ui/controllers/actions.go b/ui/controllers/actions.go index 31ab4d2..9b308f2 100644 --- a/ui/controllers/actions.go +++ b/ui/controllers/actions.go @@ -152,7 +152,7 @@ func setupPcoSubscriptions(user *models.User) error { } //Save Subscriptions - err = mongo.SaveSubscriptionsForUser(user.Id, subscriptions...) + err = mongo.SaveSubscriptionsForUser(user.Id, builtHooks...) if err != nil { return errors.Join(fmt.Errorf("Failed to save subscriptions for user: %s", user.Id), err) } diff --git a/ui/db/db.go b/ui/db/db.go index 458c0a2..935e61a 100644 --- a/ui/db/db.go +++ b/ui/db/db.go @@ -67,7 +67,7 @@ func (db *DB) SaveModels(m ...Model) error { for index, model := range m { entry := mongo.NewUpdateOneModel() - entry.SetFilter(bson.M{"_id": model.MongoId}) + entry.SetFilter(bson.M{"_id": model.MongoId()}) entry.SetUpsert(true) entry.SetUpdate(bson.M{"$set": model}) model.UpdateObjectInfo() diff --git a/ui/db/pco.go b/ui/db/pco.go index 7d8c643..6577715 100644 --- a/ui/db/pco.go +++ b/ui/db/pco.go @@ -17,7 +17,7 @@ func (db *DB) FindPcoSubscriptionForUser(userId primitive.ObjectID, eventName st conf := config.Config() opts := options.FindOne() - res := db.client.Database(conf.Mongo.EntDb).Collection(conf.Mongo.EntCol).FindOne(context.Background(), bson.M{"_id": userId, "obj_info.ent": models.PCO_SUBSCRIPTION_TYPE, "details.name": eventName}, opts) + res := db.client.Database(conf.Mongo.EntDb).Collection(conf.Mongo.EntCol).FindOne(context.Background(), bson.M{"user_id": userId, "obj_info.ent": models.PCO_SUBSCRIPTION_TYPE, "details.name": eventName}, opts) if res.Err() != nil { if res.Err() == mongo.ErrNoDocuments { diff --git a/versions.json b/versions.json index e359f06..4b4ceb0 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1,4 @@ { - "webhook_version": "0.0.31", - "frontend_version": "0.0.34" + "webhook_version": "0.0.45", + "frontend_version": "0.0.35" }