drone/registry/app/api/controller/metadata/update_registry.go
Arvind Choudhary fb65327419
feat: [AH-231]: Upstream features (#3560)
Added support for:
1. Default library/ prefix for docker hub if required.
2. Implemented manifest list support for upstream proxies. New table
introduced: `oci_image_index_mappings`
3. Fixed Secret issue on UI for both gitness and harness code
4. Code refactoring to bring controller inside wire and other minor
fixes around linting
5. Fixed few bugs around upstream proxy.
2024-09-25 01:16:26 -07:00

405 lines
13 KiB
Go

// 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 := c.UpdateUpstreamProxyEntity(
ctx,
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 (c *APIController) UpdateUpstreamProxyEntity(
ctx context.Context, 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
if res.SecretIdentifier == nil {
return nil, nil, fmt.Errorf("failed to create upstream proxy: secret_identifier missing")
}
upstreamProxyConfigEntity.SecretSpaceID, err = c.getSecretID(ctx, res.SecretSpaceId, res.SecretSpacePath)
if err != nil {
return nil, nil, err
}
upstreamProxyConfigEntity.SecretSpaceID = *res.SecretSpaceId
upstreamProxyConfigEntity.SecretIdentifier = *res.SecretIdentifier
} else {
upstreamProxyConfigEntity.UserName = ""
upstreamProxyConfigEntity.SecretIdentifier = ""
upstreamProxyConfigEntity.SecretSpaceID = 0
}
return repoEntity, upstreamProxyConfigEntity, nil
}