From 1dd621d7f89a58eef3117fff027a0b8849e2cbd7 Mon Sep 17 00:00:00 2001 From: Tosone Date: Fri, 27 Oct 2023 11:16:34 +0800 Subject: [PATCH] :sparkles: Support image spec 1.1 referrers api (#222) --- go.mod | 2 +- pkg/dal/dao/artifact.go | 16 ++++ pkg/dal/dao/mocks/artifact.go | 15 +++ .../migrations/mysql/0001_initialize.up.sql | 2 + .../postgresql/0001_initialize.up.sql | 2 + .../migrations/sqlite3/0001_initialize.up.sql | 2 + pkg/dal/models/artifact.go | 3 + pkg/dal/query/artifact_sboms.gen.go | 8 ++ pkg/dal/query/artifact_vulnerabilities.gen.go | 8 ++ pkg/dal/query/artifacts.gen.go | 96 ++++++++++++++++++- pkg/dal/query/blobs.gen.go | 8 ++ pkg/dal/query/tags.gen.go | 8 ++ pkg/dal/query/users.gen.go | 14 ++- pkg/handlers/distribution/manifest/handler.go | 4 + .../distribution/manifest/manifest_put.go | 55 +++++++++++ .../manifest/manifest_referrer_get.go | 70 ++++++++++++++ 16 files changed, 306 insertions(+), 7 deletions(-) create mode 100644 pkg/handlers/distribution/manifest/manifest_referrer_get.go diff --git a/go.mod b/go.mod index 71fffb1c..198fad31 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,7 @@ require ( github.com/moby/buildkit v0.12.2 github.com/opencontainers/distribution-spec/specs-go v0.0.0-20231016131659-3940529fe6c0 github.com/opencontainers/go-digest v1.0.0 + github.com/opencontainers/image-spec v1.1.0-rc5 github.com/redis/go-redis/v9 v9.2.1 github.com/robfig/cron/v3 v3.0.1 github.com/rs/zerolog v1.31.0 @@ -218,7 +219,6 @@ require ( github.com/mozillazg/go-httpheader v0.4.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nwaples/rardecode v1.1.3 // indirect - github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/opencontainers/runc v1.1.7 // indirect github.com/opencontainers/runtime-spec v1.1.0-rc.2 // indirect github.com/opencontainers/selinux v1.11.0 // indirect diff --git a/pkg/dal/dao/artifact.go b/pkg/dal/dao/artifact.go index 681aa291..d9634721 100644 --- a/pkg/dal/dao/artifact.go +++ b/pkg/dal/dao/artifact.go @@ -81,6 +81,8 @@ type ArtifactService interface { GetNamespaceSize(ctx context.Context, namespaceID int64) (int64, error) // GetRepositorySize get the specific repository size GetRepositorySize(ctx context.Context, repositoryID int64) (int64, error) + // GetReferrers ... + GetReferrers(ctx context.Context, repositoryID int64, digest string, artifactTypes []string) ([]*models.Artifact, error) } type artifactService struct { @@ -364,3 +366,17 @@ func (s *artifactService) GetRepositorySize(ctx context.Context, repositoryID in } return result.Size, nil } + +// GetReferrers ... +func (s *artifactService) GetReferrers(ctx context.Context, repositoryID int64, digest string, artifactTypes []string) ([]*models.Artifact, error) { + artifactObj, err := s.tx.Artifact.WithContext(ctx).Where(s.tx.Artifact.RepositoryID.Eq(repositoryID)). + Where(s.tx.Artifact.Digest.Eq(digest)).First() + if err != nil { + return nil, err + } + query := s.tx.Artifact.WithContext(ctx).Where(s.tx.Artifact.RepositoryID.Eq(repositoryID)) + if len(artifactTypes) > 0 { + query = query.Where(s.tx.Artifact.ConfigMediaType.In(artifactTypes...)) + } + return query.Where(s.tx.Artifact.ReferrerID.Eq(artifactObj.ID)).Find() +} diff --git a/pkg/dal/dao/mocks/artifact.go b/pkg/dal/dao/mocks/artifact.go index 8a9a8c13..753c8c0a 100644 --- a/pkg/dal/dao/mocks/artifact.go +++ b/pkg/dal/dao/mocks/artifact.go @@ -303,6 +303,21 @@ func (mr *MockArtifactServiceMockRecorder) GetNamespaceSize(arg0, arg1 any) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNamespaceSize", reflect.TypeOf((*MockArtifactService)(nil).GetNamespaceSize), arg0, arg1) } +// GetReferrers mocks base method. +func (m *MockArtifactService) GetReferrers(arg0 context.Context, arg1 int64, arg2 string, arg3 []string) ([]*models.Artifact, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetReferrers", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]*models.Artifact) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetReferrers indicates an expected call of GetReferrers. +func (mr *MockArtifactServiceMockRecorder) GetReferrers(arg0, arg1, arg2, arg3 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReferrers", reflect.TypeOf((*MockArtifactService)(nil).GetReferrers), arg0, arg1, arg2, arg3) +} + // GetRepositorySize mocks base method. func (m *MockArtifactService) GetRepositorySize(arg0 context.Context, arg1 int64) (int64, error) { m.ctrl.T.Helper() diff --git a/pkg/dal/migrations/mysql/0001_initialize.up.sql b/pkg/dal/migrations/mysql/0001_initialize.up.sql index e91647ec..d2d4dd6b 100644 --- a/pkg/dal/migrations/mysql/0001_initialize.up.sql +++ b/pkg/dal/migrations/mysql/0001_initialize.up.sql @@ -167,10 +167,12 @@ CREATE TABLE IF NOT EXISTS `artifacts` ( `pushed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `last_pull` timestamp, `pull_times` bigint NOT NULL DEFAULT 0, + `referrer_id` bigint, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `deleted_at` bigint NOT NULL DEFAULT 0, FOREIGN KEY (`repository_id`) REFERENCES `repositories` (`id`), + FOREIGN KEY (`referrer_id`) REFERENCES `artifacts` (`id`), CONSTRAINT `artifacts_unique_with_repo` UNIQUE (`repository_id`, `digest`, `deleted_at`) ); diff --git a/pkg/dal/migrations/postgresql/0001_initialize.up.sql b/pkg/dal/migrations/postgresql/0001_initialize.up.sql index 00170dbc..3411d229 100644 --- a/pkg/dal/migrations/postgresql/0001_initialize.up.sql +++ b/pkg/dal/migrations/postgresql/0001_initialize.up.sql @@ -233,10 +233,12 @@ CREATE TABLE IF NOT EXISTS "artifacts" ( "pushed_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "last_pull" timestamp, "pull_times" bigint NOT NULL DEFAULT 0, + "referrer_id" bigint, "created_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "deleted_at" bigint NOT NULL DEFAULT 0, FOREIGN KEY ("repository_id") REFERENCES "repositories" ("id"), + FOREIGN KEY ("referrer_id") REFERENCES "artifacts" ("id"), CONSTRAINT "artifacts_unique_with_repo" UNIQUE ("repository_id", "digest", "deleted_at") ); diff --git a/pkg/dal/migrations/sqlite3/0001_initialize.up.sql b/pkg/dal/migrations/sqlite3/0001_initialize.up.sql index a3cdd842..33566ca5 100644 --- a/pkg/dal/migrations/sqlite3/0001_initialize.up.sql +++ b/pkg/dal/migrations/sqlite3/0001_initialize.up.sql @@ -170,11 +170,13 @@ CREATE TABLE IF NOT EXISTS `artifacts` ( `type` text CHECK (`type` IN ('image', 'imageIndex', 'chart', 'cnab', 'wasm', 'provenance', 'cosign', 'unknown')) NOT NULL DEFAULT 'unknown', `pushed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `last_pull` timestamp, + `referrer_id` integer, `pull_times` bigint NOT NULL DEFAULT 0, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `deleted_at` bigint NOT NULL DEFAULT 0, FOREIGN KEY (`repository_id`) REFERENCES `repositories` (`id`), + FOREIGN KEY (`referrer_id`) REFERENCES `artifacts` (`id`), CONSTRAINT `artifacts_unique_with_repo` UNIQUE (`repository_id`, `digest`, `deleted_at`) ); diff --git a/pkg/dal/models/artifact.go b/pkg/dal/models/artifact.go index 262f31f1..35ee0a7c 100644 --- a/pkg/dal/models/artifact.go +++ b/pkg/dal/models/artifact.go @@ -51,6 +51,9 @@ type Artifact struct { Vulnerability ArtifactVulnerability `gorm:"foreignKey:ArtifactID;"` Sbom ArtifactSbom `gorm:"foreignKey:ArtifactID;"` + ReferrerID *int64 + Referrer *Artifact + ArtifactIndexes []*Artifact `gorm:"many2many:artifact_artifacts;"` Blobs []*Blob `gorm:"many2many:artifact_blobs;"` Tags []*Tag `gorm:"foreignKey:ArtifactID;"` diff --git a/pkg/dal/query/artifact_sboms.gen.go b/pkg/dal/query/artifact_sboms.gen.go index aa2c75a2..988011d3 100644 --- a/pkg/dal/query/artifact_sboms.gen.go +++ b/pkg/dal/query/artifact_sboms.gen.go @@ -74,6 +74,11 @@ func newArtifactSbom(db *gorm.DB, opts ...gen.DOOption) artifactSbom { }, }, }, + Referrer: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Artifact.Referrer", "models.Artifact"), + }, Vulnerability: struct { field.RelationField Artifact struct { @@ -259,6 +264,9 @@ type artifactSbomBelongsToArtifact struct { } } } + Referrer struct { + field.RelationField + } Vulnerability struct { field.RelationField Artifact struct { diff --git a/pkg/dal/query/artifact_vulnerabilities.gen.go b/pkg/dal/query/artifact_vulnerabilities.gen.go index b55a8ddc..22f84c89 100644 --- a/pkg/dal/query/artifact_vulnerabilities.gen.go +++ b/pkg/dal/query/artifact_vulnerabilities.gen.go @@ -75,6 +75,11 @@ func newArtifactVulnerability(db *gorm.DB, opts ...gen.DOOption) artifactVulnera }, }, }, + Referrer: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Artifact.Referrer", "models.Artifact"), + }, Vulnerability: struct { field.RelationField Artifact struct { @@ -263,6 +268,9 @@ type artifactVulnerabilityBelongsToArtifact struct { } } } + Referrer struct { + field.RelationField + } Vulnerability struct { field.RelationField Artifact struct { diff --git a/pkg/dal/query/artifacts.gen.go b/pkg/dal/query/artifacts.gen.go index be2fff6d..87e018d2 100644 --- a/pkg/dal/query/artifacts.gen.go +++ b/pkg/dal/query/artifacts.gen.go @@ -44,6 +44,7 @@ func newArtifact(db *gorm.DB, opts ...gen.DOOption) artifact { _artifact.LastPull = field.NewField(tableName, "last_pull") _artifact.PushedAt = field.NewTime(tableName, "pushed_at") _artifact.PullTimes = field.NewInt64(tableName, "pull_times") + _artifact.ReferrerID = field.NewInt64(tableName, "referrer_id") _artifact.Vulnerability = artifactHasOneVulnerability{ db: db.Session(&gorm.Session{}), @@ -62,6 +63,9 @@ func newArtifact(db *gorm.DB, opts ...gen.DOOption) artifact { } } } + Referrer struct { + field.RelationField + } Vulnerability struct { field.RelationField } @@ -123,6 +127,11 @@ func newArtifact(db *gorm.DB, opts ...gen.DOOption) artifact { }, }, }, + Referrer: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Vulnerability.Artifact.Referrer", "models.Artifact"), + }, Vulnerability: struct { field.RelationField }{ @@ -201,6 +210,12 @@ func newArtifact(db *gorm.DB, opts ...gen.DOOption) artifact { RelationField: field.NewRelation("Repository", "models.Repository"), } + _artifact.Referrer = artifactBelongsToReferrer{ + db: db.Session(&gorm.Session{}), + + RelationField: field.NewRelation("Referrer", "models.Artifact"), + } + _artifact.ArtifactIndexes = artifactManyToManyArtifactIndexes{ db: db.Session(&gorm.Session{}), @@ -238,6 +253,7 @@ type artifact struct { LastPull field.Field PushedAt field.Time PullTimes field.Int64 + ReferrerID field.Int64 Vulnerability artifactHasOneVulnerability Sbom artifactHasOneSbom @@ -246,6 +262,8 @@ type artifact struct { Repository artifactBelongsToRepository + Referrer artifactBelongsToReferrer + ArtifactIndexes artifactManyToManyArtifactIndexes Blobs artifactManyToManyBlobs @@ -281,6 +299,7 @@ func (a *artifact) updateTableName(table string) *artifact { a.LastPull = field.NewField(table, "last_pull") a.PushedAt = field.NewTime(table, "pushed_at") a.PullTimes = field.NewInt64(table, "pull_times") + a.ReferrerID = field.NewInt64(table, "referrer_id") a.fillFieldMap() @@ -305,7 +324,7 @@ func (a *artifact) GetFieldByName(fieldName string) (field.OrderExpr, bool) { } func (a *artifact) fillFieldMap() { - a.fieldMap = make(map[string]field.Expr, 22) + a.fieldMap = make(map[string]field.Expr, 24) a.fieldMap["created_at"] = a.CreatedAt a.fieldMap["updated_at"] = a.UpdatedAt a.fieldMap["deleted_at"] = a.DeletedAt @@ -322,6 +341,7 @@ func (a *artifact) fillFieldMap() { a.fieldMap["last_pull"] = a.LastPull a.fieldMap["pushed_at"] = a.PushedAt a.fieldMap["pull_times"] = a.PullTimes + a.fieldMap["referrer_id"] = a.ReferrerID } @@ -354,6 +374,9 @@ type artifactHasOneVulnerability struct { } } } + Referrer struct { + field.RelationField + } Vulnerability struct { field.RelationField } @@ -662,6 +685,77 @@ func (a artifactBelongsToRepositoryTx) Count() int64 { return a.tx.Count() } +type artifactBelongsToReferrer struct { + db *gorm.DB + + field.RelationField +} + +func (a artifactBelongsToReferrer) Where(conds ...field.Expr) *artifactBelongsToReferrer { + if len(conds) == 0 { + return &a + } + + exprs := make([]clause.Expression, 0, len(conds)) + for _, cond := range conds { + exprs = append(exprs, cond.BeCond().(clause.Expression)) + } + a.db = a.db.Clauses(clause.Where{Exprs: exprs}) + return &a +} + +func (a artifactBelongsToReferrer) WithContext(ctx context.Context) *artifactBelongsToReferrer { + a.db = a.db.WithContext(ctx) + return &a +} + +func (a artifactBelongsToReferrer) Session(session *gorm.Session) *artifactBelongsToReferrer { + a.db = a.db.Session(session) + return &a +} + +func (a artifactBelongsToReferrer) Model(m *models.Artifact) *artifactBelongsToReferrerTx { + return &artifactBelongsToReferrerTx{a.db.Model(m).Association(a.Name())} +} + +type artifactBelongsToReferrerTx struct{ tx *gorm.Association } + +func (a artifactBelongsToReferrerTx) Find() (result *models.Artifact, err error) { + return result, a.tx.Find(&result) +} + +func (a artifactBelongsToReferrerTx) Append(values ...*models.Artifact) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Append(targetValues...) +} + +func (a artifactBelongsToReferrerTx) Replace(values ...*models.Artifact) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Replace(targetValues...) +} + +func (a artifactBelongsToReferrerTx) Delete(values ...*models.Artifact) (err error) { + targetValues := make([]interface{}, len(values)) + for i, v := range values { + targetValues[i] = v + } + return a.tx.Delete(targetValues...) +} + +func (a artifactBelongsToReferrerTx) Clear() error { + return a.tx.Clear() +} + +func (a artifactBelongsToReferrerTx) Count() int64 { + return a.tx.Count() +} + type artifactManyToManyArtifactIndexes struct { db *gorm.DB diff --git a/pkg/dal/query/blobs.gen.go b/pkg/dal/query/blobs.gen.go index f78fa585..fce4ae04 100644 --- a/pkg/dal/query/blobs.gen.go +++ b/pkg/dal/query/blobs.gen.go @@ -73,6 +73,11 @@ func newBlob(db *gorm.DB, opts ...gen.DOOption) blob { }, }, }, + Referrer: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Artifacts.Referrer", "models.Artifact"), + }, Vulnerability: struct { field.RelationField Artifact struct { @@ -251,6 +256,9 @@ type blobManyToManyArtifacts struct { } } } + Referrer struct { + field.RelationField + } Vulnerability struct { field.RelationField Artifact struct { diff --git a/pkg/dal/query/tags.gen.go b/pkg/dal/query/tags.gen.go index 5e477c54..e43ac6f9 100644 --- a/pkg/dal/query/tags.gen.go +++ b/pkg/dal/query/tags.gen.go @@ -70,6 +70,11 @@ func newTag(db *gorm.DB, opts ...gen.DOOption) tag { }{ RelationField: field.NewRelation("Artifact.Repository", "models.Repository"), }, + Referrer: struct { + field.RelationField + }{ + RelationField: field.NewRelation("Artifact.Referrer", "models.Artifact"), + }, Vulnerability: struct { field.RelationField Artifact struct { @@ -322,6 +327,9 @@ type tagBelongsToArtifact struct { Repository struct { field.RelationField } + Referrer struct { + field.RelationField + } Vulnerability struct { field.RelationField Artifact struct { diff --git a/pkg/dal/query/users.gen.go b/pkg/dal/query/users.gen.go index 334e6a55..c68ca35e 100644 --- a/pkg/dal/query/users.gen.go +++ b/pkg/dal/query/users.gen.go @@ -34,8 +34,9 @@ func newUser(db *gorm.DB, opts ...gen.DOOption) user { _user.Username = field.NewString(tableName, "username") _user.Password = field.NewString(tableName, "password") _user.Email = field.NewString(tableName, "email") - _user.Status = field.NewField(tableName, "status") _user.LastLogin = field.NewTime(tableName, "last_login") + _user.Status = field.NewField(tableName, "status") + _user.Role = field.NewField(tableName, "role") _user.NamespaceLimit = field.NewInt64(tableName, "namespace_limit") _user.NamespaceCount = field.NewInt64(tableName, "namespace_count") @@ -55,8 +56,9 @@ type user struct { Username field.String Password field.String Email field.String - Status field.Field LastLogin field.Time + Status field.Field + Role field.Field NamespaceLimit field.Int64 NamespaceCount field.Int64 @@ -82,8 +84,9 @@ func (u *user) updateTableName(table string) *user { u.Username = field.NewString(table, "username") u.Password = field.NewString(table, "password") u.Email = field.NewString(table, "email") - u.Status = field.NewField(table, "status") u.LastLogin = field.NewTime(table, "last_login") + u.Status = field.NewField(table, "status") + u.Role = field.NewField(table, "role") u.NamespaceLimit = field.NewInt64(table, "namespace_limit") u.NamespaceCount = field.NewInt64(table, "namespace_count") @@ -110,7 +113,7 @@ func (u *user) GetFieldByName(fieldName string) (field.OrderExpr, bool) { } func (u *user) fillFieldMap() { - u.fieldMap = make(map[string]field.Expr, 11) + u.fieldMap = make(map[string]field.Expr, 12) u.fieldMap["created_at"] = u.CreatedAt u.fieldMap["updated_at"] = u.UpdatedAt u.fieldMap["deleted_at"] = u.DeletedAt @@ -118,8 +121,9 @@ func (u *user) fillFieldMap() { u.fieldMap["username"] = u.Username u.fieldMap["password"] = u.Password u.fieldMap["email"] = u.Email - u.fieldMap["status"] = u.Status u.fieldMap["last_login"] = u.LastLogin + u.fieldMap["status"] = u.Status + u.fieldMap["role"] = u.Role u.fieldMap["namespace_limit"] = u.NamespaceLimit u.fieldMap["namespace_count"] = u.NamespaceCount } diff --git a/pkg/handlers/distribution/manifest/handler.go b/pkg/handlers/distribution/manifest/handler.go index 6fa12112..d547cf47 100644 --- a/pkg/handlers/distribution/manifest/handler.go +++ b/pkg/handlers/distribution/manifest/handler.go @@ -36,6 +36,8 @@ type Handlers interface { PutManifest(ctx echo.Context) error // DeleteManifest ... DeleteManifest(ctx echo.Context) error + // GetReferrer ... + GetReferrer(ctx echo.Context) error } var _ Handlers = &handler{} @@ -113,6 +115,8 @@ func (f factory) Initialize(c echo.Context) error { default: return c.String(http.StatusMethodNotAllowed, "Method Not Allowed") } + } else if strings.HasSuffix(urix, "/referrers") && method == http.MethodGet { + return manifestHandler.GetReferrer(c) } return distribution.ErrNext } diff --git a/pkg/handlers/distribution/manifest/manifest_put.go b/pkg/handlers/distribution/manifest/manifest_put.go index a86641a6..9e145a9e 100644 --- a/pkg/handlers/distribution/manifest/manifest_put.go +++ b/pkg/handlers/distribution/manifest/manifest_put.go @@ -16,6 +16,7 @@ package manifest import ( "context" + "encoding/json" "errors" "io" "net/http" @@ -25,6 +26,7 @@ import ( "github.com/distribution/distribution/v3" "github.com/labstack/echo/v4" "github.com/opencontainers/go-digest" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/rs/zerolog/log" "github.com/spf13/viper" "gorm.io/gorm" @@ -135,6 +137,13 @@ func (h *handler) PutManifest(c echo.Context) error { Raw: bodyBytes, } + referrerID, err := h.getArtifactReferrer(ctx, repository, manifest) + if err != nil { + log.Error().Err(err).Str("digest", refs.Digest.String()).Msg("Get artifact referrer failed") + return xerrors.NewDSError(c, xerrors.DSErrCodeUnknown) + } + artifactObj.ReferrerID = referrerID + artifactService := h.artifactServiceFactory.New() tryFindArtifactObj, err := artifactService.GetByDigest(ctx, repositoryObj.ID, refs.Digest.String()) if err != nil { @@ -428,3 +437,49 @@ func (h *handler) getArtifactType(descriptor distribution.Descriptor, manifest d } return enums.ArtifactTypeUnknown } + +// getArtifactReferrer ... +func (h *handler) getArtifactReferrer(ctx context.Context, repository string, manifest distribution.Manifest) (*int64, error) { + mediaType, data, err := manifest.Payload() + if err != nil { + return nil, err + } + repositoryService := h.repositoryServiceFactory.New() + repositoryObj, err := repositoryService.GetByName(ctx, repository) + if err != nil { + return nil, err + } + + var digest string + + if mediaType == imgspecv1.MediaTypeImageManifest { // nolint: gocritic + var decoded imgspecv1.Manifest + err = json.Unmarshal(data, &decoded) + if err != nil { + return nil, err + } + if decoded.Subject == nil { + return nil, nil + } + digest = decoded.Subject.Digest.String() + } else if mediaType == imgspecv1.MediaTypeImageIndex { + var decoded imgspecv1.Index + err = json.Unmarshal(data, &decoded) + if err != nil { + return nil, err + } + if decoded.Subject == nil { + return nil, nil + } + digest = decoded.Subject.Digest.String() + } else { + return nil, nil + } + + artifactService := h.artifactServiceFactory.New() + artifactObj, err := artifactService.GetByDigest(ctx, repositoryObj.ID, digest) + if err != nil { + return nil, err + } + return ptr.Of(artifactObj.ID), nil +} diff --git a/pkg/handlers/distribution/manifest/manifest_referrer_get.go b/pkg/handlers/distribution/manifest/manifest_referrer_get.go new file mode 100644 index 00000000..2e81c1d0 --- /dev/null +++ b/pkg/handlers/distribution/manifest/manifest_referrer_get.go @@ -0,0 +1,70 @@ +package manifest + +import ( + "encoding/json" + "net/http" + "strings" + + "github.com/labstack/echo/v4" + "github.com/opencontainers/go-digest" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/rs/zerolog/log" + + "github.com/go-sigma/sigma/pkg/xerrors" +) + +// GetReferrer ... +func (h *handler) GetReferrer(c echo.Context) error { + ctx := log.Logger.WithContext(c.Request().Context()) + uri := c.Request().URL.Path + ref := strings.TrimPrefix(uri[strings.LastIndex(uri, "/"):], "/") + repository := strings.TrimPrefix(strings.TrimSuffix(uri[:strings.LastIndex(uri, "/")], "/referrers"), "/v2/") + + _, err := digest.Parse(ref) + if err != nil { + log.Error().Err(err).Str("ref", ref).Msg("Digest is invalid") + return xerrors.NewDSError(c, xerrors.DSErrCodeDigestInvalid) + } + + artifactType := c.QueryParam("artifactType") + c.Response().Header().Set("OCI-Filters-Applied", artifactType) + + var result = imgspecv1.Index{ + MediaType: "application/vnd.oci.image.index.v1+json", + } + result.SchemaVersion = 2 + + repositoryService := h.repositoryServiceFactory.New() + repositoryObj, err := repositoryService.GetByName(ctx, repository) + if err != nil { + log.Error().Err(err).Str("repository", repository).Msg("Get repository failed") + return xerrors.NewDSError(c, xerrors.DSErrCodeUnknown) + } + + artifactService := h.artifactServiceFactory.New() + artifactObjs, err := artifactService.GetReferrers(ctx, repositoryObj.ID, ref, strings.Split(artifactType, ",")) + if err != nil { + log.Error().Err(err).Msg("Get referrers failed") + return xerrors.NewDSError(c, xerrors.DSErrCodeUnknown) + } + if len(artifactObjs) == 0 { + return c.JSON(http.StatusOK, result) + } + result.Manifests = make([]imgspecv1.Descriptor, 0, len(artifactObjs)) + for _, artifactObj := range artifactObjs { + var decoded imgspecv1.Manifest + err = json.Unmarshal(artifactObj.Raw, &decoded) + if err != nil { + log.Error().Err(err).Msg("Unmarshal artifact failed") + return xerrors.NewDSError(c, xerrors.DSErrCodeUnknown) + } + result.Manifests = append(result.Manifests, imgspecv1.Descriptor{ + MediaType: decoded.MediaType, + Size: artifactObj.Size, + Digest: digest.Digest(artifactObj.Digest), + ArtifactType: decoded.Config.MediaType, + Annotations: decoded.Annotations, + }) + } + return c.JSON(http.StatusOK, result) +}