diff --git a/CHANGELOG.md b/CHANGELOG.md index f11d021..6f62fa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,9 @@ Changes adhere to [semantic versioning](https://semver.org). * Switched to encrypting webhook tokens in database * Added _Actions_, a simple way to trigger notifications via [shoutrrr](https://containrrr.dev/shoutrrr) which supports secrets * Switched to producing events only for _Updates_ - -## [1.1.0] - UNRELEASED - * Adapted logging which defaults to JSON encoding -* ... +* Updated dependencies +* Updated build to use Go 1.22 ## [1.0.3] - 2024/01/21 diff --git a/_doc/api.yaml b/_doc/api.yaml index 1d24077..adcc491 100644 --- a/_doc/api.yaml +++ b/_doc/api.yaml @@ -2499,6 +2499,9 @@ components: - running - error - success + message: + type: string + nullable: true actionId: type: string eventId: diff --git a/api/dto.go b/api/dto.go index b3b4877..877be00 100644 --- a/api/dto.go +++ b/api/dto.go @@ -390,10 +390,10 @@ type ActionResponse struct { ID uuid.UUID `json:"id"` Label string `json:"label"` Type string `json:"type"` - MatchEvent *string `json:"matchEvent"` - MatchHost *string `json:"matchHost"` - MatchApplication *string `json:"matchApplication"` - MatchProvider *string `json:"matchProvider"` + MatchEvent *string `json:"matchEvent,omitempty"` + MatchHost *string `json:"matchHost,omitempty"` + MatchApplication *string `json:"matchApplication,omitempty"` + MatchProvider *string `json:"matchProvider,omitempty"` Payload interface{} `json:"payload,omitempty"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` @@ -460,6 +460,7 @@ type ActionInvocationResponse struct { ID uuid.UUID `json:"id"` RetryCount int `json:"retryCount"` State string `json:"state"` + Message *string `json:"message,omitempty"` ActionID string `json:"actionId"` EventID string `json:"eventId"` CreatedAt time.Time `json:"createdAt"` @@ -470,11 +471,12 @@ type ActionInvocationSingleResponse struct { Data ActionInvocationResponse `json:"data"` } -func NewActionInvocationSingleResponse(id uuid.UUID, retryCount int, state string, actionId string, eventId string, createdAt time.Time, updatedAt time.Time) *ActionInvocationSingleResponse { +func NewActionInvocationSingleResponse(id uuid.UUID, retryCount int, state string, message *string, actionId string, eventId string, createdAt time.Time, updatedAt time.Time) *ActionInvocationSingleResponse { e := new(ActionInvocationSingleResponse) e.Data.ID = id e.Data.RetryCount = retryCount e.Data.State = state + e.Data.Message = message e.Data.ActionID = actionId e.Data.EventID = eventId e.Data.CreatedAt = createdAt diff --git a/go.mod b/go.mod index 7f522b2..60f6355 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module git.myservermanager.com/varakh/upda -go 1.21 +go 1.22 require ( github.com/Depado/ginprom v1.8.1 diff --git a/server/api_handler_action_invocation.go b/server/api_handler_action_invocation.go index 20323fa..10dcf35 100644 --- a/server/api_handler_action_invocation.go +++ b/server/api_handler_action_invocation.go @@ -63,6 +63,7 @@ func (h *actionInvocationHandler) paginate(c *gin.Context) { ID: e.ID, RetryCount: e.RetryCount, State: e.State, + Message: e.Message, ActionID: e.ActionID, EventID: e.EventID, CreatedAt: e.CreatedAt, @@ -87,7 +88,7 @@ func (h *actionInvocationHandler) get(c *gin.Context) { return } - c.JSON(http.StatusOK, api.NewActionInvocationSingleResponse(e.ID, e.RetryCount, e.State, e.ActionID, e.EventID, e.CreatedAt, e.UpdatedAt)) + c.JSON(http.StatusOK, api.NewActionInvocationSingleResponse(e.ID, e.RetryCount, e.State, e.Message, e.ActionID, e.EventID, e.CreatedAt, e.UpdatedAt)) } func (h *actionInvocationHandler) delete(c *gin.Context) { diff --git a/server/entity.go b/server/entity.go index c5f2fc5..31f6bef 100644 --- a/server/entity.go +++ b/server/entity.go @@ -167,6 +167,7 @@ type ActionInvocation struct { ID uuid.UUID `gorm:"type:uuid;primary_key;unique;not null"` RetryCount int `gorm:"not null;default:1"` State string `gorm:"not null"` + Message *string Event Event `gorm:"foreignKey:EventID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` EventID string `gorm:"not null"` Action Action `gorm:"foreignKey:ActionID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` diff --git a/server/repository_action_invocation.go b/server/repository_action_invocation.go index 8638404..e07496e 100644 --- a/server/repository_action_invocation.go +++ b/server/repository_action_invocation.go @@ -13,6 +13,7 @@ type ActionInvocationRepository interface { findAllByState(limit int, maxRetries int, state ...api.ActionInvocationState) ([]*ActionInvocation, error) create(eventId string, actionId string, state api.ActionInvocationState) (*ActionInvocation, error) updateState(id string, state api.ActionInvocationState) (*ActionInvocation, error) + updateMessage(id string, message *string) (*ActionInvocation, error) updateRetryCount(id string, retryCount int) (*ActionInvocation, error) delete(id string) (int64, error) deleteByUpdatedAtBeforeAndStates(time time.Time, retryCount int, state ...api.ActionInvocationState) (int64, error) @@ -117,6 +118,31 @@ func (r *actionInvocationDbRepo) updateState(id string, state api.ActionInvocati return e, nil } +func (r *actionInvocationDbRepo) updateMessage(id string, message *string) (*ActionInvocation, error) { + if id == "" { + return nil, errorValidationNotBlank + } + + var err error + var e *ActionInvocation + + if e, err = r.find(id); err != nil { + return nil, err + } + + e.Message = message + + var res *gorm.DB + if res = r.db.Save(&e); res.Error != nil { + return nil, res.Error + } + if res.RowsAffected == 0 { + return e, errorDatabaseRowsExpected + } + + return e, nil +} + func (r *actionInvocationDbRepo) delete(id string) (int64, error) { if id == "" { return 0, errorValidationNotBlank diff --git a/server/service_action_invocation.go b/server/service_action_invocation.go index f78e47d..0c03a8b 100644 --- a/server/service_action_invocation.go +++ b/server/service_action_invocation.go @@ -147,12 +147,20 @@ func (s *actionInvocationService) invoke(batchSize int, maxRetries int) error { } if err = s.execute(action, eventPayload); err != nil { + var cause error + cause = err + zap.L().Sugar().Errorf("Could not invoke action '%s' (%v) for action invocation '%v'. Reason: %s", action.Label, action.ID, actionInvocation.ID, err.Error()) if _, err = s.updateState(actionInvocation.ID.String(), api.ActionInvocationStateError); err != nil { zap.L().Sugar().Errorf("Could not mark action invocation '%v' as error. Reason: %s", actionInvocation.ID, err.Error()) } + msg := cause.Error() + if _, err = s.updateMessage(actionInvocation.ID.String(), &msg); err != nil { + zap.L().Sugar().Errorf("Could not update action invocation '%v' message. Reason: %s", actionInvocation.ID, err.Error()) + } + newRetryCount := actionInvocation.RetryCount + 1 if _, err = s.updateRetryCount(actionInvocation.ID.String(), newRetryCount); err != nil { @@ -170,6 +178,9 @@ func (s *actionInvocationService) invoke(batchSize int, maxRetries int) error { if _, err = s.updateState(actionInvocation.ID.String(), api.ActionInvocationStateSuccess); err != nil { zap.L().Sugar().Errorf("Could not mark action invocation '%v' as success. Reason: %s", actionInvocation.ID, err.Error()) } + if _, err = s.updateMessage(actionInvocation.ID.String(), nil); err != nil { + zap.L().Sugar().Errorf("Could not update action invocation '%v' message. Reason: %s", actionInvocation.ID, err.Error()) + } } return nil @@ -322,6 +333,26 @@ func (s *actionInvocationService) updateState(id string, state api.ActionInvocat return e, nil } +func (s *actionInvocationService) updateMessage(id string, message *string) (*ActionInvocation, error) { + if id == "" { + return nil, errorValidationNotBlank + } + + var e *ActionInvocation + var err error + + if e, err = s.get(id); err != nil { + return nil, err + } + + if e, err = s.repo.updateMessage(id, message); err != nil { + return nil, err + } + + zap.L().Sugar().Infof("Modified action invocation '%v'", id) + return e, nil +} + func (s *actionInvocationService) updateRetryCount(id string, retryCount int) (*ActionInvocation, error) { if id == "" { return nil, errorValidationNotBlank