feat:[AH-1083]: Implementation of NPM registry (#3608)

* feat:[AH-1083]: fix checks
* feat:[AH-1083]: fix checks
* feat:[AH-1083]: merge conficts
* feat:[AH-1083]: merge conficts
* feat:[AH-1083]: merge conficts
* feat:[AH-1083]: fix checks
* feat:[AH-1083]: fix checks
* feat:[AH-1083]: fix checks
* feat:[AH-1083]: fix checks
* feat:[AH-1083]: fix checks
* feat:[AH-1083]: fix checks
* feat:[AH-1083]: fix checks
* feat:[AH-1083]: review changes
* feat:[AH-1083]: review changes
* feat:[AH-1083]: review changes
* feat:[AH-1083]: fix download_count
* feat:[AH-1083]: merge conflicst
* feat:[AH-1083]: merge conflicst
* feat:[AH-1083]: merge conflicst
* feat:[AH-1083]: merge conflicst
* feat:[AH-1083]: bug fixes
* feat:[AH-1083]: bug fixes
* feat:[AH-1083]: bug fixes
* feat:[AH-1083]: review changes
* feat:[AH-1083]: review changes
* Merge branch 'main' into AH-1083
* feat:[AH-1083]: add get size and set size methods for metadata
* feat:[AH-1083]: remove remote implementation
* feat:[AH-1083]: Implementation of NPM registry
This commit is contained in:
Sourabh Awashti 2025-04-09 21:19:49 +00:00 committed by Harness
parent e5eafee82c
commit 105afe37db
69 changed files with 3333 additions and 35 deletions

View File

@ -416,4 +416,4 @@ issues:
- path: "^registry/app/storage/blobStore.go"
linters: [ gosec ]
- path: "^registry/app/metadata/nuget/metadata.go"
linters: [ tagliatelle, lll ]
linters: [ tagliatelle, lll ]

View File

@ -0,0 +1 @@
DROP TABLE package_tags;

View File

@ -0,0 +1,12 @@
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE package_tags (
package_tag_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
package_tag_name TEXT NOT NULL,
package_tag_artifact_id BIGINT REFERENCES artifacts (artifact_id) ON DELETE CASCADE,
package_tag_created_at BIGINT NOT NULL,
package_tag_created_by INTEGER NOT NULL,
package_tag_updated_at BIGINT NOT NULL,
package_tag_updated_by INTEGER NOT NULL,
CONSTRAINT unique_package_tag UNIQUE (package_tag_name, package_tag_artifact_id)
);

View File

@ -0,0 +1 @@
DROP TABLE package_tags;

View File

@ -0,0 +1,10 @@
CREATE TABLE package_tags (
package_tag_id TEXT PRIMARY KEY,
package_tag_name TEXT NOT NULL,
package_tag_artifact_id INTEGER REFERENCES artifacts (artifact_id) ON DELETE CASCADE,
package_tag_created_at INTEGER NOT NULL,
package_tag_created_by INTEGER NOT NULL,
package_tag_updated_at INTEGER NOT NULL,
package_tag_updated_by INTEGER NOT NULL,
CONSTRAINT unique_package_tag UNIQUE (package_tag_name, package_tag_artifact_id)
);

View File

@ -80,7 +80,7 @@ type Provider interface {
GetAPIProto(ctx context.Context) string
RegistryURL(ctx context.Context, params ...string) string
PackageURL(ctx context.Context, params ...string) string
PackageURL(ctx context.Context, regRef string, pkgType string, params ...string) string
GetUIBaseURL(ctx context.Context, params ...string) string
// GenerateUIRegistryURL returns the url for the UI screen of a registry.
@ -259,7 +259,7 @@ func (p *provider) RegistryURL(_ context.Context, params ...string) string {
return strings.TrimRight(u.String(), "/")
}
func (p *provider) PackageURL(_ context.Context, params ...string) string {
func (p *provider) PackageURL(_ context.Context, regRef string, pkgType string, params ...string) string {
u, err := url.Parse(p.registryURL.String())
if err != nil {
log.Warn().Msgf("failed to parse registry url: %v", err)
@ -268,6 +268,8 @@ func (p *provider) PackageURL(_ context.Context, params ...string) string {
segments := []string{u.Path}
segments = append(segments, "pkg")
segments = append(segments, regRef)
segments = append(segments, pkgType)
segments = append(segments, params...)
fullPath := path.Join(segments...)
u.Path = fullPath

View File

@ -126,6 +126,7 @@ import (
"github.com/harness/gitness/lock"
"github.com/harness/gitness/pubsub"
api2 "github.com/harness/gitness/registry/app/api"
npm2 "github.com/harness/gitness/registry/app/api/controller/pkg/npm"
nuget2 "github.com/harness/gitness/registry/app/api/controller/pkg/nuget"
python2 "github.com/harness/gitness/registry/app/api/controller/pkg/python"
"github.com/harness/gitness/registry/app/api/router"
@ -136,6 +137,7 @@ import (
"github.com/harness/gitness/registry/app/pkg/filemanager"
"github.com/harness/gitness/registry/app/pkg/generic"
"github.com/harness/gitness/registry/app/pkg/maven"
"github.com/harness/gitness/registry/app/pkg/npm"
"github.com/harness/gitness/registry/app/pkg/nuget"
"github.com/harness/gitness/registry/app/pkg/python"
database2 "github.com/harness/gitness/registry/app/store/database"
@ -530,8 +532,9 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
genericController := generic.ControllerProvider(spaceStore, authorizer, fileManager, genericDBStore, transactor)
genericHandler := api2.NewGenericHandlerProvider(spaceStore, genericController, tokenStore, controller, authenticator, provider, authorizer)
handler3 := router.GenericHandlerProvider(genericHandler)
packagesHandler := api2.NewPackageHandlerProvider(registryRepository, spaceStore, tokenStore, controller, authenticator, provider, authorizer)
localBase := base.LocalBaseProvider(registryRepository, fileManager, transactor, imageRepository, artifactRepository)
packagesHandler := api2.NewPackageHandlerProvider(registryRepository, downloadStatRepository, spaceStore, tokenStore, controller, authenticator, provider, authorizer)
packageTagRepository := database2.ProvidePackageTagDao(db)
localBase := base.LocalBaseProvider(registryRepository, fileManager, transactor, imageRepository, artifactRepository, nodesRepository, packageTagRepository)
pythonLocalRegistry := python.LocalRegistryProvider(localBase, fileManager, upstreamProxyConfigRepository, transactor, registryRepository, imageRepository, artifactRepository, provider)
localRegistryHelper := python.LocalRegistryHelperProvider(pythonLocalRegistry, localBase)
proxy := python.ProxyProvider(upstreamProxyConfigRepository, registryRepository, imageRepository, artifactRepository, fileManager, transactor, provider, spaceFinder, secretService, localRegistryHelper)
@ -540,7 +543,11 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
nugetLocalRegistry := nuget.LocalRegistryProvider(localBase, fileManager, upstreamProxyConfigRepository, transactor, registryRepository, imageRepository, artifactRepository, provider)
nugetController := nuget2.ControllerProvider(upstreamProxyConfigRepository, registryRepository, imageRepository, artifactRepository, fileManager, transactor, provider, nugetLocalRegistry)
nugetHandler := api2.NewNugetHandlerProvider(nugetController, packagesHandler)
handler4 := router.PackageHandlerProvider(packagesHandler, mavenHandler, genericHandler, pythonHandler, nugetHandler)
npmLocalRegistry := npm.LocalRegistryProvider(localBase, fileManager, upstreamProxyConfigRepository, transactor, packageTagRepository, registryRepository, imageRepository, artifactRepository, nodesRepository, provider)
npmProxy := npm.ProxyProvider(upstreamProxyConfigRepository, registryRepository, imageRepository, artifactRepository, fileManager, transactor, provider)
npmController := npm2.ControllerProvider(upstreamProxyConfigRepository, registryRepository, imageRepository, artifactRepository, fileManager, transactor, downloadStatRepository, provider, npmLocalRegistry, npmProxy)
npmHandler := api2.NewNPMHandlerProvider(npmController, packagesHandler)
handler4 := router.PackageHandlerProvider(packagesHandler, mavenHandler, genericHandler, pythonHandler, nugetHandler, npmHandler)
appRouter := router.AppRouterProvider(registryOCIHandler, apiHandler, handler2, handler3, handler4)
sender := usage.ProvideMediator(ctx, config, spaceFinder, usageMetricStore)
routerRouter := router2.ProvideRouter(ctx, config, authenticator, repoController, reposettingsController, executionController, logsController, spaceController, pipelineController, secretController, triggerController, connectorController, templateController, pluginController, pullreqController, webhookController, githookController, gitInterface, serviceaccountController, controller, principalController, usergroupController, checkController, systemController, uploadController, keywordsearchController, infraproviderController, gitspaceController, migrateController, provider, openapiService, appRouter, sender, lfsController)

View File

@ -123,6 +123,8 @@ func toPackageType(packageTypeStr string) (artifactapi.PackageType, error) {
return artifactapi.PackageTypeMAVEN, nil
case string(artifactapi.PackageTypePYTHON):
return artifactapi.PackageTypePYTHON, nil
case string(artifactapi.PackageTypeNPM):
return artifactapi.PackageTypeNPM, nil
default:
return "", errors.New("invalid package type")
}
@ -497,6 +499,27 @@ func GetPythonArtifactDetail(
return *artifactDetail
}
func GetNPMArtifactDetail(
image *types.Image, artifact *types.Artifact,
metadata map[string]interface{},
) artifactapi.ArtifactDetail {
createdAt := GetTimeInMs(artifact.CreatedAt)
modifiedAt := GetTimeInMs(artifact.UpdatedAt)
artifactDetail := &artifactapi.ArtifactDetail{
CreatedAt: &createdAt,
ModifiedAt: &modifiedAt,
Name: &image.Name,
Version: artifact.Version,
}
err := artifactDetail.FromNpmArtifactDetailConfig(artifactapi.NpmArtifactDetailConfig{
Metadata: &metadata,
})
if err != nil {
return artifactapi.ArtifactDetail{}
}
return *artifactDetail
}
func GetArtifactSummary(artifact types.ImageMetadata) *artifactapi.ArtifactSummaryResponseJSONResponse {
createdAt := GetTimeInMs(artifact.CreatedAt)
modifiedAt := GetTimeInMs(artifact.ModifiedAt)

View File

@ -98,6 +98,7 @@ func (c *APIController) GetArtifactDetails(
var artifactDetails artifact.ArtifactDetail
// FIXME: Arvind: Unify the metadata structure to avoid this type checking
//nolint:exhaustive
switch registry.PackageType {
case artifact.PackageTypeMAVEN:
var metadata metadata.MavenMetadata
@ -132,6 +133,18 @@ func (c *APIController) GetArtifactDetails(
}, nil
}
artifactDetails = GetPythonArtifactDetail(img, art, result)
case artifact.PackageTypeNPM:
var result map[string]interface{}
err := json.Unmarshal(art.Metadata, &result)
if err != nil {
return artifact.GetArtifactDetails500JSONResponse{
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
),
}, nil
}
artifactDetails = GetNPMArtifactDetail(img, art, result)
case artifact.PackageTypeDOCKER:
case artifact.PackageTypeHELM:
default:

View File

@ -133,7 +133,7 @@ func (c *APIController) GetArtifactFiles(
//nolint:exhaustive
switch registry.PackageType {
case artifact.PackageTypeGENERIC, artifact.PackageTypeMAVEN, artifact.PackageTypePYTHON:
case artifact.PackageTypeGENERIC, artifact.PackageTypeMAVEN, artifact.PackageTypePYTHON, artifact.PackageTypeNPM:
return artifact.GetArtifactFiles200JSONResponse{
FileDetailResponseJSONResponse: *GetAllArtifactFilesResponse(
fileMetadataList, count, reqInfo.pageNumber, reqInfo.limit, registryURL, img.Name, art.Version,

View File

@ -117,6 +117,8 @@ func (c *APIController) GenerateClientSetupDetails(
return c.generateGenericClientSetupDetail(ctx, blankString, registryRef, image, tag, registryType)
case string(artifact.PackageTypePYTHON):
return c.generatePythonClientSetupDetail(ctx, registryRef, username, image, tag, registryType)
case string(artifact.PackageTypeNPM):
return c.generateNpmClientSetupDetail(ctx, registryRef, username, image, tag, registryType)
case string(artifact.PackageTypeDOCKER):
return c.generateDockerClientSetupDetail(ctx, blankString, loginUsernameLabel, loginUsernameValue,
loginPasswordLabel, registryType,
@ -886,6 +888,110 @@ func (c *APIController) generatePythonClientSetupDetail(
}
}
func (c *APIController) generateNpmClientSetupDetail(
ctx context.Context,
registryRef string,
username string,
image *artifact.ArtifactParam,
tag *artifact.VersionParam,
registryType artifact.RegistryType,
) *artifact.ClientSetupDetailsResponseJSONResponse {
staticStepType := artifact.ClientSetupStepTypeStatic
generateTokenType := artifact.ClientSetupStepTypeGenerateToken
// Authentication section
section1 := artifact.ClientSetupSection{
Header: stringPtr("Configure Authentication"),
}
_ = section1.FromClientSetupStepConfig(artifact.ClientSetupStepConfig{
Steps: &[]artifact.ClientSetupStep{
{
Header: stringPtr("Create or update your ~/.npmrc file with the following content:"),
Type: &staticStepType,
Commands: &[]artifact.ClientSetupStepCommand{
{
Value: stringPtr("npm set registry https:<REGISTRY_URL>/\n\n" +
"npm set <REGISTRY_URL>/:_authToken <TOKEN>"),
},
},
},
{
Header: stringPtr("Generate an identity token for authentication"),
Type: &generateTokenType,
},
},
})
// Publish section
section2 := artifact.ClientSetupSection{
Header: stringPtr("Publish Package"),
}
_ = section2.FromClientSetupStepConfig(artifact.ClientSetupStepConfig{
Steps: &[]artifact.ClientSetupStep{
{
Header: stringPtr("Build and publish your package:"),
Type: &staticStepType,
Commands: &[]artifact.ClientSetupStepCommand{
{
Value: stringPtr("npm run build\n"),
},
{
Value: stringPtr("npm publish"),
},
},
},
},
})
// Install section
section3 := artifact.ClientSetupSection{
Header: stringPtr("Install Package"),
}
_ = section3.FromClientSetupStepConfig(artifact.ClientSetupStepConfig{
Steps: &[]artifact.ClientSetupStep{
{
Header: stringPtr("Install a package using npm"),
Type: &staticStepType,
Commands: &[]artifact.ClientSetupStepCommand{
{
Value: stringPtr("npm install <ARTIFACT_NAME>@<VERSION>"),
},
},
},
},
})
sections := []artifact.ClientSetupSection{
section1,
section2,
section3,
}
if registryType == artifact.RegistryTypeUPSTREAM {
sections = []artifact.ClientSetupSection{
section1,
section3,
}
}
clientSetupDetails := artifact.ClientSetupDetails{
MainHeader: "NPM Client Setup",
SecHeader: "Follow these instructions to install/use NPM packages from this registry.",
Sections: sections,
}
registryURL := c.URLProvider.PackageURL(ctx, registryRef, "npm")
registryURL = strings.TrimPrefix(registryURL, "http:")
registryURL = strings.TrimPrefix(registryURL, "https:")
c.replacePlaceholders(ctx, &clientSetupDetails.Sections, username, registryRef, image, tag, registryURL, "",
string(artifact.PackageTypeNPM))
return &artifact.ClientSetupDetailsResponseJSONResponse{
Data: clientSetupDetails,
Status: artifact.StatusSUCCESS,
}
}
func (c *APIController) replacePlaceholders(
ctx context.Context,
clientSetupSections *[]artifact.ClientSetupSection,

View File

@ -116,6 +116,7 @@ var validPackageTypes = []string{
string(a.PackageTypeGENERIC),
string(a.PackageTypeMAVEN),
string(a.PackageTypePYTHON),
string(a.PackageTypeNPM),
}
var validUpstreamSources = []string{
@ -371,6 +372,8 @@ func GetPullCommand(
return GetGenericArtifactFileDownloadCommand(registryURL, image, tag, "<FILENAME>")
case string(a.PackageTypePYTHON):
return GetPythonDownloadCommand(image, tag)
case string(a.PackageTypeNPM):
return GetNPMDownloadCommand(image, tag)
default:
return ""
}
@ -387,6 +390,22 @@ func GetHelmPullCommand(image string, tag string, registryURL string) string {
return "helm pull oci://" + GetRepoURLWithoutProtocol(registryURL) + "/" + image + " --version " + tag
}
func GetNPMDownloadCommand(artifact, version string) string {
downloadCommand := "npm install <ARTIFACT>@<VERSION> "
// Replace the placeholders with the actual values
replacements := map[string]string{
"<ARTIFACT>": artifact,
"<VERSION>": version,
}
for placeholder, value := range replacements {
downloadCommand = strings.ReplaceAll(downloadCommand, placeholder, value)
}
return downloadCommand
}
func GetPythonDownloadCommand(artifact, version string) string {
downloadCommand := "pip install <ARTIFACT>==<VERSION> "

View File

@ -0,0 +1,66 @@
// 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.
// nolint:goheader
package npm
import (
"context"
"fmt"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/base"
npm2 "github.com/harness/gitness/registry/app/pkg/npm"
"github.com/harness/gitness/registry/app/pkg/response"
"github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/types"
)
func (c *controller) AddTag(
ctx context.Context,
info npm.ArtifactInfo,
) *ListTagResponse {
f := func(registry types.Registry, a pkg.Artifact) response.Response {
info.RegIdentifier = registry.Name
info.RegistryID = registry.ID
npmRegistry, ok := a.(npm2.Registry)
if !ok {
return &ListTagResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid registry type: expected npm.Registry")},
}
}
tags, err := npmRegistry.AddTag(ctx, info)
if err != nil {
return &ListTagResponse{
BaseResponse: BaseResponse{Error: err},
}
}
return &ListTagResponse{
Tags: tags,
}
}
result, err := base.NoProxyWrapper(ctx, c.registryDao, f, info)
if err != nil {
return &ListTagResponse{
BaseResponse: BaseResponse{Error: err},
}
}
artifactResponse, ok := result.(*ListTagResponse)
if !ok {
return &ListTagResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid response type: expected ListTagResponse")},
}
}
return artifactResponse
}

View File

@ -0,0 +1,110 @@
// 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 npm
import (
"context"
"io"
urlprovider "github.com/harness/gitness/app/url"
"github.com/harness/gitness/registry/app/pkg/filemanager"
npm2 "github.com/harness/gitness/registry/app/pkg/npm"
"github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/app/store"
"github.com/harness/gitness/store/database/dbtx"
)
// Controller handles PyPI package operations.
type controller struct {
fileManager filemanager.FileManager
proxyStore store.UpstreamProxyConfigRepository
tx dbtx.Transactor
registryDao store.RegistryRepository
imageDao store.ImageRepository
artifactDao store.ArtifactRepository
urlProvider urlprovider.Provider
downloadStat store.DownloadStatRepository
local npm2.LocalRegistry
proxy npm2.Proxy
}
type Controller interface {
UploadPackageFile(
ctx context.Context,
info npm.ArtifactInfo,
file io.ReadCloser,
) *PutArtifactResponse
DownloadPackageFile(
ctx context.Context,
info npm.ArtifactInfo,
) *GetArtifactResponse
GetPackageMetadata(ctx context.Context, info npm.ArtifactInfo) *GetMetadataResponse
HeadPackageFileByName(ctx context.Context, info npm.ArtifactInfo) *HeadMetadataResponse
ListTags(
ctx context.Context,
info npm.ArtifactInfo,
) *ListTagResponse
AddTag(
ctx context.Context,
info npm.ArtifactInfo,
) *ListTagResponse
DeleteTag(
ctx context.Context,
info npm.ArtifactInfo,
) *ListTagResponse
DeleteVersion(
ctx context.Context,
info npm.ArtifactInfo,
) *DeleteEntityResponse
DeletePackage(
ctx context.Context,
info npm.ArtifactInfo,
) *DeleteEntityResponse
}
// NewController creates a new PyPI controller.
func NewController(
proxyStore store.UpstreamProxyConfigRepository,
registryDao store.RegistryRepository,
imageDao store.ImageRepository,
artifactDao store.ArtifactRepository,
fileManager filemanager.FileManager,
downloadStatsDao store.DownloadStatRepository,
tx dbtx.Transactor,
urlProvider urlprovider.Provider,
local npm2.LocalRegistry,
proxy npm2.Proxy,
) Controller {
return &controller{
proxyStore: proxyStore,
registryDao: registryDao,
imageDao: imageDao,
artifactDao: artifactDao,
downloadStat: downloadStatsDao,
fileManager: fileManager,
tx: tx,
urlProvider: urlProvider,
local: local,
proxy: proxy,
}
}

View File

@ -0,0 +1,60 @@
// 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 npm
import (
"context"
"fmt"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/base"
npm2 "github.com/harness/gitness/registry/app/pkg/npm"
"github.com/harness/gitness/registry/app/pkg/response"
"github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/types"
)
func (c *controller) DeletePackage(
ctx context.Context,
info npm.ArtifactInfo,
) *DeleteEntityResponse {
f := func(registry types.Registry, a pkg.Artifact) response.Response {
info.RegIdentifier = registry.Name
info.RegistryID = registry.ID
npmRegistry, ok := a.(npm2.Registry)
if !ok {
return &DeleteEntityResponse{
Error: fmt.Errorf("invalid registry type: expected npm.Registry"),
}
}
err := npmRegistry.DeletePackage(ctx, info)
if err != nil {
return &DeleteEntityResponse{Error: err}
}
return &DeleteEntityResponse{}
}
result, err := base.NoProxyWrapper(ctx, c.registryDao, f, info)
if err != nil {
return &DeleteEntityResponse{Error: err}
}
artifactResponse, ok := result.(*DeleteEntityResponse)
if !ok {
return &DeleteEntityResponse{
Error: fmt.Errorf("invalid response type: expected DeleteEntityResponse"),
}
}
return artifactResponse
}

View File

@ -0,0 +1,66 @@
// 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 npm
import (
"context"
"fmt"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/base"
npm2 "github.com/harness/gitness/registry/app/pkg/npm"
"github.com/harness/gitness/registry/app/pkg/response"
"github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/types"
)
func (c *controller) DeleteTag(
ctx context.Context,
info npm.ArtifactInfo,
) *ListTagResponse {
f := func(registry types.Registry, a pkg.Artifact) response.Response {
info.RegIdentifier = registry.Name
info.RegistryID = registry.ID
npmRegistry, ok := a.(npm2.Registry)
if !ok {
return &ListTagResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid registry type: expected npm.Registry")},
}
}
tags, err := npmRegistry.DeleteTag(ctx, info)
if err != nil {
return &ListTagResponse{
BaseResponse: BaseResponse{Error: err},
}
}
return &ListTagResponse{
Tags: tags,
}
}
result, err := base.NoProxyWrapper(ctx, c.registryDao, f, info)
if err != nil {
return &ListTagResponse{
BaseResponse: BaseResponse{Error: err},
}
}
artifactResponse, ok := result.(*ListTagResponse)
if !ok {
return &ListTagResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid response type: expected ListTagResponse")},
}
}
return artifactResponse
}

View File

@ -0,0 +1,60 @@
// 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 npm
import (
"context"
"fmt"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/base"
npm2 "github.com/harness/gitness/registry/app/pkg/npm"
"github.com/harness/gitness/registry/app/pkg/response"
"github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/types"
)
func (c *controller) DeleteVersion(
ctx context.Context,
info npm.ArtifactInfo,
) *DeleteEntityResponse {
f := func(registry types.Registry, a pkg.Artifact) response.Response {
info.RegIdentifier = registry.Name
info.RegistryID = registry.ID
npmRegistry, ok := a.(npm2.Registry)
if !ok {
return &DeleteEntityResponse{
Error: fmt.Errorf("invalid registry type: expected npm.Registry"),
}
}
err := npmRegistry.DeleteVersion(ctx, info)
if err != nil {
return &DeleteEntityResponse{Error: err}
}
return &DeleteEntityResponse{}
}
result, err := base.NoProxyWrapper(ctx, c.registryDao, f, info)
if err != nil {
return &DeleteEntityResponse{Error: err}
}
artifactResponse, ok := result.(*DeleteEntityResponse)
if !ok {
return &DeleteEntityResponse{
Error: fmt.Errorf("invalid response type: expected DeleteEntityResponse"),
}
}
return artifactResponse
}

View File

@ -0,0 +1,64 @@
// 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 npm
import (
"context"
"fmt"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/base"
commons2 "github.com/harness/gitness/registry/app/pkg/commons"
npm2 "github.com/harness/gitness/registry/app/pkg/npm"
"github.com/harness/gitness/registry/app/pkg/response"
"github.com/harness/gitness/registry/app/pkg/types/npm"
registrytypes "github.com/harness/gitness/registry/types"
)
func (c *controller) DownloadPackageFile(
ctx context.Context,
info npm.ArtifactInfo,
) *GetArtifactResponse {
f := func(registry registrytypes.Registry, a pkg.Artifact) response.Response {
info.RegIdentifier = registry.Name
info.RegistryID = registry.ID
npmRegistry, ok := a.(npm2.Registry)
if !ok {
return &GetArtifactResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid registry type: expected python.Registry")},
}
}
headers, fileReader, redirectURL, err := npmRegistry.DownloadPackageFile(ctx, info)
return &GetArtifactResponse{
BaseResponse: BaseResponse{Error: err, ResponseHeaders: headers},
RedirectURL: redirectURL,
Body: fileReader,
}
}
result, err := base.ProxyWrapper(ctx, c.registryDao, f, info)
if !commons2.IsEmpty(err) {
return &GetArtifactResponse{
BaseResponse: BaseResponse{Error: err},
}
}
getResponse, ok := result.(*GetArtifactResponse)
if !ok {
return &GetArtifactResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid response type: expected GetArtifactResponse")},
}
}
return getResponse
}

View File

@ -0,0 +1,64 @@
// 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 npm
import (
"context"
"fmt"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/base"
commons2 "github.com/harness/gitness/registry/app/pkg/commons"
npm2 "github.com/harness/gitness/registry/app/pkg/npm"
"github.com/harness/gitness/registry/app/pkg/response"
"github.com/harness/gitness/registry/app/pkg/types/npm"
registrytypes "github.com/harness/gitness/registry/types"
)
func (c *controller) DownloadPackageFileByName(
ctx context.Context,
info npm.ArtifactInfo,
) *GetArtifactResponse {
f := func(registry registrytypes.Registry, a pkg.Artifact) response.Response {
info.RegIdentifier = registry.Name
info.RegistryID = registry.ID
npmRegistry, ok := a.(npm2.Registry)
if !ok {
return &GetArtifactResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid registry type: expected npm.Registry")},
}
}
headers, fileReader, redirectURL, errs := npmRegistry.DownloadPackageFile(ctx, info)
return &GetArtifactResponse{
BaseResponse: BaseResponse{Error: errs, ResponseHeaders: headers},
RedirectURL: redirectURL,
Body: fileReader,
}
}
result, err := base.ProxyWrapper(ctx, c.registryDao, f, info)
if commons2.IsEmpty(err) {
return &GetArtifactResponse{
BaseResponse: BaseResponse{Error: err},
}
}
getResponse, ok := result.(*GetArtifactResponse)
if !ok {
return &GetArtifactResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid response type: expected GetArtifactResponse")},
}
}
return getResponse
}

View File

@ -0,0 +1,67 @@
// 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 npm
import (
"context"
"fmt"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/base"
commons2 "github.com/harness/gitness/registry/app/pkg/commons"
npm2 "github.com/harness/gitness/registry/app/pkg/npm"
"github.com/harness/gitness/registry/app/pkg/response"
"github.com/harness/gitness/registry/app/pkg/types/npm"
registrytypes "github.com/harness/gitness/registry/types"
)
func (c *controller) HeadPackageFileByName(
ctx context.Context,
info npm.ArtifactInfo,
) *HeadMetadataResponse {
f := func(registry registrytypes.Registry, a pkg.Artifact) response.Response {
info.RegIdentifier = registry.Name
info.RegistryID = registry.ID
npmRegistry, ok := a.(npm2.Registry)
if !ok {
return &HeadMetadataResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid Registry type: expected npm.Registry")},
}
}
exist, err := npmRegistry.HeadPackageMetadata(ctx, info)
if !commons2.IsEmpty(err) {
return &HeadMetadataResponse{
BaseResponse: BaseResponse{Error: err},
}
}
return &HeadMetadataResponse{
Exists: exist}
}
result, err := base.ProxyWrapper(ctx, c.registryDao, f, info)
if !commons2.IsEmpty(err) {
return &HeadMetadataResponse{
BaseResponse: BaseResponse{Error: err},
}
}
getResponse, ok := result.(*HeadMetadataResponse)
if !ok {
return &HeadMetadataResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid response type: expected GetArtifactResponse")},
}
}
return getResponse
}

View File

@ -0,0 +1,67 @@
// 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 npm
import (
"context"
"fmt"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/base"
commons2 "github.com/harness/gitness/registry/app/pkg/commons"
npm2 "github.com/harness/gitness/registry/app/pkg/npm"
"github.com/harness/gitness/registry/app/pkg/response"
"github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/types"
)
func (c *controller) ListTags(
ctx context.Context,
info npm.ArtifactInfo,
) *ListTagResponse {
f := func(registry types.Registry, a pkg.Artifact) response.Response {
info.RegIdentifier = registry.Name
info.RegistryID = registry.ID
npmRegistry, ok := a.(npm2.Registry)
if !ok {
return &ListTagResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid registry type: expected npm.Registry")},
}
}
tags, err := npmRegistry.ListTags(ctx, info)
if !commons2.IsEmpty(err) {
return &ListTagResponse{
BaseResponse: BaseResponse{Error: err},
}
}
return &ListTagResponse{
Tags: tags,
}
}
result, err := base.NoProxyWrapper(ctx, c.registryDao, f, info)
if !commons2.IsEmpty(err) {
return &ListTagResponse{
BaseResponse: BaseResponse{Error: err},
}
}
artifactResponse, ok := result.(*ListTagResponse)
if !ok {
return &ListTagResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid response type: expected ListTagResponse")},
}
}
return artifactResponse
}

View File

@ -0,0 +1,71 @@
// 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 npm
import (
"context"
"fmt"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/base"
"github.com/harness/gitness/registry/app/pkg/commons"
npm2 "github.com/harness/gitness/registry/app/pkg/npm"
"github.com/harness/gitness/registry/app/pkg/response"
"github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/types"
)
// GetPackageMetadata fetches the metadata of a package with the given artifact info.
//
// This is a proxy function to Registry.UploadPackageFile, which is used to fetch the metadata
// of a package. The function is used by the API handler.
//
// The function takes a context and an artifact info as parameters and returns a GetPackageMetadata
// containing the metadata of the package. If an error occurs during the operation, the
// function returns an error as well.
func (c *controller) GetPackageMetadata(
ctx context.Context,
info npm.ArtifactInfo,
) *GetMetadataResponse {
f := func(registry types.Registry, a pkg.Artifact) response.Response {
info.RegIdentifier = registry.Name
info.RegistryID = registry.ID
npmRegistry, ok := a.(npm2.Registry)
if !ok {
return &GetMetadataResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid registry type: expected npm.Registry")},
}
}
metadata, err := npmRegistry.GetPackageMetadata(ctx, info)
return &GetMetadataResponse{
BaseResponse: BaseResponse{Error: err},
PackageMetadata: metadata,
}
}
result, err := base.ProxyWrapper(ctx, c.registryDao, f, info)
if !commons.IsEmpty(err) {
return &GetMetadataResponse{
BaseResponse: BaseResponse{Error: err},
}
}
metadataResponse, ok := result.(*GetMetadataResponse)
if !ok {
return &GetMetadataResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid response type: expected GetMetadataResponse, got %T", result)},
}
}
return metadataResponse
}

View File

@ -0,0 +1,83 @@
// 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 npm
import (
"io"
npm2 "github.com/harness/gitness/registry/app/metadata/npm"
"github.com/harness/gitness/registry/app/pkg/commons"
"github.com/harness/gitness/registry/app/storage"
)
type GetMetadataResponse struct {
BaseResponse
PackageMetadata npm2.PackageMetadata
}
func (r *GetMetadataResponse) GetError() error {
return r.Error
}
type ListTagResponse struct {
Tags map[string]string
BaseResponse
}
func (r *ListTagResponse) GetError() error {
return r.Error
}
type BaseResponse struct {
Error error
ResponseHeaders *commons.ResponseHeaders
}
type GetArtifactResponse struct {
BaseResponse
RedirectURL string
Body *storage.FileReader
ReadCloser io.ReadCloser
}
func (r *GetArtifactResponse) GetError() error {
return r.Error
}
type PutArtifactResponse struct {
Sha256 string
BaseResponse
}
func (r *PutArtifactResponse) GetError() error {
return r.Error
}
type HeadMetadataResponse struct {
BaseResponse
Exists bool
}
func (r *HeadMetadataResponse) GetError() error {
return r.Error
}
type DeleteEntityResponse struct {
Error error
}
func (r *DeleteEntityResponse) GetError() error {
return r.Error
}

View File

@ -0,0 +1,71 @@
// 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 npm
import (
"context"
"fmt"
"io"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/base"
"github.com/harness/gitness/registry/app/pkg/commons"
npm2 "github.com/harness/gitness/registry/app/pkg/npm"
"github.com/harness/gitness/registry/app/pkg/response"
"github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/types"
)
// UploadPackageFile FIXME: Extract this upload function for all types of packageTypes
// uploads the package file to the storage.
func (c *controller) UploadPackageFile(
ctx context.Context,
info npm.ArtifactInfo,
file io.ReadCloser,
) *PutArtifactResponse {
f := func(registry types.Registry, a pkg.Artifact) response.Response {
info.RegIdentifier = registry.Name
info.RegistryID = registry.ID
npmRegistry, ok := a.(npm2.Registry)
if !ok {
return &PutArtifactResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid registry type: expected npm.Registry")},
}
}
headers, sha256, err := npmRegistry.UploadPackageFile(ctx, info, file)
if !commons.IsEmpty(err) {
return &PutArtifactResponse{
BaseResponse: BaseResponse{Error: err},
}
}
return &PutArtifactResponse{
BaseResponse: BaseResponse{ResponseHeaders: headers},
Sha256: sha256}
}
result, err := base.NoProxyWrapper(ctx, c.registryDao, f, info)
if !commons.IsEmpty(err) {
return &PutArtifactResponse{
BaseResponse: BaseResponse{Error: err},
}
}
artifactResponse, ok := result.(*PutArtifactResponse)
if !ok {
return &PutArtifactResponse{
BaseResponse: BaseResponse{Error: fmt.Errorf("invalid response type: expected PutArtifactResponse")},
}
}
return artifactResponse
}

View File

@ -0,0 +1,43 @@
// 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 npm
import (
urlprovider "github.com/harness/gitness/app/url"
"github.com/harness/gitness/registry/app/pkg/filemanager"
npm2 "github.com/harness/gitness/registry/app/pkg/npm"
"github.com/harness/gitness/registry/app/store"
"github.com/harness/gitness/store/database/dbtx"
"github.com/google/wire"
)
func ControllerProvider(
proxyStore store.UpstreamProxyConfigRepository,
registryDao store.RegistryRepository,
imageDao store.ImageRepository,
artifactDao store.ArtifactRepository,
fileManager filemanager.FileManager,
tx dbtx.Transactor,
downloadStatsDao store.DownloadStatRepository,
urlProvider urlprovider.Provider,
local npm2.LocalRegistry,
proxy npm2.Proxy,
) Controller {
return NewController(proxyStore, registryDao, imageDao,
artifactDao, fileManager, downloadStatsDao, tx, urlProvider, local, proxy)
}
var ControllerSet = wire.NewSet(ControllerProvider)

View File

@ -0,0 +1,50 @@
// 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 npm
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/registry/app/dist_temp/errcode"
"github.com/harness/gitness/registry/app/pkg/commons"
npm2 "github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/request"
)
func (h *handler) AddPackageTag(w http.ResponseWriter, r *http.Request) {
contextInfo := request.ArtifactInfoFrom(r.Context())
info, ok := contextInfo.(npm2.ArtifactInfo)
if !ok {
h.HandleErrors2(r.Context(), errcode.ErrCodeInvalidRequest.WithMessage("failed to fetch info from context"), w)
return
}
response := h.controller.AddTag(r.Context(), info)
if commons.IsEmpty(response.GetError()) {
jsonResponse, err := json.Marshal(response.Tags)
if err != nil {
http.Error(w, "Error encoding response", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err = w.Write(jsonResponse)
if err != nil {
http.Error(w, "Failed to write response", http.StatusInternalServerError)
}
}
h.HandleError(r.Context(), w, response.GetError())
}

View File

@ -0,0 +1,40 @@
// 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 npm
import (
"net/http"
"github.com/harness/gitness/registry/app/dist_temp/errcode"
"github.com/harness/gitness/registry/app/pkg/commons"
"github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/request"
)
func (h *handler) DeletePackage(w http.ResponseWriter, r *http.Request) {
contextInfo := request.ArtifactInfoFrom(r.Context())
info, ok := contextInfo.(npm.ArtifactInfo)
if !ok {
h.HandleErrors2(r.Context(), errcode.ErrCodeInvalidRequest.WithMessage("failed to fetch info from context"), w)
return
}
response := h.controller.DeletePackage(r.Context(), info)
if !commons.IsEmpty(response.GetError()) {
http.Error(w, response.GetError().Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}

View File

@ -0,0 +1,33 @@
// 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 npm
import (
"net/http"
"github.com/harness/gitness/registry/app/dist_temp/errcode"
"github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/request"
)
func (h *handler) DeletePreview(w http.ResponseWriter, r *http.Request) {
contextInfo := request.ArtifactInfoFrom(r.Context())
_, ok := contextInfo.(npm.ArtifactInfo)
if !ok {
h.HandleErrors2(r.Context(), errcode.ErrCodeInvalidRequest.WithMessage("failed to fetch info from context"), w)
return
}
w.WriteHeader(http.StatusOK)
}

View File

@ -0,0 +1,46 @@
// 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 npm
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/registry/app/dist_temp/errcode"
"github.com/harness/gitness/registry/app/pkg/commons"
"github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/request"
)
func (h *handler) DeletePackageTag(w http.ResponseWriter, r *http.Request) {
contextInfo := request.ArtifactInfoFrom(r.Context())
info, ok := contextInfo.(npm.ArtifactInfo)
if !ok {
h.HandleErrors2(r.Context(), errcode.ErrCodeInvalidRequest.WithMessage("failed to fetch info from context"), w)
return
}
response := h.controller.DeleteTag(r.Context(), info)
if !commons.IsEmpty(response.GetError()) {
http.Error(w, response.GetError().Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(response)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

View File

@ -0,0 +1,40 @@
// 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 npm
import (
"net/http"
"github.com/harness/gitness/registry/app/dist_temp/errcode"
"github.com/harness/gitness/registry/app/pkg/commons"
"github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/request"
)
func (h *handler) DeleteVersion(w http.ResponseWriter, r *http.Request) {
contextInfo := request.ArtifactInfoFrom(r.Context())
info, ok := contextInfo.(npm.ArtifactInfo)
if !ok {
h.HandleErrors2(r.Context(), errcode.ErrCodeInvalidRequest.WithMessage("failed to fetch info from context"), w)
return
}
response := h.controller.DeleteVersion(r.Context(), info)
if !commons.IsEmpty(response.GetError()) {
http.Error(w, response.GetError().Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}

View File

@ -0,0 +1,69 @@
// 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 npm
import (
"fmt"
"net/http"
"github.com/harness/gitness/registry/app/pkg/commons"
npm2 "github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/request"
"github.com/rs/zerolog/log"
)
func (h *handler) DownloadPackageFile(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
info, ok := request.ArtifactInfoFrom(ctx).(npm2.ArtifactInfo)
if !ok {
log.Ctx(ctx).Error().Msg("Failed to get npm artifact info from context")
h.HandleErrors(r.Context(), []error{fmt.Errorf("failed to fetch npm artifact info from context")}, w)
return
}
response := h.controller.DownloadPackageFile(ctx, info)
defer func() {
if response.Body != nil {
err := response.Body.Close()
if err != nil {
log.Ctx(r.Context()).Error().Msgf("Failed to close body: %v", err)
}
}
if response.ReadCloser != nil {
err := response.ReadCloser.Close()
if err != nil {
log.Ctx(ctx).Error().Msgf("Failed to close read closer: %v", err)
}
}
}()
if !commons.IsEmpty(response.GetError()) {
h.HandleError(r.Context(), w, response.GetError())
}
w.Header().Set("Content-Disposition", "attachment; filename="+info.Filename)
if response.RedirectURL != "" {
http.Redirect(w, r, response.RedirectURL, http.StatusTemporaryRedirect)
return
}
err := commons.ServeContent(w, r, response.Body, info.Filename, response.ReadCloser)
if err != nil {
log.Ctx(ctx).Error().Msgf("Failed to serve content: %v", err)
h.HandleError(ctx, w, err)
return
}
response.ResponseHeaders.WriteToResponse(w)
}

View File

@ -0,0 +1,58 @@
// 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 npm
import (
"fmt"
"net/http"
"github.com/harness/gitness/registry/app/pkg/commons"
npm2 "github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/request"
"github.com/rs/zerolog/log"
)
func (h *handler) DownloadPackageFileByName(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
info, ok := request.ArtifactInfoFrom(ctx).(npm2.ArtifactInfo)
if !ok {
log.Ctx(ctx).Error().Msg("Failed to get npm artifact info from context")
h.HandleErrors(r.Context(), []error{fmt.Errorf("failed to fetch npm artifact info from context")}, w)
return
}
response := h.controller.DownloadPackageFile(ctx, info)
defer func() {
if response.Body != nil {
err := response.Body.Close()
if err != nil {
log.Ctx(r.Context()).Error().Msgf("Failed to close body: %v", err)
}
}
}()
if !commons.IsEmpty(response.GetError()) {
h.HandleError(r.Context(), w, response.GetError())
}
w.Header().Set("Content-Disposition", "attachment; filename="+info.Filename)
if response.RedirectURL != "" {
http.Redirect(w, r, response.RedirectURL, http.StatusTemporaryRedirect)
return
}
h.ServeContent(w, r, response.Body, info.Filename)
response.ResponseHeaders.WriteToResponse(w)
}

View File

@ -0,0 +1,55 @@
// 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 npm
import (
"encoding/json"
"fmt"
"net/http"
errors2 "github.com/harness/gitness/errors"
"github.com/harness/gitness/registry/app/pkg/commons"
npm2 "github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/request"
"github.com/rs/zerolog/log"
)
func (h *handler) GetPackageMetadata(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
info, ok := request.ArtifactInfoFrom(ctx).(npm2.ArtifactInfo)
if !ok {
log.Ctx(ctx).Error().Msg("Failed to get npm artifact info from context")
h.HandleErrors(r.Context(), []error{fmt.Errorf("failed to fetch npm artifact info from context")}, w)
return
}
response := h.controller.GetPackageMetadata(ctx, info)
if !commons.IsEmpty(response.GetError()) {
if errors2.IsNotFound(response.GetError()) {
http.Error(w, response.GetError().Error(), http.StatusNotFound)
return
}
http.Error(w, response.GetError().Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(response.PackageMetadata)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

View File

@ -0,0 +1,159 @@
// 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 npm
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
npm3 "github.com/harness/gitness/registry/app/api/controller/pkg/npm"
"github.com/harness/gitness/registry/app/api/handler/packages"
npm2 "github.com/harness/gitness/registry/app/metadata/npm"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/commons"
"github.com/harness/gitness/registry/app/pkg/types/npm"
)
var (
ErrInvalidPackageVersion = errors.New("package version is invalid")
ErrInvalidAttachment = errors.New("package attachment is invalid")
)
type Handler interface {
pkg.ArtifactInfoProvider
UploadPackage(writer http.ResponseWriter, request *http.Request)
DownloadPackageFile(http.ResponseWriter, *http.Request)
GetPackageMetadata(http.ResponseWriter, *http.Request)
DownloadPackageFileByName(http.ResponseWriter, *http.Request)
HeadPackageFileByName(http.ResponseWriter, *http.Request)
ListPackageTag(http.ResponseWriter, *http.Request)
AddPackageTag(http.ResponseWriter, *http.Request)
DeletePackageTag(http.ResponseWriter, *http.Request)
DeletePackage(w http.ResponseWriter, r *http.Request)
DeleteVersion(w http.ResponseWriter, r *http.Request)
DeletePreview(w http.ResponseWriter, r *http.Request)
}
type handler struct {
packages.Handler
controller npm3.Controller
}
func NewHandler(
controller npm3.Controller,
packageHandler packages.Handler,
) Handler {
return &handler{
Handler: packageHandler,
controller: controller,
}
}
var _ Handler = (*handler)(nil)
func (h *handler) GetPackageArtifactInfo(r *http.Request) (pkg.PackageArtifactInfo, error) {
info, e := h.GetArtifactInfo(r)
if !commons.IsEmpty(e) {
return npm.ArtifactInfo{}, e
}
info.Image = PackageNameFromParams(r)
version := GetVersionFromParams(r)
fileName := GetFileName(r)
npmInfo := npm.ArtifactInfo{
ArtifactInfo: info,
Filename: fileName,
Version: version,
}
if r.Body == nil || r.ContentLength == 0 {
return npmInfo, nil
}
if strings.Contains(r.URL.Path, "/-rev/") {
return npmInfo, nil
}
if strings.Contains(r.URL.Path, "/-/package/") && strings.Contains(r.URL.Path, "/dist-tags/") {
// Process the payload only for add tag requests
if r.Body == nil || r.ContentLength == 0 {
return npmInfo, nil
}
body, err := io.ReadAll(r.Body)
if err != nil {
return npm.ArtifactInfo{}, err
}
npmInfo.Version = strings.Trim(string(body), "\"")
npmInfo.DistTags = []string{r.PathValue("tag")}
return npmInfo, err
}
return GetNPMMetadata(r, info)
}
func GetNPMMetadata(r *http.Request, info pkg.ArtifactInfo) (pkg.PackageArtifactInfo, error) {
var md npm2.PackageUpload
// Read body into a buffer
var buf bytes.Buffer
tee := io.TeeReader(r.Body, &buf)
if err := json.NewDecoder(tee).Decode(&md); err != nil {
return npm.ArtifactInfo{}, err
}
r.Body = io.NopCloser(&buf)
for _, meta := range md.Versions {
a := npm.ArtifactInfo{
ArtifactInfo: info,
Metadata: md.PackageMetadata,
Version: meta.Version,
DistTags: make([]string, 0),
}
for tag := range md.DistTags {
a.DistTags = append(a.DistTags, tag)
}
a.Filename = strings.ToLower(fmt.Sprintf("%s-%s.tgz", md.Name, a.Version))
return a, nil
}
return npm.ArtifactInfo{}, ErrInvalidPackageVersion
}
func GetNPMFile(r *http.Request) (io.ReadCloser, error) {
var md npm2.PackageUpload
if err := json.NewDecoder(r.Body).Decode(&md); err != nil {
return nil, err
}
attachment := func() *npm2.PackageAttachment {
for _, a := range md.Attachments {
return a
}
return nil
}()
if attachment == nil || len(attachment.Data) == 0 {
return nil, ErrInvalidAttachment
}
return io.NopCloser(base64.NewDecoder(base64.StdEncoding,
strings.NewReader(attachment.Data))), nil
}

View File

@ -0,0 +1,45 @@
// 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 npm
import (
"encoding/json"
"fmt"
"net/http"
npm2 "github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/request"
"github.com/rs/zerolog/log"
)
func (h *handler) HeadPackageFileByName(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
info, ok := request.ArtifactInfoFrom(ctx).(npm2.ArtifactInfo)
if !ok {
log.Ctx(ctx).Error().Msg("Failed to get npm artifact info from context")
h.HandleErrors(r.Context(), []error{fmt.Errorf("failed to fetch npm artifact info from context")}, w)
return
}
response := h.controller.HeadPackageFileByName(ctx, info)
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(response)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}

View File

@ -0,0 +1,51 @@
// 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 npm
import (
"encoding/json"
"net/http"
"github.com/harness/gitness/registry/app/dist_temp/errcode"
npm2 "github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/request"
)
func (h *handler) ListPackageTag(w http.ResponseWriter, r *http.Request) {
contextInfo := request.ArtifactInfoFrom(r.Context())
info, ok := contextInfo.(npm2.ArtifactInfo)
if !ok {
h.HandleErrors2(r.Context(), errcode.ErrCodeInvalidRequest.WithMessage("failed to fetch info from context"), w)
return
}
response := h.controller.ListTags(r.Context(), info)
if response.GetError() != nil {
h.HandleError(r.Context(), w, response.GetError())
return
}
jsonResponse, err := json.Marshal(response.Tags)
if err != nil {
http.Error(w, "Error encoding response", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err = w.Write(jsonResponse)
if err != nil {
http.Error(w, "Failed to write response", http.StatusInternalServerError)
}
}

View File

@ -0,0 +1,51 @@
// 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 npm
import (
"fmt"
"net/http"
"github.com/harness/gitness/app/api/usererror"
"github.com/harness/gitness/registry/app/dist_temp/errcode"
"github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/request"
)
func (h *handler) UploadPackage(w http.ResponseWriter, r *http.Request) {
contextInfo := request.ArtifactInfoFrom(r.Context())
info, ok := contextInfo.(npm.ArtifactInfo)
if !ok {
h.HandleErrors2(r.Context(), errcode.ErrCodeInvalidRequest.WithMessage("failed to fetch info from context"), w)
return
}
file, err := GetNPMFile(r)
if err != nil {
h.HandleError(r.Context(), w, usererror.BadRequest("File Data is empty in the request"))
return
}
response := h.controller.UploadPackageFile(r.Context(), info, file)
if response.GetError() != nil {
h.HandleError(r.Context(), w, response.GetError())
return
}
response.ResponseHeaders.WriteToResponse(w)
_, err = w.Write([]byte(fmt.Sprintf("Pushed.\nSha256: %s", response.Sha256)))
if err != nil {
h.HandleError(r.Context(), w, errcode.ErrCodeUnknown.WithDetail(err))
return
}
}

View File

@ -0,0 +1,68 @@
// 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 npm
import (
"fmt"
"net/http"
"net/url"
"regexp"
)
func PackageNameFromParams(r *http.Request) string {
scope, _ := url.PathUnescape(r.PathValue("scope")) // Decode encoded scope
id, _ := url.PathUnescape(r.PathValue("id"))
if scope != "" {
if id != "" {
return fmt.Sprintf("@%s/%s", scope, id)
}
return fmt.Sprintf("@%s", scope)
}
return id
}
func GetVersionFromParams(r *http.Request) string {
version := r.PathValue("version")
if version == "" {
var err error
version, err = VersionNameFromFileName(GetFileName(r))
if err != nil {
return ""
}
return version
}
return version
}
func VersionNameFromFileName(filename string) (string, error) {
// Define regex pattern: package-name-version.tgz
re := regexp.MustCompile(`-(\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?)\.tgz$`)
// Find match
matches := re.FindStringSubmatch(filename)
if len(matches) > 1 {
return matches[1], nil
}
return "", fmt.Errorf("version not found in filename: %s", filename)
}
func GetFileName(r *http.Request) string {
scope := r.PathValue("scope")
file := r.PathValue("filename")
if scope != "" {
return fmt.Sprintf("@%s/%s", scope, file)
}
return file
}

View File

@ -36,6 +36,7 @@ import (
"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"
@ -43,29 +44,32 @@ import (
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,
SpaceStore: spaceStore,
TokenStore: tokenStore,
UserCtrl: userCtrl,
Authenticator: authenticator,
URLProvider: urlProvider,
Authorizer: authorizer,
RegistryDao: registryDao,
DownloadStatDao: downloadStatDao,
SpaceStore: spaceStore,
TokenStore: tokenStore,
UserCtrl: userCtrl,
Authenticator: authenticator,
URLProvider: urlProvider,
Authorizer: authorizer,
}
}
type handler struct {
RegistryDao store.RegistryRepository
SpaceStore corestore.SpaceStore
TokenStore corestore.TokenStore
UserCtrl *usercontroller.Controller
Authenticator authn.Authenticator
URLProvider urlprovider.Provider
Authorizer authz.Authorizer
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 {
@ -75,6 +79,11 @@ type Handler interface {
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)
@ -91,6 +100,7 @@ const (
PathPackageTypeMaven PathPackageType = "maven"
PathPackageTypePython PathPackageType = "python"
PathPackageTypeNuget PathPackageType = "nuget"
PathPackageTypeNpm PathPackageType = "npm"
)
var packageTypeMap = map[PathPackageType]artifact2.PackageType{
@ -98,6 +108,7 @@ var packageTypeMap = map[PathPackageType]artifact2.PackageType{
PathPackageTypeMaven: artifact2.PackageTypeMAVEN,
PathPackageTypePython: artifact2.PackageTypePYTHON,
PathPackageTypeNuget: artifact2.PackageTypeNUGET,
PathPackageTypeNpm: artifact2.PackageTypeNPM,
}
func (h *handler) GetAuthenticator() authn.Authenticator {
@ -118,6 +129,19 @@ func (h *handler) GetRegistryCheckAccess(
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)

View File

@ -22,6 +22,7 @@ import (
"github.com/harness/gitness/registry/app/api/handler/generic"
"github.com/harness/gitness/registry/app/api/handler/maven"
"github.com/harness/gitness/registry/app/api/handler/oci"
"github.com/harness/gitness/registry/app/api/handler/packages"
"github.com/harness/gitness/registry/app/api/router/utils"
"github.com/harness/gitness/registry/app/dist_temp/errcode"
"github.com/harness/gitness/registry/app/pkg"
@ -263,3 +264,27 @@ func dbDownloadStatForMavenArtifact(
}
return errcode.Error{}
}
func TrackDownloadStats(
packageHandler packages.Handler,
) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
sw := &StatusWriter{ResponseWriter: w}
next.ServeHTTP(sw, r)
if sw.StatusCode != http.StatusOK && sw.StatusCode != http.StatusTemporaryRedirect {
return
}
err := packageHandler.TrackDownloadStats(ctx, r)
if err != nil {
log.Ctx(ctx).Error().Stack().Str("middleware",
"TrackDownloadStat").Err(err).Msgf("error while putting download stat of artifact, %v",
err)
return
}
},
)
}
}

View File

@ -95,6 +95,7 @@ const (
UpstreamConfigSourceDockerhub UpstreamConfigSource = "Dockerhub"
UpstreamConfigSourceMavenCentral UpstreamConfigSource = "MavenCentral"
UpstreamConfigSourcePyPi UpstreamConfigSource = "PyPi"
UpstreamConfigSourceNpm UpstreamConfigSource = "Npm"
)
// Defines values for WebhookExecResult.

View File

@ -21,6 +21,7 @@ import (
middlewareauthn "github.com/harness/gitness/app/api/middleware/authn"
"github.com/harness/gitness/registry/app/api/handler/generic"
"github.com/harness/gitness/registry/app/api/handler/maven"
"github.com/harness/gitness/registry/app/api/handler/npm"
"github.com/harness/gitness/registry/app/api/handler/nuget"
"github.com/harness/gitness/registry/app/api/handler/packages"
"github.com/harness/gitness/registry/app/api/handler/python"
@ -46,6 +47,7 @@ func NewRouter(
genericHandler *generic.Handler,
pythonHandler python.Handler,
nugetHandler nuget.Handler,
npmHandler npm.Handler,
) Handler {
r := chi.NewRouter()
@ -114,7 +116,103 @@ func NewRouter(
With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDownload)).
Get("/index.json", nugetHandler.GetServiceEndpoint)
})
r.Route("/npm", func(r chi.Router) {
r.Use(middlewareauthn.Attempt(packageHandler.GetAuthenticator()))
r.Use(middleware.CheckAuth())
r.Route("/@{scope}/{id}", func(r chi.Router) {
r.With(middleware.StoreArtifactInfo(npmHandler)).
With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsUpload)).
Put("/", npmHandler.UploadPackage)
r.With(middleware.StoreArtifactInfo(npmHandler)).
With(middleware.TrackDownloadStats(packageHandler)).
With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDownload)).
Get("/-/{version}/@{scope}/{filename}", npmHandler.DownloadPackageFile)
r.With(middleware.StoreArtifactInfo(npmHandler)).
With(middleware.TrackDownloadStats(packageHandler)).
With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDownload)).
Get("/-/@{scope}/{filename}", npmHandler.DownloadPackageFileByName)
r.With(middleware.StoreArtifactInfo(npmHandler)).
With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDownload)).
Head("/-/@{scope}/{filename}", npmHandler.HeadPackageFileByName)
r.With(middleware.StoreArtifactInfo(npmHandler)).
With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDownload)).
Get("/", npmHandler.GetPackageMetadata)
r.With(middleware.StoreArtifactInfo(npmHandler)).
With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDelete)).
Delete("/-/{version}/@{scope}/{filename}/-rev/{revision}", npmHandler.DeleteVersion)
})
r.Route("/{id}", func(r chi.Router) {
r.With(middleware.StoreArtifactInfo(npmHandler)).
With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsUpload)).
Put("/", npmHandler.UploadPackage)
r.With(middleware.StoreArtifactInfo(npmHandler)).
With(middleware.TrackDownloadStats(packageHandler)).
With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDownload)).
Get("/-/{version}/{filename}", npmHandler.DownloadPackageFile)
r.With(middleware.StoreArtifactInfo(npmHandler)).
With(middleware.TrackDownloadStats(packageHandler)).
With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDownload)).
Get("/-/{filename}", npmHandler.DownloadPackageFileByName)
r.With(middleware.StoreArtifactInfo(npmHandler)).
With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDownload)).
Head("/-/{filename}", npmHandler.HeadPackageFileByName)
r.With(middleware.StoreArtifactInfo(npmHandler)).
With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDownload)).
Get("/", npmHandler.GetPackageMetadata)
r.With(middleware.StoreArtifactInfo(npmHandler)).
With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDelete)).
Delete("/-/{version}/{filename}/-rev/{revision}", npmHandler.DeleteVersion)
})
r.Route("/-/package/@{scope}/{id}/dist-tags", func(r chi.Router) {
registerDistTagRoutes(r, npmHandler, packageHandler)
})
r.Route("/-/package/{id}/dist-tags", func(r chi.Router) {
registerDistTagRoutes(r, npmHandler, packageHandler)
})
r.Route("/@{scope}/-rev/{revision}", func(r chi.Router) {
registerRevisionRoutes(r, npmHandler, packageHandler)
})
r.Route("/{id}/-rev/{revision}", func(r chi.Router) {
registerRevisionRoutes(r, npmHandler, packageHandler)
})
})
})
return r
}
func registerDistTagRoutes(r chi.Router, npmHandler npm.Handler, packageHandler packages.Handler) {
r.With(middleware.StoreArtifactInfo(npmHandler)).
With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsUpload)).
Get("/", npmHandler.ListPackageTag)
r.With(middleware.StoreArtifactInfo(npmHandler)).
With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsUpload)).
Route("/{tag}", func(r chi.Router) {
r.Put("/", npmHandler.AddPackageTag)
r.Delete("/", npmHandler.DeletePackageTag)
})
}
func registerRevisionRoutes(r chi.Router, npmHandler npm.Handler, packageHandler packages.Handler) {
r.Use(middleware.StoreArtifactInfo(npmHandler))
r.With(middleware.RequestPackageAccess(packageHandler, enum.PermissionArtifactsDelete)).
Route("/", func(r chi.Router) {
r.Delete("/", npmHandler.DeletePackage)
r.Put("/", npmHandler.DeletePreview)
})
}

View File

@ -24,6 +24,7 @@ import (
"github.com/harness/gitness/audit"
"github.com/harness/gitness/registry/app/api/handler/generic"
"github.com/harness/gitness/registry/app/api/handler/maven"
"github.com/harness/gitness/registry/app/api/handler/npm"
"github.com/harness/gitness/registry/app/api/handler/nuget"
hoci "github.com/harness/gitness/registry/app/api/handler/oci"
"github.com/harness/gitness/registry/app/api/handler/packages"
@ -120,8 +121,9 @@ func PackageHandlerProvider(
genericHandler *generic.Handler,
pypiHandler python.Handler,
nugetHandler nuget.Handler,
npmHandler npm.Handler,
) packagerrouter.Handler {
return packagerrouter.NewRouter(handler, mavenHandler, genericHandler, pypiHandler, nugetHandler)
return packagerrouter.NewRouter(handler, mavenHandler, genericHandler, pypiHandler, nugetHandler, npmHandler)
}
var WireSet = wire.NewSet(APIHandlerProvider, OCIHandlerProvider, AppRouterProvider,

View File

@ -21,10 +21,12 @@ import (
"github.com/harness/gitness/app/services/refcache"
corestore "github.com/harness/gitness/app/store"
urlprovider "github.com/harness/gitness/app/url"
"github.com/harness/gitness/registry/app/api/controller/pkg/npm"
nuget2 "github.com/harness/gitness/registry/app/api/controller/pkg/nuget"
python2 "github.com/harness/gitness/registry/app/api/controller/pkg/python"
"github.com/harness/gitness/registry/app/api/handler/generic"
mavenhandler "github.com/harness/gitness/registry/app/api/handler/maven"
npm2 "github.com/harness/gitness/registry/app/api/handler/npm"
nugethandler "github.com/harness/gitness/registry/app/api/handler/nuget"
ocihandler "github.com/harness/gitness/registry/app/api/handler/oci"
"github.com/harness/gitness/registry/app/api/handler/packages"
@ -40,6 +42,7 @@ import (
"github.com/harness/gitness/registry/app/pkg/filemanager"
generic2 "github.com/harness/gitness/registry/app/pkg/generic"
"github.com/harness/gitness/registry/app/pkg/maven"
npm22 "github.com/harness/gitness/registry/app/pkg/npm"
"github.com/harness/gitness/registry/app/pkg/nuget"
"github.com/harness/gitness/registry/app/pkg/python"
"github.com/harness/gitness/registry/app/store"
@ -114,12 +117,14 @@ func NewMavenHandlerProvider(
}
func NewPackageHandlerProvider(
registryDao store.RegistryRepository, spaceStore corestore.SpaceStore, tokenStore corestore.TokenStore,
registryDao store.RegistryRepository, downloadStatDao store.DownloadStatRepository,
spaceStore corestore.SpaceStore, tokenStore corestore.TokenStore,
userCtrl *usercontroller.Controller, authenticator authn.Authenticator,
urlProvider urlprovider.Provider, authorizer authz.Authorizer,
) packages.Handler {
return packages.NewHandler(
registryDao,
downloadStatDao,
spaceStore,
tokenStore,
userCtrl,
@ -143,6 +148,13 @@ func NewNugetHandlerProvider(
return nugethandler.NewHandler(controller, packageHandler)
}
func NewNPMHandlerProvider(
controller npm.Controller,
packageHandler packages.Handler,
) npm2.Handler {
return npm2.NewHandler(controller, packageHandler)
}
func NewGenericHandlerProvider(
spaceStore corestore.SpaceStore, controller *generic2.Controller, tokenStore corestore.TokenStore,
userCtrl *usercontroller.Controller, authenticator authn.Authenticator, urlProvider urlprovider.Provider,
@ -167,6 +179,7 @@ var WireSet = wire.NewSet(
NewPackageHandlerProvider,
NewPythonHandlerProvider,
NewNugetHandlerProvider,
NewNPMHandlerProvider,
database.WireSet,
pkg.WireSet,
docker.WireSet,
@ -174,11 +187,13 @@ var WireSet = wire.NewSet(
maven.WireSet,
nuget.WireSet,
python.WireSet,
npm22.WireSet,
router.WireSet,
gc.WireSet,
generic2.WireSet,
python2.ControllerSet,
nuget2.ControllerSet,
npm.ControllerSet,
base.WireSet,
)

View File

@ -0,0 +1,160 @@
// 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 npm
import (
"time"
"github.com/harness/gitness/registry/app/metadata"
)
// PackageAttachment https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#package
// nolint:tagliatelle
type PackageAttachment struct {
ContentType string `json:"content_type"`
Data string `json:"data"`
Length int `json:"length"`
}
// nolint:tagliatelle
type PackageUpload struct {
PackageMetadata
Attachments map[string]*PackageAttachment `json:"_attachments"`
}
// nolint:tagliatelle
type PackageMetadata struct {
ID string `json:"_id"`
Name string `json:"name"`
Description string `json:"description"`
DistTags map[string]string `json:"dist-tags,omitempty"`
Versions map[string]*PackageMetadataVersion `json:"versions"`
Readme string `json:"readme,omitempty"`
Maintainers []User `json:"maintainers,omitempty"`
Time map[string]time.Time `json:"time,omitempty"`
Homepage string `json:"homepage,omitempty"`
Keywords []string `json:"keywords,omitempty"`
Repository Repository `json:"repository,omitempty"`
Author User `json:"author"`
ReadmeFilename string `json:"readmeFilename,omitempty"`
Users map[string]bool `json:"users,omitempty"`
License string `json:"license,omitempty"`
}
// PackageMetadataVersion documentation:
// https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#version
// PackageMetadataVersion response:
// https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md#abbreviated-version-object
// nolint:tagliatelle
type PackageMetadataVersion struct {
ID string `json:"_id"`
Name string `json:"name"`
Version string `json:"version"`
Description string `json:"description"`
Author User `json:"author"`
Homepage string `json:"homepage,omitempty"`
License string `json:"license,omitempty"`
Repository Repository `json:"repository,omitempty"`
Keywords []string `json:"keywords,omitempty"`
Dependencies map[string]string `json:"dependencies,omitempty"`
BundleDependencies []string `json:"bundleDependencies,omitempty"`
DevDependencies map[string]string `json:"devDependencies,omitempty"`
PeerDependencies map[string]string `json:"peerDependencies,omitempty"`
Bin map[string]string `json:"bin,omitempty"`
OptionalDependencies map[string]string `json:"optionalDependencies,omitempty"`
Readme string `json:"readme,omitempty"`
Dist PackageDistribution `json:"dist"`
Maintainers []User `json:"maintainers,omitempty"`
}
// Repository https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#version
// nolint:tagliatelle
type Repository struct {
Type string `json:"type"`
URL string `json:"url"`
}
// PackageDistribution https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#version
// nolint:tagliatelle
type PackageDistribution struct {
Integrity string `json:"integrity"`
Shasum string `json:"shasum"`
Tarball string `json:"tarball"`
FileCount int `json:"fileCount,omitempty"`
UnpackedSize int `json:"unpackedSize,omitempty"`
NpmSignature string `json:"npm-signature,omitempty"`
}
type PackageSearch struct {
Objects []*PackageSearchObject `json:"objects"`
Total int64 `json:"total"`
}
type PackageSearchObject struct {
Package *PackageSearchPackage `json:"package"`
}
type PackageSearchPackage struct {
Scope string `json:"scope"`
Name string `json:"name"`
Version string `json:"version"`
Date time.Time `json:"date"`
Description string `json:"description"`
Author User `json:"author"`
Publisher User `json:"publisher"`
Maintainers []User `json:"maintainers"`
Keywords []string `json:"keywords,omitempty"`
Links *PackageSearchPackageLinks `json:"links"`
}
type PackageSearchPackageLinks struct {
Registry string `json:"npm"`
Homepage string `json:"homepage,omitempty"`
Repository string `json:"repository,omitempty"`
}
type User struct {
Username string `json:"username,omitempty"`
Name string `json:"name"`
Email string `json:"email,omitempty"`
URL string `json:"url,omitempty"`
}
// PythonMetadata represents the metadata for a Python package.
//
//nolint:revive
type NpmMetadata struct {
PackageMetadata
Files []metadata.File `json:"files"`
FileCount int64 `json:"file_count"`
Size int64 `json:"size"`
}
func (p *NpmMetadata) GetFiles() []metadata.File {
return p.Files
}
func (p *NpmMetadata) SetFiles(files []metadata.File) {
p.Files = files
p.FileCount = int64(len(files))
}
func (p *NpmMetadata) GetSize() int64 {
return p.Size
}
func (p *NpmMetadata) UpdateSize(size int64) {
p.Size += size
}

View File

@ -25,6 +25,7 @@ type NugetMetadata struct {
Metadata
Files []metadata.File `json:"files"`
FileCount int64 `json:"file_count"`
Size int64 `json:"size"`
}
func (p *NugetMetadata) GetFiles() []metadata.File {
@ -36,6 +37,14 @@ func (p *NugetMetadata) SetFiles(files []metadata.File) {
p.FileCount = int64(len(files))
}
func (p *NugetMetadata) GetSize() int64 {
return p.Size
}
func (p *NugetMetadata) UpdateSize(size int64) {
p.Size += size
}
// Package represents the entire NuGet package.
type Metadata struct {
PackageMetadata PackageMetadata `json:"metadata" xml:"metadata"`

View File

@ -1,20 +1,23 @@
// 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
// 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
// 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.
// 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 metadata
type Metadata interface {
GetFiles() []File
SetFiles([]File)
GetSize() int64
UpdateSize(int64)
}

View File

@ -85,6 +85,7 @@ type PythonMetadata struct {
Metadata
Files []metadata.File `json:"files"`
FileCount int64 `json:"file_count"`
Size int64 `json:"size"`
}
func (p *PythonMetadata) GetFiles() []metadata.File {
@ -95,3 +96,10 @@ func (p *PythonMetadata) SetFiles(files []metadata.File) {
p.Files = files
p.FileCount = int64(len(files))
}
func (p *PythonMetadata) GetSize() int64 {
return p.Size
}
func (p *PythonMetadata) UpdateSize(size int64) {
p.Size += size
}

View File

@ -24,6 +24,8 @@ import (
type PackageArtifactInfo interface {
BaseArtifactInfo() ArtifactInfo
GetImageVersion() (bool, string)
GetVersion() string
}
// ArtifactInfoProvider is an interface that must be implemented by package handlers

View File

@ -34,6 +34,8 @@ import (
"github.com/harness/gitness/registry/app/store"
"github.com/harness/gitness/registry/types"
"github.com/harness/gitness/store/database/dbtx"
"github.com/rs/zerolog/log"
)
var _ LocalBase = (*localBase)(nil)
@ -66,6 +68,12 @@ type LocalBase interface {
)
Exists(ctx context.Context, info pkg.ArtifactInfo, version string, fileName string) bool
CheckIfVersionExists(ctx context.Context, info pkg.PackageArtifactInfo) (bool, error)
DeletePackage(ctx context.Context, info pkg.PackageArtifactInfo) error
DeleteVersion(ctx context.Context, info pkg.PackageArtifactInfo) error
}
type localBase struct {
@ -74,6 +82,8 @@ type localBase struct {
tx dbtx.Transactor
imageDao store.ImageRepository
artifactDao store.ArtifactRepository
nodesDao store.NodesRepository
tagsDao store.PackageTagRepository
}
func NewLocalBase(
@ -82,6 +92,8 @@ func NewLocalBase(
tx dbtx.Transactor,
imageDao store.ImageRepository,
artifactDao store.ArtifactRepository,
nodesDao store.NodesRepository,
tagsDao store.PackageTagRepository,
) LocalBase {
return &localBase{
registryDao: registryDao,
@ -89,6 +101,8 @@ func NewLocalBase(
tx: tx,
imageDao: imageDao,
artifactDao: artifactDao,
nodesDao: nodesDao,
tagsDao: tagsDao,
}
}
@ -230,6 +244,19 @@ func (l *localBase) Exists(ctx context.Context, info pkg.ArtifactInfo, version s
return exists
}
func (l *localBase) CheckIfVersionExists(ctx context.Context, info pkg.PackageArtifactInfo) (bool, error) {
artifacts, err := l.artifactDao.GetArtifactMetadata(ctx,
info.BaseArtifactInfo().ParentID, info.BaseArtifactInfo().RegIdentifier,
info.BaseArtifactInfo().Image, info.GetVersion())
if err != nil {
return false, err
}
if artifacts != nil {
return false, err
}
return true, nil
}
func (l *localBase) GetSHA256(ctx context.Context, info pkg.ArtifactInfo, version string, fileName string) (
exists bool,
sha256 string,
@ -271,6 +298,7 @@ func (l *localBase) updateMetadata(
CreatedAt: time.Now().UnixMilli(),
})
inputMetadata.SetFiles(files)
inputMetadata.UpdateSize(fileInfo.Size)
}
} else {
files = append(files, metadata.File{
@ -278,6 +306,7 @@ func (l *localBase) updateMetadata(
CreatedAt: time.Now().UnixMilli(),
})
inputMetadata.SetFiles(files)
inputMetadata.UpdateSize(fileInfo.Size)
}
return nil
}
@ -324,3 +353,60 @@ func (l *localBase) CheckIfFileAlreadyExist(
return nil
}
func (l *localBase) DeletePackage(ctx context.Context, info pkg.PackageArtifactInfo) error {
err := l.tx.WithTx(
ctx, func(ctx context.Context) error {
path := "/" + info.BaseArtifactInfo().Image
err := l.nodesDao.DeleteByNodePathAndRegistryID(ctx, path, info.BaseArtifactInfo().RegistryID)
if err != nil {
return err
}
err = l.artifactDao.DeleteByImageNameAndRegistryID(ctx,
info.BaseArtifactInfo().RegistryID, info.BaseArtifactInfo().Image)
if err != nil {
return err
}
err = l.imageDao.DeleteByImageNameAndRegID(ctx, info.BaseArtifactInfo().RegistryID, info.BaseArtifactInfo().Image)
if err != nil {
return err
}
return nil
})
if err != nil {
log.Warn().Msgf("Failed to delete the package %v", info.BaseArtifactInfo().Image)
return err
}
return nil
}
func (l *localBase) DeleteVersion(ctx context.Context, info pkg.PackageArtifactInfo) error {
err := l.tx.WithTx(
ctx, func(ctx context.Context) error {
path := "/" + info.BaseArtifactInfo().Image + "/" + info.GetVersion()
err := l.nodesDao.DeleteByNodePathAndRegistryID(ctx,
path, info.BaseArtifactInfo().RegistryID)
if err != nil {
return err
}
err = l.artifactDao.DeleteByVersionAndImageName(ctx,
info.BaseArtifactInfo().Image, info.GetVersion(), info.BaseArtifactInfo().RegistryID)
if err != nil {
return err
}
return nil
})
if err != nil {
log.Warn().Msgf("Failed to delete the version for artifact %v:%v", info.BaseArtifactInfo().Image, info.GetVersion())
return err
}
return nil
}

View File

@ -28,8 +28,10 @@ func LocalBaseProvider(
tx dbtx.Transactor,
imageDao store.ImageRepository,
artifactDao store.ArtifactRepository,
nodesDao store.NodesRepository,
tagsDao store.PackageTagRepository,
) LocalBase {
return NewLocalBase(registryDao, fileManager, tx, imageDao, artifactDao)
return NewLocalBase(registryDao, fileManager, tx, imageDao, artifactDao, nodesDao, tagsDao)
}
var WireSet = wire.NewSet(LocalBaseProvider)

View File

@ -48,6 +48,9 @@ func IsEmpty(slice interface{}) bool {
return true
}
if val.Kind() == reflect.Struct {
return false
}
return val.Len() == 0
}

View File

@ -0,0 +1,252 @@
// 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 npm
import (
"context"
"encoding/json"
"fmt"
"io"
"github.com/harness/gitness/app/api/usererror"
urlprovider "github.com/harness/gitness/app/url"
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
npm2 "github.com/harness/gitness/registry/app/metadata/npm"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/base"
"github.com/harness/gitness/registry/app/pkg/commons"
"github.com/harness/gitness/registry/app/pkg/filemanager"
"github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/app/storage"
"github.com/harness/gitness/registry/app/store"
"github.com/harness/gitness/registry/types"
"github.com/harness/gitness/store/database/dbtx"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
)
var _ pkg.Artifact = (*localRegistry)(nil)
var _ Registry = (*localRegistry)(nil)
type localRegistry struct {
localBase base.LocalBase
fileManager filemanager.FileManager
proxyStore store.UpstreamProxyConfigRepository
tx dbtx.Transactor
registryDao store.RegistryRepository
imageDao store.ImageRepository
tagsDao store.PackageTagRepository
nodesDao store.NodesRepository
artifactDao store.ArtifactRepository
urlProvider urlprovider.Provider
}
func (c *localRegistry) HeadPackageMetadata(ctx context.Context, info npm.ArtifactInfo) (bool, error) {
return c.localBase.CheckIfVersionExists(ctx, info)
}
func (c *localRegistry) DownloadPackageFile(ctx context.Context,
info npm.ArtifactInfo) (*commons.ResponseHeaders, *storage.FileReader, string, error) {
headers, fileReader, redirectURL, err :=
c.localBase.Download(ctx, info.ArtifactInfo, info.Version,
info.Filename)
if err != nil {
return nil, nil, "", err
}
return headers, fileReader, redirectURL, nil
}
type LocalRegistry interface {
Registry
}
func NewLocalRegistry(
localBase base.LocalBase,
fileManager filemanager.FileManager,
proxyStore store.UpstreamProxyConfigRepository,
tx dbtx.Transactor,
registryDao store.RegistryRepository,
tagDao store.PackageTagRepository,
imageDao store.ImageRepository,
artifactDao store.ArtifactRepository,
nodesDao store.NodesRepository,
urlProvider urlprovider.Provider,
) LocalRegistry {
return &localRegistry{
localBase: localBase,
fileManager: fileManager,
proxyStore: proxyStore,
tx: tx,
tagsDao: tagDao,
registryDao: registryDao,
imageDao: imageDao,
artifactDao: artifactDao,
nodesDao: nodesDao,
urlProvider: urlProvider,
}
}
func (c *localRegistry) GetArtifactType() artifact.RegistryType {
return artifact.RegistryTypeVIRTUAL
}
func (c *localRegistry) GetPackageTypes() []artifact.PackageType {
return []artifact.PackageType{artifact.PackageTypeNPM}
}
func (c *localRegistry) UploadPackageFile(
ctx context.Context,
info npm.ArtifactInfo,
file io.ReadCloser,
) (headers *commons.ResponseHeaders, sha256 string, err error) {
defer file.Close()
path := pkg.JoinWithSeparator("/", info.Image, info.Version, info.Filename)
response, sha, err := c.localBase.Upload(ctx, info.ArtifactInfo, info.Filename, info.Version, path, file,
&npm2.NpmMetadata{
PackageMetadata: info.Metadata,
})
if !commons.IsEmpty(err) {
return nil, "", err
}
_, err = c.AddTag(ctx, info)
if err != nil {
return nil, "", err
}
return response, sha, nil
}
func (c *localRegistry) GetPackageMetadata(ctx context.Context, info npm.ArtifactInfo) (npm2.PackageMetadata, error) {
packageMetadata := npm2.PackageMetadata{}
versions := make(map[string]*npm2.PackageMetadataVersion)
artifacts, err := c.artifactDao.GetByRegistryIDAndImage(ctx, info.RegistryID, info.Image)
if err != nil {
log.Warn().Msgf("Failed to fetch artifact for image:[%s], Reg:[%s]",
info.BaseArtifactInfo().Image, info.BaseArtifactInfo().RegIdentifier)
return packageMetadata, usererror.ErrInternal
}
if len(*artifacts) == 0 {
return packageMetadata,
usererror.NotFound(fmt.Sprintf("no artifacts found for registry %s and image %s", info.Registry.Name, info.Image))
}
regURL := c.urlProvider.PackageURL(ctx, info.RootIdentifier+"/"+info.RegIdentifier, "npm")
for _, artifact := range *artifacts {
metadata := &npm2.NpmMetadata{}
err = json.Unmarshal(artifact.Metadata, metadata)
if err != nil {
return packageMetadata, err
}
if packageMetadata.Name == "" {
packageMetadata = metadata.PackageMetadata
}
for _, versionMetadata := range metadata.Versions {
versions[artifact.Version] = CreatePackageMetadataVersion(regURL, versionMetadata)
}
}
distTags, err := c.ListTags(ctx, info)
if !commons.IsEmpty(err) {
return npm2.PackageMetadata{}, err
}
packageMetadata.Versions = versions
packageMetadata.DistTags = distTags
return packageMetadata, nil
}
func CreatePackageMetadataVersion(registryURL string,
metadata *npm2.PackageMetadataVersion) *npm2.PackageMetadataVersion {
return &npm2.PackageMetadataVersion{
ID: fmt.Sprintf("%s@%s", metadata.Name, metadata.Version),
Name: metadata.Name,
Version: metadata.Version,
Description: metadata.Description,
Author: metadata.Author,
Homepage: registryURL,
License: metadata.License,
Dependencies: metadata.Dependencies,
BundleDependencies: metadata.BundleDependencies,
DevDependencies: metadata.DevDependencies,
PeerDependencies: metadata.PeerDependencies,
OptionalDependencies: metadata.OptionalDependencies,
Readme: metadata.Readme,
Bin: metadata.Bin,
Dist: npm2.PackageDistribution{
Shasum: metadata.Dist.Shasum,
Integrity: metadata.Dist.Integrity,
Tarball: fmt.Sprintf("http://localhost:3000/pkg/test/npm1/npm/%s/-/%s/%s", metadata.Name, metadata.Version,
metadata.Name+"-"+metadata.Version+".tgz"),
},
}
}
func (c *localRegistry) ListTags(ctx context.Context, info npm.ArtifactInfo) (map[string]string, error) {
tags, err := c.tagsDao.FindByImageNameAndRegID(ctx, info.Image, info.RegistryID)
if err != nil {
return nil, err
}
pkgTags := make(map[string]string)
for _, tag := range tags {
pkgTags[tag.Name] = tag.Version
}
return pkgTags, nil
}
func (c *localRegistry) AddTag(ctx context.Context, info npm.ArtifactInfo) (map[string]string, error) {
image, err := c.imageDao.GetByRepoAndName(ctx, info.ParentID, info.RegIdentifier, info.Image)
if err != nil {
return nil, err
}
version, err := c.artifactDao.GetByName(ctx, image.ID, info.Version)
if err != nil {
return nil, err
}
if len(info.DistTags) == 0 {
return nil, err
}
packageTag := &types.PackageTag{
ID: uuid.NewString(),
Name: info.DistTags[0],
ArtifactID: version.ID,
}
_, err = c.tagsDao.Create(ctx, packageTag)
if err != nil {
return nil, err
}
return c.ListTags(ctx, info)
}
func (c *localRegistry) DeleteTag(ctx context.Context, info npm.ArtifactInfo) (map[string]string, error) {
if len(info.DistTags) == 0 {
return nil, usererror.BadRequest("Delete tag error: distTags are empty")
}
err := c.tagsDao.DeleteByTagAndImageName(ctx, info.DistTags[0], info.Image, info.RegistryID)
if err != nil {
return nil, err
}
return c.ListTags(ctx, info)
}
func (c *localRegistry) DeletePackage(ctx context.Context, info npm.ArtifactInfo) error {
return c.localBase.DeletePackage(ctx, info)
}
func (c *localRegistry) DeleteVersion(ctx context.Context, info npm.ArtifactInfo) error {
return c.localBase.DeleteVersion(ctx, info)
}

View File

@ -0,0 +1,144 @@
// 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 npm
import (
"context"
"fmt"
"io"
urlprovider "github.com/harness/gitness/app/url"
"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/metadata/npm"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/commons"
"github.com/harness/gitness/registry/app/pkg/filemanager"
npm2 "github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/app/storage"
"github.com/harness/gitness/registry/app/store"
"github.com/harness/gitness/store/database/dbtx"
"github.com/rs/zerolog/log"
)
var _ pkg.Artifact = (*proxy)(nil)
var _ Registry = (*proxy)(nil)
type proxy struct {
fileManager filemanager.FileManager
proxyStore store.UpstreamProxyConfigRepository
tx dbtx.Transactor
registryDao store.RegistryRepository
imageDao store.ImageRepository
artifactDao store.ArtifactRepository
urlProvider urlprovider.Provider
}
func (r *proxy) HeadPackageMetadata(_ context.Context, _ npm2.ArtifactInfo) (bool, error) {
// TODO implement me
panic("implement me")
}
func (r *proxy) ListTags(_ context.Context, _ npm2.ArtifactInfo) (map[string]string, error) {
// TODO implement me
panic("implement me")
}
func (r *proxy) AddTag(_ context.Context, _ npm2.ArtifactInfo) (map[string]string, error) {
// TODO implement me
panic("implement me")
}
func (r *proxy) DeleteTag(_ context.Context, _ npm2.ArtifactInfo) (map[string]string, error) {
// TODO implement me
panic("implement me")
}
func (r *proxy) DeletePackage(_ context.Context, _ npm2.ArtifactInfo) error {
// TODO implement me
panic("implement me")
}
func (r *proxy) DeleteVersion(_ context.Context, _ npm2.ArtifactInfo) error {
// TODO implement me
panic("implement me")
}
func (r *proxy) GetPackageMetadata(_ context.Context, _ npm2.ArtifactInfo) (npm.PackageMetadata, error) {
// TODO implement me
panic("implement me")
}
type Proxy interface {
Registry
}
func NewProxy(
fileManager filemanager.FileManager,
proxyStore store.UpstreamProxyConfigRepository,
tx dbtx.Transactor,
registryDao store.RegistryRepository,
imageDao store.ImageRepository,
artifactDao store.ArtifactRepository,
urlProvider urlprovider.Provider,
) Proxy {
return &proxy{
proxyStore: proxyStore,
registryDao: registryDao,
imageDao: imageDao,
artifactDao: artifactDao,
fileManager: fileManager,
tx: tx,
urlProvider: urlProvider,
}
}
func (r *proxy) GetArtifactType() artifact.RegistryType {
return artifact.RegistryTypeUPSTREAM
}
func (r *proxy) GetPackageTypes() []artifact.PackageType {
return []artifact.PackageType{artifact.PackageTypeNPM}
}
func (r *proxy) DownloadPackageFile(ctx context.Context, info npm2.ArtifactInfo) (
*commons.ResponseHeaders,
*storage.FileReader,
string,
error,
) {
headers, body, _, url, errs := r.fetchFile(ctx, info, true)
return headers, body, url, errs
}
// UploadPackageFile FIXME: Extract this upload function for all types of packageTypes
// uploads the package file to the storage.
func (r *proxy) UploadPackageFile(
ctx context.Context,
_ npm2.ArtifactInfo,
_ io.ReadCloser,
) (*commons.ResponseHeaders, string, error) {
log.Error().Ctx(ctx).Msg("Not implemented")
return nil, "", errcode.ErrCodeInvalidRequest.WithDetail(fmt.Errorf("not implemented"))
}
func (r *proxy) fetchFile(_ context.Context, _ npm2.ArtifactInfo, _ bool) (
responseHeaders *commons.ResponseHeaders, body *storage.FileReader, readCloser io.ReadCloser,
redirectURL string, err error,
) {
// TODO implement me
panic("implement me")
}

View File

@ -0,0 +1,55 @@
// 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 npm
import (
"context"
"io"
npm3 "github.com/harness/gitness/registry/app/metadata/npm"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/commons"
"github.com/harness/gitness/registry/app/pkg/types/npm"
"github.com/harness/gitness/registry/app/storage"
)
type Registry interface {
pkg.Artifact
UploadPackageFile(
ctx context.Context,
info npm.ArtifactInfo,
file io.ReadCloser,
) (*commons.ResponseHeaders, string, error)
DownloadPackageFile(ctx context.Context, info npm.ArtifactInfo) (
*commons.ResponseHeaders,
*storage.FileReader,
string,
error,
)
GetPackageMetadata(ctx context.Context, info npm.ArtifactInfo) (npm3.PackageMetadata, error)
HeadPackageMetadata(ctx context.Context, info npm.ArtifactInfo) (bool, error)
ListTags(ctx context.Context, info npm.ArtifactInfo) (map[string]string, error)
AddTag(ctx context.Context, info npm.ArtifactInfo) (map[string]string, error)
DeleteTag(ctx context.Context, info npm.ArtifactInfo) (map[string]string, error)
DeletePackage(ctx context.Context, info npm.ArtifactInfo) error
DeleteVersion(ctx context.Context, info npm.ArtifactInfo) error
}

View File

@ -0,0 +1,60 @@
// 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 npm
import (
urlprovider "github.com/harness/gitness/app/url"
"github.com/harness/gitness/registry/app/pkg/base"
"github.com/harness/gitness/registry/app/pkg/filemanager"
"github.com/harness/gitness/registry/app/store"
"github.com/harness/gitness/store/database/dbtx"
"github.com/google/wire"
)
func LocalRegistryProvider(
localBase base.LocalBase,
fileManager filemanager.FileManager,
proxyStore store.UpstreamProxyConfigRepository,
tx dbtx.Transactor,
tagDao store.PackageTagRepository,
registryDao store.RegistryRepository,
imageDao store.ImageRepository,
artifactDao store.ArtifactRepository,
nodesDao store.NodesRepository,
urlProvider urlprovider.Provider,
) LocalRegistry {
registry := NewLocalRegistry(localBase, fileManager,
proxyStore, tx, registryDao, tagDao, imageDao, artifactDao, nodesDao,
urlProvider)
base.Register(registry)
return registry
}
func ProxyProvider(
proxyStore store.UpstreamProxyConfigRepository,
registryDao store.RegistryRepository,
imageDao store.ImageRepository,
artifactDao store.ArtifactRepository,
fileManager filemanager.FileManager,
tx dbtx.Transactor,
urlProvider urlprovider.Provider,
) Proxy {
proxy := NewProxy(fileManager, proxyStore, tx, registryDao, imageDao, artifactDao, urlProvider)
base.Register(proxy)
return proxy
}
var WireSet = wire.NewSet(LocalRegistryProvider, ProxyProvider)

View File

@ -87,6 +87,21 @@ type MockLocalBase struct {
mock.Mock
}
func (m *MockLocalBase) CheckIfVersionExists(_ context.Context, _ pkg.PackageArtifactInfo) (bool, error) {
// TODO implement me
panic("implement me")
}
func (m *MockLocalBase) DeletePackage(_ context.Context, _ pkg.PackageArtifactInfo) error {
// TODO implement me
panic("implement me")
}
func (m *MockLocalBase) DeleteVersion(_ context.Context, _ pkg.PackageArtifactInfo) error {
// TODO implement me
panic("implement me")
}
func (m *MockLocalBase) Exists(ctx context.Context, info pkg.ArtifactInfo, version, filename string) bool {
args := m.Called(ctx, info, version, filename)
return args.Bool(0)

View File

@ -0,0 +1,54 @@
// 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 npm
import (
"github.com/harness/gitness/registry/app/metadata/npm"
"github.com/harness/gitness/registry/app/pkg"
)
type ArtifactInfo struct {
pkg.ArtifactInfo
Metadata npm.PackageMetadata
Version string
DistTags []string
Filename string
}
type File struct {
FileURL string
Name string
}
type PackageMetadata struct {
Name string
Files []File
}
// BaseArtifactInfo implements pkg.PackageArtifactInfo interface.
func (a ArtifactInfo) BaseArtifactInfo() pkg.ArtifactInfo {
return a.ArtifactInfo
}
func (a ArtifactInfo) GetImageVersion() (exists bool, imageVersion string) {
if a.Image != "" && a.Version != "" {
return true, pkg.JoinWithSeparator(":", a.Image, a.Version)
}
return false, ""
}
func (a ArtifactInfo) GetVersion() string {
return a.Version
}

View File

@ -38,6 +38,10 @@ func (a ArtifactInfo) GetImageVersion() (exists bool, imageVersion string) {
return false, ""
}
func (a ArtifactInfo) GetVersion() string {
return a.Version
}
type File struct {
FileURL string
Name string

View File

@ -40,6 +40,10 @@ func (a ArtifactInfo) GetImageVersion() (exists bool, imageVersion string) {
return false, ""
}
func (a ArtifactInfo) GetVersion() string {
return a.Version
}
type File struct {
FileURL string
Name string

View File

@ -436,8 +436,11 @@ type ImageRepository interface {
UpdateStatus(ctx context.Context, artifact *types.Image) (err error)
DeleteByRegistryID(ctx context.Context, registryID int64) (err error)
DeleteBandwidthStatByRegistryID(ctx context.Context, registryID int64) (err error)
DeleteDownloadStatByRegistryID(ctx context.Context, registryID int64) (err error)
DeleteByImageNameAndRegID(ctx context.Context, regID int64, image string) (err error)
}
type ArtifactRepository interface {
@ -486,6 +489,10 @@ type ArtifactRepository interface {
*[]types.Artifact,
error,
)
DeleteByImageNameAndRegistryID(ctx context.Context, regID int64, image string) (err error)
DeleteByVersionAndImageName(ctx context.Context, image string, version string, regID int64) (err error)
GetLatestByImageID(ctx context.Context, imageID int64) (*types.Artifact, error)
}
@ -497,6 +504,7 @@ type DownloadStatRepository interface {
artifactVersion []string,
imageID int64,
) (map[string]int64, error)
CreateByRegistryIDImageAndArtifactName(ctx context.Context, regID int64, image string, artifactName string) error
}
type BandwidthStatRepository interface {
@ -573,6 +581,8 @@ type NodesRepository interface {
offset int,
search string,
) (*[]types.FileNodeMetadata, error)
DeleteByNodePathAndRegistryID(ctx context.Context, nodePath string, regID int64) (err error)
}
type GenericBlobRepository interface {
@ -641,3 +651,12 @@ type WebhooksExecutionRepository interface {
// ListForTrigger lists the webhook executions for a given trigger id.
ListForTrigger(ctx context.Context, triggerID string) ([]*gitnesstypes.WebhookExecutionCore, error)
}
type PackageTagRepository interface {
FindByImageNameAndRegID(ctx context.Context, image string, regID int64) ([]*types.PackageTagMetadata, error)
Create(ctx context.Context, tag *types.PackageTag) (string, error)
DeleteByTagAndImageName(ctx context.Context, tag string, image string, regID int64) error
DeleteByImageNameAndRegID(ctx context.Context, image string, regID int64) error
}

View File

@ -183,6 +183,47 @@ func (a ArtifactDao) Count(ctx context.Context) (int64, error) {
return count, nil
}
func (a ArtifactDao) DeleteByImageNameAndRegistryID(ctx context.Context, regID int64, image string) (err error) {
delStmt := databaseg.Builder.Delete("artifacts").
Where("artifact_id IN (SELECT a.artifact_id FROM artifacts a JOIN images i ON i.image_id = a.artifact_image_id"+
" WHERE i.image_name = ? AND i.image_registry_id = ?)", image, regID)
db := dbtx.GetAccessor(ctx, a.db)
delQuery, delArgs, err := delStmt.ToSql()
if err != nil {
return fmt.Errorf("failed to convert delete query to sql: %w", err)
}
_, err = db.ExecContext(ctx, delQuery, delArgs...)
if err != nil {
return databaseg.ProcessSQLErrorf(ctx, err, "the delete query failed")
}
return nil
}
func (a ArtifactDao) DeleteByVersionAndImageName(ctx context.Context, image string,
version string, regID int64) (err error) {
delStmt := databaseg.Builder.Delete("artifacts").
Where("artifact_id IN (SELECT a.artifact_id FROM artifacts a JOIN images i ON i.image_id = a.artifact_image_id"+
" WHERE a.artifact_name = ? AND i.image_name = ? AND i.image_registry_id = ?)", version, image, regID)
sql, args, err := delStmt.ToSql()
if err != nil {
return errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, a.db)
_, err = db.ExecContext(ctx, sql, args...)
if err != nil {
return databaseg.ProcessSQLErrorf(ctx, err, "the delete query failed")
}
return nil
}
func (a ArtifactDao) mapToInternalArtifact(ctx context.Context, in *types.Artifact) *artifactDB {
session, _ := request.AuthSessionFrom(ctx)

View File

@ -17,6 +17,7 @@ package database
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/harness/gitness/app/api/request"
@ -70,7 +71,6 @@ func (d DownloadStatDao) Create(ctx context.Context, downloadStat *types.Downloa
,:download_stat_updated_by
)
RETURNING download_stat_id`
db := dbtx.GetAccessor(ctx, d.db)
query, arg, err := db.BindNamed(sqlQuery, d.mapToInternalDownloadStat(ctx, downloadStat))
if err != nil {
@ -84,6 +84,54 @@ func (d DownloadStatDao) Create(ctx context.Context, downloadStat *types.Downloa
return nil
}
func (d DownloadStatDao) CreateByRegistryIDImageAndArtifactName(ctx context.Context,
regID int64, image string, version string) error {
selectQuery := databaseg.Builder.
Select(
"a.artifact_id",
"?",
"?",
"?",
"?",
"?",
).
From("artifacts a").
Join("images i ON a.artifact_image_id = i.image_id").
Where("a.artifact_version = ? AND i.image_registry_id = ? AND i.image_name = ?").
Limit(1)
insertQuery := databaseg.Builder.
Insert("download_stats").
Columns(
"download_stat_artifact_id",
"download_stat_timestamp",
"download_stat_created_at",
"download_stat_updated_at",
"download_stat_created_by",
"download_stat_updated_by",
).
Select(selectQuery)
// Convert query to SQL string and args
sqlStr, _, err := insertQuery.ToSql()
if err != nil {
return fmt.Errorf("failed to generate SQL: %w", err)
}
session, _ := request.AuthSessionFrom(ctx)
user := session.Principal.ID
db := dbtx.GetAccessor(ctx, d.db)
// Execute the query with parameters
_, err = db.ExecContext(ctx, sqlStr,
time.Now().UnixMilli(), time.Now().UnixMilli(), time.Now().UnixMilli(),
user, user, version, regID, image)
if err != nil {
return fmt.Errorf("failed to insert download stat: %w", err)
}
return nil
}
func (d DownloadStatDao) GetTotalDownloadsForImage(ctx context.Context, imageID int64) (int64, error) {
q := databaseg.Builder.Select(`count(*)`).
From("artifacts art").Where("art.artifact_image_id = ?", imageID).

View File

@ -114,6 +114,25 @@ func (i ImageDao) DeleteBandwidthStatByRegistryID(ctx context.Context, registryI
return nil
}
func (i ImageDao) DeleteByImageNameAndRegID(ctx context.Context, regID int64, image string) (err error) {
stmt := databaseg.Builder.Delete("images").
Where("image_name = ? AND image_registry_id = ?", image, regID)
sql, args, err := stmt.ToSql()
if err != nil {
return errors.Wrap(err, "Failed to convert query to sql")
}
db := dbtx.GetAccessor(ctx, i.db)
_, err = db.ExecContext(ctx, sql, args...)
if err != nil {
return databaseg.ProcessSQLErrorf(ctx, err, "the delete query failed")
}
return nil
}
func (i ImageDao) DeleteByRegistryID(ctx context.Context, registryID int64) error {
var ids []int64
stmt := databaseg.Builder.Select("artifact_id").

View File

@ -210,6 +210,25 @@ func (n NodeDao) DeleteByRegistryID(ctx context.Context, regID int64) (err error
return nil
}
func (n NodeDao) DeleteByNodePathAndRegistryID(ctx context.Context, nodePath string, regID int64) (err error) {
db := dbtx.GetAccessor(ctx, n.sqlDB)
delStmt := databaseg.Builder.Delete("nodes").
Where("node_path = ? OR node_path LIKE ?", nodePath, nodePath+"%").
Where("node_registry_id = ?", regID)
delQuery, delArgs, err := delStmt.ToSql()
if err != nil {
return fmt.Errorf("failed to convert purge query to sql: %w", err)
}
_, err = db.ExecContext(ctx, delQuery, delArgs...)
if err != nil {
return databaseg.ProcessSQLErrorf(ctx, err, "the delete query failed")
}
return nil
}
func (n NodeDao) mapToNode(_ context.Context, dst *Nodes) (*types.Node, error) {
var blobID, parentNodeID string
if dst.BlobID != nil {
@ -290,7 +309,7 @@ func (n NodeDao) GetFilesMetadataByPathAndRegistryID(ctx context.Context, regist
db := dbtx.GetAccessor(ctx, n.sqlDB)
q = q.OrderBy(sortByField + " " + sortByOrder).Limit(uint64(limit)).Offset(uint64(offset))
q = q.OrderBy(sortByField + " " + sortByOrder).Limit(uint64(limit)).Offset(uint64(offset)) //nolint:gosec
if search != "" {
q = q.Where("name LIKE ?", sqlPartialMatch(search))

View File

@ -0,0 +1,211 @@
// 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"
"fmt"
"time"
"github.com/harness/gitness/app/api/request"
"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/pkg/errors"
)
type PackageTagDao struct {
db *sqlx.DB
}
func NewPackageTagDao(db *sqlx.DB) *PackageTagDao {
return &PackageTagDao{
db: db,
}
}
type PackageTagDB struct {
ID string `db:"package_tag_id"`
Name string `db:"package_tag_name"`
ArtifactID int64 `db:"package_tag_artifact_id"`
CreatedAt int64 `db:"package_tag_created_at"`
CreatedBy int64 `db:"package_tag_created_by"`
UpdatedAt int64 `db:"package_tag_updated_at"`
UpdatedBy int64 `db:"package_tag_updated_by"`
}
type PackageTagMetadataDB struct {
ID string `db:"package_tag_id"`
Name string `db:"package_tag_name"`
ImageID int64 `db:"package_tag_image_id"`
Version string `db:"package_tag_version"`
}
func (r PackageTagDao) FindByImageNameAndRegID(ctx context.Context,
image string, regID int64) ([]*types.PackageTagMetadata, error) {
stmt := databaseg.Builder.
Select("p.package_tag_id as package_tag_id, "+
"p.package_tag_name as package_tag_name,"+
"i.image_id as package_tag_image_id,"+
"a.artifact_version as package_tag_version").
From("package_tags as p").
Join("artifacts as a ON p.package_tag_artifact_id = a.artifact_id").
Join("images as i ON a.artifact_image_id = i.image_id").
Join("registries as r ON i.image_registry_id = r.registry_id").
Where("i.image_name = ? AND r.registry_id = ?", image, regID)
db := dbtx.GetAccessor(ctx, r.db)
dst := []PackageTagMetadataDB{}
sql, args, err := stmt.ToSql()
if err != nil {
return nil,
errors.Wrap(err, "Failed to convert query to sql")
}
if err = db.SelectContext(ctx, &dst, sql, args...); err != nil {
return nil,
databaseg.ProcessSQLErrorf(ctx, err, "Failed to find package tags")
}
return r.mapToPackageTagList(ctx, dst)
}
// DeleteByTagAndImageName Todo:postgres query can be optimised here
func (r PackageTagDao) DeleteByTagAndImageName(ctx context.Context,
tag string, image string, regID int64) error {
stmt := databaseg.Builder.Delete("package_tags").
Where("package_tag_id IN (SELECT p.package_tag_id FROM package_tags p"+
" JOIN artifacts a ON p.package_tag_artifact_id = a.artifact_id "+
"JOIN images i ON a.artifact_image_id = i.image_id "+
" JOIN registries r ON r.registry_id = i.image_registry_id "+
"WHERE i.image_name = ? AND p.package_tag_name = ? AND r.registry_id = ?)", image, tag, regID)
sql, args, err := stmt.ToSql()
if err != nil {
return fmt.Errorf("failed to convert purge package_tag query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, r.db)
_, err = db.ExecContext(ctx, sql, args...)
if err != nil {
return databaseg.ProcessSQLErrorf(ctx, err, "the delete query failed")
}
return nil
}
func (r PackageTagDao) DeleteByImageNameAndRegID(ctx context.Context, image string, regID int64) error {
stmt := databaseg.Builder.Delete("package_tags").
Where("package_tag_id IN (SELECT p.package_tag_id FROM package_tags p "+
"JOIN artifacts a ON p.package_tag_artifact_id = a.artifact_id "+
"JOIN images i ON a.artifact_image_id = i.image_id "+
" JOIN registries r ON r.registry_id = i.image_registry_id "+
"WHERE i.image_name = ? AND r.registry_id = ?)", image, regID)
sql, args, err := stmt.ToSql()
if err != nil {
return fmt.Errorf("failed to convert purge package_tag query to sql: %w", err)
}
db := dbtx.GetAccessor(ctx, r.db)
_, err = db.ExecContext(ctx, sql, args...)
if err != nil {
return databaseg.ProcessSQLErrorf(ctx, err, "the delete query failed")
}
return nil
}
func (r PackageTagDao) Create(ctx context.Context, tag *types.PackageTag) (string, error) {
const sqlQuery = `
INSERT INTO package_tags (
package_tag_id,
package_tag_name,
package_tag_artifact_id,
package_tag_created_at,
package_tag_created_by,
package_tag_updated_at,
package_tag_updated_by
) VALUES (
:package_tag_id,
:package_tag_name,
:package_tag_artifact_id,
:package_tag_created_at,
:package_tag_created_by,
:package_tag_updated_at,
:package_tag_updated_by
)
ON CONFLICT (package_tag_name,
package_tag_artifact_id)
DO UPDATE SET
package_tag_artifact_id = :package_tag_artifact_id
RETURNING package_tag_id`
db := dbtx.GetAccessor(ctx, r.db)
query, arg, err := db.BindNamed(sqlQuery, mapToInternalPackageTag(ctx, tag))
if err != nil {
return "",
databaseg.ProcessSQLErrorf(ctx, err, "Failed to bind repo object")
}
if err = db.QueryRowContext(ctx, query, arg...).Scan(&tag.ID); err != nil {
return "", databaseg.ProcessSQLErrorf(ctx, err, "Insert query failed")
}
return tag.ID, nil
}
func (r PackageTagDao) mapToPackageTagList(ctx context.Context,
dst []PackageTagMetadataDB) ([]*types.PackageTagMetadata, error) {
tags := make([]*types.PackageTagMetadata, 0, len(dst))
for _, d := range dst {
tag := r.mapToPackageTag(ctx, d)
tags = append(tags, tag)
}
return tags, nil
}
func (r PackageTagDao) mapToPackageTag(_ context.Context, dst PackageTagMetadataDB) *types.PackageTagMetadata {
return &types.PackageTagMetadata{
ID: dst.ID,
Name: dst.Name,
Version: dst.Version}
}
func mapToInternalPackageTag(ctx context.Context, in *types.PackageTag) *PackageTagDB {
session, _ := request.AuthSessionFrom(ctx)
if in.CreatedAt.IsZero() {
in.CreatedAt = time.Now()
}
in.UpdatedAt = time.Now()
if in.CreatedBy == 0 {
in.CreatedBy = session.Principal.ID
}
in.UpdatedBy = session.Principal.ID
return &PackageTagDB{
ID: in.ID,
Name: in.Name,
ArtifactID: in.ArtifactID,
CreatedAt: in.CreatedAt.UnixMilli(),
UpdatedAt: in.UpdatedAt.UnixMilli(),
CreatedBy: in.CreatedBy,
UpdatedBy: in.UpdatedBy,
}
}

View File

@ -99,6 +99,10 @@ func ProvideNodeDao(db *sqlx.DB) store.NodesRepository {
return NewNodeDao(db)
}
func ProvidePackageTagDao(db *sqlx.DB) store.PackageTagRepository {
return NewPackageTagDao(db)
}
func ProvideGenericBlobDao(db *sqlx.DB) store.GenericBlobRepository {
return NewGenericBlobDao(db)
}
@ -123,4 +127,5 @@ var WireSet = wire.NewSet(
ProvideGenericBlobDao,
ProvideWebhookDao,
ProvideWebhookExecutionDao,
ProvidePackageTagDao,
)

View File

@ -0,0 +1,33 @@
// 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 types
import "time"
type PackageTag struct {
ID string
Name string
ArtifactID int64
CreatedAt time.Time
CreatedBy int64
UpdatedAt time.Time
UpdatedBy int64
}
type PackageTagMetadata struct {
ID string
Name string
Version string
}

26
types/PackageTag.go Normal file
View File

@ -0,0 +1,26 @@
// 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 types
type PackageTag struct {
PackageID int64
Tag string
ImageID int64
ArtifactID int64
CreatedAt int64
CreatedBy int64
UpdatedAt int64
UpdatedBy int64
}