mirror of
https://github.com/harness/drone.git
synced 2025-05-05 15:32:56 +00:00
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:
parent
e5eafee82c
commit
105afe37db
@ -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 ]
|
@ -0,0 +1 @@
|
||||
DROP TABLE package_tags;
|
@ -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)
|
||||
);
|
@ -0,0 +1 @@
|
||||
DROP TABLE package_tags;
|
@ -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)
|
||||
);
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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> "
|
||||
|
||||
|
66
registry/app/api/controller/pkg/npm/add_tag.go
Normal file
66
registry/app/api/controller/pkg/npm/add_tag.go
Normal 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
|
||||
}
|
110
registry/app/api/controller/pkg/npm/controller.go
Normal file
110
registry/app/api/controller/pkg/npm/controller.go
Normal 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,
|
||||
}
|
||||
}
|
60
registry/app/api/controller/pkg/npm/delete_package.go
Normal file
60
registry/app/api/controller/pkg/npm/delete_package.go
Normal 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
|
||||
}
|
66
registry/app/api/controller/pkg/npm/delete_tag.go
Normal file
66
registry/app/api/controller/pkg/npm/delete_tag.go
Normal 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
|
||||
}
|
60
registry/app/api/controller/pkg/npm/delete_version.go
Normal file
60
registry/app/api/controller/pkg/npm/delete_version.go
Normal 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
|
||||
}
|
64
registry/app/api/controller/pkg/npm/download.go
Normal file
64
registry/app/api/controller/pkg/npm/download.go
Normal 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
|
||||
}
|
64
registry/app/api/controller/pkg/npm/download_file.go
Normal file
64
registry/app/api/controller/pkg/npm/download_file.go
Normal 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
|
||||
}
|
67
registry/app/api/controller/pkg/npm/head_file.go
Normal file
67
registry/app/api/controller/pkg/npm/head_file.go
Normal 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
|
||||
}
|
67
registry/app/api/controller/pkg/npm/list_tag.go
Normal file
67
registry/app/api/controller/pkg/npm/list_tag.go
Normal 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
|
||||
}
|
71
registry/app/api/controller/pkg/npm/metadata.go
Normal file
71
registry/app/api/controller/pkg/npm/metadata.go
Normal 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
|
||||
}
|
83
registry/app/api/controller/pkg/npm/response.go
Normal file
83
registry/app/api/controller/pkg/npm/response.go
Normal 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
|
||||
}
|
71
registry/app/api/controller/pkg/npm/upload.go
Normal file
71
registry/app/api/controller/pkg/npm/upload.go
Normal 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
|
||||
}
|
43
registry/app/api/controller/pkg/npm/wire.go
Normal file
43
registry/app/api/controller/pkg/npm/wire.go
Normal 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)
|
50
registry/app/api/handler/npm/add_tag.go
Normal file
50
registry/app/api/handler/npm/add_tag.go
Normal 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())
|
||||
}
|
40
registry/app/api/handler/npm/delete_package.go
Normal file
40
registry/app/api/handler/npm/delete_package.go
Normal 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)
|
||||
}
|
33
registry/app/api/handler/npm/delete_preview.go
Normal file
33
registry/app/api/handler/npm/delete_preview.go
Normal 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)
|
||||
}
|
46
registry/app/api/handler/npm/delete_tag.go
Normal file
46
registry/app/api/handler/npm/delete_tag.go
Normal 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
|
||||
}
|
||||
}
|
40
registry/app/api/handler/npm/delete_version.go
Normal file
40
registry/app/api/handler/npm/delete_version.go
Normal 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)
|
||||
}
|
69
registry/app/api/handler/npm/download.go
Normal file
69
registry/app/api/handler/npm/download.go
Normal 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)
|
||||
}
|
58
registry/app/api/handler/npm/download_file.go
Normal file
58
registry/app/api/handler/npm/download_file.go
Normal 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)
|
||||
}
|
55
registry/app/api/handler/npm/get_metadata.go
Normal file
55
registry/app/api/handler/npm/get_metadata.go
Normal 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
|
||||
}
|
||||
}
|
159
registry/app/api/handler/npm/handler.go
Normal file
159
registry/app/api/handler/npm/handler.go
Normal 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
|
||||
}
|
45
registry/app/api/handler/npm/head_file.go
Normal file
45
registry/app/api/handler/npm/head_file.go
Normal 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
|
||||
}
|
||||
}
|
51
registry/app/api/handler/npm/list_tag.go
Normal file
51
registry/app/api/handler/npm/list_tag.go
Normal 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)
|
||||
}
|
||||
}
|
51
registry/app/api/handler/npm/upload.go
Normal file
51
registry/app/api/handler/npm/upload.go
Normal 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
|
||||
}
|
||||
}
|
68
registry/app/api/handler/npm/utils.go
Normal file
68
registry/app/api/handler/npm/utils.go
Normal 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
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +95,7 @@ const (
|
||||
UpstreamConfigSourceDockerhub UpstreamConfigSource = "Dockerhub"
|
||||
UpstreamConfigSourceMavenCentral UpstreamConfigSource = "MavenCentral"
|
||||
UpstreamConfigSourcePyPi UpstreamConfigSource = "PyPi"
|
||||
UpstreamConfigSourceNpm UpstreamConfigSource = "Npm"
|
||||
)
|
||||
|
||||
// Defines values for WebhookExecResult.
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
)
|
||||
|
||||
|
160
registry/app/metadata/npm/metadata.go
Normal file
160
registry/app/metadata/npm/metadata.go
Normal 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
|
||||
}
|
@ -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"`
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -48,6 +48,9 @@ func IsEmpty(slice interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if val.Kind() == reflect.Struct {
|
||||
return false
|
||||
}
|
||||
return val.Len() == 0
|
||||
}
|
||||
|
||||
|
252
registry/app/pkg/npm/local.go
Normal file
252
registry/app/pkg/npm/local.go
Normal 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)
|
||||
}
|
144
registry/app/pkg/npm/proxy.go
Normal file
144
registry/app/pkg/npm/proxy.go
Normal 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")
|
||||
}
|
55
registry/app/pkg/npm/registry.go
Normal file
55
registry/app/pkg/npm/registry.go
Normal 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
|
||||
}
|
60
registry/app/pkg/npm/wire.go
Normal file
60
registry/app/pkg/npm/wire.go
Normal 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)
|
@ -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)
|
||||
|
54
registry/app/pkg/types/npm/types.go
Normal file
54
registry/app/pkg/types/npm/types.go
Normal 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
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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).
|
||||
|
@ -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").
|
||||
|
@ -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))
|
||||
|
211
registry/app/store/database/package_tag.go
Normal file
211
registry/app/store/database/package_tag.go
Normal 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,
|
||||
}
|
||||
}
|
@ -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,
|
||||
)
|
||||
|
33
registry/types/PackageTag.go
Normal file
33
registry/types/PackageTag.go
Normal 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
26
types/PackageTag.go
Normal 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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user