diff --git a/app/store/database/migrate/postgres/0106_create_index_download_stat_artifact_id.down.sql b/app/store/database/migrate/postgres/0106_create_index_download_stat_artifact_id.down.sql new file mode 100644 index 000000000..162769268 --- /dev/null +++ b/app/store/database/migrate/postgres/0106_create_index_download_stat_artifact_id.down.sql @@ -0,0 +1 @@ +DROP INDEX IF EXISTS download_stat_artifact_id; diff --git a/app/store/database/migrate/postgres/0106_create_index_download_stat_artifact_id.up.sql b/app/store/database/migrate/postgres/0106_create_index_download_stat_artifact_id.up.sql new file mode 100644 index 000000000..57a20fd1b --- /dev/null +++ b/app/store/database/migrate/postgres/0106_create_index_download_stat_artifact_id.up.sql @@ -0,0 +1 @@ +CREATE INDEX download_stat_artifact_id ON download_stats(download_stat_artifact_id); \ No newline at end of file diff --git a/app/store/database/migrate/sqlite/0106_create_index_download_stat_artifact_id.down.sql b/app/store/database/migrate/sqlite/0106_create_index_download_stat_artifact_id.down.sql new file mode 100644 index 000000000..162769268 --- /dev/null +++ b/app/store/database/migrate/sqlite/0106_create_index_download_stat_artifact_id.down.sql @@ -0,0 +1 @@ +DROP INDEX IF EXISTS download_stat_artifact_id; diff --git a/app/store/database/migrate/sqlite/0106_create_index_download_stat_artifact_id.up.sql b/app/store/database/migrate/sqlite/0106_create_index_download_stat_artifact_id.up.sql new file mode 100644 index 000000000..57a20fd1b --- /dev/null +++ b/app/store/database/migrate/sqlite/0106_create_index_download_stat_artifact_id.up.sql @@ -0,0 +1 @@ +CREATE INDEX download_stat_artifact_id ON download_stats(download_stat_artifact_id); \ No newline at end of file diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index 407f87df0..c1842b664 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -518,7 +518,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro if err != nil { return nil, err } - apiHandler := router.APIHandlerProvider(registryRepository, upstreamProxyConfigRepository, fileManager, tagRepository, manifestRepository, cleanupPolicyRepository, imageRepository, storageDriver, spaceFinder, transactor, authenticator, provider, authorizer, auditService, artifactRepository, webhooksRepository, webhooksExecutionRepository, service2, spacePathStore, reporter10) + apiHandler := router.APIHandlerProvider(registryRepository, upstreamProxyConfigRepository, fileManager, tagRepository, manifestRepository, cleanupPolicyRepository, imageRepository, storageDriver, spaceFinder, transactor, authenticator, provider, authorizer, auditService, artifactRepository, webhooksRepository, webhooksExecutionRepository, service2, spacePathStore, reporter10, downloadStatRepository) mavenDBStore := maven.DBStoreProvider(registryRepository, imageRepository, artifactRepository, spaceStore, bandwidthStatRepository, downloadStatRepository, nodesRepository, upstreamProxyConfigRepository) mavenLocalRegistry := maven.LocalRegistryProvider(mavenDBStore, transactor, fileManager) mavenController := maven.ProvideProxyController(mavenLocalRegistry, secretService, spaceFinder) diff --git a/registry/app/api/controller/metadata/artifact_mapper.go b/registry/app/api/controller/metadata/artifact_mapper.go index dd8330719..c87d303b3 100644 --- a/registry/app/api/controller/metadata/artifact_mapper.go +++ b/registry/app/api/controller/metadata/artifact_mapper.go @@ -497,7 +497,7 @@ func GetPythonArtifactDetail( return *artifactDetail } -func GetArtifactSummary(artifact types.ArtifactMetadata) *artifactapi.ArtifactSummaryResponseJSONResponse { +func GetArtifactSummary(artifact types.ImageMetadata) *artifactapi.ArtifactSummaryResponseJSONResponse { createdAt := GetTimeInMs(artifact.CreatedAt) modifiedAt := GetTimeInMs(artifact.ModifiedAt) artifactVersionSummary := &artifactapi.ArtifactSummary{ @@ -505,7 +505,6 @@ func GetArtifactSummary(artifact types.ArtifactMetadata) *artifactapi.ArtifactSu ModifiedAt: &modifiedAt, DownloadsCount: &artifact.DownloadCount, ImageName: artifact.Name, - Labels: &artifact.Labels, PackageType: artifact.PackageType, } response := &artifactapi.ArtifactSummaryResponseJSONResponse{ diff --git a/registry/app/api/controller/metadata/controller.go b/registry/app/api/controller/metadata/controller.go index 89c6dded6..8e34a3683 100644 --- a/registry/app/api/controller/metadata/controller.go +++ b/registry/app/api/controller/metadata/controller.go @@ -49,6 +49,7 @@ type APIController struct { RegistryMetadataHelper RegistryMetadataHelper WebhookService WebhookService ArtifactEventReporter registryevents.Reporter + DownloadStatRepository store.DownloadStatRepository } func NewAPIController( @@ -73,6 +74,7 @@ func NewAPIController( registryMetadataHelper RegistryMetadataHelper, webhookService WebhookService, artifactEventReporter registryevents.Reporter, + downloadStatRepository store.DownloadStatRepository, ) *APIController { return &APIController{ fileManager: fileManager, @@ -96,5 +98,6 @@ func NewAPIController( RegistryMetadataHelper: registryMetadataHelper, WebhookService: webhookService, ArtifactEventReporter: artifactEventReporter, + DownloadStatRepository: downloadStatRepository, } } diff --git a/registry/app/api/controller/metadata/get_artifacts_docker_manifests.go b/registry/app/api/controller/metadata/get_artifacts_docker_manifests.go index 7ff775323..52c62ad2b 100644 --- a/registry/app/api/controller/metadata/get_artifacts_docker_manifests.go +++ b/registry/app/api/controller/metadata/get_artifacts_docker_manifests.go @@ -75,11 +75,7 @@ func (c *APIController) GetDockerArtifactManifests( image := string(r.Artifact) version := string(r.Version) - artifactMetadata, err := c.TagStore.GetLatestTagMetadata(ctx, regInfo.parentID, regInfo.RegistryIdentifier, image) - if err != nil { - return artifactManifestsErrorRs(err), nil - } - manifestDetailsList, err := c.ProcessManifest(ctx, regInfo, image, version, artifactMetadata.DownloadCount) + manifestDetailsList, err := c.ProcessManifest(ctx, regInfo, image, version) if err != nil { return artifactManifestsErrorRs(err), nil } @@ -98,7 +94,7 @@ func (c *APIController) GetDockerArtifactManifests( func (c *APIController) getManifestList( ctx context.Context, reqManifest *ml.DeserializedManifestList, registry *types.Registry, image string, - regInfo *RegistryRequestBaseInfo, downloadCount int64, + regInfo *RegistryRequestBaseInfo, ) ([]artifact.DockerManifestDetails, error) { manifestDetailsList := []artifact.DockerManifestDetails{} for _, manifestEntry := range reqManifest.Manifests { @@ -123,7 +119,11 @@ func (c *APIController) getManifestList( if err != nil { return nil, err } - manifestDetailsList = append(manifestDetailsList, getManifestDetails(referencedManifest, mConfig, downloadCount)) + md, err := c.getManifestDetails(ctx, referencedManifest, mConfig) + if err != nil { + return nil, err + } + manifestDetailsList = append(manifestDetailsList, md) } return manifestDetailsList, nil } @@ -136,22 +136,31 @@ func artifactManifestsErrorRs(err error) artifact.GetDockerArtifactManifestsResp } } -func getManifestDetails( - m *types.Manifest, mConfig *manifestConfig, downloadsCount int64, -) artifact.DockerManifestDetails { +func (c *APIController) getManifestDetails( + ctx context.Context, + m *types.Manifest, mConfig *manifestConfig, +) (artifact.DockerManifestDetails, error) { createdAt := GetTimeInMs(m.CreatedAt) size := GetSize(m.TotalSize) - + dgst, _ := types.NewDigest(m.Digest) + image, err := c.ImageStore.GetByName(ctx, m.RegistryID, m.ImageName) + if err != nil { + return artifact.DockerManifestDetails{}, err + } manifestDetails := artifact.DockerManifestDetails{ - Digest: m.Digest.String(), - CreatedAt: &createdAt, - Size: &size, - DownloadsCount: &downloadsCount, + Digest: m.Digest.String(), + CreatedAt: &createdAt, + Size: &size, + } + downloadCountMap, err := c.DownloadStatRepository.GetTotalDownloadsForManifests(ctx, []string{string(dgst)}, image.ID) + if err == nil && len(downloadCountMap) > 0 { + downloadCount := downloadCountMap[string(dgst)] + manifestDetails.DownloadsCount = &downloadCount } if mConfig != nil { manifestDetails.OsArch = fmt.Sprintf("%s/%s", mConfig.Os, mConfig.Arch) } - return manifestDetails + return manifestDetails, nil } // ProcessManifest processes a Docker artifact manifest by retrieving the manifest details from the database, @@ -161,7 +170,7 @@ func getManifestDetails( func (c *APIController) ProcessManifest( ctx context.Context, regInfo *RegistryRequestBaseInfo, - image, version string, downloadCount int64, + image, version string, ) ([]artifact.DockerManifestDetails, error) { registry, err := c.RegistryRepository.GetByParentIDAndName(ctx, regInfo.parentID, regInfo.RegistryIdentifier) if err != nil { @@ -186,15 +195,23 @@ func (c *APIController) ProcessManifest( if err != nil { return nil, err } - manifestDetailsList = append(manifestDetailsList, getManifestDetails(m, mConfig, downloadCount)) + md, err := c.getManifestDetails(ctx, m, mConfig) + if err != nil { + return nil, err + } + manifestDetailsList = append(manifestDetailsList, md) case *ocischema.DeserializedManifest: mConfig, err := getManifestConfig(ctx, reqManifest.Config().Digest, regInfo.RootIdentifier, c.StorageDriver) if err != nil { return nil, err } - manifestDetailsList = append(manifestDetailsList, getManifestDetails(m, mConfig, downloadCount)) + md, err := c.getManifestDetails(ctx, m, mConfig) + if err != nil { + return nil, err + } + manifestDetailsList = append(manifestDetailsList, md) case *ml.DeserializedManifestList: - manifestDetailsList, err = c.getManifestList(ctx, reqManifest, registry, image, regInfo, downloadCount) + manifestDetailsList, err = c.getManifestList(ctx, reqManifest, registry, image, regInfo) if err != nil { return nil, err } diff --git a/registry/app/api/controller/metadata/get_artifacts_summary.go b/registry/app/api/controller/metadata/get_artifacts_summary.go index 9958873f5..d447a627d 100644 --- a/registry/app/api/controller/metadata/get_artifacts_summary.go +++ b/registry/app/api/controller/metadata/get_artifacts_summary.go @@ -74,29 +74,54 @@ func (c *APIController) GetArtifactSummary( }, nil } - var metadata *types.ArtifactMetadata - if registry.PackageType == artifact.PackageTypeDOCKER || registry.PackageType == artifact.PackageTypeHELM { - metadata, err = c.TagStore.GetLatestTagMetadata(ctx, regInfo.parentID, regInfo.RegistryIdentifier, image) - - if err != nil { - return artifact.GetArtifactSummary500JSONResponse{ - InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse( - *GetErrorResponse(http.StatusInternalServerError, err.Error()), - ), - }, nil - } - } else { - metadata, err = c.ArtifactStore.GetLatestArtifactMetadata(ctx, regInfo.parentID, regInfo.RegistryIdentifier, image) - - if err != nil { - return artifact.GetArtifactSummary500JSONResponse{ - InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse( - *GetErrorResponse(http.StatusInternalServerError, err.Error()), - ), - }, nil - } + metadata, err := c.getImageMetadata(ctx, registry, image) + if err != nil { + return artifact.GetArtifactSummary500JSONResponse{ + InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse( + *GetErrorResponse(http.StatusInternalServerError, err.Error()), + ), + }, nil } return artifact.GetArtifactSummary200JSONResponse{ ArtifactSummaryResponseJSONResponse: *GetArtifactSummary(*metadata), }, nil } + +func (c *APIController) getImageMetadata( + ctx context.Context, + registry *types.Registry, + image string, +) (*types.ImageMetadata, error) { + img, err := c.ImageStore.GetByName(ctx, registry.ID, image) + if err != nil { + return nil, err + } + downloadCount, err := c.DownloadStatRepository.GetTotalDownloadsForImage(ctx, img.ID) + if err != nil { + return nil, err + } + imgMetadata := &types.ImageMetadata{ + Name: image, + DownloadCount: downloadCount, + RepoName: registry.Name, + PackageType: registry.PackageType, + CreatedAt: img.CreatedAt, + } + + if registry.PackageType == artifact.PackageTypeDOCKER || registry.PackageType == artifact.PackageTypeHELM { + latestTag, err := c.TagStore.GetLatestTag(ctx, registry.ID, image) + if err != nil { + return nil, err + } + imgMetadata.LatestVersion = latestTag.Name + imgMetadata.ModifiedAt = latestTag.UpdatedAt + } else { + latestArtifact, err := c.ArtifactStore.GetLatestByImageID(ctx, img.ID) + if err != nil { + return nil, err + } + imgMetadata.LatestVersion = latestArtifact.Version + imgMetadata.ModifiedAt = latestArtifact.UpdatedAt + } + return imgMetadata, nil +} diff --git a/registry/app/api/controller/metadata/get_artifacts_versions.go b/registry/app/api/controller/metadata/get_artifacts_versions.go index 43cf6a81f..106166b12 100644 --- a/registry/app/api/controller/metadata/get_artifacts_versions.go +++ b/registry/app/api/controller/metadata/get_artifacts_versions.go @@ -82,14 +82,38 @@ func (c *APIController) GetAllArtifactVersions( if err != nil { return throw500Error(err) } - + img, err := c.ImageStore.GetByName(ctx, registry.ID, image) + if err != nil { + return throw500Error(err) + } + //nolint:nestif if registry.PackageType == artifact.PackageTypeDOCKER || registry.PackageType == artifact.PackageTypeHELM { tags, err := c.TagStore.GetAllTagsByRepoAndImage( ctx, regInfo.parentID, regInfo.RegistryIdentifier, image, regInfo.sortByField, regInfo.sortByOrder, regInfo.limit, regInfo.offset, regInfo.searchTerm, ) + if err != nil { + return throw500Error(err) + } - count, _ := c.TagStore.CountAllTagsByRepoAndImage( + var digests []string + for _, tag := range *tags { + if tag.Digest != "" { + digests = append(digests, tag.Digest) + } + } + + counts, err := c.DownloadStatRepository.GetTotalDownloadsForManifests(ctx, digests, img.ID) + if err != nil { + return throw500Error(err) + } + + for i, tag := range *tags { + if tag.Digest != "" { + (*tags)[i].DownloadCount = counts[tag.Digest] + } + } + count, err := c.TagStore.CountAllTagsByRepoAndImage( ctx, regInfo.parentID, regInfo.RegistryIdentifier, image, regInfo.searchTerm, ) diff --git a/registry/app/api/controller/metadata/get_registries.go b/registry/app/api/controller/metadata/get_registries.go index 14c16698c..ec67d2e22 100644 --- a/registry/app/api/controller/metadata/get_registries.go +++ b/registry/app/api/controller/metadata/get_registries.go @@ -47,8 +47,14 @@ func (c *APIController) GetAllRegistries( registryIDsParam: nil, recursive: r.Params.Recursive != nil && bool(*r.Params.Recursive), // default is false } - regInfo, _ := c.GetRegistryRequestInfo(ctx, *registryRequestParams) - + regInfo, err := c.GetRegistryRequestInfo(ctx, *registryRequestParams) + if err != nil { + return artifact.GetAllRegistries400JSONResponse{ + BadRequestJSONResponse: artifact.BadRequestJSONResponse( + *GetErrorResponse(http.StatusBadRequest, err.Error()), + ), + }, nil + } space, err := c.SpaceFinder.FindByRef(ctx, regInfo.ParentRef) if err != nil { return artifact.GetAllRegistries400JSONResponse{ diff --git a/registry/app/api/controller/metadata/update_artifact.go b/registry/app/api/controller/metadata/update_artifact.go index 322ab3e9a..ce220b6b2 100644 --- a/registry/app/api/controller/metadata/update_artifact.go +++ b/registry/app/api/controller/metadata/update_artifact.go @@ -94,6 +94,7 @@ func (c *APIController) UpdateArtifactLabels( return throwModifyArtifact400Error(err), nil } + // TODO: use the correct way to get download count if this endpoint is used tag, err := c.TagStore.GetLatestTagMetadata(ctx, regInfo.parentID, regInfo.RegistryIdentifier, a) if err != nil { diff --git a/registry/app/api/router/harness/route.go b/registry/app/api/router/harness/route.go index 7903d8473..fe86f698f 100644 --- a/registry/app/api/router/harness/route.go +++ b/registry/app/api/router/harness/route.go @@ -76,6 +76,7 @@ func NewAPIHandler( webhookService registrywebhook.Service, spacePathStore corestore.SpacePathStore, artifactEventReporter registryevents.Reporter, + downloadStatRepository store.DownloadStatRepository, ) APIHandler { r := chi.NewRouter() r.Use(audit.Middleware()) @@ -104,6 +105,7 @@ func NewAPIHandler( registryMetadataHelper, &webhookService, artifactEventReporter, + downloadStatRepository, ) handler := artifact.NewStrictHandler(apiController, []artifact.StrictMiddlewareFunc{}) diff --git a/registry/app/api/router/wire.go b/registry/app/api/router/wire.go index 9dfa478af..2a63d4fd3 100644 --- a/registry/app/api/router/wire.go +++ b/registry/app/api/router/wire.go @@ -74,6 +74,7 @@ func APIHandlerProvider( webhookService *registrywebhook.Service, spacePathStore corestore.SpacePathStore, artifactEventReporter *registryevents.Reporter, + downloadStatRepository store.DownloadStatRepository, ) harness.APIHandler { return harness.NewAPIHandler( repoDao, @@ -97,6 +98,7 @@ func APIHandlerProvider( *webhookService, spacePathStore, *artifactEventReporter, + downloadStatRepository, ) } diff --git a/registry/app/store/database.go b/registry/app/store/database.go index 055499e7f..cf0acaae4 100644 --- a/registry/app/store/database.go +++ b/registry/app/store/database.go @@ -486,10 +486,17 @@ type ArtifactRepository interface { *[]types.Artifact, error, ) + GetLatestByImageID(ctx context.Context, imageID int64) (*types.Artifact, error) } type DownloadStatRepository interface { Create(ctx context.Context, downloadStat *types.DownloadStat) error + GetTotalDownloadsForImage(ctx context.Context, imageID int64) (int64, error) + GetTotalDownloadsForManifests( + ctx context.Context, + artifactVersion []string, + imageID int64, + ) (map[string]int64, error) } type BandwidthStatRepository interface { diff --git a/registry/app/store/database/artifact.go b/registry/app/store/database/artifact.go index 9127d601a..0ccdd770f 100644 --- a/registry/app/store/database/artifact.go +++ b/registry/app/store/database/artifact.go @@ -110,6 +110,25 @@ func (a ArtifactDao) GetByRegistryIDAndImage(ctx context.Context, registryID int return &artifacts, nil } +func (a ArtifactDao) GetLatestByImageID(ctx context.Context, imageID int64) (*types.Artifact, error) { + q := databaseg.Builder.Select(util.ArrToStringByDelimiter(util.GetDBTagsFromStruct(artifactDB{}), ",")). + From("artifacts"). + Where("artifact_image_id = ?", imageID).OrderBy("artifact_updated_at DESC").Limit(1) + + sql, args, err := q.ToSql() + if err != nil { + return nil, errors.Wrap(err, "Failed to convert query to sql") + } + + db := dbtx.GetAccessor(ctx, a.db) + + dst := new(artifactDB) + if err = db.GetContext(ctx, dst, sql, args...); err != nil { + return nil, databaseg.ProcessSQLErrorf(ctx, err, "Failed to get artifact") + } + return a.mapToArtifact(ctx, dst) +} + func (a ArtifactDao) CreateOrUpdate(ctx context.Context, artifact *types.Artifact) error { const sqlQuery = ` INSERT INTO artifacts ( diff --git a/registry/app/store/database/download_stat.go b/registry/app/store/database/download_stat.go index 1c9fc901a..e86f4982a 100644 --- a/registry/app/store/database/download_stat.go +++ b/registry/app/store/database/download_stat.go @@ -17,16 +17,19 @@ package database import ( "context" "database/sql" - "errors" "time" "github.com/harness/gitness/app/api/request" "github.com/harness/gitness/registry/app/store" + "github.com/harness/gitness/registry/app/store/database/util" "github.com/harness/gitness/registry/types" databaseg "github.com/harness/gitness/store/database" "github.com/harness/gitness/store/database/dbtx" + sq "github.com/Masterminds/squirrel" "github.com/jmoiron/sqlx" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" ) type DownloadStatDao struct { @@ -81,8 +84,69 @@ func (d DownloadStatDao) Create(ctx context.Context, downloadStat *types.Downloa return nil } -func (d DownloadStatDao) mapToInternalDownloadStat(ctx context.Context, - in *types.DownloadStat) *downloadStatDB { +func (d DownloadStatDao) GetTotalDownloadsForImage(ctx context.Context, imageID int64) (int64, error) { + q := databaseg.Builder.Select(`count(*)`). + From("artifacts art").Where("art.artifact_image_id = ?", imageID). + Join("download_stats ds ON ds.download_stat_artifact_id = art.artifact_id") + + sql, args, err := q.ToSql() + if err != nil { + return 0, errors.Wrap(err, "Failed to convert query to sql") + } + // Log the final sql query + finalQuery := util.FormatQuery(sql, args) + log.Ctx(ctx).Debug().Str("sql", finalQuery).Msg("Executing GetTotalDownloadsForImage query") + // Execute query + db := dbtx.GetAccessor(ctx, d.db) + + var count int64 + err = db.QueryRowContext(ctx, sql, args...).Scan(&count) + if err != nil { + return 0, databaseg.ProcessSQLErrorf(ctx, err, "Failed executing count query") + } + return count, nil +} + +func (d DownloadStatDao) GetTotalDownloadsForManifests( + ctx context.Context, + artifactVersions []string, + imageID int64, +) (map[string]int64, error) { + q := databaseg.Builder.Select(`art.artifact_version, count(*)`). + From("artifacts art"). + Join("download_stats ds ON ds.download_stat_artifact_id = art.artifact_id").Where(sq.And{ + sq.Eq{"artifact_image_id": imageID}, + sq.Eq{"artifact_version": artifactVersions}, + }, "art").GroupBy("art.artifact_version") + + sql, args, err := q.ToSql() + if err != nil { + return nil, errors.Wrap(err, "Failed to convert query to sql") + } + // Log the final sql query + finalQuery := util.FormatQuery(sql, args) + log.Ctx(ctx).Debug().Str("sql", finalQuery).Msg("Executing GetTotalDownloadsForManifests query") + // Execute query + db := dbtx.GetAccessor(ctx, d.db) + + dst := []*versionsCountDB{} + if err = db.SelectContext(ctx, &dst, sql, args...); err != nil { + return nil, databaseg.ProcessSQLErrorf(ctx, err, "Failed executing query") + } + + // Convert the slice to a map + result := make(map[string]int64) + for _, v := range dst { + result[v.Version] = v.Count + } + + return result, nil +} + +func (d DownloadStatDao) mapToInternalDownloadStat( + ctx context.Context, + in *types.DownloadStat, +) *downloadStatDB { session, _ := request.AuthSessionFrom(ctx) if in.CreatedAt.IsZero() { in.CreatedAt = time.Now() @@ -104,3 +168,8 @@ func (d DownloadStatDao) mapToInternalDownloadStat(ctx context.Context, UpdatedBy: session.Principal.ID, } } + +type versionsCountDB struct { + Version string `db:"artifact_version"` + Count int64 `db:"count"` +} diff --git a/registry/app/store/database/image.go b/registry/app/store/database/image.go index 1426a138b..1360a6078 100644 --- a/registry/app/store/database/image.go +++ b/registry/app/store/database/image.go @@ -253,8 +253,10 @@ func (i ImageDao) CreateOrUpdate(ctx context.Context, image *types.Image) error return nil } -func (i ImageDao) GetLabelsByParentIDAndRepo(ctx context.Context, parentID int64, repo string, - limit int, offset int, search string) (labels []string, err error) { +func (i ImageDao) GetLabelsByParentIDAndRepo( + ctx context.Context, parentID int64, repo string, + limit int, offset int, search string, +) (labels []string, err error) { q := databaseg.Builder.Select("a.image_labels as labels"). From("images a"). Join("registries r ON r.registry_id = a.image_registry_id"). @@ -264,7 +266,8 @@ func (i ImageDao) GetLabelsByParentIDAndRepo(ctx context.Context, parentID int64 q = q.Where("a.image_labels LIKE ?", "%"+search+"%") } - q = q.OrderBy("a.image_labels ASC").Limit(uint64(limit)).Offset(uint64(offset)) + q = q.OrderBy("a.image_labels ASC"). + Limit(util.SafeIntToUInt64(limit)).Offset(util.SafeIntToUInt64(offset)) sql, args, err := q.ToSql() if err != nil { @@ -282,8 +285,10 @@ func (i ImageDao) GetLabelsByParentIDAndRepo(ctx context.Context, parentID int64 return i.mapToImageLabels(dst), nil } -func (i ImageDao) CountLabelsByParentIDAndRepo(ctx context.Context, parentID int64, repo, - search string) (count int64, err error) { +func (i ImageDao) CountLabelsByParentIDAndRepo( + ctx context.Context, parentID int64, repo, + search string, +) (count int64, err error) { q := databaseg.Builder.Select("a.image_labels as labels"). From("images a"). Join("registries r ON r.registry_id = a.image_registry_id"). @@ -309,8 +314,10 @@ func (i ImageDao) CountLabelsByParentIDAndRepo(ctx context.Context, parentID int return int64(len(dst)), nil } -func (i ImageDao) GetByRepoAndName(ctx context.Context, parentID int64, - repo string, name string) (*types.Image, error) { +func (i ImageDao) GetByRepoAndName( + ctx context.Context, parentID int64, + repo string, name string, +) (*types.Image, error) { q := databaseg.Builder.Select("a.image_id, a.image_name, "+ " a.image_registry_id, a.image_labels, a.image_created_at, "+ " a.image_updated_at, a.image_created_by, a.image_updated_by"). @@ -449,8 +456,10 @@ func (i ImageDao) mapToImageLabels(dst []*imageLabelDB) []string { return res } -func (i ImageDao) mapToImageLabel(elements map[string]bool, res []string, - dst *imageLabelDB) (map[string]bool, []string) { +func (i ImageDao) mapToImageLabel( + elements map[string]bool, res []string, + dst *imageLabelDB, +) (map[string]bool, []string) { if dst == nil { return elements, res } diff --git a/registry/app/store/database/tag.go b/registry/app/store/database/tag.go index 3714f52f6..55378e75f 100644 --- a/registry/app/store/database/tag.go +++ b/registry/app/store/database/tag.go @@ -95,6 +95,7 @@ type tagMetadataDB struct { NonConformant bool `db:"manifest_non_conformant"` Payload []byte `db:"manifest_payload"` MediaType string `db:"mt_media_type"` + Digest []byte `db:"manifest_digest"` DownloadCount int64 `db:"download_count"` } @@ -969,38 +970,16 @@ func (t tagDao) GetAllTagsByRepoAndImage( image string, sortByField string, sortByOrder string, limit int, offset int, search string, ) (*[]types.TagMetadata, error) { - // Define download count subquery - downloadCountSubquery := ` - SELECT - a.artifact_image_id, - COUNT(d.download_stat_id) AS download_count, - i.image_name, - i.image_registry_id - FROM artifacts a - JOIN download_stats d ON d.download_stat_artifact_id = a.artifact_id - JOIN images i ON i.image_id = a.artifact_image_id - GROUP BY a.artifact_image_id, i.image_name, i.image_registry_id - ` - - // Build the main query - q := databaseg.Builder. - Select(` - t.tag_name AS name, - m.manifest_total_size AS size, - r.registry_package_type AS package_type, - t.tag_updated_at AS modified_at, - m.manifest_schema_version, - m.manifest_non_conformant, - m.manifest_payload, - mt.mt_media_type, - COALESCE(dc.download_count, 0) AS download_count - `). + q := databaseg.Builder.Select( + `t.tag_name as name, m.manifest_total_size as size, + r.registry_package_type as package_type, t.tag_updated_at as modified_at, + m.manifest_schema_version, m.manifest_non_conformant, m.manifest_payload, + mt.mt_media_type, m.manifest_digest`, + ). From("tags t"). Join("registries r ON t.tag_registry_id = r.registry_id"). Join("manifests m ON t.tag_manifest_id = m.manifest_id"). Join("media_types mt ON mt.mt_id = m.manifest_media_type_id"). - LeftJoin(fmt.Sprintf("(%s) AS dc ON t.tag_image_name = dc.image_name "+ - "AND t.tag_registry_id = dc.image_registry_id", downloadCountSubquery)). Where( "r.registry_parent_id = ? AND r.registry_name = ? AND t.tag_image_name = ?", parentID, repoKey, image, @@ -1009,11 +988,7 @@ func (t tagDao) GetAllTagsByRepoAndImage( if search != "" { q = q.Where("tag_name LIKE ?", sqlPartialMatch(search)) } - sortField := "tag_" + sortByField - if sortByField == downloadCount { - sortField = downloadCount - } - q = q.OrderBy(sortField + " " + sortByOrder).Limit(uint64(limit)).Offset(uint64(offset)) //nolint:gosec + q = q.OrderBy("t.tag_" + sortByField + " " + sortByOrder).Limit(uint64(limit)).Offset(uint64(offset)) //nolint:gosec sql, args, err := q.ToSql() if err != nil { @@ -1039,7 +1014,7 @@ func (t tagDao) CountAllTagsByRepoAndImage( Join("manifests ON tag_manifest_id = manifest_id"). Where( "registry_parent_id = ? AND registry_name = ?"+ - "AND tag_image_name = ?", parentID, repoKey, image, + " AND tag_image_name = ?", parentID, repoKey, image, ) if search != "" { @@ -1222,7 +1197,7 @@ func (t tagDao) mapToTagMetadata( _ context.Context, dst *tagMetadataDB, ) (*types.TagMetadata, error) { - return &types.TagMetadata{ + tagMetadata := &types.TagMetadata{ Name: dst.Name, Size: dst.Size, PackageType: dst.PackageType, @@ -1233,7 +1208,13 @@ func (t tagDao) mapToTagMetadata( MediaType: dst.MediaType, Payload: dst.Payload, DownloadCount: dst.DownloadCount, - }, nil + } + if dst.Digest != nil { + dgst := types.Digest(util.GetHexEncodedString(dst.Digest)) + tagMetadata.Digest = string(dgst) + } + + return tagMetadata, nil } func (t tagDao) mapToTagDetail( diff --git a/registry/types/tag.go b/registry/types/tag.go index ede657565..72e5d10b5 100644 --- a/registry/types/tag.go +++ b/registry/types/tag.go @@ -45,6 +45,16 @@ type ArtifactMetadata struct { Version string } +type ImageMetadata struct { + Name string + RepoName string + DownloadCount int64 + PackageType artifact.PackageType + LatestVersion string + CreatedAt time.Time + ModifiedAt time.Time +} + type TagMetadata struct { Name string Size string @@ -55,6 +65,7 @@ type TagMetadata struct { NonConformant bool Payload Payload MediaType string + Digest string DownloadCount int64 }