feat: [AH-309]: Artifact Registry integration (#2318)

This commit is contained in:
Tudor Macari 2024-08-22 00:28:17 +00:00 committed by Harness
parent fddea6392f
commit 40ee05ca11
779 changed files with 68151 additions and 231 deletions

8
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View 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

View File

@ -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):

View File

@ -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
View 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...)
}

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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,
})
}
}

View File

@ -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)
},
)
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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;

View 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();

View File

@ -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;

View 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');

View File

@ -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)

View File

@ -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
}

View File

@ -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) {

View File

@ -33,5 +33,6 @@ func ProvideURLProvider(config *types.Config) (Provider, error) {
config.SSH.DefaultUser,
config.SSH.Enable,
config.URL.UI,
config.URL.Registry,
)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -34,6 +34,7 @@ func (c *commandCurrent) run(*kingpin.ParseContext) error {
defer cancel()
db, err := getDB(ctx, c.envfile)
if err != nil {
return err
}

View File

@ -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
}

View File

@ -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

View File

@ -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
View File

@ -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
View File

@ -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=

View 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)
}

View 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: &registry.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
}

View 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,
}
}

View File

@ -0,0 +1,71 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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,
}
}

View 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 := &registrytypes.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 := &registrytypes.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 := &registrytypes.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
}

View 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()),
),
}
}

View 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
}

View 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, &regInfo.packageTypes,
regInfo.sortByField, regInfo.sortByOrder, regInfo.limit, regInfo.offset, regInfo.searchTerm, regInfo.labels,
)
count, _ = c.TagStore.CountAllArtifactsByParentID(
ctx, regInfo.parentID, &regInfo.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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,83 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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
}

View 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 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
}

View File

@ -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
}

View File

@ -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
}

View 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: &section1step1Header,
Commands: &section1step1Commands,
Type: &section1step1Type,
},
{
Header: &section1step2Header,
Type: &section1step2Type,
},
}
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: &section2step1Header,
Commands: &section2step1Commands,
Type: &section2step1Type,
},
}
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: &section3step1Header,
Commands: &section3step1Commands,
Type: &section3step1Type,
},
}
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: &section1,
},
{
Header: &header2,
Steps: &section2,
},
{
Header: &header3,
Steps: &section3,
},
},
}
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: &section1step1Header,
Commands: &section1step1Commands,
Type: &section1step1Type,
},
{
Header: &section1step2Header,
Type: &section1step2Type,
},
}
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: &section2step1Header,
Commands: &section2step1Commands,
Type: &section2step1Type,
},
}
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: &section3step1Header,
Commands: &section3step1Commands,
Type: &section3step1Type,
},
{
Header: &section3step2Header,
Commands: &section3step2Commands,
Type: &section3step2Type,
},
}
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: &section1,
},
{
Header: &header2,
Steps: &section2,
},
{
Header: &header3,
Steps: &section3,
},
},
}
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))
}
}

View 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
}

View 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()),
),
}
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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)
}

View 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)
}

View 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)
}

View 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)
}

View 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)),
)
}
}
}

View 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)
}

View 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)
}

View 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)
}

View File

@ -0,0 +1,45 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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)
}

View File

@ -0,0 +1,68 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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)
}

View 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"`
}

View 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)
}

View File

@ -0,0 +1,43 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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)
}

View File

@ -0,0 +1,43 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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)
}

View 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)
}

View 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)
}

View File

@ -0,0 +1,50 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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)
}

View 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
}

View 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)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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
}

View 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)
}

View 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
}

View File

@ -0,0 +1,51 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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"
}

View File

@ -0,0 +1,54 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package 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
}

View 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
View 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
View 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
}

View 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
}

View 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,
},
}
}

View 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
}

View 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")
}

View 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

View 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)
}

View 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
}

View 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
}

View 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{})
}

View 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
}

View 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