drone/registry/app/store/database/registry_blobs.go

146 lines
4.1 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"
"errors"
"fmt"
"time"
"github.com/harness/gitness/app/api/request"
"github.com/harness/gitness/registry/app/store"
"github.com/harness/gitness/registry/types"
databaseg "github.com/harness/gitness/store/database"
"github.com/harness/gitness/store/database/dbtx"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
)
type registryBlobDao struct {
db *sqlx.DB
}
func NewRegistryBlobDao(db *sqlx.DB) store.RegistryBlobRepository {
return &registryBlobDao{
db: db,
}
}
// registryBlobDB holds the record of a registry_blobs in DB.
type registryBlobDB struct {
ID int64 `db:"rblob_id"`
RegistryID int64 `db:"rblob_registry_id"`
BlobID int64 `db:"rblob_blob_id"`
ImageName string `db:"rblob_image_name"`
CreatedAt int64 `db:"rblob_created_at"`
UpdatedAt int64 `db:"rblob_updated_at"`
CreatedBy int64 `db:"rblob_created_by"`
UpdatedBy int64 `db:"rblob_updated_by"`
}
func (r registryBlobDao) LinkBlob(
ctx context.Context, imageName string,
registry *types.Registry, blobID int64,
) error {
sqlQuery := `
INSERT INTO registry_blobs (
rblob_blob_id,
rblob_registry_id,
rblob_image_name,
rblob_created_at,
rblob_updated_at,
rblob_created_by,
rblob_updated_by
) VALUES (
:rblob_blob_id,
:rblob_registry_id,
:rblob_image_name,
:rblob_created_at,
:rblob_updated_at,
:rblob_created_by,
:rblob_updated_by
) ON CONFLICT (
rblob_registry_id, rblob_blob_id, rblob_image_name
) DO NOTHING
RETURNING rblob_registry_id`
rblob := mapToInternalRegistryBlob(ctx, registry.ID, blobID, imageName)
db := dbtx.GetAccessor(ctx, r.db)
query, arg, err := db.BindNamed(sqlQuery, rblob)
if err != nil {
return databaseg.ProcessSQLErrorf(ctx, err, "Failed to bind repo object")
}
var registryBlobID int64
if err = db.QueryRowContext(ctx, query, arg...).Scan(&registryBlobID); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil
}
return databaseg.ProcessSQLErrorf(ctx, err, "Insert query failed")
}
log.Ctx(ctx).Info().Msgf("Linking blob to registry %d with id: %d", registry.ID, registryBlobID)
return nil
}
// UnlinkBlob unlinks a blob from a repository. It does nothing if not linked. A boolean is returned to denote whether
// the link was deleted or not. This avoids the need for a separate preceding `SELECT` to find if it exists.
func (r registryBlobDao) UnlinkBlob(
ctx context.Context, imageName string,
registry *types.Registry, blobID int64,
) (bool, error) {
stmt := databaseg.Builder.Delete("registry_blobs").
Where("rblob_registry_id = ? AND rblob_blob_id = ? "+
"AND rblob_image_name = ?", registry.ID, blobID, imageName)
sql, args, err := stmt.ToSql()
if err != nil {
return false, fmt.Errorf("failed to convert purge registry query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, r.db)
result, err := db.ExecContext(ctx, sql, args...)
if err != nil {
return false, databaseg.ProcessSQLErrorf(ctx, err, "error unlinking blobs")
}
affected, err := result.RowsAffected()
return affected == 1, err
}
func mapToInternalRegistryBlob(
ctx context.Context, registryID int64, blobID int64,
imageName string,
) *registryBlobDB {
creationTime := time.Now().UnixMilli()
session, _ := request.AuthSessionFrom(ctx)
return &registryBlobDB{
RegistryID: registryID,
BlobID: blobID,
ImageName: imageName,
CreatedAt: creationTime,
UpdatedAt: creationTime,
CreatedBy: session.Principal.ID,
UpdatedBy: session.Principal.ID,
}
}