mirror of
https://github.com/harness/drone.git
synced 2025-05-03 01:12:04 +08:00
feat: [AH-309]: Artifact Registry integration (#2318)
This commit is contained in:
parent
fddea6392f
commit
40ee05ca11
8
.gitignore
vendored
8
.gitignore
vendored
@ -8,6 +8,8 @@ _research
|
||||
web/node_modules
|
||||
web/dist
|
||||
web/coverage
|
||||
web/.yalc
|
||||
web/yalc.lock
|
||||
yarn-error*
|
||||
release
|
||||
.idea
|
||||
@ -18,6 +20,12 @@ web/cypress/node_modules
|
||||
*.rsa
|
||||
*.rsa.pub
|
||||
node_modules/
|
||||
dist
|
||||
.yalc
|
||||
yalc.lock
|
||||
node_modules
|
||||
|
||||
# ignore any executables we build
|
||||
/gitness
|
||||
/registry/logs/*
|
||||
/distribution-spec
|
||||
|
188
.golangci.yml
188
.golangci.yml
@ -287,6 +287,8 @@ issues:
|
||||
linters: [ govet ]
|
||||
- source: "^//\\s*go:generate\\s"
|
||||
linters: [ lll ]
|
||||
- text: 'local replacement are not allowed: github.com/harness/gitness'
|
||||
linters: [ gomoddirectives ]
|
||||
- text: 'replacement are not allowed: github.com/docker/docker'
|
||||
linters: [ gomoddirectives ]
|
||||
- source: "(noinspection|TODO)"
|
||||
@ -297,6 +299,192 @@ issues:
|
||||
linters: [ errorlint ]
|
||||
- path: "^cli/"
|
||||
linters: [forbidigo]
|
||||
#Registry Specific
|
||||
- path: "^registry/app/manifest/.*"
|
||||
linters: [ tagliatelle, staticcheck, revive ]
|
||||
- path: "^registry/app/dist_temp/.*"
|
||||
linters: [ errorlint ]
|
||||
- path: "^registry/app/driver/filesystem/.*"
|
||||
linters: [ gocritic ]
|
||||
- path: "^registry/app/driver/s3-aws/.*"
|
||||
linters: [ gocognit, gocyclo, gosec, nestif, cyclop]
|
||||
- path: "^registry/app/remote/clients/registry/interceptor/interceptor.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/common/http/modifier/modifier.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/driver/fileinfo.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/driver/storagedriver.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/driver/walk.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/challenge/addr.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/challenge/authchallenge.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/challenge/authchallenge_test.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/requestutil/util.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/requestutil/util_test.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/manifest/descriptor.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/manifest/doc.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/manifest/errors.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/manifest/manifests.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/manifest/versioned.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/common/lib/authorizer.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/common/lib/link.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/common/http/tls.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/common/http/transport.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/common/http/transport_test.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/manifest/schema2/manifest.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/manifest/schema2/manifest_test.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/manifest/ocischema/index.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/manifest/ocischema/manifest.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/clients/registry/auth/null/authorizer.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/clients/registry/auth/basic/authorizer.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/clients/registry/auth/basic/authorizer_test.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/common/lib/errors/const.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/common/lib/errors/errors.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/common/lib/errors/stack.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/common/lib/errors/stack_test.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/clients/registry/auth/bearer/authorizer.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/clients/registry/auth/bearer/cache.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/clients/registry/auth/bearer/scope.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/manifest/manifestlist/manifestlist.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/manifest/manifestlist/manifestlist_test.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/driver/factory/factory.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/dcontext/context.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/dcontext/doc.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/dcontext/http.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/dcontext/logger.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/dcontext/trace.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/dcontext/util.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/dcontext/version.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/dcontext/http_test.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/dcontext/trace_test.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/dcontext/version_test.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/driver/base/base.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/driver/base/regulator.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/driver/base/regulator_test.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/storage/blobs.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/storage/blobwriter.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/storage/blobwriter_resumable.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/storage/errors.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/storage/filereader.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/storage/gcstoragelient.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/storage/io.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/storage/middleware.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/storage/ociblobstore.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/storage/paths.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/storage/storageservice.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/clients/registry/client.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/adapter/adapter.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/clients/registry/auth/authorizer.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/driver/s3-aws/s3.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/driver/s3-aws/s3_v2_signer.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/driver/filesystem/driver.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/pkg/docker/app.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/pkg/docker/catalog.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/pkg/docker/compat.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/pkg/docker/context.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/pkg/docker/controller.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/pkg/docker/local.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/pkg/docker/manifest_service.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/pkg/docker/remote.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/adapter/dockerhub/adapter.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/adapter/dockerhub/client.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/adapter/dockerhub/consts.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/driver/testsuites/testsuites.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/errcode/errors.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/errcode/handler.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/dist_temp/errcode/register.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/controller/proxy/controller.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/controller/proxy/inflight.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/controller/proxy/local.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/controller/proxy/remote.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/controller/proxy/inflight_test.go"
|
||||
linters: [ goheader ]
|
||||
- path: "^registry/app/remote/adapter/native/adapter.go"
|
||||
linters: [ goheader ]
|
||||
#Registry Specific ends
|
||||
- text: "mnd: Magic number: \\d"
|
||||
linters:
|
||||
- gomnd
|
||||
|
@ -10,3 +10,8 @@ GITNESS_DEBUG=true
|
||||
GITNESS_DOCKER_API_VERSION=1.41
|
||||
GITNESS_SSH_ENABLE=true
|
||||
GITNESS_SSH_HOST=localhost
|
||||
GITNESS_SSH_PORT=2222
|
||||
|
||||
GITNESS_REGISTRY_STORAGE_TYPE=filesystem
|
||||
GITNESS_REGISTRY_ENABLED=false
|
||||
GITNESS_REGISTRY_FILESYSTEM_ROOT_DIRECTORY=/tmp
|
||||
|
39
Makefile
39
Makefile
@ -34,7 +34,7 @@ tools: $(tools) ## Install tools required for the build
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Build and testing rules
|
||||
# Gitness Build and testing rules
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
@ -47,6 +47,43 @@ test: generate ## Run the go tests
|
||||
go test -v -coverprofile=coverage.out ./...
|
||||
go tool cover -html=coverage.out
|
||||
|
||||
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Artifact Registry Build and testing rules
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
run: clean build
|
||||
./gitness server .local.env || true
|
||||
|
||||
ar-conformance-test: clean build
|
||||
./gitness server .local.env > logfile.log 2>&1 & echo $$! > server.PID
|
||||
@sleep 10
|
||||
./registry/tests/conformance_test.sh localhost:3000 || true
|
||||
kill `cat server.PID`
|
||||
@rm server.PID
|
||||
@rm logfile.log
|
||||
|
||||
ar-hot-conformance-test:
|
||||
rm -rf distribution-spec || true
|
||||
./registry/tests/conformance_test.sh localhost:3000 || true
|
||||
|
||||
ar-api-update:
|
||||
@set -e; \
|
||||
oapi-codegen --config ./registry/config/openapi/artifact-services.yaml ./registry/app/api/openapi/api.yaml; \
|
||||
oapi-codegen --config ./registry/config/openapi/artifact-types.yaml ./registry/app/api/openapi/api.yaml;
|
||||
|
||||
ar-clean:
|
||||
@rm artifact-registry 2> /dev/null || true
|
||||
@docker stop ps_artifacthub 2> /dev/null || true
|
||||
rm -rf distribution-spec
|
||||
@kill -9 $$(lsof -t -i:3000) || true
|
||||
@rm server.PID || true
|
||||
@rm logfile.log || true
|
||||
go clean
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# Code Formatting and linting
|
||||
|
12
NOTICE
Normal file
12
NOTICE
Normal file
@ -0,0 +1,12 @@
|
||||
Copyright 2024 Harness, Inc.
|
||||
|
||||
This product includes software developed at
|
||||
|
||||
https://github.com/goharbor/harbor
|
||||
Licensed under the Apache License, Version 2.0
|
||||
|
||||
https://github.com/distribution/distribution
|
||||
Licensed under the Apache License, Version 2.0
|
||||
|
||||
https://gitlab.com/gitlab-org/container-registry
|
||||
Licensed under the Apache License, Version 2.0
|
@ -96,6 +96,14 @@ To regenerate the code, please execute the following steps:
|
||||
|
||||
The latest API changes should now be reflected in `web/src/services/code/index.tsx`
|
||||
|
||||
# Run Registry Conformance Tests
|
||||
```
|
||||
make conformance-test
|
||||
```
|
||||
For running conformance tests with existing running service, use:
|
||||
```
|
||||
make hot-conformance-test
|
||||
```
|
||||
|
||||
## User Interface
|
||||
|
||||
@ -104,6 +112,7 @@ This project includes a full user interface for interacting with the system. Whe
|
||||
## REST API
|
||||
|
||||
This project includes a swagger specification. When you run the application, you can access the swagger specification by navigating to `http://localhost:3000/swagger` in your browser (for raw yaml see `http://localhost:3000/openapi.yaml`).
|
||||
For registry endpoints, currently swagger is located on different endpoint `http://localhost:3000/registry/swagger/` (for raw json see `http://localhost:3000/registry/swagger.json`). These will be later moved to the main swagger endpoint.
|
||||
|
||||
|
||||
For testing, it's simplest to just use the cli to create a token (this requires gitness server to run):
|
||||
|
@ -38,7 +38,8 @@ var (
|
||||
// Check checks if a resource specific permission is granted for the current auth session in the scope.
|
||||
// Returns nil if the permission is granted, otherwise returns an error.
|
||||
// NotAuthenticated, NotAuthorized, or any underlying error.
|
||||
func Check(ctx context.Context, authorizer authz.Authorizer, session *auth.Session,
|
||||
func Check(
|
||||
ctx context.Context, authorizer authz.Authorizer, session *auth.Session,
|
||||
scope *types.Scope, resource *types.Resource, permission enum.Permission,
|
||||
) error {
|
||||
authorized, err := authorizer.Check(
|
||||
@ -46,7 +47,31 @@ func Check(ctx context.Context, authorizer authz.Authorizer, session *auth.Sessi
|
||||
session,
|
||||
scope,
|
||||
resource,
|
||||
permission)
|
||||
permission,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !authorized {
|
||||
return ErrNotAuthorized
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckAll checks if multiple resources specific permission is granted for the current auth session in the scope.
|
||||
// Returns nil if the permission is granted, otherwise returns an error.
|
||||
// NotAuthenticated, NotAuthorized, or any underlying error.
|
||||
func CheckAll(
|
||||
ctx context.Context, authorizer authz.Authorizer, session *auth.Session,
|
||||
permissionChecks ...types.PermissionCheck,
|
||||
) error {
|
||||
authorized, err := authorizer.CheckAll(
|
||||
ctx,
|
||||
session,
|
||||
permissionChecks...,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -62,9 +87,11 @@ func Check(ctx context.Context, authorizer authz.Authorizer, session *auth.Sessi
|
||||
// in the scope of a parent.
|
||||
// Returns nil if the permission is granted, otherwise returns an error.
|
||||
// NotAuthenticated, NotAuthorized, or any underlying error.
|
||||
func CheckChild(ctx context.Context, authorizer authz.Authorizer, session *auth.Session,
|
||||
func CheckChild(
|
||||
ctx context.Context, authorizer authz.Authorizer, session *auth.Session,
|
||||
spaceStore store.SpaceStore, repoStore store.RepoStore, parentType enum.ParentResourceType, parentID int64,
|
||||
resourceType enum.ResourceType, resourceName string, permission enum.Permission) error {
|
||||
resourceType enum.ResourceType, resourceName string, permission enum.Permission,
|
||||
) error {
|
||||
scope, err := getScopeForParent(ctx, spaceStore, repoStore, parentType, parentID)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -79,8 +106,10 @@ func CheckChild(ctx context.Context, authorizer authz.Authorizer, session *auth.
|
||||
}
|
||||
|
||||
// getScopeForParent Returns the scope for a given resource parent (space or repo).
|
||||
func getScopeForParent(ctx context.Context, spaceStore store.SpaceStore, repoStore store.RepoStore,
|
||||
parentType enum.ParentResourceType, parentID int64) (*types.Scope, error) {
|
||||
func getScopeForParent(
|
||||
ctx context.Context, spaceStore store.SpaceStore, repoStore store.RepoStore,
|
||||
parentType enum.ParentResourceType, parentID int64,
|
||||
) (*types.Scope, error) {
|
||||
// TODO: Can this be done cleaner?
|
||||
switch parentType {
|
||||
case enum.ParentResourceTypeSpace:
|
||||
|
36
app/api/auth/registry.go
Normal file
36
app/api/auth/registry.go
Normal file
@ -0,0 +1,36 @@
|
||||
// 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 auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/harness/gitness/app/auth"
|
||||
"github.com/harness/gitness/app/auth/authz"
|
||||
"github.com/harness/gitness/types"
|
||||
)
|
||||
|
||||
// CheckRegistry checks if a registry specific permission is granted for the current auth session
|
||||
// in the scope of its parent.
|
||||
// Returns nil if the permission is granted, otherwise returns an error.
|
||||
// NotAuthenticated, NotAuthorized, or any underlying error.
|
||||
func CheckRegistry(
|
||||
ctx context.Context,
|
||||
authorizer authz.Authorizer,
|
||||
session *auth.Session,
|
||||
permissionChecks ...types.PermissionCheck,
|
||||
) error {
|
||||
return CheckAll(ctx, authorizer, session, permissionChecks...)
|
||||
}
|
@ -126,7 +126,7 @@ func enc(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error)
|
||||
}
|
||||
|
||||
// helper function returns the same secret with decrypted data.
|
||||
func dec(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) {
|
||||
func Dec(encrypt encrypt.Encrypter, secret *types.Secret) (*types.Secret, error) {
|
||||
if secret == nil {
|
||||
return nil, fmt.Errorf("cannot decrypt a nil secret")
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ func (c *Controller) Find(
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find secret: %w", err)
|
||||
}
|
||||
secret, err = dec(c.encrypter, secret)
|
||||
secret, err = Dec(c.encrypter, secret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not decrypt secret: %w", err)
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ func (c *Controller) Login(
|
||||
return nil, usererror.ErrNotFound
|
||||
}
|
||||
|
||||
tokenIdentifier, err := generateSessionTokenIdentifier()
|
||||
tokenIdentifier, err := GenerateSessionTokenIdentifier()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -81,7 +81,7 @@ func (c *Controller) Login(
|
||||
return &types.TokenResponse{Token: *token, AccessToken: jwtToken}, nil
|
||||
}
|
||||
|
||||
func generateSessionTokenIdentifier() (string, error) {
|
||||
func GenerateSessionTokenIdentifier() (string, error) {
|
||||
r, err := rand.Int(rand.Reader, big.NewInt(10000))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate random number: %w", err)
|
||||
|
@ -27,6 +27,7 @@ type ConfigOutput struct {
|
||||
PublicResourceCreationEnabled bool `json:"public_resource_creation_enabled"`
|
||||
SSHEnabled bool `json:"ssh_enabled"`
|
||||
GitspaceEnabled bool `json:"gitspace_enabled"`
|
||||
ArtifactRegistryEnabled bool `json:"artifact_registry_enabled"`
|
||||
}
|
||||
|
||||
// HandleGetConfig returns an http.HandlerFunc that processes an http.Request
|
||||
@ -46,6 +47,7 @@ func HandleGetConfig(config *types.Config, sysCtrl *system.Controller) http.Hand
|
||||
UserSignupAllowed: userSignupAllowed,
|
||||
PublicResourceCreationEnabled: config.PublicResourceCreationEnabled,
|
||||
GitspaceEnabled: config.Gitspace.Enable,
|
||||
ArtifactRegistryEnabled: config.Registry.Enable,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -28,21 +28,23 @@ import (
|
||||
// BlockSessionToken blocks any request that uses a session token for authentication.
|
||||
// NOTE: Major use case as of now is blocking usage of session tokens with git.
|
||||
func BlockSessionToken(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
// only block if auth data was available and it's based on a session token.
|
||||
if session, oks := request.AuthSessionFrom(ctx); oks {
|
||||
if tokenMetadata, okt := session.Metadata.(*auth.TokenMetadata); okt &&
|
||||
tokenMetadata.TokenType == enum.TokenTypeSession {
|
||||
log.Ctx(ctx).Warn().Msg("blocking git operation - session tokens are not allowed for usage with git")
|
||||
// only block if auth data was available and it's based on a session token.
|
||||
if session, oks := request.AuthSessionFrom(ctx); oks {
|
||||
if tokenMetadata, ok := session.Metadata.(*auth.TokenMetadata); ok &&
|
||||
tokenMetadata.TokenType == enum.TokenTypeSession {
|
||||
log.Ctx(ctx).Warn().Msg("blocking git operation - session tokens are not allowed for usage with git")
|
||||
|
||||
// NOTE: Git doesn't print the error message, so just return default 401 Unauthorized.
|
||||
render.Unauthorized(ctx, w)
|
||||
return
|
||||
// NOTE: Git doesn't print the error message, so just return default 401 Unauthorized.
|
||||
render.Unauthorized(ctx, w)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
next.ServeHTTP(w, r)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
@ -62,13 +62,15 @@ func (a *JWTAuthenticator) Authenticate(r *http.Request) (*auth.Session, error)
|
||||
var principal *types.Principal
|
||||
var err error
|
||||
claims := &jwt.Claims{}
|
||||
parsed, err := gojwt.ParseWithClaims(str, claims, func(_ *gojwt.Token) (interface{}, error) {
|
||||
principal, err = a.principalStore.Find(ctx, claims.PrincipalID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get principal for token: %w", err)
|
||||
}
|
||||
return []byte(principal.Salt), nil
|
||||
})
|
||||
parsed, err := gojwt.ParseWithClaims(
|
||||
str, claims, func(_ *gojwt.Token) (interface{}, error) {
|
||||
principal, err = a.principalStore.Find(ctx, claims.PrincipalID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get principal for token: %w", err)
|
||||
}
|
||||
return []byte(principal.Salt), nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing of JWT claims failed: %w", err)
|
||||
}
|
||||
@ -90,6 +92,8 @@ func (a *JWTAuthenticator) Authenticate(r *http.Request) (*auth.Session, error)
|
||||
}
|
||||
case claims.Membership != nil:
|
||||
metadata = a.metadataFromMembershipClaims(claims.Membership)
|
||||
case claims.AccessPermissions != nil:
|
||||
metadata = a.metadataFromAccessPermissions(claims.AccessPermissions)
|
||||
default:
|
||||
return nil, fmt.Errorf("jwt is missing sub-claims")
|
||||
}
|
||||
@ -113,8 +117,10 @@ func (a *JWTAuthenticator) metadataFromTokenClaims(
|
||||
|
||||
// protect against faked JWTs for other principals in case of single salt leak
|
||||
if principal.ID != tkn.PrincipalID {
|
||||
return nil, fmt.Errorf("JWT was for principal %d while db token was for principal %d",
|
||||
principal.ID, tkn.PrincipalID)
|
||||
return nil, fmt.Errorf(
|
||||
"JWT was for principal %d while db token was for principal %d",
|
||||
principal.ID, tkn.PrincipalID,
|
||||
)
|
||||
}
|
||||
|
||||
return &auth.TokenMetadata{
|
||||
@ -133,6 +139,14 @@ func (a *JWTAuthenticator) metadataFromMembershipClaims(
|
||||
}
|
||||
}
|
||||
|
||||
func (a *JWTAuthenticator) metadataFromAccessPermissions(
|
||||
s *jwt.SubClaimsAccessPermissions,
|
||||
) auth.Metadata {
|
||||
return &auth.AccessPermissionMetadata{
|
||||
AccessPermissions: s,
|
||||
}
|
||||
}
|
||||
|
||||
func extractToken(r *http.Request, cookieName string) string {
|
||||
// Check query param first (as that's most immediately visible to caller)
|
||||
if queryToken, ok := request.GetAccessTokenFromQuery(r); ok {
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var _ Authorizer = (*MembershipAuthorizer)(nil)
|
||||
@ -110,6 +111,9 @@ func (a *MembershipAuthorizer) Check(
|
||||
case enum.ResourceTypeInfraProvider:
|
||||
spacePath = scope.SpacePath
|
||||
|
||||
case enum.ResourceTypeRegistry:
|
||||
spacePath = scope.SpacePath
|
||||
|
||||
case enum.ResourceTypeUser:
|
||||
// a user is allowed to edit themselves
|
||||
if resource.Identifier == session.Principal.UID &&
|
||||
@ -138,20 +142,29 @@ func (a *MembershipAuthorizer) Check(
|
||||
return a.checkWithMembershipMetadata(ctx, membershipMetadata, spacePath, permission)
|
||||
}
|
||||
|
||||
// accessPermissionMetadata contains the access permissions of per space
|
||||
if accessPermissionMetadata, ok := session.Metadata.(*auth.AccessPermissionMetadata); ok {
|
||||
return a.checkWithAccessPermissionMetadata(ctx, accessPermissionMetadata, spacePath, permission)
|
||||
}
|
||||
|
||||
// ensure we aren't bypassing unknown metadata with impact on authorization
|
||||
if session.Metadata != nil && session.Metadata.ImpactsAuthorization() {
|
||||
return false, fmt.Errorf("session contains unknown metadata that impacts authorization: %T", session.Metadata)
|
||||
}
|
||||
|
||||
return a.permissionCache.Get(ctx, PermissionCacheKey{
|
||||
PrincipalID: session.Principal.ID,
|
||||
SpaceRef: spacePath,
|
||||
Permission: permission,
|
||||
})
|
||||
return a.permissionCache.Get(
|
||||
ctx, PermissionCacheKey{
|
||||
PrincipalID: session.Principal.ID,
|
||||
SpaceRef: spacePath,
|
||||
Permission: permission,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (a *MembershipAuthorizer) CheckAll(ctx context.Context, session *auth.Session,
|
||||
permissionChecks ...types.PermissionCheck) (bool, error) {
|
||||
func (a *MembershipAuthorizer) CheckAll(
|
||||
ctx context.Context, session *auth.Session,
|
||||
permissionChecks ...types.PermissionCheck,
|
||||
) (bool, error) {
|
||||
for i := range permissionChecks {
|
||||
p := permissionChecks[i]
|
||||
if _, err := a.Check(ctx, session, &p.Scope, &p.Resource, p.Permission); err != nil {
|
||||
@ -193,3 +206,28 @@ func (a *MembershipAuthorizer) checkWithMembershipMetadata(
|
||||
// access is granted by ephemeral membership
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// checkWithAccessPermissionMetadata checks access using the ephemeral membership provided in the metadata.
|
||||
func (a *MembershipAuthorizer) checkWithAccessPermissionMetadata(
|
||||
ctx context.Context,
|
||||
accessPermissionMetadata *auth.AccessPermissionMetadata,
|
||||
requestedSpacePath string,
|
||||
requestedPermission enum.Permission,
|
||||
) (bool, error) {
|
||||
space, err := a.spaceStore.FindByRef(ctx, requestedSpacePath)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to find space by ref: %w", err)
|
||||
}
|
||||
|
||||
if accessPermissionMetadata.AccessPermissions.Permissions == nil {
|
||||
return false, fmt.Errorf("no %s permission provided", requestedPermission)
|
||||
}
|
||||
|
||||
for _, accessPermission := range accessPermissionMetadata.AccessPermissions.Permissions {
|
||||
if space.ID == accessPermission.SpaceID && slices.Contains(accessPermission.Permissions, requestedPermission) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("no %s permission provided", requestedPermission)
|
||||
}
|
||||
|
@ -14,7 +14,10 @@
|
||||
|
||||
package auth
|
||||
|
||||
import "github.com/harness/gitness/types/enum"
|
||||
import (
|
||||
"github.com/harness/gitness/app/jwt"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
type Metadata interface {
|
||||
ImpactsAuthorization() bool
|
||||
@ -46,3 +49,12 @@ type MembershipMetadata struct {
|
||||
func (m *MembershipMetadata) ImpactsAuthorization() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// AccessPermissionMetadata contains information about permissions per space.
|
||||
type AccessPermissionMetadata struct {
|
||||
AccessPermissions *jwt.SubClaimsAccessPermissions
|
||||
}
|
||||
|
||||
func (m *AccessPermissionMetadata) ImpactsAuthorization() bool {
|
||||
return true
|
||||
}
|
||||
|
@ -15,27 +15,35 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
issuer = "Gitness"
|
||||
)
|
||||
|
||||
// Source represents the source of the SubClaimsAccessPermissions.
|
||||
type Source string
|
||||
|
||||
const (
|
||||
OciSource Source = "oci"
|
||||
)
|
||||
|
||||
// Claims defines gitness jwt claims.
|
||||
type Claims struct {
|
||||
jwt.StandardClaims
|
||||
|
||||
PrincipalID int64 `json:"pid,omitempty"`
|
||||
|
||||
Token *SubClaimsToken `json:"tkn,omitempty"`
|
||||
Membership *SubClaimsMembership `json:"ms,omitempty"`
|
||||
Token *SubClaimsToken `json:"tkn,omitempty"`
|
||||
Membership *SubClaimsMembership `json:"ms,omitempty"`
|
||||
AccessPermissions *SubClaimsAccessPermissions `json:"ap,omitempty"`
|
||||
}
|
||||
|
||||
// SubClaimsToken contains information about the token the JWT was created for.
|
||||
@ -50,6 +58,18 @@ type SubClaimsMembership struct {
|
||||
SpaceID int64 `json:"sid,omitempty"`
|
||||
}
|
||||
|
||||
// SubClaimsAccessPermissions stores allowed actions on a resource.
|
||||
type SubClaimsAccessPermissions struct {
|
||||
Source Source `json:"src,omitempty"`
|
||||
Permissions []AccessPermissions `json:"permissions,omitempty"`
|
||||
}
|
||||
|
||||
// AccessPermissions stores allowed actions on a resource.
|
||||
type AccessPermissions struct {
|
||||
SpaceID int64 `json:"sid,omitempty"`
|
||||
Permissions []enum.Permission `json:"p"`
|
||||
}
|
||||
|
||||
// GenerateForToken generates a jwt for a given token.
|
||||
func GenerateForToken(token *types.Token, secret string) (string, error) {
|
||||
var expiresAt int64
|
||||
@ -73,7 +93,7 @@ func GenerateForToken(token *types.Token, secret string) (string, error) {
|
||||
|
||||
res, err := jwtToken.SignedString([]byte(secret))
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Failed to sign token")
|
||||
return "", fmt.Errorf("failed to sign token: %w", err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
@ -106,7 +126,37 @@ func GenerateWithMembership(
|
||||
|
||||
res, err := jwtToken.SignedString([]byte(secret))
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Failed to sign token")
|
||||
return "", fmt.Errorf("failed to sign token: %w", err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GenerateForTokenWithAccessPermissions generates a jwt for a given token.
|
||||
func GenerateForTokenWithAccessPermissions(
|
||||
principalID int64,
|
||||
lifetime *time.Duration,
|
||||
secret string, accessPermissions *SubClaimsAccessPermissions,
|
||||
) (string, error) {
|
||||
issuedAt := time.Now()
|
||||
if lifetime == nil {
|
||||
return "", fmt.Errorf("token lifetime is required")
|
||||
}
|
||||
expiresAt := issuedAt.Add(*lifetime)
|
||||
|
||||
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, Claims{
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
Issuer: issuer,
|
||||
IssuedAt: issuedAt.Unix(),
|
||||
ExpiresAt: expiresAt.Unix(),
|
||||
},
|
||||
PrincipalID: principalID,
|
||||
AccessPermissions: accessPermissions,
|
||||
})
|
||||
|
||||
res, err := jwtToken.SignedString([]byte(secret))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to sign token: %w", err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
|
@ -48,6 +48,8 @@ import (
|
||||
"github.com/harness/gitness/app/auth/authn"
|
||||
"github.com/harness/gitness/app/url"
|
||||
"github.com/harness/gitness/git"
|
||||
"github.com/harness/gitness/registry/app/api"
|
||||
"github.com/harness/gitness/registry/app/api/router"
|
||||
"github.com/harness/gitness/types"
|
||||
|
||||
"github.com/google/wire"
|
||||
@ -56,6 +58,7 @@ import (
|
||||
// WireSet provides a wire set for this package.
|
||||
var WireSet = wire.NewSet(
|
||||
ProvideRouter,
|
||||
api.WireSet,
|
||||
)
|
||||
|
||||
func GetGitRoutingHost(ctx context.Context, urlProvider url.Provider) string {
|
||||
@ -106,8 +109,9 @@ func ProvideRouter(
|
||||
capabilitiesCtrl *capabilities.Controller,
|
||||
urlProvider url.Provider,
|
||||
openapi openapi.Service,
|
||||
registryRouter router.AppRouter,
|
||||
) *Router {
|
||||
routers := make([]Interface, 3)
|
||||
routers := make([]Interface, 4)
|
||||
|
||||
gitRoutingHost := GetGitRoutingHost(appCtx, urlProvider)
|
||||
gitHandler := NewGitHandler(
|
||||
@ -116,16 +120,18 @@ func ProvideRouter(
|
||||
repoCtrl,
|
||||
)
|
||||
routers[0] = NewGitRouter(gitHandler, gitRoutingHost)
|
||||
routers[1] = router.NewRegistryRouter(registryRouter)
|
||||
|
||||
apiHandler := NewAPIHandler(appCtx, config,
|
||||
apiHandler := NewAPIHandler(
|
||||
appCtx, config,
|
||||
authenticator, repoCtrl, repoSettingsCtrl, executionCtrl, logCtrl, spaceCtrl, pipelineCtrl,
|
||||
secretCtrl, triggerCtrl, connectorCtrl, templateCtrl, pluginCtrl, pullreqCtrl, webhookCtrl,
|
||||
githookCtrl, git, saCtrl, userCtrl, principalCtrl, checkCtrl, sysCtrl, blobCtrl, searchCtrl,
|
||||
infraProviderCtrl, migrateCtrl, gitspaceCtrl, aiagentCtrl, capabilitiesCtrl)
|
||||
routers[1] = NewAPIRouter(apiHandler)
|
||||
routers[2] = NewAPIRouter(apiHandler)
|
||||
|
||||
webHandler := NewWebHandler(config, authenticator, openapi)
|
||||
routers[2] = NewWebRouter(webHandler)
|
||||
routers[3] = NewWebRouter(webHandler)
|
||||
|
||||
return NewRouter(routers)
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
DROP TABLE registries;
|
||||
DROP TABLE media_types;
|
||||
DROP TABLE blobs;
|
||||
DROP TABLE registry_blobs;
|
||||
DROP TABLE manifests;
|
||||
DROP TABLE manifest_references;
|
||||
DROP TABLE layers;
|
||||
DROP TABLE artifacts;
|
||||
DROP TABLE artifact_stats;
|
||||
DROP TABLE tags;
|
||||
DROP TABLE upstream_proxy_configs;
|
||||
DROP TABLE cleanup_policies;
|
||||
DROP TABLE cleanup_policy_prefix_mappings;
|
561
app/store/database/migrate/postgres/0066_create_ar_tables.up.sql
Normal file
561
app/store/database/migrate/postgres/0066_create_ar_tables.up.sql
Normal file
@ -0,0 +1,561 @@
|
||||
create table registries
|
||||
(
|
||||
registry_id SERIAL primary key,
|
||||
registry_name text not null
|
||||
constraint registry_name_len_check
|
||||
check (length(registry_name) <= 255),
|
||||
registry_root_parent_id INTEGER not null,
|
||||
registry_parent_id INTEGER not null,
|
||||
registry_description text,
|
||||
registry_type text not null,
|
||||
registry_package_type text not null,
|
||||
registry_upstream_proxies text,
|
||||
registry_allowed_pattern text,
|
||||
registry_blocked_pattern text,
|
||||
registry_created_at BIGINT not null,
|
||||
registry_updated_at BIGINT not null,
|
||||
registry_created_by INTEGER not null,
|
||||
registry_updated_by INTEGER not null,
|
||||
registry_labels text,
|
||||
constraint unique_registries
|
||||
unique (registry_root_parent_id, registry_name)
|
||||
);
|
||||
|
||||
|
||||
create table media_types
|
||||
(
|
||||
mt_id SERIAL primary key,
|
||||
mt_media_type text not null
|
||||
constraint unique_media_types_type
|
||||
unique,
|
||||
mt_created_at BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM now()) * 1000)::BIGINT
|
||||
);
|
||||
|
||||
create table blobs
|
||||
(
|
||||
blob_id SERIAL primary key,
|
||||
blob_root_parent_id INTEGER not null,
|
||||
blob_digest bytea not null,
|
||||
blob_media_type_id INTEGER not null
|
||||
constraint fk_blobs_media_type_id_media_types
|
||||
references media_types(mt_id),
|
||||
blob_size BIGINT not null,
|
||||
blob_created_at BIGINT not null,
|
||||
blob_created_by INTEGER not null,
|
||||
constraint unique_digest_root_parent_id unique (blob_digest, blob_root_parent_id)
|
||||
);
|
||||
|
||||
create index index_blobs_on_media_type_id
|
||||
on blobs (blob_media_type_id);
|
||||
|
||||
create table registry_blobs
|
||||
(
|
||||
rblob_id SERIAL primary key,
|
||||
rblob_registry_id INTEGER not null
|
||||
constraint fk_registry_blobs_rpstry_id_registries
|
||||
references registries
|
||||
on delete cascade,
|
||||
rblob_blob_id INTEGER not null
|
||||
constraint fk_registry_blobs_blob_id_blobs
|
||||
references blobs
|
||||
on delete cascade,
|
||||
rblob_image_name text
|
||||
constraint registry_blobs_image_len_check
|
||||
check (length(rblob_image_name) <= 255),
|
||||
rblob_created_at BIGINT not null,
|
||||
rblob_updated_at BIGINT not null,
|
||||
rblob_created_by INTEGER not null,
|
||||
rblob_updated_by INTEGER not null,
|
||||
|
||||
constraint unique_registry_blobs_registry_id_blob_id_image
|
||||
unique (rblob_registry_id, rblob_blob_id, rblob_image_name)
|
||||
);
|
||||
|
||||
create index index_registry_blobs_on_reg_id
|
||||
on registry_blobs (rblob_registry_id);
|
||||
|
||||
create index index_registry_blobs_on_reg_blob_id
|
||||
on registry_blobs (rblob_registry_id, rblob_blob_id);
|
||||
|
||||
create table manifests
|
||||
(
|
||||
manifest_id SERIAL primary key,
|
||||
manifest_registry_id INTEGER not null
|
||||
constraint fk_manifests_registry_id_registries
|
||||
references registries(registry_id)
|
||||
on delete cascade,
|
||||
manifest_schema_version smallint not null,
|
||||
manifest_media_type_id INTEGER not null
|
||||
constraint fk_manifests_media_type_id_media_types
|
||||
references media_types(mt_id),
|
||||
manifest_artifact_media_type text,
|
||||
manifest_total_size BIGINT not null,
|
||||
manifest_configuration_media_type text,
|
||||
manifest_configuration_payload bytea,
|
||||
manifest_configuration_blob_id INTEGER
|
||||
constraint fk_manifests_configuration_blob_id_blobs
|
||||
references blobs(blob_id),
|
||||
manifest_configuration_digest bytea,
|
||||
manifest_digest bytea not null,
|
||||
manifest_payload bytea not null,
|
||||
manifest_non_conformant boolean default false,
|
||||
manifest_non_distributable_layers boolean default false,
|
||||
manifest_subject_id INTEGER,
|
||||
manifest_subject_digest bytea,
|
||||
manifest_annotations bytea,
|
||||
manifest_image_name text not null
|
||||
constraint manifests_img_name_len_check
|
||||
check (length(manifest_image_name) <= 255),
|
||||
manifest_created_at BIGINT not null,
|
||||
manifest_created_by INTEGER not null,
|
||||
manifest_updated_at BIGINT not null,
|
||||
manifest_updated_by INTEGER not null,
|
||||
constraint unique_manifests_registry_id_image_name_and_digest
|
||||
unique (manifest_registry_id, manifest_image_name, manifest_digest),
|
||||
constraint unique_manifests_registry_id_id_cfg_blob_id
|
||||
unique (manifest_registry_id, manifest_id, manifest_configuration_blob_id),
|
||||
constraint fk_manifests_subject_id_manifests
|
||||
foreign key (manifest_subject_id) references manifests
|
||||
on delete cascade
|
||||
);
|
||||
|
||||
create index index_manifests_on_media_type_id
|
||||
on manifests (manifest_media_type_id);
|
||||
|
||||
create index index_manifests_on_configuration_blob_id
|
||||
on manifests (manifest_configuration_blob_id);
|
||||
|
||||
create table manifest_references
|
||||
(
|
||||
manifest_ref_id SERIAL primary key,
|
||||
manifest_ref_registry_id INTEGER not null,
|
||||
manifest_ref_parent_id INTEGER not null,
|
||||
manifest_ref_child_id INTEGER not null,
|
||||
manifest_ref_created_at BIGINT not null,
|
||||
manifest_ref_updated_at BIGINT not null,
|
||||
manifest_ref_created_by INTEGER not null,
|
||||
manifest_ref_updated_by INTEGER not null,
|
||||
constraint unique_manifest_references_prt_id_chd_id
|
||||
unique (manifest_ref_registry_id, manifest_ref_parent_id, manifest_ref_child_id),
|
||||
constraint fk_manifest_references_parent_id_mnfsts
|
||||
foreign key (manifest_ref_parent_id) references manifests
|
||||
on delete cascade,
|
||||
constraint fk_manifest_references_child_id_mnfsts
|
||||
foreign key (manifest_ref_child_id) references manifests,
|
||||
constraint check_manifest_references_parent_id_and_child_id_differ
|
||||
check (manifest_ref_parent_id <> manifest_ref_child_id)
|
||||
);
|
||||
|
||||
create index index_manifest_references_on_rpstry_id_child_id
|
||||
on manifest_references (manifest_ref_registry_id, manifest_ref_child_id);
|
||||
|
||||
create table layers
|
||||
(
|
||||
layer_id SERIAL primary key,
|
||||
layer_registry_id INTEGER not null,
|
||||
layer_manifest_id INTEGER not null,
|
||||
layer_media_type_id INTEGER not null
|
||||
constraint fk_layer_media_type_id_media_types
|
||||
references media_types,
|
||||
layer_blob_id INTEGER not null
|
||||
constraint fk_layer_blob_id_blobs
|
||||
references blobs,
|
||||
layer_size BIGINT not null,
|
||||
layer_created_at BIGINT not null,
|
||||
layer_updated_at BIGINT not null,
|
||||
layer_created_by INTEGER not null,
|
||||
layer_updated_by INTEGER not null,
|
||||
constraint unique_layer_rpstry_id_and_mnfst_id_and_blob_id
|
||||
unique (layer_registry_id, layer_manifest_id, layer_blob_id),
|
||||
constraint unique_layer_rpstry_id_and_id_and_blob_id
|
||||
unique (layer_registry_id, layer_id, layer_blob_id),
|
||||
constraint fk_manifst_id_manifests
|
||||
foreign key (layer_manifest_id) references manifests(manifest_id)
|
||||
on delete cascade
|
||||
);
|
||||
|
||||
create index index_layer_on_media_type_id
|
||||
on layers (layer_media_type_id);
|
||||
|
||||
create index index_layer_on_blob_id
|
||||
on layers (layer_blob_id);
|
||||
|
||||
create table artifacts
|
||||
(
|
||||
artifact_id SERIAL primary key,
|
||||
artifact_name text not null,
|
||||
artifact_registry_id INTEGER not null
|
||||
constraint fk_registries_registry_id
|
||||
references registries(registry_id)
|
||||
on delete cascade,
|
||||
artifact_labels text,
|
||||
artifact_enabled boolean default false,
|
||||
artifact_created_at BIGINT,
|
||||
artifact_updated_at BIGINT,
|
||||
artifact_created_by INTEGER,
|
||||
artifact_updated_by INTEGER,
|
||||
constraint unique_artifact_registry_id_and_name unique (artifact_registry_id, artifact_name),
|
||||
constraint check_artifact_name_length check ((char_length(artifact_name) <= 255))
|
||||
);
|
||||
|
||||
create index index_artifact_on_registry_id ON artifacts USING btree (artifact_registry_id);
|
||||
|
||||
|
||||
create table artifact_stats
|
||||
(
|
||||
artifact_stat_id SERIAL primary key,
|
||||
artifact_stat_artifact_id INTEGER not null
|
||||
constraint fk_artifacts_artifact_id
|
||||
references artifacts(artifact_id),
|
||||
artifact_stat_date BIGINT,
|
||||
artifact_stat_download_count BIGINT,
|
||||
artifact_stat_upload_bytes BIGINT,
|
||||
artifact_stat_download_bytes BIGINT,
|
||||
artifact_stat_created_at BIGINT not null,
|
||||
artifact_stat_updated_at BIGINT not null,
|
||||
artifact_stat_created_by INTEGER not null,
|
||||
artifact_stat_updated_by INTEGER not null,
|
||||
constraint unique_artifact_stats_artifact_id_and_date unique (artifact_stat_artifact_id, artifact_stat_date)
|
||||
);
|
||||
|
||||
create table tags
|
||||
(
|
||||
tag_id SERIAL primary key,
|
||||
tag_name text not null
|
||||
constraint tag_name_len_check
|
||||
check (char_length(tag_name) <= 128),
|
||||
tag_image_name text not null
|
||||
constraint tag_img_name_len_check
|
||||
check (length(tag_image_name) <= 255),
|
||||
tag_registry_id INTEGER not null,
|
||||
tag_manifest_id INTEGER not null,
|
||||
tag_created_at BIGINT,
|
||||
tag_updated_at BIGINT,
|
||||
tag_created_by INTEGER,
|
||||
tag_updated_by INTEGER,
|
||||
constraint fk_tag_manifest_id_manifests FOREIGN KEY
|
||||
(tag_manifest_id) REFERENCES manifests (manifest_id) ON DELETE CASCADE,
|
||||
constraint unique_tag_registry_id_and_name_and_image_name
|
||||
unique (tag_registry_id, tag_name, tag_image_name)
|
||||
);
|
||||
|
||||
create index index_tag_on_rpository_id_and_manifest_id
|
||||
on tags (tag_registry_id, tag_manifest_id);
|
||||
|
||||
create table upstream_proxy_configs
|
||||
(
|
||||
upstream_proxy_config_id SERIAL primary key,
|
||||
upstream_proxy_config_registry_id INTEGER not null
|
||||
constraint fk_upstream_proxy_config_registry_id
|
||||
references registries
|
||||
on delete cascade,
|
||||
upstream_proxy_config_source text,
|
||||
upstream_proxy_config_url text,
|
||||
upstream_proxy_config_auth_type text not null,
|
||||
upstream_proxy_config_user_name text,
|
||||
upstream_proxy_config_secret_identifier text,
|
||||
upstream_proxy_config_secret_space_id INTEGER,
|
||||
constraint fk_layers_secret_identifier_and_secret_space_id
|
||||
foreign key (upstream_proxy_config_secret_identifier, upstream_proxy_config_secret_space_id)
|
||||
references secrets(secret_uid, secret_space_id)
|
||||
on delete cascade,
|
||||
upstream_proxy_config_token text,
|
||||
upstream_proxy_config_created_at BIGINT,
|
||||
upstream_proxy_config_updated_at BIGINT,
|
||||
upstream_proxy_config_created_by INTEGER,
|
||||
upstream_proxy_config_updated_by INTEGER
|
||||
);
|
||||
|
||||
create index index_upstream_proxy_config_on_registry_id
|
||||
on upstream_proxy_configs (upstream_proxy_config_registry_id);
|
||||
|
||||
create table cleanup_policies
|
||||
(
|
||||
cp_id SERIAL primary key,
|
||||
cp_registry_id INTEGER not null
|
||||
constraint fk_cleanup_policies_registry_id
|
||||
references registries ON DELETE CASCADE,
|
||||
cp_name text,
|
||||
cp_expiry_time_ms BIGINT,
|
||||
cp_created_at BIGINT not null,
|
||||
cp_updated_at BIGINT not null,
|
||||
cp_created_by INTEGER not null,
|
||||
cp_updated_by INTEGER not null
|
||||
);
|
||||
|
||||
create index index_cleanup_policies_on_registry_id
|
||||
on cleanup_policies (cp_registry_id);
|
||||
|
||||
create table cleanup_policy_prefix_mappings
|
||||
(
|
||||
cpp_id SERIAL primary key,
|
||||
cpp_cleanup_policy_id INTEGER not null
|
||||
constraint fk_cleanup_policies_id
|
||||
references cleanup_policies(cp_id) ON DELETE CASCADE,
|
||||
cpp_prefix text not null,
|
||||
cpp_prefix_type text not null
|
||||
);
|
||||
|
||||
create index index_cleanup_policy_map_on_policy_id
|
||||
on cleanup_policy_prefix_mappings (cpp_cleanup_policy_id);
|
||||
|
||||
insert into media_types (mt_media_type)
|
||||
values ('application/vnd.docker.distribution.manifest.v1+json'),
|
||||
('application/vnd.docker.distribution.manifest.v1+prettyjws'),
|
||||
('application/vnd.docker.distribution.manifest.v2+json'),
|
||||
('application/vnd.docker.distribution.manifest.list.v2+json'),
|
||||
('application/vnd.docker.image.rootfs.diff.tar'),
|
||||
('application/vnd.docker.image.rootfs.diff.tar.gzip'),
|
||||
('application/vnd.docker.image.rootfs.foreign.diff.tar.gzip'),
|
||||
('application/vnd.docker.container.image.v1+json'),
|
||||
('application/vnd.docker.container.image.rootfs.diff+x-gtar'),
|
||||
('application/vnd.docker.plugin.v1+json'),
|
||||
('application/vnd.oci.image.layer.v1.tar'),
|
||||
('application/vnd.oci.image.layer.v1.tar+gzip'),
|
||||
('application/vnd.oci.image.layer.v1.tar+zstd'),
|
||||
('application/vnd.oci.image.layer.nondistributable.v1.tar'),
|
||||
('application/vnd.oci.image.layer.nondistributable.v1.tar+gzip'),
|
||||
('application/vnd.oci.image.config.v1+json'),
|
||||
('application/vnd.oci.image.manifest.v1+json'),
|
||||
('application/vnd.oci.image.index.v1+json'),
|
||||
('application/vnd.cncf.helm.config.v1+json'),
|
||||
('application/tar+gzip'),
|
||||
('application/octet-stream'),
|
||||
('application/vnd.buildkit.cacheconfig.v0'),
|
||||
('application/vnd.cncf.helm.chart.content.v1.tar+gzip'),
|
||||
('application/vnd.cncf.helm.chart.provenance.v1.prov');
|
||||
|
||||
|
||||
CREATE TABLE gc_blob_review_queue
|
||||
(
|
||||
blob_id INTEGER NOT NULL,
|
||||
review_after BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM (NOW() + INTERVAL '1 day'))),
|
||||
review_count INTEGER NOT NULL DEFAULT 0,
|
||||
created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()),
|
||||
event text NOT NULL,
|
||||
CONSTRAINT pk_gc_blob_review_queue primary key (blob_id)
|
||||
);
|
||||
|
||||
CREATE INDEX index_gc_blob_review_queue_on_review_after ON gc_blob_review_queue USING btree (review_after);
|
||||
|
||||
CREATE TABLE gc_review_after_defaults
|
||||
(
|
||||
event text NOT NULL,
|
||||
value interval NOT NULL,
|
||||
CONSTRAINT pk_gc_review_after_defaults PRIMARY KEY (event),
|
||||
CONSTRAINT check_gc_review_after_defaults_event_length CHECK ((char_length(event) <= 255))
|
||||
);
|
||||
|
||||
INSERT INTO gc_review_after_defaults (event, value)
|
||||
VALUES ('blob_upload', interval '1 day'),
|
||||
('manifest_upload', interval '1 day'),
|
||||
('manifest_delete', interval '1 day'),
|
||||
('layer_delete', interval '1 day'),
|
||||
('manifest_list_delete', interval '1 day'),
|
||||
('tag_delete', interval '1 day'),
|
||||
('tag_switch', interval '1 day')
|
||||
ON CONFLICT (event)
|
||||
DO NOTHING;
|
||||
|
||||
CREATE TABLE gc_manifest_review_queue
|
||||
(
|
||||
registry_id INTEGER NOT NULL,
|
||||
manifest_id INTEGER NOT NULL,
|
||||
review_after BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM (NOW() + INTERVAL '1 day'))),
|
||||
review_count INTEGER NOT NULL DEFAULT 0,
|
||||
created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()),
|
||||
event text NOT NULL,
|
||||
CONSTRAINT pk_gc_manifest_review_queue PRIMARY KEY (registry_id, manifest_id),
|
||||
CONSTRAINT fk_gc_manifest_review_queue_rp_id_mfst_id_mnfsts FOREIGN KEY (manifest_id) REFERENCES manifests (manifest_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX index_gc_manifest_review_queue_on_review_after ON gc_manifest_review_queue USING btree (review_after);
|
||||
|
||||
CREATE OR REPLACE FUNCTION gc_review_after(e text)
|
||||
RETURNS BIGINT
|
||||
VOLATILE
|
||||
AS
|
||||
$$
|
||||
DECLARE
|
||||
result timestamp WITH time zone;
|
||||
BEGIN
|
||||
SELECT (now() + value)
|
||||
INTO result
|
||||
FROM gc_review_after_defaults
|
||||
WHERE event = e;
|
||||
IF result IS NULL THEN
|
||||
RETURN EXTRACT(EPOCH FROM (now() + interval '1 day'));
|
||||
ELSE
|
||||
RETURN EXTRACT(EPOCH FROM result);
|
||||
END IF;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION gc_track_blob_uploads()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
BEGIN
|
||||
INSERT INTO gc_blob_review_queue (blob_id, review_after, event)
|
||||
VALUES (NEW.blob_id, gc_review_after('blob_upload'), 'blob_upload')
|
||||
ON CONFLICT (blob_id)
|
||||
DO UPDATE SET review_after = gc_review_after('blob_upload'),
|
||||
event = 'blob_upload';
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER gc_track_blob_uploads_trigger
|
||||
AFTER INSERT
|
||||
ON blobs
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE public.gc_track_blob_uploads();
|
||||
|
||||
CREATE OR REPLACE FUNCTION gc_track_manifest_uploads()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
BEGIN
|
||||
INSERT INTO gc_manifest_review_queue (registry_id, manifest_id, review_after, event)
|
||||
VALUES (NEW.manifest_registry_id, NEW.manifest_id, gc_review_after('manifest_upload'), 'manifest_upload');
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER gc_track_manifest_uploads_trigger
|
||||
AFTER INSERT
|
||||
ON manifests
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE gc_track_manifest_uploads();
|
||||
|
||||
CREATE OR REPLACE FUNCTION gc_track_deleted_manifests()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
BEGIN
|
||||
IF OLD.manifest_configuration_blob_id IS NOT NULL THEN -- not all manifests have a configuration
|
||||
INSERT INTO gc_blob_review_queue (blob_id, review_after, event)
|
||||
VALUES (OLD.manifest_configuration_blob_id, gc_review_after('manifest_delete'), 'manifest_delete')
|
||||
ON CONFLICT (blob_id)
|
||||
DO UPDATE SET
|
||||
review_after = gc_review_after('manifest_delete'),
|
||||
event = 'manifest_delete';
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION gc_track_deleted_layers()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
BEGIN
|
||||
IF (TG_LEVEL = 'STATEMENT') THEN
|
||||
INSERT INTO gc_blob_review_queue (blob_id, review_after, event)
|
||||
SELECT deleted_rows.layer_blob_id,
|
||||
gc_review_after('layer_delete'),
|
||||
'layer_delete'
|
||||
FROM old_table deleted_rows
|
||||
JOIN
|
||||
blobs b ON deleted_rows.layer_blob_id = b.blob_id
|
||||
ORDER BY deleted_rows.layer_blob_id ASC
|
||||
ON CONFLICT (blob_id)
|
||||
DO UPDATE SET review_after = gc_review_after('layer_delete'),
|
||||
event = 'layer_delete';
|
||||
ELSIF (TG_LEVEL = 'ROW') THEN
|
||||
INSERT INTO gc_blob_review_queue (blob_id, review_after, event)
|
||||
VALUES (OLD.blob_id, gc_review_after('layer_delete'), 'layer_delete')
|
||||
ON CONFLICT (blob_id)
|
||||
DO UPDATE SET review_after = gc_review_after('layer_delete'),
|
||||
event = 'layer_delete';
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER gc_track_deleted_manifests_trigger
|
||||
AFTER DELETE
|
||||
ON manifests
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE gc_track_deleted_manifests();
|
||||
|
||||
CREATE TRIGGER gc_track_deleted_layers_trigger
|
||||
AFTER DELETE
|
||||
ON layers
|
||||
REFERENCING OLD TABLE AS old_table
|
||||
FOR EACH STATEMENT
|
||||
EXECUTE FUNCTION gc_track_deleted_layers();
|
||||
|
||||
CREATE OR REPLACE FUNCTION gc_track_deleted_manifest_lists()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
BEGIN
|
||||
INSERT INTO gc_manifest_review_queue (registry_id, manifest_id, review_after, event)
|
||||
VALUES (OLD.manifest_ref_registry_id, OLD.manifest_ref_child_id, gc_review_after('manifest_list_delete'), 'manifest_list_delete')
|
||||
ON CONFLICT (registry_id, manifest_id)
|
||||
DO UPDATE SET review_after = gc_review_after('manifest_list_delete'),
|
||||
event = 'manifest_list_delete';
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER gc_track_deleted_manifest_lists_trigger
|
||||
AFTER DELETE
|
||||
ON manifest_references
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE gc_track_deleted_manifest_lists();
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION gc_track_deleted_tags()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1
|
||||
FROM manifests
|
||||
WHERE manifest_registry_id = OLD.tag_registry_id
|
||||
AND manifest_id = OLD.tag_registry_id) THEN
|
||||
INSERT INTO gc_manifest_review_queue (registry_id, manifest_id, review_after, event)
|
||||
VALUES (OLD.tag_registry_id, OLD.tag_manifest_id, gc_review_after('tag_delete'), 'tag_delete')
|
||||
ON CONFLICT (registry_id, manifest_id)
|
||||
DO UPDATE SET review_after = gc_review_after('tag_delete'),
|
||||
event = 'tag_delete';
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER gc_track_deleted_tag_trigger
|
||||
AFTER DELETE
|
||||
ON tags
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE gc_track_deleted_tags();
|
||||
|
||||
CREATE OR REPLACE FUNCTION gc_track_switched_tags()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
BEGIN
|
||||
INSERT INTO gc_manifest_review_queue (registry_id, manifest_id, review_after, event)
|
||||
VALUES (OLD.tag_registry_id, OLD.tag_manifest_id, gc_review_after('tag_switch'), 'tag_switch')
|
||||
ON CONFLICT (registry_id, manifest_id)
|
||||
DO UPDATE SET review_after = gc_review_after('tag_switch'),
|
||||
event = 'tag_switch';
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$
|
||||
LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER gc_track_switched_tag_trigger
|
||||
AFTER UPDATE OF tag_manifest_id
|
||||
ON tags
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE gc_track_switched_tags();
|
@ -0,0 +1,13 @@
|
||||
DROP TABLE registries;
|
||||
DROP TABLE media_types;
|
||||
DROP TABLE blobs;
|
||||
DROP TABLE registry_blobs;
|
||||
DROP TABLE manifests;
|
||||
DROP TABLE manifest_references;
|
||||
DROP TABLE layers;
|
||||
DROP TABLE artifacts;
|
||||
DROP TABLE artifact_stats;
|
||||
DROP TABLE tags;
|
||||
DROP TABLE upstream_proxy_configs;
|
||||
DROP TABLE cleanup_policies;
|
||||
DROP TABLE cleanup_policy_prefix_mappings;
|
330
app/store/database/migrate/sqlite/0065_create_ar_tables.up.sql
Normal file
330
app/store/database/migrate/sqlite/0065_create_ar_tables.up.sql
Normal file
@ -0,0 +1,330 @@
|
||||
create table registries
|
||||
(
|
||||
registry_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
registry_name text not null
|
||||
constraint registry_name_len_check
|
||||
check (length(registry_name) <= 255),
|
||||
registry_root_parent_id INTEGER not null,
|
||||
registry_parent_id INTEGER not null,
|
||||
registry_description text,
|
||||
registry_type text not null,
|
||||
registry_package_type text not null,
|
||||
registry_upstream_proxies text,
|
||||
registry_allowed_pattern text,
|
||||
registry_blocked_pattern text,
|
||||
registry_labels text,
|
||||
registry_created_at INTEGER not null,
|
||||
registry_updated_at INTEGER not null,
|
||||
registry_created_by INTEGER not null,
|
||||
registry_updated_by INTEGER not null,
|
||||
constraint unique_registries
|
||||
unique (registry_root_parent_id, registry_name)
|
||||
);
|
||||
|
||||
create table media_types
|
||||
(
|
||||
mt_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
mt_media_type text not null
|
||||
constraint unique_media_types_type
|
||||
unique,
|
||||
mt_created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000)
|
||||
);
|
||||
|
||||
create table blobs
|
||||
(
|
||||
blob_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
blob_root_parent_id INTEGER not null,
|
||||
blob_digest bytea not null,
|
||||
blob_media_type_id INTEGER not null
|
||||
constraint fk_blobs_media_type_id_media_types
|
||||
references media_types(mt_id),
|
||||
blob_size INTEGER not null,
|
||||
blob_created_at INTEGER not null,
|
||||
blob_created_by INTEGER not null,
|
||||
constraint unique_digest_root_parent_id unique (blob_digest, blob_root_parent_id)
|
||||
);
|
||||
|
||||
create index index_blobs_on_media_type_id
|
||||
on blobs (blob_media_type_id);
|
||||
|
||||
create table registry_blobs
|
||||
(
|
||||
rblob_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
rblob_registry_id INTEGER not null
|
||||
constraint fk_registry_blobs_rpstry_id_registries
|
||||
references registries(registry_id)
|
||||
on delete cascade,
|
||||
rblob_blob_id INTEGER not null
|
||||
constraint fk_registry_blobs_blob_id_blobs
|
||||
references blobs(blob_id)
|
||||
on delete cascade,
|
||||
rblob_image_name text
|
||||
constraint registry_blobs_image_len_check
|
||||
check (length(rblob_image_name) <= 255),
|
||||
rblob_created_at INTEGER not null,
|
||||
rblob_updated_at INTEGER not null,
|
||||
rblob_created_by INTEGER not null,
|
||||
rblob_updated_by INTEGER not null,
|
||||
|
||||
constraint unique_registry_blobs_registry_id_blob_id_image
|
||||
unique (rblob_registry_id, rblob_blob_id, rblob_image_name)
|
||||
);
|
||||
|
||||
create index index_registry_blobs_on_reg_id
|
||||
on registry_blobs (rblob_registry_id);
|
||||
|
||||
create index index_registry_blobs_on_reg_blob_id
|
||||
on registry_blobs (rblob_registry_id, rblob_blob_id);
|
||||
|
||||
|
||||
|
||||
create table manifests
|
||||
(
|
||||
manifest_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
manifest_registry_id INTEGER not null
|
||||
constraint fk_manifests_registry_id_registries
|
||||
references registries(registry_id)
|
||||
on delete cascade,
|
||||
manifest_schema_version smallint not null,
|
||||
manifest_media_type_id INTEGER not null
|
||||
constraint fk_manifests_media_type_id_media_types
|
||||
references media_types(mt_id),
|
||||
manifest_artifact_media_type text,
|
||||
manifest_total_size INTEGER not null,
|
||||
manifest_configuration_media_type text,
|
||||
manifest_configuration_payload bytea,
|
||||
manifest_configuration_blob_id INTEGER
|
||||
constraint fk_manifests_configuration_blob_id_blobs
|
||||
references blobs(blob_id),
|
||||
manifest_configuration_digest bytea,
|
||||
manifest_digest bytea not null,
|
||||
manifest_payload bytea not null,
|
||||
manifest_non_conformant boolean default false,
|
||||
manifest_non_distributable_layers boolean default false,
|
||||
manifest_subject_id INTEGER,
|
||||
manifest_subject_digest bytea,
|
||||
manifest_annotations bytea,
|
||||
manifest_image_name text not null
|
||||
constraint manifests_img_name_len_check
|
||||
check (length(manifest_image_name) <= 255),
|
||||
manifest_created_at INTEGER not null,
|
||||
manifest_created_by INTEGER not null,
|
||||
manifest_updated_at INTEGER not null,
|
||||
manifest_updated_by INTEGER not null,
|
||||
constraint unique_manifests_registry_id_image_name_and_digest
|
||||
unique (manifest_registry_id, manifest_image_name, manifest_digest),
|
||||
constraint unique_manifests_registry_id_id_cfg_blob_id
|
||||
unique (manifest_registry_id, manifest_id, manifest_configuration_blob_id),
|
||||
constraint fk_manifests_subject_id_manifests
|
||||
foreign key (manifest_subject_id) references manifests(manifest_id)
|
||||
on delete cascade
|
||||
);
|
||||
|
||||
create index index_manifests_on_media_type_id
|
||||
on manifests (manifest_media_type_id);
|
||||
|
||||
create index index_manifests_on_configuration_blob_id
|
||||
on manifests (manifest_configuration_blob_id);
|
||||
|
||||
|
||||
|
||||
create table manifest_references
|
||||
(
|
||||
manifest_ref_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
manifest_ref_registry_id INTEGER not null,
|
||||
manifest_ref_parent_id INTEGER not null,
|
||||
manifest_ref_child_id INTEGER not null,
|
||||
manifest_ref_created_at INTEGER not null,
|
||||
manifest_ref_updated_at INTEGER not null,
|
||||
manifest_ref_created_by INTEGER not null,
|
||||
manifest_ref_updated_by INTEGER not null,
|
||||
constraint unique_manifest_references_prt_id_chd_id
|
||||
unique (manifest_ref_registry_id, manifest_ref_parent_id, manifest_ref_child_id),
|
||||
constraint fk_manifest_ref_parent_id_manifests_manifest_id
|
||||
foreign key (manifest_ref_parent_id) references manifests(manifest_id)
|
||||
on delete cascade,
|
||||
constraint fk_manifest_ref_child_id_manifests_manifest_id
|
||||
foreign key (manifest_ref_child_id) references manifests(manifest_id),
|
||||
constraint check_manifest_references_parent_id_and_child_id_differ
|
||||
check (manifest_ref_parent_id <> manifest_ref_child_id)
|
||||
);
|
||||
|
||||
create index index_manifest_references_on_rpstry_id_child_id
|
||||
on manifest_references (manifest_ref_registry_id, manifest_ref_child_id);
|
||||
|
||||
create table layers
|
||||
(
|
||||
layer_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
layer_registry_id INTEGER not null,
|
||||
layer_manifest_id INTEGER not null,
|
||||
layer_media_type_id INTEGER not null
|
||||
constraint fk_layer_media_type_id_media_types
|
||||
references media_types(mt_id),
|
||||
layer_blob_id INTEGER not null
|
||||
constraint fk_layer_blob_id_blobs
|
||||
references blobs(blob_id),
|
||||
layer_size INTEGER not null,
|
||||
layer_created_at INTEGER not null,
|
||||
layer_updated_at INTEGER not null,
|
||||
layer_created_by INTEGER not null,
|
||||
layer_updated_by INTEGER not null,
|
||||
constraint unique_layer_rpstry_id_and_mnfst_id_and_blob_id
|
||||
unique (layer_registry_id, layer_manifest_id, layer_blob_id),
|
||||
constraint unique_layer_rpstry_id_and_id_and_blob_id
|
||||
unique (layer_registry_id, layer_id, layer_blob_id),
|
||||
constraint fk_layer_manifest_id_and_manifests_manifest_id
|
||||
foreign key (layer_manifest_id) references manifests(manifest_id)
|
||||
on delete cascade
|
||||
);
|
||||
|
||||
create index index_layer_on_media_type_id
|
||||
on layers (layer_media_type_id);
|
||||
|
||||
create index index_layer_on_blob_id
|
||||
on layers (layer_blob_id);
|
||||
|
||||
create table artifacts
|
||||
(
|
||||
artifact_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
artifact_name text not null,
|
||||
artifact_registry_id INTEGER not null
|
||||
constraint fk_registries_registry_id
|
||||
references registries(registry_id)
|
||||
on delete cascade,
|
||||
artifact_labels text,
|
||||
artifact_enabled boolean default false,
|
||||
artifact_created_at INTEGER,
|
||||
artifact_updated_at INTEGER,
|
||||
artifact_created_by INTEGER,
|
||||
artifact_updated_by INTEGER,
|
||||
constraint unique_artifact_registry_id_and_name unique (artifact_registry_id, artifact_name),
|
||||
constraint check_artifact_name_length check ((length(artifact_name) <= 255))
|
||||
);
|
||||
|
||||
create index index_artifact_on_registry_id ON artifacts (artifact_registry_id);
|
||||
|
||||
|
||||
create table artifact_stats
|
||||
(
|
||||
artifact_stat_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
artifact_stat_artifact_id INTEGER not null
|
||||
constraint fk_artifacts_artifact_id
|
||||
references artifacts(artifact_id),
|
||||
artifact_stat_date INTEGER,
|
||||
artifact_stat_download_count INTEGER,
|
||||
artifact_stat_upload_bytes INTEGER,
|
||||
artifact_stat_download_bytes INTEGER,
|
||||
artifact_stat_created_at INTEGER not null,
|
||||
artifact_stat_updated_at INTEGER not null,
|
||||
artifact_stat_created_by INTEGER not null,
|
||||
artifact_stat_updated_by INTEGER not null,
|
||||
constraint unique_artifact_stats_artifact_id_and_date unique (artifact_stat_artifact_id, artifact_stat_date)
|
||||
);
|
||||
|
||||
create table tags
|
||||
(
|
||||
tag_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
tag_name text not null
|
||||
constraint tag_name_len_check
|
||||
check (length(tag_name) <= 128),
|
||||
tag_image_name text not null
|
||||
constraint tag_img_name_len_check
|
||||
check (length(tag_image_name) <= 255),
|
||||
tag_registry_id INTEGER not null,
|
||||
tag_manifest_id INTEGER not null,
|
||||
tag_created_at INTEGER,
|
||||
tag_updated_at INTEGER,
|
||||
tag_created_by INTEGER,
|
||||
tag_updated_by INTEGER,
|
||||
constraint fk_tag_manifest_id_and_manifests_manifest_id FOREIGN KEY
|
||||
(tag_manifest_id) REFERENCES manifests (manifest_id) ON DELETE CASCADE,
|
||||
constraint unique_tag_registry_id_and_name_and_image_name
|
||||
unique (tag_registry_id, tag_name, tag_image_name)
|
||||
);
|
||||
|
||||
create index index_tag_on_rpository_id_and_manifest_id
|
||||
on tags (tag_registry_id, tag_manifest_id);
|
||||
|
||||
create table upstream_proxy_configs
|
||||
(
|
||||
upstream_proxy_config_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
upstream_proxy_config_registry_id INTEGER not null
|
||||
constraint fk_upstream_proxy_config_registry_id
|
||||
references registries(registry_id)
|
||||
on delete cascade,
|
||||
upstream_proxy_config_source text,
|
||||
upstream_proxy_config_url text,
|
||||
upstream_proxy_config_auth_type text not null,
|
||||
upstream_proxy_config_user_name text,
|
||||
upstream_proxy_config_secret_identifier text,
|
||||
upstream_proxy_config_secret_space_id int,
|
||||
upstream_proxy_config_token text,
|
||||
upstream_proxy_config_created_at INTEGER,
|
||||
upstream_proxy_config_updated_at INTEGER,
|
||||
upstream_proxy_config_created_by INTEGER,
|
||||
upstream_proxy_config_updated_by INTEGER,
|
||||
constraint fk_layers_secret_identifier_and_secret_space_id FOREIGN KEY
|
||||
(upstream_proxy_config_secret_identifier, upstream_proxy_config_secret_space_id) REFERENCES secrets(secret_uid, secret_space_id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
create index index_upstream_proxy_config_on_registry_id
|
||||
on upstream_proxy_configs (upstream_proxy_config_registry_id);
|
||||
|
||||
create table cleanup_policies
|
||||
(
|
||||
cp_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
cp_registry_id INTEGER not null
|
||||
constraint fk_cleanup_policies_registry_id
|
||||
references registries(registry_id) ON DELETE CASCADE,
|
||||
cp_name text,
|
||||
cp_expiry_time_ms INTEGER,
|
||||
cp_created_at INTEGER not null,
|
||||
cp_updated_at INTEGER not null,
|
||||
cp_created_by INTEGER not null,
|
||||
cp_updated_by INTEGER not null
|
||||
);
|
||||
|
||||
create index index_cleanup_policies_on_registry_id
|
||||
on cleanup_policies (cp_registry_id);
|
||||
|
||||
create table cleanup_policy_prefix_mappings
|
||||
(
|
||||
cpp_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
cpp_cleanup_policy_id INTEGER not null
|
||||
constraint fk_cleanup_policy_prefix_registry_id
|
||||
references cleanup_policies(cp_id) ON DELETE CASCADE,
|
||||
cpp_prefix text not null,
|
||||
cpp_prefix_type text not null
|
||||
);
|
||||
|
||||
create index index_cleanup_policy_map_on_policy_id
|
||||
on cleanup_policy_prefix_mappings (cpp_cleanup_policy_id);
|
||||
|
||||
|
||||
|
||||
insert into media_types (mt_media_type)
|
||||
values ('application/vnd.docker.distribution.manifest.v1+json'),
|
||||
('application/vnd.docker.distribution.manifest.v1+prettyjws'),
|
||||
('application/vnd.docker.distribution.manifest.v2+json'),
|
||||
('application/vnd.docker.distribution.manifest.list.v2+json'),
|
||||
('application/vnd.docker.image.rootfs.diff.tar'),
|
||||
('application/vnd.docker.image.rootfs.diff.tar.gzip'),
|
||||
('application/vnd.docker.image.rootfs.foreign.diff.tar.gzip'),
|
||||
('application/vnd.docker.container.image.v1+json'),
|
||||
('application/vnd.docker.container.image.rootfs.diff+x-gtar'),
|
||||
('application/vnd.docker.plugin.v1+json'),
|
||||
('application/vnd.oci.image.layer.v1.tar'),
|
||||
('application/vnd.oci.image.layer.v1.tar+gzip'),
|
||||
('application/vnd.oci.image.layer.v1.tar+zstd'),
|
||||
('application/vnd.oci.image.layer.nondistributable.v1.tar'),
|
||||
('application/vnd.oci.image.layer.nondistributable.v1.tar+gzip'),
|
||||
('application/vnd.oci.image.config.v1+json'),
|
||||
('application/vnd.oci.image.manifest.v1+json'),
|
||||
('application/vnd.oci.image.index.v1+json'),
|
||||
('application/vnd.cncf.helm.config.v1+json'),
|
||||
('application/tar+gzip'),
|
||||
('application/octet-stream'),
|
||||
('application/vnd.buildkit.cacheconfig.v0'),
|
||||
('application/vnd.cncf.helm.chart.content.v1.tar+gzip'),
|
||||
('application/vnd.cncf.helm.chart.provenance.v1.prov');
|
@ -223,14 +223,16 @@ func ProvideTokenStore(db *sqlx.DB) store.TokenStore {
|
||||
}
|
||||
|
||||
// ProvidePullReqStore provides a pull request store.
|
||||
func ProvidePullReqStore(db *sqlx.DB,
|
||||
func ProvidePullReqStore(
|
||||
db *sqlx.DB,
|
||||
principalInfoCache store.PrincipalInfoCache,
|
||||
) store.PullReqStore {
|
||||
return NewPullReqStore(db, principalInfoCache)
|
||||
}
|
||||
|
||||
// ProvidePullReqActivityStore provides a pull request activity store.
|
||||
func ProvidePullReqActivityStore(db *sqlx.DB,
|
||||
func ProvidePullReqActivityStore(
|
||||
db *sqlx.DB,
|
||||
principalInfoCache store.PrincipalInfoCache,
|
||||
) store.PullReqActivityStore {
|
||||
return NewPullReqActivityStore(db, principalInfoCache)
|
||||
@ -247,7 +249,8 @@ func ProvidePullReqReviewStore(db *sqlx.DB) store.PullReqReviewStore {
|
||||
}
|
||||
|
||||
// ProvidePullReqReviewerStore provides a pull request reviewer store.
|
||||
func ProvidePullReqReviewerStore(db *sqlx.DB,
|
||||
func ProvidePullReqReviewerStore(
|
||||
db *sqlx.DB,
|
||||
principalInfoCache store.PrincipalInfoCache,
|
||||
) store.PullReqReviewerStore {
|
||||
return NewPullReqReviewerStore(db, principalInfoCache)
|
||||
@ -269,7 +272,8 @@ func ProvideWebhookExecutionStore(db *sqlx.DB) store.WebhookExecutionStore {
|
||||
}
|
||||
|
||||
// ProvideCheckStore provides a status check result store.
|
||||
func ProvideCheckStore(db *sqlx.DB,
|
||||
func ProvideCheckStore(
|
||||
db *sqlx.DB,
|
||||
principalInfoCache store.PrincipalInfoCache,
|
||||
) store.CheckStore {
|
||||
return NewCheckStore(db, principalInfoCache)
|
||||
|
@ -30,9 +30,22 @@ import (
|
||||
const (
|
||||
// userSessionTokenLifeTime is the duration a login / register token is valid.
|
||||
// NOTE: Users can list / delete session tokens via rest API if they want to cleanup earlier.
|
||||
userSessionTokenLifeTime time.Duration = 30 * 24 * time.Hour // 30 days.
|
||||
userSessionTokenLifeTime time.Duration = 30 * 24 * time.Hour // 30 days.
|
||||
sessionTokenWithAccessPermissionsLifeTime time.Duration = 24 * time.Hour // 24 hours.
|
||||
)
|
||||
|
||||
func CreateUserWithAccessPermissions(
|
||||
user *types.User,
|
||||
accessPermissions *jwt.SubClaimsAccessPermissions,
|
||||
) (string, error) {
|
||||
principal := user.ToPrincipal()
|
||||
return createWithAccessPermissions(
|
||||
principal,
|
||||
ptr.Duration(sessionTokenWithAccessPermissionsLifeTime),
|
||||
accessPermissions,
|
||||
)
|
||||
}
|
||||
|
||||
func CreateUserSession(
|
||||
ctx context.Context,
|
||||
tokenStore store.TokenStore,
|
||||
@ -128,3 +141,18 @@ func create(
|
||||
|
||||
return &token, jwtToken, nil
|
||||
}
|
||||
|
||||
func createWithAccessPermissions(
|
||||
createdFor *types.Principal,
|
||||
lifetime *time.Duration,
|
||||
accessPermissions *jwt.SubClaimsAccessPermissions,
|
||||
) (string, error) {
|
||||
jwtToken, err := jwt.GenerateForTokenWithAccessPermissions(
|
||||
createdFor.ID, lifetime, createdFor.Salt, accessPermissions,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create jwt token: %w", err)
|
||||
}
|
||||
|
||||
return jwtToken, nil
|
||||
}
|
||||
|
@ -74,6 +74,9 @@ type Provider interface {
|
||||
|
||||
// GetAPIProto returns the proto for the API hostname
|
||||
GetAPIProto(ctx context.Context) string
|
||||
|
||||
// RegistryURL returns the url for oci token endpoint
|
||||
RegistryURL() string
|
||||
}
|
||||
|
||||
// Provider provides the URLs of the gitness system.
|
||||
@ -99,6 +102,9 @@ type provider struct {
|
||||
|
||||
// uiURL stores the raw URL to the ui endpoints.
|
||||
uiURL *url.URL
|
||||
|
||||
// registryURL stores the raw URL to the registry endpoints.
|
||||
registryURL *url.URL
|
||||
}
|
||||
|
||||
func NewProvider(
|
||||
@ -110,6 +116,7 @@ func NewProvider(
|
||||
sshDefaultUser string,
|
||||
sshEnabled bool,
|
||||
uiURLRaw string,
|
||||
registryURLRaw string,
|
||||
) (Provider, error) {
|
||||
// remove trailing '/' to make usage easier
|
||||
internalURLRaw = strings.TrimRight(internalURLRaw, "/")
|
||||
@ -118,6 +125,7 @@ func NewProvider(
|
||||
gitURLRaw = strings.TrimRight(gitURLRaw, "/")
|
||||
gitSSHURLRaw = strings.TrimRight(gitSSHURLRaw, "/")
|
||||
uiURLRaw = strings.TrimRight(uiURLRaw, "/")
|
||||
registryURLRaw = strings.TrimRight(registryURLRaw, "/")
|
||||
|
||||
internalURL, err := url.Parse(internalURLRaw)
|
||||
if err != nil {
|
||||
@ -149,6 +157,11 @@ func NewProvider(
|
||||
return nil, fmt.Errorf("provided uiURLRaw '%s' is invalid: %w", uiURLRaw, err)
|
||||
}
|
||||
|
||||
registryURL, err := url.Parse(registryURLRaw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("provided registryURLRaw '%s' is invalid: %w", registryURLRaw, err)
|
||||
}
|
||||
|
||||
return &provider{
|
||||
internalURL: internalURL,
|
||||
containerURL: containerURL,
|
||||
@ -158,6 +171,7 @@ func NewProvider(
|
||||
SSHDefaultUser: sshDefaultUser,
|
||||
SSHEnabled: sshEnabled,
|
||||
uiURL: uiURL,
|
||||
registryURL: registryURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -191,8 +205,10 @@ func (p *provider) GenerateGITCloneSSHURL(_ context.Context, repoPath string) st
|
||||
}
|
||||
|
||||
func (p *provider) GenerateUIBuildURL(_ context.Context, repoPath, pipelineIdentifier string, seqNumber int64) string {
|
||||
return p.uiURL.JoinPath(repoPath, "pipelines",
|
||||
pipelineIdentifier, "execution", strconv.Itoa(int(seqNumber))).String()
|
||||
return p.uiURL.JoinPath(
|
||||
repoPath, "pipelines",
|
||||
pipelineIdentifier, "execution", strconv.Itoa(int(seqNumber)),
|
||||
).String()
|
||||
}
|
||||
|
||||
func (p *provider) GenerateUIRepoURL(_ context.Context, repoPath string) string {
|
||||
@ -219,6 +235,10 @@ func (p *provider) GetAPIProto(context.Context) string {
|
||||
return p.apiURL.Scheme
|
||||
}
|
||||
|
||||
func (p *provider) RegistryURL() string {
|
||||
return p.registryURL.String()
|
||||
}
|
||||
|
||||
func BuildGITCloneSSHURL(user string, sshURL *url.URL, repoPath string) string {
|
||||
repoPath = path.Clean(repoPath)
|
||||
if !strings.HasSuffix(repoPath, GITSuffix) {
|
||||
|
@ -33,5 +33,6 @@ func ProvideURLProvider(config *types.Config) (Provider, error) {
|
||||
config.SSH.DefaultUser,
|
||||
config.SSH.Enable,
|
||||
config.URL.UI,
|
||||
config.URL.Registry,
|
||||
)
|
||||
}
|
||||
|
@ -54,17 +54,22 @@ func (a Action) Validate() error {
|
||||
type ResourceType string
|
||||
|
||||
const (
|
||||
ResourceTypeRepository ResourceType = "repository"
|
||||
ResourceTypeBranchRule ResourceType = "branch_rule"
|
||||
ResourceTypeRepositorySettings ResourceType = "repository_settings"
|
||||
ResourceTypeRepository ResourceType = "repository"
|
||||
ResourceTypeBranchRule ResourceType = "branch_rule"
|
||||
ResourceTypeRepositorySettings ResourceType = "repository_settings"
|
||||
ResourceTypeRegistry ResourceType = "registry"
|
||||
ResourceTypeRegistryUpstreamProxy ResourceType = "registry_upstream_proxy"
|
||||
)
|
||||
|
||||
func (a ResourceType) Validate() error {
|
||||
switch a {
|
||||
case ResourceTypeRepository,
|
||||
ResourceTypeBranchRule,
|
||||
ResourceTypeRepositorySettings:
|
||||
ResourceTypeRepositorySettings,
|
||||
ResourceTypeRegistry,
|
||||
ResourceTypeRegistryUpstreamProxy:
|
||||
return nil
|
||||
|
||||
default:
|
||||
return ErrResourceTypeUndefined
|
||||
}
|
||||
|
@ -14,7 +14,12 @@
|
||||
|
||||
package audit
|
||||
|
||||
import "github.com/harness/gitness/types"
|
||||
import (
|
||||
"time"
|
||||
|
||||
registrytypes "github.com/harness/gitness/registry/types"
|
||||
"github.com/harness/gitness/types"
|
||||
)
|
||||
|
||||
// RepositoryObject is the object used for emitting repository related audits.
|
||||
// TODO: ensure audit only takes audit related objects?
|
||||
@ -22,3 +27,18 @@ type RepositoryObject struct {
|
||||
types.Repository
|
||||
IsPublic bool `yaml:"is_public"`
|
||||
}
|
||||
|
||||
type RegistryObject struct {
|
||||
registrytypes.Registry
|
||||
}
|
||||
type RegistryUpstreamProxyConfigObject struct {
|
||||
ID int64
|
||||
RegistryID int64
|
||||
Source string
|
||||
URL string
|
||||
AuthType string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
CreatedBy int64
|
||||
UpdatedBy int64
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ func (c *commandCurrent) run(*kingpin.ParseContext) error {
|
||||
defer cancel()
|
||||
|
||||
db, err := getDB(ctx, c.envfile)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -194,6 +194,9 @@ func backfillURLs(config *types.Config) error {
|
||||
if config.URL.UI == "" {
|
||||
config.URL.UI = baseURL.String()
|
||||
}
|
||||
if config.URL.Registry == "" {
|
||||
config.URL.Registry = baseURL.String()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -238,22 +241,26 @@ func getSanitizedMachineName() (string, error) {
|
||||
norm.NFD,
|
||||
runes.ReplaceIllFormed(),
|
||||
runes.Remove(runes.In(unicode.Mn)),
|
||||
runes.Map(func(r rune) rune {
|
||||
switch {
|
||||
case 'A' <= r && r <= 'Z':
|
||||
return r + 32
|
||||
case 'a' <= r && r <= 'z':
|
||||
return r
|
||||
case '0' <= r && r <= '9':
|
||||
return r
|
||||
case r == '-', r == '.':
|
||||
return r
|
||||
default:
|
||||
return '_'
|
||||
}
|
||||
}),
|
||||
norm.NFC),
|
||||
hostName)
|
||||
runes.Map(
|
||||
func(r rune) rune {
|
||||
switch {
|
||||
case 'A' <= r && r <= 'Z':
|
||||
return r + 32
|
||||
case 'a' <= r && r <= 'z':
|
||||
return r
|
||||
case '0' <= r && r <= '9':
|
||||
return r
|
||||
case r == '-', r == '.':
|
||||
return r
|
||||
default:
|
||||
return '_'
|
||||
}
|
||||
},
|
||||
),
|
||||
norm.NFC,
|
||||
),
|
||||
hostName,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -1,6 +1,16 @@
|
||||
// Copyright 2021 Harness Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Polyform Free Trial License
|
||||
// that can be found in the LICENSE.md file for this repository.
|
||||
// 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.
|
||||
|
||||
//go:build wireinject
|
||||
// +build wireinject
|
||||
|
@ -63,7 +63,7 @@ import (
|
||||
"github.com/harness/gitness/app/pipeline/runner"
|
||||
"github.com/harness/gitness/app/pipeline/scheduler"
|
||||
"github.com/harness/gitness/app/pipeline/triggerer"
|
||||
"github.com/harness/gitness/app/router"
|
||||
router2 "github.com/harness/gitness/app/router"
|
||||
server2 "github.com/harness/gitness/app/server"
|
||||
"github.com/harness/gitness/app/services"
|
||||
"github.com/harness/gitness/app/services/aiagent"
|
||||
@ -113,6 +113,12 @@ import (
|
||||
"github.com/harness/gitness/livelog"
|
||||
"github.com/harness/gitness/lock"
|
||||
"github.com/harness/gitness/pubsub"
|
||||
api2 "github.com/harness/gitness/registry/app/api"
|
||||
"github.com/harness/gitness/registry/app/api/router"
|
||||
"github.com/harness/gitness/registry/app/pkg"
|
||||
"github.com/harness/gitness/registry/app/pkg/docker"
|
||||
database2 "github.com/harness/gitness/registry/app/store/database"
|
||||
"github.com/harness/gitness/registry/gc"
|
||||
"github.com/harness/gitness/ssh"
|
||||
"github.com/harness/gitness/store/database/dbtx"
|
||||
"github.com/harness/gitness/types"
|
||||
@ -396,7 +402,36 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
|
||||
}
|
||||
aiagentController := aiagent2.ProvideController(authorizer, harnessIntelligence, repoStore, pipelineStore, executionStore)
|
||||
openapiService := openapi.ProvideOpenAPIService()
|
||||
routerRouter := router.ProvideRouter(ctx, config, authenticator, repoController, reposettingsController, executionController, logsController, spaceController, pipelineController, secretController, triggerController, connectorController, templateController, pluginController, pullreqController, webhookController, githookController, gitInterface, serviceaccountController, controller, principalController, checkController, systemController, uploadController, keywordsearchController, infraproviderController, gitspaceController, migrateController, aiagentController, capabilitiesController, provider, openapiService)
|
||||
storageDriver, err := api2.BlobStorageProvider(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
storageDeleter := gc.StorageDeleterProvider(storageDriver)
|
||||
mediaTypesRepository := database2.ProvideMediaTypeDao(db)
|
||||
blobRepository := database2.ProvideBlobDao(db, mediaTypesRepository)
|
||||
storageService := docker.StorageServiceProvider(config, storageDriver)
|
||||
manifestRepository := database2.ProvideManifestDao(db, mediaTypesRepository)
|
||||
gcService := gc.ServiceProvider()
|
||||
app := docker.NewApp(ctx, db, storageDeleter, blobRepository, spaceStore, config, storageService, mediaTypesRepository, manifestRepository, gcService)
|
||||
registryRepository := database2.ProvideRepoDao(db, mediaTypesRepository)
|
||||
manifestReferenceRepository := database2.ProvideManifestRefDao(db)
|
||||
tagRepository := database2.ProvideTagDao(db)
|
||||
artifactRepository := database2.ProvideArtifactDao(db)
|
||||
artifactStatRepository := database2.ProvideArtifactStatDao(db)
|
||||
layerRepository := database2.ProvideLayerDao(db, mediaTypesRepository)
|
||||
manifestService := docker.ManifestServiceProvider(registryRepository, manifestRepository, blobRepository, mediaTypesRepository, manifestReferenceRepository, tagRepository, artifactRepository, artifactStatRepository, layerRepository, gcService, transactor)
|
||||
registryBlobRepository := database2.ProvideRegistryBlobDao(db)
|
||||
localRegistry := docker.LocalRegistryProvider(app, manifestService, blobRepository, registryRepository, manifestRepository, registryBlobRepository, mediaTypesRepository, tagRepository, artifactRepository, artifactStatRepository, gcService, transactor)
|
||||
upstreamProxyConfigRepository := database2.ProvideUpstreamDao(db, registryRepository)
|
||||
remoteRegistry := docker.RemoteRegistryProvider(localRegistry, app, upstreamProxyConfigRepository, secretStore, encrypter)
|
||||
coreController := pkg.CoreControllerProvider(registryRepository)
|
||||
dockerController := docker.ControllerProvider(localRegistry, remoteRegistry, coreController, spaceStore, authorizer)
|
||||
handler := api2.NewHandlerProvider(dockerController, spaceStore, tokenStore, controller, authenticator, provider, authorizer)
|
||||
registryOCIHandler := router.OCIHandlerProvider(handler)
|
||||
cleanupPolicyRepository := database2.ProvideCleanupPolicyDao(db, transactor)
|
||||
apiHandler := router.APIHandlerProvider(registryRepository, upstreamProxyConfigRepository, tagRepository, manifestRepository, cleanupPolicyRepository, artifactRepository, storageDriver, spaceStore, transactor, authenticator, provider, authorizer, auditService)
|
||||
appRouter := router.AppRouterProvider(registryOCIHandler, apiHandler)
|
||||
routerRouter := router2.ProvideRouter(ctx, config, authenticator, repoController, reposettingsController, executionController, logsController, spaceController, pipelineController, secretController, triggerController, connectorController, templateController, pluginController, pullreqController, webhookController, githookController, gitInterface, serviceaccountController, controller, principalController, checkController, systemController, uploadController, keywordsearchController, infraproviderController, gitspaceController, migrateController, aiagentController, capabilitiesController, provider, openapiService, appRouter)
|
||||
serverServer := server2.ProvideServer(config, routerRouter)
|
||||
publickeyService := publickey.ProvidePublicKey(publicKeyStore, principalInfoCache)
|
||||
sshServer := ssh.ProvideServer(config, publickeyService, repoController)
|
||||
|
44
go.mod
44
go.mod
@ -10,6 +10,8 @@ require (
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1
|
||||
github.com/coreos/go-semver v0.3.1
|
||||
github.com/dchest/uniuri v1.2.0
|
||||
github.com/distribution/distribution/v3 v3.0.0-alpha.1
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/docker v27.1.1+incompatible
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/drone-runners/drone-runner-docker v1.8.4-0.20240815103043-c6c3a3e33ce3
|
||||
@ -21,23 +23,30 @@ require (
|
||||
github.com/drone/go-scm v1.38.4
|
||||
github.com/drone/runner-go v1.12.0
|
||||
github.com/drone/spec v0.0.0-20230920145636-3827abdce961
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/fatih/color v1.17.0
|
||||
github.com/gabriel-vasile/mimetype v1.4.4
|
||||
github.com/getkin/kin-openapi v0.123.0
|
||||
github.com/gliderlabs/ssh v0.3.7
|
||||
github.com/go-chi/chi v1.5.5
|
||||
github.com/go-chi/chi/v5 v5.0.12
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/go-redsync/redsync/v4 v4.13.0
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/golang-migrate/migrate/v4 v4.17.1
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/go-jsonnet v0.20.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/google/wire v0.6.0
|
||||
github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/gotidy/ptr v1.4.0
|
||||
github.com/guregu/null v4.0.0+incompatible
|
||||
github.com/harness/harness-migrate v0.21.1-0.20240804180936-b1de602aa8e7
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438
|
||||
github.com/jackc/pgx/v5 v5.5.5
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
@ -47,6 +56,9 @@ require (
|
||||
github.com/matoous/go-nanoid/v2 v2.1.0
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/oapi-codegen/runtime v1.1.1
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rs/xid v1.5.0
|
||||
github.com/rs/zerolog v1.33.0
|
||||
@ -55,6 +67,8 @@ require (
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/swaggest/openapi-go v0.2.23
|
||||
github.com/swaggest/swgui v1.8.1
|
||||
github.com/swaggo/http-swagger v1.3.4
|
||||
github.com/swaggo/swag v1.16.2
|
||||
github.com/unrolled/secure v1.15.0
|
||||
github.com/zricethezav/gitleaks/v8 v8.18.5-0.20240614204812-26f34692fac6
|
||||
go.starlark.net v0.0.0-20231121155337-90ade8b19d09
|
||||
@ -78,19 +92,18 @@ require (
|
||||
cloud.google.com/go/iam v1.1.12 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/BobuSumisu/aho-corasick v1.0.3 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||
github.com/antonmedv/expr v1.15.5 // indirect
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bmatcuk/doublestar v1.3.4 // indirect
|
||||
github.com/buildkite/yaml v2.1.0+incompatible // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.12.1 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.1.4 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/drone/envsubst v1.0.3 // indirect
|
||||
github.com/fatih/semgroup v1.2.0 // indirect
|
||||
@ -99,6 +112,10 @@ require (
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/gitleaks/go-gitdiff v0.9.0 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.2 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/spec v0.20.9 // indirect
|
||||
github.com/go-openapi/swag v0.22.8 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
@ -106,26 +123,29 @@ require (
|
||||
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
|
||||
github.com/h2non/filetype v1.1.3 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.12.0 // indirect
|
||||
github.com/invopop/yaml v0.2.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4 // indirect
|
||||
github.com/onsi/gomega v1.27.10 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.6.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
@ -134,14 +154,14 @@ require (
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.19.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
|
||||
go.opentelemetry.io/otel v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240723171418-e6d459c13d2a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240723171418-e6d459c13d2a // indirect
|
||||
@ -180,10 +200,12 @@ require (
|
||||
github.com/vearutop/statigz v1.4.0 // indirect
|
||||
github.com/yuin/goldmark v1.4.13
|
||||
golang.org/x/mod v0.19.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/net v0.27.0
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/tools v0.23.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240722135656-d784300faade // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
replace github.com/harness/gitness/registry => ./registry
|
||||
|
116
go.sum
116
go.sum
@ -30,12 +30,15 @@ github.com/BobuSumisu/aho-corasick v1.0.3 h1:uuf+JHwU9CHP2Vx+wAy6jcksJThhJS9ehR8
|
||||
github.com/BobuSumisu/aho-corasick v1.0.3/go.mod h1:hm4jLcvZKI2vRF2WDU1N4p/jpWtpOzp3nLmi9AzX/XE=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
|
||||
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
|
||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
@ -57,6 +60,8 @@ github.com/antonmedv/expr v1.15.5 h1:y0Iz3cEwmpRz5/r3w4qQR0MfIqJGdGM1zbhD/v0G5Vg
|
||||
github.com/antonmedv/expr v1.15.5/go.mod h1:0E/6TxnOlRNp81GMzX9QfDPAmHo2Phg00y4JUv1ihsE=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
@ -85,9 +90,10 @@ github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6M
|
||||
github.com/buildkite/yaml v2.1.0+incompatible h1:xirI+ql5GzfikVNDmt+yeiXpf/v1Gt03qXTtT5WXdr8=
|
||||
github.com/buildkite/yaml v2.1.0+incompatible/go.mod h1:UoU8vbcwu1+vjZq01+KrpSeLBgQQIjL/H7Y6KwikUrI=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
|
||||
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
@ -115,6 +121,7 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@ -124,6 +131,8 @@ github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/distribution/distribution/v3 v3.0.0-alpha.1 h1:jn7I1gvjOvmLztH1+1cLiUFud7aeJCIQcgzugtwjyJo=
|
||||
github.com/distribution/distribution/v3 v3.0.0-alpha.1/go.mod h1:LCp4JZp1ZalYg0W/TN05jarCQu+h4w7xc7ZfQF4Y/cY=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/djherbis/buffer v1.1.0/go.mod h1:VwN8VdFkMY0DCALdY8o00d3IZ6Amz/UNVMWcSaJT44o=
|
||||
@ -132,8 +141,9 @@ github.com/djherbis/buffer v1.2.0/go.mod h1:fjnebbZjCUpPinBRD+TDwXSOeNQ7fPQWLfGQ
|
||||
github.com/djherbis/nio/v3 v3.0.1 h1:6wxhnuppteMa6RHA4L81Dq7ThkZH8SwnDzXDYy95vB4=
|
||||
github.com/djherbis/nio/v3 v3.0.1/go.mod h1:Ng4h80pbZFMla1yKzm61cF0tqqilXZYrogmWgZxOcmg=
|
||||
github.com/docker/distribution v0.0.0-20170726174610-edc3ab29cdff/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
|
||||
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
@ -167,6 +177,8 @@ github.com/drone/signal v1.0.0/go.mod h1:S8t92eFT0g4WUgEc/LxG+LCuiskpMNsG0ajAMGn
|
||||
github.com/drone/spec v0.0.0-20230920145636-3827abdce961 h1:aUWrLS2ghyxIpDICpZOV50V1x7JLM3U80UQDQxMKT54=
|
||||
github.com/drone/spec v0.0.0-20230920145636-3827abdce961/go.mod h1:KyQZA9qwuscbbM7yTrtZg25Wammoc5GKwaRem8kDA5k=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
@ -192,6 +204,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
|
||||
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
|
||||
github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8=
|
||||
github.com/getkin/kin-openapi v0.123.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gitleaks/go-gitdiff v0.9.0 h1:SHAU2l0ZBEo8g82EeFewhVy81sb7JCxW76oSPtR/Nqg=
|
||||
@ -200,6 +214,8 @@ github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE=
|
||||
github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw=
|
||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
@ -215,6 +231,18 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-logr/zerologr v1.2.3 h1:up5N9vcH9Xck3jJkXzgyOxozT14R47IyDODz8LM1KSs=
|
||||
github.com/go-logr/zerologr v1.2.3/go.mod h1:BxwGo7y5zgSHYR1BjbnHPyF/5ZjVKfKxAZANVu6E8Ho=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
|
||||
github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
|
||||
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
|
||||
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||
github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8=
|
||||
github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw=
|
||||
github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI=
|
||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI=
|
||||
@ -228,9 +256,10 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v0.0.0-20170307180453-100ba4e88506/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
@ -240,6 +269,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4=
|
||||
github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@ -305,6 +336,8 @@ github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b0
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gotidy/ptr v1.4.0 h1:7++suUs+HNHMnyz6/AW3SE+4EnBhupPSQTSI7QNijVc=
|
||||
github.com/gotidy/ptr v1.4.0/go.mod h1:MjRBG6/IETiiZGWI8LrRtISXEji+8b/jigmj2q0mEyM=
|
||||
@ -359,6 +392,8 @@ github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36
|
||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY=
|
||||
github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
|
||||
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
@ -370,14 +405,14 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU
|
||||
github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
|
||||
github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
|
||||
github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
|
||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||
github.com/jackc/pgconn v1.8.1/go.mod h1:JV6m6b6jhjdmzchES0drzCcYcAHS1OPD5xu3OZ/lE2g=
|
||||
github.com/jackc/pgconn v1.9.0 h1:gqibKSTJup/ahCsNKyMZAniPuZEfIqfXFc8FOWVYR+Q=
|
||||
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
||||
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
|
||||
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
|
||||
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0=
|
||||
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
|
||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
|
||||
@ -389,11 +424,12 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:
|
||||
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.1.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI=
|
||||
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
|
||||
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||
@ -401,8 +437,8 @@ github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4
|
||||
github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
|
||||
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
|
||||
github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE=
|
||||
github.com/jackc/pgtype v1.8.0 h1:iFVCcVhYlw0PulYCVoguRGm0SE9guIcPcccnLzHj8bA=
|
||||
github.com/jackc/pgtype v1.8.0/go.mod h1:PqDKcEBtllAtk/2p6z6SHdXW5UB+MhE75tUol2OKexE=
|
||||
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
|
||||
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||
@ -410,8 +446,10 @@ github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXg
|
||||
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
|
||||
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
|
||||
github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc=
|
||||
github.com/jackc/pgx/v4 v4.12.0 h1:xiP3TdnkwyslWNp77yE5XAPfxAsU9RMFDe0c1SwN8h4=
|
||||
github.com/jackc/pgx/v4 v4.12.0/go.mod h1:fE547h6VulLPA3kySjfnSG/e2D861g/50JlVUa/ub60=
|
||||
github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU=
|
||||
github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
|
||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
@ -427,11 +465,14 @@ github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
||||
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
||||
@ -457,7 +498,6 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
@ -467,6 +507,11 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/maragudk/migrate v0.4.3 h1:3NrpSzNdCSSPgN/xwkEduEwqrBIRewSEvtN+mhMS6zc=
|
||||
github.com/maragudk/migrate v0.4.3/go.mod h1:vhmL4s+Xz75KU6DPZWRfqb45YyqjYQfcXliA1DsYzvY=
|
||||
github.com/matoous/go-nanoid v1.5.0 h1:VRorl6uCngneC4oUQqOYtO3S0H5QKFtKuKycFG3euek=
|
||||
@ -517,6 +562,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
@ -534,8 +581,11 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
|
||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
|
||||
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
@ -567,6 +617,8 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/petar/GoLLRB v0.0.0-20130427215148-53be0d36a84c/go.mod h1:HUpKUBZnpzkdx0kD/+Yfuft+uD3zHGtXF/XJB14TUr4=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
@ -614,8 +666,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
@ -639,7 +691,6 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
@ -663,6 +714,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||
@ -676,6 +728,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
@ -697,7 +750,15 @@ github.com/swaggest/refl v1.1.0 h1:a+9a75Kv6ciMozPjVbOfcVTEQe81t2R3emvaD9oGQGc=
|
||||
github.com/swaggest/refl v1.1.0/go.mod h1:g3Qa6ki0A/L2yxiuUpT+cuBURuRaltF5SDQpg1kMZSY=
|
||||
github.com/swaggest/swgui v1.8.1 h1:OLcigpoelY0spbpvp6WvBt0I1z+E9egMQlUeEKya+zU=
|
||||
github.com/swaggest/swgui v1.8.1/go.mod h1:YBaAVAwS3ndfvdtW8A4yWDJpge+W57y+8kW+f/DqZtU=
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=
|
||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||
github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww=
|
||||
github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ=
|
||||
github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04=
|
||||
github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/unrolled/secure v1.15.0 h1:q7x+pdp8jAHnbzxu6UheP8fRlG/rwYTb8TPuQ3rn9Og=
|
||||
github.com/unrolled/secure v1.15.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
@ -730,8 +791,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc=
|
||||
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 h1:1wp/gyxsuYtuE/JFxsQRtcCDtMrO2qMvlfXALU5wkzI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0/go.mod h1:gbTHmghkGgqxMomVQQMur1Nba4M0MQ8AYThXDUjsJ38=
|
||||
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
||||
@ -748,6 +809,8 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
@ -770,10 +833,8 @@ golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
@ -818,6 +879,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
@ -861,14 +923,13 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -880,7 +941,6 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@ -892,7 +952,6 @@ golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
@ -986,6 +1045,7 @@ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gG
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
@ -1009,6 +1069,8 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
|
280
registry/app/api/controller/metadata/artifact_mapper.go
Normal file
280
registry/app/api/controller/metadata/artifact_mapper.go
Normal file
@ -0,0 +1,280 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
|
||||
artifactapi "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/registry/types"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func GetArtifactMetadata(artifacts *[]types.ArtifactMetadata) []artifactapi.ArtifactMetadata {
|
||||
artifactMetadataList := make([]artifactapi.ArtifactMetadata, 0, len(*artifacts))
|
||||
for _, artifact := range *artifacts {
|
||||
artifactMetadata := mapToArtifactMetadata(artifact)
|
||||
artifactMetadataList = append(artifactMetadataList, *artifactMetadata)
|
||||
}
|
||||
return artifactMetadataList
|
||||
}
|
||||
|
||||
func mapToArtifactMetadata(artifact types.ArtifactMetadata) *artifactapi.ArtifactMetadata {
|
||||
lastModified := GetTimeInMs(artifact.ModifiedAt)
|
||||
packageType := artifact.PackageType
|
||||
return &artifactapi.ArtifactMetadata{
|
||||
RegistryIdentifier: artifact.RepoName,
|
||||
Name: artifact.Name,
|
||||
LatestVersion: artifact.LatestVersion,
|
||||
Labels: &artifact.Labels,
|
||||
LastModified: &lastModified,
|
||||
PackageType: &packageType,
|
||||
DownloadsCount: &artifact.DownloadCount,
|
||||
}
|
||||
}
|
||||
|
||||
func toPackageType(packageTypeStr string) (artifactapi.PackageType, error) {
|
||||
switch packageTypeStr {
|
||||
case string(artifactapi.PackageTypeDOCKER):
|
||||
return artifactapi.PackageTypeDOCKER, nil
|
||||
case string(artifactapi.PackageTypeGENERIC):
|
||||
return artifactapi.PackageTypeGENERIC, nil
|
||||
case string(artifactapi.PackageTypeHELM):
|
||||
return artifactapi.PackageTypeHELM, nil
|
||||
case string(artifactapi.PackageTypeMAVEN):
|
||||
return artifactapi.PackageTypeMAVEN, nil
|
||||
default:
|
||||
return "", errors.New("invalid package type")
|
||||
}
|
||||
}
|
||||
|
||||
func GetTagMetadata(
|
||||
ctx context.Context,
|
||||
tags *[]types.TagMetadata,
|
||||
latestTag string,
|
||||
image string,
|
||||
regIdentifier string,
|
||||
rootIdentifier string,
|
||||
registryURL string,
|
||||
) []artifactapi.ArtifactVersionMetadata {
|
||||
artifactVersionMetadataList := []artifactapi.ArtifactVersionMetadata{}
|
||||
digestCount := int64(1)
|
||||
for _, tag := range *tags {
|
||||
modifiedAt := GetTimeInMs(tag.ModifiedAt)
|
||||
size := GetImageSize(tag.Size)
|
||||
isLatestVersion := latestTag == tag.Name
|
||||
command := GetPullCommand(rootIdentifier, regIdentifier, image, tag.Name, string(tag.PackageType), registryURL)
|
||||
packageType, err := toPackageType(string(tag.PackageType))
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msgf("Error converting package type %s", tag.PackageType)
|
||||
continue
|
||||
}
|
||||
artifactVersionMetadata := &artifactapi.ArtifactVersionMetadata{
|
||||
PackageType: &packageType,
|
||||
Name: tag.Name,
|
||||
Size: &size,
|
||||
LastModified: &modifiedAt,
|
||||
DigestCount: &digestCount,
|
||||
IslatestVersion: &isLatestVersion,
|
||||
PullCommand: &command,
|
||||
}
|
||||
artifactVersionMetadataList = append(artifactVersionMetadataList, *artifactVersionMetadata)
|
||||
}
|
||||
return artifactVersionMetadataList
|
||||
}
|
||||
|
||||
func GetAllArtifactResponse(
|
||||
artifacts *[]types.ArtifactMetadata,
|
||||
count int64,
|
||||
pageNumber int64,
|
||||
pageSize int,
|
||||
) *artifactapi.ListArtifactResponseJSONResponse {
|
||||
artifactMetadataList := GetArtifactMetadata(artifacts)
|
||||
pageCount := GetPageCount(count, pageSize)
|
||||
listArtifact := &artifactapi.ListArtifact{
|
||||
ItemCount: &count,
|
||||
PageCount: &pageCount,
|
||||
PageIndex: &pageNumber,
|
||||
PageSize: &pageSize,
|
||||
Artifacts: artifactMetadataList,
|
||||
}
|
||||
response := &artifactapi.ListArtifactResponseJSONResponse{
|
||||
Data: *listArtifact,
|
||||
Status: artifactapi.StatusSUCCESS,
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func GetAllArtifactLabelsResponse(
|
||||
artifactLabels *[]string,
|
||||
count int64,
|
||||
pageNumber int64,
|
||||
pageSize int,
|
||||
) *artifactapi.ListArtifactLabelResponseJSONResponse {
|
||||
pageCount := GetPageCount(count, pageSize)
|
||||
listArtifactLabels := &artifactapi.ListArtifactLabel{
|
||||
ItemCount: &count,
|
||||
PageCount: &pageCount,
|
||||
PageIndex: &pageNumber,
|
||||
PageSize: &pageSize,
|
||||
Labels: *artifactLabels,
|
||||
}
|
||||
response := &artifactapi.ListArtifactLabelResponseJSONResponse{
|
||||
Data: *listArtifactLabels,
|
||||
Status: artifactapi.StatusSUCCESS,
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func GetAllArtifactVersionResponse(
|
||||
ctx context.Context,
|
||||
tags *[]types.TagMetadata,
|
||||
latestTag string,
|
||||
image string,
|
||||
count int64,
|
||||
regInfo *RegistryRequestInfo,
|
||||
pageNumber int64,
|
||||
pageSize int,
|
||||
rootIdentifier string,
|
||||
registryURL string,
|
||||
) *artifactapi.ListArtifactVersionResponseJSONResponse {
|
||||
artifactVersionMetadataList := GetTagMetadata(
|
||||
ctx, tags, latestTag, image,
|
||||
regInfo.RegistryIdentifier, rootIdentifier, registryURL,
|
||||
)
|
||||
pageCount := GetPageCount(count, pageSize)
|
||||
listArtifactVersions := &artifactapi.ListArtifactVersion{
|
||||
ItemCount: &count,
|
||||
PageCount: &pageCount,
|
||||
PageIndex: &pageNumber,
|
||||
PageSize: &pageSize,
|
||||
ArtifactVersions: &artifactVersionMetadataList,
|
||||
}
|
||||
response := &artifactapi.ListArtifactVersionResponseJSONResponse{
|
||||
Data: *listArtifactVersions,
|
||||
Status: artifactapi.StatusSUCCESS,
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func GetDockerArtifactDetails(
|
||||
registry *types.Registry,
|
||||
tag *types.TagDetail,
|
||||
manifest *types.Manifest,
|
||||
isLatestTag bool,
|
||||
regInfo *RegistryRequestBaseInfo,
|
||||
registryURL string,
|
||||
) *artifactapi.DockerArtifactDetailResponseJSONResponse {
|
||||
repoPath := getRepoPath(registry.Name, tag.ImageName, manifest.Digest.String())
|
||||
pullCommand := GetDockerPullCommand(regInfo.rootIdentifier, registry.Name, tag.ImageName, tag.Name, registryURL)
|
||||
createdAt := GetTimeInMs(tag.CreatedAt)
|
||||
modifiedAt := GetTimeInMs(tag.UpdatedAt)
|
||||
size := GetSize(manifest.TotalSize)
|
||||
artifactDetail := &artifactapi.DockerArtifactDetail{
|
||||
ImageName: tag.ImageName,
|
||||
Version: tag.Name,
|
||||
PackageType: registry.PackageType,
|
||||
IsLatestVersion: &isLatestTag,
|
||||
CreatedAt: &createdAt,
|
||||
ModifiedAt: &modifiedAt,
|
||||
RegistryPath: repoPath,
|
||||
PullCommand: &pullCommand,
|
||||
Url: GetTagURL(regInfo.rootIdentifier, tag.ImageName, tag.Name, registry.Name, registryURL),
|
||||
Size: &size,
|
||||
}
|
||||
|
||||
response := &artifactapi.DockerArtifactDetailResponseJSONResponse{
|
||||
Data: *artifactDetail,
|
||||
Status: artifactapi.StatusSUCCESS,
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func GetHelmArtifactDetails(
|
||||
registry *types.Registry,
|
||||
tag *types.TagDetail,
|
||||
manifest *types.Manifest,
|
||||
isLatestTag bool,
|
||||
rootIdentifier string,
|
||||
registryURL string,
|
||||
) *artifactapi.HelmArtifactDetailResponseJSONResponse {
|
||||
repoPath := getRepoPath(registry.Name, tag.ImageName, manifest.Digest.String())
|
||||
pullCommand := GetHelmPullCommand(rootIdentifier, registry.Name, tag.ImageName, tag.Name, registryURL)
|
||||
createdAt := GetTimeInMs(tag.CreatedAt)
|
||||
modifiedAt := GetTimeInMs(tag.UpdatedAt)
|
||||
size := GetSize(manifest.TotalSize)
|
||||
artifactDetail := &artifactapi.HelmArtifactDetail{
|
||||
Artifact: &tag.ImageName,
|
||||
Version: tag.Name,
|
||||
PackageType: registry.PackageType,
|
||||
IsLatestVersion: &isLatestTag,
|
||||
CreatedAt: &createdAt,
|
||||
ModifiedAt: &modifiedAt,
|
||||
RegistryPath: repoPath,
|
||||
PullCommand: &pullCommand,
|
||||
Url: GetTagURL(rootIdentifier, tag.ImageName, tag.Name, registry.Name, registryURL),
|
||||
Size: &size,
|
||||
}
|
||||
|
||||
response := &artifactapi.HelmArtifactDetailResponseJSONResponse{
|
||||
Data: *artifactDetail,
|
||||
Status: artifactapi.StatusSUCCESS,
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func GetArtifactSummary(artifact types.ArtifactMetadata) *artifactapi.ArtifactSummaryResponseJSONResponse {
|
||||
downloads := int64(0)
|
||||
createdAt := GetTimeInMs(artifact.CreatedAt)
|
||||
modifiedAt := GetTimeInMs(artifact.ModifiedAt)
|
||||
artifactVersionSummary := &artifactapi.ArtifactSummary{
|
||||
CreatedAt: &createdAt,
|
||||
ModifiedAt: &modifiedAt,
|
||||
DownloadsCount: &downloads,
|
||||
ImageName: artifact.Name,
|
||||
Labels: &artifact.Labels,
|
||||
PackageType: artifact.PackageType,
|
||||
}
|
||||
response := &artifactapi.ArtifactSummaryResponseJSONResponse{
|
||||
Data: *artifactVersionSummary,
|
||||
Status: artifactapi.StatusSUCCESS,
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func GetArtifactVersionSummary(
|
||||
tag *types.TagMetadata,
|
||||
artifactName string,
|
||||
isLatestTag bool,
|
||||
) *artifactapi.ArtifactVersionSummaryResponseJSONResponse {
|
||||
artifactVersionSummary := &artifactapi.ArtifactVersionSummary{
|
||||
ImageName: artifactName,
|
||||
IsLatestVersion: &isLatestTag,
|
||||
PackageType: tag.PackageType,
|
||||
Version: tag.Name,
|
||||
}
|
||||
response := &artifactapi.ArtifactVersionSummaryResponseJSONResponse{
|
||||
Data: *artifactVersionSummary,
|
||||
Status: artifactapi.StatusSUCCESS,
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func getRepoPath(registry string, image string, tag string) string {
|
||||
return filepath.Join(registry, image, tag)
|
||||
}
|
349
registry/app/api/controller/metadata/base.go
Normal file
349
registry/app/api/controller/metadata/base.go
Normal file
@ -0,0 +1,349 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/harness/gitness/app/paths"
|
||||
api "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
storagedriver "github.com/harness/gitness/registry/app/driver"
|
||||
"github.com/harness/gitness/registry/app/pkg/commons"
|
||||
"github.com/harness/gitness/registry/app/storage"
|
||||
"github.com/harness/gitness/registry/types"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const MediaTypeImageConfig = "application/vnd.docker.container.image.v1+json"
|
||||
|
||||
var _ api.StrictServerInterface = (*APIController)(nil)
|
||||
|
||||
type RegistryRequestBaseInfo struct {
|
||||
rootIdentifier string
|
||||
rootIdentifierID int64
|
||||
|
||||
registryRef string
|
||||
RegistryIdentifier string
|
||||
registryID int64
|
||||
|
||||
parentRef string
|
||||
parentID int64
|
||||
}
|
||||
|
||||
type RegistryRequestInfo struct {
|
||||
RegistryRequestBaseInfo
|
||||
packageTypes []string
|
||||
sortByField string
|
||||
sortByOrder string
|
||||
offset int
|
||||
limit int
|
||||
pageNumber int64
|
||||
searchTerm string
|
||||
labels []string
|
||||
}
|
||||
|
||||
// GetRegistryRequestBaseInfo returns the base info for the registry request
|
||||
// One of the regRefParam or (parentRefParam + regIdentifierParam) should be provided.
|
||||
func (c *APIController) GetRegistryRequestBaseInfo(
|
||||
ctx context.Context,
|
||||
parentRef string,
|
||||
regRef string,
|
||||
) (*RegistryRequestBaseInfo, error) {
|
||||
// ---------- CHECKS ------------
|
||||
if commons.IsEmpty(parentRef) && !commons.IsEmpty(regRef) {
|
||||
parentRef, _, _ = paths.DisectLeaf(regRef)
|
||||
}
|
||||
|
||||
// ---------- PARENT ------------
|
||||
if commons.IsEmpty(parentRef) {
|
||||
return nil, fmt.Errorf("parent reference is required")
|
||||
}
|
||||
rootIdentifier, _, err := paths.DisectRoot(parentRef)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid parent reference: %w", err)
|
||||
}
|
||||
|
||||
rootSpace, err := c.spaceStore.FindByRef(ctx, rootIdentifier)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("root space not found: %w", err)
|
||||
}
|
||||
parentSpace, err := c.spaceStore.FindByRef(ctx, parentRef)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parent space not found: %w", err)
|
||||
}
|
||||
rootIdentifierID := rootSpace.ID
|
||||
parentID := parentSpace.ID
|
||||
|
||||
baseInfo := &RegistryRequestBaseInfo{
|
||||
parentRef: parentRef,
|
||||
parentID: parentID,
|
||||
rootIdentifier: rootIdentifier,
|
||||
rootIdentifierID: rootIdentifierID,
|
||||
}
|
||||
|
||||
// ---------- REGISTRY ------------
|
||||
if !commons.IsEmpty(regRef) {
|
||||
_, regIdentifier, _ := paths.DisectLeaf(regRef)
|
||||
|
||||
reg, getRegistryErr := c.RegistryRepository.GetByParentIDAndName(ctx, parentID, regIdentifier)
|
||||
if getRegistryErr != nil {
|
||||
return nil, fmt.Errorf("registry not found: %w", err)
|
||||
}
|
||||
|
||||
baseInfo.registryRef = regRef
|
||||
baseInfo.RegistryIdentifier = regIdentifier
|
||||
baseInfo.registryID = reg.ID
|
||||
}
|
||||
|
||||
return baseInfo, nil
|
||||
}
|
||||
|
||||
func (c *APIController) GetRegistryRequestInfo(
|
||||
ctx context.Context,
|
||||
packageTypesParam *api.PackageTypeParam,
|
||||
page *api.PageNumber,
|
||||
size *api.PageSize,
|
||||
search *api.SearchTerm,
|
||||
resource string,
|
||||
parentRef string,
|
||||
regRef string,
|
||||
labelsParam *api.LabelsParam,
|
||||
sortOrder *api.SortOrder,
|
||||
sortField *api.SortField,
|
||||
) (*RegistryRequestInfo, error) {
|
||||
packageTypes := []string{}
|
||||
if packageTypesParam != nil {
|
||||
packageTypes = *packageTypesParam
|
||||
}
|
||||
sortByField := ""
|
||||
sortByOrder := ""
|
||||
if sortOrder != nil {
|
||||
sortByOrder = string(*sortOrder)
|
||||
}
|
||||
|
||||
if sortField != nil {
|
||||
sortByField = string(*sortField)
|
||||
}
|
||||
|
||||
labels := []string{}
|
||||
|
||||
if labelsParam != nil {
|
||||
labels = *labelsParam
|
||||
}
|
||||
|
||||
sortByField = GetSortByField(sortByField, resource)
|
||||
sortByOrder = GetSortByOrder(sortByOrder)
|
||||
|
||||
offset := GetOffset(size, page)
|
||||
limit := GetPageLimit(size)
|
||||
pageNumber := GetPageNumber(page)
|
||||
|
||||
searchTerm := ""
|
||||
if search != nil {
|
||||
searchTerm = string(*search)
|
||||
}
|
||||
|
||||
baseInfo, err := c.GetRegistryRequestBaseInfo(ctx, parentRef, regRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &RegistryRequestInfo{
|
||||
RegistryRequestBaseInfo: *baseInfo,
|
||||
packageTypes: packageTypes,
|
||||
sortByField: sortByField,
|
||||
sortByOrder: sortByOrder,
|
||||
offset: offset,
|
||||
limit: limit,
|
||||
pageNumber: pageNumber,
|
||||
searchTerm: searchTerm,
|
||||
labels: labels,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getManifestConfig(
|
||||
ctx context.Context,
|
||||
digest digest.Digest,
|
||||
rootRef string,
|
||||
driver storagedriver.StorageDriver,
|
||||
) (*manifestConfig, error) {
|
||||
var config manifestConfig
|
||||
path, err := storage.PathFn(rootRef, digest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get path: %w", err)
|
||||
}
|
||||
|
||||
content, err := driver.GetContent(ctx, path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get content for image config: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(content, &config); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal manifest config: %w", err)
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func (c *APIController) setUpstreamProxyIDs(
|
||||
ctx context.Context,
|
||||
registry *types.Registry,
|
||||
dto api.RegistryRequest,
|
||||
parentID int64,
|
||||
) error {
|
||||
if dto.Config.Type != api.RegistryTypeVIRTUAL {
|
||||
return fmt.Errorf("invalid call to set upstream proxy ids for parentID: %d", parentID)
|
||||
}
|
||||
virtualConfig, err := dto.Config.AsVirtualConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get virtualConfig: %w", err)
|
||||
}
|
||||
if nil == virtualConfig.UpstreamProxies || commons.IsEmpty(*(virtualConfig.UpstreamProxies)) {
|
||||
log.Ctx(ctx).Debug().Msgf("Nothing to do for registryRequest: %s", dto.Identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
upstreamProxies, err := c.RegistryRepository.FetchUpstreamProxyIDs(
|
||||
ctx,
|
||||
*virtualConfig.UpstreamProxies,
|
||||
parentID,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fectch upstream proxy IDs :%w", err)
|
||||
}
|
||||
registry.UpstreamProxies = upstreamProxies
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *APIController) getUpstreamProxyKeys(ctx context.Context, ids []int64) []string {
|
||||
repoKeys, _ := c.RegistryRepository.FetchUpstreamProxyKeys(ctx, ids)
|
||||
return repoKeys
|
||||
}
|
||||
|
||||
type manifestConfig struct {
|
||||
CreatedAt *string `json:"created,omitempty"`
|
||||
Digest string `json:"digest,omitempty"`
|
||||
History []historyEntry `json:"history"`
|
||||
ModifiedAt *string `json:"modified,omitempty"`
|
||||
Os string `json:"os"`
|
||||
Arch string `json:"architecture,omitempty"`
|
||||
}
|
||||
|
||||
type historyEntry struct {
|
||||
Created string `json:"created"`
|
||||
CreatedBy string `json:"created_by"`
|
||||
EmptyLayer bool `json:"empty_layer"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
}
|
||||
|
||||
func getRepoEntityFields(dto api.RegistryRequest) ([]string, []string, string, []string) {
|
||||
allowedPattern := []string{}
|
||||
if dto.AllowedPattern != nil {
|
||||
allowedPattern = *dto.AllowedPattern
|
||||
}
|
||||
blockedPattern := []string{}
|
||||
if dto.BlockedPattern != nil {
|
||||
blockedPattern = *dto.BlockedPattern
|
||||
}
|
||||
description := ""
|
||||
if dto.Description != nil {
|
||||
description = *dto.Description
|
||||
}
|
||||
labels := []string{}
|
||||
if dto.Labels != nil {
|
||||
labels = *dto.Labels
|
||||
}
|
||||
return allowedPattern, blockedPattern, description, labels
|
||||
}
|
||||
|
||||
func CreateVirtualRepositoryResponse(
|
||||
registry *types.Registry,
|
||||
upstreamProxyKeys []string,
|
||||
cleanupPolicies *[]types.CleanupPolicy,
|
||||
rootIdentifier string,
|
||||
registryURL string,
|
||||
) *api.RegistryResponseJSONResponse {
|
||||
createdAt := GetTimeInMs(registry.CreatedAt)
|
||||
modifiedAt := GetTimeInMs(registry.UpdatedAt)
|
||||
allowedPattern := registry.AllowedPattern
|
||||
blockedPattern := registry.BlockedPattern
|
||||
labels := registry.Labels
|
||||
|
||||
config := api.RegistryConfig{}
|
||||
_ = config.FromVirtualConfig(api.VirtualConfig{UpstreamProxies: &upstreamProxyKeys})
|
||||
response := &api.RegistryResponseJSONResponse{
|
||||
Data: api.Registry{
|
||||
Identifier: registry.Name,
|
||||
Description: ®istry.Description,
|
||||
Url: GetRepoURL(rootIdentifier, registry.Name, registryURL),
|
||||
PackageType: registry.PackageType,
|
||||
AllowedPattern: &allowedPattern,
|
||||
BlockedPattern: &blockedPattern,
|
||||
CreatedAt: &createdAt,
|
||||
ModifiedAt: &modifiedAt,
|
||||
CleanupPolicy: CreateCleanupPolicyResponse(cleanupPolicies),
|
||||
Config: &config,
|
||||
Labels: &labels,
|
||||
},
|
||||
Status: api.StatusSUCCESS,
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func CreateUpstreamProxyResponseJSONResponse(upstreamproxy *types.UpstreamProxy) *api.RegistryResponseJSONResponse {
|
||||
createdAt := GetTimeInMs(upstreamproxy.CreatedAt)
|
||||
modifiedAt := GetTimeInMs(upstreamproxy.UpdatedAt)
|
||||
allowedPattern := upstreamproxy.AllowedPattern
|
||||
blockedPattern := upstreamproxy.BlockedPattern
|
||||
configAuth := &api.UpstreamConfig_Auth{}
|
||||
|
||||
if api.AuthType(upstreamproxy.RepoAuthType) == api.AuthTypeUserPassword {
|
||||
auth := api.UserPassword{}
|
||||
auth.UserName = upstreamproxy.UserName
|
||||
// FIXME: Mask this password.
|
||||
auth.SecretIdentifier = &upstreamproxy.SecretIdentifier
|
||||
auth.SecretSpaceId = &upstreamproxy.SecretSpaceID
|
||||
_ = configAuth.FromUserPassword(auth)
|
||||
}
|
||||
|
||||
source := api.UpstreamConfigSource(upstreamproxy.Source)
|
||||
|
||||
config := api.UpstreamConfig{
|
||||
AuthType: api.AuthType(upstreamproxy.RepoAuthType),
|
||||
Auth: configAuth,
|
||||
Source: &source,
|
||||
Url: &upstreamproxy.RepoURL,
|
||||
}
|
||||
registryConfig := &api.RegistryConfig{}
|
||||
_ = registryConfig.FromUpstreamConfig(config)
|
||||
|
||||
response := &api.RegistryResponseJSONResponse{
|
||||
Data: api.Registry{
|
||||
Identifier: upstreamproxy.RepoKey,
|
||||
PackageType: upstreamproxy.PackageType,
|
||||
Url: upstreamproxy.RepoURL,
|
||||
AllowedPattern: &allowedPattern,
|
||||
BlockedPattern: &blockedPattern,
|
||||
CreatedAt: &createdAt,
|
||||
ModifiedAt: &modifiedAt,
|
||||
Config: registryConfig,
|
||||
},
|
||||
Status: api.StatusSUCCESS,
|
||||
}
|
||||
return response
|
||||
}
|
81
registry/app/api/controller/metadata/cleanuppolicy_mapper.go
Normal file
81
registry/app/api/controller/metadata/cleanuppolicy_mapper.go
Normal file
@ -0,0 +1,81 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/registry/types"
|
||||
)
|
||||
|
||||
func CreateCleanupPolicyEntity(
|
||||
config *artifact.ModifyRegistryJSONRequestBody,
|
||||
repoID int64,
|
||||
) *[]types.CleanupPolicy {
|
||||
if config == nil || config.CleanupPolicy == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var cleanupPolicyEntities []types.CleanupPolicy
|
||||
cleanupPolicyDto := *config.CleanupPolicy
|
||||
|
||||
for _, value := range cleanupPolicyDto {
|
||||
cleanupPolicyEntity := getCleanupPolicyEntity(value, repoID)
|
||||
cleanupPolicyEntities = append(cleanupPolicyEntities, *cleanupPolicyEntity)
|
||||
}
|
||||
return &cleanupPolicyEntities
|
||||
}
|
||||
|
||||
func CreateCleanupPolicyResponse(
|
||||
cleanupPolicyEntities *[]types.CleanupPolicy,
|
||||
) *[]artifact.CleanupPolicy {
|
||||
var cleanupPolicyDtos []artifact.CleanupPolicy
|
||||
|
||||
for _, value := range *cleanupPolicyEntities {
|
||||
cleanupPolicy := getCleanupPolicyDto(value)
|
||||
cleanupPolicyDtos = append(cleanupPolicyDtos, *cleanupPolicy)
|
||||
}
|
||||
return &cleanupPolicyDtos
|
||||
}
|
||||
|
||||
func getCleanupPolicyEntity(
|
||||
cleanupPolicy artifact.CleanupPolicy,
|
||||
repoID int64,
|
||||
) *types.CleanupPolicy {
|
||||
expireTime := time.Duration(*cleanupPolicy.ExpireDays) * 24 * time.Hour
|
||||
return &types.CleanupPolicy{
|
||||
Name: *cleanupPolicy.Name,
|
||||
VersionPrefix: *cleanupPolicy.VersionPrefix,
|
||||
PackagePrefix: *cleanupPolicy.PackagePrefix,
|
||||
ExpiryTime: expireTime.Milliseconds(),
|
||||
RegistryID: repoID,
|
||||
}
|
||||
}
|
||||
|
||||
func getCleanupPolicyDto(
|
||||
cleanupPolicy types.CleanupPolicy,
|
||||
) *artifact.CleanupPolicy {
|
||||
packagePrefix := cleanupPolicy.PackagePrefix
|
||||
versionPrefix := cleanupPolicy.VersionPrefix
|
||||
expiryDays := int(time.Duration(cleanupPolicy.ExpiryTime).Hours() / 24)
|
||||
|
||||
return &artifact.CleanupPolicy{
|
||||
Name: &cleanupPolicy.Name,
|
||||
VersionPrefix: &versionPrefix,
|
||||
PackagePrefix: &packagePrefix,
|
||||
ExpireDays: &expiryDays,
|
||||
}
|
||||
}
|
71
registry/app/api/controller/metadata/controller.go
Normal file
71
registry/app/api/controller/metadata/controller.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 metadata
|
||||
|
||||
import (
|
||||
"github.com/harness/gitness/app/auth/authz"
|
||||
corestore "github.com/harness/gitness/app/store"
|
||||
urlprovider "github.com/harness/gitness/app/url"
|
||||
"github.com/harness/gitness/audit"
|
||||
storagedriver "github.com/harness/gitness/registry/app/driver"
|
||||
"github.com/harness/gitness/registry/app/store"
|
||||
"github.com/harness/gitness/store/database/dbtx"
|
||||
)
|
||||
|
||||
// APIController simple struct.
|
||||
type APIController struct {
|
||||
ArtifactStore store.ArtifactRepository
|
||||
RegistryRepository store.RegistryRepository
|
||||
UpstreamProxyStore store.UpstreamProxyConfigRepository
|
||||
TagStore store.TagRepository
|
||||
ManifestStore store.ManifestRepository
|
||||
CleanupPolicyStore store.CleanupPolicyRepository
|
||||
spaceStore corestore.SpaceStore
|
||||
tx dbtx.Transactor
|
||||
StorageDriver storagedriver.StorageDriver
|
||||
URLProvider urlprovider.Provider
|
||||
Authorizer authz.Authorizer
|
||||
AuditService audit.Service
|
||||
}
|
||||
|
||||
func NewAPIController(
|
||||
repositoryStore store.RegistryRepository,
|
||||
upstreamProxyStore store.UpstreamProxyConfigRepository,
|
||||
tagStore store.TagRepository,
|
||||
manifestStore store.ManifestRepository,
|
||||
cleanupPolicyStore store.CleanupPolicyRepository,
|
||||
artifactStore store.ArtifactRepository,
|
||||
driver storagedriver.StorageDriver,
|
||||
spaceStore corestore.SpaceStore,
|
||||
tx dbtx.Transactor,
|
||||
urlProvider urlprovider.Provider,
|
||||
authorizer authz.Authorizer,
|
||||
auditService audit.Service,
|
||||
) *APIController {
|
||||
return &APIController{
|
||||
RegistryRepository: repositoryStore,
|
||||
UpstreamProxyStore: upstreamProxyStore,
|
||||
TagStore: tagStore,
|
||||
ManifestStore: manifestStore,
|
||||
CleanupPolicyStore: cleanupPolicyStore,
|
||||
ArtifactStore: artifactStore,
|
||||
spaceStore: spaceStore,
|
||||
StorageDriver: driver,
|
||||
tx: tx,
|
||||
URLProvider: urlProvider,
|
||||
Authorizer: authorizer,
|
||||
AuditService: auditService,
|
||||
}
|
||||
}
|
321
registry/app/api/controller/metadata/create_registry.go
Normal file
321
registry/app/api/controller/metadata/create_registry.go
Normal file
@ -0,0 +1,321 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/app/auth"
|
||||
"github.com/harness/gitness/audit"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
registrytypes "github.com/harness/gitness/registry/types"
|
||||
"github.com/harness/gitness/types"
|
||||
gitnessenum "github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (c *APIController) CreateRegistry(
|
||||
ctx context.Context,
|
||||
r artifact.CreateRegistryRequestObject,
|
||||
) (artifact.CreateRegistryResponseObject, error) {
|
||||
registryRequest := artifact.RegistryRequest(*r.Body)
|
||||
parentRef := artifact.SpaceRefPathParam(*registryRequest.ParentRef)
|
||||
|
||||
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, string(parentRef), "")
|
||||
if err != nil {
|
||||
return artifact.CreateRegistry400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, err
|
||||
}
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.CreateRegistry400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, err
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
if err = apiauth.CheckSpaceScope(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
space,
|
||||
gitnessenum.ResourceTypeRegistry,
|
||||
gitnessenum.PermissionRegistryEdit,
|
||||
); err != nil {
|
||||
return artifact.CreateRegistry403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, err
|
||||
}
|
||||
|
||||
if registryRequest.Config.Type == artifact.RegistryTypeVIRTUAL {
|
||||
return c.createVirtualRegistry(ctx, registryRequest, regInfo, session, parentRef)
|
||||
}
|
||||
registry, upstreamproxy, err := CreateUpstreamProxyEntity(
|
||||
registryRequest,
|
||||
regInfo.parentID, regInfo.rootIdentifierID,
|
||||
)
|
||||
var registryID int64
|
||||
if err != nil {
|
||||
return throwCreateRegistry400Error(err), err
|
||||
}
|
||||
|
||||
err = c.tx.WithTx(
|
||||
ctx, func(ctx context.Context) error {
|
||||
registryID, err = c.createRegistryWithAudit(ctx, registry, session.Principal, string(parentRef))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create registry: %w", err)
|
||||
}
|
||||
|
||||
upstreamproxy.RegistryID = registryID
|
||||
|
||||
_, err = c.createUpstreamProxyWithAudit(
|
||||
ctx, upstreamproxy, session.Principal, string(parentRef), registry.Name,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create upstream proxy: %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return throwCreateRegistry400Error(err), err
|
||||
}
|
||||
upstreamproxyEntity, err := c.UpstreamProxyStore.Get(ctx, registryID)
|
||||
if err != nil {
|
||||
return throwCreateRegistry400Error(err), err
|
||||
}
|
||||
|
||||
return artifact.CreateRegistry201JSONResponse{
|
||||
RegistryResponseJSONResponse: *CreateUpstreamProxyResponseJSONResponse(upstreamproxyEntity),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *APIController) createVirtualRegistry(
|
||||
ctx context.Context, registryRequest artifact.RegistryRequest, regInfo *RegistryRequestBaseInfo,
|
||||
session *auth.Session, parentRef artifact.SpaceRefPathParam,
|
||||
) (artifact.CreateRegistryResponseObject, error) {
|
||||
registry, err := CreateRegistryEntity(registryRequest, regInfo.parentID, regInfo.rootIdentifierID)
|
||||
if err != nil {
|
||||
return throwCreateRegistry400Error(err), nil
|
||||
}
|
||||
err = c.setUpstreamProxyIDs(ctx, registry, registryRequest, regInfo.parentID)
|
||||
if err != nil {
|
||||
return throwCreateRegistry400Error(err), nil
|
||||
}
|
||||
id, err := c.createRegistryWithAudit(ctx, registry, session.Principal, string(parentRef))
|
||||
if err != nil {
|
||||
return throwCreateRegistry400Error(err), nil
|
||||
}
|
||||
repoEntity, err := c.RegistryRepository.Get(ctx, id)
|
||||
if err != nil {
|
||||
return throwCreateRegistry400Error(err), nil
|
||||
}
|
||||
cleanupPolicies, err := c.CleanupPolicyStore.GetByRegistryID(ctx, repoEntity.ID)
|
||||
if err != nil {
|
||||
return throwCreateRegistry400Error(err), nil
|
||||
}
|
||||
|
||||
return artifact.CreateRegistry201JSONResponse{
|
||||
RegistryResponseJSONResponse: *CreateVirtualRepositoryResponse(
|
||||
repoEntity, c.getUpstreamProxyKeys(ctx, repoEntity.UpstreamProxies),
|
||||
cleanupPolicies, regInfo.rootIdentifier, c.URLProvider.RegistryURL(),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *APIController) createUpstreamProxyWithAudit(
|
||||
ctx context.Context,
|
||||
upstreamProxy *registrytypes.UpstreamProxyConfig, principal types.Principal,
|
||||
parentRef string, registryName string,
|
||||
) (int64, error) {
|
||||
id, err := c.UpstreamProxyStore.Create(ctx, upstreamProxy)
|
||||
if err != nil {
|
||||
return id, err
|
||||
}
|
||||
auditErr := c.AuditService.Log(
|
||||
ctx,
|
||||
principal,
|
||||
audit.NewResource(audit.ResourceTypeRegistryUpstreamProxy, registryName),
|
||||
audit.ActionCreated,
|
||||
parentRef,
|
||||
audit.WithNewObject(
|
||||
audit.RegistryUpstreamProxyConfigObject{
|
||||
ID: id,
|
||||
RegistryID: upstreamProxy.RegistryID,
|
||||
Source: upstreamProxy.Source,
|
||||
URL: upstreamProxy.URL,
|
||||
AuthType: upstreamProxy.AuthType,
|
||||
CreatedAt: upstreamProxy.CreatedAt,
|
||||
UpdatedAt: upstreamProxy.UpdatedAt,
|
||||
CreatedBy: upstreamProxy.CreatedBy,
|
||||
UpdatedBy: upstreamProxy.UpdatedBy,
|
||||
},
|
||||
),
|
||||
)
|
||||
if auditErr != nil {
|
||||
log.Ctx(ctx).Warn().Msgf(
|
||||
"failed to insert audit log for create upstream proxy config operation: %s", auditErr,
|
||||
)
|
||||
}
|
||||
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (c *APIController) createRegistryWithAudit(
|
||||
ctx context.Context, registry *registrytypes.Registry,
|
||||
principal types.Principal, parentRef string,
|
||||
) (int64, error) {
|
||||
id, err := c.RegistryRepository.Create(ctx, registry)
|
||||
if err != nil {
|
||||
return id, err
|
||||
}
|
||||
auditErr := c.AuditService.Log(
|
||||
ctx,
|
||||
principal,
|
||||
audit.NewResource(audit.ResourceTypeRegistry, registry.Name),
|
||||
audit.ActionCreated,
|
||||
parentRef,
|
||||
audit.WithNewObject(
|
||||
audit.RegistryObject{
|
||||
Registry: *registry,
|
||||
},
|
||||
),
|
||||
)
|
||||
if auditErr != nil {
|
||||
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for create registry operation: %s", auditErr)
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
|
||||
func throwCreateRegistry400Error(err error) artifact.CreateRegistry400JSONResponse {
|
||||
return artifact.CreateRegistry400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func CreateRegistryEntity(
|
||||
dto artifact.RegistryRequest, parentID int64,
|
||||
rootParentID int64,
|
||||
) (*registrytypes.Registry, error) {
|
||||
allowedPattern, blockedPattern, description, labels := getRepoEntityFields(dto)
|
||||
e := ValidatePackageType(string(dto.PackageType))
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
e = ValidateRepoType(string(dto.Config.Type))
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
e = ValidateIdentifier(dto.Identifier)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
entity := ®istrytypes.Registry{
|
||||
Name: dto.Identifier,
|
||||
ParentID: parentID,
|
||||
RootParentID: rootParentID,
|
||||
Description: description,
|
||||
AllowedPattern: allowedPattern,
|
||||
BlockedPattern: blockedPattern,
|
||||
PackageType: dto.PackageType,
|
||||
Labels: labels,
|
||||
Type: dto.Config.Type,
|
||||
}
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
func CreateUpstreamProxyEntity(
|
||||
dto artifact.RegistryRequest,
|
||||
parentID int64,
|
||||
rootParentID int64,
|
||||
) (*registrytypes.Registry, *registrytypes.UpstreamProxyConfig, error) {
|
||||
allowedPattern := []string{}
|
||||
if dto.AllowedPattern != nil {
|
||||
allowedPattern = *dto.AllowedPattern
|
||||
}
|
||||
blockedPattern := []string{}
|
||||
if dto.BlockedPattern != nil {
|
||||
blockedPattern = *dto.BlockedPattern
|
||||
}
|
||||
e := ValidatePackageType(string(dto.PackageType))
|
||||
if e != nil {
|
||||
return nil, nil, e
|
||||
}
|
||||
e = ValidateUpstream(dto.Config)
|
||||
if e != nil {
|
||||
return nil, nil, e
|
||||
}
|
||||
e = ValidateIdentifier(dto.Identifier)
|
||||
if e != nil {
|
||||
return nil, nil, e
|
||||
}
|
||||
repoEntity := ®istrytypes.Registry{
|
||||
Name: dto.Identifier,
|
||||
ParentID: parentID,
|
||||
RootParentID: rootParentID,
|
||||
AllowedPattern: allowedPattern,
|
||||
BlockedPattern: blockedPattern,
|
||||
PackageType: dto.PackageType,
|
||||
Type: artifact.RegistryTypeUPSTREAM,
|
||||
}
|
||||
|
||||
config, e := dto.Config.AsUpstreamConfig()
|
||||
if e != nil {
|
||||
return nil, nil, e
|
||||
}
|
||||
CleanURLPath(config.Url)
|
||||
upstreamProxyConfigEntity := ®istrytypes.UpstreamProxyConfig{
|
||||
URL: *config.Url,
|
||||
AuthType: string(config.AuthType),
|
||||
}
|
||||
if config.Source != nil && len(string(*config.Source)) > 0 {
|
||||
err := ValidateUpstreamSource(string(*config.Source))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
upstreamProxyConfigEntity.Source = string(*config.Source)
|
||||
}
|
||||
if config.AuthType == artifact.AuthTypeUserPassword {
|
||||
res, err := config.Auth.AsUserPassword()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
upstreamProxyConfigEntity.UserName = res.UserName
|
||||
if res.SecretIdentifier == nil || res.SecretSpaceId == nil {
|
||||
return nil, nil, fmt.Errorf("failed to create upstream proxy: secret_identifier or secret_space_id missing")
|
||||
}
|
||||
upstreamProxyConfigEntity.SecretIdentifier = *res.SecretIdentifier
|
||||
upstreamProxyConfigEntity.SecretSpaceID = *res.SecretSpaceId
|
||||
}
|
||||
return repoEntity, upstreamProxyConfigEntity, nil
|
||||
}
|
170
registry/app/api/controller/metadata/delete_registry.go
Normal file
170
registry/app/api/controller/metadata/delete_registry.go
Normal file
@ -0,0 +1,170 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/audit"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
registrytypes "github.com/harness/gitness/registry/types"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (c *APIController) DeleteRegistry(
|
||||
ctx context.Context,
|
||||
r artifact.DeleteRegistryRequestObject,
|
||||
) (artifact.DeleteRegistryResponseObject, error) {
|
||||
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
|
||||
if err != nil {
|
||||
return artifact.DeleteRegistry400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, err
|
||||
}
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.DeleteRegistry400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, err
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryDelete)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.DeleteRegistry403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, err
|
||||
}
|
||||
|
||||
repoEntity, err := c.RegistryRepository.GetByParentIDAndName(ctx, regInfo.parentID, regInfo.RegistryIdentifier)
|
||||
if len(repoEntity.Name) == 0 {
|
||||
return artifact.DeleteRegistry404JSONResponse{
|
||||
NotFoundJSONResponse: artifact.NotFoundJSONResponse(
|
||||
*GetErrorResponse(http.StatusNotFound, "registry doesn't exist with this key"),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return throwDeleteRegistry500Error(err), err
|
||||
}
|
||||
|
||||
if string(repoEntity.Type) == string(artifact.RegistryTypeVIRTUAL) {
|
||||
err = c.deleteRegistryWithAudit(ctx, regInfo, repoEntity, session.Principal, regInfo.parentRef)
|
||||
} else {
|
||||
err = c.tx.WithTx(
|
||||
ctx, func(ctx context.Context) error {
|
||||
err = c.deleteUpstreamProxyWithAudit(
|
||||
ctx, regInfo, session.Principal, regInfo.parentRef, repoEntity.Name,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete upstream proxy: %w", err)
|
||||
}
|
||||
|
||||
err = c.deleteRegistryWithAudit(ctx, regInfo, repoEntity, session.Principal, regInfo.parentRef)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete registry: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return throwDeleteRegistry500Error(err), err
|
||||
}
|
||||
return artifact.DeleteRegistry200JSONResponse{
|
||||
SuccessJSONResponse: artifact.SuccessJSONResponse(*GetSuccessResponse()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *APIController) deleteUpstreamProxyWithAudit(
|
||||
ctx context.Context,
|
||||
regInfo *RegistryRequestBaseInfo, principal types.Principal, parentRef string, registryName string,
|
||||
) error {
|
||||
err := c.UpstreamProxyStore.Delete(ctx, regInfo.parentID, regInfo.RegistryIdentifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
auditErr := c.AuditService.Log(
|
||||
ctx,
|
||||
principal,
|
||||
audit.NewResource(audit.ResourceTypeRegistryUpstreamProxy, registryName),
|
||||
audit.ActionDeleted,
|
||||
parentRef,
|
||||
audit.WithData("registry name", registryName),
|
||||
)
|
||||
if auditErr != nil {
|
||||
log.Ctx(ctx).Warn().Msgf(
|
||||
"failed to insert audit log for delete upstream proxy config operation: %s", auditErr,
|
||||
)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIController) deleteRegistryWithAudit(
|
||||
ctx context.Context, regInfo *RegistryRequestBaseInfo,
|
||||
registry *registrytypes.Registry, principal types.Principal, parentRef string,
|
||||
) error {
|
||||
err := c.RegistryRepository.Delete(ctx, regInfo.parentID, regInfo.RegistryIdentifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auditErr := c.AuditService.Log(
|
||||
ctx,
|
||||
principal,
|
||||
audit.NewResource(audit.ResourceTypeRegistry, registry.Name),
|
||||
audit.ActionDeleted,
|
||||
parentRef,
|
||||
audit.WithOldObject(
|
||||
audit.RegistryObject{
|
||||
Registry: *registry,
|
||||
},
|
||||
),
|
||||
)
|
||||
if auditErr != nil {
|
||||
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for delete registry operation: %s", auditErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func throwDeleteRegistry500Error(err error) artifact.DeleteRegistry500JSONResponse {
|
||||
return artifact.DeleteRegistry500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}
|
||||
}
|
112
registry/app/api/controller/metadata/get_artifact_stats.go
Normal file
112
registry/app/api/controller/metadata/get_artifact_stats.go
Normal file
@ -0,0 +1,112 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
func (c *APIController) GetArtifactStats(
|
||||
_ context.Context,
|
||||
_ artifact.GetArtifactStatsRequestObject,
|
||||
) (artifact.GetArtifactStatsResponseObject, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *APIController) GetArtifactStatsForSpace(
|
||||
ctx context.Context,
|
||||
r artifact.GetArtifactStatsForSpaceRequestObject,
|
||||
) (artifact.GetArtifactStatsForSpaceResponseObject, error) {
|
||||
parentRef := r.SpaceRef
|
||||
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, string(parentRef), "")
|
||||
if err != nil {
|
||||
return artifact.GetArtifactStatsForSpace400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.GetArtifactStatsForSpace400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.GetArtifactStatsForSpace403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *APIController) GetArtifactStatsForRegistry(
|
||||
ctx context.Context,
|
||||
r artifact.GetArtifactStatsForRegistryRequestObject,
|
||||
) (artifact.GetArtifactStatsForRegistryResponseObject, error) {
|
||||
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
|
||||
if err != nil {
|
||||
return artifact.GetArtifactStatsForRegistry400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.GetArtifactStatsForRegistry400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
if err = apiauth.CheckSpaceScope(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
space,
|
||||
enum.ResourceTypeRegistry,
|
||||
enum.PermissionRegistryView,
|
||||
); err != nil {
|
||||
return artifact.GetArtifactStatsForRegistry403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
113
registry/app/api/controller/metadata/get_artifacts.go
Normal file
113
registry/app/api/controller/metadata/get_artifacts.go
Normal file
@ -0,0 +1,113 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/registry/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
func (c *APIController) GetAllArtifacts(
|
||||
ctx context.Context,
|
||||
r artifact.GetAllArtifactsRequestObject,
|
||||
) (artifact.GetAllArtifactsResponseObject, error) {
|
||||
ref := ""
|
||||
if r.Params.RegIdentifier != nil {
|
||||
ref2, err2 := GetRegRef(string(r.SpaceRef), string(*r.Params.RegIdentifier))
|
||||
if err2 != nil {
|
||||
return c.getAllArtifacts400JsonResponse(err2)
|
||||
}
|
||||
ref = ref2
|
||||
}
|
||||
|
||||
regInfo, err := c.GetRegistryRequestInfo(
|
||||
ctx, r.Params.PackageType, r.Params.Page, r.Params.Size,
|
||||
r.Params.SearchTerm, ArtifactResource, string(r.SpaceRef), ref, r.Params.Label,
|
||||
r.Params.SortOrder, r.Params.SortField,
|
||||
)
|
||||
if err != nil {
|
||||
return c.getAllArtifacts400JsonResponse(err)
|
||||
}
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.GetAllArtifacts400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.GetAllArtifacts403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var artifacts *[]types.ArtifactMetadata
|
||||
var count int64
|
||||
if len(regInfo.RegistryIdentifier) == 0 {
|
||||
artifacts, err = c.TagStore.GetAllArtifactsByParentID(
|
||||
ctx, regInfo.parentID, ®Info.packageTypes,
|
||||
regInfo.sortByField, regInfo.sortByOrder, regInfo.limit, regInfo.offset, regInfo.searchTerm, regInfo.labels,
|
||||
)
|
||||
count, _ = c.TagStore.CountAllArtifactsByParentID(
|
||||
ctx, regInfo.parentID, ®Info.packageTypes,
|
||||
regInfo.searchTerm, regInfo.labels,
|
||||
)
|
||||
} else {
|
||||
artifacts, err = c.TagStore.GetAllArtifactsByRepo(
|
||||
ctx, regInfo.parentID, regInfo.RegistryIdentifier,
|
||||
regInfo.sortByField, regInfo.sortByOrder, regInfo.limit, regInfo.offset, regInfo.searchTerm, regInfo.labels,
|
||||
)
|
||||
count, _ = c.TagStore.CountAllArtifactsByRepo(
|
||||
ctx, regInfo.parentID, regInfo.RegistryIdentifier,
|
||||
regInfo.searchTerm, regInfo.labels,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return artifact.GetAllArtifacts500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
return artifact.GetAllArtifacts200JSONResponse{
|
||||
ListArtifactResponseJSONResponse: *GetAllArtifactResponse(artifacts, count, regInfo.pageNumber, regInfo.limit),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *APIController) getAllArtifacts400JsonResponse(err error) (artifact.GetAllArtifactsResponseObject, error) {
|
||||
return artifact.GetAllArtifacts400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/registry/types"
|
||||
store2 "github.com/harness/gitness/store"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func (c *APIController) GetDockerArtifactDetails(
|
||||
ctx context.Context,
|
||||
r artifact.GetDockerArtifactDetailsRequestObject,
|
||||
) (artifact.GetDockerArtifactDetailsResponseObject, error) {
|
||||
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
|
||||
if err != nil {
|
||||
return artifact.GetDockerArtifactDetails400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.GetDockerArtifactDetails400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.GetDockerArtifactDetails403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
image := string(r.Artifact)
|
||||
version := string(r.Version)
|
||||
manifestDigest := string(r.Params.Digest)
|
||||
|
||||
registry, err := c.RegistryRepository.GetByParentIDAndName(ctx, regInfo.parentID, regInfo.RegistryIdentifier)
|
||||
|
||||
if err != nil {
|
||||
return artifact.GetDockerArtifactDetails500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
tag, err := c.TagStore.GetTagDetail(ctx, registry.ID, image, version)
|
||||
if err != nil {
|
||||
return getArtifactDetailsErrResponse(err)
|
||||
}
|
||||
dgst, err := types.NewDigest(digest.Digest(manifestDigest))
|
||||
if err != nil {
|
||||
return getArtifactDetailsErrResponse(err)
|
||||
}
|
||||
m, err := c.ManifestStore.FindManifestByDigest(ctx, registry.ID, image, dgst)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, store2.ErrResourceNotFound) {
|
||||
return getArtifactDetailsErrResponse(fmt.Errorf("manifest not found"))
|
||||
}
|
||||
return getArtifactDetailsErrResponse(err)
|
||||
}
|
||||
|
||||
latestTag, err := c.TagStore.GetLatestTag(ctx, registry.ID, image)
|
||||
if err != nil {
|
||||
return getArtifactDetailsErrResponse(err)
|
||||
}
|
||||
|
||||
return artifact.GetDockerArtifactDetails200JSONResponse{
|
||||
DockerArtifactDetailResponseJSONResponse: *GetDockerArtifactDetails(
|
||||
registry, tag, m,
|
||||
latestTag.ID == tag.ID, regInfo, c.URLProvider.RegistryURL(),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getArtifactDetailsErrResponse(err error) (artifact.GetDockerArtifactDetailsResponseObject, error) {
|
||||
return artifact.GetDockerArtifactDetails500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/registry/types"
|
||||
store2 "github.com/harness/gitness/store"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func (c *APIController) GetDockerArtifactLayers(
|
||||
ctx context.Context,
|
||||
r artifact.GetDockerArtifactLayersRequestObject,
|
||||
) (artifact.GetDockerArtifactLayersResponseObject, error) {
|
||||
regInfo, _ := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.GetDockerArtifactLayers400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.GetDockerArtifactLayers403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
manifestDigest := string(r.Params.Digest)
|
||||
image := string(r.Artifact)
|
||||
|
||||
dgst, err := types.NewDigest(digest.Digest(manifestDigest))
|
||||
if err != nil {
|
||||
return getLayersErrorResponse(err)
|
||||
}
|
||||
registry, err := c.RegistryRepository.GetByParentIDAndName(ctx, regInfo.parentID, regInfo.RegistryIdentifier)
|
||||
if err != nil {
|
||||
return getLayersErrorResponse(err)
|
||||
}
|
||||
if registry == nil {
|
||||
return getLayersErrorResponse(fmt.Errorf("repository not found"))
|
||||
}
|
||||
|
||||
m, err := c.ManifestStore.FindManifestByDigest(ctx, registry.ID, image, dgst)
|
||||
if err != nil {
|
||||
if errors.Is(err, store2.ErrResourceNotFound) {
|
||||
return getLayersErrorResponse(fmt.Errorf("manifest not found"))
|
||||
}
|
||||
return getLayersErrorResponse(err)
|
||||
}
|
||||
|
||||
mConfig, err := getManifestConfig(ctx, m.Configuration.Digest, regInfo.rootIdentifier, c.StorageDriver)
|
||||
if err != nil {
|
||||
return getLayersErrorResponse(err)
|
||||
}
|
||||
|
||||
layersSummary := &artifact.DockerLayersSummary{
|
||||
Digest: m.Digest.String(),
|
||||
}
|
||||
|
||||
if mConfig != nil {
|
||||
osArch := fmt.Sprintf("%s/%s", mConfig.Os, mConfig.Arch)
|
||||
layersSummary.OsArch = &osArch
|
||||
var historyLayers []artifact.DockerLayerEntry
|
||||
for _, history := range mConfig.History {
|
||||
historyLayers = append(
|
||||
historyLayers, artifact.DockerLayerEntry{
|
||||
Command: history.CreatedBy,
|
||||
},
|
||||
)
|
||||
}
|
||||
layersSummary.Layers = &historyLayers
|
||||
} else {
|
||||
return getLayersErrorResponse(fmt.Errorf("manifest config not found"))
|
||||
}
|
||||
|
||||
return artifact.GetDockerArtifactLayers200JSONResponse{
|
||||
DockerLayersResponseJSONResponse: artifact.DockerLayersResponseJSONResponse{
|
||||
Data: *layersSummary,
|
||||
Status: artifact.StatusSUCCESS,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getLayersErrorResponse(err error) (artifact.GetDockerArtifactLayersResponseObject, error) {
|
||||
return artifact.GetDockerArtifactLayers500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/registry/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func (c *APIController) GetDockerArtifactManifest(
|
||||
ctx context.Context,
|
||||
r artifact.GetDockerArtifactManifestRequestObject,
|
||||
) (artifact.GetDockerArtifactManifestResponseObject, error) {
|
||||
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
|
||||
if err != nil {
|
||||
return artifact.GetDockerArtifactManifest400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.GetDockerArtifactManifest400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.GetDockerArtifactManifest403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
imageName := string(r.Artifact)
|
||||
dgst := string(r.Params.Digest)
|
||||
manifestDigest, err := types.NewDigest(digest.Digest(dgst))
|
||||
if err != nil {
|
||||
return getArtifactManifestErrorResponse(err)
|
||||
}
|
||||
|
||||
manifestPayload, err := c.ManifestStore.GetManifestPayload(
|
||||
ctx,
|
||||
regInfo.parentID,
|
||||
regInfo.RegistryIdentifier,
|
||||
imageName,
|
||||
manifestDigest,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return getArtifactManifestErrorResponse(err)
|
||||
}
|
||||
|
||||
payload := *manifestPayload
|
||||
return artifact.GetDockerArtifactManifest200JSONResponse{
|
||||
DockerArtifactManifestResponseJSONResponse: artifact.DockerArtifactManifestResponseJSONResponse{
|
||||
Data: artifact.DockerArtifactManifest{
|
||||
Manifest: string(payload),
|
||||
},
|
||||
Status: artifact.StatusSUCCESS,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getArtifactManifestErrorResponse(err error) (artifact.GetDockerArtifactManifestResponseObject, error) {
|
||||
return artifact.GetDockerArtifactManifest500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
@ -0,0 +1,165 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
ml "github.com/harness/gitness/registry/app/manifest/manifestlist"
|
||||
os "github.com/harness/gitness/registry/app/manifest/ocischema"
|
||||
s2 "github.com/harness/gitness/registry/app/manifest/schema2"
|
||||
"github.com/harness/gitness/registry/app/pkg/docker"
|
||||
"github.com/harness/gitness/registry/types"
|
||||
store2 "github.com/harness/gitness/store"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (c *APIController) GetDockerArtifactManifests(
|
||||
ctx context.Context,
|
||||
r artifact.GetDockerArtifactManifestsRequestObject,
|
||||
) (artifact.GetDockerArtifactManifestsResponseObject, error) {
|
||||
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
|
||||
if err != nil {
|
||||
return artifact.GetDockerArtifactManifests400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.GetDockerArtifactManifests400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.GetDockerArtifactManifests403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
image := string(r.Artifact)
|
||||
version := string(r.Version)
|
||||
registry, err := c.RegistryRepository.GetByParentIDAndName(ctx, regInfo.parentID, regInfo.RegistryIdentifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err := c.TagStore.FindTag(ctx, registry.ID, image, version)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, err
|
||||
}
|
||||
m, err := c.ManifestStore.Get(ctx, t.ManifestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
manifest, err := docker.DBManifestToManifest(m)
|
||||
manifestDetailsList := []artifact.DockerManifestDetails{}
|
||||
switch reqManifest := manifest.(type) {
|
||||
case *s2.DeserializedManifest:
|
||||
mConfig, err := getManifestConfig(ctx, reqManifest.Config().Digest, regInfo.rootIdentifier, c.StorageDriver)
|
||||
if err != nil {
|
||||
return artifactManifestsErrorRs(err), nil
|
||||
}
|
||||
manifestDetailsList = append(manifestDetailsList, getManifestDetails(m, mConfig))
|
||||
case *os.DeserializedManifest:
|
||||
mConfig, err := getManifestConfig(ctx, reqManifest.Config().Digest, regInfo.rootIdentifier, c.StorageDriver)
|
||||
if err != nil {
|
||||
return artifactManifestsErrorRs(err), nil
|
||||
}
|
||||
manifestDetailsList = append(manifestDetailsList, getManifestDetails(m, mConfig))
|
||||
case *ml.DeserializedManifestList:
|
||||
for _, manifestEntry := range reqManifest.Manifests {
|
||||
dgst, err := types.NewDigest(manifestEntry.Digest)
|
||||
if err != nil {
|
||||
return artifactManifestsErrorRs(err), nil
|
||||
}
|
||||
referencedManifest, err := c.ManifestStore.FindManifestByDigest(ctx, registry.ID, image, dgst)
|
||||
if err != nil {
|
||||
if errors.Is(err, store2.ErrResourceNotFound) {
|
||||
return artifactManifestsErrorRs(
|
||||
fmt.Errorf("manifest not found"),
|
||||
), nil
|
||||
}
|
||||
return artifactManifestsErrorRs(err), nil
|
||||
}
|
||||
mConfig, err := getManifestConfig(
|
||||
ctx, referencedManifest.Configuration.Digest,
|
||||
regInfo.rootIdentifier, c.StorageDriver,
|
||||
)
|
||||
if err != nil {
|
||||
return artifactManifestsErrorRs(err), nil
|
||||
}
|
||||
manifestDetailsList = append(manifestDetailsList, getManifestDetails(referencedManifest, mConfig))
|
||||
}
|
||||
default:
|
||||
log.Ctx(ctx).Error().Stack().Err(err).Msgf("Unknown manifest type: %T", manifest)
|
||||
}
|
||||
|
||||
return artifact.GetDockerArtifactManifests200JSONResponse{
|
||||
DockerManifestsResponseJSONResponse: artifact.DockerManifestsResponseJSONResponse{
|
||||
Data: artifact.DockerManifests{
|
||||
ImageName: t.ImageName,
|
||||
Version: t.Name,
|
||||
Manifests: &manifestDetailsList,
|
||||
},
|
||||
Status: artifact.StatusSUCCESS,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func artifactManifestsErrorRs(err error) artifact.GetDockerArtifactManifestsResponseObject {
|
||||
return artifact.GetDockerArtifactManifests500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func getManifestDetails(m *types.Manifest, mConfig *manifestConfig) artifact.DockerManifestDetails {
|
||||
createdAt := GetTimeInMs(m.CreatedAt)
|
||||
size := GetSize(m.TotalSize)
|
||||
|
||||
manifestDetails := artifact.DockerManifestDetails{
|
||||
Digest: m.Digest.String(),
|
||||
CreatedAt: &createdAt,
|
||||
Size: &size,
|
||||
}
|
||||
if mConfig != nil {
|
||||
manifestDetails.OsArch = fmt.Sprintf("%s/%s", mConfig.Os, mConfig.Arch)
|
||||
}
|
||||
return manifestDetails
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
store2 "github.com/harness/gitness/store"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
func (c *APIController) GetHelmArtifactDetails(
|
||||
ctx context.Context,
|
||||
r artifact.GetHelmArtifactDetailsRequestObject,
|
||||
) (artifact.GetHelmArtifactDetailsResponseObject, error) {
|
||||
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
|
||||
if err != nil {
|
||||
return artifact.GetHelmArtifactDetails400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.GetHelmArtifactDetails400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.GetHelmArtifactDetails403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
image := string(r.Artifact)
|
||||
version := string(r.Version)
|
||||
|
||||
registry, err := c.RegistryRepository.GetByParentIDAndName(ctx, regInfo.parentID, regInfo.RegistryIdentifier)
|
||||
|
||||
if err != nil {
|
||||
return artifact.GetHelmArtifactDetails500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
tag, err := c.TagStore.GetTagDetail(ctx, registry.ID, image, version)
|
||||
if err != nil {
|
||||
return getHelmArtifactDetailsErrResponse(err)
|
||||
}
|
||||
m, err := c.ManifestStore.FindManifestByTagName(ctx, registry.ID, image, version)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, store2.ErrResourceNotFound) {
|
||||
return getHelmArtifactDetailsErrResponse(fmt.Errorf("manifest not found"))
|
||||
}
|
||||
return getHelmArtifactDetailsErrResponse(err)
|
||||
}
|
||||
|
||||
latestTag, _ := c.TagStore.GetLatestTag(ctx, registry.ID, image)
|
||||
|
||||
return artifact.GetHelmArtifactDetails200JSONResponse{
|
||||
HelmArtifactDetailResponseJSONResponse: *GetHelmArtifactDetails(
|
||||
registry, tag, m,
|
||||
latestTag.ID == tag.ID, regInfo.rootIdentifier, c.URLProvider.RegistryURL(),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getHelmArtifactDetailsErrResponse(err error) (artifact.GetHelmArtifactDetailsResponseObject, error) {
|
||||
return artifact.GetHelmArtifactDetails500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
store2 "github.com/harness/gitness/store"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
func (c *APIController) GetHelmArtifactManifest(
|
||||
ctx context.Context,
|
||||
r artifact.GetHelmArtifactManifestRequestObject,
|
||||
) (artifact.GetHelmArtifactManifestResponseObject, error) {
|
||||
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
|
||||
if err != nil {
|
||||
return c.get400Error(err)
|
||||
}
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.GetHelmArtifactManifest400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.GetHelmArtifactManifest403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
imageName := string(r.Artifact)
|
||||
version := string(r.Version)
|
||||
|
||||
manifestPayload, err := c.ManifestStore.FindManifestPayloadByTagName(
|
||||
ctx,
|
||||
regInfo.parentID,
|
||||
regInfo.RegistryIdentifier,
|
||||
imageName,
|
||||
version,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, store2.ErrResourceNotFound) {
|
||||
return artifact.GetHelmArtifactManifest400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
return artifact.GetHelmArtifactManifest500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
payload := *manifestPayload
|
||||
return artifact.GetHelmArtifactManifest200JSONResponse{
|
||||
HelmArtifactManifestResponseJSONResponse: artifact.HelmArtifactManifestResponseJSONResponse{
|
||||
Data: artifact.HelmArtifactManifest{
|
||||
Manifest: string(payload),
|
||||
},
|
||||
Status: artifact.StatusSUCCESS,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *APIController) get400Error(err error) (artifact.GetHelmArtifactManifestResponseObject, error) {
|
||||
return artifact.GetHelmArtifactManifest400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
83
registry/app/api/controller/metadata/get_artifacts_labels.go
Normal file
83
registry/app/api/controller/metadata/get_artifacts_labels.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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
func (c *APIController) ListArtifactLabels(
|
||||
ctx context.Context,
|
||||
r artifact.ListArtifactLabelsRequestObject,
|
||||
) (artifact.ListArtifactLabelsResponseObject, error) {
|
||||
regInfo, _ := c.GetRegistryRequestInfo(
|
||||
ctx, nil, r.Params.Page, r.Params.Size,
|
||||
r.Params.SearchTerm, ArtifactResource, "", string(r.RegistryRef),
|
||||
nil, nil, nil,
|
||||
)
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.ListArtifactLabels400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.ListArtifactLabels403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
labels, err := c.ArtifactStore.GetLabelsByParentIDAndRepo(
|
||||
ctx, regInfo.parentID,
|
||||
regInfo.RegistryIdentifier, regInfo.limit, regInfo.offset, regInfo.searchTerm,
|
||||
)
|
||||
count, _ := c.ArtifactStore.CountLabelsByParentIDAndRepo(
|
||||
ctx, regInfo.parentID,
|
||||
regInfo.RegistryIdentifier, regInfo.searchTerm,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return artifact.ListArtifactLabels500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
return artifact.ListArtifactLabels200JSONResponse{
|
||||
ListArtifactLabelResponseJSONResponse: *GetAllArtifactLabelsResponse(
|
||||
&labels, count,
|
||||
regInfo.pageNumber, regInfo.limit,
|
||||
),
|
||||
}, nil
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
func (c *APIController) GetArtifactSummary(
|
||||
ctx context.Context,
|
||||
r artifact.GetArtifactSummaryRequestObject,
|
||||
) (artifact.GetArtifactSummaryResponseObject, error) {
|
||||
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
|
||||
if err != nil {
|
||||
return artifact.GetArtifactSummary400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.GetArtifactSummary400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.GetArtifactSummary403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
image := string(r.Artifact)
|
||||
|
||||
tag, err := c.TagStore.GetLatestTagMetadata(ctx, regInfo.parentID, regInfo.RegistryIdentifier, image)
|
||||
|
||||
if err != nil {
|
||||
return artifact.GetArtifactSummary500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
return artifact.GetArtifactSummary200JSONResponse{
|
||||
ArtifactSummaryResponseJSONResponse: *GetArtifactSummary(*tag),
|
||||
}, nil
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
func (c *APIController) GetArtifactVersionSummary(
|
||||
ctx context.Context,
|
||||
r artifact.GetArtifactVersionSummaryRequestObject,
|
||||
) (artifact.GetArtifactVersionSummaryResponseObject, error) {
|
||||
regInfo, _ := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.GetArtifactVersionSummary400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.GetArtifactVersionSummary403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
image := string(r.Artifact)
|
||||
version := string(r.Version)
|
||||
|
||||
tag, err := c.TagStore.GetTagMetadata(ctx, regInfo.parentID, regInfo.RegistryIdentifier, image, version)
|
||||
|
||||
if err != nil {
|
||||
return artifact.GetArtifactVersionSummary500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
latestTag, _ := c.TagStore.GetLatestTagName(ctx, regInfo.parentID, regInfo.RegistryIdentifier, image)
|
||||
|
||||
isLatestTag := latestTag == version
|
||||
|
||||
return artifact.GetArtifactVersionSummary200JSONResponse{
|
||||
ArtifactVersionSummaryResponseJSONResponse: *GetArtifactVersionSummary(tag, image, isLatestTag),
|
||||
}, nil
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
func (c *APIController) GetAllArtifactVersions(
|
||||
ctx context.Context,
|
||||
r artifact.GetAllArtifactVersionsRequestObject,
|
||||
) (artifact.GetAllArtifactVersionsResponseObject, error) {
|
||||
regInfo, _ := c.GetRegistryRequestInfo(
|
||||
ctx, nil, r.Params.Page, r.Params.Size,
|
||||
r.Params.SearchTerm, ArtifactVersionResource, "", string(r.RegistryRef),
|
||||
nil, r.Params.SortOrder, r.Params.SortField,
|
||||
)
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.GetAllArtifactVersions400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.GetAllArtifactVersions403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
image := string(r.Artifact)
|
||||
|
||||
tags, err := c.TagStore.GetAllTagsByRepoAndImage(
|
||||
ctx, regInfo.parentID, regInfo.RegistryIdentifier,
|
||||
image, regInfo.sortByField, regInfo.sortByOrder, regInfo.limit, regInfo.offset, regInfo.searchTerm,
|
||||
)
|
||||
|
||||
latestTag, _ := c.TagStore.GetLatestTagName(ctx, regInfo.parentID, regInfo.RegistryIdentifier, image)
|
||||
|
||||
count, _ := c.TagStore.CountAllTagsByRepoAndImage(
|
||||
ctx, regInfo.parentID, regInfo.RegistryIdentifier,
|
||||
image, regInfo.searchTerm,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return artifact.GetAllArtifactVersions500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return artifact.GetAllArtifactVersions200JSONResponse{
|
||||
ListArtifactVersionResponseJSONResponse: *GetAllArtifactVersionResponse(
|
||||
ctx, tags, latestTag, image, count,
|
||||
regInfo, regInfo.pageNumber, regInfo.limit, regInfo.rootIdentifier, c.URLProvider.RegistryURL(),
|
||||
),
|
||||
}, nil
|
||||
}
|
317
registry/app/api/controller/metadata/get_client_setup_details.go
Normal file
317
registry/app/api/controller/metadata/get_client_setup_details.go
Normal file
@ -0,0 +1,317 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/registry/app/common"
|
||||
"github.com/harness/gitness/registry/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
func (c *APIController) GetClientSetupDetails(
|
||||
ctx context.Context,
|
||||
r artifact.GetClientSetupDetailsRequestObject,
|
||||
) (artifact.GetClientSetupDetailsResponseObject, error) {
|
||||
regRefParam := r.RegistryRef
|
||||
imageParam := r.Params.Artifact
|
||||
tagParam := r.Params.Version
|
||||
|
||||
regInfo, _ := c.GetRegistryRequestBaseInfo(ctx, "", string(regRefParam))
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.GetClientSetupDetails400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.GetClientSetupDetails403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
reg, err := c.RegistryRepository.GetByParentIDAndName(ctx, regInfo.parentID, regInfo.RegistryIdentifier)
|
||||
if err != nil {
|
||||
return artifact.GetClientSetupDetails404JSONResponse{
|
||||
NotFoundJSONResponse: artifact.NotFoundJSONResponse(
|
||||
*GetErrorResponse(http.StatusNotFound, "registry doesn't exist with this ref"),
|
||||
),
|
||||
}, err
|
||||
}
|
||||
|
||||
if imageParam != nil {
|
||||
_, err := c.ArtifactStore.GetByName(ctx, reg.ID, string(*imageParam))
|
||||
if err != nil {
|
||||
return artifact.GetClientSetupDetails404JSONResponse{
|
||||
NotFoundJSONResponse: artifact.NotFoundJSONResponse(
|
||||
*GetErrorResponse(http.StatusNotFound, "image doesn't exist"),
|
||||
),
|
||||
}, err
|
||||
}
|
||||
if tagParam != nil {
|
||||
_, err := c.TagStore.FindTag(ctx, reg.ID, string(*imageParam), string(*tagParam))
|
||||
if err != nil {
|
||||
return artifact.GetClientSetupDetails404JSONResponse{
|
||||
NotFoundJSONResponse: artifact.NotFoundJSONResponse(
|
||||
*GetErrorResponse(http.StatusNotFound, "tag doesn't exist"),
|
||||
),
|
||||
}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
packageType := string(reg.PackageType)
|
||||
|
||||
return artifact.GetClientSetupDetails200JSONResponse{
|
||||
ClientSetupDetailsResponseJSONResponse: *GetClientSetupDetails(
|
||||
ctx, packageType, regInfo, reg,
|
||||
string(r.RegistryRef), imageParam, tagParam, c.URLProvider.RegistryURL(),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetClientSetupDetails(
|
||||
ctx context.Context,
|
||||
packageType string,
|
||||
_ *RegistryRequestBaseInfo,
|
||||
_ *types.Registry,
|
||||
regRef string,
|
||||
image *artifact.ArtifactParam,
|
||||
tag *artifact.VersionParam,
|
||||
registryURL string,
|
||||
) *artifact.ClientSetupDetailsResponseJSONResponse {
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
username := session.Principal.Email
|
||||
hostname := common.GenerateSetupClientHostname(registryURL)
|
||||
regRef = strings.ToLower(regRef)
|
||||
|
||||
// Fixme: Use ENUMS
|
||||
if packageType == "HELM" {
|
||||
header1 := "Login to Helm"
|
||||
section1step1Header := "Run this Helm command in your terminal to authenticate the client."
|
||||
section1step1Commands := []string{"helm registry login <HOSTNAME>"}
|
||||
section1step1Type := artifact.ClientSetupStepTypeStatic
|
||||
section1step2Header := "For the Password field above, generate an identity token"
|
||||
section1step2Type := artifact.ClientSetupStepTypeGenerateToken
|
||||
section1 := []artifact.ClientSetupStep{
|
||||
{
|
||||
Header: §ion1step1Header,
|
||||
Commands: §ion1step1Commands,
|
||||
Type: §ion1step1Type,
|
||||
},
|
||||
{
|
||||
Header: §ion1step2Header,
|
||||
Type: §ion1step2Type,
|
||||
},
|
||||
}
|
||||
|
||||
header2 := "Push a version"
|
||||
section2step1Header := "Run this Helm push command in your terminal to push a chart in OCI form." +
|
||||
" Note: Make sure you add oci:// prefix to the repository URL."
|
||||
section2step1Commands := []string{"helm push <CHART_TGZ_FILE> oci://<HOSTNAME>/<REPOSITORY_REFERENCE>"}
|
||||
section2step1Type := artifact.ClientSetupStepTypeStatic
|
||||
section2 := []artifact.ClientSetupStep{
|
||||
{
|
||||
Header: §ion2step1Header,
|
||||
Commands: §ion2step1Commands,
|
||||
Type: §ion2step1Type,
|
||||
},
|
||||
}
|
||||
|
||||
header3 := "Pull a version"
|
||||
section3step1Header := "Run this Helm command in your terminal to pull a specific chart version."
|
||||
section3step1Commands := []string{
|
||||
"helm pull oci://<HOSTNAME>/<REPOSITORY_REFERENCE>/<IMAGE_NAME> --version <TAG>",
|
||||
}
|
||||
section3step1Type := artifact.ClientSetupStepTypeStatic
|
||||
section3 := []artifact.ClientSetupStep{
|
||||
{
|
||||
Header: §ion3step1Header,
|
||||
Commands: §ion3step1Commands,
|
||||
Type: §ion3step1Type,
|
||||
},
|
||||
}
|
||||
clientSetupDetails := artifact.ClientSetupDetails{
|
||||
MainHeader: "Helm Client Setup",
|
||||
SecHeader: "Follow these instructions to install/use Helm artifacts or compatible packages.",
|
||||
Sections: []artifact.ClientSetupSection{
|
||||
{
|
||||
Header: &header1,
|
||||
Steps: §ion1,
|
||||
},
|
||||
{
|
||||
Header: &header2,
|
||||
Steps: §ion2,
|
||||
},
|
||||
{
|
||||
Header: &header3,
|
||||
Steps: §ion3,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
replacePlaceholders(clientSetupDetails, username, hostname, regRef, image, tag)
|
||||
|
||||
return &artifact.ClientSetupDetailsResponseJSONResponse{
|
||||
Data: clientSetupDetails,
|
||||
Status: artifact.StatusSUCCESS,
|
||||
}
|
||||
}
|
||||
header1 := "Login to Docker"
|
||||
section1step1Header := "Run this Docker command in your terminal to authenticate the client."
|
||||
section1step1Commands := []string{"docker login <HOSTNAME>", "Username: <USERNAME>", "Password: *see step 2*"}
|
||||
section1step1Type := artifact.ClientSetupStepTypeStatic
|
||||
section1step2Header := "For the Password field above, generate an identity token"
|
||||
section1step2Type := artifact.ClientSetupStepTypeGenerateToken
|
||||
section1 := []artifact.ClientSetupStep{
|
||||
{
|
||||
Header: §ion1step1Header,
|
||||
Commands: §ion1step1Commands,
|
||||
Type: §ion1step1Type,
|
||||
},
|
||||
{
|
||||
Header: §ion1step2Header,
|
||||
Type: §ion1step2Type,
|
||||
},
|
||||
}
|
||||
header2 := "Pull an image"
|
||||
section2step1Header := "Run this Docker command in your terminal to pull image."
|
||||
section2step1Commands := []string{"docker pull <HOSTNAME>/<REPOSITORY_REFERENCE>/<IMAGE_NAME>:<TAG>"}
|
||||
section2step1Type := artifact.ClientSetupStepTypeStatic
|
||||
section2 := []artifact.ClientSetupStep{
|
||||
{
|
||||
Header: §ion2step1Header,
|
||||
Commands: §ion2step1Commands,
|
||||
Type: §ion2step1Type,
|
||||
},
|
||||
}
|
||||
header3 := "Retag and Push the image"
|
||||
section3step1Header := "Run this Docker command in your terminal to tag the image."
|
||||
section3step1Commands := []string{
|
||||
"docker tag <HOSTNAME>/<REPOSITORY_REFERENCE>/<IMAGE_NAME>" +
|
||||
" <HOSTNAME>/<REPOSITORY_REFERENCE>/<IMAGE_NAME>:<TAG>",
|
||||
}
|
||||
section3step1Type := artifact.ClientSetupStepTypeStatic
|
||||
section3step2Header := "Run this Docker command in your terminal to push the image."
|
||||
section3step2Commands := []string{"docker push <HOSTNAME>/<REPOSITORY_REFERENCE>/<IMAGE_NAME>:<TAG>"}
|
||||
section3step2Type := artifact.ClientSetupStepTypeStatic
|
||||
section3 := []artifact.ClientSetupStep{
|
||||
{
|
||||
Header: §ion3step1Header,
|
||||
Commands: §ion3step1Commands,
|
||||
Type: §ion3step1Type,
|
||||
},
|
||||
{
|
||||
Header: §ion3step2Header,
|
||||
Commands: §ion3step2Commands,
|
||||
Type: §ion3step2Type,
|
||||
},
|
||||
}
|
||||
clientSetupDetails := artifact.ClientSetupDetails{
|
||||
MainHeader: "Docker Client Setup",
|
||||
SecHeader: "Follow these instructions to install/use Docker artifacts or compatible packages.",
|
||||
Sections: []artifact.ClientSetupSection{
|
||||
{
|
||||
Header: &header1,
|
||||
Steps: §ion1,
|
||||
},
|
||||
{
|
||||
Header: &header2,
|
||||
Steps: §ion2,
|
||||
},
|
||||
{
|
||||
Header: &header3,
|
||||
Steps: §ion3,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
replacePlaceholders(clientSetupDetails, username, hostname, regRef, image, tag)
|
||||
|
||||
return &artifact.ClientSetupDetailsResponseJSONResponse{
|
||||
Data: clientSetupDetails,
|
||||
Status: artifact.StatusSUCCESS,
|
||||
}
|
||||
}
|
||||
|
||||
func replacePlaceholders(
|
||||
clientSetupDetails artifact.ClientSetupDetails, username string, hostname string,
|
||||
regRef string, image *artifact.ArtifactParam, tag *artifact.VersionParam,
|
||||
) {
|
||||
for _, s := range clientSetupDetails.Sections {
|
||||
if s.Steps == nil {
|
||||
continue
|
||||
}
|
||||
for _, st := range *s.Steps {
|
||||
if st.Commands == nil {
|
||||
continue
|
||||
}
|
||||
for i := range *st.Commands {
|
||||
replaceText(username, st, i, hostname, regRef, image, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func replaceText(
|
||||
username string,
|
||||
st artifact.ClientSetupStep,
|
||||
i int,
|
||||
hostname string,
|
||||
regRef string,
|
||||
image *artifact.ArtifactParam,
|
||||
tag *artifact.VersionParam,
|
||||
) {
|
||||
if username != "" {
|
||||
(*st.Commands)[i] = strings.ReplaceAll((*st.Commands)[i], "<USERNAME>", username)
|
||||
}
|
||||
if hostname != "" {
|
||||
(*st.Commands)[i] = strings.ReplaceAll((*st.Commands)[i], "<HOSTNAME>", hostname)
|
||||
}
|
||||
if regRef != "" {
|
||||
(*st.Commands)[i] = strings.ReplaceAll(
|
||||
(*st.Commands)[i],
|
||||
"<REPOSITORY_REFERENCE>", regRef,
|
||||
)
|
||||
}
|
||||
if image != nil {
|
||||
(*st.Commands)[i] = strings.ReplaceAll(
|
||||
(*st.Commands)[i],
|
||||
"<IMAGE_NAME>", string(*image),
|
||||
)
|
||||
}
|
||||
if tag != nil {
|
||||
(*st.Commands)[i] = strings.ReplaceAll((*st.Commands)[i], "<TAG>", string(*tag))
|
||||
}
|
||||
}
|
179
registry/app/api/controller/metadata/get_registries.go
Normal file
179
registry/app/api/controller/metadata/get_registries.go
Normal file
@ -0,0 +1,179 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/registry/app/pkg/commons"
|
||||
"github.com/harness/gitness/registry/app/store"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/gotidy/ptr"
|
||||
)
|
||||
|
||||
func (c *APIController) GetAllRegistries(
|
||||
ctx context.Context,
|
||||
r artifact.GetAllRegistriesRequestObject,
|
||||
) (artifact.GetAllRegistriesResponseObject, error) {
|
||||
regInfo, _ := c.GetRegistryRequestInfo(
|
||||
ctx, r.Params.PackageType, r.Params.Page, r.Params.Size,
|
||||
r.Params.SearchTerm, RepositoryResource, string(r.SpaceRef), "", nil,
|
||||
r.Params.SortOrder, r.Params.SortField,
|
||||
)
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.GetAllRegistries400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
if err = apiauth.CheckSpaceScope(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
space,
|
||||
enum.ResourceTypeRegistry,
|
||||
enum.PermissionRegistryView,
|
||||
); err != nil {
|
||||
return artifact.GetAllRegistries403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var repos *[]store.RegistryMetadata
|
||||
repoType := ""
|
||||
if r.Params.Type != nil {
|
||||
repoType = string(*r.Params.Type)
|
||||
}
|
||||
e := ValidatePackageTypes(regInfo.packageTypes)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
e = ValidateRepoType(repoType)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
var count int64
|
||||
repos, err = c.RegistryRepository.GetAll(
|
||||
ctx,
|
||||
regInfo.parentID,
|
||||
regInfo.packageTypes,
|
||||
regInfo.sortByField,
|
||||
regInfo.sortByOrder,
|
||||
regInfo.limit,
|
||||
regInfo.offset,
|
||||
regInfo.searchTerm,
|
||||
repoType,
|
||||
)
|
||||
count, _ = c.RegistryRepository.CountAll(
|
||||
ctx,
|
||||
regInfo.parentID,
|
||||
regInfo.packageTypes,
|
||||
regInfo.searchTerm,
|
||||
repoType,
|
||||
)
|
||||
if err != nil {
|
||||
return artifact.GetAllRegistries500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
return artifact.GetAllRegistries200JSONResponse{
|
||||
ListRegistryResponseJSONResponse: *GetAllRegistryResponse(
|
||||
repos, count, regInfo.pageNumber,
|
||||
regInfo.limit, regInfo.rootIdentifier, c.URLProvider.RegistryURL(),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetAllRegistryResponse(
|
||||
repos *[]store.RegistryMetadata,
|
||||
count int64,
|
||||
pageNumber int64,
|
||||
pageSize int,
|
||||
rootIdentifier string,
|
||||
registryURL string,
|
||||
) *artifact.ListRegistryResponseJSONResponse {
|
||||
repoMetadataList := GetRegistryMetadata(repos, rootIdentifier, registryURL)
|
||||
pageCount := GetPageCount(count, pageSize)
|
||||
listRepository := &artifact.ListRegistry{
|
||||
ItemCount: &count,
|
||||
PageCount: &pageCount,
|
||||
PageIndex: &pageNumber,
|
||||
PageSize: &pageSize,
|
||||
Registries: repoMetadataList,
|
||||
}
|
||||
response := &artifact.ListRegistryResponseJSONResponse{
|
||||
Data: *listRepository,
|
||||
Status: artifact.StatusSUCCESS,
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func GetRegistryMetadata(
|
||||
registryMetadatas *[]store.RegistryMetadata,
|
||||
rootIdentifier string,
|
||||
registryURL string,
|
||||
) []artifact.RegistryMetadata {
|
||||
repoMetadataList := []artifact.RegistryMetadata{}
|
||||
for _, reg := range *registryMetadatas {
|
||||
modifiedAt := GetTimeInMs(reg.LastModified)
|
||||
var labels *[]string
|
||||
if !commons.IsEmpty(reg.Labels) {
|
||||
temp := []string(reg.Labels)
|
||||
labels = &temp
|
||||
}
|
||||
var description string
|
||||
if !commons.IsEmpty(reg.Description) {
|
||||
description = reg.Description
|
||||
}
|
||||
var artifactCount *int64
|
||||
if reg.ArtifactCount != 0 {
|
||||
artifactCount = ptr.Int64(reg.ArtifactCount)
|
||||
}
|
||||
var downloadCount *int64
|
||||
if reg.DownloadCount != 0 {
|
||||
downloadCount = ptr.Int64(reg.DownloadCount)
|
||||
}
|
||||
// fix: refactor it
|
||||
size := GetSize(reg.Size)
|
||||
repoMetadata := artifact.RegistryMetadata{
|
||||
Identifier: reg.RegIdentifier,
|
||||
Description: &description,
|
||||
PackageType: reg.PackageType,
|
||||
Type: reg.Type,
|
||||
LastModified: &modifiedAt,
|
||||
Url: GetRepoURL(rootIdentifier, reg.RegIdentifier, registryURL),
|
||||
ArtifactsCount: artifactCount,
|
||||
DownloadsCount: downloadCount,
|
||||
RegistrySize: &size,
|
||||
Labels: labels,
|
||||
}
|
||||
repoMetadataList = append(repoMetadataList, repoMetadata)
|
||||
}
|
||||
return repoMetadataList
|
||||
}
|
109
registry/app/api/controller/metadata/get_registry.go
Normal file
109
registry/app/api/controller/metadata/get_registry.go
Normal file
@ -0,0 +1,109 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
func (c *APIController) GetRegistry(
|
||||
ctx context.Context,
|
||||
r artifact.GetRegistryRequestObject,
|
||||
) (artifact.GetRegistryResponseObject, error) {
|
||||
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
|
||||
if err != nil {
|
||||
return artifact.GetRegistry400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.GetRegistry400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryView)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.GetRegistry403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
repoEntity, _ := c.RegistryRepository.GetByParentIDAndName(ctx, regInfo.parentID, regInfo.RegistryIdentifier)
|
||||
if string(repoEntity.Type) == string(artifact.RegistryTypeVIRTUAL) {
|
||||
cleanupPolicies, err := c.CleanupPolicyStore.GetByRegistryID(ctx, repoEntity.ID)
|
||||
if err != nil {
|
||||
return throwGetRegistry500Error(err), nil
|
||||
}
|
||||
if len(repoEntity.Name) == 0 {
|
||||
return artifact.GetRegistry404JSONResponse{
|
||||
NotFoundJSONResponse: artifact.NotFoundJSONResponse(
|
||||
*GetErrorResponse(http.StatusNotFound, "registry doesn't exist with this key"),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
return artifact.GetRegistry200JSONResponse{
|
||||
RegistryResponseJSONResponse: *CreateVirtualRepositoryResponse(
|
||||
repoEntity, c.getUpstreamProxyKeys(
|
||||
ctx,
|
||||
repoEntity.UpstreamProxies,
|
||||
), cleanupPolicies, regInfo.rootIdentifier, c.URLProvider.RegistryURL(),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
upstreamproxyEntity, err := c.UpstreamProxyStore.GetByRegistryIdentifier(
|
||||
ctx,
|
||||
regInfo.parentID, regInfo.RegistryIdentifier,
|
||||
)
|
||||
if len(upstreamproxyEntity.RepoKey) == 0 {
|
||||
return artifact.GetRegistry404JSONResponse{
|
||||
NotFoundJSONResponse: artifact.NotFoundJSONResponse(
|
||||
*GetErrorResponse(http.StatusNotFound, "registry doesn't exist with this key"),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return throwGetRegistry500Error(err), nil
|
||||
}
|
||||
return artifact.GetRegistry200JSONResponse{
|
||||
RegistryResponseJSONResponse: *CreateUpstreamProxyResponseJSONResponse(upstreamproxyEntity),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func throwGetRegistry500Error(err error) artifact.GetRegistry500JSONResponse {
|
||||
return artifact.GetRegistry500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}
|
||||
}
|
138
registry/app/api/controller/metadata/update_artifact.go
Normal file
138
registry/app/api/controller/metadata/update_artifact.go
Normal file
@ -0,0 +1,138 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/registry/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
)
|
||||
|
||||
func (c *APIController) UpdateArtifactLabels(
|
||||
ctx context.Context,
|
||||
r artifact.UpdateArtifactLabelsRequestObject,
|
||||
) (artifact.UpdateArtifactLabelsResponseObject, error) {
|
||||
regInfo, _ := c.GetRegistryRequestInfo(
|
||||
ctx, nil, nil, nil, nil,
|
||||
ArtifactVersionResource, "", string(r.RegistryRef), nil, nil, nil,
|
||||
)
|
||||
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.UpdateArtifactLabels400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, enum.PermissionRegistryEdit)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.UpdateArtifactLabels403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
a := string(r.Artifact)
|
||||
|
||||
artifactEntity, err := c.ArtifactStore.GetByRepoAndName(ctx, regInfo.parentID, regInfo.RegistryIdentifier, a)
|
||||
|
||||
if len(artifactEntity.Name) == 0 {
|
||||
return artifact.UpdateArtifactLabels404JSONResponse{
|
||||
NotFoundJSONResponse: artifact.NotFoundJSONResponse(
|
||||
*GetErrorResponse(http.StatusNotFound, "artifact doesn't exist with this name"),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return throwModifyArtifact400Error(err), nil
|
||||
}
|
||||
existingArtifact, err := AttachLabels(artifact.ArtifactLabelRequest(*r.Body), artifactEntity)
|
||||
if err != nil {
|
||||
return throwModifyArtifact400Error(err), nil
|
||||
}
|
||||
|
||||
err = c.ArtifactStore.Update(ctx, existingArtifact)
|
||||
|
||||
if err != nil {
|
||||
return throwModifyArtifact400Error(err), nil
|
||||
}
|
||||
|
||||
tag, err := c.TagStore.GetLatestTagMetadata(ctx, regInfo.parentID, regInfo.RegistryIdentifier, a)
|
||||
|
||||
if err != nil {
|
||||
return artifact.UpdateArtifactLabels500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
return artifact.UpdateArtifactLabels200JSONResponse{
|
||||
ArtifactLabelResponseJSONResponse: *getArtifactSummary(*tag),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func throwModifyArtifact400Error(err error) artifact.UpdateArtifactLabels400JSONResponse {
|
||||
return artifact.UpdateArtifactLabels400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func AttachLabels(
|
||||
dto artifact.ArtifactLabelRequest,
|
||||
existingArtifact *types.Artifact,
|
||||
) (*types.Artifact, error) {
|
||||
return &types.Artifact{
|
||||
ID: existingArtifact.ID,
|
||||
RegistryID: existingArtifact.RegistryID,
|
||||
Name: existingArtifact.Name,
|
||||
Labels: dto.Labels,
|
||||
CreatedAt: existingArtifact.CreatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getArtifactSummary(t types.ArtifactMetadata) *artifact.ArtifactLabelResponseJSONResponse {
|
||||
downloads := int64(0)
|
||||
createdAt := GetTimeInMs(t.CreatedAt)
|
||||
modifiedAt := GetTimeInMs(t.ModifiedAt)
|
||||
artifactVersionSummary := &artifact.ArtifactSummary{
|
||||
CreatedAt: &createdAt,
|
||||
ModifiedAt: &modifiedAt,
|
||||
DownloadsCount: &downloads,
|
||||
ImageName: t.Name,
|
||||
Labels: &t.Labels,
|
||||
PackageType: t.PackageType,
|
||||
}
|
||||
response := &artifact.ArtifactLabelResponseJSONResponse{
|
||||
Data: *artifactVersionSummary,
|
||||
Status: artifact.StatusSUCCESS,
|
||||
}
|
||||
return response
|
||||
}
|
398
registry/app/api/controller/metadata/update_registry.go
Normal file
398
registry/app/api/controller/metadata/update_registry.go
Normal file
@ -0,0 +1,398 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/app/auth"
|
||||
"github.com/harness/gitness/audit"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/registry/types"
|
||||
types2 "github.com/harness/gitness/types"
|
||||
gitnessenum "github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (c *APIController) ModifyRegistry(
|
||||
ctx context.Context,
|
||||
r artifact.ModifyRegistryRequestObject,
|
||||
) (artifact.ModifyRegistryResponseObject, error) {
|
||||
regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef))
|
||||
if err != nil {
|
||||
return artifact.ModifyRegistry400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, err
|
||||
}
|
||||
space, err := c.spaceStore.FindByRef(ctx, regInfo.parentRef)
|
||||
if err != nil {
|
||||
return artifact.ModifyRegistry400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusBadRequest, err.Error()),
|
||||
),
|
||||
}, err
|
||||
}
|
||||
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
permissionChecks := getPermissionChecks(space, regInfo.RegistryIdentifier, gitnessenum.PermissionRegistryEdit)
|
||||
if err = apiauth.CheckRegistry(
|
||||
ctx,
|
||||
c.Authorizer,
|
||||
session,
|
||||
permissionChecks...,
|
||||
); err != nil {
|
||||
return artifact.ModifyRegistry403JSONResponse{
|
||||
UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse(
|
||||
*GetErrorResponse(http.StatusForbidden, err.Error()),
|
||||
),
|
||||
}, err
|
||||
}
|
||||
|
||||
repoEntity, err := c.RegistryRepository.GetByParentIDAndName(ctx, regInfo.parentID, regInfo.RegistryIdentifier)
|
||||
if err != nil {
|
||||
return throwModifyRegistry500Error(err), err
|
||||
}
|
||||
|
||||
if string(repoEntity.Type) == string(artifact.RegistryTypeVIRTUAL) {
|
||||
return c.updateVirtualRegistry(ctx, r, repoEntity, err, regInfo, session)
|
||||
}
|
||||
upstreamproxyEntity, err := c.UpstreamProxyStore.GetByRegistryIdentifier(
|
||||
ctx, regInfo.parentID,
|
||||
regInfo.RegistryIdentifier,
|
||||
)
|
||||
if len(upstreamproxyEntity.RepoKey) == 0 {
|
||||
return artifact.ModifyRegistry404JSONResponse{
|
||||
NotFoundJSONResponse: artifact.NotFoundJSONResponse(
|
||||
*GetErrorResponse(http.StatusNotFound, "registry doesn't exist with this key"),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return throwModifyRegistry500Error(err), err
|
||||
}
|
||||
registry, upstreamproxy, err := UpdateUpstreamProxyEntity(
|
||||
artifact.RegistryRequest(*r.Body),
|
||||
regInfo.parentID, regInfo.rootIdentifierID, upstreamproxyEntity,
|
||||
)
|
||||
registry.ID = repoEntity.ID
|
||||
upstreamproxy.ID = upstreamproxyEntity.ID
|
||||
upstreamproxy.RegistryID = repoEntity.ID
|
||||
if err != nil {
|
||||
return throwModifyRegistry500Error(err), err
|
||||
}
|
||||
err = c.tx.WithTx(
|
||||
ctx, func(ctx context.Context) error {
|
||||
err = c.updateRegistryWithAudit(ctx, repoEntity, registry, session.Principal, regInfo.parentRef)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update registry: %w", err)
|
||||
}
|
||||
|
||||
err = c.updateUpstreamProxyWithAudit(
|
||||
ctx, upstreamproxy, session.Principal, regInfo.parentRef, registry.Name,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update upstream proxy: %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return throwModifyRegistry500Error(err), err
|
||||
}
|
||||
modifiedRepoEntity, err := c.UpstreamProxyStore.Get(ctx, upstreamproxyEntity.RegistryID)
|
||||
if err != nil {
|
||||
return throwModifyRegistry500Error(err), err
|
||||
}
|
||||
return artifact.ModifyRegistry200JSONResponse{
|
||||
RegistryResponseJSONResponse: *CreateUpstreamProxyResponseJSONResponse(modifiedRepoEntity),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *APIController) updateVirtualRegistry(
|
||||
ctx context.Context, r artifact.ModifyRegistryRequestObject, repoEntity *types.Registry, err error,
|
||||
regInfo *RegistryRequestBaseInfo, session *auth.Session,
|
||||
) (artifact.ModifyRegistryResponseObject, error) {
|
||||
if len(repoEntity.Name) == 0 {
|
||||
return artifact.ModifyRegistry404JSONResponse{
|
||||
NotFoundJSONResponse: artifact.NotFoundJSONResponse(
|
||||
*GetErrorResponse(http.StatusNotFound, "registry doesn't exist with this key"),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return throwModifyRegistry500Error(err), err
|
||||
}
|
||||
registry, err := UpdateRepoEntity(
|
||||
artifact.RegistryRequest(*r.Body),
|
||||
repoEntity.ParentID,
|
||||
repoEntity.RootParentID,
|
||||
repoEntity,
|
||||
)
|
||||
if err != nil {
|
||||
return artifact.ModifyRegistry400JSONResponse{
|
||||
BadRequestJSONResponse: artifact.BadRequestJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
err = c.setUpstreamProxyIDs(ctx, registry, artifact.RegistryRequest(*r.Body), regInfo.parentID)
|
||||
if err != nil {
|
||||
return throwModifyRegistry500Error(err), nil
|
||||
}
|
||||
err = c.updateRegistryWithAudit(ctx, repoEntity, registry, session.Principal, regInfo.parentRef)
|
||||
|
||||
if err != nil {
|
||||
return throwModifyRegistry500Error(err), nil
|
||||
}
|
||||
err = c.updateCleanupPolicy(ctx, r.Body, registry.ID)
|
||||
if err != nil {
|
||||
return throwModifyRegistry500Error(err), nil
|
||||
}
|
||||
modifiedRepoEntity, err := c.RegistryRepository.Get(ctx, registry.ID)
|
||||
if err != nil {
|
||||
return throwModifyRegistry500Error(err), nil
|
||||
}
|
||||
cleanupPolicies, err := c.CleanupPolicyStore.GetByRegistryID(ctx, repoEntity.ID)
|
||||
if err != nil {
|
||||
return throwModifyRegistry500Error(err), nil
|
||||
}
|
||||
return artifact.ModifyRegistry200JSONResponse{
|
||||
RegistryResponseJSONResponse: *CreateVirtualRepositoryResponse(
|
||||
modifiedRepoEntity,
|
||||
c.getUpstreamProxyKeys(ctx, modifiedRepoEntity.UpstreamProxies), cleanupPolicies,
|
||||
regInfo.rootIdentifier, c.URLProvider.RegistryURL(),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *APIController) updateUpstreamProxyWithAudit(
|
||||
ctx context.Context, upstreamProxy *types.UpstreamProxyConfig,
|
||||
principal types2.Principal, parentRef string, registryName string,
|
||||
) error {
|
||||
existingUpstreamProxy, err := c.UpstreamProxyStore.Get(ctx, upstreamProxy.RegistryID)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Warn().Msgf(
|
||||
"failed to fig upstream proxy config for: %d",
|
||||
upstreamProxy.RegistryID,
|
||||
)
|
||||
}
|
||||
|
||||
err = c.UpstreamProxyStore.Update(ctx, upstreamProxy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existingUpstreamProxy != nil {
|
||||
auditErr := c.AuditService.Log(
|
||||
ctx,
|
||||
principal,
|
||||
audit.NewResource(audit.ResourceTypeRegistryUpstreamProxy, registryName),
|
||||
audit.ActionUpdated,
|
||||
parentRef,
|
||||
audit.WithOldObject(
|
||||
audit.RegistryUpstreamProxyConfigObject{
|
||||
ID: existingUpstreamProxy.ID,
|
||||
RegistryID: existingUpstreamProxy.RegistryID,
|
||||
Source: existingUpstreamProxy.Source,
|
||||
URL: existingUpstreamProxy.RepoURL,
|
||||
AuthType: existingUpstreamProxy.RepoAuthType,
|
||||
CreatedAt: existingUpstreamProxy.CreatedAt,
|
||||
UpdatedAt: existingUpstreamProxy.UpdatedAt,
|
||||
CreatedBy: existingUpstreamProxy.CreatedBy,
|
||||
UpdatedBy: existingUpstreamProxy.UpdatedBy,
|
||||
},
|
||||
),
|
||||
audit.WithNewObject(
|
||||
audit.RegistryUpstreamProxyConfigObject{
|
||||
ID: upstreamProxy.ID,
|
||||
RegistryID: upstreamProxy.RegistryID,
|
||||
Source: upstreamProxy.Source,
|
||||
URL: upstreamProxy.URL,
|
||||
AuthType: upstreamProxy.AuthType,
|
||||
CreatedAt: upstreamProxy.CreatedAt,
|
||||
UpdatedAt: upstreamProxy.UpdatedAt,
|
||||
CreatedBy: upstreamProxy.CreatedBy,
|
||||
UpdatedBy: upstreamProxy.UpdatedBy,
|
||||
},
|
||||
),
|
||||
)
|
||||
if auditErr != nil {
|
||||
log.Ctx(ctx).Warn().Msgf(
|
||||
"failed to insert audit log for update upstream proxy "+
|
||||
"config operation: %s", auditErr,
|
||||
)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *APIController) updateRegistryWithAudit(
|
||||
ctx context.Context, oldRegistry *types.Registry,
|
||||
newRegistry *types.Registry, principal types2.Principal, parentRef string,
|
||||
) error {
|
||||
err := c.RegistryRepository.Update(ctx, newRegistry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
auditErr := c.AuditService.Log(
|
||||
ctx,
|
||||
principal,
|
||||
audit.NewResource(audit.ResourceTypeRegistry, newRegistry.Name),
|
||||
audit.ActionUpdated,
|
||||
parentRef,
|
||||
audit.WithOldObject(newRegistry),
|
||||
audit.WithNewObject(oldRegistry),
|
||||
)
|
||||
if auditErr != nil {
|
||||
log.Ctx(ctx).Warn().Msgf("failed to insert audit log for update registry operation: %s", auditErr)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func throwModifyRegistry500Error(err error) artifact.ModifyRegistry500JSONResponse {
|
||||
return artifact.ModifyRegistry500JSONResponse{
|
||||
InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse(
|
||||
*GetErrorResponse(http.StatusInternalServerError, err.Error()),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *APIController) updateCleanupPolicy(
|
||||
ctx context.Context, config *artifact.ModifyRegistryJSONRequestBody, registryID int64,
|
||||
) error {
|
||||
existingCleanupPolicies, err := c.CleanupPolicyStore.GetIDsByRegistryID(ctx, registryID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentCleanupPolicyEntities := CreateCleanupPolicyEntity(config, registryID)
|
||||
|
||||
err = c.CleanupPolicyStore.ModifyCleanupPolicies(ctx, currentCleanupPolicyEntities, existingCleanupPolicies)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateRepoEntity(
|
||||
dto artifact.RegistryRequest,
|
||||
parentID int64,
|
||||
rootParentID int64,
|
||||
existingRepo *types.Registry,
|
||||
) (*types.Registry, error) {
|
||||
allowedPattern, blockedPattern, description, labels := getRepoEntityFields(dto)
|
||||
e := ValidatePackageTypeChange(string(existingRepo.PackageType), string(dto.PackageType))
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
e = ValidateRepoTypeChange(string(existingRepo.Type), string(dto.Config.Type))
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
e = ValidateIdentifierChange(existingRepo.Name, dto.Identifier)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
entity := &types.Registry{
|
||||
Name: dto.Identifier,
|
||||
ID: existingRepo.ID,
|
||||
ParentID: parentID,
|
||||
RootParentID: rootParentID,
|
||||
Description: description,
|
||||
AllowedPattern: allowedPattern,
|
||||
BlockedPattern: blockedPattern,
|
||||
PackageType: existingRepo.PackageType,
|
||||
Type: existingRepo.Type,
|
||||
Labels: labels,
|
||||
CreatedAt: existingRepo.CreatedAt,
|
||||
}
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
func UpdateUpstreamProxyEntity(
|
||||
dto artifact.RegistryRequest,
|
||||
parentID int64,
|
||||
rootParentID int64,
|
||||
u *types.UpstreamProxy,
|
||||
) (*types.Registry, *types.UpstreamProxyConfig, error) {
|
||||
allowedPattern := []string{}
|
||||
if dto.AllowedPattern != nil {
|
||||
allowedPattern = *dto.AllowedPattern
|
||||
}
|
||||
blockedPattern := []string{}
|
||||
if dto.BlockedPattern != nil {
|
||||
blockedPattern = *dto.BlockedPattern
|
||||
}
|
||||
e := ValidatePackageTypeChange(string(u.PackageType), string(dto.PackageType))
|
||||
if e != nil {
|
||||
return nil, nil, e
|
||||
}
|
||||
e = ValidateIdentifierChange(u.RepoKey, dto.Identifier)
|
||||
if e != nil {
|
||||
return nil, nil, e
|
||||
}
|
||||
repoEntity := &types.Registry{
|
||||
ID: u.RegistryID,
|
||||
Name: dto.Identifier,
|
||||
ParentID: parentID,
|
||||
RootParentID: rootParentID,
|
||||
AllowedPattern: allowedPattern,
|
||||
BlockedPattern: blockedPattern,
|
||||
PackageType: dto.PackageType,
|
||||
Type: artifact.RegistryTypeUPSTREAM,
|
||||
CreatedAt: u.CreatedAt,
|
||||
}
|
||||
config, _ := dto.Config.AsUpstreamConfig()
|
||||
CleanURLPath(config.Url)
|
||||
upstreamProxyConfigEntity := &types.UpstreamProxyConfig{
|
||||
URL: *config.Url,
|
||||
AuthType: string(config.AuthType),
|
||||
RegistryID: u.RegistryID,
|
||||
CreatedAt: u.CreatedAt,
|
||||
}
|
||||
if config.Source != nil && len(string(*config.Source)) > 0 {
|
||||
err := ValidateUpstreamSource(string(*config.Source))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
upstreamProxyConfigEntity.Source = string(*config.Source)
|
||||
}
|
||||
if string(artifact.UpstreamConfigSourceDockerhub) == string(*config.Source) {
|
||||
upstreamProxyConfigEntity.URL = ""
|
||||
}
|
||||
if u.ID != -1 {
|
||||
upstreamProxyConfigEntity.ID = u.ID
|
||||
}
|
||||
if config.AuthType == artifact.AuthTypeUserPassword {
|
||||
res, err := config.Auth.AsUserPassword()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
upstreamProxyConfigEntity.UserName = res.UserName
|
||||
upstreamProxyConfigEntity.SecretIdentifier = *res.SecretIdentifier
|
||||
upstreamProxyConfigEntity.SecretSpaceID = *res.SecretSpaceId
|
||||
} else {
|
||||
upstreamProxyConfigEntity.UserName = ""
|
||||
upstreamProxyConfigEntity.SecretIdentifier = ""
|
||||
upstreamProxyConfigEntity.SecretSpaceID = 0
|
||||
}
|
||||
return repoEntity, upstreamProxyConfigEntity, nil
|
||||
}
|
408
registry/app/api/controller/metadata/utils.go
Normal file
408
registry/app/api/controller/metadata/utils.go
Normal file
@ -0,0 +1,408 @@
|
||||
// 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 metadata
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
api "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/registry/app/pkg/commons"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var registrySort = []string{
|
||||
"identifier",
|
||||
"lastModified",
|
||||
"registrySize",
|
||||
"artifactsCount",
|
||||
"downloadsCount",
|
||||
}
|
||||
|
||||
const (
|
||||
RepositoryResource = "repository"
|
||||
ArtifactResource = "artifact"
|
||||
ArtifactVersionResource = "artifactversion"
|
||||
RegistryIdentifierErrorMsg = "registry name should be 1~255 characters long with lower case characters, numbers " +
|
||||
"and ._- and must be start with numbers or characters"
|
||||
RegexIdentifierPattern = "^[a-z0-9]+(?:[._-][a-z0-9]+)*$"
|
||||
)
|
||||
|
||||
var RegistrySortMap = map[string]string{
|
||||
"identifier": "name",
|
||||
"lastModified": "updated_at",
|
||||
"registrySize": "size",
|
||||
"artifactsCount": "artifact_count",
|
||||
"downloadsCount": "download_count",
|
||||
"createdAt": "created_at",
|
||||
}
|
||||
|
||||
var artifactSort = []string{
|
||||
"repoKey",
|
||||
"name",
|
||||
"lastModified",
|
||||
"downloadsCount",
|
||||
}
|
||||
|
||||
var artifactSortMap = map[string]string{
|
||||
"repoKey": "name",
|
||||
"lastModified": "updated_at",
|
||||
"name": "image_name",
|
||||
"downloadsCount": "image_name",
|
||||
"createdAt": "created_at",
|
||||
}
|
||||
|
||||
var artifactVersionSort = []string{
|
||||
"name",
|
||||
"size",
|
||||
"pullCommand",
|
||||
"downloadsCount",
|
||||
"lastModified",
|
||||
}
|
||||
|
||||
var artifactVersionSortMap = map[string]string{
|
||||
"name": "name",
|
||||
"size": "name",
|
||||
"pullCommand": "name",
|
||||
"downloadsCount": "name",
|
||||
"lastModified": "updated_at",
|
||||
"createdAt": "created_at",
|
||||
}
|
||||
|
||||
var validRepositoryTypes = []string{
|
||||
string(api.RegistryTypeUPSTREAM),
|
||||
string(api.RegistryTypeVIRTUAL),
|
||||
}
|
||||
|
||||
var validPackageTypes = []string{
|
||||
string(api.PackageTypeDOCKER),
|
||||
string(api.PackageTypeHELM),
|
||||
string(api.PackageTypeMAVEN),
|
||||
}
|
||||
|
||||
var validUpstreamSources = []string{
|
||||
string(api.UpstreamConfigSourceCustom),
|
||||
string(api.UpstreamConfigSourceDockerhub),
|
||||
}
|
||||
|
||||
func ValidatePackageTypes(packageTypes []string) error {
|
||||
if commons.IsEmpty(packageTypes) || IsPackageTypesValid(packageTypes) {
|
||||
return nil
|
||||
}
|
||||
return errors.New("invalid package type")
|
||||
}
|
||||
|
||||
func ValidatePackageType(packageType string) error {
|
||||
if len(packageType) == 0 || IsPackageTypeValid(packageType) {
|
||||
return nil
|
||||
}
|
||||
return errors.New("invalid package type")
|
||||
}
|
||||
|
||||
func ValidatePackageTypeChange(fromDB, newPackage string) error {
|
||||
if len(fromDB) > 0 && len(newPackage) > 0 && fromDB == newPackage {
|
||||
return nil
|
||||
}
|
||||
return errors.New("package type change is not allowed")
|
||||
}
|
||||
|
||||
func ValidateRepoTypeChange(fromDB, newRepo string) error {
|
||||
if len(fromDB) > 0 && len(newRepo) > 0 && fromDB == newRepo {
|
||||
return nil
|
||||
}
|
||||
return errors.New("registry type change is not allowed")
|
||||
}
|
||||
|
||||
func ValidateIdentifierChange(fromDB, newIdentifier string) error {
|
||||
if len(fromDB) > 0 && len(newIdentifier) > 0 && fromDB == newIdentifier {
|
||||
return nil
|
||||
}
|
||||
return errors.New("registry identifier change is not allowed")
|
||||
}
|
||||
|
||||
func ValidateIdentifier(identifier string) error {
|
||||
if len(identifier) == 0 {
|
||||
return errors.New(RegistryIdentifierErrorMsg)
|
||||
}
|
||||
|
||||
matched, err := regexp.MatchString(RegexIdentifierPattern, identifier)
|
||||
if err != nil || !matched {
|
||||
return errors.New(RegistryIdentifierErrorMsg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateUpstream(config *api.RegistryConfig) error {
|
||||
if !commons.IsEmpty(config.Type) && config.Type == api.RegistryTypeUPSTREAM {
|
||||
upstreamConfig, err := config.AsUpstreamConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if commons.IsEmpty(upstreamConfig.Url) {
|
||||
return errors.New("URL is required for upstream repository")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateRepoType(repoType string) error {
|
||||
if len(repoType) == 0 || IsRepoTypeValid(repoType) {
|
||||
return nil
|
||||
}
|
||||
return errors.New("invalid repository type")
|
||||
}
|
||||
|
||||
func ValidateUpstreamSource(source string) error {
|
||||
if len(source) == 0 || IsUpstreamSourceValid(source) {
|
||||
return nil
|
||||
}
|
||||
return errors.New("invalid upstream proxy source")
|
||||
}
|
||||
|
||||
func IsRepoTypeValid(repoType string) bool {
|
||||
for _, item := range validRepositoryTypes {
|
||||
if item == repoType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsUpstreamSourceValid(source string) bool {
|
||||
for _, item := range validUpstreamSources {
|
||||
if item == source {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsPackageTypeValid(packageType string) bool {
|
||||
for _, item := range validPackageTypes {
|
||||
if item == packageType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsPackageTypesValid(packageTypes []string) bool {
|
||||
for _, item := range packageTypes {
|
||||
if !IsPackageTypeValid(item) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func GetTimeInMs(t time.Time) string {
|
||||
return fmt.Sprint(t.UnixMilli())
|
||||
}
|
||||
|
||||
func GetErrorResponse(code int, message string) *api.Error {
|
||||
return &api.Error{
|
||||
Code: fmt.Sprint(code),
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
func GetSortByOrder(sortOrder string) string {
|
||||
defaultSortOrder := "ASC"
|
||||
decreasingSortOrder := "DESC"
|
||||
if len(sortOrder) == 0 {
|
||||
return defaultSortOrder
|
||||
}
|
||||
if sortOrder == decreasingSortOrder {
|
||||
return decreasingSortOrder
|
||||
}
|
||||
return defaultSortOrder
|
||||
}
|
||||
|
||||
func sortKey(slice []string, target string) string {
|
||||
for _, item := range slice {
|
||||
if item == target {
|
||||
return item
|
||||
}
|
||||
}
|
||||
return "createdAt"
|
||||
}
|
||||
|
||||
func GetSortByField(sortByField string, resource string) string {
|
||||
switch resource {
|
||||
case RepositoryResource:
|
||||
sortkey := sortKey(registrySort, sortByField)
|
||||
return RegistrySortMap[sortkey]
|
||||
case ArtifactResource:
|
||||
sortkey := sortKey(artifactSort, sortByField)
|
||||
return artifactSortMap[sortkey]
|
||||
case ArtifactVersionResource:
|
||||
sortkey := sortKey(artifactVersionSort, sortByField)
|
||||
return artifactVersionSortMap[sortkey]
|
||||
}
|
||||
return "created_at"
|
||||
}
|
||||
|
||||
func GetPageLimit(pageSize *api.PageSize) int {
|
||||
defaultPageSize := 10
|
||||
if pageSize != nil {
|
||||
return int(*pageSize)
|
||||
}
|
||||
return defaultPageSize
|
||||
}
|
||||
|
||||
func GetOffset(pageSize *api.PageSize, pageNumber *api.PageNumber) int {
|
||||
defaultOffset := 0
|
||||
if pageSize == nil || pageNumber == nil {
|
||||
return defaultOffset
|
||||
}
|
||||
if *pageNumber == 0 {
|
||||
return 0
|
||||
}
|
||||
return (int(*pageSize)) * int(*pageNumber)
|
||||
}
|
||||
|
||||
func GetPageNumber(pageNumber *api.PageNumber) int64 {
|
||||
defaultPageNumber := int64(1)
|
||||
if pageNumber == nil {
|
||||
return defaultPageNumber
|
||||
}
|
||||
return int64(*pageNumber)
|
||||
}
|
||||
|
||||
func GetSuccessResponse() *api.Success {
|
||||
return &api.Success{
|
||||
Status: api.StatusSUCCESS,
|
||||
}
|
||||
}
|
||||
|
||||
func GetPageCount(count int64, pageSize int) int64 {
|
||||
return int64(math.Ceil(float64(count) / float64(pageSize)))
|
||||
}
|
||||
|
||||
func GetImageSize(size string) string {
|
||||
sizeVal, _ := strconv.ParseInt(size, 10, 64)
|
||||
return GetSize(sizeVal)
|
||||
}
|
||||
|
||||
func GetSize(sizeVal int64) string {
|
||||
humanReadable := humanize.Bytes(uint64(sizeVal))
|
||||
return humanReadable
|
||||
}
|
||||
|
||||
func GetRegRef(parentRef string, regIdentifier string) (string, error) {
|
||||
result := ""
|
||||
if commons.IsEmpty(parentRef) || commons.IsEmpty(regIdentifier) {
|
||||
return result, errors.New("parentRef or regIdentifier is empty")
|
||||
}
|
||||
return parentRef + "/" + regIdentifier, nil
|
||||
}
|
||||
|
||||
func GetRepoURL(rootIdentifier, registry string, registryURL string) string {
|
||||
parsedURL, err := url.Parse(registryURL)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Error parsing URL: %s", registryURL)
|
||||
return ""
|
||||
}
|
||||
parsedURL.Path = path.Join(parsedURL.Path, strings.ToLower(rootIdentifier), registry)
|
||||
return parsedURL.String()
|
||||
}
|
||||
|
||||
func GetRepoURLWithoutProtocol(rootIdentifier string, registry string, registryURL string) string {
|
||||
repoURL := GetRepoURL(rootIdentifier, registry, registryURL)
|
||||
parsedURL, err := url.Parse(repoURL)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("Error parsing URL: ")
|
||||
return ""
|
||||
}
|
||||
|
||||
return parsedURL.Host + parsedURL.Path
|
||||
}
|
||||
|
||||
func GetTagURL(rootIdentifier string, artifact string, version string, registry string, registryURL string) string {
|
||||
url := GetRepoURL(rootIdentifier, registry, registryURL)
|
||||
url += "/" + artifact + "/"
|
||||
url += version
|
||||
return url
|
||||
}
|
||||
|
||||
func GetPullCommand(
|
||||
rootIdentifier string, registry string, image string, tag string,
|
||||
packageType string, registryURL string,
|
||||
) string {
|
||||
if packageType == "DOCKER" {
|
||||
return GetDockerPullCommand(rootIdentifier, registry, image, tag, registryURL)
|
||||
} else if packageType == "HELM" {
|
||||
return GetHelmPullCommand(rootIdentifier, registry, image, tag, registryURL)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetDockerPullCommand(
|
||||
rootIdentifier string, registry string, image string,
|
||||
tag string, registryURL string,
|
||||
) string {
|
||||
return "docker pull " + GetRepoURLWithoutProtocol(rootIdentifier, registry, registryURL) + "/" + image + ":" + tag
|
||||
}
|
||||
|
||||
func GetHelmPullCommand(rootIdentifier string, registry string, image string, tag string, registryURL string) string {
|
||||
return "helm install " + GetRepoURLWithoutProtocol(rootIdentifier, registry, registryURL) + "/" + image + ":" + tag
|
||||
}
|
||||
|
||||
// CleanURLPath removes leading and trailing spaces and trailing slashes from the given URL string.
|
||||
func CleanURLPath(input *string) {
|
||||
if input == nil {
|
||||
return
|
||||
}
|
||||
// Parse the input to URL
|
||||
u, err := url.Parse(*input)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Clean the path by removing trailing slashes and spaces
|
||||
cleanedPath := strings.TrimRight(strings.TrimSpace(u.Path), "/")
|
||||
|
||||
// Update the URL path in the original input string
|
||||
u.Path = cleanedPath
|
||||
|
||||
// Update the input string with the cleaned URL string representation
|
||||
*input = u.String()
|
||||
}
|
||||
|
||||
func getPermissionChecks(
|
||||
space *types.Space,
|
||||
registryIdentifier string,
|
||||
permission enum.Permission,
|
||||
) []types.PermissionCheck {
|
||||
var permissionChecks []types.PermissionCheck
|
||||
permissionCheck := &types.PermissionCheck{
|
||||
Scope: types.Scope{SpacePath: space.Identifier},
|
||||
Resource: types.Resource{Type: enum.ResourceTypeRegistry, Identifier: registryIdentifier},
|
||||
Permission: permission,
|
||||
}
|
||||
permissionChecks = append(permissionChecks, *permissionCheck)
|
||||
return permissionChecks
|
||||
}
|
90
registry/app/api/handler/oci/artifactfilter.go
Normal file
90
registry/app/api/handler/oci/artifactfilter.go
Normal file
@ -0,0 +1,90 @@
|
||||
// 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 oci
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func MatchArtifactFilter(
|
||||
allowedPattern pq.StringArray,
|
||||
blockedPattern pq.StringArray, artifact string,
|
||||
) (bool, error) {
|
||||
allowedPatterns := []string(allowedPattern)
|
||||
blockedPatterns := []string(blockedPattern)
|
||||
|
||||
if len(blockedPatterns) > 0 {
|
||||
flag, err := matchPatterns(blockedPatterns, artifact)
|
||||
if err != nil {
|
||||
return flag, fmt.Errorf(
|
||||
"failed to match blocked patterns for artifact %s: %w",
|
||||
artifact, err,
|
||||
)
|
||||
}
|
||||
if flag {
|
||||
return false, errors.New(
|
||||
"failed because artifact seems to be matching blocked patterns configured on repository",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if len(allowedPatterns) > 0 {
|
||||
flag, err := matchPatterns(allowedPatterns, artifact)
|
||||
if err != nil {
|
||||
return flag, fmt.Errorf(
|
||||
"failed to match allowed patterns for artifact %s: %w",
|
||||
artifact, err,
|
||||
)
|
||||
}
|
||||
|
||||
if !flag {
|
||||
return false, errors.New(
|
||||
"failed because artifact doesn't seems to be matching allowed patterns configured on repository",
|
||||
)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func matchPatterns(
|
||||
patterns []string,
|
||||
val string,
|
||||
) (bool, error) {
|
||||
for _, pattern := range patterns {
|
||||
flag, err := regexp.MatchString(pattern, val)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf(
|
||||
"failed to match pattern %s for val %s",
|
||||
pattern,
|
||||
val,
|
||||
)
|
||||
return flag, fmt.Errorf(
|
||||
"failed to match pattern %s for val %s: %w",
|
||||
pattern,
|
||||
val,
|
||||
err,
|
||||
)
|
||||
}
|
||||
if flag {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
249
registry/app/api/handler/oci/base.go
Normal file
249
registry/app/api/handler/oci/base.go
Normal file
@ -0,0 +1,249 @@
|
||||
// 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 oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
usercontroller "github.com/harness/gitness/app/api/controller/user"
|
||||
"github.com/harness/gitness/app/auth/authn"
|
||||
"github.com/harness/gitness/app/auth/authz"
|
||||
corestore "github.com/harness/gitness/app/store"
|
||||
urlprovider "github.com/harness/gitness/app/url"
|
||||
"github.com/harness/gitness/registry/app/api/controller/metadata"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
"github.com/harness/gitness/registry/app/dist_temp/dcontext"
|
||||
"github.com/harness/gitness/registry/app/dist_temp/errcode"
|
||||
"github.com/harness/gitness/registry/app/pkg"
|
||||
"github.com/harness/gitness/registry/app/pkg/commons"
|
||||
"github.com/harness/gitness/registry/app/pkg/docker"
|
||||
|
||||
v2 "github.com/distribution/distribution/v3/registry/api/v2"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func NewHandler(
|
||||
controller *docker.Controller, spaceStore corestore.SpaceStore, tokenStore corestore.TokenStore,
|
||||
userCtrl *usercontroller.Controller, authenticator authn.Authenticator, urlProvider urlprovider.Provider,
|
||||
authorizer authz.Authorizer,
|
||||
) *Handler {
|
||||
return &Handler{
|
||||
Controller: controller,
|
||||
SpaceStore: spaceStore,
|
||||
TokenStore: tokenStore,
|
||||
UserCtrl: userCtrl,
|
||||
Authenticator: authenticator,
|
||||
URLProvider: urlProvider,
|
||||
Authorizer: authorizer,
|
||||
}
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
Controller *docker.Controller
|
||||
SpaceStore corestore.SpaceStore
|
||||
TokenStore corestore.TokenStore
|
||||
UserCtrl *usercontroller.Controller
|
||||
Authenticator authn.Authenticator
|
||||
URLProvider urlprovider.Provider
|
||||
Authorizer authz.Authorizer
|
||||
}
|
||||
|
||||
type routeType string
|
||||
|
||||
const (
|
||||
Manifests routeType = "manifests" // /v2/:registry/:image/manifests/:reference.
|
||||
Blobs routeType = "blobs" // /v2/:registry/:image/blobs/:digest.
|
||||
BlobsUploadsSession routeType = "blob-uploads-session" // /v2/:registry/:image/blobs/uploads/:session_id.
|
||||
Tags routeType = "tags" // /v2/:registry/:image/tags/list.
|
||||
Referrers routeType = "referrers" // /v2/:registry/:image/referrers/:digest.
|
||||
Invalid routeType = "invalid" // Invalid route.
|
||||
MinSizeOfURLSegments = 5
|
||||
|
||||
APIPartManifest = "manifests"
|
||||
APIPartBlobs = "blobs"
|
||||
APIPartUpload = "uploads"
|
||||
APIPartTag = "tags"
|
||||
APIPartReferrer = "referrers"
|
||||
// Add other route types here.
|
||||
)
|
||||
|
||||
func getRouteType(url string) routeType {
|
||||
url = strings.Trim(url, "/")
|
||||
segments := strings.Split(url, "/")
|
||||
if len(segments) < MinSizeOfURLSegments {
|
||||
return Invalid
|
||||
}
|
||||
typ := segments[len(segments)-2]
|
||||
switch typ {
|
||||
case APIPartManifest:
|
||||
return Manifests
|
||||
case APIPartBlobs:
|
||||
if segments[len(segments)-1] == APIPartUpload {
|
||||
return BlobsUploadsSession
|
||||
}
|
||||
return Blobs
|
||||
case APIPartUpload:
|
||||
return BlobsUploadsSession
|
||||
case APIPartTag:
|
||||
return Tags
|
||||
case APIPartReferrer:
|
||||
return Referrers
|
||||
}
|
||||
return Invalid
|
||||
}
|
||||
|
||||
func GetQueryParamMap(queryParams url.Values) map[string]string {
|
||||
queryMap := make(map[string]string)
|
||||
for key, values := range queryParams {
|
||||
if len(values) > 0 {
|
||||
queryMap[key] = values[0]
|
||||
}
|
||||
}
|
||||
return queryMap
|
||||
}
|
||||
|
||||
// ExtractPathVars extracts registry, image, reference, digest and tag from the path
|
||||
// Path format: /v2/:rootSpace/:registry/:image/manifests/:reference (for ex:
|
||||
// /v2/myRootSpace/reg1/alpine/blobs/sha256:a258b2a6b59a7aa244d8ceab095c7f8df726f27075a69fca7ad8490f3f63148a).
|
||||
func ExtractPathVars(path string, paramMap map[string]string) (rootIdentifier, registry, image, ref, dgst, tag string) {
|
||||
path = strings.Trim(path, "/")
|
||||
segments := strings.Split(path, "/")
|
||||
rootIdentifier = segments[1]
|
||||
registry = segments[2]
|
||||
image = strings.Join(segments[3:len(segments)-2], "/")
|
||||
typ := getRouteType(path)
|
||||
|
||||
switch typ {
|
||||
case Manifests:
|
||||
ref = segments[len(segments)-1]
|
||||
_, err := digest.Parse(ref)
|
||||
if err != nil {
|
||||
tag = ref
|
||||
} else {
|
||||
dgst = ref
|
||||
}
|
||||
case Blobs:
|
||||
dgst = segments[len(segments)-1]
|
||||
case BlobsUploadsSession:
|
||||
if segments[len(segments)-1] != APIPartUpload && segments[len(segments)-2] == APIPartUpload {
|
||||
image = strings.Join(segments[3:len(segments)-3], "/")
|
||||
ref = segments[len(segments)-1]
|
||||
}
|
||||
if _, ok := paramMap["digest"]; ok {
|
||||
dgst = paramMap["digest"]
|
||||
}
|
||||
case Tags:
|
||||
// do nothing.
|
||||
case Referrers:
|
||||
dgst = segments[len(segments)-1]
|
||||
case Invalid:
|
||||
log.Warn().Msgf("Invalid route: %s", path)
|
||||
default:
|
||||
log.Warn().Msgf("Unknown route type: %s", typ)
|
||||
}
|
||||
|
||||
log.Debug().Msgf(
|
||||
"For path: %s, rootIdentifier: %s, registry: %s, image: %s, ref: %s, dgst: %s, tag: %s",
|
||||
path, rootIdentifier, registry, image, ref, dgst, tag,
|
||||
)
|
||||
|
||||
return rootIdentifier, registry, image, ref, dgst, tag
|
||||
}
|
||||
|
||||
func handleErrors(ctx context.Context, errors errcode.Errors, w http.ResponseWriter) {
|
||||
if !commons.IsEmpty(errors) {
|
||||
_ = errcode.ServeJSON(w, errors)
|
||||
docker.LogError(errors)
|
||||
log.Ctx(ctx).Error().Errs("OCI errors", errors).Msgf("Error occurred")
|
||||
} else if status, ok := ctx.Value("http.response.status").(int); ok && status >= 200 && status <= 399 {
|
||||
dcontext.GetResponseLogger(ctx, log.Info()).Msg("response completed")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) getRegistryInfo(r *http.Request, remoteSupport bool) (pkg.RegistryInfo, error) {
|
||||
ctx := r.Context()
|
||||
queryParams := r.URL.Query()
|
||||
path := r.URL.Path
|
||||
paramMap := GetQueryParamMap(queryParams)
|
||||
rootIdentifier, registryIdentifier, image, ref, dgst, tag := ExtractPathVars(path, paramMap)
|
||||
if err := metadata.ValidateIdentifier(rootIdentifier); err != nil {
|
||||
return pkg.RegistryInfo{}, err
|
||||
}
|
||||
|
||||
rootSpace, err := h.SpaceStore.FindByRef(ctx, rootIdentifier)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Msgf("Root space not found: %s", rootIdentifier)
|
||||
return pkg.RegistryInfo{}, errcode.ErrCodeRootNotFound
|
||||
}
|
||||
|
||||
registry, err := h.Controller.RegistryDao.GetByRootParentIDAndName(ctx, rootSpace.ID, registryIdentifier)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Msgf(
|
||||
"registry %s not found for root: %s. Reason: %s", registryIdentifier, rootSpace.Identifier, err,
|
||||
)
|
||||
return pkg.RegistryInfo{}, errcode.ErrCodeRegNotFound
|
||||
}
|
||||
_, err = h.SpaceStore.Find(r.Context(), registry.ParentID)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Msgf("Parent space not found: %d", registry.ParentID)
|
||||
return pkg.RegistryInfo{}, errcode.ErrCodeParentNotFound
|
||||
}
|
||||
|
||||
info := &pkg.RegistryInfo{
|
||||
ArtifactInfo: &pkg.ArtifactInfo{
|
||||
BaseInfo: &pkg.BaseInfo{
|
||||
RootIdentifier: rootIdentifier,
|
||||
RootParentID: rootSpace.ID,
|
||||
ParentID: registry.ParentID,
|
||||
},
|
||||
RegIdentifier: registryIdentifier,
|
||||
Image: image,
|
||||
},
|
||||
Reference: ref,
|
||||
Digest: dgst,
|
||||
Tag: tag,
|
||||
URLBuilder: v2.NewURLBuilderFromRequest(r, false),
|
||||
Path: r.URL.Path,
|
||||
}
|
||||
|
||||
log.Ctx(ctx).Info().Msgf("Dispatch: URI: %s", path)
|
||||
if commons.IsEmpty(rootSpace.Identifier) {
|
||||
log.Ctx(ctx).Error().Msgf("ParentRef not found in context")
|
||||
return pkg.RegistryInfo{}, errcode.ErrCodeParentNotFound
|
||||
}
|
||||
|
||||
if commons.IsEmpty(registryIdentifier) {
|
||||
log.Ctx(ctx).Warn().Msgf("registry not found in context")
|
||||
return pkg.RegistryInfo{}, errcode.ErrCodeRegNotFound
|
||||
}
|
||||
|
||||
if !commons.IsEmpty(info.Image) && !commons.IsEmpty(info.Tag) {
|
||||
flag, err2 := MatchArtifactFilter(registry.AllowedPattern, registry.BlockedPattern, info.Image+":"+info.Tag)
|
||||
if !flag || err2 != nil {
|
||||
return pkg.RegistryInfo{}, errcode.ErrCodeDenied
|
||||
}
|
||||
}
|
||||
|
||||
if registry.Type == artifact.RegistryTypeUPSTREAM && !remoteSupport {
|
||||
log.Ctx(ctx).Warn().Msgf("Remote registryIdentifier %s not supported", registryIdentifier)
|
||||
return pkg.RegistryInfo{}, errcode.ErrCodeDenied
|
||||
}
|
||||
|
||||
return *info, nil
|
||||
}
|
35
registry/app/api/handler/oci/delete_blob.go
Normal file
35
registry/app/api/handler/oci/delete_blob.go
Normal file
@ -0,0 +1,35 @@
|
||||
// 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 oci
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/registry/app/pkg/commons"
|
||||
)
|
||||
|
||||
func (h *Handler) DeleteBlob(w http.ResponseWriter, r *http.Request) {
|
||||
info, err := h.getRegistryInfo(r, false)
|
||||
if err != nil {
|
||||
handleErrors(r.Context(), []error{err}, w)
|
||||
return
|
||||
}
|
||||
|
||||
headers, errs := h.Controller.DeleteBlob(r.Context(), info)
|
||||
if commons.IsEmpty(errs) {
|
||||
headers.WriteToResponse(w)
|
||||
}
|
||||
handleErrors(r.Context(), errs, w)
|
||||
}
|
35
registry/app/api/handler/oci/delete_blob_upload.go
Normal file
35
registry/app/api/handler/oci/delete_blob_upload.go
Normal file
@ -0,0 +1,35 @@
|
||||
// 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 oci
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/registry/app/pkg/commons"
|
||||
)
|
||||
|
||||
func (h *Handler) CancelBlobUpload(w http.ResponseWriter, r *http.Request) {
|
||||
info, err := h.getRegistryInfo(r, false)
|
||||
if err != nil {
|
||||
handleErrors(r.Context(), []error{err}, w)
|
||||
return
|
||||
}
|
||||
|
||||
headers, errs := h.Controller.CancelBlobUpload(r.Context(), info, r.FormValue("_state"))
|
||||
if commons.IsEmpty(errs) {
|
||||
headers.WriteToResponse(w)
|
||||
}
|
||||
handleErrors(r.Context(), errs, w)
|
||||
}
|
47
registry/app/api/handler/oci/delete_manifest.go
Normal file
47
registry/app/api/handler/oci/delete_manifest.go
Normal file
@ -0,0 +1,47 @@
|
||||
// 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 oci
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/registry/app/pkg/commons"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// PutManifest validates and stores a manifest in the registry.
|
||||
func (h *Handler) DeleteManifest(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
info, err := h.getRegistryInfo(r, false)
|
||||
if err != nil {
|
||||
handleErrors(r.Context(), []error{err}, w)
|
||||
return
|
||||
}
|
||||
length := r.ContentLength
|
||||
if length > 0 {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, length)
|
||||
}
|
||||
errs, headers := h.Controller.DeleteManifest(r.Context(), info)
|
||||
|
||||
if !commons.IsEmpty(errs) {
|
||||
log.Ctx(ctx).Error().Msgf("DeleteManifest: %v", errs)
|
||||
}
|
||||
|
||||
if headers != nil {
|
||||
headers.WriteToResponse(w)
|
||||
}
|
||||
handleErrors(ctx, errs, w)
|
||||
}
|
23
registry/app/api/handler/oci/get_base.go
Normal file
23
registry/app/api/handler/oci/get_base.go
Normal file
@ -0,0 +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
|
||||
//
|
||||
// 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 oci
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (h *Handler) APIBase(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
91
registry/app/api/handler/oci/get_blob.go
Normal file
91
registry/app/api/handler/oci/get_blob.go
Normal file
@ -0,0 +1,91 @@
|
||||
// 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 oci
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/harness/gitness/registry/app/pkg"
|
||||
"github.com/harness/gitness/registry/app/pkg/commons"
|
||||
"github.com/harness/gitness/registry/app/pkg/docker"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) GetBlob(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
info, err := h.getRegistryInfo(r, true)
|
||||
if err != nil {
|
||||
handleErrors(r.Context(), []error{err}, w)
|
||||
return
|
||||
}
|
||||
result := h.Controller.GetBlob(ctx, info)
|
||||
|
||||
response, ok := result.(*docker.GetBlobResponse)
|
||||
if !ok {
|
||||
log.Ctx(ctx).Error().Msg("Failed to cast result to GetBlobResponse")
|
||||
handleErrors(ctx, []error{errors.New("failed to cast result to GetBlobResponse")}, w)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if response.Body != nil {
|
||||
response.Body.Close()
|
||||
}
|
||||
if response.ReadCloser != nil {
|
||||
response.ReadCloser.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if commons.IsEmpty(response.GetErrors()) {
|
||||
if !commons.IsEmpty(response.RedirectURL) {
|
||||
http.Redirect(w, r, response.RedirectURL, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
response.ResponseHeaders.WriteHeadersToResponse(w)
|
||||
if r.Method == http.MethodHead {
|
||||
return
|
||||
}
|
||||
|
||||
h.serveContent(w, r, response, info)
|
||||
response.ResponseHeaders.WriteToResponse(w)
|
||||
}
|
||||
|
||||
handleErrors(r.Context(), response.GetErrors(), w)
|
||||
}
|
||||
|
||||
func (h *Handler) serveContent(
|
||||
w http.ResponseWriter, r *http.Request, response *docker.GetBlobResponse, info pkg.RegistryInfo,
|
||||
) {
|
||||
if response.Body != nil {
|
||||
http.ServeContent(w, r, info.Digest, time.Time{}, response.Body)
|
||||
} else {
|
||||
// Use io.CopyN to avoid out of memory when pulling big blob
|
||||
written, err2 := io.CopyN(w, response.ReadCloser, response.Size)
|
||||
if err2 != nil {
|
||||
response.Errors = append(response.Errors, errors.New("error copying blob to response"))
|
||||
log.Ctx(r.Context()).Error().Msg("error copying blob to response:")
|
||||
}
|
||||
if written != response.Size {
|
||||
response.Errors = append(
|
||||
response.Errors,
|
||||
fmt.Errorf(fmt.Sprintf("The size mismatch, actual:%d, expected: %d", written, response.Size)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
37
registry/app/api/handler/oci/get_blob_upload.go
Normal file
37
registry/app/api/handler/oci/get_blob_upload.go
Normal file
@ -0,0 +1,37 @@
|
||||
// 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 oci
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/registry/app/pkg/commons"
|
||||
)
|
||||
|
||||
func (h *Handler) GetUploadBlobStatus(w http.ResponseWriter, r *http.Request) {
|
||||
info, err := h.getRegistryInfo(r, false)
|
||||
if err != nil {
|
||||
handleErrors(r.Context(), []error{err}, w)
|
||||
return
|
||||
}
|
||||
stateToken := r.FormValue("_state")
|
||||
headers, errs := h.Controller.GetUploadBlobStatus(r.Context(), info, stateToken)
|
||||
|
||||
if commons.IsEmpty(errs) {
|
||||
headers.WriteToResponse(w)
|
||||
return
|
||||
}
|
||||
handleErrors(r.Context(), errs, w)
|
||||
}
|
21
registry/app/api/handler/oci/get_catalog.go
Normal file
21
registry/app/api/handler/oci/get_catalog.go
Normal file
@ -0,0 +1,21 @@
|
||||
// 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 oci
|
||||
|
||||
import "net/http"
|
||||
|
||||
func (h *Handler) GetCatalog(w http.ResponseWriter, r *http.Request) {
|
||||
h.Controller.GetCatalog(w, r)
|
||||
}
|
57
registry/app/api/handler/oci/get_manifest.go
Normal file
57
registry/app/api/handler/oci/get_manifest.go
Normal file
@ -0,0 +1,57 @@
|
||||
// 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 oci
|
||||
|
||||
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/docker"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// GetManifest fetches the image manifest from the storage backend, if it exists.
|
||||
func (h *Handler) GetManifest(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
info, err := h.getRegistryInfo(r, true)
|
||||
if err != nil {
|
||||
handleErrors(ctx, errcode.Errors{err}, w)
|
||||
return
|
||||
}
|
||||
|
||||
result := h.Controller.PullManifest(
|
||||
ctx,
|
||||
info,
|
||||
r.Header[commons.HeaderAccept],
|
||||
r.Header[commons.HeaderIfNoneMatch],
|
||||
)
|
||||
if commons.IsEmpty(result.GetErrors()) {
|
||||
response, ok := result.(*docker.GetManifestResponse)
|
||||
if !ok {
|
||||
log.Ctx(ctx).Error().Msg("Failed to cast result to GetManifestResponse")
|
||||
return
|
||||
}
|
||||
response.ResponseHeaders.WriteToResponse(w)
|
||||
_, bytes, _ := response.Manifest.Payload()
|
||||
if _, err := w.Write(bytes); err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msg("Failed to write response")
|
||||
response.ResponseHeaders.Code = http.StatusInternalServerError
|
||||
}
|
||||
return
|
||||
}
|
||||
handleErrors(ctx, result.GetErrors(), w)
|
||||
}
|
45
registry/app/api/handler/oci/get_referrers.go
Normal file
45
registry/app/api/handler/oci/get_referrers.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 oci
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/registry/app/dist_temp/errcode"
|
||||
)
|
||||
|
||||
func (h *Handler) GetReferrers(w http.ResponseWriter, r *http.Request) {
|
||||
info, err := h.getRegistryInfo(r, false)
|
||||
if err != nil {
|
||||
handleErrors(r.Context(), []error{err}, w)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
errorsList := make(errcode.Errors, 0)
|
||||
|
||||
index, responseHeaders, err := h.Controller.GetReferrers(r.Context(), info, r.URL.Query().Get("artifactType"))
|
||||
if err != nil {
|
||||
errorsList = append(errorsList, err)
|
||||
}
|
||||
if index != nil {
|
||||
responseHeaders.WriteHeadersToResponse(w)
|
||||
if err := json.NewEncoder(w).Encode(index); err != nil {
|
||||
errorsList = append(errorsList, errcode.ErrCodeUnknown.WithDetail(err))
|
||||
}
|
||||
}
|
||||
|
||||
handleErrors(r.Context(), errorsList, w)
|
||||
}
|
68
registry/app/api/handler/oci/get_tags.go
Normal file
68
registry/app/api/handler/oci/get_tags.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 oci
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/harness/gitness/registry/app/dist_temp/errcode"
|
||||
"github.com/harness/gitness/registry/app/pkg/docker"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) GetTags(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
info, err := h.getRegistryInfo(r, false)
|
||||
if err != nil {
|
||||
handleErrors(ctx, []error{err}, w)
|
||||
return
|
||||
}
|
||||
errorsList := make(errcode.Errors, 0)
|
||||
|
||||
q := r.URL.Query()
|
||||
lastEntry := q.Get("last")
|
||||
maxEntries, err := strconv.Atoi(q.Get("n"))
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msgf("Failed to parse max entries %s", q.Get("n"))
|
||||
maxEntries = docker.DefaultMaximumReturnedEntries
|
||||
}
|
||||
|
||||
if maxEntries <= 0 {
|
||||
maxEntries = docker.DefaultMaximumReturnedEntries
|
||||
}
|
||||
|
||||
rs, tags, err := h.Controller.GetTags(ctx, lastEntry, maxEntries, r.URL.String(), info)
|
||||
log.Ctx(ctx).Debug().Msgf("GetTags: %v %s", rs, tags)
|
||||
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msg("Failed to list tags")
|
||||
handleErrors(ctx, errorsList, w)
|
||||
return
|
||||
}
|
||||
rs.WriteHeadersToResponse(w)
|
||||
enc := json.NewEncoder(w)
|
||||
if err := enc.Encode(
|
||||
docker.TagsAPIResponse{
|
||||
Name: info.RegIdentifier,
|
||||
Tags: tags,
|
||||
},
|
||||
); err != nil {
|
||||
errorsList = append(errorsList, errcode.ErrCodeUnknown.WithDetail(err))
|
||||
}
|
||||
handleErrors(ctx, errorsList, w)
|
||||
}
|
212
registry/app/api/handler/oci/get_token.go
Normal file
212
registry/app/api/handler/oci/get_token.go
Normal file
@ -0,0 +1,212 @@
|
||||
// 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 oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
apiauth "github.com/harness/gitness/app/api/auth"
|
||||
"github.com/harness/gitness/app/api/render"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/app/auth"
|
||||
"github.com/harness/gitness/app/jwt"
|
||||
"github.com/harness/gitness/app/paths"
|
||||
"github.com/harness/gitness/app/token"
|
||||
"github.com/harness/gitness/types"
|
||||
"github.com/harness/gitness/types/enum"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (h *Handler) GetToken(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
session, ok := request.AuthSessionFrom(ctx)
|
||||
if !ok || session.Principal == auth.AnonymousPrincipal {
|
||||
returnForbiddenResponse(w, fmt.Errorf("no auth session found"))
|
||||
return
|
||||
}
|
||||
|
||||
if tokenMetadata, okt := session.Metadata.(*auth.TokenMetadata); okt &&
|
||||
tokenMetadata.TokenType != enum.TokenTypePAT {
|
||||
returnForbiddenResponse(w, fmt.Errorf("only personal access token allowed"))
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.UserCtrl.FindNoAuth(ctx, session.Principal.UID)
|
||||
if err != nil {
|
||||
returnForbiddenResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
requestedOciAccess := GetRequestedResourceActions(getScopes(r.URL))
|
||||
var accessPermissionsList = []jwt.AccessPermissions{}
|
||||
for _, ra := range requestedOciAccess {
|
||||
space, err := h.getSpace(ctx, ra.Name)
|
||||
if err != nil {
|
||||
render.TranslatedUserError(ctx, w, err)
|
||||
log.Ctx(ctx).Warn().Msgf("failed to find space by ref: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
accessPermissionsList = h.getAccessPermissionList(ctx, space, ra, session, accessPermissionsList)
|
||||
}
|
||||
|
||||
subClaimsAccessPermissions := &jwt.SubClaimsAccessPermissions{
|
||||
Source: jwt.OciSource,
|
||||
Permissions: accessPermissionsList,
|
||||
}
|
||||
|
||||
jwtToken, err := h.getTokenDetails(user, subClaimsAccessPermissions)
|
||||
if err != nil {
|
||||
returnForbiddenResponse(w, err)
|
||||
return
|
||||
}
|
||||
if jwtToken != "" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := w.Write([]byte(fmt.Sprintf("{\"token\":\"%s\"}", jwtToken)))
|
||||
if err != nil {
|
||||
log.Error().Msgf("failed to write token response: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) getSpace(ctx context.Context, name string) (*types.Space, error) {
|
||||
spaceRef, _, _ := paths.DisectRoot(name)
|
||||
space, err := h.SpaceStore.FindByRef(ctx, spaceRef)
|
||||
return space, err
|
||||
}
|
||||
|
||||
func (h *Handler) getAccessPermissionList(
|
||||
ctx context.Context, space *types.Space, ra *ResourceActions, session *auth.Session,
|
||||
accessPermissionsList []jwt.AccessPermissions,
|
||||
) []jwt.AccessPermissions {
|
||||
accessPermissions := &jwt.AccessPermissions{SpaceID: space.ID, Permissions: []enum.Permission{}}
|
||||
|
||||
for _, a := range ra.Actions {
|
||||
permission, err := getPermissionFromAction(ctx, a)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Warn().Msgf("failed to get permission from action: %v", err)
|
||||
continue
|
||||
}
|
||||
scopeErr := apiauth.CheckSpaceScope(
|
||||
ctx,
|
||||
h.Authorizer,
|
||||
session,
|
||||
space,
|
||||
enum.ResourceTypeRegistry,
|
||||
permission,
|
||||
)
|
||||
if scopeErr != nil {
|
||||
log.Ctx(ctx).Warn().Msgf("failed to check space scope: %v", scopeErr)
|
||||
continue
|
||||
}
|
||||
accessPermissions.Permissions = append(accessPermissions.Permissions, permission)
|
||||
}
|
||||
accessPermissionsList = append(accessPermissionsList, *accessPermissions)
|
||||
return accessPermissionsList
|
||||
}
|
||||
|
||||
func getPermissionFromAction(ctx context.Context, action string) (enum.Permission, error) {
|
||||
switch action {
|
||||
case "pull":
|
||||
return enum.PermissionArtifactsDownload, nil
|
||||
case "push":
|
||||
return enum.PermissionArtifactsUpload, nil
|
||||
case "delete":
|
||||
return enum.PermissionArtifactsDelete, nil
|
||||
default:
|
||||
err := fmt.Errorf("unknown action: %s", action)
|
||||
log.Ctx(ctx).Err(err).Msgf("Failed to get permission from action: %v", err)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
func returnForbiddenResponse(w http.ResponseWriter, err error) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
_, err2 := w.Write([]byte(fmt.Sprintf("requested access to the resource is denied: %v", err)))
|
||||
if err2 != nil {
|
||||
log.Error().Msgf("failed to write token response: %v", err2)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* getTokenDetails attempts to get token details.
|
||||
*/
|
||||
func (h *Handler) getTokenDetails(
|
||||
user *types.User,
|
||||
accessPermissions *jwt.SubClaimsAccessPermissions,
|
||||
) (string, error) {
|
||||
return token.CreateUserWithAccessPermissions(user, accessPermissions)
|
||||
}
|
||||
|
||||
// GetRequestedResourceActions ...
|
||||
func GetRequestedResourceActions(scopes []string) []*ResourceActions {
|
||||
var res []*ResourceActions
|
||||
for _, s := range scopes {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
items := strings.Split(s, ":")
|
||||
length := len(items)
|
||||
|
||||
var resourceType string
|
||||
var resourceName string
|
||||
actions := make([]string, 0)
|
||||
|
||||
switch length {
|
||||
case 1:
|
||||
resourceType = items[0]
|
||||
case 2:
|
||||
resourceType = items[0]
|
||||
resourceName = items[1]
|
||||
default:
|
||||
resourceType = items[0]
|
||||
resourceName = strings.Join(items[1:length-1], ":")
|
||||
if len(items[length-1]) > 0 {
|
||||
actions = strings.Split(items[length-1], ",")
|
||||
}
|
||||
}
|
||||
|
||||
res = append(
|
||||
res, &ResourceActions{
|
||||
Type: resourceType,
|
||||
Name: resourceName,
|
||||
Actions: actions,
|
||||
},
|
||||
)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func getScopes(u *url.URL) []string {
|
||||
var sector string
|
||||
var result []string
|
||||
for _, sector = range u.Query()["scope"] {
|
||||
result = append(result, strings.Split(sector, " ")...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ResourceActions stores allowed actions on a resource.
|
||||
type ResourceActions struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Actions []string `json:"actions"`
|
||||
}
|
48
registry/app/api/handler/oci/head_blob.go
Normal file
48
registry/app/api/handler/oci/head_blob.go
Normal file
@ -0,0 +1,48 @@
|
||||
// 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 oci
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/registry/app/pkg/commons"
|
||||
)
|
||||
|
||||
func (h *Handler) HeadBlob(w http.ResponseWriter, r *http.Request) {
|
||||
info, err := h.getRegistryInfo(r, false)
|
||||
if err != nil {
|
||||
handleErrors(r.Context(), []error{err}, w)
|
||||
return
|
||||
}
|
||||
headers, body, _, readCloser, redirectURL, errs := h.Controller.HeadBlob(r.Context(), info)
|
||||
defer func() {
|
||||
if body != nil {
|
||||
body.Close()
|
||||
}
|
||||
if readCloser != nil {
|
||||
readCloser.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if commons.IsEmpty(errs) {
|
||||
if redirectURL != "" {
|
||||
http.Redirect(w, r, redirectURL, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
headers.WriteHeadersToResponse(w)
|
||||
}
|
||||
handleErrors(r.Context(), errs, w)
|
||||
}
|
43
registry/app/api/handler/oci/head_manifest.go
Normal file
43
registry/app/api/handler/oci/head_manifest.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 oci
|
||||
|
||||
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/docker"
|
||||
)
|
||||
|
||||
// HeadManifest fetches the image manifest from the storage backend, if it exists.
|
||||
func (h *Handler) HeadManifest(w http.ResponseWriter, r *http.Request) {
|
||||
info, err := h.getRegistryInfo(r, true)
|
||||
if err != nil {
|
||||
handleErrors(r.Context(), errcode.Errors{err}, w)
|
||||
return
|
||||
}
|
||||
|
||||
result := h.Controller.HeadManifest(
|
||||
r.Context(),
|
||||
info,
|
||||
r.Header[commons.HeaderAccept],
|
||||
r.Header[commons.HeaderIfNoneMatch],
|
||||
)
|
||||
if commons.IsEmpty(result.GetErrors()) {
|
||||
result.(*docker.GetManifestResponse).ResponseHeaders.WriteToResponse(w)
|
||||
}
|
||||
handleErrors(r.Context(), result.GetErrors(), w)
|
||||
}
|
43
registry/app/api/handler/oci/patch_blob_upload.go
Normal file
43
registry/app/api/handler/oci/patch_blob_upload.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 oci
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/registry/app/pkg/commons"
|
||||
)
|
||||
|
||||
func (h *Handler) PatchBlobUpload(w http.ResponseWriter, r *http.Request) {
|
||||
info, err := h.getRegistryInfo(r, false)
|
||||
if err != nil {
|
||||
handleErrors(r.Context(), []error{err}, w)
|
||||
return
|
||||
}
|
||||
ct := r.Header.Get(commons.HeaderContentType)
|
||||
cr := r.Header.Get(commons.HeaderContentRange)
|
||||
cl := r.Header.Get(commons.HeaderContentLength)
|
||||
length := r.ContentLength
|
||||
if length > 0 {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, length)
|
||||
}
|
||||
stateToken := r.FormValue("_state")
|
||||
headers, errs := h.Controller.PatchBlobUpload(r.Context(), info, ct, cr, cl, length, stateToken, r.Body)
|
||||
|
||||
if commons.IsEmpty(errs) {
|
||||
headers.WriteToResponse(w)
|
||||
}
|
||||
handleErrors(r.Context(), errs, w)
|
||||
}
|
42
registry/app/api/handler/oci/post_blob_upload.go
Normal file
42
registry/app/api/handler/oci/post_blob_upload.go
Normal file
@ -0,0 +1,42 @@
|
||||
// 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 oci
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/harness/gitness/registry/app/pkg/commons"
|
||||
)
|
||||
|
||||
func (h *Handler) InitiateUploadBlob(w http.ResponseWriter, r *http.Request) {
|
||||
info, err := h.getRegistryInfo(r, false)
|
||||
if err != nil {
|
||||
handleErrors(r.Context(), []error{err}, w)
|
||||
return
|
||||
}
|
||||
fromParam := r.FormValue("from")
|
||||
fromParamParts := strings.Split(fromParam, "/")
|
||||
fromRepo := ""
|
||||
if len(fromParamParts) > 1 {
|
||||
fromRepo = fromParamParts[1]
|
||||
}
|
||||
mountDigest := r.FormValue("mount")
|
||||
headers, errs := h.Controller.InitiateUploadBlob(r.Context(), info, fromRepo, mountDigest)
|
||||
if commons.IsEmpty(errs) {
|
||||
headers.WriteToResponse(w)
|
||||
}
|
||||
handleErrors(r.Context(), errs, w)
|
||||
}
|
41
registry/app/api/handler/oci/put_blob_upload.go
Normal file
41
registry/app/api/handler/oci/put_blob_upload.go
Normal file
@ -0,0 +1,41 @@
|
||||
// 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 oci
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/registry/app/pkg/commons"
|
||||
)
|
||||
|
||||
func (h *Handler) CompleteBlobUpload(w http.ResponseWriter, r *http.Request) {
|
||||
info, err := h.getRegistryInfo(r, false)
|
||||
if err != nil {
|
||||
handleErrors(r.Context(), []error{err}, w)
|
||||
return
|
||||
}
|
||||
stateToken := r.FormValue("_state")
|
||||
length := r.ContentLength
|
||||
if length > 0 {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, length)
|
||||
}
|
||||
|
||||
headers, errs := h.Controller.CompleteBlobUpload(r.Context(), info, r.Body, r.ContentLength, stateToken)
|
||||
|
||||
if commons.IsEmpty(errs) {
|
||||
headers.WriteToResponse(w)
|
||||
}
|
||||
handleErrors(r.Context(), errs, w)
|
||||
}
|
50
registry/app/api/handler/oci/put_manifest.go
Normal file
50
registry/app/api/handler/oci/put_manifest.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 oci
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/registry/app/pkg/commons"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
maxManifestBodySize = 4 * 1024 * 1024
|
||||
)
|
||||
|
||||
// PutManifest validates and stores a manifest in the registry.
|
||||
func (h *Handler) PutManifest(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
info, err := h.getRegistryInfo(r, false)
|
||||
if err != nil {
|
||||
handleErrors(r.Context(), []error{err}, w)
|
||||
return
|
||||
}
|
||||
mediaType := r.Header.Get("Content-Type")
|
||||
length := r.ContentLength
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxManifestBodySize)
|
||||
|
||||
headers, errs := h.Controller.PutManifest(r.Context(), info, mediaType, r.Body, length)
|
||||
if !commons.IsEmpty(errs) {
|
||||
log.Ctx(ctx).Error().Errs("Failed to Put manifest", errs).Msg("Failed to Put manifest")
|
||||
}
|
||||
|
||||
if commons.IsEmpty(errs) {
|
||||
headers.WriteToResponse(w)
|
||||
}
|
||||
handleErrors(ctx, errs, w)
|
||||
}
|
63
registry/app/api/handler/swagger/swagger.go
Normal file
63
registry/app/api/handler/swagger/swagger.go
Normal file
@ -0,0 +1,63 @@
|
||||
// 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 swagger
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
httpswagger "github.com/swaggo/http-swagger"
|
||||
)
|
||||
|
||||
type Handler interface {
|
||||
http.Handler
|
||||
}
|
||||
|
||||
func GetSwaggerHandler(base string) Handler {
|
||||
r := chi.NewRouter()
|
||||
// Generate OpenAPI specification
|
||||
swagger, err := artifact.GetSwagger()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Serve the OpenAPI specification JSON
|
||||
r.Get(
|
||||
fmt.Sprintf("%s/swagger.json", base), http.HandlerFunc(
|
||||
func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
jsonResponse, _ := json.Marshal(swagger)
|
||||
_, err2 := w.Write(jsonResponse)
|
||||
if err2 != nil {
|
||||
log.Error().Err(err2).Msg("Failed to write response")
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
r.Get(
|
||||
fmt.Sprintf("%s/swagger/*", base), httpswagger.Handler(
|
||||
httpswagger.URL(fmt.Sprintf("%s/swagger.json", base)), // The url pointing to API definition
|
||||
),
|
||||
)
|
||||
|
||||
return r
|
||||
}
|
121
registry/app/api/middleware/auth.go
Normal file
121
registry/app/api/middleware/auth.go
Normal file
@ -0,0 +1,121 @@
|
||||
// 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 middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/harness/gitness/app/api/render"
|
||||
"github.com/harness/gitness/app/api/request"
|
||||
"github.com/harness/gitness/app/auth"
|
||||
"github.com/harness/gitness/app/jwt"
|
||||
"github.com/harness/gitness/registry/app/api/handler/oci"
|
||||
registryauth "github.com/harness/gitness/registry/app/auth"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func OciCheckAuth(url string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
if session.Principal == auth.AnonymousPrincipal {
|
||||
scope := getScope(r)
|
||||
returnUnauthorised(ctx, w, url, scope)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// BlockNonOciSourceToken blocks any request that doesn't have AccessPermissionMetadata.
|
||||
func BlockNonOciSourceToken(url string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
if session, oks := request.AuthSessionFrom(ctx); oks {
|
||||
if metadata, okt := session.Metadata.(*auth.AccessPermissionMetadata); !okt ||
|
||||
metadata.AccessPermissions.Source != jwt.OciSource {
|
||||
log.Ctx(ctx).Warn().
|
||||
Msg("blocking request - non OCI source tokens are not allowed for usage with oci endpoints")
|
||||
|
||||
scope := getScope(r)
|
||||
returnUnauthorised(ctx, w, url, scope)
|
||||
return
|
||||
}
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckAuth() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
session, _ := request.AuthSessionFrom(ctx)
|
||||
if session.Principal == auth.AnonymousPrincipal {
|
||||
render.Unauthorized(ctx, w)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func getRefsFromName(name string) (spaceRef, repoRef string) {
|
||||
name = strings.Trim(name, "/")
|
||||
refs := strings.Split(name, "/")
|
||||
spaceRef = refs[0]
|
||||
repoRef = refs[1]
|
||||
return
|
||||
}
|
||||
|
||||
func getScope(r *http.Request) string {
|
||||
var scope string
|
||||
path := r.URL.Path
|
||||
if path != "/v2/" && path != "/v2/token" {
|
||||
paramMap := oci.GetQueryParamMap(r.URL.Query())
|
||||
rootIdentifier, registryIdentifier, _, _, _, _ := oci.ExtractPathVars(path, paramMap)
|
||||
var access []registryauth.Access
|
||||
access = registryauth.AppendAccess(access, r.Method, rootIdentifier, registryIdentifier)
|
||||
if fromRepo := r.FormValue("from"); fromRepo != "" {
|
||||
space, repoName := getRefsFromName(fromRepo)
|
||||
access = registryauth.AppendAccess(access, http.MethodGet, space, repoName)
|
||||
}
|
||||
scope = registryauth.NewAccessSet(access...).ScopeParam()
|
||||
}
|
||||
return scope
|
||||
}
|
||||
|
||||
func returnUnauthorised(ctx context.Context, w http.ResponseWriter, url string, scope string) {
|
||||
header := fmt.Sprintf(`Bearer realm="%s", service="gitness-registry"`, url)
|
||||
if scope != "" {
|
||||
header = fmt.Sprintf(`%s, scope="%s"`, header, scope)
|
||||
}
|
||||
w.Header().Set("WWW-Authenticate", header)
|
||||
render.Unauthorized(ctx, w)
|
||||
}
|
1607
registry/app/api/openapi/api.yaml
Normal file
1607
registry/app/api/openapi/api.yaml
Normal file
File diff suppressed because it is too large
Load Diff
3803
registry/app/api/openapi/contracts/artifact/services.gen.go
Normal file
3803
registry/app/api/openapi/contracts/artifact/services.gen.go
Normal file
File diff suppressed because it is too large
Load Diff
977
registry/app/api/openapi/contracts/artifact/types.gen.go
Normal file
977
registry/app/api/openapi/contracts/artifact/types.gen.go
Normal file
@ -0,0 +1,977 @@
|
||||
// Package artifact provides primitives to interact with the openapi HTTP API.
|
||||
//
|
||||
// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.1.0 DO NOT EDIT.
|
||||
package artifact
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/oapi-codegen/runtime"
|
||||
)
|
||||
|
||||
// Defines values for AuthType.
|
||||
const (
|
||||
AuthTypeAnonymous AuthType = "Anonymous"
|
||||
AuthTypeUserPassword AuthType = "UserPassword"
|
||||
)
|
||||
|
||||
// Defines values for ClientSetupStepType.
|
||||
const (
|
||||
ClientSetupStepTypeGenerateToken ClientSetupStepType = "GenerateToken"
|
||||
ClientSetupStepTypeStatic ClientSetupStepType = "Static"
|
||||
)
|
||||
|
||||
// Defines values for PackageType.
|
||||
const (
|
||||
PackageTypeDOCKER PackageType = "DOCKER"
|
||||
PackageTypeGENERIC PackageType = "GENERIC"
|
||||
PackageTypeHELM PackageType = "HELM"
|
||||
PackageTypeMAVEN PackageType = "MAVEN"
|
||||
)
|
||||
|
||||
// Defines values for RegistryType.
|
||||
const (
|
||||
RegistryTypeUPSTREAM RegistryType = "UPSTREAM"
|
||||
RegistryTypeVIRTUAL RegistryType = "VIRTUAL"
|
||||
)
|
||||
|
||||
// Defines values for Status.
|
||||
const (
|
||||
StatusERROR Status = "ERROR"
|
||||
StatusFAILURE Status = "FAILURE"
|
||||
StatusSUCCESS Status = "SUCCESS"
|
||||
)
|
||||
|
||||
// Defines values for UpstreamConfigSource.
|
||||
const (
|
||||
UpstreamConfigSourceCustom UpstreamConfigSource = "Custom"
|
||||
UpstreamConfigSourceDockerhub UpstreamConfigSource = "Dockerhub"
|
||||
)
|
||||
|
||||
// Defines values for RegistryTypeParam.
|
||||
const (
|
||||
UPSTREAM RegistryTypeParam = "UPSTREAM"
|
||||
VIRTUAL RegistryTypeParam = "VIRTUAL"
|
||||
)
|
||||
|
||||
// Defines values for GetAllRegistriesParamsType.
|
||||
const (
|
||||
GetAllRegistriesParamsTypeUPSTREAM GetAllRegistriesParamsType = "UPSTREAM"
|
||||
GetAllRegistriesParamsTypeVIRTUAL GetAllRegistriesParamsType = "VIRTUAL"
|
||||
)
|
||||
|
||||
// Anonymous defines model for Anonymous.
|
||||
type Anonymous interface{}
|
||||
|
||||
// ArtifactLabelRequest defines model for ArtifactLabelRequest.
|
||||
type ArtifactLabelRequest struct {
|
||||
Labels []string `json:"labels"`
|
||||
}
|
||||
|
||||
// ArtifactMetadata Artifact Metadata
|
||||
type ArtifactMetadata struct {
|
||||
DownloadsCount *int64 `json:"downloadsCount,omitempty"`
|
||||
Labels *[]string `json:"labels,omitempty"`
|
||||
LastModified *string `json:"lastModified,omitempty"`
|
||||
LatestVersion string `json:"latestVersion"`
|
||||
Name string `json:"name"`
|
||||
|
||||
// PackageType refers to package
|
||||
PackageType *PackageType `json:"packageType,omitempty"`
|
||||
RegistryIdentifier string `json:"registryIdentifier"`
|
||||
RegistryPath string `json:"registryPath"`
|
||||
}
|
||||
|
||||
// ArtifactStats Harness Artifact Stats
|
||||
type ArtifactStats struct {
|
||||
DownloadCount *int64 `json:"downloadCount,omitempty"`
|
||||
DownloadSize *int64 `json:"downloadSize,omitempty"`
|
||||
TotalStorageSize *int64 `json:"totalStorageSize,omitempty"`
|
||||
UploadSize *int64 `json:"uploadSize,omitempty"`
|
||||
}
|
||||
|
||||
// ArtifactSummary Harness Artifact Summary
|
||||
type ArtifactSummary struct {
|
||||
CreatedAt *string `json:"createdAt,omitempty"`
|
||||
DownloadsCount *int64 `json:"downloadsCount,omitempty"`
|
||||
ImageName string `json:"imageName"`
|
||||
Labels *[]string `json:"labels,omitempty"`
|
||||
ModifiedAt *string `json:"modifiedAt,omitempty"`
|
||||
|
||||
// PackageType refers to package
|
||||
PackageType PackageType `json:"packageType"`
|
||||
}
|
||||
|
||||
// ArtifactVersionMetadata Artifact Version Metadata
|
||||
type ArtifactVersionMetadata struct {
|
||||
DigestCount *int64 `json:"digestCount,omitempty"`
|
||||
DownloadsCount *int64 `json:"downloadsCount,omitempty"`
|
||||
IslatestVersion *bool `json:"islatestVersion,omitempty"`
|
||||
LastModified *string `json:"lastModified,omitempty"`
|
||||
Name string `json:"name"`
|
||||
|
||||
// PackageType refers to package
|
||||
PackageType *PackageType `json:"packageType,omitempty"`
|
||||
PullCommand *string `json:"pullCommand,omitempty"`
|
||||
RegistryIdentifier string `json:"registryIdentifier"`
|
||||
RegistryPath string `json:"registryPath"`
|
||||
Size *string `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
// ArtifactVersionSummary Docker Artifact Version Summary
|
||||
type ArtifactVersionSummary struct {
|
||||
ImageName string `json:"imageName"`
|
||||
IsLatestVersion *bool `json:"isLatestVersion,omitempty"`
|
||||
|
||||
// PackageType refers to package
|
||||
PackageType PackageType `json:"packageType"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// AuthType Authentication type
|
||||
type AuthType string
|
||||
|
||||
// CleanupPolicy Cleanup Policy for Harness Artifact Registries
|
||||
type CleanupPolicy struct {
|
||||
ExpireDays *int `json:"expireDays,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
PackagePrefix *[]string `json:"packagePrefix,omitempty"`
|
||||
VersionPrefix *[]string `json:"versionPrefix,omitempty"`
|
||||
}
|
||||
|
||||
// ClientSetupDetails Client Setup Details
|
||||
type ClientSetupDetails struct {
|
||||
MainHeader string `json:"mainHeader"`
|
||||
SecHeader string `json:"secHeader"`
|
||||
Sections []ClientSetupSection `json:"sections"`
|
||||
}
|
||||
|
||||
// ClientSetupSection Client Setup Section
|
||||
type ClientSetupSection struct {
|
||||
Header *string `json:"header,omitempty"`
|
||||
Steps *[]ClientSetupStep `json:"steps,omitempty"`
|
||||
}
|
||||
|
||||
// ClientSetupStep Client Setup Step
|
||||
type ClientSetupStep struct {
|
||||
Commands *[]string `json:"commands,omitempty"`
|
||||
Header *string `json:"header,omitempty"`
|
||||
|
||||
// Type ClientSetupStepType type
|
||||
Type *ClientSetupStepType `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// ClientSetupStepType ClientSetupStepType type
|
||||
type ClientSetupStepType string
|
||||
|
||||
// DockerArtifactDetail Docker Artifact Detail
|
||||
type DockerArtifactDetail struct {
|
||||
CreatedAt *string `json:"createdAt,omitempty"`
|
||||
DownloadsCount *int64 `json:"downloadsCount,omitempty"`
|
||||
ImageName string `json:"imageName"`
|
||||
IsLatestVersion *bool `json:"isLatestVersion,omitempty"`
|
||||
ModifiedAt *string `json:"modifiedAt,omitempty"`
|
||||
|
||||
// PackageType refers to package
|
||||
PackageType PackageType `json:"packageType"`
|
||||
PullCommand *string `json:"pullCommand,omitempty"`
|
||||
RegistryPath string `json:"registryPath"`
|
||||
Size *string `json:"size,omitempty"`
|
||||
Url string `json:"url"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// DockerArtifactManifest Docker Artifact Manifest
|
||||
type DockerArtifactManifest struct {
|
||||
Manifest string `json:"manifest"`
|
||||
}
|
||||
|
||||
// DockerLayerEntry Harness Artifact Layers
|
||||
type DockerLayerEntry struct {
|
||||
Command string `json:"command"`
|
||||
Size *string `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
// DockerLayersSummary Harness Layers Summary
|
||||
type DockerLayersSummary struct {
|
||||
Digest string `json:"digest"`
|
||||
Layers *[]DockerLayerEntry `json:"layers,omitempty"`
|
||||
OsArch *string `json:"osArch,omitempty"`
|
||||
}
|
||||
|
||||
// DockerManifestDetails Harness Artifact Layers
|
||||
type DockerManifestDetails struct {
|
||||
CreatedAt *string `json:"createdAt,omitempty"`
|
||||
Digest string `json:"digest"`
|
||||
OsArch string `json:"osArch"`
|
||||
Size *string `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
// DockerManifests Harness Manifests
|
||||
type DockerManifests struct {
|
||||
ImageName string `json:"imageName"`
|
||||
IsLatestVersion *bool `json:"isLatestVersion,omitempty"`
|
||||
Manifests *[]DockerManifestDetails `json:"manifests,omitempty"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// Error defines model for Error.
|
||||
type Error struct {
|
||||
// Code The http error code
|
||||
Code string `json:"code"`
|
||||
|
||||
// Details Additional details about the error
|
||||
Details *map[string]interface{} `json:"details,omitempty"`
|
||||
|
||||
// Message The reason the request failed
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// HelmArtifactDetail Helm Artifact Detail
|
||||
type HelmArtifactDetail struct {
|
||||
Artifact *string `json:"artifact,omitempty"`
|
||||
CreatedAt *string `json:"createdAt,omitempty"`
|
||||
DownloadsCount *int64 `json:"downloadsCount,omitempty"`
|
||||
IsLatestVersion *bool `json:"isLatestVersion,omitempty"`
|
||||
ModifiedAt *string `json:"modifiedAt,omitempty"`
|
||||
|
||||
// PackageType refers to package
|
||||
PackageType PackageType `json:"packageType"`
|
||||
PullCommand *string `json:"pullCommand,omitempty"`
|
||||
RegistryPath string `json:"registryPath"`
|
||||
Size *string `json:"size,omitempty"`
|
||||
Url string `json:"url"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// HelmArtifactManifest Helm Artifact Manifest
|
||||
type HelmArtifactManifest struct {
|
||||
Manifest string `json:"manifest"`
|
||||
}
|
||||
|
||||
// ListArtifact A list of Artifacts
|
||||
type ListArtifact struct {
|
||||
// Artifacts A list of Artifact
|
||||
Artifacts []ArtifactMetadata `json:"artifacts"`
|
||||
|
||||
// ItemCount The total number of items
|
||||
ItemCount *int64 `json:"itemCount,omitempty"`
|
||||
|
||||
// PageCount The total number of pages
|
||||
PageCount *int64 `json:"pageCount,omitempty"`
|
||||
|
||||
// PageIndex The current page
|
||||
PageIndex *int64 `json:"pageIndex,omitempty"`
|
||||
|
||||
// PageSize The number of items per page
|
||||
PageSize *int `json:"pageSize,omitempty"`
|
||||
}
|
||||
|
||||
// ListArtifactLabel A list of Harness Artifact Labels
|
||||
type ListArtifactLabel struct {
|
||||
// ItemCount The total number of items
|
||||
ItemCount *int64 `json:"itemCount,omitempty"`
|
||||
Labels []string `json:"labels"`
|
||||
|
||||
// PageCount The total number of pages
|
||||
PageCount *int64 `json:"pageCount,omitempty"`
|
||||
|
||||
// PageIndex The current page
|
||||
PageIndex *int64 `json:"pageIndex,omitempty"`
|
||||
|
||||
// PageSize The number of items per page
|
||||
PageSize *int `json:"pageSize,omitempty"`
|
||||
}
|
||||
|
||||
// ListArtifactVersion A list of Artifact versions
|
||||
type ListArtifactVersion struct {
|
||||
// ArtifactVersions A list of Artifact versions
|
||||
ArtifactVersions *[]ArtifactVersionMetadata `json:"artifactVersions,omitempty"`
|
||||
|
||||
// ItemCount The total number of items
|
||||
ItemCount *int64 `json:"itemCount,omitempty"`
|
||||
|
||||
// PageCount The total number of pages
|
||||
PageCount *int64 `json:"pageCount,omitempty"`
|
||||
|
||||
// PageIndex The current page
|
||||
PageIndex *int64 `json:"pageIndex,omitempty"`
|
||||
|
||||
// PageSize The number of items per page
|
||||
PageSize *int `json:"pageSize,omitempty"`
|
||||
}
|
||||
|
||||
// ListRegistry A list of Harness Artifact Registries
|
||||
type ListRegistry struct {
|
||||
// ItemCount The total number of items
|
||||
ItemCount *int64 `json:"itemCount,omitempty"`
|
||||
|
||||
// PageCount The total number of pages
|
||||
PageCount *int64 `json:"pageCount,omitempty"`
|
||||
|
||||
// PageIndex The current page
|
||||
PageIndex *int64 `json:"pageIndex,omitempty"`
|
||||
|
||||
// PageSize The number of items per page
|
||||
PageSize *int `json:"pageSize,omitempty"`
|
||||
|
||||
// Registries A list of Harness Artifact Registries
|
||||
Registries []RegistryMetadata `json:"registries"`
|
||||
}
|
||||
|
||||
// PackageType refers to package
|
||||
type PackageType string
|
||||
|
||||
// Registry Harness Artifact Registry
|
||||
type Registry struct {
|
||||
AllowedPattern *[]string `json:"allowedPattern,omitempty"`
|
||||
BlockedPattern *[]string `json:"blockedPattern,omitempty"`
|
||||
CleanupPolicy *[]CleanupPolicy `json:"cleanupPolicy,omitempty"`
|
||||
|
||||
// Config SubConfig specific for Virtual or Upstream Registry
|
||||
Config *RegistryConfig `json:"config,omitempty"`
|
||||
CreatedAt *string `json:"createdAt,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Identifier string `json:"identifier"`
|
||||
Labels *[]string `json:"labels,omitempty"`
|
||||
ModifiedAt *string `json:"modifiedAt,omitempty"`
|
||||
|
||||
// PackageType refers to package
|
||||
PackageType PackageType `json:"packageType"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
// RegistryConfig SubConfig specific for Virtual or Upstream Registry
|
||||
type RegistryConfig struct {
|
||||
// Type refers to type of registry i.e virtual or upstream
|
||||
Type RegistryType `json:"type"`
|
||||
union json.RawMessage
|
||||
}
|
||||
|
||||
// RegistryMetadata Harness Artifact Registry Metadata
|
||||
type RegistryMetadata struct {
|
||||
ArtifactsCount *int64 `json:"artifactsCount,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
DownloadsCount *int64 `json:"downloadsCount,omitempty"`
|
||||
Identifier string `json:"identifier"`
|
||||
Labels *[]string `json:"labels,omitempty"`
|
||||
LastModified *string `json:"lastModified,omitempty"`
|
||||
|
||||
// PackageType refers to package
|
||||
PackageType PackageType `json:"packageType"`
|
||||
Path *string `json:"path,omitempty"`
|
||||
RegistrySize *string `json:"registrySize,omitempty"`
|
||||
|
||||
// Type refers to type of registry i.e virtual or upstream
|
||||
Type RegistryType `json:"type"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
// RegistryRequest defines model for RegistryRequest.
|
||||
type RegistryRequest struct {
|
||||
AllowedPattern *[]string `json:"allowedPattern,omitempty"`
|
||||
BlockedPattern *[]string `json:"blockedPattern,omitempty"`
|
||||
CleanupPolicy *[]CleanupPolicy `json:"cleanupPolicy,omitempty"`
|
||||
|
||||
// Config SubConfig specific for Virtual or Upstream Registry
|
||||
Config *RegistryConfig `json:"config,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Identifier string `json:"identifier"`
|
||||
Labels *[]string `json:"labels,omitempty"`
|
||||
|
||||
// PackageType refers to package
|
||||
PackageType PackageType `json:"packageType"`
|
||||
ParentRef *string `json:"parentRef,omitempty"`
|
||||
}
|
||||
|
||||
// RegistryType refers to type of registry i.e virtual or upstream
|
||||
type RegistryType string
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
type Status string
|
||||
|
||||
// UpstreamConfig Configuration for Harness Artifact UpstreamProxies
|
||||
type UpstreamConfig struct {
|
||||
Auth *UpstreamConfig_Auth `json:"auth,omitempty"`
|
||||
|
||||
// AuthType Authentication type
|
||||
AuthType AuthType `json:"authType"`
|
||||
Source *UpstreamConfigSource `json:"source,omitempty"`
|
||||
Url *string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// UpstreamConfig_Auth defines model for UpstreamConfig.Auth.
|
||||
type UpstreamConfig_Auth struct {
|
||||
union json.RawMessage
|
||||
}
|
||||
|
||||
// UpstreamConfigSource defines model for UpstreamConfig.Source.
|
||||
type UpstreamConfigSource string
|
||||
|
||||
// UserPassword defines model for UserPassword.
|
||||
type UserPassword struct {
|
||||
SecretIdentifier *string `json:"secretIdentifier,omitempty"`
|
||||
SecretSpaceId *int `json:"secretSpaceId,omitempty"`
|
||||
UserName string `json:"userName"`
|
||||
}
|
||||
|
||||
// VirtualConfig Configuration for Harness Virtual Artifact Registries
|
||||
type VirtualConfig struct {
|
||||
UpstreamProxies *[]string `json:"upstreamProxies,omitempty"`
|
||||
}
|
||||
|
||||
// LabelsParam defines model for LabelsParam.
|
||||
type LabelsParam []string
|
||||
|
||||
// RegistryIdentifierParam defines model for RegistryIdentifierParam.
|
||||
type RegistryIdentifierParam string
|
||||
|
||||
// RegistryTypeParam defines model for RegistryTypeParam.
|
||||
type RegistryTypeParam string
|
||||
|
||||
// ArtifactParam defines model for artifactParam.
|
||||
type ArtifactParam string
|
||||
|
||||
// ArtifactPathParam defines model for artifactPathParam.
|
||||
type ArtifactPathParam string
|
||||
|
||||
// DigestParam defines model for digestParam.
|
||||
type DigestParam string
|
||||
|
||||
// FromDateParam defines model for fromDateParam.
|
||||
type FromDateParam string
|
||||
|
||||
// PackageTypeParam defines model for packageTypeParam.
|
||||
type PackageTypeParam []string
|
||||
|
||||
// PageNumber defines model for pageNumber.
|
||||
type PageNumber int64
|
||||
|
||||
// PageSize defines model for pageSize.
|
||||
type PageSize int64
|
||||
|
||||
// RegistryRefPathParam defines model for registryRefPathParam.
|
||||
type RegistryRefPathParam string
|
||||
|
||||
// SearchTerm defines model for searchTerm.
|
||||
type SearchTerm string
|
||||
|
||||
// SortField defines model for sortField.
|
||||
type SortField string
|
||||
|
||||
// SortOrder defines model for sortOrder.
|
||||
type SortOrder string
|
||||
|
||||
// SpaceRefPathParam defines model for spaceRefPathParam.
|
||||
type SpaceRefPathParam string
|
||||
|
||||
// ToDateParam defines model for toDateParam.
|
||||
type ToDateParam string
|
||||
|
||||
// VersionParam defines model for versionParam.
|
||||
type VersionParam string
|
||||
|
||||
// VersionPathParam defines model for versionPathParam.
|
||||
type VersionPathParam string
|
||||
|
||||
// ArtifactLabelResponse defines model for ArtifactLabelResponse.
|
||||
type ArtifactLabelResponse struct {
|
||||
// Data Harness Artifact Summary
|
||||
Data ArtifactSummary `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// ArtifactStatsResponse defines model for ArtifactStatsResponse.
|
||||
type ArtifactStatsResponse struct {
|
||||
// Data Harness Artifact Stats
|
||||
Data ArtifactStats `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// ArtifactSummaryResponse defines model for ArtifactSummaryResponse.
|
||||
type ArtifactSummaryResponse struct {
|
||||
// Data Harness Artifact Summary
|
||||
Data ArtifactSummary `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// ArtifactVersionSummaryResponse defines model for ArtifactVersionSummaryResponse.
|
||||
type ArtifactVersionSummaryResponse struct {
|
||||
// Data Docker Artifact Version Summary
|
||||
Data ArtifactVersionSummary `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// BadRequest defines model for BadRequest.
|
||||
type BadRequest Error
|
||||
|
||||
// ClientSetupDetailsResponse defines model for ClientSetupDetailsResponse.
|
||||
type ClientSetupDetailsResponse struct {
|
||||
// Data Client Setup Details
|
||||
Data ClientSetupDetails `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// DockerArtifactDetailResponse defines model for DockerArtifactDetailResponse.
|
||||
type DockerArtifactDetailResponse struct {
|
||||
// Data Docker Artifact Detail
|
||||
Data DockerArtifactDetail `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// DockerArtifactManifestResponse defines model for DockerArtifactManifestResponse.
|
||||
type DockerArtifactManifestResponse struct {
|
||||
// Data Docker Artifact Manifest
|
||||
Data DockerArtifactManifest `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// DockerLayersResponse defines model for DockerLayersResponse.
|
||||
type DockerLayersResponse struct {
|
||||
// Data Harness Layers Summary
|
||||
Data DockerLayersSummary `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// DockerManifestsResponse defines model for DockerManifestsResponse.
|
||||
type DockerManifestsResponse struct {
|
||||
// Data Harness Manifests
|
||||
Data DockerManifests `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// HelmArtifactDetailResponse defines model for HelmArtifactDetailResponse.
|
||||
type HelmArtifactDetailResponse struct {
|
||||
// Data Helm Artifact Detail
|
||||
Data HelmArtifactDetail `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// HelmArtifactManifestResponse defines model for HelmArtifactManifestResponse.
|
||||
type HelmArtifactManifestResponse struct {
|
||||
// Data Helm Artifact Manifest
|
||||
Data HelmArtifactManifest `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// InternalServerError defines model for InternalServerError.
|
||||
type InternalServerError Error
|
||||
|
||||
// ListArtifactLabelResponse defines model for ListArtifactLabelResponse.
|
||||
type ListArtifactLabelResponse struct {
|
||||
// Data A list of Harness Artifact Labels
|
||||
Data ListArtifactLabel `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// ListArtifactResponse defines model for ListArtifactResponse.
|
||||
type ListArtifactResponse struct {
|
||||
// Data A list of Artifacts
|
||||
Data ListArtifact `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// ListArtifactVersionResponse defines model for ListArtifactVersionResponse.
|
||||
type ListArtifactVersionResponse struct {
|
||||
// Data A list of Artifact versions
|
||||
Data ListArtifactVersion `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// ListRegistryResponse defines model for ListRegistryResponse.
|
||||
type ListRegistryResponse struct {
|
||||
// Data A list of Harness Artifact Registries
|
||||
Data ListRegistry `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// NotFound defines model for NotFound.
|
||||
type NotFound Error
|
||||
|
||||
// RegistryResponse defines model for RegistryResponse.
|
||||
type RegistryResponse struct {
|
||||
// Data Harness Artifact Registry
|
||||
Data Registry `json:"data"`
|
||||
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// Success defines model for Success.
|
||||
type Success struct {
|
||||
// Status Indicates if the request was successful or not
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
// Unauthenticated defines model for Unauthenticated.
|
||||
type Unauthenticated Error
|
||||
|
||||
// Unauthorized defines model for Unauthorized.
|
||||
type Unauthorized Error
|
||||
|
||||
// ListArtifactLabelsParams defines parameters for ListArtifactLabels.
|
||||
type ListArtifactLabelsParams struct {
|
||||
// Page Current page number
|
||||
Page *PageNumber `form:"page,omitempty" json:"page,omitempty"`
|
||||
|
||||
// Size Number of items per page
|
||||
Size *PageSize `form:"size,omitempty" json:"size,omitempty"`
|
||||
|
||||
// SearchTerm search Term.
|
||||
SearchTerm *SearchTerm `form:"search_term,omitempty" json:"search_term,omitempty"`
|
||||
}
|
||||
|
||||
// GetArtifactStatsForRegistryParams defines parameters for GetArtifactStatsForRegistry.
|
||||
type GetArtifactStatsForRegistryParams struct {
|
||||
// From Date. Format - MM/DD/YYYY
|
||||
From *FromDateParam `form:"from,omitempty" json:"from,omitempty"`
|
||||
|
||||
// To Date. Format - MM/DD/YYYY
|
||||
To *ToDateParam `form:"to,omitempty" json:"to,omitempty"`
|
||||
}
|
||||
|
||||
// GetArtifactStatsParams defines parameters for GetArtifactStats.
|
||||
type GetArtifactStatsParams struct {
|
||||
// From Date. Format - MM/DD/YYYY
|
||||
From *FromDateParam `form:"from,omitempty" json:"from,omitempty"`
|
||||
|
||||
// To Date. Format - MM/DD/YYYY
|
||||
To *ToDateParam `form:"to,omitempty" json:"to,omitempty"`
|
||||
}
|
||||
|
||||
// GetDockerArtifactDetailsParams defines parameters for GetDockerArtifactDetails.
|
||||
type GetDockerArtifactDetailsParams struct {
|
||||
// Digest Digest.
|
||||
Digest DigestParam `form:"digest" json:"digest"`
|
||||
}
|
||||
|
||||
// GetDockerArtifactLayersParams defines parameters for GetDockerArtifactLayers.
|
||||
type GetDockerArtifactLayersParams struct {
|
||||
// Digest Digest.
|
||||
Digest DigestParam `form:"digest" json:"digest"`
|
||||
}
|
||||
|
||||
// GetDockerArtifactManifestParams defines parameters for GetDockerArtifactManifest.
|
||||
type GetDockerArtifactManifestParams struct {
|
||||
// Digest Digest.
|
||||
Digest DigestParam `form:"digest" json:"digest"`
|
||||
}
|
||||
|
||||
// GetAllArtifactVersionsParams defines parameters for GetAllArtifactVersions.
|
||||
type GetAllArtifactVersionsParams struct {
|
||||
// Page Current page number
|
||||
Page *PageNumber `form:"page,omitempty" json:"page,omitempty"`
|
||||
|
||||
// Size Number of items per page
|
||||
Size *PageSize `form:"size,omitempty" json:"size,omitempty"`
|
||||
|
||||
// SortOrder sortOrder
|
||||
SortOrder *SortOrder `form:"sort_order,omitempty" json:"sort_order,omitempty"`
|
||||
|
||||
// SortField sortField
|
||||
SortField *SortField `form:"sort_field,omitempty" json:"sort_field,omitempty"`
|
||||
|
||||
// SearchTerm search Term.
|
||||
SearchTerm *SearchTerm `form:"search_term,omitempty" json:"search_term,omitempty"`
|
||||
}
|
||||
|
||||
// GetClientSetupDetailsParams defines parameters for GetClientSetupDetails.
|
||||
type GetClientSetupDetailsParams struct {
|
||||
// Artifact Artifat
|
||||
Artifact *ArtifactParam `form:"artifact,omitempty" json:"artifact,omitempty"`
|
||||
|
||||
// Version Version
|
||||
Version *VersionParam `form:"version,omitempty" json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// GetArtifactStatsForSpaceParams defines parameters for GetArtifactStatsForSpace.
|
||||
type GetArtifactStatsForSpaceParams struct {
|
||||
// From Date. Format - MM/DD/YYYY
|
||||
From *FromDateParam `form:"from,omitempty" json:"from,omitempty"`
|
||||
|
||||
// To Date. Format - MM/DD/YYYY
|
||||
To *ToDateParam `form:"to,omitempty" json:"to,omitempty"`
|
||||
}
|
||||
|
||||
// GetAllArtifactsParams defines parameters for GetAllArtifacts.
|
||||
type GetAllArtifactsParams struct {
|
||||
// Label Label.
|
||||
Label *LabelsParam `form:"label,omitempty" json:"label,omitempty"`
|
||||
|
||||
// PackageType Registry Package Type
|
||||
PackageType *PackageTypeParam `form:"package_type,omitempty" json:"package_type,omitempty"`
|
||||
|
||||
// RegIdentifier Registry Identifier
|
||||
RegIdentifier *RegistryIdentifierParam `form:"reg_identifier,omitempty" json:"reg_identifier,omitempty"`
|
||||
|
||||
// Page Current page number
|
||||
Page *PageNumber `form:"page,omitempty" json:"page,omitempty"`
|
||||
|
||||
// Size Number of items per page
|
||||
Size *PageSize `form:"size,omitempty" json:"size,omitempty"`
|
||||
|
||||
// SortOrder sortOrder
|
||||
SortOrder *SortOrder `form:"sort_order,omitempty" json:"sort_order,omitempty"`
|
||||
|
||||
// SortField sortField
|
||||
SortField *SortField `form:"sort_field,omitempty" json:"sort_field,omitempty"`
|
||||
|
||||
// SearchTerm search Term.
|
||||
SearchTerm *SearchTerm `form:"search_term,omitempty" json:"search_term,omitempty"`
|
||||
}
|
||||
|
||||
// GetAllRegistriesParams defines parameters for GetAllRegistries.
|
||||
type GetAllRegistriesParams struct {
|
||||
// PackageType Registry Package Type
|
||||
PackageType *PackageTypeParam `form:"package_type,omitempty" json:"package_type,omitempty"`
|
||||
|
||||
// Type Registry Type
|
||||
Type *GetAllRegistriesParamsType `form:"type,omitempty" json:"type,omitempty"`
|
||||
|
||||
// Page Current page number
|
||||
Page *PageNumber `form:"page,omitempty" json:"page,omitempty"`
|
||||
|
||||
// Size Number of items per page
|
||||
Size *PageSize `form:"size,omitempty" json:"size,omitempty"`
|
||||
|
||||
// SortOrder sortOrder
|
||||
SortOrder *SortOrder `form:"sort_order,omitempty" json:"sort_order,omitempty"`
|
||||
|
||||
// SortField sortField
|
||||
SortField *SortField `form:"sort_field,omitempty" json:"sort_field,omitempty"`
|
||||
|
||||
// SearchTerm search Term.
|
||||
SearchTerm *SearchTerm `form:"search_term,omitempty" json:"search_term,omitempty"`
|
||||
}
|
||||
|
||||
// GetAllRegistriesParamsType defines parameters for GetAllRegistries.
|
||||
type GetAllRegistriesParamsType string
|
||||
|
||||
// CreateRegistryJSONRequestBody defines body for CreateRegistry for application/json ContentType.
|
||||
type CreateRegistryJSONRequestBody RegistryRequest
|
||||
|
||||
// ModifyRegistryJSONRequestBody defines body for ModifyRegistry for application/json ContentType.
|
||||
type ModifyRegistryJSONRequestBody RegistryRequest
|
||||
|
||||
// UpdateArtifactLabelsJSONRequestBody defines body for UpdateArtifactLabels for application/json ContentType.
|
||||
type UpdateArtifactLabelsJSONRequestBody ArtifactLabelRequest
|
||||
|
||||
// AsVirtualConfig returns the union data inside the RegistryConfig as a VirtualConfig
|
||||
func (t RegistryConfig) AsVirtualConfig() (VirtualConfig, error) {
|
||||
var body VirtualConfig
|
||||
err := json.Unmarshal(t.union, &body)
|
||||
return body, err
|
||||
}
|
||||
|
||||
// FromVirtualConfig overwrites any union data inside the RegistryConfig as the provided VirtualConfig
|
||||
func (t *RegistryConfig) FromVirtualConfig(v VirtualConfig) error {
|
||||
t.Type = "VIRTUAL"
|
||||
|
||||
b, err := json.Marshal(v)
|
||||
t.union = b
|
||||
return err
|
||||
}
|
||||
|
||||
// MergeVirtualConfig performs a merge with any union data inside the RegistryConfig, using the provided VirtualConfig
|
||||
func (t *RegistryConfig) MergeVirtualConfig(v VirtualConfig) error {
|
||||
t.Type = "VIRTUAL"
|
||||
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
merged, err := runtime.JSONMerge(t.union, b)
|
||||
t.union = merged
|
||||
return err
|
||||
}
|
||||
|
||||
// AsUpstreamConfig returns the union data inside the RegistryConfig as a UpstreamConfig
|
||||
func (t RegistryConfig) AsUpstreamConfig() (UpstreamConfig, error) {
|
||||
var body UpstreamConfig
|
||||
err := json.Unmarshal(t.union, &body)
|
||||
return body, err
|
||||
}
|
||||
|
||||
// FromUpstreamConfig overwrites any union data inside the RegistryConfig as the provided UpstreamConfig
|
||||
func (t *RegistryConfig) FromUpstreamConfig(v UpstreamConfig) error {
|
||||
t.Type = "UPSTREAM"
|
||||
|
||||
b, err := json.Marshal(v)
|
||||
t.union = b
|
||||
return err
|
||||
}
|
||||
|
||||
// MergeUpstreamConfig performs a merge with any union data inside the RegistryConfig, using the provided UpstreamConfig
|
||||
func (t *RegistryConfig) MergeUpstreamConfig(v UpstreamConfig) error {
|
||||
t.Type = "UPSTREAM"
|
||||
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
merged, err := runtime.JSONMerge(t.union, b)
|
||||
t.union = merged
|
||||
return err
|
||||
}
|
||||
|
||||
func (t RegistryConfig) Discriminator() (string, error) {
|
||||
var discriminator struct {
|
||||
Discriminator string `json:"type"`
|
||||
}
|
||||
err := json.Unmarshal(t.union, &discriminator)
|
||||
return discriminator.Discriminator, err
|
||||
}
|
||||
|
||||
func (t RegistryConfig) ValueByDiscriminator() (interface{}, error) {
|
||||
discriminator, err := t.Discriminator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch discriminator {
|
||||
case "UPSTREAM":
|
||||
return t.AsUpstreamConfig()
|
||||
case "VIRTUAL":
|
||||
return t.AsVirtualConfig()
|
||||
default:
|
||||
return nil, errors.New("unknown discriminator value: " + discriminator)
|
||||
}
|
||||
}
|
||||
|
||||
func (t RegistryConfig) MarshalJSON() ([]byte, error) {
|
||||
b, err := t.union.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
object := make(map[string]json.RawMessage)
|
||||
if t.union != nil {
|
||||
err = json.Unmarshal(b, &object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
object["type"], err = json.Marshal(t.Type)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling 'type': %w", err)
|
||||
}
|
||||
|
||||
b, err = json.Marshal(object)
|
||||
return b, err
|
||||
}
|
||||
|
||||
func (t *RegistryConfig) UnmarshalJSON(b []byte) error {
|
||||
err := t.union.UnmarshalJSON(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
object := make(map[string]json.RawMessage)
|
||||
err = json.Unmarshal(b, &object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if raw, found := object["type"]; found {
|
||||
err = json.Unmarshal(raw, &t.Type)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading 'type': %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// AsUserPassword returns the union data inside the UpstreamConfig_Auth as a UserPassword
|
||||
func (t UpstreamConfig_Auth) AsUserPassword() (UserPassword, error) {
|
||||
var body UserPassword
|
||||
err := json.Unmarshal(t.union, &body)
|
||||
return body, err
|
||||
}
|
||||
|
||||
// FromUserPassword overwrites any union data inside the UpstreamConfig_Auth as the provided UserPassword
|
||||
func (t *UpstreamConfig_Auth) FromUserPassword(v UserPassword) error {
|
||||
b, err := json.Marshal(v)
|
||||
t.union = b
|
||||
return err
|
||||
}
|
||||
|
||||
// MergeUserPassword performs a merge with any union data inside the UpstreamConfig_Auth, using the provided UserPassword
|
||||
func (t *UpstreamConfig_Auth) MergeUserPassword(v UserPassword) error {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
merged, err := runtime.JSONMerge(t.union, b)
|
||||
t.union = merged
|
||||
return err
|
||||
}
|
||||
|
||||
// AsAnonymous returns the union data inside the UpstreamConfig_Auth as a Anonymous
|
||||
func (t UpstreamConfig_Auth) AsAnonymous() (Anonymous, error) {
|
||||
var body Anonymous
|
||||
err := json.Unmarshal(t.union, &body)
|
||||
return body, err
|
||||
}
|
||||
|
||||
// FromAnonymous overwrites any union data inside the UpstreamConfig_Auth as the provided Anonymous
|
||||
func (t *UpstreamConfig_Auth) FromAnonymous(v Anonymous) error {
|
||||
b, err := json.Marshal(v)
|
||||
t.union = b
|
||||
return err
|
||||
}
|
||||
|
||||
// MergeAnonymous performs a merge with any union data inside the UpstreamConfig_Auth, using the provided Anonymous
|
||||
func (t *UpstreamConfig_Auth) MergeAnonymous(v Anonymous) error {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
merged, err := runtime.JSONMerge(t.union, b)
|
||||
t.union = merged
|
||||
return err
|
||||
}
|
||||
|
||||
func (t UpstreamConfig_Auth) MarshalJSON() ([]byte, error) {
|
||||
b, err := t.union.MarshalJSON()
|
||||
return b, err
|
||||
}
|
||||
|
||||
func (t *UpstreamConfig_Auth) UnmarshalJSON(b []byte) error {
|
||||
err := t.union.UnmarshalJSON(b)
|
||||
return err
|
||||
}
|
76
registry/app/api/router/harness/route.go
Normal file
76
registry/app/api/router/harness/route.go
Normal file
@ -0,0 +1,76 @@
|
||||
// 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 harness
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
middlewareauthn "github.com/harness/gitness/app/api/middleware/authn"
|
||||
"github.com/harness/gitness/app/auth/authn"
|
||||
"github.com/harness/gitness/app/auth/authz"
|
||||
corestore "github.com/harness/gitness/app/store"
|
||||
urlprovider "github.com/harness/gitness/app/url"
|
||||
"github.com/harness/gitness/audit"
|
||||
"github.com/harness/gitness/registry/app/api/controller/metadata"
|
||||
"github.com/harness/gitness/registry/app/api/middleware"
|
||||
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
|
||||
storagedriver "github.com/harness/gitness/registry/app/driver"
|
||||
"github.com/harness/gitness/registry/app/store"
|
||||
"github.com/harness/gitness/store/database/dbtx"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type APIHandler interface {
|
||||
http.Handler
|
||||
}
|
||||
|
||||
func NewAPIHandler(
|
||||
repoDao store.RegistryRepository,
|
||||
upstreamproxyDao store.UpstreamProxyConfigRepository,
|
||||
tagDao store.TagRepository,
|
||||
manifestDao store.ManifestRepository,
|
||||
cleanupPolicyDao store.CleanupPolicyRepository,
|
||||
artifactDao store.ArtifactRepository,
|
||||
driver storagedriver.StorageDriver,
|
||||
baseURL string,
|
||||
spaceStore corestore.SpaceStore,
|
||||
tx dbtx.Transactor,
|
||||
authenticator authn.Authenticator,
|
||||
urlProvider urlprovider.Provider,
|
||||
authorizer authz.Authorizer,
|
||||
auditService audit.Service,
|
||||
) APIHandler {
|
||||
r := chi.NewRouter()
|
||||
r.Use(audit.Middleware())
|
||||
r.Use(middlewareauthn.Attempt(authenticator))
|
||||
r.Use(middleware.CheckAuth())
|
||||
apiController := metadata.NewAPIController(
|
||||
repoDao,
|
||||
upstreamproxyDao,
|
||||
tagDao,
|
||||
manifestDao,
|
||||
cleanupPolicyDao,
|
||||
artifactDao,
|
||||
driver,
|
||||
spaceStore,
|
||||
tx,
|
||||
urlProvider,
|
||||
authorizer,
|
||||
auditService,
|
||||
)
|
||||
handler := artifact.NewStrictHandler(apiController, []artifact.StrictMiddlewareFunc{})
|
||||
return artifact.HandlerFromMuxWithBaseURL(handler, r, baseURL)
|
||||
}
|
151
registry/app/api/router/oci/route.go
Normal file
151
registry/app/api/router/oci/route.go
Normal file
@ -0,0 +1,151 @@
|
||||
// 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 oci
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
middlewareauthn "github.com/harness/gitness/app/api/middleware/authn"
|
||||
"github.com/harness/gitness/registry/app/api/handler/oci"
|
||||
"github.com/harness/gitness/registry/app/api/middleware"
|
||||
"github.com/harness/gitness/registry/app/common"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type RouteType string
|
||||
|
||||
const (
|
||||
Manifests RouteType = "manifests" // /v2/:registry/:image/manifests/:reference.
|
||||
Blobs RouteType = "blobs" // /v2/:registry/:image/blobs/:digest.
|
||||
BlobsUploadsSession RouteType = "blob-uploads-session" // /v2/:registry/:image/blobs/uploads/:session_id.
|
||||
Tags RouteType = "tags" // /v2/:registry/:image/tags/list.
|
||||
Referrers RouteType = "referrers" // /v2/:registry/:image/referrers/:digest.
|
||||
Invalid RouteType = "invalid" // Invalid route.
|
||||
// Add other route types here.
|
||||
)
|
||||
|
||||
func GetRouteTypeV2(url string) RouteType {
|
||||
url = strings.Trim(url, "/")
|
||||
segments := strings.Split(url, "/")
|
||||
if len(segments) < 4 {
|
||||
return Invalid
|
||||
}
|
||||
|
||||
typ := segments[len(segments)-2]
|
||||
|
||||
switch typ {
|
||||
case "manifests":
|
||||
return Manifests
|
||||
case "blobs":
|
||||
if segments[len(segments)-1] == "uploads" {
|
||||
return BlobsUploadsSession
|
||||
}
|
||||
return Blobs
|
||||
case "uploads":
|
||||
return BlobsUploadsSession
|
||||
case "tags":
|
||||
return Tags
|
||||
case "referrers":
|
||||
return Referrers
|
||||
}
|
||||
return Invalid
|
||||
}
|
||||
|
||||
type HandlerBlock struct {
|
||||
Handler2 http.HandlerFunc
|
||||
RemoteSupport bool
|
||||
}
|
||||
|
||||
func NewHandlerBlock2(h2 http.HandlerFunc, remoteSupport bool) HandlerBlock {
|
||||
return HandlerBlock{
|
||||
Handler2: h2,
|
||||
RemoteSupport: remoteSupport,
|
||||
}
|
||||
}
|
||||
|
||||
type RegistryOCIHandler interface {
|
||||
http.Handler
|
||||
}
|
||||
|
||||
func NewOCIHandler(handlerV2 *oci.Handler) RegistryOCIHandler {
|
||||
r := chi.NewRouter()
|
||||
|
||||
var routeHandlers = map[RouteType]map[string]HandlerBlock{
|
||||
Manifests: {
|
||||
http.MethodGet: NewHandlerBlock2(handlerV2.GetManifest, true),
|
||||
http.MethodHead: NewHandlerBlock2(handlerV2.HeadManifest, true),
|
||||
http.MethodPut: NewHandlerBlock2(handlerV2.PutManifest, false),
|
||||
http.MethodDelete: NewHandlerBlock2(handlerV2.DeleteManifest, false),
|
||||
},
|
||||
Blobs: {
|
||||
http.MethodGet: NewHandlerBlock2(handlerV2.GetBlob, true),
|
||||
http.MethodHead: NewHandlerBlock2(handlerV2.HeadBlob, false),
|
||||
http.MethodDelete: NewHandlerBlock2(handlerV2.DeleteBlob, false),
|
||||
},
|
||||
BlobsUploadsSession: {
|
||||
http.MethodGet: NewHandlerBlock2(handlerV2.GetUploadBlobStatus, false),
|
||||
http.MethodPatch: NewHandlerBlock2(handlerV2.PatchBlobUpload, false),
|
||||
http.MethodPut: NewHandlerBlock2(handlerV2.CompleteBlobUpload, false),
|
||||
http.MethodDelete: NewHandlerBlock2(handlerV2.CancelBlobUpload, false),
|
||||
http.MethodPost: NewHandlerBlock2(handlerV2.InitiateUploadBlob, false),
|
||||
},
|
||||
Tags: {
|
||||
http.MethodGet: NewHandlerBlock2(handlerV2.GetTags, false),
|
||||
},
|
||||
Referrers: {
|
||||
http.MethodGet: NewHandlerBlock2(handlerV2.GetReferrers, false),
|
||||
},
|
||||
}
|
||||
r.Route("/v2", func(r chi.Router) {
|
||||
r.Use(middlewareauthn.Attempt(handlerV2.Authenticator))
|
||||
r.Get("/token", func(w http.ResponseWriter, req *http.Request) {
|
||||
handlerV2.GetToken(w, req)
|
||||
})
|
||||
|
||||
r.With(middleware.OciCheckAuth(common.GenerateOciTokenURL(handlerV2.URLProvider.RegistryURL()))).
|
||||
Get("/", func(w http.ResponseWriter, req *http.Request) {
|
||||
handlerV2.APIBase(w, req)
|
||||
})
|
||||
r.Route("/{registryIdentifier}", func(r chi.Router) {
|
||||
r.Use(middleware.OciCheckAuth(common.GenerateOciTokenURL(handlerV2.URLProvider.RegistryURL())))
|
||||
r.Use(middleware.BlockNonOciSourceToken(handlerV2.URLProvider.RegistryURL()))
|
||||
r.Handle("/*", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
path := req.URL.Path
|
||||
methodType := req.Method
|
||||
|
||||
requestType := GetRouteTypeV2(path)
|
||||
|
||||
if _, ok := routeHandlers[requestType]; ok {
|
||||
if h, ok2 := routeHandlers[requestType][methodType]; ok2 {
|
||||
h.Handler2(w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, err := w.Write([]byte("Invalid route"))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to write response")
|
||||
return
|
||||
}
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
51
registry/app/api/router/registry_router.go
Normal file
51
registry/app/api/router/registry_router.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 router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const RegistryMount = "/api/v1/registry"
|
||||
const APIMount = "/api"
|
||||
|
||||
type RegistryRouter struct {
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
func NewRegistryRouter(handler http.Handler) *RegistryRouter {
|
||||
return &RegistryRouter{handler: handler}
|
||||
}
|
||||
|
||||
func (r *RegistryRouter) Handle(w http.ResponseWriter, req *http.Request) {
|
||||
r.handler.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
func (r *RegistryRouter) IsEligibleTraffic(req *http.Request) bool {
|
||||
if strings.HasPrefix(req.URL.Path, RegistryMount) || strings.HasPrefix(req.URL.Path, "/v2/") ||
|
||||
strings.HasPrefix(req.URL.Path, "/registry/") ||
|
||||
(strings.HasPrefix(req.URL.Path, APIMount+"/v1/spaces/") &&
|
||||
(strings.HasSuffix(req.URL.Path, "/artifacts") ||
|
||||
strings.HasSuffix(req.URL.Path, "/registries"))) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *RegistryRouter) Name() string {
|
||||
return "registry"
|
||||
}
|
54
registry/app/api/router/router.go
Normal file
54
registry/app/api/router/router.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 router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/harness/gitness/app/api/middleware/address"
|
||||
"github.com/harness/gitness/app/api/middleware/logging"
|
||||
"github.com/harness/gitness/registry/app/api/handler/swagger"
|
||||
"github.com/harness/gitness/registry/app/api/router/harness"
|
||||
"github.com/harness/gitness/registry/app/api/router/oci"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/rs/zerolog/hlog"
|
||||
)
|
||||
|
||||
type AppRouter interface {
|
||||
http.Handler
|
||||
}
|
||||
|
||||
func GetAppRouter(
|
||||
ociHandler oci.RegistryOCIHandler,
|
||||
appHandler harness.APIHandler,
|
||||
baseURL string,
|
||||
) AppRouter {
|
||||
r := chi.NewRouter()
|
||||
r.Use(hlog.URLHandler("http.url"))
|
||||
r.Use(hlog.MethodHandler("http.method"))
|
||||
r.Use(logging.HLogRequestIDHandler())
|
||||
r.Use(logging.HLogAccessLogHandler())
|
||||
r.Use(address.Handler("", ""))
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Handle(fmt.Sprintf("%s/*", baseURL), appHandler)
|
||||
r.Handle("/v2/*", ociHandler)
|
||||
|
||||
r.Handle("/registry/swagger*", swagger.GetSwaggerHandler("/registry"))
|
||||
})
|
||||
return r
|
||||
}
|
78
registry/app/api/router/wire.go
Normal file
78
registry/app/api/router/wire.go
Normal file
@ -0,0 +1,78 @@
|
||||
// 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 router
|
||||
|
||||
import (
|
||||
"github.com/harness/gitness/app/auth/authn"
|
||||
"github.com/harness/gitness/app/auth/authz"
|
||||
"github.com/harness/gitness/app/config"
|
||||
corestore "github.com/harness/gitness/app/store"
|
||||
urlprovider "github.com/harness/gitness/app/url"
|
||||
"github.com/harness/gitness/audit"
|
||||
hoci "github.com/harness/gitness/registry/app/api/handler/oci"
|
||||
"github.com/harness/gitness/registry/app/api/router/harness"
|
||||
"github.com/harness/gitness/registry/app/api/router/oci"
|
||||
storagedriver "github.com/harness/gitness/registry/app/driver"
|
||||
"github.com/harness/gitness/registry/app/store"
|
||||
"github.com/harness/gitness/store/database/dbtx"
|
||||
|
||||
"github.com/google/wire"
|
||||
)
|
||||
|
||||
func AppRouterProvider(
|
||||
ocir oci.RegistryOCIHandler,
|
||||
appHandler harness.APIHandler,
|
||||
) AppRouter {
|
||||
return GetAppRouter(ocir, appHandler, config.APIURL)
|
||||
}
|
||||
|
||||
func APIHandlerProvider(
|
||||
repoDao store.RegistryRepository,
|
||||
upstreamproxyDao store.UpstreamProxyConfigRepository,
|
||||
tagDao store.TagRepository,
|
||||
manifestDao store.ManifestRepository,
|
||||
cleanupPolicyDao store.CleanupPolicyRepository,
|
||||
artifactDao store.ArtifactRepository,
|
||||
driver storagedriver.StorageDriver,
|
||||
spaceStore corestore.SpaceStore,
|
||||
tx dbtx.Transactor,
|
||||
authenticator authn.Authenticator,
|
||||
urlProvider urlprovider.Provider,
|
||||
authorizer authz.Authorizer,
|
||||
auditService audit.Service,
|
||||
) harness.APIHandler {
|
||||
return harness.NewAPIHandler(
|
||||
repoDao,
|
||||
upstreamproxyDao,
|
||||
tagDao,
|
||||
manifestDao,
|
||||
cleanupPolicyDao,
|
||||
artifactDao,
|
||||
driver,
|
||||
config.APIURL,
|
||||
spaceStore,
|
||||
tx,
|
||||
authenticator,
|
||||
urlProvider,
|
||||
authorizer,
|
||||
auditService,
|
||||
)
|
||||
}
|
||||
|
||||
func OCIHandlerProvider(handlerV2 *hoci.Handler) oci.RegistryOCIHandler {
|
||||
return oci.NewOCIHandler(handlerV2)
|
||||
}
|
||||
|
||||
var WireSet = wire.NewSet(APIHandlerProvider, OCIHandlerProvider, AppRouterProvider)
|
85
registry/app/api/wire.go
Normal file
85
registry/app/api/wire.go
Normal file
@ -0,0 +1,85 @@
|
||||
// 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 api
|
||||
|
||||
import (
|
||||
usercontroller "github.com/harness/gitness/app/api/controller/user"
|
||||
"github.com/harness/gitness/app/auth/authn"
|
||||
"github.com/harness/gitness/app/auth/authz"
|
||||
corestore "github.com/harness/gitness/app/store"
|
||||
urlprovider "github.com/harness/gitness/app/url"
|
||||
ocihandler "github.com/harness/gitness/registry/app/api/handler/oci"
|
||||
"github.com/harness/gitness/registry/app/api/router"
|
||||
storagedriver "github.com/harness/gitness/registry/app/driver"
|
||||
"github.com/harness/gitness/registry/app/driver/factory"
|
||||
"github.com/harness/gitness/registry/app/driver/filesystem"
|
||||
"github.com/harness/gitness/registry/app/driver/s3-aws"
|
||||
"github.com/harness/gitness/registry/app/pkg"
|
||||
"github.com/harness/gitness/registry/app/pkg/docker"
|
||||
"github.com/harness/gitness/registry/app/store/database"
|
||||
"github.com/harness/gitness/registry/config"
|
||||
"github.com/harness/gitness/types"
|
||||
|
||||
"github.com/google/wire"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type RegistryApp struct {
|
||||
Config *types.Config
|
||||
|
||||
AppRouter router.AppRouter
|
||||
}
|
||||
|
||||
func BlobStorageProvider(c *types.Config) (storagedriver.StorageDriver, error) {
|
||||
var d storagedriver.StorageDriver
|
||||
var err error
|
||||
|
||||
if c.Registry.Storage.StorageType == "filesystem" {
|
||||
filesystem.Register()
|
||||
d, err = factory.Create("filesystem", config.GetFilesystemParams(c))
|
||||
if err != nil {
|
||||
log.Fatal().Stack().Err(err).Msgf("")
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
s3.Register()
|
||||
d, err = factory.Create("s3aws", config.GetS3StorageParameters(c))
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("failed to init s3 Blob storage ")
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return d, err
|
||||
}
|
||||
|
||||
func NewHandlerProvider(controller *docker.Controller, spaceStore corestore.SpaceStore,
|
||||
tokenStore corestore.TokenStore, userCtrl *usercontroller.Controller, authenticator authn.Authenticator,
|
||||
urlProvider urlprovider.Provider, authorizer authz.Authorizer) *ocihandler.Handler {
|
||||
return ocihandler.NewHandler(controller, spaceStore, tokenStore, userCtrl, authenticator, urlProvider, authorizer)
|
||||
}
|
||||
|
||||
var WireSet = wire.NewSet(
|
||||
BlobStorageProvider,
|
||||
NewHandlerProvider,
|
||||
database.WireSet,
|
||||
pkg.WireSet,
|
||||
docker.WireSet,
|
||||
router.WireSet,
|
||||
)
|
||||
|
||||
func Wire(_ *types.Config) (RegistryApp, error) {
|
||||
wire.Build(WireSet, wire.Struct(new(RegistryApp), "*"))
|
||||
return RegistryApp{}, nil
|
||||
}
|
169
registry/app/auth/auth.go
Normal file
169
registry/app/auth/auth.go
Normal file
@ -0,0 +1,169 @@
|
||||
// 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 auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AccessSet maps a typed, named resource to
|
||||
// a set of actions requested or authorized.
|
||||
type AccessSet map[Resource]ActionSet
|
||||
|
||||
// NewAccessSet constructs an accessSet from
|
||||
// a variable number of auth.Access items.
|
||||
func NewAccessSet(accessItems ...Access) AccessSet {
|
||||
accessSet := make(AccessSet, len(accessItems))
|
||||
|
||||
for _, access := range accessItems {
|
||||
resource := Resource{
|
||||
Type: access.Type,
|
||||
Name: access.Name,
|
||||
Space: access.Space,
|
||||
}
|
||||
|
||||
set, exists := accessSet[resource]
|
||||
if !exists {
|
||||
set = NewActionSet()
|
||||
accessSet[resource] = set
|
||||
}
|
||||
|
||||
set.Add(access.Action)
|
||||
}
|
||||
|
||||
return accessSet
|
||||
}
|
||||
|
||||
// Contains returns whether or not the given access is in this accessSet.
|
||||
func (s AccessSet) Contains(access Access) bool {
|
||||
actionSet, ok := s[access.Resource]
|
||||
if ok {
|
||||
return actionSet.contains(access.Action)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ScopeParam returns a collection of scopes which can
|
||||
// be used for a WWW-Authenticate challenge parameter.
|
||||
func (s AccessSet) ScopeParam() string {
|
||||
scopes := make([]string, 0, len(s))
|
||||
|
||||
for resource, actionSet := range s {
|
||||
actions := strings.Join(actionSet.keys(), ",")
|
||||
resourceName := strings.Join([]string{resource.Space, resource.Name}, "/")
|
||||
scopes = append(scopes, strings.Join([]string{resource.Type, resourceName, actions}, ":"))
|
||||
}
|
||||
|
||||
return strings.Join(scopes, " ")
|
||||
}
|
||||
|
||||
// Resource describes a resource by type and name.
|
||||
type Resource struct {
|
||||
Type string
|
||||
Name string
|
||||
Space string
|
||||
}
|
||||
|
||||
// Access describes a specific action that is
|
||||
// requested or allowed for a given resource.
|
||||
type Access struct {
|
||||
Resource
|
||||
Action string
|
||||
}
|
||||
|
||||
func AppendAccess(records []Access, method string, rootIdentifier string, repo string) []Access {
|
||||
resource := Resource{
|
||||
Type: "repository",
|
||||
Name: repo,
|
||||
Space: rootIdentifier,
|
||||
}
|
||||
|
||||
switch method {
|
||||
case http.MethodGet, http.MethodHead:
|
||||
records = append(records,
|
||||
Access{
|
||||
Resource: resource,
|
||||
Action: "pull",
|
||||
})
|
||||
case http.MethodPost, http.MethodPut, http.MethodPatch:
|
||||
records = append(records,
|
||||
Access{
|
||||
Resource: resource,
|
||||
Action: "pull",
|
||||
},
|
||||
Access{
|
||||
Resource: resource,
|
||||
Action: "push",
|
||||
})
|
||||
case http.MethodDelete:
|
||||
records = append(records,
|
||||
Access{
|
||||
Resource: resource,
|
||||
Action: "delete",
|
||||
})
|
||||
}
|
||||
return records
|
||||
}
|
||||
|
||||
// ActionSet is a special type of stringSet.
|
||||
type ActionSet struct {
|
||||
stringSet
|
||||
}
|
||||
|
||||
func NewActionSet(actions ...string) ActionSet {
|
||||
return ActionSet{newStringSet(actions...)}
|
||||
}
|
||||
|
||||
// Contains calls StringSet.Contains() for
|
||||
// either "*" or the given action string.
|
||||
func (s ActionSet) Contains(action string) bool {
|
||||
return s.stringSet.contains("*") || s.stringSet.contains(action)
|
||||
}
|
||||
|
||||
// StringSet is a useful type for looking up strings.
|
||||
type stringSet map[string]struct{}
|
||||
|
||||
// NewStringSet creates a new StringSet with the given strings.
|
||||
func newStringSet(keys ...string) stringSet {
|
||||
ss := make(stringSet, len(keys))
|
||||
ss.Add(keys...)
|
||||
return ss
|
||||
}
|
||||
|
||||
// Add inserts the given keys into this StringSet.
|
||||
func (ss stringSet) Add(keys ...string) {
|
||||
for _, key := range keys {
|
||||
ss[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns whether the given key is in this StringSet.
|
||||
func (ss stringSet) contains(key string) bool {
|
||||
_, ok := ss[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Keys returns a slice of all keys in this StringSet.
|
||||
func (ss stringSet) keys() []string {
|
||||
keys := make([]string, 0, len(ss))
|
||||
|
||||
for key := range ss {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
26
registry/app/common/http/modifier/modifier.go
Normal file
26
registry/app/common/http/modifier/modifier.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Source: https://github.com/goharbor/harbor
|
||||
|
||||
// Copyright 2016 Project Harbor Authors
|
||||
//
|
||||
// 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 modifier
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Modifier modifies request.
|
||||
type Modifier interface {
|
||||
Modify(*http.Request) error
|
||||
}
|
84
registry/app/common/http/tls.go
Normal file
84
registry/app/common/http/tls.go
Normal file
@ -0,0 +1,84 @@
|
||||
// Source: https://github.com/goharbor/harbor
|
||||
|
||||
// Copyright 2016 Project Harbor Authors
|
||||
//
|
||||
// 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 http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// Internal TLS ENV.
|
||||
internalTLSEnable = "GITNESS_INTERNAL_TLS_ENABLED"
|
||||
internalVerifyClientCert = "GITNESS_INTERNAL_VERIFY_CLIENT_CERT"
|
||||
internalTLSKeyPath = "GITNESS_INTERNAL_TLS_KEY_PATH"
|
||||
internalTLSCertPath = "GITNESS_INTERNAL_TLS_CERT_PATH"
|
||||
)
|
||||
|
||||
// InternalTLSEnabled returns true if internal TLS enabled.
|
||||
func InternalTLSEnabled() bool {
|
||||
return strings.ToLower(os.Getenv(internalTLSEnable)) == "true"
|
||||
}
|
||||
|
||||
// InternalEnableVerifyClientCert returns true if mTLS enabled.
|
||||
func InternalEnableVerifyClientCert() bool {
|
||||
return strings.ToLower(os.Getenv(internalVerifyClientCert)) == "true"
|
||||
}
|
||||
|
||||
// GetInternalCertPair used to get internal cert and key pair from environment.
|
||||
func GetInternalCertPair() (tls.Certificate, error) {
|
||||
crtPath := os.Getenv(internalTLSCertPath)
|
||||
keyPath := os.Getenv(internalTLSKeyPath)
|
||||
return tls.LoadX509KeyPair(crtPath, keyPath)
|
||||
}
|
||||
|
||||
// GetInternalTLSConfig return a tls.Config for internal https communicate.
|
||||
func GetInternalTLSConfig() (*tls.Config, error) {
|
||||
// genrate key pair
|
||||
cert, err := GetInternalCertPair()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("internal TLS enabled but can't get cert file %w", err)
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewServerTLSConfig returns a modern tls config,
|
||||
// refer to https://blog.cloudflare.com/exposing-go-on-the-internet/
|
||||
func NewServerTLSConfig() *tls.Config {
|
||||
return &tls.Config{
|
||||
PreferServerCipherSuites: true,
|
||||
CurvePreferences: []tls.CurveID{
|
||||
tls.CurveP256,
|
||||
tls.X25519,
|
||||
},
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
},
|
||||
}
|
||||
}
|
137
registry/app/common/http/transport.go
Normal file
137
registry/app/common/http/transport.go
Normal file
@ -0,0 +1,137 @@
|
||||
// Source: https://github.com/goharbor/harbor
|
||||
|
||||
// Copyright 2016 Project Harbor Authors
|
||||
//
|
||||
// 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 http
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// InsecureTransport used to get the insecure http Transport.
|
||||
InsecureTransport = iota
|
||||
// SecureTransport used to get the external secure http Transport.
|
||||
SecureTransport
|
||||
)
|
||||
|
||||
var (
|
||||
secureHTTPTransport http.RoundTripper
|
||||
insecureHTTPTransport http.RoundTripper
|
||||
)
|
||||
|
||||
func init() {
|
||||
insecureHTTPTransport = NewTransport(WithInsecureSkipVerify(true))
|
||||
if InternalTLSEnabled() {
|
||||
secureHTTPTransport = NewTransport(WithInternalTLSConfig())
|
||||
} else {
|
||||
secureHTTPTransport = NewTransport()
|
||||
}
|
||||
}
|
||||
|
||||
// Use this instead of Default Transport in library because it sets ForceAttemptHTTP2 to true
|
||||
// And that options introduced in go 1.13 will cause the https requests hang forever in replication environment.
|
||||
func newDefaultTransport() *http.Transport {
|
||||
return &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
TLSClientConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
},
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// WithInternalTLSConfig returns a TransportOption that configures the transport to use the internal TLS configuration.
|
||||
func WithInternalTLSConfig() func(*http.Transport) {
|
||||
return func(tr *http.Transport) {
|
||||
tlsConfig, err := GetInternalTLSConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tr.TLSClientConfig = tlsConfig
|
||||
}
|
||||
}
|
||||
|
||||
// WithInsecureSkipVerify returns a TransportOption that configures the
|
||||
// transport to skip verification of the server's certificate.
|
||||
func WithInsecureSkipVerify(skipVerify bool) func(*http.Transport) {
|
||||
return func(tr *http.Transport) {
|
||||
tr.TLSClientConfig.InsecureSkipVerify = skipVerify
|
||||
}
|
||||
}
|
||||
|
||||
// WithMaxIdleConns returns a TransportOption that configures the
|
||||
// transport to use the specified number of idle connections per host.
|
||||
func WithMaxIdleConns(maxIdleConns int) func(*http.Transport) {
|
||||
return func(tr *http.Transport) {
|
||||
tr.MaxIdleConns = maxIdleConns
|
||||
}
|
||||
}
|
||||
|
||||
// WithIdleconnectionTimeout returns a TransportOption that configures
|
||||
// the transport to use the specified idle connection timeout.
|
||||
func WithIdleconnectionTimeout(idleConnectionTimeout time.Duration) func(*http.Transport) {
|
||||
return func(tr *http.Transport) {
|
||||
tr.IdleConnTimeout = idleConnectionTimeout
|
||||
}
|
||||
}
|
||||
|
||||
// NewTransport returns a new http.Transport with the specified options.
|
||||
func NewTransport(opts ...func(*http.Transport)) http.RoundTripper {
|
||||
tr := newDefaultTransport()
|
||||
for _, opt := range opts {
|
||||
opt(tr)
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
// TransportConfig is the configuration for http transport.
|
||||
type TransportConfig struct {
|
||||
Insecure bool
|
||||
}
|
||||
|
||||
// TransportOption is the option for http transport.
|
||||
type TransportOption func(*TransportConfig)
|
||||
|
||||
// WithInsecure returns a TransportOption that configures the
|
||||
// transport to skip verification of the server's certificate.
|
||||
func WithInsecure(skipVerify bool) TransportOption {
|
||||
return func(cfg *TransportConfig) {
|
||||
cfg.Insecure = skipVerify
|
||||
}
|
||||
}
|
||||
|
||||
// GetHTTPTransport returns HttpTransport based on insecure configuration.
|
||||
func GetHTTPTransport(opts ...TransportOption) http.RoundTripper {
|
||||
cfg := &TransportConfig{}
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
if cfg.Insecure {
|
||||
return insecureHTTPTransport
|
||||
}
|
||||
return secureHTTPTransport
|
||||
}
|
30
registry/app/common/http/transport_test.go
Normal file
30
registry/app/common/http/transport_test.go
Normal file
@ -0,0 +1,30 @@
|
||||
// Source: https://github.com/goharbor/harbor
|
||||
|
||||
// Copyright 2016 Project Harbor Authors
|
||||
//
|
||||
// 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 http
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetHTTPTransport(t *testing.T) {
|
||||
transport := GetHTTPTransport()
|
||||
assert.Equal(t, secureHTTPTransport, transport, "Transport should be secure")
|
||||
transport = GetHTTPTransport(WithInsecure(true))
|
||||
assert.Equal(t, insecureHTTPTransport, transport, "Transport should be insecure")
|
||||
}
|
24
registry/app/common/lib/authorizer.go
Normal file
24
registry/app/common/lib/authorizer.go
Normal file
@ -0,0 +1,24 @@
|
||||
// Source: https://github.com/goharbor/harbor
|
||||
|
||||
// Copyright 2016 Project Harbor Authors
|
||||
//
|
||||
// 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 lib
|
||||
|
||||
import (
|
||||
"github.com/harness/gitness/registry/app/common/http/modifier"
|
||||
)
|
||||
|
||||
// Authorizer authorizes the request.
|
||||
type Authorizer modifier.Modifier
|
71
registry/app/common/lib/errors/const.go
Normal file
71
registry/app/common/lib/errors/const.go
Normal file
@ -0,0 +1,71 @@
|
||||
// Source: https://github.com/goharbor/harbor
|
||||
|
||||
// Copyright 2016 Project Harbor Authors
|
||||
//
|
||||
// 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 errors
|
||||
|
||||
const (
|
||||
// NotFoundCode is code for the error of no object found.
|
||||
NotFoundCode = "NOT_FOUND"
|
||||
// ConflictCode ...
|
||||
ConflictCode = "CONFLICT"
|
||||
// UnAuthorizedCode ...
|
||||
UnAuthorizedCode = "UNAUTHORIZED"
|
||||
// BadRequestCode ...
|
||||
BadRequestCode = "BAD_REQUEST"
|
||||
// ForbiddenCode ...
|
||||
ForbiddenCode = "FORBIDDEN"
|
||||
// MethodNotAllowedCode ...
|
||||
MethodNotAllowedCode = "METHOD_NOT_ALLOWED"
|
||||
// RateLimitCode.
|
||||
RateLimitCode = "TOO_MANY_REQUEST"
|
||||
// PreconditionCode ...
|
||||
PreconditionCode = "PRECONDITION"
|
||||
// GeneralCode ...
|
||||
GeneralCode = "UNKNOWN"
|
||||
// DENIED it's used by middleware(readonly, vul and content trust)
|
||||
// and returned to docker client to index the request is denied.
|
||||
DENIED = "DENIED"
|
||||
// PROJECTPOLICYVIOLATION ...
|
||||
PROJECTPOLICYVIOLATION = "PROJECTPOLICYVIOLATION"
|
||||
// ViolateForeignKeyConstraintCode is the error code for violating foreign key constraint error.
|
||||
ViolateForeignKeyConstraintCode = "VIOLATE_FOREIGN_KEY_CONSTRAINT"
|
||||
// DIGESTINVALID ...
|
||||
DIGESTINVALID = "DIGEST_INVALID"
|
||||
// MANIFESTINVALID ...
|
||||
MANIFESTINVALID = "MANIFEST_INVALID"
|
||||
// UNSUPPORTED is for digest UNSUPPORTED error.
|
||||
UNSUPPORTED = "UNSUPPORTED"
|
||||
)
|
||||
|
||||
// NotFoundError is error for the case of object not found.
|
||||
func NotFoundError(err error) *Error {
|
||||
return New("resource not found").WithCode(NotFoundCode).WithCause(err)
|
||||
}
|
||||
|
||||
// UnknownError ...
|
||||
func UnknownError(err error) *Error {
|
||||
return New("unknown").WithCode(GeneralCode).WithCause(err)
|
||||
}
|
||||
|
||||
// IsNotFoundErr returns true when the error is NotFoundError.
|
||||
func IsNotFoundErr(err error) bool {
|
||||
return IsErr(err, NotFoundCode)
|
||||
}
|
||||
|
||||
// IsRateLimitError checks whether the err chains contains rate limit error.
|
||||
func IsRateLimitError(err error) bool {
|
||||
return IsErr(err, RateLimitCode)
|
||||
}
|
207
registry/app/common/lib/errors/errors.go
Normal file
207
registry/app/common/lib/errors/errors.go
Normal file
@ -0,0 +1,207 @@
|
||||
// Source: https://github.com/goharbor/harbor
|
||||
|
||||
// Copyright 2016 Project Harbor Authors
|
||||
//
|
||||
// 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 errors
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var (
|
||||
// As alias function of `errors.As`.
|
||||
As = errors.As
|
||||
// Is alias function of `errors.Is`.
|
||||
Is = errors.Is
|
||||
)
|
||||
|
||||
// Error ...
|
||||
type Error struct {
|
||||
Cause error `json:"-"`
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Stack *stack `json:"-"`
|
||||
}
|
||||
|
||||
// Error returns a human readable error, error.Error() will not
|
||||
// contains the track information. Needs it? just call error.StackTrace()
|
||||
// Code will not be in the error output.
|
||||
func (e *Error) Error() string {
|
||||
out := e.Message
|
||||
if e.Cause != nil {
|
||||
out = out + ": " + e.Cause.Error()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// StackTrace ...
|
||||
func (e *Error) StackTrace() string {
|
||||
return e.Stack.frames().format()
|
||||
}
|
||||
|
||||
// MarshalJSON ...
|
||||
func (e *Error) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(
|
||||
&struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Code: e.Code,
|
||||
Message: e.Error(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// WithMessage ...
|
||||
func (e *Error) WithMessage(format string, v ...interface{}) *Error {
|
||||
e.Message = fmt.Sprintf(format, v...)
|
||||
return e
|
||||
}
|
||||
|
||||
// WithCode ...
|
||||
func (e *Error) WithCode(code string) *Error {
|
||||
e.Code = code
|
||||
return e
|
||||
}
|
||||
|
||||
// WithCause ...
|
||||
func (e *Error) WithCause(err error) *Error {
|
||||
e.Cause = err
|
||||
return e
|
||||
}
|
||||
|
||||
// Unwrap ...
|
||||
func (e *Error) Unwrap() error { return e.Cause }
|
||||
|
||||
// Errors ...
|
||||
type Errors []error
|
||||
|
||||
var _ error = Errors{}
|
||||
|
||||
// Error converts slice of error.
|
||||
func (errs Errors) Error() string {
|
||||
var tmpErrs struct {
|
||||
Errors []Error `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
for _, e := range errs {
|
||||
var err *Error
|
||||
ok := errors.As(e, &err)
|
||||
if !ok {
|
||||
err = UnknownError(e)
|
||||
}
|
||||
if err.Code == "" {
|
||||
err.Code = GeneralCode
|
||||
}
|
||||
|
||||
tmpErrs.Errors = append(tmpErrs.Errors, *err)
|
||||
}
|
||||
|
||||
msg, err := json.Marshal(tmpErrs)
|
||||
if err != nil {
|
||||
log.Error().Stack().Err(err).Msg("")
|
||||
return "{}"
|
||||
}
|
||||
return string(msg)
|
||||
}
|
||||
|
||||
// Len returns the current number of errors.
|
||||
func (errs Errors) Len() int {
|
||||
return len(errs)
|
||||
}
|
||||
|
||||
// NewErrs ...
|
||||
func NewErrs(err error) Errors {
|
||||
return Errors{err}
|
||||
}
|
||||
|
||||
// New ...
|
||||
func New(in interface{}) *Error {
|
||||
var err error
|
||||
switch in := in.(type) {
|
||||
case error:
|
||||
err = in
|
||||
default:
|
||||
err = fmt.Errorf("%v", in)
|
||||
}
|
||||
|
||||
return &Error{
|
||||
Message: err.Error(),
|
||||
Stack: newStack(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap ...
|
||||
func Wrap(err error, message string) *Error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
e := &Error{
|
||||
Cause: err,
|
||||
Message: message,
|
||||
Stack: newStack(),
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// Wrapf ...
|
||||
func Wrapf(err error, format string, args ...interface{}) *Error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
e := &Error{
|
||||
Cause: err,
|
||||
Message: fmt.Sprintf(format, args...),
|
||||
Stack: newStack(),
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// Errorf ...
|
||||
func Errorf(format string, args ...interface{}) *Error {
|
||||
return &Error{
|
||||
Message: fmt.Sprintf(format, args...),
|
||||
Stack: newStack(),
|
||||
}
|
||||
}
|
||||
|
||||
// IsErr checks whether the err chain contains error matches the code.
|
||||
func IsErr(err error, code string) bool {
|
||||
var e *Error
|
||||
if As(err, &e) {
|
||||
return e.Code == code
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ErrCode returns code of err.
|
||||
func ErrCode(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var e *Error
|
||||
if ok := As(err, &e); ok && e.Code != "" {
|
||||
return e.Code
|
||||
} else if ok && e.Cause != nil {
|
||||
return ErrCode(e.Cause)
|
||||
}
|
||||
|
||||
return GeneralCode
|
||||
}
|
63
registry/app/common/lib/errors/stack.go
Normal file
63
registry/app/common/lib/errors/stack.go
Normal file
@ -0,0 +1,63 @@
|
||||
// Source: https://github.com/goharbor/harbor
|
||||
|
||||
// Copyright 2016 Project Harbor Authors
|
||||
//
|
||||
// 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 errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const maxDepth = 50
|
||||
|
||||
type stack []uintptr
|
||||
|
||||
func (s *stack) frames() StackFrames {
|
||||
var stackFrames StackFrames
|
||||
frames := runtime.CallersFrames(*s)
|
||||
for {
|
||||
frame, next := frames.Next()
|
||||
// filter out runtime
|
||||
if !strings.Contains(frame.File, "runtime/") {
|
||||
stackFrames = append(stackFrames, frame)
|
||||
}
|
||||
if !next {
|
||||
break
|
||||
}
|
||||
}
|
||||
return stackFrames
|
||||
}
|
||||
|
||||
// newStack ...
|
||||
func newStack() *stack {
|
||||
var pcs [maxDepth]uintptr
|
||||
n := runtime.Callers(3, pcs[:])
|
||||
var st stack = pcs[0:n]
|
||||
return &st
|
||||
}
|
||||
|
||||
// StackFrames ...
|
||||
type StackFrames []runtime.Frame
|
||||
|
||||
// Output: <File>:<Line>, <Method>.
|
||||
func (frames StackFrames) format() string {
|
||||
var msg string
|
||||
for _, frame := range frames {
|
||||
msg += fmt.Sprintf("\n%v:%v, %v", frame.File, frame.Line, frame.Function)
|
||||
}
|
||||
return msg
|
||||
}
|
47
registry/app/common/lib/errors/stack_test.go
Normal file
47
registry/app/common/lib/errors/stack_test.go
Normal file
@ -0,0 +1,47 @@
|
||||
// Source: https://github.com/goharbor/harbor
|
||||
|
||||
// Copyright 2016 Project Harbor Authors
|
||||
//
|
||||
// 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 errors
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type stackTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (c *stackTestSuite) SetupTest() {}
|
||||
|
||||
func (c *stackTestSuite) TestFrame() {
|
||||
stack := newStack()
|
||||
frames := stack.frames()
|
||||
c.Equal(len(frames), 4)
|
||||
log.Info().Msg(frames.format())
|
||||
}
|
||||
|
||||
func (c *stackTestSuite) TestFormat() {
|
||||
stack := newStack()
|
||||
frames := stack.frames()
|
||||
c.Contains(frames[len(frames)-1].Function, "testing.tRunner")
|
||||
}
|
||||
|
||||
func TestStackTestSuite(t *testing.T) {
|
||||
suite.Run(t, &stackTestSuite{})
|
||||
}
|
92
registry/app/common/lib/link.go
Normal file
92
registry/app/common/lib/link.go
Normal file
@ -0,0 +1,92 @@
|
||||
// Source: https://github.com/goharbor/harbor
|
||||
|
||||
// Copyright 2016 Project Harbor Authors
|
||||
//
|
||||
// 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 lib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Link defines the model that describes the HTTP link header.
|
||||
type Link struct {
|
||||
URL string
|
||||
Rel string
|
||||
Attrs map[string]string
|
||||
}
|
||||
|
||||
// String returns the string representation of a link.
|
||||
func (l *Link) String() string {
|
||||
s := fmt.Sprintf("<%s>", l.URL)
|
||||
if len(l.Rel) > 0 {
|
||||
s = fmt.Sprintf(`%s; rel="%s"`, s, l.Rel)
|
||||
}
|
||||
for key, value := range l.Attrs {
|
||||
s = fmt.Sprintf(`%s; %s="%s"`, s, key, value)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Links is a link object array.
|
||||
type Links []*Link
|
||||
|
||||
// String returns the string representation of links.
|
||||
func (l Links) String() string {
|
||||
var strs []string
|
||||
for _, link := range l {
|
||||
strs = append(strs, link.String())
|
||||
}
|
||||
return strings.Join(strs, " , ")
|
||||
}
|
||||
|
||||
// ParseLinks parses the link header into Links
|
||||
// e.g. <http://example.com/TheBook/chapter2>; rel="previous";
|
||||
// title="previous chapter" , <http://example.com/TheBook/chapter4>; rel="next"; title="next chapter".
|
||||
func ParseLinks(str string) Links {
|
||||
var links Links
|
||||
for _, lk := range strings.Split(str, ",") {
|
||||
link := &Link{
|
||||
Attrs: map[string]string{},
|
||||
}
|
||||
for _, attr := range strings.Split(lk, ";") {
|
||||
attr = strings.TrimSpace(attr)
|
||||
if len(attr) == 0 {
|
||||
continue
|
||||
}
|
||||
if attr[0] == '<' && attr[len(attr)-1] == '>' {
|
||||
link.URL = attr[1 : len(attr)-1]
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.SplitN(attr, "=", 2)
|
||||
key := parts[0]
|
||||
value := ""
|
||||
if len(parts) == 2 {
|
||||
value = strings.Trim(parts[1], `"`)
|
||||
}
|
||||
if key == "rel" {
|
||||
link.Rel = value
|
||||
} else {
|
||||
link.Attrs[key] = value
|
||||
}
|
||||
}
|
||||
if len(link.URL) == 0 {
|
||||
continue
|
||||
}
|
||||
links = append(links, link)
|
||||
}
|
||||
return links
|
||||
}
|
31
registry/app/common/url_utils.go
Normal file
31
registry/app/common/url_utils.go
Normal file
@ -0,0 +1,31 @@
|
||||
// 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 common
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func GenerateOciTokenURL(registryURL string) string {
|
||||
return registryURL + "/v2/token"
|
||||
}
|
||||
|
||||
func GenerateSetupClientHostname(registryURL string) string {
|
||||
regURL, err := url.Parse(registryURL)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return regURL.Host
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user