fix: [AH-489] Fix Docker & Helm DownloadCount | Gitness (#3032)

* fix: [AH-489] Update Tag DB Queries
* fix: [AH-489] Fix Docker Download Count in Get Manifests
* add Download Count in Docker manifest Details
* fix: [AH-489] Fix Docker DownloadCount in Details Page
* fix: [AH-489] Fix Helm DownloadCount DB Query
pull/3597/head
Ritek Rounak 2024-12-04 03:16:17 +00:00 committed by Harness
parent 423702f0c0
commit d4987eb0cb
6 changed files with 110 additions and 47 deletions

View File

@ -114,6 +114,7 @@ func GetTagMetadata(
isLatestVersion := latestTag == tag.Name isLatestVersion := latestTag == tag.Name
command := GetPullCommand(image, tag.Name, string(tag.PackageType), registryURL) command := GetPullCommand(image, tag.Name, string(tag.PackageType), registryURL)
packageType, err := toPackageType(string(tag.PackageType)) packageType, err := toPackageType(string(tag.PackageType))
downloadCount := tag.DownloadCount
if err != nil { if err != nil {
log.Ctx(ctx).Error().Err(err).Msgf("Error converting package type %s", tag.PackageType) log.Ctx(ctx).Error().Err(err).Msgf("Error converting package type %s", tag.PackageType)
continue continue
@ -126,6 +127,7 @@ func GetTagMetadata(
DigestCount: &digestCount, DigestCount: &digestCount,
IslatestVersion: &isLatestVersion, IslatestVersion: &isLatestVersion,
PullCommand: &command, PullCommand: &command,
DownloadsCount: &downloadCount,
} }
artifactVersionMetadataList = append(artifactVersionMetadataList, *artifactVersionMetadata) artifactVersionMetadataList = append(artifactVersionMetadataList, *artifactVersionMetadata)
} }
@ -261,6 +263,7 @@ func GetDockerArtifactDetails(
PullCommand: &pullCommand, PullCommand: &pullCommand,
Url: GetTagURL(tag.ImageName, tag.Name, registryURL), Url: GetTagURL(tag.ImageName, tag.Name, registryURL),
Size: &size, Size: &size,
DownloadsCount: &tag.DownloadCount,
} }
response := &artifactapi.DockerArtifactDetailResponseJSONResponse{ response := &artifactapi.DockerArtifactDetailResponseJSONResponse{
@ -282,6 +285,7 @@ func GetHelmArtifactDetails(
createdAt := GetTimeInMs(tag.CreatedAt) createdAt := GetTimeInMs(tag.CreatedAt)
modifiedAt := GetTimeInMs(tag.UpdatedAt) modifiedAt := GetTimeInMs(tag.UpdatedAt)
size := GetSize(manifest.TotalSize) size := GetSize(manifest.TotalSize)
downloadCount := tag.DownloadCount
artifactDetail := &artifactapi.HelmArtifactDetail{ artifactDetail := &artifactapi.HelmArtifactDetail{
Artifact: &tag.ImageName, Artifact: &tag.ImageName,
Version: tag.Name, Version: tag.Name,
@ -293,6 +297,7 @@ func GetHelmArtifactDetails(
PullCommand: &pullCommand, PullCommand: &pullCommand,
Url: GetTagURL(tag.ImageName, tag.Name, registryURL), Url: GetTagURL(tag.ImageName, tag.Name, registryURL),
Size: &size, Size: &size,
DownloadsCount: &downloadCount,
} }
response := &artifactapi.HelmArtifactDetailResponseJSONResponse{ response := &artifactapi.HelmArtifactDetailResponseJSONResponse{

View File

@ -74,7 +74,11 @@ func (c *APIController) GetDockerArtifactManifests(
image := string(r.Artifact) image := string(r.Artifact)
version := string(r.Version) version := string(r.Version)
manifestDetailsList, err := c.ProcessManifest(ctx, regInfo, image, 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)
if err != nil { if err != nil {
return artifactManifestsErrorRs(err), nil return artifactManifestsErrorRs(err), nil
} }
@ -93,7 +97,7 @@ func (c *APIController) GetDockerArtifactManifests(
func (c *APIController) getManifestList( func (c *APIController) getManifestList(
ctx context.Context, reqManifest *ml.DeserializedManifestList, registry *types.Registry, image string, ctx context.Context, reqManifest *ml.DeserializedManifestList, registry *types.Registry, image string,
regInfo *RegistryRequestBaseInfo, regInfo *RegistryRequestBaseInfo, downloadCount int64,
) ([]artifact.DockerManifestDetails, error) { ) ([]artifact.DockerManifestDetails, error) {
manifestDetailsList := []artifact.DockerManifestDetails{} manifestDetailsList := []artifact.DockerManifestDetails{}
for _, manifestEntry := range reqManifest.Manifests { for _, manifestEntry := range reqManifest.Manifests {
@ -118,7 +122,7 @@ func (c *APIController) getManifestList(
if err != nil { if err != nil {
return nil, err return nil, err
} }
manifestDetailsList = append(manifestDetailsList, getManifestDetails(referencedManifest, mConfig)) manifestDetailsList = append(manifestDetailsList, getManifestDetails(referencedManifest, mConfig, downloadCount))
} }
return manifestDetailsList, nil return manifestDetailsList, nil
} }
@ -131,14 +135,16 @@ func artifactManifestsErrorRs(err error) artifact.GetDockerArtifactManifestsResp
} }
} }
func getManifestDetails(m *types.Manifest, mConfig *manifestConfig) artifact.DockerManifestDetails { func getManifestDetails(
m *types.Manifest, mConfig *manifestConfig, downloadsCount int64) artifact.DockerManifestDetails {
createdAt := GetTimeInMs(m.CreatedAt) createdAt := GetTimeInMs(m.CreatedAt)
size := GetSize(m.TotalSize) size := GetSize(m.TotalSize)
manifestDetails := artifact.DockerManifestDetails{ manifestDetails := artifact.DockerManifestDetails{
Digest: m.Digest.String(), Digest: m.Digest.String(),
CreatedAt: &createdAt, CreatedAt: &createdAt,
Size: &size, Size: &size,
DownloadsCount: &downloadsCount,
} }
if mConfig != nil { if mConfig != nil {
manifestDetails.OsArch = fmt.Sprintf("%s/%s", mConfig.Os, mConfig.Arch) manifestDetails.OsArch = fmt.Sprintf("%s/%s", mConfig.Os, mConfig.Arch)
@ -153,7 +159,7 @@ func getManifestDetails(m *types.Manifest, mConfig *manifestConfig) artifact.Doc
func (c *APIController) ProcessManifest( func (c *APIController) ProcessManifest(
ctx context.Context, ctx context.Context,
regInfo *RegistryRequestBaseInfo, regInfo *RegistryRequestBaseInfo,
image, version string, image, version string, downloadCount int64,
) ([]artifact.DockerManifestDetails, error) { ) ([]artifact.DockerManifestDetails, error) {
registry, err := c.RegistryRepository.GetByParentIDAndName(ctx, regInfo.parentID, regInfo.RegistryIdentifier) registry, err := c.RegistryRepository.GetByParentIDAndName(ctx, regInfo.parentID, regInfo.RegistryIdentifier)
if err != nil { if err != nil {
@ -178,15 +184,15 @@ func (c *APIController) ProcessManifest(
if err != nil { if err != nil {
return nil, err return nil, err
} }
manifestDetailsList = append(manifestDetailsList, getManifestDetails(m, mConfig)) manifestDetailsList = append(manifestDetailsList, getManifestDetails(m, mConfig, downloadCount))
case *ocischema.DeserializedManifest: case *ocischema.DeserializedManifest:
mConfig, err := getManifestConfig(ctx, reqManifest.Config().Digest, regInfo.RootIdentifier, c.StorageDriver) mConfig, err := getManifestConfig(ctx, reqManifest.Config().Digest, regInfo.RootIdentifier, c.StorageDriver)
if err != nil { if err != nil {
return nil, err return nil, err
} }
manifestDetailsList = append(manifestDetailsList, getManifestDetails(m, mConfig)) manifestDetailsList = append(manifestDetailsList, getManifestDetails(m, mConfig, downloadCount))
case *ml.DeserializedManifestList: case *ml.DeserializedManifestList:
manifestDetailsList, err = c.getManifestList(ctx, reqManifest, registry, image, regInfo) manifestDetailsList, err = c.getManifestList(ctx, reqManifest, registry, image, regInfo, downloadCount)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1418,6 +1418,9 @@ components:
type: string type: string
createdAt: createdAt:
type: string type: string
downloadsCount:
type: integer
format: int64
required: required:
- digest - digest
- layers - layers

View File

@ -210,10 +210,11 @@ type DockerLayersSummary struct {
// DockerManifestDetails Harness Artifact Layers // DockerManifestDetails Harness Artifact Layers
type DockerManifestDetails struct { type DockerManifestDetails struct {
CreatedAt *string `json:"createdAt,omitempty"` CreatedAt *string `json:"createdAt,omitempty"`
Digest string `json:"digest"` Digest string `json:"digest"`
OsArch string `json:"osArch"` DownloadsCount *int64 `json:"downloadsCount,omitempty"`
Size *string `json:"size,omitempty"` OsArch string `json:"osArch"`
Size *string `json:"size,omitempty"`
} }
// DockerManifests Harness Manifests // DockerManifests Harness Manifests

View File

@ -91,15 +91,17 @@ type tagMetadataDB struct {
NonConformant bool `db:"manifest_non_conformant"` NonConformant bool `db:"manifest_non_conformant"`
Payload []byte `db:"manifest_payload"` Payload []byte `db:"manifest_payload"`
MediaType string `db:"mt_media_type"` MediaType string `db:"mt_media_type"`
DownloadCount int64 `db:"download_count"`
} }
type tagDetailDB struct { type tagDetailDB struct {
ID int64 `db:"id"` ID int64 `db:"id"`
Name string `db:"name"` Name string `db:"name"`
ImageName string `db:"image_name"` ImageName string `db:"image_name"`
CreatedAt int64 `db:"created_at"` CreatedAt int64 `db:"created_at"`
UpdatedAt int64 `db:"updated_at"` UpdatedAt int64 `db:"updated_at"`
Size string `db:"size"` Size string `db:"size"`
DownloadCount int64 `db:"download_count"`
} }
func (t tagDao) CreateOrUpdate(ctx context.Context, tag *types.Tag) error { func (t tagDao) CreateOrUpdate(ctx context.Context, tag *types.Tag) error {
@ -466,15 +468,35 @@ func (t tagDao) GetTagDetail(
ctx context.Context, repoID int64, imageName string, ctx context.Context, repoID int64, imageName string,
name string, name string,
) (*types.TagDetail, error) { ) (*types.TagDetail, error) {
q := databaseg.Builder.Select( // Define subquery for download counts
`tag_id as id, tag_name as name, downloadCountSubquery := `
tag_image_name as image_name, tag_created_at as created_at, SELECT
tag_updated_at as updated_at, manifest_total_size as size`, a.artifact_image_id,
). COUNT(d.download_stat_id) AS download_count,
From("tags"). i.image_name,
Join("manifests ON manifest_id = tag_manifest_id"). 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 main query
q := databaseg.Builder.
Select(`
t.tag_id AS id,
t.tag_name AS name,
t.tag_image_name AS image_name,
t.tag_created_at AS created_at,
t.tag_updated_at AS updated_at,
m.manifest_total_size AS size,
COALESCE(dc.download_count, 0) AS download_count
`).
From("tags AS t").
Join("manifests AS m ON m.manifest_id = t.tag_manifest_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( Where(
"tag_registry_id = ? AND tag_image_name = ? AND tag_name = ?", "t.tag_registry_id = ? AND t.tag_image_name = ? AND t.tag_name = ?",
repoID, imageName, name, repoID, imageName, name,
) )
@ -765,16 +787,38 @@ func (t tagDao) GetAllTagsByRepoAndImage(
image string, sortByField string, sortByOrder string, limit int, offset int, image string, sortByField string, sortByOrder string, limit int, offset int,
search string, search string,
) (*[]types.TagMetadata, error) { ) (*[]types.TagMetadata, error) {
q := databaseg.Builder.Select( // Define download count subquery
`t.tag_name as name, m.manifest_total_size as size, downloadCountSubquery := `
r.registry_package_type as package_type, t.tag_updated_at as modified_at, SELECT
m.manifest_schema_version, m.manifest_non_conformant, m.manifest_payload, a.artifact_image_id,
mt.mt_media_type `, 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
`).
From("tags t"). From("tags t").
Join("registries r ON t.tag_registry_id = r.registry_id"). Join("registries r ON t.tag_registry_id = r.registry_id").
Join("manifests m ON t.tag_manifest_id = m.manifest_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"). 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( Where(
"r.registry_parent_id = ? AND r.registry_name = ? AND t.tag_image_name = ?", "r.registry_parent_id = ? AND r.registry_name = ? AND t.tag_image_name = ?",
parentID, repoKey, image, parentID, repoKey, image,
@ -977,6 +1021,7 @@ func (t tagDao) mapToTagMetadata(
NonConformant: dst.NonConformant, NonConformant: dst.NonConformant,
MediaType: dst.MediaType, MediaType: dst.MediaType,
Payload: dst.Payload, Payload: dst.Payload,
DownloadCount: dst.DownloadCount,
}, nil }, nil
} }
@ -985,11 +1030,12 @@ func (t tagDao) mapToTagDetail(
dst *tagDetailDB, dst *tagDetailDB,
) (*types.TagDetail, error) { ) (*types.TagDetail, error) {
return &types.TagDetail{ return &types.TagDetail{
ID: dst.ID, ID: dst.ID,
Name: dst.Name, Name: dst.Name,
ImageName: dst.ImageName, ImageName: dst.ImageName,
Size: dst.Size, Size: dst.Size,
CreatedAt: time.UnixMilli(dst.CreatedAt), CreatedAt: time.UnixMilli(dst.CreatedAt),
UpdatedAt: time.UnixMilli(dst.UpdatedAt), UpdatedAt: time.UnixMilli(dst.UpdatedAt),
DownloadCount: dst.DownloadCount,
}, nil }, nil
} }

View File

@ -56,13 +56,15 @@ type TagMetadata struct {
NonConformant bool NonConformant bool
Payload Payload Payload Payload
MediaType string MediaType string
DownloadCount int64
} }
type TagDetail struct { type TagDetail struct {
ID int64 ID int64
Name string Name string
ImageName string ImageName string
CreatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time UpdatedAt time.Time
Size string Size string
DownloadCount int64
} }