2016-02-01 12:59:10 +01:00
|
|
|
/*
|
|
|
|
Copyright (c) 2016 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.
|
|
|
|
*/
|
2016-02-26 11:54:14 +01:00
|
|
|
|
2016-02-01 12:59:10 +01:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
2016-05-25 09:25:16 +02:00
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
2016-08-29 15:21:49 +02:00
|
|
|
"net"
|
2016-05-25 09:25:16 +02:00
|
|
|
"net/http"
|
2016-05-27 04:45:21 +02:00
|
|
|
"os"
|
2016-08-29 15:21:49 +02:00
|
|
|
"sort"
|
2016-05-25 09:25:16 +02:00
|
|
|
"strings"
|
2016-08-29 15:21:49 +02:00
|
|
|
"time"
|
2016-05-25 09:25:16 +02:00
|
|
|
|
2016-02-01 12:59:10 +01:00
|
|
|
"github.com/vmware/harbor/dao"
|
|
|
|
"github.com/vmware/harbor/models"
|
2016-08-23 09:56:30 +02:00
|
|
|
"github.com/vmware/harbor/service/cache"
|
|
|
|
"github.com/vmware/harbor/utils"
|
2016-03-28 02:50:09 +02:00
|
|
|
"github.com/vmware/harbor/utils/log"
|
2016-08-29 15:21:49 +02:00
|
|
|
"github.com/vmware/harbor/utils/registry"
|
2016-02-01 12:59:10 +01:00
|
|
|
)
|
|
|
|
|
2016-02-26 11:35:55 +01:00
|
|
|
func checkProjectPermission(userID int, projectID int64) bool {
|
2016-05-19 12:36:40 +02:00
|
|
|
roles, err := listRoles(userID, projectID)
|
2016-02-01 12:59:10 +01:00
|
|
|
if err != nil {
|
2016-05-19 12:36:40 +02:00
|
|
|
log.Errorf("error occurred in getProjectPermission: %v", err)
|
2016-02-01 12:59:10 +01:00
|
|
|
return false
|
|
|
|
}
|
2016-05-19 12:36:40 +02:00
|
|
|
return len(roles) > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func hasProjectAdminRole(userID int, projectID int64) bool {
|
|
|
|
roles, err := listRoles(userID, projectID)
|
2016-02-01 12:59:10 +01:00
|
|
|
if err != nil {
|
2016-05-19 12:36:40 +02:00
|
|
|
log.Errorf("error occurred in getProjectPermission: %v", err)
|
2016-02-01 12:59:10 +01:00
|
|
|
return false
|
|
|
|
}
|
2016-05-19 12:36:40 +02:00
|
|
|
|
|
|
|
for _, role := range roles {
|
|
|
|
if role.RoleID == models.PROJECTADMIN {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
//sysadmin has all privileges to all projects
|
|
|
|
func listRoles(userID int, projectID int64) ([]models.Role, error) {
|
2016-06-01 07:47:23 +02:00
|
|
|
roles := make([]models.Role, 0, 1)
|
2016-05-19 12:36:40 +02:00
|
|
|
isSysAdmin, err := dao.IsAdminRole(userID)
|
|
|
|
if err != nil {
|
2016-06-15 07:42:19 +02:00
|
|
|
log.Errorf("failed to determine whether the user %d is system admin: %v", userID, err)
|
2016-05-19 12:36:40 +02:00
|
|
|
return roles, err
|
|
|
|
}
|
|
|
|
if isSysAdmin {
|
|
|
|
role, err := dao.GetRoleByID(models.PROJECTADMIN)
|
|
|
|
if err != nil {
|
2016-06-15 07:42:19 +02:00
|
|
|
log.Errorf("failed to get role %d: %v", models.PROJECTADMIN, err)
|
2016-05-19 12:36:40 +02:00
|
|
|
return roles, err
|
|
|
|
}
|
|
|
|
roles = append(roles, *role)
|
|
|
|
return roles, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
rs, err := dao.GetUserProjectRoles(userID, projectID)
|
|
|
|
if err != nil {
|
2016-06-15 07:42:19 +02:00
|
|
|
log.Errorf("failed to get user %d 's roles for project %d: %v", userID, projectID, err)
|
2016-05-19 12:36:40 +02:00
|
|
|
return roles, err
|
|
|
|
}
|
|
|
|
roles = append(roles, rs...)
|
|
|
|
return roles, nil
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
|
|
|
|
2016-02-26 11:35:55 +01:00
|
|
|
func checkUserExists(name string) int {
|
2016-02-01 12:59:10 +01:00
|
|
|
u, err := dao.GetUser(models.User{Username: name})
|
|
|
|
if err != nil {
|
2016-03-28 02:50:09 +02:00
|
|
|
log.Errorf("Error occurred in GetUser, error: %v", err)
|
2016-02-01 12:59:10 +01:00
|
|
|
return 0
|
|
|
|
}
|
|
|
|
if u != nil {
|
2016-02-26 03:15:01 +01:00
|
|
|
return u.UserID
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
2016-05-25 09:25:16 +02:00
|
|
|
|
|
|
|
// TriggerReplication triggers the replication according to the policy
|
2016-05-27 04:45:21 +02:00
|
|
|
func TriggerReplication(policyID int64, repository string,
|
|
|
|
tags []string, operation string) error {
|
2016-05-25 09:25:16 +02:00
|
|
|
data := struct {
|
2016-05-27 04:45:21 +02:00
|
|
|
PolicyID int64 `json:"policy_id"`
|
|
|
|
Repo string `json:"repository"`
|
|
|
|
Operation string `json:"operation"`
|
|
|
|
TagList []string `json:"tags"`
|
2016-05-25 09:25:16 +02:00
|
|
|
}{
|
|
|
|
PolicyID: policyID,
|
|
|
|
Repo: repository,
|
2016-05-27 04:45:21 +02:00
|
|
|
TagList: tags,
|
2016-05-25 09:25:16 +02:00
|
|
|
Operation: operation,
|
|
|
|
}
|
|
|
|
|
2016-05-27 04:45:21 +02:00
|
|
|
b, err := json.Marshal(&data)
|
2016-05-25 09:25:16 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
url := buildReplicationURL()
|
|
|
|
|
2016-08-01 10:38:36 +02:00
|
|
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(b))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
addAuthentication(req)
|
|
|
|
|
|
|
|
client := &http.Client{}
|
|
|
|
resp, err := client.Do(req)
|
2016-05-25 09:25:16 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-07-14 11:50:25 +02:00
|
|
|
defer resp.Body.Close()
|
2016-05-25 09:25:16 +02:00
|
|
|
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPoliciesByRepository returns policies according the repository
|
|
|
|
func GetPoliciesByRepository(repository string) ([]*models.RepPolicy, error) {
|
|
|
|
repository = strings.TrimSpace(repository)
|
|
|
|
repository = strings.TrimRight(repository, "/")
|
|
|
|
projectName := repository[:strings.LastIndex(repository, "/")]
|
|
|
|
|
|
|
|
project, err := dao.GetProjectByName(projectName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
policies, err := dao.GetRepPolicyByProject(project.ProjectID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return policies, nil
|
|
|
|
}
|
|
|
|
|
2016-05-27 12:46:07 +02:00
|
|
|
// TriggerReplicationByRepository triggers the replication according to the repository
|
2016-05-27 04:45:21 +02:00
|
|
|
func TriggerReplicationByRepository(repository string, tags []string, operation string) {
|
2016-05-25 09:25:16 +02:00
|
|
|
policies, err := GetPoliciesByRepository(repository)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("failed to get policies for repository %s: %v", repository, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, policy := range policies {
|
2016-07-13 11:19:23 +02:00
|
|
|
if policy.Enabled == 0 {
|
|
|
|
continue
|
|
|
|
}
|
2016-05-27 09:04:20 +02:00
|
|
|
if err := TriggerReplication(policy.ID, repository, tags, operation); err != nil {
|
2016-05-30 09:39:51 +02:00
|
|
|
log.Errorf("failed to trigger replication of policy %d for %s: %v", policy.ID, repository, err)
|
2016-05-25 09:25:16 +02:00
|
|
|
} else {
|
2016-05-30 09:39:51 +02:00
|
|
|
log.Infof("replication of policy %d for %s triggered", policy.ID, repository)
|
2016-05-25 09:25:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-02 12:17:00 +02:00
|
|
|
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()
|
|
|
|
|
2016-08-01 10:38:36 +02:00
|
|
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer(b))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
addAuthentication(req)
|
|
|
|
|
|
|
|
client := &http.Client{}
|
|
|
|
|
|
|
|
resp, err := client.Do(req)
|
2016-06-02 12:17:00 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-07-14 11:50:25 +02:00
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2016-06-02 12:17:00 +02:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2016-08-01 10:38:36 +02:00
|
|
|
func addAuthentication(req *http.Request) {
|
|
|
|
if req != nil {
|
|
|
|
req.AddCookie(&http.Cookie{
|
|
|
|
Name: models.UISecretCookie,
|
|
|
|
// TODO read secret from config
|
|
|
|
Value: os.Getenv("UI_SECRET"),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-29 15:21:49 +02:00
|
|
|
// SyncRegistry syncs the repositories of registry with database.
|
|
|
|
func SyncRegistry() error {
|
|
|
|
|
|
|
|
log.Debugf("Start syncing repositories from registry to DB... ")
|
|
|
|
rc, err := initRegistryClient()
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("error occurred while initializing registry client for %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
reposInRegistry, err := rc.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 = diffRepos(reposInRegistry, reposInDB)
|
|
|
|
|
|
|
|
if len(reposToAdd) > 0 {
|
|
|
|
log.Debugf("Start adding repositories into DB... ")
|
|
|
|
for _, repoToAdd := range reposToAdd {
|
|
|
|
project, _ := utils.ParseRepository(repoToAdd)
|
|
|
|
user, err := dao.GetAccessLogCreator(repoToAdd)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Error happens when getting the repository owner from access log: %v", err)
|
|
|
|
}
|
|
|
|
if len(user) == 0 {
|
|
|
|
user = "anonymous"
|
|
|
|
}
|
|
|
|
pullCount, err := dao.CountPull(repoToAdd)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Error happens when counting pull count from access log: %v", err)
|
|
|
|
}
|
|
|
|
repoRecord := models.RepoRecord{Name: repoToAdd, OwnerName: user, ProjectName: project, PullCount: pullCount}
|
|
|
|
if err := dao.AddRepository(repoRecord); err != nil {
|
|
|
|
log.Errorf("Error happens when adding the missing repository: %v", err)
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
log.Debugf("Delete repository: %s success.", repoToDel)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Debugf("Sync repositories from registry to DB is done.")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func diffRepos(reposInRegistry []string, reposInDB []string) ([]string, []string) {
|
|
|
|
var needsAdd []string
|
|
|
|
var needsDel []string
|
|
|
|
|
|
|
|
sort.Strings(reposInRegistry)
|
|
|
|
sort.Strings(reposInDB)
|
|
|
|
|
|
|
|
i, j := 0, 0
|
|
|
|
for i < len(reposInRegistry) && j < len(reposInDB) {
|
|
|
|
d := strings.Compare(reposInRegistry[i], reposInDB[j])
|
|
|
|
if d < 0 {
|
|
|
|
needsAdd = append(needsAdd, reposInRegistry[i])
|
|
|
|
i++
|
|
|
|
} else if d > 0 {
|
|
|
|
needsDel = append(needsDel, reposInDB[j])
|
|
|
|
j++
|
|
|
|
} else {
|
|
|
|
i++
|
|
|
|
j++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i < len(reposInRegistry) {
|
|
|
|
needsAdd = append(needsAdd, reposInRegistry[i])
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
|
|
|
|
for j < len(reposInDB) {
|
|
|
|
needsDel = append(needsDel, reposInDB[j])
|
|
|
|
j++
|
|
|
|
}
|
|
|
|
|
|
|
|
return needsAdd, needsDel
|
|
|
|
}
|
|
|
|
|
|
|
|
func initRegistryClient() (r *registry.Registry, err error) {
|
|
|
|
endpoint := os.Getenv("REGISTRY_URL")
|
|
|
|
|
|
|
|
addr := ""
|
|
|
|
if strings.Contains(endpoint, "/") {
|
|
|
|
addr = endpoint[strings.LastIndex(endpoint, "/")+1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
ch := make(chan int, 1)
|
|
|
|
go func() {
|
|
|
|
var err error
|
|
|
|
var c net.Conn
|
|
|
|
for {
|
|
|
|
c, err = net.DialTimeout("tcp", addr, 20*time.Second)
|
|
|
|
if err == nil {
|
|
|
|
c.Close()
|
|
|
|
ch <- 1
|
|
|
|
} else {
|
|
|
|
log.Errorf("failed to connect to registry client, retry after 2 seconds :%v", err)
|
|
|
|
time.Sleep(2 * time.Second)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
select {
|
|
|
|
case <-ch:
|
|
|
|
case <-time.After(60 * time.Second):
|
|
|
|
panic("Failed to connect to registry client after 60 seconds")
|
|
|
|
}
|
|
|
|
|
|
|
|
registryClient, err := cache.NewRegistryClient(endpoint, true, "admin",
|
|
|
|
"registry", "catalog", "*")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return registryClient, nil
|
|
|
|
}
|
|
|
|
|
2016-05-25 09:25:16 +02:00
|
|
|
func buildReplicationURL() string {
|
2016-05-27 04:45:21 +02:00
|
|
|
url := getJobServiceURL()
|
2016-05-30 09:39:51 +02:00
|
|
|
return fmt.Sprintf("%s/api/jobs/replication", url)
|
2016-05-25 09:25:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func buildJobLogURL(jobID string) string {
|
2016-05-27 04:45:21 +02:00
|
|
|
url := getJobServiceURL()
|
2016-05-30 09:39:51 +02:00
|
|
|
return fmt.Sprintf("%s/api/jobs/replication/%s/log", url, jobID)
|
2016-05-27 04:45:21 +02:00
|
|
|
}
|
|
|
|
|
2016-06-02 12:17:00 +02:00
|
|
|
func buildReplicationActionURL() string {
|
|
|
|
url := getJobServiceURL()
|
|
|
|
return fmt.Sprintf("%s/api/jobs/replication/actions", url)
|
|
|
|
}
|
|
|
|
|
2016-05-27 04:45:21 +02:00
|
|
|
func getJobServiceURL() string {
|
|
|
|
url := os.Getenv("JOB_SERVICE_URL")
|
2016-06-02 12:17:00 +02:00
|
|
|
url = strings.TrimSpace(url)
|
|
|
|
url = strings.TrimRight(url, "/")
|
|
|
|
|
2016-05-27 04:45:21 +02:00
|
|
|
if len(url) == 0 {
|
2016-05-30 09:39:51 +02:00
|
|
|
url = "http://jobservice"
|
2016-05-27 04:45:21 +02:00
|
|
|
}
|
2016-06-02 12:17:00 +02:00
|
|
|
|
2016-05-27 04:45:21 +02:00
|
|
|
return url
|
2016-05-25 09:25:16 +02:00
|
|
|
}
|
2016-08-23 09:56:30 +02:00
|
|
|
|
|
|
|
func getReposByProject(name string, keyword ...string) ([]string, error) {
|
|
|
|
repositories := []string{}
|
|
|
|
|
|
|
|
list, err := getAllRepos()
|
|
|
|
if err != nil {
|
|
|
|
return repositories, err
|
|
|
|
}
|
|
|
|
|
|
|
|
project := ""
|
|
|
|
rest := ""
|
|
|
|
for _, repository := range list {
|
|
|
|
project, rest = utils.ParseRepository(repository)
|
|
|
|
if project != name {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(keyword) > 0 && len(keyword[0]) != 0 &&
|
|
|
|
!strings.Contains(rest, keyword[0]) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
repositories = append(repositories, repository)
|
|
|
|
}
|
|
|
|
|
|
|
|
return repositories, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getAllRepos() ([]string, error) {
|
|
|
|
return cache.GetRepoFromCache()
|
|
|
|
}
|