mirror of
https://github.com/harness/drone.git
synced 2025-05-05 15:32:56 +00:00
* 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
843 lines
26 KiB
Go
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"`
|
|
}
|