notifications/event.go (81 lines of code) (raw):
package notifications
import (
"fmt"
"time"
"github.com/docker/distribution"
)
// EventAction constants used in action field of Event.
const (
EventActionPull = "pull"
EventActionPush = "push"
EventActionMount = "mount"
EventActionDelete = "delete"
EventActionRename = "rename"
)
const (
// EventsMediaType is the mediatype for the json event envelope. If the
// Event, ActorRecord, SourceRecord or Envelope structs change, the version
// number should be incremented.
EventsMediaType = "application/vnd.docker.distribution.events.v1+json"
// LayerMediaType is the media type for image rootfs diffs (aka "layers")
// used by Docker. We don't expect this to change for quite a while.
layerMediaType = "application/vnd.docker.container.image.rootfs.diff+x-gtar"
)
// Envelope defines the fields of a json event envelope message that can hold
// one or more events.
type Envelope struct {
// Events make up the contents of the envelope. Events present in a single
// envelope are not necessarily related.
Events []Event `json:"events,omitempty"`
}
// Event provides the fields required to describe a registry event.
type Event struct {
// ID provides a unique identifier for the event.
ID string `json:"id,omitempty"`
// Timestamp is the time at which the event occurred.
Timestamp time.Time `json:"timestamp,omitempty"`
// Action indicates what action encompasses the provided event.
Action string `json:"action,omitempty"`
// Target uniquely describes the target of the event.
Target Target `json:"target,omitempty"`
// Request covers the request that generated the event.
Request RequestRecord `json:"request,omitempty"`
// Actor specifies the agent that initiated the event. For most
// situations, this could be from the authorization context of the request.
Actor ActorRecord `json:"actor,omitempty"`
// Source identifies the registry node that generated the event. Put
// differently, while the actor "initiates" the event, the source
// "generates" it.
Source SourceRecord `json:"source,omitempty"`
// Meta is a map of key-value pairs that are loosely associated to an event.
// This field should be used only to propagate non-common/highly-specific details
// of an event in cases where the detail can not be communicated in existing attributes of the `Event` struct.
// Meta is not guaranteed to be in every event or to have simiar keys across event types.
Meta map[string]Meta `json:"meta,omitempty"`
}
// Meta is the event meta type
type Meta any
// Target uniquely describes the target of the event.
type Target struct {
distribution.Descriptor
// Rename identifies the changed repository paths
// if appropriate.
Rename *Rename `json:"rename,omitempty"`
// Length in bytes of content. Same as Size field in Descriptor.
// Provided for backwards compatibility.
Length int64 `json:"length,omitempty"`
// Repository identifies the named repository.
Repository string `json:"repository,omitempty"`
// FromRepository identifies the named repository which a blob was mounted
// from if appropriate.
FromRepository string `json:"fromRepository,omitempty"`
// URL provides a direct link to the content.
URL string `json:"url,omitempty"`
// Tag provides the tag
Tag string `json:"tag,omitempty"`
// References provides the references descriptors.
References []distribution.Descriptor `json:"references,omitempty"`
}
type Rename struct {
// Type identifies the rename operation type (i.e. namespace or name)
Type RenameType `json:"type,omitempty"`
// From identifies the repository path which was renamed
From string `json:"from,omitempty"`
// To identifies the newly renamed path of a repository
To string `json:"to,omitempty"`
}
// RenameType defines the types of repository renames
type RenameType string
var (
NameRename RenameType = "name"
NamespaceRename RenameType = "namespace"
)
// ActorRecord specifies the agent that initiated the event. For most
// situations, this could be from the authorization context of the request.
// Data in this record can refer to both the initiating client and the
// generating request.
type ActorRecord struct {
// Name corresponds to the subject or username associated with the
// request context that generated the event.
Name string `json:"name,omitempty"`
// UserType is filled when authentication was used, and we were able to identify the user
// successfully against the auth service.
UserType string `json:"user_type,omitempty"`
// User is a JWT that GitLab Rails generates during authentication. It is forwarded in the notification
// events for tracking purposes. See https://gitlab.com/gitlab-org/container-registry/-/issues/1097.
User string `json:"user,omitempty"`
}
// RequestRecord covers the request that generated the event.
type RequestRecord struct {
// ID uniquely identifies the request that initiated the event.
ID string `json:"id"`
// Addr contains the ip or hostname and possibly port of the client
// connection that initiated the event. This is the RemoteAddr from
// the standard http request.
Addr string `json:"addr,omitempty"`
// Host is the externally accessible host name of the registry instance,
// as specified by the http host header on incoming requests.
Host string `json:"host,omitempty"`
// Method has the request method that generated the event.
Method string `json:"method"`
// UserAgent contains the user agent header of the request.
UserAgent string `json:"useragent"`
}
// SourceRecord identifies the registry node that generated the event. Put
// differently, while the actor "initiates" the event, the source "generates"
// it.
type SourceRecord struct {
// Addr contains the ip or hostname and the port of the registry node
// that generated the event. Generally, this will be resolved by
// os.Hostname() along with the running port.
Addr string `json:"addr,omitempty"`
// InstanceID identifies a running instance of an application. Changes
// after each restart.
InstanceID string `json:"instanceID,omitempty"`
}
// ErrSinkClosed is returned if a write is issued to a sink that has been
// closed. If encountered, the error should be considered terminal and
// retries will not be successful.
var ErrSinkClosed = fmt.Errorf("sink: closed")
// Sink accepts and sends events.
type Sink interface {
// Write writes an event to the sink. If no error is returned,
// the caller will assume that the event was committed and will not
// try to send it again. If an error is received, the caller may retry
// sending the event. The caller should cede the slice of memory to the
// sink and not modify it after calling this method.
Write(event *Event) error
// Close the sink, possibly waiting for pending events to flush.
Close() error
}
func (e *Event) artifact() string {
if e.Target.Tag != "" {
return "tag"
}
if e.Target.MediaType == "application/octet-stream" {
return "blob"
}
// We know that the previous cases take precedence, so we can fall back to manifest. The only exception right now
// is that there is no way to distinguish between deleted blobs and manifests.
// TODO: support for deleted blobs https://gitlab.com/gitlab-org/container-registry/-/issues/920
return "manifest"
}