mirror of
https://github.com/goharbor/harbor
synced 2025-04-19 17:04:15 +00:00
433 lines
10 KiB
Go
433 lines
10 KiB
Go
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
|
//
|
|
// 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 (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/vmware/harbor/src/common/dao"
|
|
"github.com/vmware/harbor/src/common/models"
|
|
"github.com/vmware/harbor/src/common/notifier"
|
|
"github.com/vmware/harbor/src/common/utils"
|
|
"github.com/vmware/harbor/src/common/utils/clair"
|
|
registry_error "github.com/vmware/harbor/src/common/utils/error"
|
|
"github.com/vmware/harbor/src/common/utils/log"
|
|
"github.com/vmware/harbor/src/common/utils/registry"
|
|
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
|
"github.com/vmware/harbor/src/ui/config"
|
|
"github.com/vmware/harbor/src/ui/promgr"
|
|
"github.com/vmware/harbor/src/ui/service/token"
|
|
uiutils "github.com/vmware/harbor/src/ui/utils"
|
|
)
|
|
|
|
//sysadmin has all privileges to all projects
|
|
func listRoles(userID int, projectID int64) ([]models.Role, error) {
|
|
roles := make([]models.Role, 0, 1)
|
|
isSysAdmin, err := dao.IsAdminRole(userID)
|
|
if err != nil {
|
|
log.Errorf("failed to determine whether the user %d is system admin: %v", userID, err)
|
|
return roles, err
|
|
}
|
|
if isSysAdmin {
|
|
role, err := dao.GetRoleByID(models.PROJECTADMIN)
|
|
if err != nil {
|
|
log.Errorf("failed to get role %d: %v", models.PROJECTADMIN, err)
|
|
return roles, err
|
|
}
|
|
roles = append(roles, *role)
|
|
return roles, nil
|
|
}
|
|
|
|
rs, err := dao.GetUserProjectRoles(userID, projectID)
|
|
if err != nil {
|
|
log.Errorf("failed to get user %d 's roles for project %d: %v", userID, projectID, err)
|
|
return roles, err
|
|
}
|
|
roles = append(roles, rs...)
|
|
return roles, nil
|
|
}
|
|
|
|
func checkUserExists(name string) int {
|
|
u, err := dao.GetUser(models.User{Username: name})
|
|
if err != nil {
|
|
log.Errorf("Error occurred in GetUser, error: %v", err)
|
|
return 0
|
|
}
|
|
if u != nil {
|
|
return u.UserID
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// TriggerReplication triggers the replication according to the policy
|
|
// TODO remove
|
|
func TriggerReplication(policyID int64, repository string,
|
|
tags []string, operation string) error {
|
|
data := struct {
|
|
PolicyID int64 `json:"policy_id"`
|
|
Repo string `json:"repository"`
|
|
Operation string `json:"operation"`
|
|
TagList []string `json:"tags"`
|
|
}{
|
|
PolicyID: policyID,
|
|
Repo: repository,
|
|
TagList: tags,
|
|
Operation: operation,
|
|
}
|
|
|
|
b, err := json.Marshal(&data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
url := buildReplicationURL()
|
|
|
|
return uiutils.RequestAsUI("POST", url, bytes.NewBuffer(b), uiutils.NewStatusRespHandler(http.StatusOK))
|
|
}
|
|
|
|
// TODO remove
|
|
func postReplicationAction(policyID int64, acton string) error {
|
|
data := struct {
|
|
PolicyID int64 `json:"policy_id"`
|
|
Action string `json:"action"`
|
|
}{
|
|
PolicyID: policyID,
|
|
Action: acton,
|
|
}
|
|
|
|
b, err := json.Marshal(&data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
url := buildReplicationActionURL()
|
|
|
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(b))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
uiutils.AddUISecret(req)
|
|
|
|
client := &http.Client{}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusOK {
|
|
return nil
|
|
}
|
|
|
|
b, err = ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return fmt.Errorf("%d %s", resp.StatusCode, string(b))
|
|
}
|
|
|
|
// SyncRegistry syncs the repositories of registry with database.
|
|
func SyncRegistry(pm promgr.ProjectManager) error {
|
|
|
|
log.Infof("Start syncing repositories from registry to DB... ")
|
|
|
|
reposInRegistry, err := catalog()
|
|
if err != nil {
|
|
log.Error(err)
|
|
return err
|
|
}
|
|
|
|
var repoRecordsInDB []*models.RepoRecord
|
|
repoRecordsInDB, err = dao.GetAllRepositories()
|
|
if err != nil {
|
|
log.Errorf("error occurred while getting all registories. %v", err)
|
|
return err
|
|
}
|
|
|
|
var reposInDB []string
|
|
for _, repoRecordInDB := range repoRecordsInDB {
|
|
reposInDB = append(reposInDB, repoRecordInDB.Name)
|
|
}
|
|
|
|
var reposToAdd []string
|
|
var reposToDel []string
|
|
reposToAdd, reposToDel, err = diffRepos(reposInRegistry, reposInDB, pm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(reposToAdd) > 0 {
|
|
log.Debugf("Start adding repositories into DB... ")
|
|
for _, repoToAdd := range reposToAdd {
|
|
project, _ := utils.ParseRepository(repoToAdd)
|
|
pullCount, err := dao.CountPull(repoToAdd)
|
|
if err != nil {
|
|
log.Errorf("Error happens when counting pull count from access log: %v", err)
|
|
}
|
|
pro, err := pm.Get(project)
|
|
if err != nil {
|
|
log.Errorf("failed to get project %s: %v", project, err)
|
|
continue
|
|
}
|
|
repoRecord := models.RepoRecord{
|
|
Name: repoToAdd,
|
|
ProjectID: pro.ProjectID,
|
|
PullCount: pullCount,
|
|
}
|
|
|
|
if err := dao.AddRepository(repoRecord); err != nil {
|
|
log.Errorf("Error happens when adding the missing repository: %v", err)
|
|
} else {
|
|
log.Debugf("Add repository: %s success.", repoToAdd)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(reposToDel) > 0 {
|
|
log.Debugf("Start deleting repositories from DB... ")
|
|
for _, repoToDel := range reposToDel {
|
|
if err := dao.DeleteRepository(repoToDel); err != nil {
|
|
log.Errorf("Error happens when deleting the repository: %v", err)
|
|
} else {
|
|
log.Debugf("Delete repository: %s success.", repoToDel)
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Infof("Sync repositories from registry to DB is done.")
|
|
return nil
|
|
}
|
|
|
|
func catalog() ([]string, error) {
|
|
repositories := []string{}
|
|
|
|
rc, err := initRegistryClient()
|
|
if err != nil {
|
|
return repositories, err
|
|
}
|
|
|
|
repositories, err = rc.Catalog()
|
|
if err != nil {
|
|
return repositories, err
|
|
}
|
|
|
|
return repositories, nil
|
|
}
|
|
|
|
func diffRepos(reposInRegistry []string, reposInDB []string,
|
|
pm promgr.ProjectManager) ([]string, []string, error) {
|
|
var needsAdd []string
|
|
var needsDel []string
|
|
|
|
sort.Strings(reposInRegistry)
|
|
sort.Strings(reposInDB)
|
|
|
|
i, j := 0, 0
|
|
repoInR, repoInD := "", ""
|
|
for i < len(reposInRegistry) && j < len(reposInDB) {
|
|
repoInR = reposInRegistry[i]
|
|
repoInD = reposInDB[j]
|
|
d := strings.Compare(repoInR, repoInD)
|
|
if d < 0 {
|
|
i++
|
|
exist, err := projectExists(pm, repoInR)
|
|
if err != nil {
|
|
log.Errorf("failed to check the existence of project %s: %v", repoInR, err)
|
|
continue
|
|
}
|
|
|
|
if !exist {
|
|
continue
|
|
}
|
|
|
|
// TODO remove the workaround when the bug of registry is fixed
|
|
client, err := uiutils.NewRepositoryClientForUI("harbor-ui", repoInR)
|
|
if err != nil {
|
|
return needsAdd, needsDel, err
|
|
}
|
|
|
|
exist, err = repositoryExist(repoInR, client)
|
|
if err != nil {
|
|
return needsAdd, needsDel, err
|
|
}
|
|
|
|
if !exist {
|
|
continue
|
|
}
|
|
|
|
needsAdd = append(needsAdd, repoInR)
|
|
} else if d > 0 {
|
|
needsDel = append(needsDel, repoInD)
|
|
j++
|
|
} else {
|
|
// TODO remove the workaround when the bug of registry is fixed
|
|
client, err := uiutils.NewRepositoryClientForUI("harbor-ui", repoInR)
|
|
if err != nil {
|
|
return needsAdd, needsDel, err
|
|
}
|
|
|
|
exist, err := repositoryExist(repoInR, client)
|
|
if err != nil {
|
|
return needsAdd, needsDel, err
|
|
}
|
|
|
|
if !exist {
|
|
needsDel = append(needsDel, repoInD)
|
|
}
|
|
|
|
i++
|
|
j++
|
|
}
|
|
}
|
|
|
|
for i < len(reposInRegistry) {
|
|
repoInR = reposInRegistry[i]
|
|
i++
|
|
exist, err := projectExists(pm, repoInR)
|
|
if err != nil {
|
|
log.Errorf("failed to check whether project of %s exists: %v", repoInR, err)
|
|
continue
|
|
}
|
|
|
|
if !exist {
|
|
continue
|
|
}
|
|
|
|
client, err := uiutils.NewRepositoryClientForUI("harbor-ui", repoInR)
|
|
if err != nil {
|
|
log.Errorf("failed to create repository client: %v", err)
|
|
continue
|
|
}
|
|
|
|
exist, err = repositoryExist(repoInR, client)
|
|
if err != nil {
|
|
log.Errorf("failed to check the existence of repository %s: %v", repoInR, err)
|
|
continue
|
|
}
|
|
|
|
if !exist {
|
|
continue
|
|
}
|
|
|
|
needsAdd = append(needsAdd, repoInR)
|
|
}
|
|
|
|
for j < len(reposInDB) {
|
|
needsDel = append(needsDel, reposInDB[j])
|
|
j++
|
|
}
|
|
|
|
return needsAdd, needsDel, nil
|
|
}
|
|
|
|
func projectExists(pm promgr.ProjectManager, repository string) (bool, error) {
|
|
project, _ := utils.ParseRepository(repository)
|
|
return pm.Exists(project)
|
|
}
|
|
|
|
func initRegistryClient() (r *registry.Registry, err error) {
|
|
endpoint, err := config.RegistryURL()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
addr := endpoint
|
|
if strings.Contains(endpoint, "://") {
|
|
addr = strings.Split(endpoint, "://")[1]
|
|
}
|
|
|
|
if err := utils.TestTCPConn(addr, 60, 2); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
authorizer := auth.NewRawTokenAuthorizer("harbor-ui", token.Registry)
|
|
return registry.NewRegistry(endpoint, &http.Client{
|
|
Transport: registry.NewTransport(registry.GetHTTPTransport(), authorizer),
|
|
})
|
|
}
|
|
|
|
func buildReplicationURL() string {
|
|
url := config.InternalJobServiceURL()
|
|
return fmt.Sprintf("%s/api/jobs/replication", url)
|
|
}
|
|
|
|
func buildJobLogURL(jobID string, jobType string) string {
|
|
url := config.InternalJobServiceURL()
|
|
return fmt.Sprintf("%s/api/jobs/%s/%s/log", url, jobType, jobID)
|
|
}
|
|
|
|
func buildReplicationActionURL() string {
|
|
url := config.InternalJobServiceURL()
|
|
return fmt.Sprintf("%s/api/jobs/replication/actions", url)
|
|
}
|
|
|
|
func repositoryExist(name string, client *registry.Repository) (bool, error) {
|
|
tags, err := client.ListTag()
|
|
if err != nil {
|
|
if regErr, ok := err.(*registry_error.HTTPError); ok && regErr.StatusCode == http.StatusNotFound {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
return len(tags) != 0, nil
|
|
}
|
|
|
|
// transformVulnerabilities transforms the returned value of Clair API to a list of VulnerabilityItem
|
|
func transformVulnerabilities(layerWithVuln *models.ClairLayerEnvelope) []*models.VulnerabilityItem {
|
|
res := []*models.VulnerabilityItem{}
|
|
l := layerWithVuln.Layer
|
|
if l == nil {
|
|
return res
|
|
}
|
|
features := l.Features
|
|
if features == nil {
|
|
return res
|
|
}
|
|
for _, f := range features {
|
|
vulnerabilities := f.Vulnerabilities
|
|
if vulnerabilities == nil {
|
|
continue
|
|
}
|
|
for _, v := range vulnerabilities {
|
|
vItem := &models.VulnerabilityItem{
|
|
ID: v.Name,
|
|
Pkg: f.Name,
|
|
Version: f.Version,
|
|
Severity: clair.ParseClairSev(v.Severity),
|
|
Fixed: v.FixedBy,
|
|
Link: v.Link,
|
|
Description: v.Description,
|
|
}
|
|
res = append(res, vItem)
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
//Watch the configuration changes.
|
|
//Wrap the same method in common utils.
|
|
func watchConfigChanges(cfg map[string]interface{}) error {
|
|
return notifier.WatchConfigChanges(cfg)
|
|
}
|