Skip to content

Commit

Permalink
add Harbor Satellite Replication adapter
Browse files Browse the repository at this point in the history
* This commit enables us to use harbor with harbor-satellite

Signed-off-by: bupd <[email protected]>
  • Loading branch information
bupd committed Dec 17, 2024
1 parent e417875 commit 5bb898e
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 7 deletions.
42 changes: 35 additions & 7 deletions src/controller/replication/flow/stage.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ func fetchResources(adapter adp.Adapter, policy *repctlmodel.Policy) ([]*model.R

// assemble the source resources by filling the registry information
func assembleSourceResources(resources []*model.Resource,
policy *repctlmodel.Policy) []*model.Resource {
policy *repctlmodel.Policy,
) []*model.Resource {
for _, resource := range resources {
resource.Registry = policy.SrcRegistry
}
Expand All @@ -83,32 +84,59 @@ func assembleSourceResources(resources []*model.Resource,

// assemble the destination resources by filling the metadata, registry and override properties
func assembleDestinationResources(resources []*model.Resource,
policy *repctlmodel.Policy, dstRepoComponentPathType string) ([]*model.Resource, error) {
policy *repctlmodel.Policy, dstRepoComponentPathType string,
) ([]*model.Resource, error) {
var result []*model.Resource
for _, resource := range resources {
name, err := replaceNamespace(resource.Metadata.Repository.Name, policy.DestNamespace, policy.DestNamespaceReplaceCount, dstRepoComponentPathType)
if err != nil {
return nil, err
var registry *model.Registry
var repositoryName string
var err error

log.Debugf("assembling dest resources...")

// Check condition to determine whether to assemble list or destination resources
if policy.DestRegistry.Type == "harbor-satellite" {
// Assemble list resources
registry = policy.SrcRegistry
repositoryName = resource.Metadata.Repository.Name

if resource.ExtendedInfo == nil {
resource.ExtendedInfo = make(map[string]interface{})
}
resource.ExtendedInfo["destinationURL"] = policy.DestRegistry.URL
resource.ExtendedInfo["groupName"] = policy.DestNamespace
} else {
// Assemble destination resources
registry = policy.DestRegistry
repositoryName, err = replaceNamespace(resource.Metadata.Repository.Name, policy.DestNamespace, policy.DestNamespaceReplaceCount, dstRepoComponentPathType)
if err != nil {
return nil, err
}
}

// Create the resource
res := &model.Resource{
Type: resource.Type,
Registry: policy.DestRegistry,
Registry: registry,
ExtendedInfo: resource.ExtendedInfo,
Deleted: resource.Deleted,
IsDeleteTag: resource.IsDeleteTag,
Override: policy.Override,
Skip: resource.Skip,
}

// Fill the resource metadata
res.Metadata = &model.ResourceMetadata{
Repository: &model.Repository{
Name: name,
Name: repositoryName,
Metadata: resource.Metadata.Repository.Metadata,
},
Vtags: resource.Metadata.Vtags,
Artifacts: resource.Metadata.Artifacts,
}
result = append(result, res)
}

log.Debug("assemble the destination resources completed")
return result, nil
}
Expand Down
2 changes: 2 additions & 0 deletions src/jobservice/job/impl/replication/replication.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import (
_ "github.com/goharbor/harbor/src/pkg/reg/adapter/googlegcr"
// import harbor adapter
_ "github.com/goharbor/harbor/src/pkg/reg/adapter/harbor"
// import harborSatellite adapter
_ "github.com/goharbor/harbor/src/pkg/reg/adapter/harborsatellite"
// import huawei adapter
_ "github.com/goharbor/harbor/src/pkg/reg/adapter/huawei"
// import jfrog adapter
Expand Down
238 changes: 238 additions & 0 deletions src/pkg/reg/adapter/harborsatellite/adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
package harborsatellite

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"

"github.com/docker/distribution"
"github.com/goharbor/harbor/src/common/secret"
"github.com/goharbor/harbor/src/lib/config"
"github.com/goharbor/harbor/src/lib/errors"
regadapter "github.com/goharbor/harbor/src/pkg/reg/adapter"
"github.com/goharbor/harbor/src/pkg/reg/model"
)

var (
_ regadapter.Adapter = (*adapter)(nil)
_ regadapter.ArtifactRegistry = (*adapter)(nil)
)
var ErrNotImplemented = errors.New("not implemented")

type Result struct {
Group string `json:"group"`
Registry string `json:"registry"`
Artifacts []Artifact `json:"artifacts"`
}

type Artifact struct {
Repository string `json:"repository"`
Tags []string `json:"tag"`
Labels []string `json:"labels"`
Type string `json:"type"`
Digest string `json:"digest"`
Deleted bool `json:"deleted"`
}

func init() {
err := regadapter.RegisterFactory(model.RegistryHarborSatellite, &factory{})
if err != nil {
return
}
}

type factory struct{}

// Create ...
func (f *factory) Create(r *model.Registry) (regadapter.Adapter, error) {
return newAdapter(r)
}

// AdapterPattern ...
func (f *factory) AdapterPattern() *model.AdapterPattern {
return nil
}

type adapter struct {
httpClient *http.Client
}

func (a adapter) RoundTrip(request *http.Request) (*http.Response, error) {
u, err := url.Parse(config.InternalCoreURL())
if err != nil {
return nil, fmt.Errorf("unable to parse internal core url: %v", err)
}

// replace request's host with core's address
request.Host = config.InternalCoreURL()
request.URL.Host = u.Host

request.URL.Scheme = u.Scheme
// adds auth headers
_ = secret.AddToRequest(request, config.JobserviceSecret())

return a.httpClient.Do(request)
}

func (a adapter) Info() (*model.RegistryInfo, error) {
return &model.RegistryInfo{}, nil
}

func (a adapter) PrepareForPush(resources []*model.Resource) error {
var (
artifacts []Artifact
registry *model.Registry
destinationURL string
groupName string
)

for _, r := range resources {
if r.Metadata == nil {
continue
}
if r.Metadata.Repository == nil {
continue
}
if r.Registry == nil {
continue
}
if r.ExtendedInfo == nil {
return fmt.Errorf("extended_info map is nil")
}

if registry == nil {
registry = r.Registry
}
if destinationURL == "" {
destURL, ok := r.ExtendedInfo["destinationURL"].(string)
if ok {
destinationURL = destURL
} else {
return fmt.Errorf("destination_url not a string or missing")
}
}

if groupName == "" {
grp, ok := r.ExtendedInfo["groupName"].(string)
if ok {
groupName = grp
} else {
return fmt.Errorf("groupName not a string or missing")
}
}

for _, at := range r.Metadata.Artifacts {
artifacts = append(artifacts, Artifact{
Repository: r.Metadata.Repository.Name,
Deleted: r.Deleted,
Tags: at.Tags,
Labels: at.Labels,
Type: at.Type,
Digest: at.Digest,
})
}
}

if registry == nil {
return fmt.Errorf("no registry information found")
}

result := &Result{
Group: groupName,
Registry: registry.URL,
Artifacts: artifacts,
}

data, err := json.Marshal(result)
if err != nil {
return errors.Wrap(err, "failed to marshal result")
}

// Create a POST request
req, err := http.NewRequest("POST", destinationURL, bytes.NewBuffer(data))
if err != nil {
return fmt.Errorf("error creating request: %v", err)
}

// Set the content type header
req.Header.Set("Content-Type", "application/json")

// Send the request using http.Client
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("error sending request: %v", err)
}
defer resp.Body.Close()

return nil
}

func (a adapter) HealthCheck() (string, error) {
return model.Healthy, nil
}

func (a adapter) FetchArtifacts(filters []*model.Filter) ([]*model.Resource, error) {
return nil, nil
}

func (a adapter) ManifestExist(repository, reference string) (exist bool, desc *distribution.Descriptor, err error) {
return true, nil, nil
}

func (a adapter) PullManifest(repository, reference string, accepttedMediaTypes ...string) (manifest distribution.Manifest, digest string, err error) {
return nil, "", ErrNotImplemented
}

func (a adapter) PushManifest(repository, reference, mediaType string, payload []byte) (string, error) {
return "", nil
}

func (a adapter) DeleteManifest(repository, reference string) error {
return ErrNotImplemented
}

func (a adapter) BlobExist(repository, digest string) (exist bool, err error) {
return true, nil
}

func (a adapter) PullBlob(repository, digest string) (size int64, blob io.ReadCloser, err error) {
return 0, nil, ErrNotImplemented
}

func (a adapter) PullBlobChunk(repository, digest string, blobSize, start, end int64) (size int64, blob io.ReadCloser, err error) {
return 0, nil, ErrNotImplemented
}

func (a adapter) PushBlobChunk(repository, digest string, size int64, chunk io.Reader, start, end int64, location string) (nextUploadLocation string, endRange int64, err error) {
return "", 0, ErrNotImplemented
}

func (a adapter) PushBlob(repository, digest string, size int64, blob io.Reader) error {
return nil
}

func (a adapter) MountBlob(srcRepository, digest, dstRepository string) (err error) {
return nil
}

func (a adapter) CanBeMount(digest string) (mount bool, repository string, err error) {
return false, "", ErrNotImplemented
}

func (a adapter) DeleteTag(repository, tag string) error {
return ErrNotImplemented
}

func (a adapter) ListTags(repository string) (tags []string, err error) {
return nil, nil
}

func newAdapter(_ *model.Registry) (regadapter.Adapter, error) {
return &adapter{
httpClient: &http.Client{},
}, nil
}
2 changes: 2 additions & 0 deletions src/pkg/reg/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import (
_ "github.com/goharbor/harbor/src/pkg/reg/adapter/googlegcr"
// register the Harbor adapter
_ "github.com/goharbor/harbor/src/pkg/reg/adapter/harbor"
// register the HarborSatellite adapter
_ "github.com/goharbor/harbor/src/pkg/reg/adapter/harborsatellite"
// register the huawei adapter
_ "github.com/goharbor/harbor/src/pkg/reg/adapter/huawei"
// register the Jfrog Artifactory adapter
Expand Down
1 change: 1 addition & 0 deletions src/pkg/reg/model/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const (

RegistryTypeHelmHub = "helm-hub"
RegistryTypeArtifactHub = "artifact-hub"
RegistryHarborSatellite = "harbor-satellite"

FilterStyleTypeText = "input"
FilterStyleTypeRadio = "radio"
Expand Down

0 comments on commit 5bb898e

Please sign in to comment.