From f44cb0a89890bad23c1b0b6f33455238561277b7 Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Wed, 25 Feb 2026 21:52:02 +0530 Subject: [PATCH 01/16] [feat]: [AH-2903]: add support for upload/download for raw files --- registry/app/api/handler/generic/base.go | 122 +++++++++++++++++++--- registry/app/api/router/packages/route.go | 33 +++--- registry/app/pkg/base/base.go | 85 +++++++++++++++ registry/app/pkg/base/utils.go | 3 + registry/app/pkg/generic/local.go | 56 ++++++++++ 5 files changed, 266 insertions(+), 33 deletions(-) diff --git a/registry/app/api/handler/generic/base.go b/registry/app/api/handler/generic/base.go index 82bc6def0d..bab6e696e9 100644 --- a/registry/app/api/handler/generic/base.go +++ b/registry/app/api/handler/generic/base.go @@ -188,29 +188,86 @@ func (h *Handler) GetGenericArtifactInfo(r *http.Request) ( return info, errcode.Error{} } -func (h *Handler) GetGenericArtifactInfoV2(r *http.Request) (generic2.ArtifactInfo, error) { +// GetFileArtifactInfoV2 handles file artifact info for non-GENERIC package types +// Path format: /pkg/{rootIdentifier}/{registryIdentifier}/files/a/b/c/d/e +// Where: fileName = e (last segment), filePath = a/b/c/d/e (entire path) +func (h *Handler) GetFileArtifactInfoV2(r *http.Request) (generic2.ArtifactInfo, error) { ctx := r.Context() path := r.URL.Path path = strings.TrimPrefix(path, "/") splits := strings.Split(path, "/") - filePath := strings.Join(splits[6:], "/") - fileName := splits[len(splits)-1] - rootIdentifier, registryIdentifier, packageName, version := - chi.URLParam(r, "rootIdentifier"), - chi.URLParam(r, "registryIdentifier"), - chi.URLParam(r, "package"), - chi.URLParam(r, "version") + rootIdentifier := chi.URLParam(r, "rootIdentifier") + registryIdentifier := chi.URLParam(r, "registryIdentifier") - if err := validatePackageVersionV2(packageName, version); err != nil { - return generic2.ArtifactInfo{}, fmt.Errorf("invalid image name/version/fileName: %q/%q %w", packageName, - version, err) + rootSpace, err := h.SpaceFinder.FindByRef(ctx, rootIdentifier) + if err != nil { + log.Ctx(ctx).Error().Err(err).Msgf("Root space not found: %q", rootIdentifier) + return generic2.ArtifactInfo{}, usererror.NotFoundf("Root %q not found", rootIdentifier) + } + + registry, err := h.RegistryFinder.FindByRootRef(ctx, rootSpace.Identifier, registryIdentifier) + if err != nil { + log.Ctx(ctx).Error().Err(err).Msgf( + "registry %q not found for root: %q. Reason: %q", registryIdentifier, rootSpace.Identifier, err, + ) + return generic2.ArtifactInfo{}, usererror.NotFoundf("Registry %q not found for root: %q", registryIdentifier, + rootSpace.Identifier) } - if err := validateFilePath(filePath); err != nil { + // Path format: /pkg/{rootIdentifier}/{registryIdentifier}/files/* + // splits[0] = "pkg", splits[1] = rootIdentifier, splits[2] = registryIdentifier, splits[3] = "files" + // Remaining path starts at splits[4] + remainingSegments := splits[4:] + + if len(remainingSegments) == 0 { + return generic2.ArtifactInfo{}, usererror.BadRequestf("Invalid request: no file path provided") + } + + fileName := remainingSegments[len(remainingSegments)-1] + filePath := strings.Join(remainingSegments, "/") + + if err = validateFilePath(filePath); err != nil { return generic2.ArtifactInfo{}, usererror.BadRequestf("%v", err) } + info := generic2.ArtifactInfo{ + ArtifactInfo: pkg.ArtifactInfo{ + BaseInfo: &pkg.BaseInfo{ + PathPackageType: registry.PackageType, + ParentID: registry.ParentID, + RootIdentifier: rootIdentifier, + RootParentID: rootSpace.ID, + }, + RegIdentifier: registryIdentifier, + RegistryID: registry.ID, + Registry: *registry, + }, + FileName: fileName, + FilePath: filePath, + } + + log.Ctx(ctx).Info().Msgf("Dispatch: URI: %s", path) + err2 := utils.PatternAllowed(registry.AllowedPattern, registry.BlockedPattern, + info.FilePath) + if err2 != nil { + return generic2.ArtifactInfo{}, usererror.BadRequestf("Invalid request: File path %q not "+ + "allowed due to allowed / blocked patterns", + info.FilePath) + } + + return info, nil +} + +func (h *Handler) GetGenericArtifactInfoV2(r *http.Request) (generic2.ArtifactInfo, error) { + ctx := r.Context() + path := r.URL.Path + path = strings.TrimPrefix(path, "/") + splits := strings.Split(path, "/") + + rootIdentifier := chi.URLParam(r, "rootIdentifier") + registryIdentifier := chi.URLParam(r, "registryIdentifier") + rootSpace, err := h.SpaceFinder.FindByRef(ctx, rootIdentifier) if err != nil { log.Ctx(ctx).Error().Err(err).Msgf("Root space not found: %q", rootIdentifier) @@ -226,6 +283,47 @@ func (h *Handler) GetGenericArtifactInfoV2(r *http.Request) (generic2.ArtifactIn rootSpace.Identifier) } + // For non-GENERIC package types, delegate to GetFileArtifactInfoV2 + if registry.PackageType != artifact2.PackageTypeGENERIC { + return h.GetFileArtifactInfoV2(r) + } + + // Handle GENERIC package type + // Path format: /pkg/{rootIdentifier}/{registryIdentifier}/files/* + // splits[0] = "pkg", splits[1] = rootIdentifier, splits[2] = registryIdentifier, splits[3] = "files" + // Remaining path starts at splits[4] + remainingSegments := splits[4:] + + if len(remainingSegments) == 0 { + return generic2.ArtifactInfo{}, usererror.BadRequestf("Invalid request: no file path provided") + } + + // For GENERIC package type: + // remainingSegments = [a, b, c, d, e] + // packageName = a (first segment) + // version = b (second segment) + // fileName = e (last segment) + // filePath = c/d/e (segments from index 2 onwards) + if len(remainingSegments) < 3 { + return generic2.ArtifactInfo{}, usererror.BadRequestf( + "Invalid request: expected at least package/version/file , got: %s", + strings.Join(remainingSegments, "/")) + } + + packageName := remainingSegments[0] + version := remainingSegments[1] + fileName := remainingSegments[len(remainingSegments)-1] + filePath := strings.Join(remainingSegments[2:], "/") + + if err = validatePackageVersionV2(packageName, version); err != nil { + return generic2.ArtifactInfo{}, fmt.Errorf("invalid image name/version/fileName: %q/%q %w", packageName, + version, err) + } + + if err = validateFilePath(filePath); err != nil { + return generic2.ArtifactInfo{}, usererror.BadRequestf("%v", err) + } + info := generic2.ArtifactInfo{ ArtifactInfo: pkg.ArtifactInfo{ BaseInfo: &pkg.BaseInfo{ diff --git a/registry/app/api/router/packages/route.go b/registry/app/api/router/packages/route.go index a5350dff34..6b171b938d 100644 --- a/registry/app/api/router/packages/route.go +++ b/registry/app/api/router/packages/route.go @@ -96,27 +96,18 @@ func NewRouter( // Files uses Generic Engine to serve and manage files r.Route("/files", func(r chi.Router) { r.Use(middlewareauthn.Attempt(packageHandler.GetAuthenticator())) - // We currently support managing files for a given package and a version. If requirements change in the future, - // this line will need to be removed - r.Route("/{package}/{version}", func(r chi.Router) { - r.Use(middleware.StoreArtifactInfo(genericHandler)) - - r.Route("/", func(r chi.Router) { - r.Group(func(r chi.Router) { - r.With(middleware.CheckQuarantineStatus(packageHandler)). - With(middleware.TrackDownloadStats(packageHandler)). - With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDownload)). - Get("/*", genericHandler.GetFile) - r.With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDelete)). - Delete("/*", genericHandler.DeleteFile) - r.With(middleware.CheckQuarantineStatus(packageHandler)). - With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDownload)). - Head("/*", genericHandler.HeadFile) - r.With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsUpload)). - Put("/*", genericHandler.PutFile) - }) - }) - }) + r.Use(middleware.StoreArtifactInfo(genericHandler)) + r.With(middleware.CheckQuarantineStatus(packageHandler)). + With(middleware.TrackDownloadStats(packageHandler)). + With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDownload)). + Get("/*", genericHandler.GetFile) + r.With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDelete)). + Delete("/*", genericHandler.DeleteFile) + r.With(middleware.CheckQuarantineStatus(packageHandler)). + With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDownload)). + Head("/*", genericHandler.HeadFile) + r.With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsUpload)). + Put("/*", genericHandler.PutFile) }) r.Route("/python", func(r chi.Router) { diff --git a/registry/app/pkg/base/base.go b/registry/app/pkg/base/base.go index 800d8a74b3..44e73dbd27 100644 --- a/registry/app/pkg/base/base.go +++ b/registry/app/pkg/base/base.go @@ -72,6 +72,20 @@ type LocalBase interface { file io.ReadCloser, metadata metadata.Metadata, ) (*commons.ResponseHeaders, string, error) + // UploadRawFile uploads a raw file to storage without creating Image/Artifact records. + UploadRawFile( + ctx context.Context, + info pkg.ArtifactInfo, + filePath string, + file io.ReadCloser, + failOnConflict bool, + ) (*commons.ResponseHeaders, string, error) + // DownloadRawFile downloads a raw file from storage using the file path directly. + DownloadRawFile( + ctx context.Context, + info pkg.ArtifactInfo, + filePath string, + ) (*commons.ResponseHeaders, *storage.FileReader, string, error) UpdateFileManagerAndCreateArtifact( ctx context.Context, info pkg.ArtifactInfo, @@ -187,6 +201,77 @@ func (l *localBase) Upload( return l.uploadInternal(ctx, info, fileName, version, path, nil, file, metadata) } +func (l *localBase) UploadRawFile( + ctx context.Context, + info pkg.ArtifactInfo, + filePath string, + file io.ReadCloser, + failOnConflict bool, +) (*commons.ResponseHeaders, string, error) { + responseHeaders := &commons.ResponseHeaders{ + Headers: make(map[string]string), + Code: 0, + } + + registry, err := l.registryFinder.FindByRootParentID(ctx, info.RootParentID, info.RegIdentifier) + if err != nil { + return responseHeaders, "", errcode.ErrCodeUnknown.WithDetail(err) + } + + // Check if file already exists + exists, err := l.ExistsByFilePath(ctx, registry.ID, filePath) + if err != nil { + return responseHeaders, "", errcode.ErrCodeUnknown.WithDetail(err) + } + + if exists && failOnConflict { + return responseHeaders, "", usererror.Conflict( + fmt.Sprintf("File already exists at path: %s", filePath)) + } + + session, _ := request.AuthSessionFrom(ctx) + fileInfo, err := l.fileManager.UploadFile( + ctx, + filePath, + registry.ID, + info.RootParentID, + info.RootIdentifier, + nil, // multipart.File not available + file, + session.Principal.ID, + ) + if err != nil { + return responseHeaders, "", errcode.ErrCodeUnknown.WithDetail(err) + } + + responseHeaders.Code = http.StatusCreated + return responseHeaders, fileInfo.Sha256, nil +} + +func (l *localBase) DownloadRawFile( + ctx context.Context, + info pkg.ArtifactInfo, + filePath string, +) (*commons.ResponseHeaders, *storage.FileReader, string, error) { + responseHeaders := &commons.ResponseHeaders{ + Headers: make(map[string]string), + Code: 0, + } + + registry, err := l.registryFinder.FindByRootParentID(ctx, info.RootParentID, info.RegIdentifier) + if err != nil { + return responseHeaders, nil, "", errcode.ErrCodeUnknown.WithDetail(err) + } + + fileReader, _, redirectURL, err := l.fileManager.DownloadFileByPath(ctx, filePath, registry.ID, + info.RegIdentifier, info.RootIdentifier, true) + if err != nil { + return responseHeaders, nil, "", err + } + responseHeaders.Code = http.StatusOK + return responseHeaders, fileReader, redirectURL, nil +} + func (l *localBase) UpdateFileManagerAndCreateArtifact( ctx context.Context, info pkg.ArtifactInfo, diff --git a/registry/app/pkg/base/utils.go b/registry/app/pkg/base/utils.go index 8af2b61e78..a5fd85b7c6 100644 --- a/registry/app/pkg/base/utils.go +++ b/registry/app/pkg/base/utils.go @@ -17,5 +17,8 @@ package base import "github.com/harness/gitness/registry/app/pkg" func GetCompletePath(info pkg.PackageArtifactInfo, filePath string) string { + if info.GetImage() == "" || info.GetVersion() == "" { + return filePath + } return info.GetImage() + "/" + info.GetVersion() + "/" + filePath } diff --git a/registry/app/pkg/generic/local.go b/registry/app/pkg/generic/local.go index 7f5d324415..47a8e651be 100644 --- a/registry/app/pkg/generic/local.go +++ b/registry/app/pkg/generic/local.go @@ -88,6 +88,12 @@ func (c *localRegistry) PutFile( reader io.ReadCloser, contentType string, ) (*commons.ResponseHeaders, string, error) { + // For non-GENERIC package types, use raw file upload + if info.Registry.PackageType != artifact.PackageTypeGENERIC { + return c.uploadRawFile(ctx, info, reader, contentType) + } + + // For GENERIC package type, use the existing upload flow completePath := pkg.JoinWithSeparator("/", info.Image, info.Version, info.FilePath) headers, sha256, err := c.localBase.Upload(ctx, info.ArtifactInfo, info.FileName, info.Version, completePath, reader, &generic2.GenericMetadata{}) @@ -105,6 +111,12 @@ func (c *localRegistry) DownloadFile( info generic.ArtifactInfo, filePath string, ) (*commons.ResponseHeaders, *storage.FileReader, io.ReadCloser, string, error) { + // For non-GENERIC package types, use raw file download + if info.Registry.PackageType != artifact.PackageTypeGENERIC { + return c.downloadRawFile(ctx, info, filePath) + } + + // For GENERIC package type, use the existing download flow download, reader, url, err := c.localBase.Download(ctx, info.ArtifactInfo, info.Version, filePath) if err != nil { log.Ctx(ctx).Error().Msgf("Failed to download file: %q, %q, %q", info.FileName, info.Version, filePath) @@ -124,3 +136,47 @@ func (c *localRegistry) HeadFile( ) (headers *commons.ResponseHeaders, err error) { return c.localBase.ExistsE(ctx, info, filePath) } + +func (c *localRegistry) uploadRawFile( + ctx context.Context, + info generic.ArtifactInfo, + reader io.ReadCloser, + contentType string, +) (*commons.ResponseHeaders, string, error) { + log.Ctx(ctx).Debug(). + Msgf("Uploading raw file for package type: %s, filePath: %s", + info.Registry.PackageType, info.FilePath) + + headers, sha256, err := c.localBase.UploadRawFile(ctx, info.ArtifactInfo, info.FilePath, reader, true) + if err != nil { + log.Ctx(ctx).Error().Err(err). + Msgf("Failed to upload raw file: %q, filePath: %q", + info.FileName, info.FilePath) + return nil, "", fmt.Errorf("failed to upload raw file: %w", err) + } + + log.Ctx(ctx).Info().Str("sha256", sha256). + Msgf("Successfully uploaded raw file. content type: %s", contentType) + return headers, sha256, nil +} + +func (c *localRegistry) downloadRawFile( + ctx context.Context, + info generic.ArtifactInfo, + filePath string, +) (*commons.ResponseHeaders, *storage.FileReader, io.ReadCloser, string, error) { + log.Ctx(ctx).Debug(). + Msgf("Downloading raw file for package type: %s, filePath: %s", + info.Registry.PackageType, filePath) + + headers, reader, url, err := c.localBase.DownloadRawFile(ctx, info.ArtifactInfo, filePath) + if err != nil { + log.Ctx(ctx).Error().Err(err). + Msgf("Failed to download raw file: %q, filePath: %q", + info.FileName, filePath) + return nil, nil, nil, "", fmt.Errorf("failed to download raw file: %w", err) + } + + log.Ctx(ctx).Info().Msgf("Successfully downloaded raw file from path: %s", filePath) + return headers, reader, nil, url, nil +} From 2b3429442fd34b7ddb9a07a0c8f241ce25eee704 Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Thu, 26 Feb 2026 01:59:05 +0530 Subject: [PATCH 02/16] [feat]: [AH-2903]: add support for upload/download for raw files --- registry/app/api/handler/generic/base.go | 41 +++++++++++++++++++-- registry/app/pkg/base/base.go | 46 +++++++++++++++++++++++- registry/app/pkg/generic/local.go | 22 +----------- 3 files changed, 84 insertions(+), 25 deletions(-) diff --git a/registry/app/api/handler/generic/base.go b/registry/app/api/handler/generic/base.go index bab6e696e9..c448e8fb3c 100644 --- a/registry/app/api/handler/generic/base.go +++ b/registry/app/api/handler/generic/base.go @@ -42,6 +42,8 @@ import ( generic2 "github.com/harness/gitness/registry/app/pkg/types/generic" refcache2 "github.com/harness/gitness/registry/app/services/refcache" "github.com/harness/gitness/registry/request" + regtypes "github.com/harness/gitness/registry/types" + coretypes "github.com/harness/gitness/types" "github.com/go-chi/chi/v5" "github.com/rs/zerolog/log" @@ -215,19 +217,41 @@ func (h *Handler) GetFileArtifactInfoV2(r *http.Request) (generic2.ArtifactInfo, rootSpace.Identifier) } + return h.buildFileArtifactInfo(ctx, registry, rootSpace, rootIdentifier, registryIdentifier, path, splits) +} + +// buildFileArtifactInfo is an internal helper that builds ArtifactInfo for non-GENERIC package types +// using already-fetched registry and rootSpace to avoid duplicate database queries +func (h *Handler) buildFileArtifactInfo( + ctx context.Context, + registry *regtypes.Registry, + rootSpace *coretypes.SpaceCore, + rootIdentifier string, + registryIdentifier string, + path string, + splits []string, +) (generic2.ArtifactInfo, error) { // Path format: /pkg/{rootIdentifier}/{registryIdentifier}/files/* // splits[0] = "pkg", splits[1] = rootIdentifier, splits[2] = registryIdentifier, splits[3] = "files" // Remaining path starts at splits[4] remainingSegments := splits[4:] if len(remainingSegments) == 0 { + log.Ctx(ctx).Error(). + Str("path", path). + Str("registryIdentifier", registryIdentifier). + Msg("No file path provided in request") return generic2.ArtifactInfo{}, usererror.BadRequestf("Invalid request: no file path provided") } fileName := remainingSegments[len(remainingSegments)-1] filePath := strings.Join(remainingSegments, "/") - if err = validateFilePath(filePath); err != nil { + if err := validateFilePath(filePath); err != nil { + log.Ctx(ctx).Error().Err(err). + Str("filePath", filePath). + Str("registryIdentifier", registryIdentifier). + Msg("Invalid file path") return generic2.ArtifactInfo{}, usererror.BadRequestf("%v", err) } @@ -251,11 +275,22 @@ func (h *Handler) GetFileArtifactInfoV2(r *http.Request) (generic2.ArtifactInfo, err2 := utils.PatternAllowed(registry.AllowedPattern, registry.BlockedPattern, info.FilePath) if err2 != nil { + log.Ctx(ctx).Error().Err(err2). + Str("filePath", info.FilePath). + Str("registryIdentifier", registryIdentifier). + Msg("File path not allowed due to allowed/blocked patterns") return generic2.ArtifactInfo{}, usererror.BadRequestf("Invalid request: File path %q not "+ "allowed due to allowed / blocked patterns", info.FilePath) } + log.Ctx(ctx).Debug(). + Str("filePath", filePath). + Str("fileName", fileName). + Str("registryIdentifier", registryIdentifier). + Str("packageType", string(registry.PackageType)). + Msg("Built file artifact info for non-GENERIC package") + return info, nil } @@ -283,9 +318,9 @@ func (h *Handler) GetGenericArtifactInfoV2(r *http.Request) (generic2.ArtifactIn rootSpace.Identifier) } - // For non-GENERIC package types, delegate to GetFileArtifactInfoV2 + // For non-GENERIC package types, use the helper to avoid duplicate DB queries if registry.PackageType != artifact2.PackageTypeGENERIC { - return h.GetFileArtifactInfoV2(r) + return h.buildFileArtifactInfo(ctx, registry, rootSpace, rootIdentifier, registryIdentifier, path, splits) } // Handle GENERIC package type diff --git a/registry/app/pkg/base/base.go b/registry/app/pkg/base/base.go index 44e73dbd27..10261e5df8 100644 --- a/registry/app/pkg/base/base.go +++ b/registry/app/pkg/base/base.go @@ -215,21 +215,40 @@ func (l *localBase) UploadRawFile( registry, err := l.registryFinder.FindByRootParentID(ctx, info.RootParentID, info.RegIdentifier) if err != nil { + log.Ctx(ctx).Error().Err(err). + Int64("rootParentID", info.RootParentID). + Str("regIdentifier", info.RegIdentifier). + Msg("Failed to find registry for raw file upload") return responseHeaders, "", errcode.ErrCodeUnknown.WithDetail(err) } // Check if file already exists exists, err := l.ExistsByFilePath(ctx, registry.ID, filePath) if err != nil { + log.Ctx(ctx).Error().Err(err). + Int64("registryID", registry.ID). + Str("filePath", filePath). + Msg("Failed to check if file exists") return responseHeaders, "", errcode.ErrCodeUnknown.WithDetail(err) } if exists && failOnConflict { + log.Ctx(ctx).Warn(). + Str("filePath", filePath). + Int64("registryID", registry.ID). + Msg("File already exists and failOnConflict is true") return responseHeaders, "", usererror.Conflict( fmt.Sprintf("File already exists at path: %s", filePath)) } - session, _ := request.AuthSessionFrom(ctx) + session, ok := request.AuthSessionFrom(ctx) + if !ok { + log.Ctx(ctx).Error(). + Str("filePath", filePath). + Msg("Authentication required for raw file upload") + return responseHeaders, "", usererror.ErrUnauthorized + } + fileInfo, err := l.fileManager.UploadFile( ctx, filePath, @@ -241,9 +260,20 @@ func (l *localBase) UploadRawFile( session.Principal.ID, ) if err != nil { + log.Ctx(ctx).Error().Err(err). + Str("filePath", filePath). + Int64("registryID", registry.ID). + Int64("principalID", session.Principal.ID). + Msg("Failed to upload raw file") return responseHeaders, "", errcode.ErrCodeUnknown.WithDetail(err) } + log.Ctx(ctx).Info(). + Str("filePath", filePath). + Str("sha256", fileInfo.Sha256). + Int64("registryID", registry.ID). + Msg("Successfully uploaded raw file") + responseHeaders.Code = http.StatusCreated return responseHeaders, fileInfo.Sha256, nil } @@ -260,14 +290,28 @@ func (l *localBase) DownloadRawFile( registry, err := l.registryFinder.FindByRootParentID(ctx, info.RootParentID, info.RegIdentifier) if err != nil { + log.Ctx(ctx).Error().Err(err). + Int64("rootParentID", info.RootParentID). + Str("regIdentifier", info.RegIdentifier). + Msg("Failed to find registry for raw file download") return responseHeaders, nil, "", errcode.ErrCodeUnknown.WithDetail(err) } fileReader, _, redirectURL, err := l.fileManager.DownloadFileByPath(ctx, filePath, registry.ID, info.RegIdentifier, info.RootIdentifier, true) if err != nil { + log.Ctx(ctx).Error().Err(err). + Str("filePath", filePath). + Int64("registryID", registry.ID). + Msg("Failed to download raw file") return responseHeaders, nil, "", err } + + log.Ctx(ctx).Info(). + Str("filePath", filePath). + Int64("registryID", registry.ID). + Msg("Successfully downloaded raw file") + responseHeaders.Code = http.StatusOK return responseHeaders, fileReader, redirectURL, nil } diff --git a/registry/app/pkg/generic/local.go b/registry/app/pkg/generic/local.go index 47a8e651be..8726862f0d 100644 --- a/registry/app/pkg/generic/local.go +++ b/registry/app/pkg/generic/local.go @@ -90,7 +90,7 @@ func (c *localRegistry) PutFile( ) (*commons.ResponseHeaders, string, error) { // For non-GENERIC package types, use raw file upload if info.Registry.PackageType != artifact.PackageTypeGENERIC { - return c.uploadRawFile(ctx, info, reader, contentType) + return c.uploadRawFile(ctx, info, reader) } // For GENERIC package type, use the existing upload flow @@ -141,22 +141,11 @@ func (c *localRegistry) uploadRawFile( ctx context.Context, info generic.ArtifactInfo, reader io.ReadCloser, - contentType string, ) (*commons.ResponseHeaders, string, error) { - log.Ctx(ctx).Debug(). - Msgf("Uploading raw file for package type: %s, filePath: %s", - info.Registry.PackageType, info.FilePath) - headers, sha256, err := c.localBase.UploadRawFile(ctx, info.ArtifactInfo, info.FilePath, reader, true) if err != nil { - log.Ctx(ctx).Error().Err(err). - Msgf("Failed to upload raw file: %q, filePath: %q", - info.FileName, info.FilePath) return nil, "", fmt.Errorf("failed to upload raw file: %w", err) } - - log.Ctx(ctx).Info().Str("sha256", sha256). - Msgf("Successfully uploaded raw file. content type: %s", contentType) return headers, sha256, nil } @@ -165,18 +154,9 @@ func (c *localRegistry) downloadRawFile( info generic.ArtifactInfo, filePath string, ) (*commons.ResponseHeaders, *storage.FileReader, io.ReadCloser, string, error) { - log.Ctx(ctx).Debug(). - Msgf("Downloading raw file for package type: %s, filePath: %s", - info.Registry.PackageType, filePath) - headers, reader, url, err := c.localBase.DownloadRawFile(ctx, info.ArtifactInfo, filePath) if err != nil { - log.Ctx(ctx).Error().Err(err). - Msgf("Failed to download raw file: %q, filePath: %q", - info.FileName, filePath) return nil, nil, nil, "", fmt.Errorf("failed to download raw file: %w", err) } - - log.Ctx(ctx).Info().Msgf("Successfully downloaded raw file from path: %s", filePath) return headers, reader, nil, url, nil } From 628e31a185a9076a7036024f63eec7e0166e075a Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Thu, 26 Feb 2026 02:04:19 +0530 Subject: [PATCH 03/16] [feat]: [AH-2903]: add support for upload/download for raw files --- registry/app/pkg/base/base.go | 45 +++++++--------------------------- registry/app/pkg/base/utils.go | 2 +- 2 files changed, 10 insertions(+), 37 deletions(-) diff --git a/registry/app/pkg/base/base.go b/registry/app/pkg/base/base.go index 10261e5df8..855601678d 100644 --- a/registry/app/pkg/base/base.go +++ b/registry/app/pkg/base/base.go @@ -212,35 +212,22 @@ func (l *localBase) UploadRawFile( Headers: make(map[string]string), Code: 0, } - - registry, err := l.registryFinder.FindByRootParentID(ctx, info.RootParentID, info.RegIdentifier) - if err != nil { - log.Ctx(ctx).Error().Err(err). - Int64("rootParentID", info.RootParentID). - Str("regIdentifier", info.RegIdentifier). - Msg("Failed to find registry for raw file upload") - return responseHeaders, "", errcode.ErrCodeUnknown.WithDetail(err) - } - - // Check if file already exists - exists, err := l.ExistsByFilePath(ctx, registry.ID, filePath) + exists, err := l.ExistsByFilePath(ctx, info.RegistryID, filePath) if err != nil { log.Ctx(ctx).Error().Err(err). - Int64("registryID", registry.ID). + Int64("registryID", info.RegistryID). Str("filePath", filePath). Msg("Failed to check if file exists") return responseHeaders, "", errcode.ErrCodeUnknown.WithDetail(err) } - if exists && failOnConflict { log.Ctx(ctx).Warn(). Str("filePath", filePath). - Int64("registryID", registry.ID). + Int64("registryID", info.RegistryID). Msg("File already exists and failOnConflict is true") return responseHeaders, "", usererror.Conflict( fmt.Sprintf("File already exists at path: %s", filePath)) } - session, ok := request.AuthSessionFrom(ctx) if !ok { log.Ctx(ctx).Error(). @@ -248,11 +235,10 @@ func (l *localBase) UploadRawFile( Msg("Authentication required for raw file upload") return responseHeaders, "", usererror.ErrUnauthorized } - fileInfo, err := l.fileManager.UploadFile( ctx, filePath, - registry.ID, + info.RegistryID, info.RootParentID, info.RootIdentifier, nil, // multipart.File not available @@ -262,7 +248,7 @@ func (l *localBase) UploadRawFile( if err != nil { log.Ctx(ctx).Error().Err(err). Str("filePath", filePath). - Int64("registryID", registry.ID). + Int64("registryID", info.RegistryID). Int64("principalID", session.Principal.ID). Msg("Failed to upload raw file") return responseHeaders, "", errcode.ErrCodeUnknown.WithDetail(err) @@ -271,9 +257,8 @@ func (l *localBase) UploadRawFile( log.Ctx(ctx).Info(). Str("filePath", filePath). Str("sha256", fileInfo.Sha256). - Int64("registryID", registry.ID). + Int64("registryID", info.RegistryID). Msg("Successfully uploaded raw file") - responseHeaders.Code = http.StatusCreated return responseHeaders, fileInfo.Sha256, nil } @@ -287,31 +272,19 @@ func (l *localBase) DownloadRawFile( Headers: make(map[string]string), Code: 0, } - - registry, err := l.registryFinder.FindByRootParentID(ctx, info.RootParentID, info.RegIdentifier) - if err != nil { - log.Ctx(ctx).Error().Err(err). - Int64("rootParentID", info.RootParentID). - Str("regIdentifier", info.RegIdentifier). - Msg("Failed to find registry for raw file download") - return responseHeaders, nil, "", errcode.ErrCodeUnknown.WithDetail(err) - } - - fileReader, _, redirectURL, err := l.fileManager.DownloadFileByPath(ctx, filePath, registry.ID, + fileReader, _, redirectURL, err := l.fileManager.DownloadFileByPath(ctx, filePath, info.RegistryID, info.RegIdentifier, info.RootIdentifier, true) if err != nil { log.Ctx(ctx).Error().Err(err). Str("filePath", filePath). - Int64("registryID", registry.ID). + Int64("registryID", info.RegistryID). Msg("Failed to download raw file") return responseHeaders, nil, "", err } - log.Ctx(ctx).Info(). Str("filePath", filePath). - Int64("registryID", registry.ID). + Int64("registryID", info.RegistryID). Msg("Successfully downloaded raw file") - responseHeaders.Code = http.StatusOK return responseHeaders, fileReader, redirectURL, nil } diff --git a/registry/app/pkg/base/utils.go b/registry/app/pkg/base/utils.go index a5fd85b7c6..8d81c9d2dd 100644 --- a/registry/app/pkg/base/utils.go +++ b/registry/app/pkg/base/utils.go @@ -17,7 +17,7 @@ package base import "github.com/harness/gitness/registry/app/pkg" func GetCompletePath(info pkg.PackageArtifactInfo, filePath string) string { - if info.GetImage() == "" || info.GetVersion() == "" { + if info.GetImage() == "" && info.GetVersion() == "" { return filePath } return info.GetImage() + "/" + info.GetVersion() + "/" + filePath From 57b40533c5b0f54b977a815c42ac4e64947c8bb5 Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Thu, 26 Feb 2026 02:06:27 +0530 Subject: [PATCH 04/16] [feat]: [AH-2903]: add support for upload/download for raw files --- registry/app/api/handler/generic/base.go | 30 ------------------------ 1 file changed, 30 deletions(-) diff --git a/registry/app/api/handler/generic/base.go b/registry/app/api/handler/generic/base.go index c448e8fb3c..54008fa564 100644 --- a/registry/app/api/handler/generic/base.go +++ b/registry/app/api/handler/generic/base.go @@ -190,36 +190,6 @@ func (h *Handler) GetGenericArtifactInfo(r *http.Request) ( return info, errcode.Error{} } -// GetFileArtifactInfoV2 handles file artifact info for non-GENERIC package types -// Path format: /pkg/{rootIdentifier}/{registryIdentifier}/files/a/b/c/d/e -// Where: fileName = e (last segment), filePath = a/b/c/d/e (entire path) -func (h *Handler) GetFileArtifactInfoV2(r *http.Request) (generic2.ArtifactInfo, error) { - ctx := r.Context() - path := r.URL.Path - path = strings.TrimPrefix(path, "/") - splits := strings.Split(path, "/") - - rootIdentifier := chi.URLParam(r, "rootIdentifier") - registryIdentifier := chi.URLParam(r, "registryIdentifier") - - rootSpace, err := h.SpaceFinder.FindByRef(ctx, rootIdentifier) - if err != nil { - log.Ctx(ctx).Error().Err(err).Msgf("Root space not found: %q", rootIdentifier) - return generic2.ArtifactInfo{}, usererror.NotFoundf("Root %q not found", rootIdentifier) - } - - registry, err := h.RegistryFinder.FindByRootRef(ctx, rootSpace.Identifier, registryIdentifier) - if err != nil { - log.Ctx(ctx).Error().Err(err).Msgf( - "registry %q not found for root: %q. Reason: %q", registryIdentifier, rootSpace.Identifier, err, - ) - return generic2.ArtifactInfo{}, usererror.NotFoundf("Registry %q not found for root: %q", registryIdentifier, - rootSpace.Identifier) - } - - return h.buildFileArtifactInfo(ctx, registry, rootSpace, rootIdentifier, registryIdentifier, path, splits) -} - // buildFileArtifactInfo is an internal helper that builds ArtifactInfo for non-GENERIC package types // using already-fetched registry and rootSpace to avoid duplicate database queries func (h *Handler) buildFileArtifactInfo( From a3f26403b31af68544250a2efc9ccc21731fa7ca Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Thu, 26 Feb 2026 02:07:41 +0530 Subject: [PATCH 05/16] [feat]: [AH-2903]: add support for upload/download for raw files --- registry/app/api/handler/generic/base.go | 148 +++++++++++------------ 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/registry/app/api/handler/generic/base.go b/registry/app/api/handler/generic/base.go index 54008fa564..740216d2bb 100644 --- a/registry/app/api/handler/generic/base.go +++ b/registry/app/api/handler/generic/base.go @@ -190,80 +190,6 @@ func (h *Handler) GetGenericArtifactInfo(r *http.Request) ( return info, errcode.Error{} } -// buildFileArtifactInfo is an internal helper that builds ArtifactInfo for non-GENERIC package types -// using already-fetched registry and rootSpace to avoid duplicate database queries -func (h *Handler) buildFileArtifactInfo( - ctx context.Context, - registry *regtypes.Registry, - rootSpace *coretypes.SpaceCore, - rootIdentifier string, - registryIdentifier string, - path string, - splits []string, -) (generic2.ArtifactInfo, error) { - // Path format: /pkg/{rootIdentifier}/{registryIdentifier}/files/* - // splits[0] = "pkg", splits[1] = rootIdentifier, splits[2] = registryIdentifier, splits[3] = "files" - // Remaining path starts at splits[4] - remainingSegments := splits[4:] - - if len(remainingSegments) == 0 { - log.Ctx(ctx).Error(). - Str("path", path). - Str("registryIdentifier", registryIdentifier). - Msg("No file path provided in request") - return generic2.ArtifactInfo{}, usererror.BadRequestf("Invalid request: no file path provided") - } - - fileName := remainingSegments[len(remainingSegments)-1] - filePath := strings.Join(remainingSegments, "/") - - if err := validateFilePath(filePath); err != nil { - log.Ctx(ctx).Error().Err(err). - Str("filePath", filePath). - Str("registryIdentifier", registryIdentifier). - Msg("Invalid file path") - return generic2.ArtifactInfo{}, usererror.BadRequestf("%v", err) - } - - info := generic2.ArtifactInfo{ - ArtifactInfo: pkg.ArtifactInfo{ - BaseInfo: &pkg.BaseInfo{ - PathPackageType: registry.PackageType, - ParentID: registry.ParentID, - RootIdentifier: rootIdentifier, - RootParentID: rootSpace.ID, - }, - RegIdentifier: registryIdentifier, - RegistryID: registry.ID, - Registry: *registry, - }, - FileName: fileName, - FilePath: filePath, - } - - log.Ctx(ctx).Info().Msgf("Dispatch: URI: %s", path) - err2 := utils.PatternAllowed(registry.AllowedPattern, registry.BlockedPattern, - info.FilePath) - if err2 != nil { - log.Ctx(ctx).Error().Err(err2). - Str("filePath", info.FilePath). - Str("registryIdentifier", registryIdentifier). - Msg("File path not allowed due to allowed/blocked patterns") - return generic2.ArtifactInfo{}, usererror.BadRequestf("Invalid request: File path %q not "+ - "allowed due to allowed / blocked patterns", - info.FilePath) - } - - log.Ctx(ctx).Debug(). - Str("filePath", filePath). - Str("fileName", fileName). - Str("registryIdentifier", registryIdentifier). - Str("packageType", string(registry.PackageType)). - Msg("Built file artifact info for non-GENERIC package") - - return info, nil -} - func (h *Handler) GetGenericArtifactInfoV2(r *http.Request) (generic2.ArtifactInfo, error) { ctx := r.Context() path := r.URL.Path @@ -371,6 +297,80 @@ func (h *Handler) GetGenericArtifactInfoV2(r *http.Request) (generic2.ArtifactIn return info, nil } +// buildFileArtifactInfo is an internal helper that builds ArtifactInfo for non-GENERIC package types +// using already-fetched registry and rootSpace to avoid duplicate database queries +func (h *Handler) buildFileArtifactInfo( + ctx context.Context, + registry *regtypes.Registry, + rootSpace *coretypes.SpaceCore, + rootIdentifier string, + registryIdentifier string, + path string, + splits []string, +) (generic2.ArtifactInfo, error) { + // Path format: /pkg/{rootIdentifier}/{registryIdentifier}/files/* + // splits[0] = "pkg", splits[1] = rootIdentifier, splits[2] = registryIdentifier, splits[3] = "files" + // Remaining path starts at splits[4] + remainingSegments := splits[4:] + + if len(remainingSegments) == 0 { + log.Ctx(ctx).Error(). + Str("path", path). + Str("registryIdentifier", registryIdentifier). + Msg("No file path provided in request") + return generic2.ArtifactInfo{}, usererror.BadRequestf("Invalid request: no file path provided") + } + + fileName := remainingSegments[len(remainingSegments)-1] + filePath := strings.Join(remainingSegments, "/") + + if err := validateFilePath(filePath); err != nil { + log.Ctx(ctx).Error().Err(err). + Str("filePath", filePath). + Str("registryIdentifier", registryIdentifier). + Msg("Invalid file path") + return generic2.ArtifactInfo{}, usererror.BadRequestf("%v", err) + } + + info := generic2.ArtifactInfo{ + ArtifactInfo: pkg.ArtifactInfo{ + BaseInfo: &pkg.BaseInfo{ + PathPackageType: registry.PackageType, + ParentID: registry.ParentID, + RootIdentifier: rootIdentifier, + RootParentID: rootSpace.ID, + }, + RegIdentifier: registryIdentifier, + RegistryID: registry.ID, + Registry: *registry, + }, + FileName: fileName, + FilePath: filePath, + } + + log.Ctx(ctx).Info().Msgf("Dispatch: URI: %s", path) + err2 := utils.PatternAllowed(registry.AllowedPattern, registry.BlockedPattern, + info.FilePath) + if err2 != nil { + log.Ctx(ctx).Error().Err(err2). + Str("filePath", info.FilePath). + Str("registryIdentifier", registryIdentifier). + Msg("File path not allowed due to allowed/blocked patterns") + return generic2.ArtifactInfo{}, usererror.BadRequestf("Invalid request: File path %q not "+ + "allowed due to allowed / blocked patterns", + info.FilePath) + } + + log.Ctx(ctx).Debug(). + Str("filePath", filePath). + Str("fileName", fileName). + Str("registryIdentifier", registryIdentifier). + Str("packageType", string(registry.PackageType)). + Msg("Built file artifact info for non-GENERIC package") + + return info, nil +} + func (h *Handler) GetPackageArtifactInfo(r *http.Request) (pkg.PackageArtifactInfo, error) { path := r.URL.Path path = strings.TrimPrefix(path, "/") From e806d1db9e137e274ce680aaa4105ea497724fd8 Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Thu, 26 Feb 2026 02:19:12 +0530 Subject: [PATCH 06/16] [feat]: [AH-2903]: add support for upload/download for raw files --- registry/app/api/handler/generic/base_test.go | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/registry/app/api/handler/generic/base_test.go b/registry/app/api/handler/generic/base_test.go index 2984058f3b..c97f481ed5 100644 --- a/registry/app/api/handler/generic/base_test.go +++ b/registry/app/api/handler/generic/base_test.go @@ -399,3 +399,95 @@ func TestValidateFilePathRegexPattern(t *testing.T) { }) } } + +// Test helpers for URL path parsing + +func TestURLPathParsing(t *testing.T) { + tests := []struct { + name string + urlPath string + wantSegments []string + wantPackage string + wantVersion string + wantFileName string + wantFilePath string + isGeneric bool + }{ + { + name: "GENERIC package - simple", + urlPath: "/pkg/root/registry/files/mypackage/1.0.0/file.jar", + wantSegments: []string{"mypackage", "1.0.0", "file.jar"}, + wantPackage: "mypackage", + wantVersion: "1.0.0", + wantFileName: "file.jar", + wantFilePath: "file.jar", + isGeneric: true, + }, + { + name: "GENERIC package - nested path", + urlPath: "/pkg/root/registry/files/org.example/2.0.0/sub/dir/file.jar", + wantSegments: []string{"org.example", "2.0.0", "sub", "dir", "file.jar"}, + wantPackage: "org.example", + wantVersion: "2.0.0", + wantFileName: "file.jar", + wantFilePath: "sub/dir/file.jar", + isGeneric: true, + }, + { + name: "non-GENERIC package - MAVEN", + urlPath: "/pkg/root/registry/files/org/example/lib/1.0/file.jar", + wantSegments: []string{"org", "example", "lib", "1.0", "file.jar"}, + wantFileName: "file.jar", + wantFilePath: "org/example/lib/1.0/file.jar", + isGeneric: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + path := strings.TrimPrefix(tt.urlPath, "/") + splits := strings.Split(path, "/") + // splits[0] = "pkg", splits[1] = root, splits[2] = registry, splits[3] = "files" + remainingSegments := splits[4:] + + if len(remainingSegments) != len(tt.wantSegments) { + t.Errorf("Expected %d segments, got %d", len(tt.wantSegments), len(remainingSegments)) + } + + if tt.isGeneric { + if len(remainingSegments) < 3 { + t.Errorf("GENERIC package needs at least 3 segments") + return + } + + packageName := remainingSegments[0] + version := remainingSegments[1] + fileName := remainingSegments[len(remainingSegments)-1] + filePath := strings.Join(remainingSegments[2:], "/") + + if packageName != tt.wantPackage { + t.Errorf("Expected package=%s, got %s", tt.wantPackage, packageName) + } + if version != tt.wantVersion { + t.Errorf("Expected version=%s, got %s", tt.wantVersion, version) + } + if fileName != tt.wantFileName { + t.Errorf("Expected fileName=%s, got %s", tt.wantFileName, fileName) + } + if filePath != tt.wantFilePath { + t.Errorf("Expected filePath=%s, got %s", tt.wantFilePath, filePath) + } + } else { + fileName := remainingSegments[len(remainingSegments)-1] + filePath := strings.Join(remainingSegments, "/") + + if fileName != tt.wantFileName { + t.Errorf("Expected fileName=%s, got %s", tt.wantFileName, fileName) + } + if filePath != tt.wantFilePath { + t.Errorf("Expected filePath=%s, got %s", tt.wantFilePath, filePath) + } + } + }) + } +} From 6f10489a65124961e5e6f27793e7ed22ccd1ecf2 Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Thu, 26 Feb 2026 16:11:07 +0530 Subject: [PATCH 07/16] [feat]: [AH-2903]: add support for upload/download for raw files --- registry/app/api/handler/generic/base.go | 23 +---------------------- registry/app/pkg/types/generic/types.go | 2 ++ 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/registry/app/api/handler/generic/base.go b/registry/app/api/handler/generic/base.go index 740216d2bb..8257ba9452 100644 --- a/registry/app/api/handler/generic/base.go +++ b/registry/app/api/handler/generic/base.go @@ -214,7 +214,6 @@ func (h *Handler) GetGenericArtifactInfoV2(r *http.Request) (generic2.ArtifactIn rootSpace.Identifier) } - // For non-GENERIC package types, use the helper to avoid duplicate DB queries if registry.PackageType != artifact2.PackageTypeGENERIC { return h.buildFileArtifactInfo(ctx, registry, rootSpace, rootIdentifier, registryIdentifier, path, splits) } @@ -247,7 +246,7 @@ func (h *Handler) GetGenericArtifactInfoV2(r *http.Request) (generic2.ArtifactIn filePath := strings.Join(remainingSegments[2:], "/") if err = validatePackageVersionV2(packageName, version); err != nil { - return generic2.ArtifactInfo{}, fmt.Errorf("invalid image name/version/fileName: %q/%q %w", packageName, + return generic2.ArtifactInfo{}, fmt.Errorf("invalid package name/version/fileName: %q/%q %w", packageName, version, err) } @@ -347,27 +346,7 @@ func (h *Handler) buildFileArtifactInfo( FileName: fileName, FilePath: filePath, } - log.Ctx(ctx).Info().Msgf("Dispatch: URI: %s", path) - err2 := utils.PatternAllowed(registry.AllowedPattern, registry.BlockedPattern, - info.FilePath) - if err2 != nil { - log.Ctx(ctx).Error().Err(err2). - Str("filePath", info.FilePath). - Str("registryIdentifier", registryIdentifier). - Msg("File path not allowed due to allowed/blocked patterns") - return generic2.ArtifactInfo{}, usererror.BadRequestf("Invalid request: File path %q not "+ - "allowed due to allowed / blocked patterns", - info.FilePath) - } - - log.Ctx(ctx).Debug(). - Str("filePath", filePath). - Str("fileName", fileName). - Str("registryIdentifier", registryIdentifier). - Str("packageType", string(registry.PackageType)). - Msg("Built file artifact info for non-GENERIC package") - return info, nil } diff --git a/registry/app/pkg/types/generic/types.go b/registry/app/pkg/types/generic/types.go index 986fd84ab3..00142c4515 100644 --- a/registry/app/pkg/types/generic/types.go +++ b/registry/app/pkg/types/generic/types.go @@ -35,6 +35,8 @@ func (a ArtifactInfo) BaseArtifactInfo() pkg.ArtifactInfo { func (a ArtifactInfo) GetImageVersion() (exists bool, imageVersion string) { if a.Image != "" && a.Version != "" { return true, pkg.JoinWithSeparator(":", a.Image, a.Version) + } else if a.Image == "" && a.Version == "" { + return true, a.FilePath } return false, "" } From c22b9e2268e350365cd1d6c3da3166178c97fd3f Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Thu, 26 Feb 2026 20:59:15 +0530 Subject: [PATCH 08/16] [feat]: [AH-2903]: add support for upload/download for raw files --- cmd/gitness/wire_gen.go | 6 +-- .../api/controller/pkg/generic/controller.go | 3 ++ registry/app/api/controller/pkg/generic/v2.go | 41 +++++++++++++------ .../app/api/controller/pkg/generic/wire.go | 2 + .../api/interfaces/package_type_factory.go | 1 + .../app/api/interfaces/package_wrapper.go | 1 + registry/app/helpers/package_wrapper.go | 11 +++++ registry/app/helpers/pkg/cargo.go | 4 ++ registry/app/helpers/pkg/docker.go | 4 ++ registry/app/helpers/pkg/generic.go | 4 ++ registry/app/helpers/pkg/gopkg.go | 4 ++ registry/app/helpers/pkg/helm.go | 4 ++ registry/app/helpers/pkg/huggingface.go | 4 ++ registry/app/helpers/pkg/maven.go | 4 ++ registry/app/helpers/pkg/npm.go | 4 ++ registry/app/helpers/pkg/nuget.go | 4 ++ registry/app/helpers/pkg/python.go | 4 ++ registry/app/helpers/pkg/rpm.go | 4 ++ registry/app/pkg/base/wrapper.go | 8 ++++ 19 files changed, 102 insertions(+), 15 deletions(-) diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index fb8fa414db..79b033247c 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -8,7 +8,6 @@ package main import ( "context" - check2 "github.com/harness/gitness/app/api/controller/check" connector2 "github.com/harness/gitness/app/api/controller/connector" "github.com/harness/gitness/app/api/controller/execution" @@ -183,9 +182,10 @@ import ( "github.com/harness/gitness/store/database/dbtx" "github.com/harness/gitness/types" "github.com/harness/gitness/types/check" +) +import ( _ "github.com/lib/pq" - _ "github.com/mattn/go-sqlite3" ) @@ -657,7 +657,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro genericLocalRegistry := generic2.LocalRegistryProvider(localBase, fileManager, upstreamProxyConfigRepository, transactor, registryRepository, imageRepository, artifactRepository, provider) localRegistryHelper := generic2.LocalRegistryHelperProvider(genericLocalRegistry, localBase) proxy := generic2.ProxyProvider(upstreamProxyConfigRepository, registryRepository, imageRepository, artifactRepository, fileManager, transactor, provider, spaceFinder, secretService, localRegistryHelper) - genericController := generic.ControllerProvider(spaceStore, authorizer, fileManager, genericDBStore, transactor, spaceFinder, genericLocalRegistry, proxy, finder, dependencyFirewallChecker, auditService) + genericController := generic.ControllerProvider(spaceStore, authorizer, fileManager, genericDBStore, transactor, spaceFinder, genericLocalRegistry, proxy, finder, dependencyFirewallChecker, auditService, packageWrapper) packagesHandler := api2.NewPackageHandlerProvider(registryRepository, downloadStatRepository, bandwidthStatRepository, spaceStore, tokenStore, controller, authenticator, provider, authorizer, spaceFinder, registryFinder, fileManager, finder, packageWrapper, auditService, artifactRepository) genericHandler := api2.NewGenericHandlerProvider(spaceStore, genericController, tokenStore, controller, authenticator, provider, authorizer, packagesHandler, spaceFinder, registryFinder, auditService) handler3 := router.GenericHandlerProvider(genericHandler) diff --git a/registry/app/api/controller/pkg/generic/controller.go b/registry/app/api/controller/pkg/generic/controller.go index 57724fbbe0..855f4f48cc 100644 --- a/registry/app/api/controller/pkg/generic/controller.go +++ b/registry/app/api/controller/pkg/generic/controller.go @@ -59,6 +59,7 @@ type Controller struct { quarantineFinder quarantine.Finder dependencyFirewallChecker interfaces.DependencyFirewallChecker auditService audit.Service + packageWrapper interfaces.PackageWrapper } type DBStore struct { @@ -82,6 +83,7 @@ func NewController( quarantineFinder quarantine.Finder, dependencyFirewallChecker interfaces.DependencyFirewallChecker, auditService audit.Service, + packageWrapper interfaces.PackageWrapper, ) *Controller { return &Controller{ SpaceStore: spaceStore, @@ -95,6 +97,7 @@ func NewController( quarantineFinder: quarantineFinder, dependencyFirewallChecker: dependencyFirewallChecker, auditService: auditService, + packageWrapper: packageWrapper, } } diff --git a/registry/app/api/controller/pkg/generic/v2.go b/registry/app/api/controller/pkg/generic/v2.go index d832b6ebac..d3f9da3114 100644 --- a/registry/app/api/controller/pkg/generic/v2.go +++ b/registry/app/api/controller/pkg/generic/v2.go @@ -19,6 +19,7 @@ import ( "fmt" "io" + artifact2 "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact" "github.com/harness/gitness/registry/app/pkg" "github.com/harness/gitness/registry/app/pkg/base" generic2 "github.com/harness/gitness/registry/app/pkg/generic" @@ -34,14 +35,18 @@ func (c Controller) DownloadFile( ) *GetArtifactResponse { f := func(registry registrytypes.Registry, a pkg.Artifact) response.Response { info.UpdateRegistryInfo(registry) - genericRegistry, ok := a.(generic2.Registry) - if !ok { + + // Check if file operations are supported for this package type + if !c.packageWrapper.IsFileOperationSupported(string(registry.PackageType)) { return &GetArtifactResponse{ BaseResponse: BaseResponse{ - Error: fmt.Errorf("invalid registry type: expected generic.Registry, got %T", a), + Error: fmt.Errorf("file operations not supported for package type: %s", registry.PackageType), }, } } + + key := base.GetFactoryKey(artifact2.PackageTypeGENERIC, registry.Type) + genericRegistry, _ := base.Factory(key).(generic2.Registry) headers, fileReader, readCloser, redirectURL, err := genericRegistry.DownloadFile(ctx, info, filePath) return &GetArtifactResponse{ BaseResponse: BaseResponse{ @@ -89,14 +94,18 @@ func (c Controller) HeadFile( ) *HeadArtifactResponse { f := func(registry registrytypes.Registry, a pkg.Artifact) response.Response { info.UpdateRegistryInfo(registry) - genericRegistry, ok := a.(generic2.Registry) - if !ok { + + // Check if file operations are supported for this package type + if !c.packageWrapper.IsFileOperationSupported(string(registry.PackageType)) { return &HeadArtifactResponse{ BaseResponse: BaseResponse{ - Error: fmt.Errorf("invalid registry type: expected generic.Registry, got %T", a), + Error: fmt.Errorf("file operations not supported for package type: %s", registry.PackageType), }, } } + + key := base.GetFactoryKey(artifact2.PackageTypeGENERIC, registry.Type) + genericRegistry, _ := base.Factory(key).(generic2.Registry) headers, err := genericRegistry.HeadFile(ctx, info, filePath) return &HeadArtifactResponse{ BaseResponse: BaseResponse{ @@ -128,14 +137,18 @@ func (c Controller) HeadFile( func (c Controller) DeleteFile(ctx context.Context, info generic.ArtifactInfo) *DeleteArtifactResponse { f := func(registry registrytypes.Registry, a pkg.Artifact) response.Response { info.UpdateRegistryInfo(registry) - genericRegistry, ok := a.(generic2.Registry) - if !ok { + + // Check if file operations are supported for this package type + if !c.packageWrapper.IsFileOperationSupported(string(registry.PackageType)) { return &DeleteArtifactResponse{ BaseResponse: BaseResponse{ - Error: fmt.Errorf("invalid registry type: expected generic.Registry, got %T", a), + Error: fmt.Errorf("file operations not supported for package type: %s", registry.PackageType), }, } } + + key := base.GetFactoryKey(artifact2.PackageTypeGENERIC, registry.Type) + genericRegistry, _ := base.Factory(key).(generic2.Registry) headers, err := genericRegistry.DeleteFile(ctx, info) return &DeleteArtifactResponse{ BaseResponse: BaseResponse{ @@ -172,14 +185,18 @@ func (c Controller) PutFile( ) *PutArtifactResponse { f := func(registry registrytypes.Registry, a pkg.Artifact) response.Response { info.UpdateRegistryInfo(registry) - genericRegistry, ok := a.(generic2.Registry) - if !ok { + + // Check if file operations are supported for this package type + if !c.packageWrapper.IsFileOperationSupported(string(registry.PackageType)) { return &PutArtifactResponse{ BaseResponse: BaseResponse{ - Error: fmt.Errorf("invalid registry type: expected generic.Registry, got %T", a), + Error: fmt.Errorf("file operations not supported for package type: %s", registry.PackageType), }, } } + + key := base.GetFactoryKey(artifact2.PackageTypeGENERIC, registry.Type) + genericRegistry, _ := base.Factory(key).(generic2.Registry) headers, sha256, err := genericRegistry.PutFile(ctx, info, reader, contentType) return &PutArtifactResponse{ BaseResponse: BaseResponse{ diff --git a/registry/app/api/controller/pkg/generic/wire.go b/registry/app/api/controller/pkg/generic/wire.go index cf5748ed46..1ed36e3ddf 100644 --- a/registry/app/api/controller/pkg/generic/wire.go +++ b/registry/app/api/controller/pkg/generic/wire.go @@ -51,6 +51,7 @@ func ControllerProvider( quarantineFinder quarantine.Finder, dependencyFirewallChecker interfaces.DependencyFirewallChecker, auditService audit.Service, + packageWrapper interfaces.PackageWrapper, ) *Controller { return NewController( spaceStore, @@ -64,6 +65,7 @@ func ControllerProvider( quarantineFinder, dependencyFirewallChecker, auditService, + packageWrapper, ) } diff --git a/registry/app/api/interfaces/package_type_factory.go b/registry/app/api/interfaces/package_type_factory.go index 019641d29f..86f456021c 100644 --- a/registry/app/api/interfaces/package_type_factory.go +++ b/registry/app/api/interfaces/package_type_factory.go @@ -24,6 +24,7 @@ import ( type PackageHelper interface { GetPackageType() string GetPathPackageType() string + IsFileOperationSupported() bool GetPackageURL(ctx context.Context, rootIdentifier string, registryIdentifier string, diff --git a/registry/app/api/interfaces/package_wrapper.go b/registry/app/api/interfaces/package_wrapper.go index c59315a6c1..690bcc95a7 100644 --- a/registry/app/api/interfaces/package_wrapper.go +++ b/registry/app/api/interfaces/package_wrapper.go @@ -22,6 +22,7 @@ import ( ) type PackageWrapper interface { + IsFileOperationSupported(packageType string) bool IsValidPackageType(packageType string) bool IsValidPackageTypes(packageTypes []string) bool IsValidRepoType(repoType string) bool diff --git a/registry/app/helpers/package_wrapper.go b/registry/app/helpers/package_wrapper.go index 1a69d62fcf..f014ab1df1 100644 --- a/registry/app/helpers/package_wrapper.go +++ b/registry/app/helpers/package_wrapper.go @@ -45,6 +45,17 @@ func (p *packageWrapper) GetPackage(packageType string) interfaces.PackageHelper return p.packageFactory.Get(packageType) } +func (p *packageWrapper) IsFileOperationSupported(packageType string) bool { + if packageType == "" { + return false + } + pkg := p.GetPackage(packageType) + if pkg == nil { + return false + } + return pkg.IsFileOperationSupported() +} + func (p *packageWrapper) IsValidPackageType(packageType string) bool { if packageType == "" { return true diff --git a/registry/app/helpers/pkg/cargo.go b/registry/app/helpers/pkg/cargo.go index 71c3be5302..b07142a733 100644 --- a/registry/app/helpers/pkg/cargo.go +++ b/registry/app/helpers/pkg/cargo.go @@ -81,6 +81,10 @@ func (c *cargoPackageType) GetPackageType() string { return c.packageType } +func (c *cargoPackageType) IsFileOperationSupported() bool { + return false +} + func (c *cargoPackageType) GetPathPackageType() string { return c.pathPackageType } diff --git a/registry/app/helpers/pkg/docker.go b/registry/app/helpers/pkg/docker.go index dd5ea657da..0986d65936 100644 --- a/registry/app/helpers/pkg/docker.go +++ b/registry/app/helpers/pkg/docker.go @@ -72,6 +72,10 @@ func (c *dockerPackageType) GetPackageType() string { return c.packageType } +func (c *dockerPackageType) IsFileOperationSupported() bool { + return false +} + func (c *dockerPackageType) GetPathPackageType() string { return c.pathPackageType } diff --git a/registry/app/helpers/pkg/generic.go b/registry/app/helpers/pkg/generic.go index 786bc21480..ba85e4bad7 100644 --- a/registry/app/helpers/pkg/generic.go +++ b/registry/app/helpers/pkg/generic.go @@ -63,6 +63,10 @@ func (c *genericPackageType) GetPackageType() string { return c.packageType } +func (c *genericPackageType) IsFileOperationSupported() bool { + return false +} + func (c *genericPackageType) GetPathPackageType() string { return c.pathPackageType } diff --git a/registry/app/helpers/pkg/gopkg.go b/registry/app/helpers/pkg/gopkg.go index a0a52f0a5c..f1af3a3a7f 100644 --- a/registry/app/helpers/pkg/gopkg.go +++ b/registry/app/helpers/pkg/gopkg.go @@ -67,6 +67,10 @@ func (c *goPackageType) GetPackageType() string { return c.packageType } +func (c *goPackageType) IsFileOperationSupported() bool { + return false +} + func (c *goPackageType) GetPathPackageType() string { return c.pathPackageType } diff --git a/registry/app/helpers/pkg/helm.go b/registry/app/helpers/pkg/helm.go index f0647822a2..6851fd6a72 100644 --- a/registry/app/helpers/pkg/helm.go +++ b/registry/app/helpers/pkg/helm.go @@ -64,6 +64,10 @@ func (c *helmPackageType) GetPackageType() string { return c.packageType } +func (c *helmPackageType) IsFileOperationSupported() bool { + return false +} + func (c *helmPackageType) GetPathPackageType() string { return c.pathPackageType } diff --git a/registry/app/helpers/pkg/huggingface.go b/registry/app/helpers/pkg/huggingface.go index 30b804a7d1..c09816dc30 100644 --- a/registry/app/helpers/pkg/huggingface.go +++ b/registry/app/helpers/pkg/huggingface.go @@ -62,6 +62,10 @@ func (c *huggingFacePackageType) GetPackageType() string { return c.packageType } +func (c *huggingFacePackageType) IsFileOperationSupported() bool { + return false +} + func (c *huggingFacePackageType) GetPathPackageType() string { return c.pathPackageType } diff --git a/registry/app/helpers/pkg/maven.go b/registry/app/helpers/pkg/maven.go index 9e1fe11888..6b724a2b1e 100644 --- a/registry/app/helpers/pkg/maven.go +++ b/registry/app/helpers/pkg/maven.go @@ -68,6 +68,10 @@ func (c *mavenPackageType) GetPackageType() string { return c.packageType } +func (c *mavenPackageType) IsFileOperationSupported() bool { + return false +} + func (c *mavenPackageType) GetPathPackageType() string { return c.pathPackageType } diff --git a/registry/app/helpers/pkg/npm.go b/registry/app/helpers/pkg/npm.go index 4c40edb749..08d554b006 100644 --- a/registry/app/helpers/pkg/npm.go +++ b/registry/app/helpers/pkg/npm.go @@ -68,6 +68,10 @@ func (c *npmPackageType) GetPackageType() string { return c.packageType } +func (c *npmPackageType) IsFileOperationSupported() bool { + return false +} + func (c *npmPackageType) GetPathPackageType() string { return c.pathPackageType } diff --git a/registry/app/helpers/pkg/nuget.go b/registry/app/helpers/pkg/nuget.go index fb2b83bf6b..beeece4877 100644 --- a/registry/app/helpers/pkg/nuget.go +++ b/registry/app/helpers/pkg/nuget.go @@ -67,6 +67,10 @@ func (c *nugetPackageType) GetPackageType() string { return c.packageType } +func (c *nugetPackageType) IsFileOperationSupported() bool { + return false +} + func (c *nugetPackageType) GetPathPackageType() string { return c.pathPackageType } diff --git a/registry/app/helpers/pkg/python.go b/registry/app/helpers/pkg/python.go index 9e117254db..f7dc8c038f 100644 --- a/registry/app/helpers/pkg/python.go +++ b/registry/app/helpers/pkg/python.go @@ -67,6 +67,10 @@ func (c *pythonPackageType) GetPackageType() string { return c.packageType } +func (c *pythonPackageType) IsFileOperationSupported() bool { + return false +} + func (c *pythonPackageType) GetPathPackageType() string { return c.pathPackageType } diff --git a/registry/app/helpers/pkg/rpm.go b/registry/app/helpers/pkg/rpm.go index 789b27cba4..b77b8dfdee 100644 --- a/registry/app/helpers/pkg/rpm.go +++ b/registry/app/helpers/pkg/rpm.go @@ -64,6 +64,10 @@ func (c *rpmPackageType) GetPackageType() string { return c.packageType } +func (c *rpmPackageType) IsFileOperationSupported() bool { + return false +} + func (c *rpmPackageType) GetPathPackageType() string { return c.pathPackageType } diff --git a/registry/app/pkg/base/wrapper.go b/registry/app/pkg/base/wrapper.go index 388f058309..2e11153e9c 100644 --- a/registry/app/pkg/base/wrapper.go +++ b/registry/app/pkg/base/wrapper.go @@ -201,10 +201,18 @@ func factory(key string) pkg.Artifact { return TypeRegistry[key] } +func Factory(key string) pkg.Artifact { + return factory(key) +} + func getFactoryKey(packageType artifact.PackageType, registryType artifact.RegistryType) string { return string(packageType) + ":" + string(registryType) } +func GetFactoryKey(packageType artifact.PackageType, registryType artifact.RegistryType) string { + return getFactoryKey(packageType, registryType) +} + func GetArtifactRegistry(registry registrytypes.Registry) pkg.Artifact { key := getFactoryKey(registry.PackageType, registry.Type) return factory(key) From fb17cacfdf73e0ca8cf8d2f6c4553a81e5a46aa6 Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Thu, 26 Feb 2026 22:04:26 +0530 Subject: [PATCH 09/16] [feat]: [AH-2903]: add support for upload/download for raw files --- cmd/gitness/wire_gen.go | 4 +-- registry/app/api/handler/generic/base_test.go | 16 +++++------ registry/app/helpers/pkg/generic.go | 2 +- registry/app/pkg/python/local_helper_test.go | 28 +++++++++++++++++++ 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/cmd/gitness/wire_gen.go b/cmd/gitness/wire_gen.go index 79b033247c..e1eb4adc27 100644 --- a/cmd/gitness/wire_gen.go +++ b/cmd/gitness/wire_gen.go @@ -8,6 +8,7 @@ package main import ( "context" + check2 "github.com/harness/gitness/app/api/controller/check" connector2 "github.com/harness/gitness/app/api/controller/connector" "github.com/harness/gitness/app/api/controller/execution" @@ -182,10 +183,9 @@ import ( "github.com/harness/gitness/store/database/dbtx" "github.com/harness/gitness/types" "github.com/harness/gitness/types/check" -) -import ( _ "github.com/lib/pq" + _ "github.com/mattn/go-sqlite3" ) diff --git a/registry/app/api/handler/generic/base_test.go b/registry/app/api/handler/generic/base_test.go index c97f481ed5..389a6bfacb 100644 --- a/registry/app/api/handler/generic/base_test.go +++ b/registry/app/api/handler/generic/base_test.go @@ -404,14 +404,14 @@ func TestValidateFilePathRegexPattern(t *testing.T) { func TestURLPathParsing(t *testing.T) { tests := []struct { - name string - urlPath string - wantSegments []string - wantPackage string - wantVersion string - wantFileName string - wantFilePath string - isGeneric bool + name string + urlPath string + wantSegments []string + wantPackage string + wantVersion string + wantFileName string + wantFilePath string + isGeneric bool }{ { name: "GENERIC package - simple", diff --git a/registry/app/helpers/pkg/generic.go b/registry/app/helpers/pkg/generic.go index ba85e4bad7..095416b495 100644 --- a/registry/app/helpers/pkg/generic.go +++ b/registry/app/helpers/pkg/generic.go @@ -64,7 +64,7 @@ func (c *genericPackageType) GetPackageType() string { } func (c *genericPackageType) IsFileOperationSupported() bool { - return false + return true } func (c *genericPackageType) GetPathPackageType() string { diff --git a/registry/app/pkg/python/local_helper_test.go b/registry/app/pkg/python/local_helper_test.go index e0bfa47c8d..06e8290a9f 100644 --- a/registry/app/pkg/python/local_helper_test.go +++ b/registry/app/pkg/python/local_helper_test.go @@ -98,6 +98,11 @@ func (m *MockLocalBase) ExistsE( return args.Get(0).(*commons.ResponseHeaders), args.Error(1) } +func (m *MockLocalBase) ExistsByFilePath(ctx context.Context, registryID int64, filePath string) (bool, error) { + args := m.Called(ctx, registryID, filePath) + return args.Bool(0), args.Error(1) +} + func (m *MockLocalBase) DeleteFile( ctx context.Context, info pkg.PackageArtifactInfo, @@ -171,6 +176,29 @@ func (m *MockLocalBase) UploadFile( return args.Get(0).(*commons.ResponseHeaders), args.String(1), args.Error(2) //nolint:errcheck } +func (m *MockLocalBase) UploadRawFile( + ctx context.Context, + info pkg.ArtifactInfo, + filePath string, + file io.ReadCloser, + failOnConflict bool, +) (*commons.ResponseHeaders, string, error) { + args := m.Called(ctx, info, filePath, file, failOnConflict) + //nolint:errcheck + return args.Get(0).(*commons.ResponseHeaders), args.String(1), args.Error(2) +} + +func (m *MockLocalBase) DownloadRawFile( + ctx context.Context, + info pkg.ArtifactInfo, + filePath string, +) (*commons.ResponseHeaders, *storage.FileReader, string, error) { + args := m.Called(ctx, info, filePath) + //nolint:errcheck + return args.Get(0).(*commons.ResponseHeaders), args.Get(1).(*storage.FileReader), + args.String(2), args.Error(3) +} + func (m *MockLocalBase) MoveMultipleTempFilesAndCreateArtifact( ctx context.Context, info *pkg.ArtifactInfo, From d0668738e4bc6b5268750d24f50ebf632a44f1c5 Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Thu, 26 Feb 2026 22:15:05 +0530 Subject: [PATCH 10/16] [feat]: [AH-2903]: add support for upload/download for raw files --- registry/app/pkg/npm/local_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/registry/app/pkg/npm/local_test.go b/registry/app/pkg/npm/local_test.go index 9b282a40ec..5dd70f74b6 100644 --- a/registry/app/pkg/npm/local_test.go +++ b/registry/app/pkg/npm/local_test.go @@ -199,6 +199,24 @@ func (m *mockLocalBase) AuditPush( } } +func (m *mockLocalBase) UploadRawFile( + context.Context, + pkg.ArtifactInfo, + string, + io.ReadCloser, + bool, +) (*commons.ResponseHeaders, string, error) { + panic("not implemented in tests") +} + +func (m *mockLocalBase) DownloadRawFile( + context.Context, + pkg.ArtifactInfo, + string, +) (*commons.ResponseHeaders, *storage.FileReader, string, error) { + panic("not implemented in tests") +} + type mockTagsDAO struct { findByImageNameAndRegID func( ctx context.Context, From 3821da77d2263dab5bb7e286329dba4db77210e8 Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Thu, 26 Feb 2026 22:35:30 +0530 Subject: [PATCH 11/16] [feat]: [AH-2903]: add support for upload/download for raw files --- registry/app/pkg/python/local_helper_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/registry/app/pkg/python/local_helper_test.go b/registry/app/pkg/python/local_helper_test.go index 06e8290a9f..60e37ad7e9 100644 --- a/registry/app/pkg/python/local_helper_test.go +++ b/registry/app/pkg/python/local_helper_test.go @@ -145,11 +145,6 @@ func (m *MockLocalBase) Exists(ctx context.Context, info pkg.ArtifactInfo, path return args.Bool(0) } -func (m *MockLocalBase) ExistsByFilePath(ctx context.Context, registryID int64, filePath string) (bool, error) { - args := m.Called(ctx, registryID, filePath) - return args.Bool(0), args.Error(1) -} - func (m *MockLocalBase) Download(ctx context.Context, info pkg.ArtifactInfo, version, filename string) ( *commons.ResponseHeaders, *storage.FileReader, string, error, ) { From d8b60b7b134baed9ebb579e9cd5d4457d721ef31 Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Thu, 26 Feb 2026 22:56:19 +0530 Subject: [PATCH 12/16] [feat]: [AH-2903]: add support for upload/download for raw files --- .../api/controller/mocks/package_wrapper.go | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/registry/app/api/controller/mocks/package_wrapper.go b/registry/app/api/controller/mocks/package_wrapper.go index 159cebe72a..40a31ef29a 100644 --- a/registry/app/api/controller/mocks/package_wrapper.go +++ b/registry/app/api/controller/mocks/package_wrapper.go @@ -1070,6 +1070,57 @@ func (_c *MockPackageWrapper_IsValidUpstreamSources_Call) RunAndReturn(run func( return _c } +// IsFileOperationSupported provides a mock function for the type MockPackageWrapper +func (_mock *MockPackageWrapper) IsFileOperationSupported(packageType string) bool { + ret := _mock.Called(packageType) + + if len(ret) == 0 { + panic("no return value specified for IsFileOperationSupported") + } + + var r0 bool + if returnFunc, ok := ret.Get(0).(func(string) bool); ok { + r0 = returnFunc(packageType) + } else { + r0 = ret.Get(0).(bool) + } + return r0 +} + +// MockPackageWrapper_IsFileOperationSupported_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsFileOperationSupported' +type MockPackageWrapper_IsFileOperationSupported_Call struct { + *mock.Call +} + +// IsFileOperationSupported is a helper method to define mock.On call +// - packageType string +func (_e *MockPackageWrapper_Expecter) IsFileOperationSupported(packageType interface{}) *MockPackageWrapper_IsFileOperationSupported_Call { + return &MockPackageWrapper_IsFileOperationSupported_Call{Call: _e.mock.On("IsFileOperationSupported", packageType)} +} + +func (_c *MockPackageWrapper_IsFileOperationSupported_Call) Run(run func(packageType string)) *MockPackageWrapper_IsFileOperationSupported_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 string + if args[0] != nil { + arg0 = args[0].(string) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockPackageWrapper_IsFileOperationSupported_Call) Return(b bool) *MockPackageWrapper_IsFileOperationSupported_Call { + _c.Call.Return(b) + return _c +} + +func (_c *MockPackageWrapper_IsFileOperationSupported_Call) RunAndReturn(run func(packageType string) bool) *MockPackageWrapper_IsFileOperationSupported_Call { + _c.Call.Return(run) + return _c +} + // ValidateRepoType provides a mock function for the type MockPackageWrapper func (_mock *MockPackageWrapper) ValidateRepoType(packageType string, repoType string) bool { ret := _mock.Called(packageType, repoType) From 900133bb326a763232ad8d8af45ed6c7cf19a476 Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Thu, 26 Feb 2026 23:19:27 +0530 Subject: [PATCH 13/16] [feat]: [AH-2903]: add support for upload/download for raw files --- registry/app/api/handler/generic/base.go | 2 - registry/app/api/handler/generic/base_test.go | 94 +++++++++++-------- 2 files changed, 53 insertions(+), 43 deletions(-) diff --git a/registry/app/api/handler/generic/base.go b/registry/app/api/handler/generic/base.go index 8257ba9452..2b08ab94f0 100644 --- a/registry/app/api/handler/generic/base.go +++ b/registry/app/api/handler/generic/base.go @@ -296,8 +296,6 @@ func (h *Handler) GetGenericArtifactInfoV2(r *http.Request) (generic2.ArtifactIn return info, nil } -// buildFileArtifactInfo is an internal helper that builds ArtifactInfo for non-GENERIC package types -// using already-fetched registry and rootSpace to avoid duplicate database queries func (h *Handler) buildFileArtifactInfo( ctx context.Context, registry *regtypes.Registry, diff --git a/registry/app/api/handler/generic/base_test.go b/registry/app/api/handler/generic/base_test.go index 389a6bfacb..7b16df69f7 100644 --- a/registry/app/api/handler/generic/base_test.go +++ b/registry/app/api/handler/generic/base_test.go @@ -402,17 +402,58 @@ func TestValidateFilePathRegexPattern(t *testing.T) { // Test helpers for URL path parsing +type urlPathTest struct { + name string + urlPath string + wantSegments []string + wantPackage string + wantVersion string + wantFileName string + wantFilePath string + isGeneric bool +} + +func validateGenericPackageSegments(t *testing.T, segments []string, tt urlPathTest) { + t.Helper() + if len(segments) < 3 { + t.Errorf("GENERIC package needs at least 3 segments") + return + } + + packageName := segments[0] + version := segments[1] + fileName := segments[len(segments)-1] + filePath := strings.Join(segments[2:], "/") + + if packageName != tt.wantPackage { + t.Errorf("Expected package=%s, got %s", tt.wantPackage, packageName) + } + if version != tt.wantVersion { + t.Errorf("Expected version=%s, got %s", tt.wantVersion, version) + } + if fileName != tt.wantFileName { + t.Errorf("Expected fileName=%s, got %s", tt.wantFileName, fileName) + } + if filePath != tt.wantFilePath { + t.Errorf("Expected filePath=%s, got %s", tt.wantFilePath, filePath) + } +} + +func validateNonGenericPackageSegments(t *testing.T, segments []string, tt urlPathTest) { + t.Helper() + fileName := segments[len(segments)-1] + filePath := strings.Join(segments, "/") + + if fileName != tt.wantFileName { + t.Errorf("Expected fileName=%s, got %s", tt.wantFileName, fileName) + } + if filePath != tt.wantFilePath { + t.Errorf("Expected filePath=%s, got %s", tt.wantFilePath, filePath) + } +} + func TestURLPathParsing(t *testing.T) { - tests := []struct { - name string - urlPath string - wantSegments []string - wantPackage string - wantVersion string - wantFileName string - wantFilePath string - isGeneric bool - }{ + tests := []urlPathTest{ { name: "GENERIC package - simple", urlPath: "/pkg/root/registry/files/mypackage/1.0.0/file.jar", @@ -455,38 +496,9 @@ func TestURLPathParsing(t *testing.T) { } if tt.isGeneric { - if len(remainingSegments) < 3 { - t.Errorf("GENERIC package needs at least 3 segments") - return - } - - packageName := remainingSegments[0] - version := remainingSegments[1] - fileName := remainingSegments[len(remainingSegments)-1] - filePath := strings.Join(remainingSegments[2:], "/") - - if packageName != tt.wantPackage { - t.Errorf("Expected package=%s, got %s", tt.wantPackage, packageName) - } - if version != tt.wantVersion { - t.Errorf("Expected version=%s, got %s", tt.wantVersion, version) - } - if fileName != tt.wantFileName { - t.Errorf("Expected fileName=%s, got %s", tt.wantFileName, fileName) - } - if filePath != tt.wantFilePath { - t.Errorf("Expected filePath=%s, got %s", tt.wantFilePath, filePath) - } + validateGenericPackageSegments(t, remainingSegments, tt) } else { - fileName := remainingSegments[len(remainingSegments)-1] - filePath := strings.Join(remainingSegments, "/") - - if fileName != tt.wantFileName { - t.Errorf("Expected fileName=%s, got %s", tt.wantFileName, fileName) - } - if filePath != tt.wantFilePath { - t.Errorf("Expected filePath=%s, got %s", tt.wantFilePath, filePath) - } + validateNonGenericPackageSegments(t, remainingSegments, tt) } }) } From 734f19038d960b5ecac8d0203152284969138f90 Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Fri, 27 Feb 2026 01:30:11 +0530 Subject: [PATCH 14/16] [feat]: [AH-2903]: add support for upload/download for raw files --- registry/app/pkg/generic/local.go | 1 + 1 file changed, 1 insertion(+) diff --git a/registry/app/pkg/generic/local.go b/registry/app/pkg/generic/local.go index 8726862f0d..f2eada3445 100644 --- a/registry/app/pkg/generic/local.go +++ b/registry/app/pkg/generic/local.go @@ -154,6 +154,7 @@ func (c *localRegistry) downloadRawFile( info generic.ArtifactInfo, filePath string, ) (*commons.ResponseHeaders, *storage.FileReader, io.ReadCloser, string, error) { + filePath = "/" + filePath headers, reader, url, err := c.localBase.DownloadRawFile(ctx, info.ArtifactInfo, filePath) if err != nil { return nil, nil, nil, "", fmt.Errorf("failed to download raw file: %w", err) From 21453e7cd0ec018d6cd43b0f32b288dd0662f0f1 Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Fri, 27 Feb 2026 12:06:04 +0530 Subject: [PATCH 15/16] [feat]: [AH-2903]: add support for upload/download for raw files --- registry/app/api/controller/pkg/generic/v2.go | 14 +++++--------- registry/app/pkg/base/wrapper.go | 13 +++++-------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/registry/app/api/controller/pkg/generic/v2.go b/registry/app/api/controller/pkg/generic/v2.go index d3f9da3114..d2501c74f4 100644 --- a/registry/app/api/controller/pkg/generic/v2.go +++ b/registry/app/api/controller/pkg/generic/v2.go @@ -22,7 +22,7 @@ import ( artifact2 "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact" "github.com/harness/gitness/registry/app/pkg" "github.com/harness/gitness/registry/app/pkg/base" - generic2 "github.com/harness/gitness/registry/app/pkg/generic" + genericpkg "github.com/harness/gitness/registry/app/pkg/generic" "github.com/harness/gitness/registry/app/pkg/response" "github.com/harness/gitness/registry/app/pkg/types/generic" registrytypes "github.com/harness/gitness/registry/types" @@ -45,8 +45,7 @@ func (c Controller) DownloadFile( } } - key := base.GetFactoryKey(artifact2.PackageTypeGENERIC, registry.Type) - genericRegistry, _ := base.Factory(key).(generic2.Registry) + genericRegistry := base.GetRegistry(artifact2.PackageTypeGENERIC, registry.Type).(genericpkg.Registry) headers, fileReader, readCloser, redirectURL, err := genericRegistry.DownloadFile(ctx, info, filePath) return &GetArtifactResponse{ BaseResponse: BaseResponse{ @@ -104,8 +103,7 @@ func (c Controller) HeadFile( } } - key := base.GetFactoryKey(artifact2.PackageTypeGENERIC, registry.Type) - genericRegistry, _ := base.Factory(key).(generic2.Registry) + genericRegistry := base.GetRegistry(artifact2.PackageTypeGENERIC, registry.Type).(genericpkg.Registry) headers, err := genericRegistry.HeadFile(ctx, info, filePath) return &HeadArtifactResponse{ BaseResponse: BaseResponse{ @@ -147,8 +145,7 @@ func (c Controller) DeleteFile(ctx context.Context, info generic.ArtifactInfo) * } } - key := base.GetFactoryKey(artifact2.PackageTypeGENERIC, registry.Type) - genericRegistry, _ := base.Factory(key).(generic2.Registry) + genericRegistry := base.GetRegistry(artifact2.PackageTypeGENERIC, registry.Type).(genericpkg.Registry) headers, err := genericRegistry.DeleteFile(ctx, info) return &DeleteArtifactResponse{ BaseResponse: BaseResponse{ @@ -195,8 +192,7 @@ func (c Controller) PutFile( } } - key := base.GetFactoryKey(artifact2.PackageTypeGENERIC, registry.Type) - genericRegistry, _ := base.Factory(key).(generic2.Registry) + genericRegistry := base.GetRegistry(artifact2.PackageTypeGENERIC, registry.Type).(genericpkg.Registry) headers, sha256, err := genericRegistry.PutFile(ctx, info, reader, contentType) return &PutArtifactResponse{ BaseResponse: BaseResponse{ diff --git a/registry/app/pkg/base/wrapper.go b/registry/app/pkg/base/wrapper.go index 2e11153e9c..d0f4e4a435 100644 --- a/registry/app/pkg/base/wrapper.go +++ b/registry/app/pkg/base/wrapper.go @@ -201,23 +201,20 @@ func factory(key string) pkg.Artifact { return TypeRegistry[key] } -func Factory(key string) pkg.Artifact { - return factory(key) -} - func getFactoryKey(packageType artifact.PackageType, registryType artifact.RegistryType) string { return string(packageType) + ":" + string(registryType) } -func GetFactoryKey(packageType artifact.PackageType, registryType artifact.RegistryType) string { - return getFactoryKey(packageType, registryType) -} - func GetArtifactRegistry(registry registrytypes.Registry) pkg.Artifact { key := getFactoryKey(registry.PackageType, registry.Type) return factory(key) } +func GetRegistry(packageType artifact.PackageType, registryType artifact.RegistryType) pkg.Artifact { + key := getFactoryKey(packageType, registryType) + return factory(key) +} + func filterRegs( ctx context.Context, registryDao store.RegistryRepository, From 22e0c56495c98098150df2cdf0535ccfbfb56f61 Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Fri, 27 Feb 2026 12:31:45 +0530 Subject: [PATCH 16/16] [feat]: [AH-2903]: add support for upload/download for raw files --- registry/app/api/controller/pkg/generic/v2.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/registry/app/api/controller/pkg/generic/v2.go b/registry/app/api/controller/pkg/generic/v2.go index d2501c74f4..2be432b6a9 100644 --- a/registry/app/api/controller/pkg/generic/v2.go +++ b/registry/app/api/controller/pkg/generic/v2.go @@ -45,6 +45,7 @@ func (c Controller) DownloadFile( } } + //nolint:errcheck genericRegistry := base.GetRegistry(artifact2.PackageTypeGENERIC, registry.Type).(genericpkg.Registry) headers, fileReader, readCloser, redirectURL, err := genericRegistry.DownloadFile(ctx, info, filePath) return &GetArtifactResponse{ @@ -103,6 +104,7 @@ func (c Controller) HeadFile( } } + //nolint:errcheck genericRegistry := base.GetRegistry(artifact2.PackageTypeGENERIC, registry.Type).(genericpkg.Registry) headers, err := genericRegistry.HeadFile(ctx, info, filePath) return &HeadArtifactResponse{ @@ -145,6 +147,7 @@ func (c Controller) DeleteFile(ctx context.Context, info generic.ArtifactInfo) * } } + //nolint:errcheck genericRegistry := base.GetRegistry(artifact2.PackageTypeGENERIC, registry.Type).(genericpkg.Registry) headers, err := genericRegistry.DeleteFile(ctx, info) return &DeleteArtifactResponse{ @@ -192,6 +195,7 @@ func (c Controller) PutFile( } } + //nolint:errcheck genericRegistry := base.GetRegistry(artifact2.PackageTypeGENERIC, registry.Type).(genericpkg.Registry) headers, sha256, err := genericRegistry.PutFile(ctx, info, reader, contentType) return &PutArtifactResponse{