fleet/server/service/calendar.go
Victor Lyuboslavsky c1a5e3b7b6
Fix calendar duplicated events and other issues (#20443)
#19352
Includes the following changes:
- Re-enable calendar callback
- Introduced a new Redis key that indicates event was updated by
calendar callback. In that case, we ignore subsequent callbacks for 10
seconds.
- This reduces the amount of Google API calls, including handling of the
unneeded callback generated by our own event change.
- Read event from DB after acquiring lock. This is critical since we get
the updated ETag of the Google Calendar event from our DB. Using the
previous ETag when fetching event sometimes returns stale data,
resulting in duplicate events.
- Fixed bug in getCalendarLock where calendar cron would always think it
got the lock
- Do not refetch timezone during calendar callback to reduce Google API
load
- Watch for calendar event changes for 1 week after event end (to
account for user moving event into the future)
- #20442: Speculative improvement for Google callback latency by keeping
the same notification channel (callback URL).
- processCalendarAsync now takes at least 1 sec to process all events,
to reduce CPU/Redis load
- Increased lock expiration time from 1 minute to 20 minutes to account
for potential Google API retries, fixing occasional duplicate events.
- Added `get-events.go` helper script that gets maintenance events from
user calendars, and checks for duplicates

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
2024-07-24 13:40:33 +02:00

59 lines
1.7 KiB
Go

package service
import (
"context"
"net/http"
"net/url"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/gorilla/mux"
)
type calendarWebhookRequest struct {
eventUUID string
googleChannelID string
googleResourceState string
}
// DecodeRequest implement requestDecoder interface to take full control of decoding the request
func (calendarWebhookRequest) DecodeRequest(_ context.Context, r *http.Request) (interface{}, error) {
var req calendarWebhookRequest
eventUUID, ok := mux.Vars(r)["event_uuid"]
if !ok {
return nil, errBadRoute
}
unescaped, err := url.PathUnescape(eventUUID)
if err != nil {
return "", ctxerr.Wrap(r.Context(), err, "unescape value in path")
}
req.eventUUID = unescaped
req.googleChannelID = r.Header.Get("X-Goog-Channel-Id")
req.googleResourceState = r.Header.Get("X-Goog-Resource-State")
return &req, nil
}
type calendarWebhookResponse struct {
Err error `json:"error,omitempty"`
}
func (r calendarWebhookResponse) error() error { return r.Err }
func calendarWebhookEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
req := request.(*calendarWebhookRequest)
err := svc.CalendarWebhook(ctx, req.eventUUID, req.googleChannelID, req.googleResourceState)
if err != nil {
return calendarWebhookResponse{Err: err}, err
}
resp := calendarWebhookResponse{}
return resp, nil
}
func (svc *Service) CalendarWebhook(ctx context.Context, eventUUID string, channelID string, resourceState string) error {
// skipauth: No authorization check needed due to implementation returning only license error.
svc.authz.SkipAuthorization(ctx)
return fleet.ErrMissingLicense
}