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

263 lines
8.3 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 packages
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
usercontroller "github.com/harness/gitness/app/api/controller/user"
"github.com/harness/gitness/app/api/render"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/app/auth/authn"
"github.com/harness/gitness/app/auth/authz"
corestore "github.com/harness/gitness/app/store"
urlprovider "github.com/harness/gitness/app/url"
artifact2 "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
"github.com/harness/gitness/registry/app/dist_temp/errcode"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/commons"
"github.com/harness/gitness/registry/app/storage"
"github.com/harness/gitness/registry/app/store"
"github.com/harness/gitness/registry/request"
"github.com/harness/gitness/types/enum"
"github.com/rs/zerolog/log"
)
func NewHandler(
registryDao store.RegistryRepository,
downloadStatDao store.DownloadStatRepository,
spaceStore corestore.SpaceStore, tokenStore corestore.TokenStore,
userCtrl *usercontroller.Controller, authenticator authn.Authenticator,
urlProvider urlprovider.Provider, authorizer authz.Authorizer,
) Handler {
return &handler{
RegistryDao: registryDao,
DownloadStatDao: downloadStatDao,
SpaceStore: spaceStore,
TokenStore: tokenStore,
UserCtrl: userCtrl,
Authenticator: authenticator,
URLProvider: urlProvider,
Authorizer: authorizer,
}
}
type handler struct {
RegistryDao store.RegistryRepository
DownloadStatDao store.DownloadStatRepository
SpaceStore corestore.SpaceStore
TokenStore corestore.TokenStore
UserCtrl *usercontroller.Controller
Authenticator authn.Authenticator
URLProvider urlprovider.Provider
Authorizer authz.Authorizer
}
type Handler interface {
GetRegistryCheckAccess(
ctx context.Context,
r *http.Request,
reqPermissions ...enum.Permission,
) error
GetArtifactInfo(r *http.Request) (pkg.ArtifactInfo, error)
TrackDownloadStats(
ctx context.Context,
r *http.Request,
) error
GetAuthenticator() authn.Authenticator
HandleErrors2(ctx context.Context, errors errcode.Error, w http.ResponseWriter)
HandleErrors(ctx context.Context, errors errcode.Errors, w http.ResponseWriter)
HandleError(ctx context.Context, w http.ResponseWriter, err error)
ServeContent(
w http.ResponseWriter, r *http.Request, fileReader *storage.FileReader, filename string,
)
}
type PathPackageType string
const (
PathPackageTypeGeneric PathPackageType = "generic"
PathPackageTypeMaven PathPackageType = "maven"
PathPackageTypePython PathPackageType = "python"
PathPackageTypeNuget PathPackageType = "nuget"
PathPackageTypeNpm PathPackageType = "npm"
PathPackageTypeRPM PathPackageType = "rpm"
)
var packageTypeMap = map[PathPackageType]artifact2.PackageType{
PathPackageTypeGeneric: artifact2.PackageTypeGENERIC,
PathPackageTypeMaven: artifact2.PackageTypeMAVEN,
PathPackageTypePython: artifact2.PackageTypePYTHON,
PathPackageTypeNuget: artifact2.PackageTypeNUGET,
PathPackageTypeNpm: artifact2.PackageTypeNPM,
PathPackageTypeRPM: artifact2.PackageTypeRPM,
}
func (h *handler) GetAuthenticator() authn.Authenticator {
return h.Authenticator
}
func (h *handler) GetRegistryCheckAccess(
ctx context.Context,
r *http.Request,
reqPermissions ...enum.Permission,
) error {
info, err := h.GetArtifactInfo(r)
if err != nil {
return err
}
return pkg.GetRegistryCheckAccess(ctx, h.RegistryDao, h.Authorizer,
h.SpaceStore,
info.RegIdentifier, info.ParentID, reqPermissions...)
}
func (h *handler) TrackDownloadStats(
ctx context.Context,
r *http.Request,
) error {
info := request.ArtifactInfoFrom(r.Context()) //nolint:contextcheck
if err := h.DownloadStatDao.CreateByRegistryIDImageAndArtifactName(ctx,
info.BaseArtifactInfo().RegistryID, info.BaseArtifactInfo().Image, info.GetVersion()); err != nil {
log.Error().Msgf("failed to create download stat: %v", err.Error())
return usererror.ErrInternal
}
return nil
}
func (h *handler) GetArtifactInfo(r *http.Request) (pkg.ArtifactInfo, error) {
ctx := r.Context()
rootIdentifier, registryIdentifier, pathPackageType, err := extractPathVars(r)
if err != nil {
return pkg.ArtifactInfo{}, errcode.ErrCodeInvalidRequest.WithDetail(err)
}
rootSpace, err := h.SpaceStore.FindByRefCaseInsensitive(ctx, rootIdentifier)
if err != nil {
log.Ctx(ctx).Error().Msgf("Root space not found: %s", rootIdentifier)
return pkg.ArtifactInfo{}, usererror.NotFoundf("Root not found: %s", rootIdentifier)
}
registry, err := h.RegistryDao.GetByRootParentIDAndName(ctx, rootSpace.ID, registryIdentifier)
if err != nil {
log.Ctx(ctx).Error().Msgf(
"registry %s not found for root: %s. Reason: %s", registryIdentifier, rootSpace.Identifier, err,
)
return pkg.ArtifactInfo{}, usererror.NotFoundf("Registry not found: %s", registryIdentifier)
}
_, err = h.SpaceStore.Find(r.Context(), registry.ParentID)
if err != nil {
log.Ctx(ctx).Error().Msgf("Parent space not found: %d", registry.ParentID)
return pkg.ArtifactInfo{}, usererror.NotFoundf("Parent not found for registry: %s", registryIdentifier)
}
return pkg.ArtifactInfo{
BaseInfo: &pkg.BaseInfo{
RootIdentifier: rootIdentifier,
RootParentID: rootSpace.ID,
ParentID: registry.ParentID,
PathPackageType: pathPackageType,
},
RegIdentifier: registryIdentifier,
RegistryID: registry.ID,
Registry: *registry,
Image: "",
}, nil
}
func (h *handler) HandleErrors2(ctx context.Context, err errcode.Error, w http.ResponseWriter) {
if !commons.IsEmptyError(err) {
w.WriteHeader(err.Code.Descriptor().HTTPStatusCode)
_ = errcode.ServeJSON(w, err)
log.Ctx(ctx).Error().Msgf("Error occurred while performing artifact action: %s", err.Message)
}
}
// HandleErrors TODO: Improve Error Handling
// HandleErrors handles errors and writes the appropriate response to the client.
func (h *handler) HandleErrors(ctx context.Context, errs errcode.Errors, w http.ResponseWriter) {
if !commons.IsEmpty(errs) {
LogError(errs)
log.Ctx(ctx).Error().Errs("errs occurred during artifact operation: ", errs).Msgf("Error occurred")
err := errs[0]
var e *commons.Error
if errors.As(err, &e) {
code := e.Status
w.WriteHeader(code)
} else {
w.WriteHeader(http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(errs)
if err != nil {
log.Ctx(ctx).Error().Err(err).Msgf("Error occurred during artifact error encoding")
}
}
}
func (h *handler) HandleError(ctx context.Context, w http.ResponseWriter, err error) {
if nil != err {
log.Error().Err(err).Ctx(ctx).Msgf("error: %v", err)
render.TranslatedUserError(ctx, w, err)
return
}
}
func LogError(errList errcode.Errors) {
for _, e1 := range errList {
log.Error().Err(e1).Msgf("error: %v", e1)
}
}
// extractPathVars extracts rootSpace, registryId, pathPackageType from the path
// Path format: /pkg/:rootSpace/:registry/:pathPackageType/...
func extractPathVars(r *http.Request) (
rootIdentifier string,
registry string,
packageType artifact2.PackageType,
err error,
) {
path := r.URL.Path
parts := strings.Split(path, "/")
if len(parts) < 5 {
return "", "", "", fmt.Errorf("invalid path: %s", path)
}
rootIdentifier = parts[2]
registry = parts[3]
pathPackageType := PathPackageType(parts[4])
if _, ok := packageTypeMap[pathPackageType]; !ok {
return "", "", "", fmt.Errorf("invalid package type: %s", packageType)
}
return rootIdentifier, registry, packageTypeMap[pathPackageType], nil
}
func (h *handler) ServeContent(
w http.ResponseWriter, r *http.Request, fileReader *storage.FileReader, filename string,
) {
if fileReader != nil {
http.ServeContent(w, r, filename, time.Time{}, fileReader)
}
}