Tudor Macari a3d828f0a2 feat: [AH-1060]: RPM flows skeleton, RPM package upload (#3599)
* resolve PR comments
* fix lint
* resolve PR comments
* fix lint issue
* resolve PR comments
* resolve PR comments
* fix lint issue
* feat: [AH-1060]: RPM flows skeleton, RPM package upload/install
2025-04-16 06:05:50 +00:00

843 lines
26 KiB
Go

// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package database
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"sort"
"time"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
"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 ArtifactDao struct {
db *sqlx.DB
}
func NewArtifactDao(db *sqlx.DB) store.ArtifactRepository {
return &ArtifactDao{
db: db,
}
}
type artifactDB struct {
ID int64 `db:"artifact_id"`
Version string `db:"artifact_version"`
ImageID int64 `db:"artifact_image_id"`
Metadata *json.RawMessage `db:"artifact_metadata"`
CreatedAt int64 `db:"artifact_created_at"`
UpdatedAt int64 `db:"artifact_updated_at"`
CreatedBy int64 `db:"artifact_created_by"`
UpdatedBy int64 `db:"artifact_updated_by"`
}
func (a ArtifactDao) GetByName(ctx context.Context, imageID int64, version string) (*types.Artifact, error) {
q := databaseg.Builder.Select(util.ArrToStringByDelimiter(util.GetDBTagsFromStruct(artifactDB{}), ",")).
From("artifacts").
Where("artifact_image_id = ? AND artifact_version = ?", imageID, version)
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) GetByRegistryIDAndImage(ctx context.Context, registryID int64, image string) (
*[]types.Artifact,
error,
) {
q := databaseg.Builder.Select(util.ArrToStringByDelimiter(util.GetDBTagsFromStruct(artifactDB{}), ",")).
From("artifacts a").
Join("images i ON a.artifact_image_id = i.image_id").
Where("i.image_registry_id = ? AND i.image_name = ?", registryID, image).
OrderBy("a.artifact_created_at DESC")
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 := []artifactDB{}
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, databaseg.ProcessSQLErrorf(ctx, err, "Failed to get artifacts")
}
artifacts := make([]types.Artifact, len(dst))
for i := range dst {
d := dst[i]
art, err := a.mapToArtifact(ctx, &d)
if err != nil {
return nil, errors.Wrap(err, "Failed to map artifact")
}
artifacts[i] = *art
}
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 (
artifact_image_id
,artifact_version
,artifact_created_at
,artifact_metadata
,artifact_updated_at
,artifact_created_by
,artifact_updated_by
) VALUES (
:artifact_image_id
,:artifact_version
,:artifact_created_at
,:artifact_metadata
,:artifact_updated_at
,:artifact_created_by
,:artifact_updated_by
)
ON CONFLICT (artifact_image_id, artifact_version)
DO UPDATE SET artifact_metadata = :artifact_metadata
RETURNING artifact_id`
db := dbtx.GetAccessor(ctx, a.db)
query, arg, err := db.BindNamed(sqlQuery, a.mapToInternalArtifact(ctx, artifact))
if err != nil {
return databaseg.ProcessSQLErrorf(ctx, err, "Failed to bind artifact object")
}
if err = db.QueryRowContext(ctx, query, arg...).Scan(&artifact.ID); err != nil && !errors.Is(err, sql.ErrNoRows) {
return databaseg.ProcessSQLErrorf(ctx, err, "Insert query failed")
}
return nil
}
func (a ArtifactDao) Count(ctx context.Context) (int64, error) {
stmt := databaseg.Builder.Select("COUNT(*)").
From("artifacts")
sql, args, err := stmt.ToSql()
if err != nil {
return 0, errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, a.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 (a ArtifactDao) DeleteByImageNameAndRegistryID(ctx context.Context, regID int64, image string) (err error) {
delStmt := databaseg.Builder.Delete("artifacts").
Where("artifact_id IN (SELECT a.artifact_id FROM artifacts a JOIN images i ON i.image_id = a.artifact_image_id"+
" WHERE i.image_name = ? AND i.image_registry_id = ?)", image, regID)
db := dbtx.GetAccessor(ctx, a.db)
delQuery, delArgs, err := delStmt.ToSql()
if err != nil {
return fmt.Errorf("failed to convert delete query to sql: %w", err)
}
_, err = db.ExecContext(ctx, delQuery, delArgs...)
if err != nil {
return databaseg.ProcessSQLErrorf(ctx, err, "the delete query failed")
}
return nil
}
func (a ArtifactDao) DeleteByVersionAndImageName(
ctx context.Context, image string,
version string, regID int64,
) (err error) {
delStmt := databaseg.Builder.Delete("artifacts").
Where("artifact_id IN (SELECT a.artifact_id FROM artifacts a JOIN images i ON i.image_id = a.artifact_image_id"+
" WHERE a.artifact_name = ? AND i.image_name = ? AND i.image_registry_id = ?)", version, image, regID)
sql, args, err := delStmt.ToSql()
if err != nil {
return errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, a.db)
_, err = db.ExecContext(ctx, sql, args...)
if err != nil {
return databaseg.ProcessSQLErrorf(ctx, err, "the delete query failed")
}
return nil
}
func (a ArtifactDao) mapToInternalArtifact(ctx context.Context, in *types.Artifact) *artifactDB {
session, _ := request.AuthSessionFrom(ctx)
if in.CreatedAt.IsZero() {
in.CreatedAt = time.Now()
}
if in.CreatedBy == 0 {
in.CreatedBy = session.Principal.ID
}
var metadata = json.RawMessage("null")
if in.Metadata != nil {
metadata = in.Metadata
}
in.UpdatedAt = time.Now()
in.UpdatedBy = session.Principal.ID
return &artifactDB{
ID: in.ID,
Version: in.Version,
ImageID: in.ImageID,
Metadata: &metadata,
CreatedAt: in.CreatedAt.UnixMilli(),
UpdatedAt: in.UpdatedAt.UnixMilli(),
CreatedBy: in.CreatedBy,
UpdatedBy: in.UpdatedBy,
}
}
func (a ArtifactDao) mapToArtifact(_ context.Context, dst *artifactDB) (*types.Artifact, error) {
createdBy := dst.CreatedBy
updatedBy := dst.UpdatedBy
var metadata json.RawMessage
if dst.Metadata != nil {
metadata = *dst.Metadata
}
return &types.Artifact{
ID: dst.ID,
Version: dst.Version,
ImageID: dst.ImageID,
Metadata: metadata,
CreatedAt: time.UnixMilli(dst.CreatedAt),
UpdatedAt: time.UnixMilli(dst.UpdatedAt),
CreatedBy: createdBy,
UpdatedBy: updatedBy,
}, nil
}
func (a ArtifactDao) GetAllArtifactsByParentID(
ctx context.Context,
parentID int64,
registryIDs *[]string,
sortByField string,
sortByOrder string,
limit int,
offset int,
search string,
latestVersion bool,
packageTypes []string,
) (*[]types.ArtifactMetadata, error) {
q := databaseg.Builder.Select(
`r.registry_name as repo_name,
i.image_name as name,
r.registry_package_type as package_type,
a.artifact_version as version,
a.artifact_updated_at as modified_at,
i.image_labels as labels,
COALESCE(t2.download_count,0) as download_count `,
).
From("artifacts a").
Join("images i ON a.artifact_image_id = i.image_id").
Join("registries r ON r.registry_id = i.image_registry_id").
Where("r.registry_parent_id = ?", parentID).
LeftJoin(
`( SELECT i.image_name, SUM(COALESCE(t1.download_count, 0)) as download_count FROM
( SELECT a.artifact_image_id, COUNT(d.download_stat_id) as download_count
FROM artifacts a JOIN download_stats d ON d.download_stat_artifact_id = a.artifact_id
GROUP BY a.artifact_image_id ) as t1
JOIN images i ON i.image_id = t1.artifact_image_id
JOIN registries r ON r.registry_id = i.image_registry_id
WHERE r.registry_parent_id = ? GROUP BY i.image_name) as t2
ON i.image_name = t2.image_name`, parentID,
)
if latestVersion {
q = q.Join(
`(SELECT t.artifact_id as id, ROW_NUMBER() OVER (PARTITION BY t.artifact_image_id
ORDER BY t.artifact_updated_at DESC) AS rank FROM artifacts t
JOIN images i ON t.artifact_image_id = i.image_id
JOIN registries r ON i.image_registry_id = r.registry_id
WHERE r.registry_parent_id = ? ) AS a1
ON a.artifact_id = a1.id`, parentID, // nolint:goconst
).
Where("a1.rank = 1")
}
if len(*registryIDs) > 0 {
q = q.Where(sq.Eq{"r.registry_name": registryIDs})
}
if len(packageTypes) > 0 {
q = q.Where(sq.Eq{"r.registry_package_type": packageTypes})
}
if search != "" {
q = q.Where("image_name LIKE ?", sqlPartialMatch(search))
}
sortField := "i." + sortByField
if sortByField == downloadCount {
sortField = downloadCount
}
q = q.OrderBy(sortField + " " + sortByOrder).Limit(util.SafeIntToUInt64(limit)).Offset(util.SafeIntToUInt64(offset))
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 := []*artifactMetadataDB{}
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, databaseg.ProcessSQLErrorf(ctx, err, "Failed executing custom list query")
}
return a.mapToArtifactMetadataList(ctx, dst)
}
func (a ArtifactDao) CountAllArtifactsByParentID(
ctx context.Context, parentID int64,
registryIDs *[]string, search string, latestVersion bool, packageTypes []string,
) (int64, error) {
// nolint:goconst
q := databaseg.Builder.Select("COUNT(*)").
From("artifacts a").
Join("images i ON i.image_id = a.artifact_image_id").
Join("registries r ON i.image_registry_id = r.registry_id"). // nolint:goconst
Where("r.registry_parent_id = ?", parentID)
if latestVersion {
q = q.Join(
`(SELECT t.artifact_id as id, ROW_NUMBER() OVER (PARTITION BY t.artifact_image_id
ORDER BY t.artifact_updated_at DESC) AS rank FROM artifacts t
JOIN images i ON t.artifact_image_id = i.image_id
JOIN registries r ON i.image_registry_id = r.registry_id
WHERE r.registry_parent_id = ? ) AS a1
ON a.artifact_id = a1.id`, parentID, // nolint:goconst
).
Where("a1.rank = 1")
}
if len(*registryIDs) > 0 {
q = q.Where(sq.Eq{"r.registry_name": registryIDs})
}
if search != "" {
q = q.Where("image_name LIKE ?", sqlPartialMatch(search))
}
if len(packageTypes) > 0 {
q = q.Where(sq.Eq{"registry_package_type": packageTypes})
}
sql, args, err := q.ToSql()
if err != nil {
return -1, errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, a.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 (a ArtifactDao) GetArtifactsByRepo(
ctx context.Context, registryParentID int64, repoKey string,
sortByField string, sortByOrder string, limit int, offset int, search string,
labels []string,
) (*[]types.ArtifactMetadata, error) {
q := databaseg.Builder.Select(
`r.registry_name as repo_name, i.image_name as name,
r.registry_package_type as package_type, a.artifact_version as latest_version,
a.artifact_updated_at as modified_at, i.image_labels as labels,
COALESCE(t2.download_count, 0) as download_count `,
).
From("artifacts a").
Join(
`(SELECT a.artifact_id as id, ROW_NUMBER() OVER (PARTITION BY a.artifact_image_id
ORDER BY a.artifact_updated_at DESC) AS rank FROM artifacts a
JOIN images i ON i.image_id = a.artifact_image_id
JOIN registries r ON i.image_registry_id = r.registry_id
WHERE r.registry_parent_id = ? AND r.registry_name = ? ) AS a1
ON a.artifact_id = a1.id`, registryParentID, repoKey, // nolint:goconst
).
Join("images i ON i.image_id = a.artifact_image_id").
Join("registries r ON i.image_registry_id = r.registry_id").
LeftJoin(
`( SELECT i.image_name, SUM(COALESCE(t1.download_count, 0)) as download_count FROM
( SELECT a.artifact_image_id, COUNT(d.download_stat_id) as download_count
FROM artifacts a
JOIN download_stats d ON d.download_stat_artifact_id = a.artifact_id GROUP BY
a.artifact_image_id ) as t1
JOIN images i ON i.image_id = t1.artifact_image_id
JOIN registries r ON r.registry_id = i.image_registry_id
WHERE r.registry_parent_id = ? AND r.registry_name = ? GROUP BY i.image_name) as t2
ON i.image_name = t2.image_name`, registryParentID, repoKey,
).
Where("a1.rank = 1 ")
if search != "" {
q = q.Where("i.image_name LIKE ?", sqlPartialMatch(search))
}
if len(labels) > 0 {
sort.Strings(labels)
labelsVal := util.GetEmptySQLString(util.ArrToString(labels))
labelsVal.String = labelSeparatorStart + labelsVal.String + labelSeparatorEnd
q = q.Where("'^_' || i.image_labels || '^_' LIKE ?", labelsVal)
}
// nolint:goconst
sortField := "image_" + sortByField
if sortByField == downloadCount {
sortField = downloadCount
} else if sortByField == imageName {
sortField = name
}
q = q.OrderBy(sortField + " " + sortByOrder).Limit(util.SafeIntToUInt64(limit)).Offset(util.SafeIntToUInt64(offset))
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 := []*artifactMetadataDB{}
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, databaseg.ProcessSQLErrorf(ctx, err, "Failed executing custom list query")
}
return a.mapToArtifactMetadataList(ctx, dst)
}
// nolint:goconst
func (a ArtifactDao) CountArtifactsByRepo(
ctx context.Context, parentID int64, repoKey string,
search string, labels []string,
) (int64, error) {
q := databaseg.Builder.Select("COUNT(*)").
From("artifacts a").
Join(
"images i ON i.image_id = a.artifact_image_id").
Join("registries r ON i.image_registry_id = r.registry_id").
Where("r.registry_parent_id = ? AND r.registry_name = ?", parentID, repoKey)
if search != "" {
q = q.Where("i.image_name LIKE ?", sqlPartialMatch(search))
}
if len(labels) > 0 {
sort.Strings(labels)
labelsVal := util.GetEmptySQLString(util.ArrToString(labels))
labelsVal.String = labelSeparatorStart + labelsVal.String + labelSeparatorEnd
q = q.Where("'^_' || i.image_labels || '^_' LIKE ?", labelsVal)
}
sql, args, err := q.ToSql()
if err != nil {
return -1, errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, a.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 (a ArtifactDao) GetLatestArtifactMetadata(
ctx context.Context,
parentID int64,
repoKey string,
imageName string,
) (*types.ArtifactMetadata, error) {
// Precomputed download count subquery
downloadCountSubquery := `
SELECT
i.image_name,
i.image_registry_id,
SUM(COALESCE(dc.download_count, 0)) AS download_count
FROM
images i
LEFT JOIN (
SELECT
a.artifact_image_id,
COUNT(d.download_stat_id) AS download_count
FROM
artifacts a
JOIN
download_stats d ON d.download_stat_artifact_id = a.artifact_id
GROUP BY
a.artifact_image_id
) AS dc ON i.image_id = dc.artifact_image_id
GROUP BY
i.image_name, i.image_registry_id
`
var q sq.SelectBuilder
if a.db.DriverName() == SQLITE3 {
q = databaseg.Builder.Select(
`r.registry_name AS repo_name, r.registry_package_type AS package_type,
i.image_name AS name, a.artifact_version AS latest_version,
a.artifact_created_at AS created_at, a.artifact_updated_at AS modified_at,
i.image_labels AS labels, COALESCE(dc_subquery.download_count, 0) AS download_count`,
).
From("artifacts a").
Join("images i ON i.image_id = a.artifact_image_id").
Join("registries r ON i.image_registry_id = r.registry_id"). // nolint:goconst
LeftJoin(fmt.Sprintf("(%s) AS dc_subquery ON dc_subquery.image_name = i.image_name "+
"AND dc_subquery.image_registry_id = r.registry_id", downloadCountSubquery)).
Where(
"r.registry_parent_id = ? AND r.registry_name = ? AND i.image_name = ?",
parentID, repoKey, imageName,
).
OrderBy("a.artifact_updated_at DESC").Limit(1)
} else {
q = databaseg.Builder.Select(
`r.registry_name AS repo_name,
r.registry_package_type AS package_type,
i.image_name AS name,
a.artifact_version AS latest_version,
a.artifact_created_at AS created_at,
a.artifact_updated_at AS modified_at,
i.image_labels AS labels,
COALESCE(t2.download_count, 0) AS download_count`,
).
From("artifacts a").
Join("images i ON i.image_id = a.artifact_image_id").
Join("registries r ON i.image_registry_id = r.registry_id"). // nolint:goconst
LeftJoin(fmt.Sprintf("LATERAL (%s) AS t2 ON i.image_name = t2.image_name", downloadCountSubquery)).
Where(
"r.registry_parent_id = ? AND r.registry_name = ? AND i.image_name = ?",
parentID, repoKey, imageName,
).
OrderBy("a.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")
}
// Log the final sql query
finalQuery := util.FormatQuery(sql, args)
log.Ctx(ctx).Debug().Str("sql", finalQuery).Msg("Executing GetLatestTagMetadata query")
// Execute query
db := dbtx.GetAccessor(ctx, a.db)
dst := new(artifactMetadataDB)
if err = db.GetContext(ctx, dst, sql, args...); err != nil {
return nil, databaseg.ProcessSQLErrorf(ctx, err, "Failed to get tag detail")
}
return a.mapToArtifactMetadata(ctx, dst)
}
func (a ArtifactDao) mapToArtifactMetadataList(
ctx context.Context,
dst []*artifactMetadataDB,
) (*[]types.ArtifactMetadata, error) {
artifacts := make([]types.ArtifactMetadata, 0, len(dst))
for _, d := range dst {
artifact, err := a.mapToArtifactMetadata(ctx, d)
if err != nil {
return nil, err
}
artifacts = append(artifacts, *artifact)
}
return &artifacts, nil
}
func (a ArtifactDao) GetAllVersionsByRepoAndImage(
ctx context.Context, parentID int64, repoKey string,
image string, sortByField string, sortByOrder string, limit int, offset int,
search string,
) (*[]types.NonOCIArtifactMetadata, 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(`
a.artifact_version AS name,
a.artifact_metadata ->> 'size' AS size,
a.artifact_metadata ->> 'file_count' AS file_count,
r.registry_package_type AS package_type,
a.artifact_updated_at AS modified_at,
COALESCE(dc.download_count, 0) AS download_count
`)
if a.db.DriverName() == SQLITE3 {
q = databaseg.Builder.Select(`
a.artifact_version AS name,
json_extract(a.artifact_metadata, '$.size') AS size,
json_extract(a.artifact_metadata, '$.file_count') AS file_count,
r.registry_package_type AS package_type,
a.artifact_updated_at AS modified_at,
COALESCE(dc.download_count, 0) AS download_count
`)
}
q = q.From("artifacts a").
Join("images i ON i.image_id = a.artifact_image_id").
Join("registries r ON i.image_registry_id = r.registry_id").
LeftJoin(fmt.Sprintf(
"(%s) AS dc ON a.artifact_image_id = dc.artifact_image_id",
downloadCountSubquery,
)).
Where(
"r.registry_parent_id = ? AND r.registry_name = ? AND i.image_name = ?",
parentID, repoKey, image,
)
if search != "" {
q = q.Where("artifact_version LIKE ?", sqlPartialMatch(search))
}
// nolint:goconst
sortField := "image_" + sortByField
if sortByField == downloadCount {
sortField = downloadCount
} else if sortByField == name {
sortField = name
}
q = q.OrderBy(sortField + " " + sortByOrder).Limit(util.SafeIntToUInt64(limit)).Offset(util.SafeIntToUInt64(offset))
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 := []*nonOCIArtifactMetadataDB{}
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, databaseg.ProcessSQLErrorf(ctx, err, "Failed executing custom list query")
}
return a.mapToNonOCIMetadataList(dst)
}
func (a ArtifactDao) CountAllVersionsByRepoAndImage(
ctx context.Context, parentID int64,
repoKey string, image string, search string,
) (int64, error) {
stmt := databaseg.Builder.Select("COUNT(*)").
From("artifacts a").
Join("images i ON i.image_id = a.artifact_image_id").
Join("registries r ON i.image_registry_id = r.registry_id").
Where(
"r.registry_parent_id = ? AND r.registry_name = ? "+
"AND i.image_name = ?", parentID, repoKey, image,
)
if search != "" {
stmt = stmt.Where("artifact_version LIKE ?", sqlPartialMatch(search))
}
sql, args, err := stmt.ToSql()
if err != nil {
return -1, errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, a.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 (a ArtifactDao) GetArtifactMetadata(
ctx context.Context,
parentID int64,
repoKey string,
imageName string,
name string,
) (*types.ArtifactMetadata, error) {
q := databaseg.Builder.Select(
"r.registry_package_type as package_type, a.artifact_version as name,"+
"a.artifact_updated_at as modified_at").
From("artifacts a").
Join("images i ON i.image_id = a.artifact_image_id").
Join("registries r ON i.image_registry_id = registry_id").
Where(
"r.registry_parent_id = ? AND r.registry_name = ?"+
" AND i.image_name = ? AND a.artifact_version = ?", parentID, repoKey, imageName, name,
)
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(artifactMetadataDB)
if err = db.GetContext(ctx, dst, sql, args...); err != nil {
return nil, databaseg.ProcessSQLErrorf(ctx, err, "Failed to get artifact metadata")
}
return a.mapToArtifactMetadata(ctx, dst)
}
func (a ArtifactDao) GetAllArtifactsByRepo(
ctx context.Context, registryID int64, batchSize int, artifactID int64,
) (*[]types.ArtifactMetadata, error) {
q := databaseg.Builder.Select(
`r.registry_name as repo_name, i.image_name as name,
a.artifact_id as artifact_id, a.artifact_version as version, a.artifact_metadata as metadata`,
).
From("artifacts a").
Join("images i ON i.image_id = a.artifact_image_id").
Join("registries r ON i.image_registry_id = r.registry_id").
Where("artifact_id > ? AND r.registry_id = ?", artifactID, registryID).
OrderBy("artifact_id ASC").
Limit(util.SafeIntToUInt64(batchSize))
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)
var dst []*artifactMetadataDB
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil, databaseg.ProcessSQLErrorf(ctx, err, "Failed executing GetAllArtifactsByRepo query")
}
return a.mapToArtifactMetadataList(ctx, dst)
}
func (a ArtifactDao) mapToArtifactMetadata(
_ context.Context,
dst *artifactMetadataDB,
) (*types.ArtifactMetadata, error) {
return &types.ArtifactMetadata{
ID: dst.ID,
Name: dst.Name,
RepoName: dst.RepoName,
DownloadCount: dst.DownloadCount,
PackageType: dst.PackageType,
LatestVersion: dst.LatestVersion,
Labels: util.StringToArr(dst.Labels.String),
CreatedAt: time.UnixMilli(dst.CreatedAt),
ModifiedAt: time.UnixMilli(dst.ModifiedAt),
Version: dst.Version,
Metadata: *dst.Metadata,
}, nil
}
func (a ArtifactDao) mapToNonOCIMetadata(
dst *nonOCIArtifactMetadataDB,
) *types.NonOCIArtifactMetadata {
var size string
var fileCount int64
if dst.Size != nil {
size = *dst.Size
}
if dst.FileCount != nil {
fileCount = *dst.FileCount
}
return &types.NonOCIArtifactMetadata{
Name: dst.Name,
DownloadCount: dst.DownloadCount,
PackageType: dst.PackageType,
Size: size,
FileCount: fileCount,
ModifiedAt: time.UnixMilli(dst.ModifiedAt),
}
}
func (a ArtifactDao) mapToNonOCIMetadataList(
dst []*nonOCIArtifactMetadataDB,
) (*[]types.NonOCIArtifactMetadata, error) {
metadataList := make([]types.NonOCIArtifactMetadata, 0, len(dst))
for _, d := range dst {
metadata := a.mapToNonOCIMetadata(d)
metadataList = append(metadataList, *metadata)
}
return &metadataList, nil
}
type nonOCIArtifactMetadataDB struct {
Name string `db:"name"`
Size *string `db:"size"`
PackageType artifact.PackageType `db:"package_type"`
FileCount *int64 `db:"file_count"`
ModifiedAt int64 `db:"modified_at"`
DownloadCount int64 `db:"download_count"`
}