mirror of
https://github.com/goharbor/harbor
synced 2025-04-28 04:53:02 +00:00
497 lines
12 KiB
Go
497 lines
12 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
common_http "github.com/goharbor/harbor/src/common/http"
|
|
"github.com/goharbor/harbor/src/common/utils"
|
|
"github.com/goharbor/harbor/src/common/utils/log"
|
|
"github.com/goharbor/harbor/src/core/api/models"
|
|
"github.com/goharbor/harbor/src/replication"
|
|
"github.com/goharbor/harbor/src/replication/adapter"
|
|
"github.com/goharbor/harbor/src/replication/event"
|
|
"github.com/goharbor/harbor/src/replication/model"
|
|
"github.com/goharbor/harbor/src/replication/policy"
|
|
"github.com/goharbor/harbor/src/replication/registry"
|
|
)
|
|
|
|
// RegistryAPI handles requests to /api/registries/{}. It manages registries integrated to Harbor.
|
|
type RegistryAPI struct {
|
|
BaseController
|
|
manager registry.Manager
|
|
policyCtl policy.Controller
|
|
}
|
|
|
|
// Prepare validates the user
|
|
func (t *RegistryAPI) Prepare() {
|
|
t.BaseController.Prepare()
|
|
if !t.SecurityCtx.IsAuthenticated() {
|
|
t.SendUnAuthorizedError(errors.New("UnAuthorized"))
|
|
return
|
|
}
|
|
|
|
if !t.SecurityCtx.IsSysAdmin() {
|
|
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
|
|
return
|
|
}
|
|
|
|
t.manager = replication.RegistryMgr
|
|
t.policyCtl = replication.PolicyCtl
|
|
}
|
|
|
|
// Ping checks health status of a registry
|
|
func (t *RegistryAPI) Ping() {
|
|
req := struct {
|
|
ID *int64 `json:"id"`
|
|
Type *string `json:"type"`
|
|
URL *string `json:"url"`
|
|
CredentialType *string `json:"credential_type"`
|
|
AccessKey *string `json:"access_key"`
|
|
AccessSecret *string `json:"access_secret"`
|
|
Insecure *bool `json:"insecure"`
|
|
}{}
|
|
t.DecodeJSONReq(&req)
|
|
|
|
reg := &model.Registry{}
|
|
var err error
|
|
if req.ID != nil {
|
|
reg, err = t.manager.Get(*req.ID)
|
|
if err != nil {
|
|
t.SendInternalServerError(fmt.Errorf("failed to get registry %d: %v", *req.ID, err))
|
|
return
|
|
}
|
|
|
|
if reg == nil {
|
|
t.SendNotFoundError(fmt.Errorf("registry %d not found", *req.ID))
|
|
return
|
|
}
|
|
}
|
|
if req.Type != nil {
|
|
reg.Type = model.RegistryType(*req.Type)
|
|
}
|
|
if req.URL != nil {
|
|
url, err := utils.ParseEndpoint(*req.URL)
|
|
if err != nil {
|
|
t.SendBadRequestError(err)
|
|
return
|
|
}
|
|
|
|
// Prevent SSRF security issue #3755
|
|
reg.URL = url.Scheme + "://" + url.Host + url.Path
|
|
}
|
|
if req.CredentialType != nil {
|
|
if reg.Credential == nil {
|
|
reg.Credential = &model.Credential{}
|
|
}
|
|
reg.Credential.Type = model.CredentialType(*req.CredentialType)
|
|
}
|
|
if req.AccessKey != nil {
|
|
if reg.Credential == nil {
|
|
reg.Credential = &model.Credential{}
|
|
}
|
|
reg.Credential.AccessKey = *req.AccessKey
|
|
}
|
|
if req.AccessSecret != nil {
|
|
if reg.Credential == nil {
|
|
reg.Credential = &model.Credential{}
|
|
}
|
|
reg.Credential.AccessSecret = *req.AccessSecret
|
|
}
|
|
if req.Insecure != nil {
|
|
reg.Insecure = *req.Insecure
|
|
}
|
|
if len(reg.Type) == 0 || len(reg.URL) == 0 {
|
|
t.SendBadRequestError(errors.New("type or url cannot be empty"))
|
|
return
|
|
}
|
|
|
|
status, err := registry.CheckHealthStatus(reg)
|
|
if err != nil {
|
|
e, ok := err.(*common_http.Error)
|
|
if ok && e.Code == http.StatusUnauthorized {
|
|
t.SendBadRequestError(errors.New("invalid credential"))
|
|
return
|
|
}
|
|
t.SendInternalServerError(fmt.Errorf("failed to check health of registry %s: %v", reg.URL, err))
|
|
return
|
|
}
|
|
|
|
if status != model.Healthy {
|
|
t.SendBadRequestError(errors.New(""))
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// Get gets a registry by id.
|
|
func (t *RegistryAPI) Get() {
|
|
id, err := t.GetIDFromURL()
|
|
if err != nil {
|
|
t.SendBadRequestError(err)
|
|
return
|
|
}
|
|
|
|
r, err := t.manager.Get(id)
|
|
if err != nil {
|
|
log.Errorf("failed to get registry %d: %v", id, err)
|
|
t.SendInternalServerError(err)
|
|
return
|
|
}
|
|
|
|
if r == nil {
|
|
t.SendNotFoundError(fmt.Errorf("registry %d not found", id))
|
|
return
|
|
}
|
|
|
|
// Hide access secret
|
|
if r.Credential != nil && len(r.Credential.AccessSecret) != 0 {
|
|
r.Credential.AccessSecret = "*****"
|
|
}
|
|
|
|
t.Data["json"] = r
|
|
t.ServeJSON()
|
|
}
|
|
|
|
// List lists all registries that match a given registry name.
|
|
func (t *RegistryAPI) List() {
|
|
name := t.GetString("name")
|
|
|
|
_, registries, err := t.manager.List(&model.RegistryQuery{
|
|
Name: name,
|
|
})
|
|
if err != nil {
|
|
log.Errorf("failed to list registries %s: %v", name, err)
|
|
t.SendInternalServerError(err)
|
|
return
|
|
}
|
|
|
|
// Hide passwords
|
|
for _, r := range registries {
|
|
if r.Credential != nil && len(r.Credential.AccessSecret) != 0 {
|
|
r.Credential.AccessSecret = "*****"
|
|
}
|
|
}
|
|
|
|
t.Data["json"] = registries
|
|
t.ServeJSON()
|
|
return
|
|
}
|
|
|
|
// Post creates a registry
|
|
func (t *RegistryAPI) Post() {
|
|
r := &model.Registry{}
|
|
isValid, err := t.DecodeJSONReqAndValidate(r)
|
|
if !isValid {
|
|
t.SendBadRequestError(err)
|
|
return
|
|
}
|
|
|
|
reg, err := t.manager.GetByName(r.Name)
|
|
if err != nil {
|
|
log.Errorf("failed to get registry %s: %v", r.Name, err)
|
|
t.SendInternalServerError(err)
|
|
return
|
|
}
|
|
|
|
if reg != nil {
|
|
t.SendConflictError(fmt.Errorf("name '%s' is already used", r.Name))
|
|
return
|
|
}
|
|
|
|
status, err := registry.CheckHealthStatus(r)
|
|
if err != nil {
|
|
t.SendBadRequestError(fmt.Errorf("health check to registry %s failed: %v", r.URL, err))
|
|
return
|
|
}
|
|
if status != model.Healthy {
|
|
t.SendBadRequestError(fmt.Errorf("registry %s is unhealthy: %s", r.URL, status))
|
|
return
|
|
}
|
|
|
|
id, err := t.manager.Add(r)
|
|
if err != nil {
|
|
log.Errorf("Add registry '%s' error: %v", r.URL, err)
|
|
t.SendInternalServerError(err)
|
|
return
|
|
}
|
|
|
|
t.Redirect(http.StatusCreated, strconv.FormatInt(id, 10))
|
|
}
|
|
|
|
// Put updates a registry
|
|
func (t *RegistryAPI) Put() {
|
|
id, err := t.GetIDFromURL()
|
|
if err != nil {
|
|
t.SendBadRequestError(err)
|
|
return
|
|
}
|
|
|
|
r, err := t.manager.Get(id)
|
|
if err != nil {
|
|
log.Errorf("Get registry by id %d error: %v", id, err)
|
|
t.SendInternalServerError(err)
|
|
return
|
|
}
|
|
|
|
if r == nil {
|
|
t.SendNotFoundError(fmt.Errorf("Registry %d not found", id))
|
|
return
|
|
}
|
|
|
|
req := models.RegistryUpdateRequest{}
|
|
if err := t.DecodeJSONReq(&req); err != nil {
|
|
t.SendBadRequestError(err)
|
|
return
|
|
}
|
|
|
|
originalName := r.Name
|
|
|
|
if req.Name != nil {
|
|
r.Name = *req.Name
|
|
}
|
|
if req.Description != nil {
|
|
r.Description = *req.Description
|
|
}
|
|
if req.URL != nil {
|
|
r.URL = *req.URL
|
|
}
|
|
if req.CredentialType != nil {
|
|
r.Credential.Type = (model.CredentialType)(*req.CredentialType)
|
|
}
|
|
if req.AccessKey != nil {
|
|
r.Credential.AccessKey = *req.AccessKey
|
|
}
|
|
if req.AccessSecret != nil {
|
|
r.Credential.AccessSecret = *req.AccessSecret
|
|
}
|
|
if req.Insecure != nil {
|
|
r.Insecure = *req.Insecure
|
|
}
|
|
|
|
t.Validate(r)
|
|
|
|
if r.Name != originalName {
|
|
reg, err := t.manager.GetByName(r.Name)
|
|
if err != nil {
|
|
log.Errorf("Get registry by name '%s' error: %v", r.Name, err)
|
|
t.SendInternalServerError(err)
|
|
return
|
|
}
|
|
|
|
if reg != nil {
|
|
t.SendConflictError(errors.New("name is already used"))
|
|
return
|
|
}
|
|
}
|
|
|
|
status, err := registry.CheckHealthStatus(r)
|
|
if err != nil {
|
|
t.SendBadRequestError(fmt.Errorf("health check to registry %s failed: %v", r.URL, err))
|
|
return
|
|
}
|
|
if status != model.Healthy {
|
|
t.SendBadRequestError(fmt.Errorf("registry %s is unhealthy: %s", r.URL, status))
|
|
return
|
|
}
|
|
|
|
if err := t.manager.Update(r); err != nil {
|
|
log.Errorf("Update registry %d error: %v", id, err)
|
|
t.SendInternalServerError(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Delete deletes a registry
|
|
func (t *RegistryAPI) Delete() {
|
|
id, err := t.GetIDFromURL()
|
|
if err != nil {
|
|
t.SendBadRequestError(err)
|
|
return
|
|
}
|
|
|
|
registry, err := t.manager.Get(id)
|
|
if err != nil {
|
|
msg := fmt.Sprintf("Get registry %d error: %v", id, err)
|
|
log.Error(msg)
|
|
t.SendInternalServerError(errors.New(msg))
|
|
return
|
|
}
|
|
|
|
if registry == nil {
|
|
t.SendNotFoundError(fmt.Errorf("Registry %d not found", id))
|
|
return
|
|
}
|
|
|
|
// Check whether there are replication policies that use this registry as source registry.
|
|
total, _, err := t.policyCtl.List([]*model.PolicyQuery{
|
|
{
|
|
SrcRegistry: id,
|
|
},
|
|
}...)
|
|
if err != nil {
|
|
t.SendInternalServerError(fmt.Errorf("List replication policies with source registry %d error: %v", id, err))
|
|
return
|
|
}
|
|
if total > 0 {
|
|
msg := fmt.Sprintf("Can't delete registry %d, %d replication policies use it as source registry", id, total)
|
|
log.Error(msg)
|
|
t.SendPreconditionFailedError(errors.New(msg))
|
|
return
|
|
}
|
|
|
|
// Check whether there are replication policies that use this registry as destination registry.
|
|
total, _, err = t.policyCtl.List([]*model.PolicyQuery{
|
|
{
|
|
DestRegistry: id,
|
|
},
|
|
}...)
|
|
if err != nil {
|
|
t.SendInternalServerError(fmt.Errorf("List replication policies with destination registry %d error: %v", id, err))
|
|
return
|
|
}
|
|
if total > 0 {
|
|
msg := fmt.Sprintf("Can't delete registry %d, %d replication policies use it as destination registry", id, total)
|
|
log.Error(msg)
|
|
t.SendPreconditionFailedError(errors.New(msg))
|
|
return
|
|
}
|
|
|
|
if err := t.manager.Remove(id); err != nil {
|
|
msg := fmt.Sprintf("Delete registry %d error: %v", id, err)
|
|
log.Error(msg)
|
|
t.SendPreconditionFailedError(errors.New(msg))
|
|
return
|
|
}
|
|
}
|
|
|
|
// GetInfo returns the base info and capability declarations of the registry
|
|
func (t *RegistryAPI) GetInfo() {
|
|
id, err := t.GetInt64FromPath(":id")
|
|
// "0" is used for the ID of the local Harbor registry
|
|
if err != nil || id < 0 {
|
|
t.SendBadRequestError(fmt.Errorf("invalid registry ID %s", t.GetString(":id")))
|
|
return
|
|
}
|
|
var registry *model.Registry
|
|
if id == 0 {
|
|
registry = event.GetLocalRegistry()
|
|
} else {
|
|
registry, err = t.manager.Get(id)
|
|
if err != nil {
|
|
t.SendInternalServerError(fmt.Errorf("failed to get registry %d: %v", id, err))
|
|
return
|
|
}
|
|
if registry == nil {
|
|
t.SendNotFoundError(fmt.Errorf("registry %d not found", id))
|
|
return
|
|
}
|
|
}
|
|
|
|
factory, err := adapter.GetFactory(registry.Type)
|
|
if err != nil {
|
|
t.SendInternalServerError(fmt.Errorf("failed to get the adapter factory for registry type %s: %v", registry.Type, err))
|
|
return
|
|
}
|
|
adp, err := factory(registry)
|
|
if err != nil {
|
|
t.SendInternalServerError(fmt.Errorf("failed to create the adapter for registry %d: %v", registry.ID, err))
|
|
return
|
|
}
|
|
info, err := adp.Info()
|
|
if err != nil {
|
|
t.SendInternalServerError(fmt.Errorf("failed to get registry info %d: %v", id, err))
|
|
return
|
|
}
|
|
t.WriteJSONData(process(info))
|
|
}
|
|
|
|
// GetNamespace get the namespace of a registry
|
|
// TODO remove
|
|
func (t *RegistryAPI) GetNamespace() {
|
|
/*
|
|
var registry *model.Registry
|
|
var err error
|
|
|
|
id, err := t.GetInt64FromPath(":id")
|
|
if err != nil || id < 0 {
|
|
t.HandleBadRequest(fmt.Sprintf("invalid registry ID %s", t.GetString(":id")))
|
|
return
|
|
}
|
|
if id > 0 {
|
|
registry, err = t.manager.Get(id)
|
|
if err != nil {
|
|
t.HandleInternalServerError(fmt.Sprintf("failed to get registry %d: %v", id, err))
|
|
return
|
|
}
|
|
} else if id == 0 {
|
|
registry = event.GetLocalRegistry()
|
|
}
|
|
|
|
if registry == nil {
|
|
t.HandleNotFound(fmt.Sprintf("registry %d not found", id))
|
|
return
|
|
}
|
|
|
|
if !adapter.HasFactory(registry.Type) {
|
|
t.HandleInternalServerError(fmt.Sprintf("no adapter factory found for %s", registry.Type))
|
|
return
|
|
}
|
|
|
|
regFactory, err := adapter.GetFactory(registry.Type)
|
|
if err != nil {
|
|
t.HandleInternalServerError(fmt.Sprintf("fail to get adapter factory %s", registry.Type))
|
|
return
|
|
}
|
|
regAdapter, err := regFactory(registry)
|
|
if err != nil {
|
|
t.HandleInternalServerError(fmt.Sprintf("fail to get adapter %s", registry.Type))
|
|
return
|
|
}
|
|
|
|
query := &model.NamespaceQuery{
|
|
Name: t.GetString("name"),
|
|
}
|
|
npResults, err := regAdapter.ListNamespaces(query)
|
|
if err != nil {
|
|
t.HandleInternalServerError(fmt.Sprintf("fail to list namespaces %s %v", registry.Type, err))
|
|
return
|
|
}
|
|
|
|
t.Data["json"] = npResults
|
|
t.ServeJSON()
|
|
*/
|
|
}
|
|
|
|
// merge "SupportedResourceTypes" into "SupportedResourceFilters" for UI to render easier
|
|
func process(info *model.RegistryInfo) *model.RegistryInfo {
|
|
if info == nil {
|
|
return nil
|
|
}
|
|
in := &model.RegistryInfo{
|
|
Type: info.Type,
|
|
Description: info.Description,
|
|
SupportedTriggers: info.SupportedTriggers,
|
|
}
|
|
filters := []*model.FilterStyle{}
|
|
for _, filter := range info.SupportedResourceFilters {
|
|
if filter.Type != model.FilterTypeResource {
|
|
filters = append(filters, filter)
|
|
}
|
|
}
|
|
values := []string{}
|
|
for _, resourceType := range info.SupportedResourceTypes {
|
|
values = append(values, string(resourceType))
|
|
}
|
|
filters = append(filters, &model.FilterStyle{
|
|
Type: model.FilterTypeResource,
|
|
Style: model.FilterStyleTypeRadio,
|
|
Values: values,
|
|
})
|
|
in.SupportedResourceFilters = filters
|
|
|
|
return in
|
|
}
|