Skip to content

Commit

Permalink
feat: Add the OCI Referrers API
Browse files Browse the repository at this point in the history
Signed-off-by: jay-dee7 <[email protected]>
  • Loading branch information
jay-dee7 committed Oct 26, 2023
1 parent 3de684f commit 7de1cc3
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 52 deletions.
29 changes: 29 additions & 0 deletions registry/v2/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,12 @@ func (r *registry) PushManifest(ctx echo.Context) error {
Size: 0,
}

if manifest.Subject != nil && manifest.Subject.Digest != "" {
// we got a manifest with a subject
val.Subject = manifest.Subject.ToGoMap()
ctx.Response().Header().Set("OCI-Subject", manifest.Subject.Digest)
}

txnOp, err := r.store.NewTxn(context.Background())
if err != nil {
errMsg := r.errorResponse(RegistryErrorCodeUnknown, err.Error(), echo.Map{
Expand Down Expand Up @@ -1027,3 +1033,26 @@ func (r *registry) GetRepositoryFromCtx(ctx echo.Context) *types_v2.ContainerIma
}
return nil
}

func (r *registry) GetReferrers(ctx echo.Context) error {
ctx.Set("start", time.Now())

digest := ctx.Param("digest")
artifactType := ctx.QueryParams().Get("artifactType")
if artifactType != "" {
ctx.Response().Header().Set("OCI-Filters-Applied", artifactType)
}

referrer, err := r.store.GetReferrers(ctx.Request().Context(), digest, artifactType)
if err != nil {
ctx.Response().Header().Set("content-type", "application/vnd.oci.image.index.v1+json")
echoErr := ctx.JSON(http.StatusOK, echo.Map{})
r.logger.Log(ctx, nil).Bool("referrersFound", false).Str("artifactType", artifactType).Str("digest", digest).Send()
return echoErr
}

ctx.Response().Header().Set("content-type", "application/vnd.oci.image.index.v1+json")
echoErr := ctx.JSON(http.StatusOK, referrer)
r.logger.Log(ctx, nil).Bool("referrersFound", true).Str("artifactType", artifactType).Str("digest", digest).Send()
return echoErr
}
13 changes: 9 additions & 4 deletions registry/v2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/containerish/OpenRegistry/config"
dfsImpl "github.com/containerish/OpenRegistry/dfs"
store_v2 "github.com/containerish/OpenRegistry/store/v2/registry"
"github.com/containerish/OpenRegistry/store/v2/types"
"github.com/containerish/OpenRegistry/telemetry"
"github.com/labstack/echo/v4"
"github.com/uptrace/bun"
Expand Down Expand Up @@ -84,6 +85,7 @@ const (
RegistryErrorCodeUnauthorized = "UNAUTHORIZED" // authentication is required
RegistryErrorCodeDenied = "DENIED" // request access to resource is denied
RegistryErrorCodeUnsupported = "UNSUPPORTED" // operation is not supported
RegistryErrorCodeReferrerUnknown = "REFERRER_UNKOWN"
)

type (
Expand Down Expand Up @@ -131,10 +133,11 @@ type (
}

ImageManifest struct {
Config Config `json:"config"`
MediaType string `json:"mediaType"`
Layers Layers `json:"layers"`
SchemaVersion int `json:"schemaVersion"`
Subject *types.ReferrerManifest `json:"subject"`
MediaType string `json:"mediaType"`
Config Config `json:"config"`
Layers Layers `json:"layers"`
SchemaVersion int `json:"schemaVersion"`
}

Layers []struct {
Expand Down Expand Up @@ -281,4 +284,6 @@ type Registry interface {

// Create Repository
CreateRepository(ctx echo.Context) error

GetReferrers(ctx echo.Context) error
}
2 changes: 2 additions & 0 deletions router/route_names.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ const (
// this is also a part of catalog api
TagsList = "/tags/list"

GetReferrers = "/referrers/:digest"

// Catalog is used to list the available repositories
Catalog = "/_catalog"

Expand Down
2 changes: 2 additions & 0 deletions router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ func RegisterNSRoutes(nsRouter *echo.Group, reg registry.Registry, registryStore
///GET /v2/<name>/tags/list
nsRouter.Add(http.MethodGet, TagsList, reg.ListTags)

///GET /v2/<name>/tags/list
nsRouter.Add(http.MethodGet, GetReferrers, reg.GetReferrers)
/// mf/sha -> mf/latest
nsRouter.Add(http.MethodDelete, BlobsDigest, reg.DeleteLayer)
nsRouter.Add(http.MethodDelete, ManifestsReference, reg.DeleteTagOrManifest, registryReferenceOrTagValidator())
Expand Down
24 changes: 24 additions & 0 deletions store/v2/registry/registry_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,3 +556,27 @@ func (s *registryStore) Abort(ctx context.Context, txn *bun.Tx) error {
func (s *registryStore) Commit(ctx context.Context, txn *bun.Tx) error {
return txn.Commit()
}

func (s *registryStore) GetReferrers(
ctx context.Context,
digest string,
artifactTypes ...string,
) (*types.ReferrerManifest, error) {
var mf types.ImageManifest
q := s.
db.
NewSelect().
Model(&mf).
Column("subject").
Where("subject ->>'digest' = ?", bun.Ident(digest))

if len(artifactTypes) > 0 {
q.Where("subject ->>'artifactType' in (?)", bun.In(artifactTypes))
}

if err := q.Scan(ctx); err != nil {
return nil, err
}

return mf.GetSubject(), nil
}
1 change: 1 addition & 0 deletions store/v2/registry/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type RegistryBaseStore interface {
SetManifest(ctx context.Context, txn *bun.Tx, im *types.ImageManifest) error
GetManifest(ctx context.Context, ref string) (*types.ImageManifest, error)
GetManifestByReference(ctx context.Context, namespace string, ref string) (*types.ImageManifest, error)
GetReferrers(ctx context.Context, digest string, artifactTypes ...string) (*types.ReferrerManifest, error)
}

type RegistryStore interface {
Expand Down
149 changes: 101 additions & 48 deletions store/v2/types/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package types

import (
"context"
"encoding/json"
"fmt"
"time"

Expand All @@ -26,66 +27,118 @@ func (v RepositoryVisibility) String() string {
}
}

type ContainerImageVisibilityChangeRequest struct {
ImageManifestUUID string `json:"image_manifest_uuid"`
Visibility RepositoryVisibility `json:"visibility_mode"`
}
type (
ContainerImageVisibilityChangeRequest struct {
ImageManifestUUID string `json:"image_manifest_uuid"`
Visibility RepositoryVisibility `json:"visibility_mode"`
}

type ImageManifest struct {
bun.BaseModel `bun:"table:image_manifests,alias:m" json:"-"`
ImageManifest struct {
bun.BaseModel `bun:"table:image_manifests,alias:m" json:"-"`
CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"created_at"`
UpdatedAt time.Time `bun:"updated_at,nullzero" json:"updated_at"`
Repository *ContainerImageRepository `bun:"rel:belongs-to,join:repository_id=id" json:"-"`
User *User `bun:"rel:belongs-to,join:owner_id=id" json:"-"`
Subject map[string]any `bun:"subject,type:jsonb" json:"subject"`
Digest string `bun:"digest,notnull" json:"digest"`
Reference string `bun:"reference,notnull" json:"reference"`
MediaType string `bun:"media_type,notnull" json:"media_type"`
DFSLink string `bun:"dfs_link,notnull" json:"dfs_link"`
Layers []string `bun:"layers,array" json:"layers"`
SchemaVersion int `bun:"schema_version,notnull" json:"schema_version"`
Size uint64 `bun:"size,notnull" json:"size"`
RepositoryID uuid.UUID `bun:"repository_id,type:uuid" json:"repository_id"`
ID uuid.UUID `bun:"id,pk,type:uuid" json:"id"`
OwnerID uuid.UUID `bun:"owner_id,type:uuid" json:"owner_id"`
}

CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"created_at"`
UpdatedAt time.Time `bun:"updated_at,nullzero" json:"updated_at"`
Repository *ContainerImageRepository `bun:"rel:belongs-to,join:repository_id=id" json:"-"`
User *User `bun:"rel:belongs-to,join:owner_id=id" json:"-"`
Digest string `bun:"digest,notnull" json:"digest"`
MediaType string `bun:"media_type,notnull" json:"media_type"`
Reference string `bun:"reference,notnull" json:"reference"`
DFSLink string `bun:"dfs_link,notnull" json:"dfs_link"`
Layers []string `bun:"layers,array" json:"layers"`
SchemaVersion int `bun:"schema_version,notnull" json:"schema_version"`
Size uint64 `bun:"size,notnull" json:"size"`
RepositoryID uuid.UUID `bun:"repository_id,type:uuid" json:"repository_id"`
ID uuid.UUID `bun:"id,pk,type:uuid" json:"id"`
OwnerID uuid.UUID `bun:"owner_id,type:uuid" json:"owner_id"`
}
ContainerImageLayer struct {
bun.BaseModel `bun:"table:layers,alias:l" json:"-"`

CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"created_at"`
UpdatedAt time.Time `bun:"updated_at,nullzero" json:"updated_at"`
ID string `bun:"id,pk,type:uuid" json:"id"`
Digest string `bun:"digest,notnull,unique" json:"digest"`
MediaType string `bun:"media_type,notnull" json:"media_type"`
DFSLink string `bun:"dfs_link" json:"dfs_link"`
Size uint64 `bun:"size,default:0" json:"size"`
}

ContainerImageRepository struct {
bun.BaseModel `bun:"table:repositories,alias:r" json:"-"`

CreatedAt time.Time `bun:"created_at" json:"created_at"`
UpdatedAt time.Time `bun:"updated_at" json:"updated_at"`
MetaTags map[string]any `bun:"meta_tags" json:"meta_tags"`
User *User `bun:"rel:belongs-to,join:owner_id=id" json:"-"`
Project *RepositoryBuild `bun:"rel:has-one,join:id=repository_id" json:"-"`
Description string `bun:"description" json:"description"`
Visibility RepositoryVisibility `bun:"visibility,notnull" json:"visibility"`
Name string `bun:"name,notnull,unique" json:"name"`
ImageManifests []*ImageManifest `bun:"rel:has-many,join:id=repository_id" json:"image_manifests,omitempty"`
Builds []*RepositoryBuild `bun:"rel:has-many,join:id=repository_id" json:"-"`
ID uuid.UUID `bun:"id,pk,type:uuid,default:gen_random_uuid()" json:"id"`
OwnerID uuid.UUID `bun:"owner_id,type:uuid" json:"owner_id"`
}

RepositoryVisibility string

ReferrerManifest struct {
Annotations map[string]string `json:"annotations,omitempty"`
MediaType string `json:"mediaType"`
Digest string `json:"digest"`
ArtifactType string `json:"artifactType,omitempty"`
NewUnspecifiedField string `json:"newUnspecifiedField,omitempty"`
Size int `json:"size"`
}

Referrer struct {
MediaType string `json:"mediaType"`
Manifests []ReferrerManifest `json:"manifests"`
SchemaVersion int `json:"schemaVersion"`
}
)

type ContainerImageLayer struct {
bun.BaseModel `bun:"table:layers,alias:l" json:"-"`
func (rm *ReferrerManifest) ToGoMap() map[string]any {
if rm == nil {
return nil
}

m := map[string]any{
"annotations": rm.Annotations,
"mediaType": rm.MediaType,
"digest": rm.Digest,
"artifactType": rm.ArtifactType,
"newUnspecifiedField": rm.NewUnspecifiedField,
"size": rm.Size,
}

CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"created_at"`
UpdatedAt time.Time `bun:"updated_at,nullzero" json:"updated_at"`
ID string `bun:"id,pk,type:uuid" json:"id"`
Digest string `bun:"digest,notnull,unique" json:"digest"`
MediaType string `bun:"media_type,notnull" json:"media_type"`
DFSLink string `bun:"dfs_link" json:"dfs_link"`
Size uint64 `bun:"size,default:0" json:"size"`
return m
}

type RepositoryVisibility string
func (imf *ImageManifest) GetSubject() *ReferrerManifest {
if imf.Subject == nil {
return nil
}

bz, err := json.Marshal(imf.Subject)
if err != nil {
return nil
}

var refManifest ReferrerManifest
if err = json.Unmarshal(bz, &refManifest); err == nil {
return &refManifest
}

return nil
}

const (
RepositoryVisibilityPublic RepositoryVisibility = "Public"
RepositoryVisibilityPrivate RepositoryVisibility = "Private"
)

type ContainerImageRepository struct {
bun.BaseModel `bun:"table:repositories,alias:r" json:"-"`

CreatedAt time.Time `bun:"created_at" json:"created_at"`
UpdatedAt time.Time `bun:"updated_at" json:"updated_at"`
MetaTags map[string]any `bun:"meta_tags" json:"meta_tags"`
User *User `bun:"rel:belongs-to,join:owner_id=id" json:"-"`
Project *RepositoryBuild `bun:"rel:has-one,join:id=repository_id" json:"-"`
Description string `bun:"description" json:"description"`
Visibility RepositoryVisibility `bun:"visibility,notnull" json:"visibility"`
Name string `bun:"name,notnull,unique" json:"name"`
ImageManifests []*ImageManifest `bun:"rel:has-many,join:id=repository_id" json:"image_manifests,omitempty"`
Builds []*RepositoryBuild `bun:"rel:has-many,join:id=repository_id" json:"-"`
ID uuid.UUID `bun:"id,pk,type:uuid,default:gen_random_uuid()" json:"id"`
OwnerID uuid.UUID `bun:"owner_id,type:uuid" json:"owner_id"`
}

var _ bun.BeforeAppendModelHook = (*ImageManifest)(nil)
var _ bun.BeforeAppendModelHook = (*ContainerImageLayer)(nil)
var _ bun.BeforeAppendModelHook = (*ContainerImageRepository)(nil)
Expand Down

0 comments on commit 7de1cc3

Please sign in to comment.