harbor/src/controller/proxy/local.go

163 lines
5.7 KiB
Go

// Copyright 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 proxy
import (
"context"
"io"
"github.com/docker/distribution"
"github.com/goharbor/harbor/src/controller/artifact"
"github.com/goharbor/harbor/src/controller/event/metadata"
"github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/cache"
"github.com/goharbor/harbor/src/lib/config"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/goharbor/harbor/src/pkg/proxy/secret"
"github.com/goharbor/harbor/src/pkg/registry"
)
// TrimmedManifestlist - key prefix for trimmed manifest
const TrimmedManifestlist = "trimmedmanifestlist:"
// localInterface defines operations related to local repo under proxy mode
type localInterface interface {
// BlobExist check if the blob exist in local repo
BlobExist(ctx context.Context, art lib.ArtifactInfo) (bool, error)
// GetManifest get the manifest info
GetManifest(ctx context.Context, art lib.ArtifactInfo) (*artifact.Artifact, error)
// PushBlob push blob to local repo
PushBlob(localRepo string, desc distribution.Descriptor, bReader io.ReadCloser) error
// PushManifest push manifest to local repo, ref can be digest or tag
PushManifest(repo string, ref string, manifest distribution.Manifest) error
// CheckDependencies check if the manifest's dependency is ready
CheckDependencies(ctx context.Context, repo string, man distribution.Manifest) []distribution.Descriptor
// DeleteManifest cleanup delete tag from local cache
DeleteManifest(repo, ref string)
}
func (l *localHelper) GetManifest(ctx context.Context, art lib.ArtifactInfo) (*artifact.Artifact, error) {
ref := getReference(art)
a, err := l.artifactCtl.GetByReference(ctx, art.Repository, ref, nil)
if err != nil {
if errors.IsNotFoundErr(err) {
return nil, nil
}
return nil, err
}
return a, nil
}
// localHelper defines operations related to local repo under proxy mode
type localHelper struct {
registry registry.Client
artifactCtl artifactController
cache cache.Cache
}
type artifactController interface {
GetByReference(ctx context.Context, repository, reference string, option *artifact.Option) (artifact *artifact.Artifact, err error)
}
// newLocalHelper create the localInterface
func newLocalHelper() localInterface {
l := &localHelper{artifactCtl: artifact.Ctl}
l.init()
return l
}
func (l *localHelper) BlobExist(_ context.Context, art lib.ArtifactInfo) (bool, error) {
return l.registry.BlobExist(art.Repository, art.Digest)
}
func (l *localHelper) init() {
if l.registry != nil {
return
}
log.Debugf("core url:%s, local core url: %v", config.GetCoreURL(), config.LocalCoreURL())
// the traffic is internal only
registryURL := config.LocalCoreURL()
l.registry = registry.NewClientWithAuthorizer(registryURL, secret.NewAuthorizer(), true)
l.cache = cache.Default()
}
func (l *localHelper) PushBlob(localRepo string, desc distribution.Descriptor, bReader io.ReadCloser) error {
log.Debugf("Put blob to local registry, localRepo:%v, digest: %v", localRepo, desc.Digest)
ref := string(desc.Digest)
artName := localRepo + ":" + ref
// use inflight checker to avoid multiple requests to push blob to local in same time
if !inflightChecker.addRequest(artName) {
return nil
}
defer inflightChecker.removeRequest(artName)
err := l.registry.PushBlob(localRepo, ref, desc.Size, bReader)
return err
}
func (l *localHelper) PushManifest(repo string, ref string, manifest distribution.Manifest) error {
// Make sure there is only one go routing to push current artName to local repo
artName := repo + ":" + ref
// use inflight checker to avoid multiple requests to push manifest to local in same time
if !inflightChecker.addRequest(artName) {
return nil
}
defer inflightChecker.removeRequest(artName)
mediaType, payload, err := manifest.Payload()
if err != nil {
return err
}
_, err = l.registry.PushManifest(repo, ref, mediaType, payload)
return err
}
// DeleteManifest cleanup delete tag from local repo
func (l *localHelper) DeleteManifest(repo, ref string) {
log.Debugf("Remove tag from repo if it is exist, repo: %v ref: %v", repo, ref)
if err := l.registry.DeleteManifest(repo, ref); err != nil {
// sometimes user pull a non-exist image
log.Warningf("failed to remove artifact, error %v", err)
}
}
func (l *localHelper) CheckDependencies(ctx context.Context, repo string, man distribution.Manifest) []distribution.Descriptor {
descriptors := man.References()
waitDesc := make([]distribution.Descriptor, 0)
for _, desc := range descriptors {
log.Debugf("checking the blob dependency: %v", desc.Digest)
art := lib.ArtifactInfo{Repository: repo, Digest: string(desc.Digest)}
exist, err := l.BlobExist(ctx, art)
if err != nil || !exist {
log.Debugf("Check dependency failed!")
waitDesc = append(waitDesc, desc)
}
}
log.Debugf("Check dependency result %v", waitDesc)
return waitDesc
}
// SendPullEvent send a pull image event
func SendPullEvent(ctx context.Context, a *artifact.Artifact, tag, operator string) {
e := &metadata.PullArtifactEventMetadata{
Artifact: &a.Artifact,
Tag: tag,
Operator: operator,
}
event.BuildAndPublish(ctx, e)
}