mirror of
https://github.com/goharbor/harbor.git
synced 2024-09-29 05:47:31 +02:00
Merge pull request #2283 from ywk253100/170509_repo_api
Refactor repository API
This commit is contained in:
commit
161cbea40b
@ -656,11 +656,6 @@ paths:
|
|||||||
format: int32
|
format: int32
|
||||||
required: true
|
required: true
|
||||||
description: Relevant project ID.
|
description: Relevant project ID.
|
||||||
- name: detail
|
|
||||||
in: query
|
|
||||||
type: boolean
|
|
||||||
required: false
|
|
||||||
description: Get detail info or not.
|
|
||||||
- name: q
|
- name: q
|
||||||
in: query
|
in: query
|
||||||
type: string
|
type: string
|
||||||
@ -682,7 +677,7 @@ paths:
|
|||||||
- Products
|
- Products
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: If detail is false, the response body is a string array which contains the names of repositories, or the response body contains an object array as described in schema.
|
description: Get repositories successfully.
|
||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
@ -702,6 +697,30 @@ paths:
|
|||||||
description: Project ID does not exist.
|
description: Project ID does not exist.
|
||||||
500:
|
500:
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
|
/repositories/{repo_name}:
|
||||||
|
delete:
|
||||||
|
summary: Delete a repository.
|
||||||
|
description: |
|
||||||
|
This endpoint let user delete a repository with name.
|
||||||
|
parameters:
|
||||||
|
- name: repo_name
|
||||||
|
in: path
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: The name of repository which will be deleted.
|
||||||
|
tags:
|
||||||
|
- Products
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Delete successfully.
|
||||||
|
400:
|
||||||
|
description: Invalid repo_name.
|
||||||
|
401:
|
||||||
|
description: Unauthorized.
|
||||||
|
404:
|
||||||
|
description: Repository not found.
|
||||||
|
403:
|
||||||
|
description: Forbidden.
|
||||||
/repositories/{repo_name}/tags/{tag}:
|
/repositories/{repo_name}/tags/{tag}:
|
||||||
delete:
|
delete:
|
||||||
summary: Delete a tag in a repository.
|
summary: Delete a tag in a repository.
|
||||||
@ -742,45 +761,17 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
required: true
|
required: true
|
||||||
description: Relevant repository name.
|
description: Relevant repository name.
|
||||||
- name: detail
|
|
||||||
in: query
|
|
||||||
type: boolean
|
|
||||||
required: false
|
|
||||||
description: If detail is true, the manifests is returned too.
|
|
||||||
tags:
|
tags:
|
||||||
- Products
|
- Products
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: If detail is false, the response body is a string array, or the response body contains the manifest informations as described in schema.
|
description: Get tags successfully.
|
||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/DetailedTag'
|
$ref: '#/definitions/DetailedTag'
|
||||||
500:
|
500:
|
||||||
description: Unexpected internal errors.
|
description: Unexpected internal errors.
|
||||||
delete:
|
|
||||||
summary: Delete all tags of a repository.
|
|
||||||
description: |
|
|
||||||
This endpoint let user delete all tags with repo name.
|
|
||||||
parameters:
|
|
||||||
- name: repo_name
|
|
||||||
in: path
|
|
||||||
type: string
|
|
||||||
required: true
|
|
||||||
description: The name of repository which will be deleted.
|
|
||||||
tags:
|
|
||||||
- Products
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: Delete successfully.
|
|
||||||
400:
|
|
||||||
description: Invalid repo_name.
|
|
||||||
401:
|
|
||||||
description: Unauthorized.
|
|
||||||
404:
|
|
||||||
description: Repository not found.
|
|
||||||
403:
|
|
||||||
description: Forbidden.
|
|
||||||
/repositories/{repo_name}/tags/{tag}/manifest:
|
/repositories/{repo_name}/tags/{tag}/manifest:
|
||||||
get:
|
get:
|
||||||
summary: Get manifests of a relevant repository.
|
summary: Get manifests of a relevant repository.
|
||||||
@ -851,16 +842,11 @@ paths:
|
|||||||
format: int32
|
format: int32
|
||||||
required: false
|
required: false
|
||||||
description: The number of the requested public repositories, default is 10 if not provided.
|
description: The number of the requested public repositories, default is 10 if not provided.
|
||||||
- name: detail
|
|
||||||
in: query
|
|
||||||
type: boolean
|
|
||||||
required: false
|
|
||||||
description: Get detail info or not.
|
|
||||||
tags:
|
tags:
|
||||||
- Products
|
- Products
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: If detail is true, the response is described as the schema, or the response contains a TopRepo array.
|
description: Get popular repositories successfully.
|
||||||
schema:
|
schema:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
|
@ -39,6 +39,36 @@ type BaseAPI struct {
|
|||||||
beego.Controller
|
beego.Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleNotFound ...
|
||||||
|
func (b *BaseAPI) HandleNotFound(text string) {
|
||||||
|
log.Info(text)
|
||||||
|
b.RenderError(http.StatusNotFound, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleUnauthorized ...
|
||||||
|
func (b *BaseAPI) HandleUnauthorized() {
|
||||||
|
log.Info("unauthorized")
|
||||||
|
b.RenderError(http.StatusUnauthorized, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleForbidden ...
|
||||||
|
func (b *BaseAPI) HandleForbidden(username string) {
|
||||||
|
log.Info("forbidden: %s", username)
|
||||||
|
b.RenderError(http.StatusForbidden, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleBadRequest ...
|
||||||
|
func (b *BaseAPI) HandleBadRequest(text string) {
|
||||||
|
log.Info(text)
|
||||||
|
b.RenderError(http.StatusBadRequest, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleInternalServerError ...
|
||||||
|
func (b *BaseAPI) HandleInternalServerError(text string) {
|
||||||
|
log.Error(text)
|
||||||
|
b.RenderError(http.StatusInternalServerError, "")
|
||||||
|
}
|
||||||
|
|
||||||
// Render returns nil as it won't render template
|
// Render returns nil as it won't render template
|
||||||
func (b *BaseAPI) Render() error {
|
func (b *BaseAPI) Render() error {
|
||||||
return nil
|
return nil
|
||||||
@ -83,6 +113,7 @@ func (b *BaseAPI) DecodeJSONReqAndValidate(v interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateUser checks if the request triggered by a valid user
|
// ValidateUser checks if the request triggered by a valid user
|
||||||
|
// TODO remove
|
||||||
func (b *BaseAPI) ValidateUser() int {
|
func (b *BaseAPI) ValidateUser() int {
|
||||||
userID, needsCheck, ok := b.GetUserIDForRequest()
|
userID, needsCheck, ok := b.GetUserIDForRequest()
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -103,28 +103,15 @@ func GetRepositoryByProjectName(name string) ([]*models.RepoRecord, error) {
|
|||||||
return repos, err
|
return repos, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//GetTopRepos returns the most popular repositories
|
//GetTopRepos returns the most popular repositories whose project ID is
|
||||||
func GetTopRepos(userID int, count int) ([]*models.RepoRecord, error) {
|
// in projectIDs
|
||||||
sql :=
|
func GetTopRepos(projectIDs []int64, n int) ([]*models.RepoRecord, error) {
|
||||||
`select r.repository_id, r.name,
|
|
||||||
r.project_id, r.description, r.pull_count,
|
|
||||||
r.star_count, r.creation_time, r.update_time
|
|
||||||
from repository r
|
|
||||||
inner join project p on r.project_id = p.project_id
|
|
||||||
where (
|
|
||||||
p.deleted = 0 and (
|
|
||||||
p.public = 1 or (
|
|
||||||
? <> ? and (
|
|
||||||
exists (
|
|
||||||
select 1 from user u
|
|
||||||
where u.user_id = ? and u.sysadmin_flag = 1
|
|
||||||
) or exists (
|
|
||||||
select 1 from project_member pm
|
|
||||||
where pm.project_id = p.project_id and pm.user_id = ?
|
|
||||||
)))))
|
|
||||||
order by r.pull_count desc, r.name limit ?`
|
|
||||||
repositories := []*models.RepoRecord{}
|
repositories := []*models.RepoRecord{}
|
||||||
_, err := GetOrmer().Raw(sql, userID, NonExistUserID, userID, userID, count).QueryRows(&repositories)
|
_, err := GetOrmer().QueryTable(&models.RepoRecord{}).
|
||||||
|
Filter("project_id__in", projectIDs).
|
||||||
|
OrderBy("-pull_count").
|
||||||
|
Limit(n).
|
||||||
|
All(&repositories)
|
||||||
|
|
||||||
return repositories, err
|
return repositories, err
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ package dao
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
@ -170,47 +169,25 @@ func TestGetTopRepos(t *testing.T) {
|
|||||||
require.NoError(GetOrmer().Rollback())
|
require.NoError(GetOrmer().Rollback())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
admin, err := GetUser(models.User{Username: "admin"})
|
projectIDs := []int64{}
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
user := models.User{
|
|
||||||
Username: "user",
|
|
||||||
Password: "user",
|
|
||||||
Email: "user@test.com",
|
|
||||||
}
|
|
||||||
userID, err := Register(user)
|
|
||||||
require.NoError(err)
|
|
||||||
user.UserID = int(userID)
|
|
||||||
|
|
||||||
//
|
|
||||||
// public project with 1 repository
|
|
||||||
// non-public project with 2 repositories visible by admin
|
|
||||||
// non-public project with 1 repository visible by admin and user
|
|
||||||
// deleted public project with 1 repository
|
|
||||||
//
|
|
||||||
|
|
||||||
project1 := models.Project{
|
project1 := models.Project{
|
||||||
OwnerID: admin.UserID,
|
OwnerID: 1,
|
||||||
Name: "project1",
|
Name: "project1",
|
||||||
CreationTime: time.Now(),
|
Public: 0,
|
||||||
OwnerName: admin.Username,
|
|
||||||
Public: 0,
|
|
||||||
}
|
}
|
||||||
project1.ProjectID, err = AddProject(project1)
|
project1.ProjectID, err = AddProject(project1)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
projectIDs = append(projectIDs, project1.ProjectID)
|
||||||
|
|
||||||
project2 := models.Project{
|
project2 := models.Project{
|
||||||
OwnerID: user.UserID,
|
OwnerID: 1,
|
||||||
Name: "project2",
|
Name: "project2",
|
||||||
CreationTime: time.Now(),
|
Public: 0,
|
||||||
OwnerName: user.Username,
|
|
||||||
Public: 0,
|
|
||||||
}
|
}
|
||||||
project2.ProjectID, err = AddProject(project2)
|
project2.ProjectID, err = AddProject(project2)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
projectIDs = append(projectIDs, project2.ProjectID)
|
||||||
err = AddRepository(*repository)
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
repository1 := &models.RepoRecord{
|
repository1 := &models.RepoRecord{
|
||||||
Name: fmt.Sprintf("%v/repository1", project1.Name),
|
Name: fmt.Sprintf("%v/repository1", project1.Name),
|
||||||
@ -219,8 +196,6 @@ func TestGetTopRepos(t *testing.T) {
|
|||||||
err = AddRepository(*repository1)
|
err = AddRepository(*repository1)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
require.NoError(IncreasePullCount(repository1.Name))
|
require.NoError(IncreasePullCount(repository1.Name))
|
||||||
repository1, err = GetRepositoryByName(repository1.Name)
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
repository2 := &models.RepoRecord{
|
repository2 := &models.RepoRecord{
|
||||||
Name: fmt.Sprintf("%v/repository2", project1.Name),
|
Name: fmt.Sprintf("%v/repository2", project1.Name),
|
||||||
@ -230,8 +205,6 @@ func TestGetTopRepos(t *testing.T) {
|
|||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
require.NoError(IncreasePullCount(repository2.Name))
|
require.NoError(IncreasePullCount(repository2.Name))
|
||||||
require.NoError(IncreasePullCount(repository2.Name))
|
require.NoError(IncreasePullCount(repository2.Name))
|
||||||
repository2, err = GetRepositoryByName(repository2.Name)
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
repository3 := &models.RepoRecord{
|
repository3 := &models.RepoRecord{
|
||||||
Name: fmt.Sprintf("%v/repository3", project2.Name),
|
Name: fmt.Sprintf("%v/repository3", project2.Name),
|
||||||
@ -242,49 +215,11 @@ func TestGetTopRepos(t *testing.T) {
|
|||||||
require.NoError(IncreasePullCount(repository3.Name))
|
require.NoError(IncreasePullCount(repository3.Name))
|
||||||
require.NoError(IncreasePullCount(repository3.Name))
|
require.NoError(IncreasePullCount(repository3.Name))
|
||||||
require.NoError(IncreasePullCount(repository3.Name))
|
require.NoError(IncreasePullCount(repository3.Name))
|
||||||
repository3, err = GetRepositoryByName(repository3.Name)
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
deletedPublicProject := models.Project{
|
topRepos, err := GetTopRepos(projectIDs, 100)
|
||||||
OwnerID: admin.UserID,
|
|
||||||
Name: "public-deleted",
|
|
||||||
CreationTime: time.Now(),
|
|
||||||
OwnerName: admin.Username,
|
|
||||||
Public: 1,
|
|
||||||
}
|
|
||||||
deletedPublicProject.ProjectID, err = AddProject(deletedPublicProject)
|
|
||||||
require.NoError(err)
|
|
||||||
deletedPublicRepository1 := &models.RepoRecord{
|
|
||||||
Name: fmt.Sprintf("%v/repository1", deletedPublicProject.Name),
|
|
||||||
ProjectID: deletedPublicProject.ProjectID,
|
|
||||||
}
|
|
||||||
err = AddRepository(*deletedPublicRepository1)
|
|
||||||
require.NoError(err)
|
|
||||||
err = DeleteProject(deletedPublicProject.ProjectID)
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
var topRepos []*models.RepoRecord
|
|
||||||
|
|
||||||
// anonymous should retrieve public non-deleted repositories
|
|
||||||
topRepos, err = GetTopRepos(NonExistUserID, 100)
|
|
||||||
require.NoError(err)
|
|
||||||
require.Len(topRepos, 1)
|
|
||||||
require.Equal(topRepos[0].Name, repository.Name)
|
|
||||||
|
|
||||||
// admin should retrieve all repositories
|
|
||||||
topRepos, err = GetTopRepos(admin.UserID, 100)
|
|
||||||
require.NoError(err)
|
|
||||||
require.Len(topRepos, 4)
|
|
||||||
|
|
||||||
// user should retrieve visible repositories
|
|
||||||
topRepos, err = GetTopRepos(user.UserID, 100)
|
|
||||||
require.NoError(err)
|
|
||||||
require.Len(topRepos, 2)
|
|
||||||
|
|
||||||
// limit by count
|
|
||||||
topRepos, err = GetTopRepos(admin.UserID, 3)
|
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
require.Len(topRepos, 3)
|
require.Len(topRepos, 3)
|
||||||
|
require.Equal(topRepos[0].Name, repository3.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetTotalOfRepositoriesByProject(t *testing.T) {
|
func TestGetTotalOfRepositoriesByProject(t *testing.T) {
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
// 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 models
|
|
||||||
|
|
||||||
// TopRepo holds information about repository that accessed most
|
|
||||||
type TopRepo struct {
|
|
||||||
RepoName string `json:"name"`
|
|
||||||
AccessCount int64 `json:"count"`
|
|
||||||
// Creator string `json:"creator"`
|
|
||||||
}
|
|
@ -33,6 +33,18 @@ func (f *fakePM) IsPublic(projectIDOrName interface{}) bool {
|
|||||||
func (f *fakePM) GetRoles(username string, projectIDOrName interface{}) []int {
|
func (f *fakePM) GetRoles(username string, projectIDOrName interface{}) []int {
|
||||||
return f.roles[projectIDOrName.(string)]
|
return f.roles[projectIDOrName.(string)]
|
||||||
}
|
}
|
||||||
|
func (f *fakePM) Get(projectIDOrName interface{}) *models.Project {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (f *fakePM) Exist(projectIDOrName interface{}) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func (f *fakePM) GetPublic() []models.Project {
|
||||||
|
return []models.Project{}
|
||||||
|
}
|
||||||
|
func (f *fakePM) GetByMember(username string) []models.Project {
|
||||||
|
return []models.Project{}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIsAuthenticated(t *testing.T) {
|
func TestIsAuthenticated(t *testing.T) {
|
||||||
// unauthenticated
|
// unauthenticated
|
||||||
|
54
src/ui/api/base.go
Normal file
54
src/ui/api/base.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// 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 (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/vmware/harbor/src/common/api"
|
||||||
|
"github.com/vmware/harbor/src/common/security"
|
||||||
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
|
"github.com/vmware/harbor/src/ui/filter"
|
||||||
|
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BaseController ...
|
||||||
|
type BaseController struct {
|
||||||
|
api.BaseAPI
|
||||||
|
// SecurityCtx is the security context used to authN &authZ
|
||||||
|
SecurityCtx security.Context
|
||||||
|
// ProjectMgr is the project manager which abstracts the operations
|
||||||
|
// related to projects
|
||||||
|
ProjectMgr projectmanager.ProjectManager
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare inits security context and project manager from beego
|
||||||
|
// context
|
||||||
|
func (b *BaseController) Prepare() {
|
||||||
|
ok := false
|
||||||
|
ctx := b.Ctx.Input.GetData(filter.HarborSecurityContext)
|
||||||
|
b.SecurityCtx, ok = ctx.(security.Context)
|
||||||
|
if !ok {
|
||||||
|
log.Error("failed to get security context")
|
||||||
|
b.CustomAbort(http.StatusInternalServerError, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
pm := b.Ctx.Input.GetData(filter.HarborProjectManager)
|
||||||
|
b.ProjectMgr, ok = pm.(projectmanager.ProjectManager)
|
||||||
|
if !ok {
|
||||||
|
log.Error("failed to get project manager")
|
||||||
|
b.CustomAbort(http.StatusInternalServerError, "")
|
||||||
|
}
|
||||||
|
}
|
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils"
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
|
"github.com/vmware/harbor/src/ui/filter"
|
||||||
"github.com/vmware/harbor/tests/apitests/apilib"
|
"github.com/vmware/harbor/tests/apitests/apilib"
|
||||||
// "strconv"
|
// "strconv"
|
||||||
// "strings"
|
// "strings"
|
||||||
@ -86,6 +87,8 @@ func init() {
|
|||||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||||
beego.TestBeegoInit(apppath)
|
beego.TestBeegoInit(apppath)
|
||||||
|
|
||||||
|
beego.InsertFilter("/*", beego.BeforeRouter, filter.SecurityFilter)
|
||||||
|
|
||||||
beego.Router("/api/search/", &SearchAPI{})
|
beego.Router("/api/search/", &SearchAPI{})
|
||||||
beego.Router("/api/projects/", &ProjectAPI{}, "get:List;post:Post;head:Head")
|
beego.Router("/api/projects/", &ProjectAPI{}, "get:List;post:Post;head:Head")
|
||||||
beego.Router("/api/projects/:id", &ProjectAPI{}, "delete:Delete;get:Get")
|
beego.Router("/api/projects/:id", &ProjectAPI{}, "delete:Delete;get:Get")
|
||||||
@ -473,8 +476,8 @@ func (a testapi) PutProjectMember(authInfo usrInfo, projectID string, userID str
|
|||||||
|
|
||||||
//-------------------------Repositories Test---------------------------------------//
|
//-------------------------Repositories Test---------------------------------------//
|
||||||
//Return relevant repos of projectID
|
//Return relevant repos of projectID
|
||||||
func (a testapi) GetRepos(authInfo usrInfo, projectID,
|
func (a testapi) GetRepos(authInfo usrInfo, projectID, keyword string) (
|
||||||
keyword, detail string) (int, interface{}, error) {
|
int, interface{}, error) {
|
||||||
_sling := sling.New().Get(a.basePath)
|
_sling := sling.New().Get(a.basePath)
|
||||||
|
|
||||||
path := "/api/repositories/"
|
path := "/api/repositories/"
|
||||||
@ -483,13 +486,11 @@ func (a testapi) GetRepos(authInfo usrInfo, projectID,
|
|||||||
|
|
||||||
type QueryParams struct {
|
type QueryParams struct {
|
||||||
ProjectID string `url:"project_id"`
|
ProjectID string `url:"project_id"`
|
||||||
Detail string `url:"detail"`
|
|
||||||
Keyword string `url:"q"`
|
Keyword string `url:"q"`
|
||||||
}
|
}
|
||||||
|
|
||||||
_sling = _sling.QueryStruct(&QueryParams{
|
_sling = _sling.QueryStruct(&QueryParams{
|
||||||
ProjectID: projectID,
|
ProjectID: projectID,
|
||||||
Detail: detail,
|
|
||||||
Keyword: keyword,
|
Keyword: keyword,
|
||||||
})
|
})
|
||||||
code, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
code, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||||
@ -498,15 +499,7 @@ func (a testapi) GetRepos(authInfo usrInfo, projectID,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if code == http.StatusOK {
|
if code == http.StatusOK {
|
||||||
if detail == "1" || detail == "true" {
|
repositories := []repoResp{}
|
||||||
repositories := []repoResp{}
|
|
||||||
if err = json.Unmarshal(body, &repositories); err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
return code, repositories, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories := []string{}
|
|
||||||
if err = json.Unmarshal(body, &repositories); err != nil {
|
if err = json.Unmarshal(body, &repositories); err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
@ -517,21 +510,13 @@ func (a testapi) GetRepos(authInfo usrInfo, projectID,
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Get tags of a relevant repository
|
//Get tags of a relevant repository
|
||||||
func (a testapi) GetReposTags(authInfo usrInfo, repoName,
|
func (a testapi) GetReposTags(authInfo usrInfo, repoName string) (int, interface{}, error) {
|
||||||
detail string) (int, interface{}, error) {
|
|
||||||
_sling := sling.New().Get(a.basePath)
|
_sling := sling.New().Get(a.basePath)
|
||||||
|
|
||||||
path := fmt.Sprintf("/api/repositories/%s/tags", repoName)
|
path := fmt.Sprintf("/api/repositories/%s/tags", repoName)
|
||||||
|
|
||||||
_sling = _sling.Path(path)
|
_sling = _sling.Path(path)
|
||||||
|
|
||||||
type QueryParams struct {
|
|
||||||
Detail string `url:"detail"`
|
|
||||||
}
|
|
||||||
|
|
||||||
_sling = _sling.QueryStruct(&QueryParams{
|
|
||||||
Detail: detail,
|
|
||||||
})
|
|
||||||
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
@ -541,15 +526,7 @@ func (a testapi) GetReposTags(authInfo usrInfo, repoName,
|
|||||||
return httpStatusCode, body, nil
|
return httpStatusCode, body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if detail == "true" || detail == "1" {
|
result := []tagResp{}
|
||||||
result := []detailedTagResp{}
|
|
||||||
if err := json.Unmarshal(body, &result); err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
return http.StatusOK, result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result := []string{}
|
|
||||||
if err := json.Unmarshal(body, &result); err != nil {
|
if err := json.Unmarshal(body, &result); err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
@ -569,8 +546,7 @@ func (a testapi) GetReposManifests(authInfo usrInfo, repoName string, tag string
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Get public repositories which are accessed most
|
//Get public repositories which are accessed most
|
||||||
func (a testapi) GetReposTop(authInfo usrInfo, count,
|
func (a testapi) GetReposTop(authInfo usrInfo, count string) (int, interface{}, error) {
|
||||||
detail string) (int, interface{}, error) {
|
|
||||||
_sling := sling.New().Get(a.basePath)
|
_sling := sling.New().Get(a.basePath)
|
||||||
|
|
||||||
path := "/api/repositories/top"
|
path := "/api/repositories/top"
|
||||||
@ -578,13 +554,11 @@ func (a testapi) GetReposTop(authInfo usrInfo, count,
|
|||||||
_sling = _sling.Path(path)
|
_sling = _sling.Path(path)
|
||||||
|
|
||||||
type QueryParams struct {
|
type QueryParams struct {
|
||||||
Count string `url:"count"`
|
Count string `url:"count"`
|
||||||
Detail string `url:"detail"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_sling = _sling.QueryStruct(&QueryParams{
|
_sling = _sling.QueryStruct(&QueryParams{
|
||||||
Count: count,
|
Count: count,
|
||||||
Detail: detail,
|
|
||||||
})
|
})
|
||||||
code, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
code, body, err := request(_sling, jsonAcceptHeader, authInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -595,15 +569,7 @@ func (a testapi) GetReposTop(authInfo usrInfo, count,
|
|||||||
return code, body, err
|
return code, body, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if detail == "true" || detail == "1" {
|
result := []*repoResp{}
|
||||||
result := []*repoResp{}
|
|
||||||
if err = json.Unmarshal(body, &result); err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
return http.StatusOK, result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result := []*models.TopRepo{}
|
|
||||||
if err = json.Unmarshal(body, &result); err != nil {
|
if err = json.Unmarshal(body, &result); err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
@ -23,23 +23,20 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
"github.com/docker/distribution/manifest/schema2"
|
"github.com/docker/distribution/manifest/schema2"
|
||||||
"github.com/vmware/harbor/src/common/api"
|
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/utils"
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/common/utils/notary"
|
"github.com/vmware/harbor/src/common/utils/notary"
|
||||||
"github.com/vmware/harbor/src/common/utils/registry"
|
"github.com/vmware/harbor/src/common/utils/registry"
|
||||||
"github.com/vmware/harbor/src/common/utils/registry/auth"
|
|
||||||
registry_error "github.com/vmware/harbor/src/common/utils/registry/error"
|
registry_error "github.com/vmware/harbor/src/common/utils/registry/error"
|
||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
svc_utils "github.com/vmware/harbor/src/ui/service/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RepositoryAPI handles request to /api/repositories /api/repositories/tags /api/repositories/manifests, the parm has to be put
|
// RepositoryAPI handles request to /api/repositories /api/repositories/tags /api/repositories/manifests, the parm has to be put
|
||||||
// in the query string as the web framework can not parse the URL if it contains veriadic sectors.
|
// in the query string as the web framework can not parse the URL if it contains veriadic sectors.
|
||||||
type RepositoryAPI struct {
|
type RepositoryAPI struct {
|
||||||
api.BaseAPI
|
BaseController
|
||||||
}
|
}
|
||||||
|
|
||||||
type repoResp struct {
|
type repoResp struct {
|
||||||
@ -54,7 +51,7 @@ type repoResp struct {
|
|||||||
UpdateTime time.Time `json:"update_time"`
|
UpdateTime time.Time `json:"update_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type detailedTagResp struct {
|
type tagResp struct {
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
Manifest interface{} `json:"manifest"`
|
Manifest interface{} `json:"manifest"`
|
||||||
}
|
}
|
||||||
@ -68,50 +65,40 @@ type manifestResp struct {
|
|||||||
func (ra *RepositoryAPI) Get() {
|
func (ra *RepositoryAPI) Get() {
|
||||||
projectID, err := ra.GetInt64("project_id")
|
projectID, err := ra.GetInt64("project_id")
|
||||||
if err != nil || projectID <= 0 {
|
if err != nil || projectID <= 0 {
|
||||||
ra.CustomAbort(http.StatusBadRequest, "invalid project_id")
|
ra.HandleBadRequest(fmt.Sprintf("invalid project_id %s", ra.GetString("project_id")))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
project, err := dao.GetProjectByID(projectID)
|
if !ra.ProjectMgr.Exist(projectID) {
|
||||||
if err != nil {
|
ra.HandleNotFound(fmt.Sprintf("project %d not found", projectID))
|
||||||
log.Errorf("failed to get project %d: %v", projectID, err)
|
return
|
||||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if project == nil {
|
if !ra.SecurityCtx.HasReadPerm(projectID) {
|
||||||
ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("project %d not found", projectID))
|
if !ra.SecurityCtx.IsAuthenticated() {
|
||||||
}
|
ra.HandleUnauthorized()
|
||||||
|
return
|
||||||
if project.Public == 0 {
|
|
||||||
var userID int
|
|
||||||
|
|
||||||
if svc_utils.VerifySecret(ra.Ctx.Request, config.JobserviceSecret()) {
|
|
||||||
userID = 1
|
|
||||||
} else {
|
|
||||||
userID = ra.ValidateUser()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !checkProjectPermission(userID, projectID) {
|
|
||||||
ra.CustomAbort(http.StatusForbidden, "")
|
|
||||||
}
|
}
|
||||||
|
ra.HandleForbidden(ra.SecurityCtx.GetUsername())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
keyword := ra.GetString("q")
|
keyword := ra.GetString("q")
|
||||||
|
|
||||||
total, err := dao.GetTotalOfRepositoriesByProject(projectID, keyword)
|
total, err := dao.GetTotalOfRepositoriesByProject(projectID, keyword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to get total of repositories of project %d: %v", projectID, err)
|
ra.HandleInternalServerError(fmt.Sprintf("failed to get total of repositories of project %d: %v",
|
||||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
projectID, err))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
page, pageSize := ra.GetPaginationParams()
|
page, pageSize := ra.GetPaginationParams()
|
||||||
|
|
||||||
detail := ra.GetString("detail") == "1" || ra.GetString("detail") == "true"
|
|
||||||
|
|
||||||
repositories, err := getRepositories(projectID,
|
repositories, err := getRepositories(projectID,
|
||||||
keyword, pageSize, pageSize*(page-1), detail)
|
keyword, pageSize, pageSize*(page-1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to get repository: %v", err)
|
ra.HandleInternalServerError(fmt.Sprintf("failed to get repository: %v", err))
|
||||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ra.SetPaginationHeader(total, page, pageSize)
|
ra.SetPaginationHeader(total, page, pageSize)
|
||||||
@ -120,21 +107,12 @@ func (ra *RepositoryAPI) Get() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getRepositories(projectID int64, keyword string,
|
func getRepositories(projectID int64, keyword string,
|
||||||
limit, offset int64, detail bool) (interface{}, error) {
|
limit, offset int64) ([]*repoResp, error) {
|
||||||
repositories, err := dao.GetRepositoriesByProject(projectID, keyword, limit, offset)
|
repositories, err := dao.GetRepositoriesByProject(projectID, keyword, limit, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//keep compatibility with old API
|
|
||||||
if !detail {
|
|
||||||
result := []string{}
|
|
||||||
for _, repository := range repositories {
|
|
||||||
result = append(result, repository.Name)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return populateTagsCount(repositories)
|
return populateTagsCount(repositories)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,19 +146,19 @@ func (ra *RepositoryAPI) Delete() {
|
|||||||
repoName := ra.GetString(":splat")
|
repoName := ra.GetString(":splat")
|
||||||
|
|
||||||
projectName, _ := utils.ParseRepository(repoName)
|
projectName, _ := utils.ParseRepository(repoName)
|
||||||
project, err := dao.GetProjectByName(projectName)
|
if !ra.ProjectMgr.Exist(projectName) {
|
||||||
if err != nil {
|
ra.HandleNotFound(fmt.Sprintf("project %s not found", projectName))
|
||||||
log.Errorf("failed to get project %s: %v", projectName, err)
|
return
|
||||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if project == nil {
|
if !ra.SecurityCtx.IsAuthenticated() {
|
||||||
ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("project %s not found", projectName))
|
ra.HandleUnauthorized()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userID := ra.ValidateUser()
|
if !ra.SecurityCtx.HasAllPerm(projectName) {
|
||||||
if !hasProjectAdminRole(userID, project.ProjectID) {
|
ra.HandleForbidden(ra.SecurityCtx.GetUsername())
|
||||||
ra.CustomAbort(http.StatusForbidden, "")
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rc, err := ra.initRepositoryClient(repoName)
|
rc, err := ra.initRepositoryClient(repoName)
|
||||||
@ -212,18 +190,11 @@ func (ra *RepositoryAPI) Delete() {
|
|||||||
tags = append(tags, tag)
|
tags = append(tags, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
user, _, ok := ra.Ctx.Request.BasicAuth()
|
|
||||||
if !ok {
|
|
||||||
user, err = ra.getUsername()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to get user: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.WithNotary() {
|
if config.WithNotary() {
|
||||||
var digest string
|
var digest string
|
||||||
signedTags := make(map[string]struct{})
|
signedTags := make(map[string]struct{})
|
||||||
targets, err := notary.GetInternalTargets(config.InternalNotaryEndpoint(), user, repoName)
|
targets, err := notary.GetInternalTargets(config.InternalNotaryEndpoint(),
|
||||||
|
ra.SecurityCtx.GetUsername(), repoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to get Notary targets for repository: %s, error: %v", repoName, err)
|
log.Errorf("Failed to get Notary targets for repository: %s, error: %v", repoName, err)
|
||||||
log.Warningf("Failed to check signature status of repository: %s for deletion, there maybe orphaned targets in Notary.", repoName)
|
log.Warningf("Failed to check signature status of repository: %s for deletion, there maybe orphaned targets in Notary.", repoName)
|
||||||
@ -243,7 +214,7 @@ func (ra *RepositoryAPI) Delete() {
|
|||||||
ra.CustomAbort(http.StatusInternalServerError, err.Error())
|
ra.CustomAbort(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
log.Debugf("Tag: %s, digest: %s", t, digest)
|
log.Debugf("Tag: %s, digest: %s", t, digest)
|
||||||
if _, ok = signedTags[digest]; ok {
|
if _, ok := signedTags[digest]; ok {
|
||||||
log.Errorf("Found signed tag, repostory: %s, tag: %s, deletion will be canceled", repoName, t)
|
log.Errorf("Found signed tag, repostory: %s, tag: %s, deletion will be canceled", repoName, t)
|
||||||
ra.CustomAbort(http.StatusPreconditionFailed, fmt.Sprintf("tag %s is signed", t))
|
ra.CustomAbort(http.StatusPreconditionFailed, fmt.Sprintf("tag %s is signed", t))
|
||||||
}
|
}
|
||||||
@ -265,13 +236,9 @@ func (ra *RepositoryAPI) Delete() {
|
|||||||
go TriggerReplicationByRepository(repoName, []string{t}, models.RepOpDelete)
|
go TriggerReplicationByRepository(repoName, []string{t}, models.RepOpDelete)
|
||||||
|
|
||||||
go func(tag string) {
|
go func(tag string) {
|
||||||
project, err := dao.GetProjectByName(projectName)
|
project := ra.ProjectMgr.Get(projectName)
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to get project by name %s: %v", projectName, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := dao.AddAccessLog(models.AccessLog{
|
if err := dao.AddAccessLog(models.AccessLog{
|
||||||
Username: user,
|
Username: ra.SecurityCtx.GetUsername(),
|
||||||
ProjectID: project.ProjectID,
|
ProjectID: project.ProjectID,
|
||||||
RepoName: repoName,
|
RepoName: repoName,
|
||||||
RepoTag: tag,
|
RepoTag: tag,
|
||||||
@ -296,32 +263,23 @@ func (ra *RepositoryAPI) Delete() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type tag struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Tags []string `json:"tags"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTags returns tags of a repository
|
// GetTags returns tags of a repository
|
||||||
func (ra *RepositoryAPI) GetTags() {
|
func (ra *RepositoryAPI) GetTags() {
|
||||||
repoName := ra.GetString(":splat")
|
repoName := ra.GetString(":splat")
|
||||||
detail := ra.GetString("detail") == "1" || ra.GetString("detail") == "true"
|
|
||||||
|
|
||||||
projectName, _ := utils.ParseRepository(repoName)
|
projectName, _ := utils.ParseRepository(repoName)
|
||||||
project, err := dao.GetProjectByName(projectName)
|
if !ra.ProjectMgr.Exist(projectName) {
|
||||||
if err != nil {
|
ra.HandleNotFound(fmt.Sprintf("project %s not found", projectName))
|
||||||
log.Errorf("failed to get project %s: %v", projectName, err)
|
return
|
||||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if project == nil {
|
if !ra.SecurityCtx.HasReadPerm(projectName) {
|
||||||
ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("project %s not found", projectName))
|
if !ra.SecurityCtx.IsAuthenticated() {
|
||||||
}
|
ra.HandleUnauthorized()
|
||||||
|
return
|
||||||
if project.Public == 0 {
|
|
||||||
userID := ra.ValidateUser()
|
|
||||||
if !checkProjectPermission(userID, project.ProjectID) {
|
|
||||||
ra.CustomAbort(http.StatusForbidden, "")
|
|
||||||
}
|
}
|
||||||
|
ra.HandleForbidden(ra.SecurityCtx.GetUsername())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := ra.initRepositoryClient(repoName)
|
client, err := ra.initRepositoryClient(repoName)
|
||||||
@ -340,13 +298,7 @@ func (ra *RepositoryAPI) GetTags() {
|
|||||||
ra.CustomAbort(regErr.StatusCode, regErr.Detail)
|
ra.CustomAbort(regErr.StatusCode, regErr.Detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !detail {
|
result := []tagResp{}
|
||||||
ra.Data["json"] = tags
|
|
||||||
ra.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result := []detailedTagResp{}
|
|
||||||
|
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
manifest, err := getManifest(client, tag, "v1")
|
manifest, err := getManifest(client, tag, "v1")
|
||||||
@ -359,7 +311,7 @@ func (ra *RepositoryAPI) GetTags() {
|
|||||||
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||||
}
|
}
|
||||||
|
|
||||||
result = append(result, detailedTagResp{
|
result = append(result, tagResp{
|
||||||
Tag: tag,
|
Tag: tag,
|
||||||
Manifest: manifest.Manifest,
|
Manifest: manifest.Manifest,
|
||||||
})
|
})
|
||||||
@ -367,7 +319,6 @@ func (ra *RepositoryAPI) GetTags() {
|
|||||||
|
|
||||||
ra.Data["json"] = result
|
ra.Data["json"] = result
|
||||||
ra.ServeJSON()
|
ra.ServeJSON()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func listTag(client *registry.Repository) ([]string, error) {
|
func listTag(client *registry.Repository) ([]string, error) {
|
||||||
@ -409,21 +360,19 @@ func (ra *RepositoryAPI) GetManifests() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
projectName, _ := utils.ParseRepository(repoName)
|
projectName, _ := utils.ParseRepository(repoName)
|
||||||
project, err := dao.GetProjectByName(projectName)
|
if !ra.ProjectMgr.Exist(projectName) {
|
||||||
if err != nil {
|
ra.HandleNotFound(fmt.Sprintf("project %s not found", projectName))
|
||||||
log.Errorf("failed to get project %s: %v", projectName, err)
|
return
|
||||||
ra.CustomAbort(http.StatusInternalServerError, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if project == nil {
|
if !ra.SecurityCtx.HasReadPerm(projectName) {
|
||||||
ra.CustomAbort(http.StatusNotFound, fmt.Sprintf("project %s not found", projectName))
|
if !ra.SecurityCtx.IsAuthenticated() {
|
||||||
}
|
ra.HandleUnauthorized()
|
||||||
|
return
|
||||||
if project.Public == 0 {
|
|
||||||
userID := ra.ValidateUser()
|
|
||||||
if !checkProjectPermission(userID, project.ProjectID) {
|
|
||||||
ra.CustomAbort(http.StatusForbidden, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ra.HandleForbidden(ra.SecurityCtx.GetUsername())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rc, err := ra.initRepositoryClient(repoName)
|
rc, err := ra.initRepositoryClient(repoName)
|
||||||
@ -494,55 +443,8 @@ func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repo
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
verify, err := config.VerifyRemoteCert()
|
return NewRepositoryClient(endpoint, true, ra.SecurityCtx.GetUsername(),
|
||||||
if err != nil {
|
repoName, "repository", repoName, "pull", "push", "*")
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
username, password, ok := ra.Ctx.Request.BasicAuth()
|
|
||||||
if ok {
|
|
||||||
return newRepositoryClient(endpoint, !verify, username, password,
|
|
||||||
repoName, "repository", repoName, "pull", "push", "*")
|
|
||||||
}
|
|
||||||
|
|
||||||
username, err = ra.getUsername()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewRepositoryClient(endpoint, !verify, username, repoName,
|
|
||||||
"repository", repoName, "pull", "push", "*")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ra *RepositoryAPI) getUsername() (string, error) {
|
|
||||||
// get username from session
|
|
||||||
sessionUsername := ra.GetSession("username")
|
|
||||||
if sessionUsername != nil {
|
|
||||||
username, ok := sessionUsername.(string)
|
|
||||||
if ok {
|
|
||||||
return username, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if username does not exist in session, try to get userId from sessiion
|
|
||||||
// and then get username from DB according to the userId
|
|
||||||
sessionUserID := ra.GetSession("userId")
|
|
||||||
if sessionUserID != nil {
|
|
||||||
userID, ok := sessionUserID.(int)
|
|
||||||
if ok {
|
|
||||||
u := models.User{
|
|
||||||
UserID: userID,
|
|
||||||
}
|
|
||||||
user, err := dao.GetUser(u)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return user.Username, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//GetTopRepos returns the most populor repositories
|
//GetTopRepos returns the most populor repositories
|
||||||
@ -552,33 +454,23 @@ func (ra *RepositoryAPI) GetTopRepos() {
|
|||||||
ra.CustomAbort(http.StatusBadRequest, "invalid count")
|
ra.CustomAbort(http.StatusBadRequest, "invalid count")
|
||||||
}
|
}
|
||||||
|
|
||||||
userID, _, ok := ra.GetUserIDForRequest()
|
projectIDs := []int64{}
|
||||||
if !ok {
|
projects := ra.ProjectMgr.GetPublic()
|
||||||
userID = dao.NonExistUserID
|
if ra.SecurityCtx.IsAuthenticated() {
|
||||||
|
projects = append(projects, ra.ProjectMgr.GetByMember(
|
||||||
|
ra.SecurityCtx.GetUsername())...)
|
||||||
}
|
}
|
||||||
|
|
||||||
repos, err := dao.GetTopRepos(userID, count)
|
for _, project := range projects {
|
||||||
|
projectIDs = append(projectIDs, project.ProjectID)
|
||||||
|
}
|
||||||
|
|
||||||
|
repos, err := dao.GetTopRepos(projectIDs, count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to get top repos: %v", err)
|
log.Errorf("failed to get top repos: %v", err)
|
||||||
ra.CustomAbort(http.StatusInternalServerError, "internal server error")
|
ra.CustomAbort(http.StatusInternalServerError, "internal server error")
|
||||||
}
|
}
|
||||||
|
|
||||||
detail := ra.GetString("detail") == "1" || ra.GetString("detail") == "true"
|
|
||||||
if !detail {
|
|
||||||
result := []*models.TopRepo{}
|
|
||||||
|
|
||||||
for _, repo := range repos {
|
|
||||||
result = append(result, &models.TopRepo{
|
|
||||||
RepoName: repo.Name,
|
|
||||||
AccessCount: repo.PullCount,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
ra.Data["json"] = result
|
|
||||||
ra.ServeJSON()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := populateTagsCount(repos)
|
result, err := populateTagsCount(repos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to popultate tags count to repositories: %v", err)
|
log.Errorf("failed to popultate tags count to repositories: %v", err)
|
||||||
@ -591,15 +483,10 @@ func (ra *RepositoryAPI) GetTopRepos() {
|
|||||||
|
|
||||||
//GetSignatures returns signatures of a repository
|
//GetSignatures returns signatures of a repository
|
||||||
func (ra *RepositoryAPI) GetSignatures() {
|
func (ra *RepositoryAPI) GetSignatures() {
|
||||||
//use this func to init session.
|
|
||||||
ra.GetUserIDForRequest()
|
|
||||||
username, err := ra.getUsername()
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("Error when getting username: %v", err)
|
|
||||||
}
|
|
||||||
repoName := ra.GetString(":splat")
|
repoName := ra.GetString(":splat")
|
||||||
|
|
||||||
targets, err := notary.GetInternalTargets(config.InternalNotaryEndpoint(), username, repoName)
|
targets, err := notary.GetInternalTargets(config.InternalNotaryEndpoint(),
|
||||||
|
ra.SecurityCtx.GetUsername(), repoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error while fetching signature from notary: %v", err)
|
log.Errorf("Error while fetching signature from notary: %v", err)
|
||||||
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
||||||
@ -607,23 +494,3 @@ func (ra *RepositoryAPI) GetSignatures() {
|
|||||||
ra.Data["json"] = targets
|
ra.Data["json"] = targets
|
||||||
ra.ServeJSON()
|
ra.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRepositoryClient(endpoint string, insecure bool, username, password, repository, scopeType, scopeName string,
|
|
||||||
scopeActions ...string) (*registry.Repository, error) {
|
|
||||||
|
|
||||||
credential := auth.NewBasicAuthCredential(username, password)
|
|
||||||
|
|
||||||
authorizer := auth.NewStandardTokenAuthorizer(credential, insecure,
|
|
||||||
config.InternalTokenServiceEndpoint(), scopeType, scopeName, scopeActions...)
|
|
||||||
|
|
||||||
store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := registry.NewRepositoryWithModifiers(repository, endpoint, insecure, store)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetRepos(t *testing.T) {
|
func TestGetRepos(t *testing.T) {
|
||||||
@ -27,12 +26,11 @@ func TestGetRepos(t *testing.T) {
|
|||||||
apiTest := newHarborAPI()
|
apiTest := newHarborAPI()
|
||||||
projectID := "1"
|
projectID := "1"
|
||||||
keyword := "hello-world"
|
keyword := "hello-world"
|
||||||
detail := "true"
|
|
||||||
|
|
||||||
fmt.Println("Testing Repos Get API")
|
fmt.Println("Testing Repos Get API")
|
||||||
//-------------------case 1 : response code = 200------------------------//
|
//-------------------case 1 : response code = 200------------------------//
|
||||||
fmt.Println("case 1 : response code = 200")
|
fmt.Println("case 1 : response code = 200")
|
||||||
code, repositories, err := apiTest.GetRepos(*admin, projectID, keyword, detail)
|
code, repositories, err := apiTest.GetRepos(*admin, projectID, keyword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to get repositories: %v", err)
|
t.Errorf("failed to get repositories: %v", err)
|
||||||
} else {
|
} else {
|
||||||
@ -41,14 +39,14 @@ func TestGetRepos(t *testing.T) {
|
|||||||
assert.Equal(int(1), len(repos), "the length of repositories should be 1")
|
assert.Equal(int(1), len(repos), "the length of repositories should be 1")
|
||||||
assert.Equal(repos[0].Name, "library/hello-world", "unexpected repository name")
|
assert.Equal(repos[0].Name, "library/hello-world", "unexpected repository name")
|
||||||
} else {
|
} else {
|
||||||
t.Error("the response should return more info as detail is true")
|
t.Error("unexpected reponse")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------case 2 : response code = 404------------------------//
|
//-------------------case 2 : response code = 404------------------------//
|
||||||
fmt.Println("case 2 : response code = 404:project not found")
|
fmt.Println("case 2 : response code = 404:project not found")
|
||||||
projectID = "111"
|
projectID = "111"
|
||||||
httpStatusCode, _, err := apiTest.GetRepos(*admin, projectID, keyword, detail)
|
httpStatusCode, _, err := apiTest.GetRepos(*admin, projectID, keyword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Error whihle get repos by projectID", err.Error())
|
t.Error("Error whihle get repos by projectID", err.Error())
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
@ -56,27 +54,10 @@ func TestGetRepos(t *testing.T) {
|
|||||||
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
|
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------case 3 : response code = 200------------------------//
|
//-------------------case 3 : response code = 400------------------------//
|
||||||
fmt.Println("case 3 : response code = 200")
|
fmt.Println("case 3 : response code = 400,invalid project_id")
|
||||||
projectID = "1"
|
|
||||||
detail = "false"
|
|
||||||
code, repositories, err = apiTest.GetRepos(*admin, projectID, keyword, detail)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to get repositories: %v", err)
|
|
||||||
} else {
|
|
||||||
assert.Equal(int(200), code, "response code should be 200")
|
|
||||||
if repos, ok := repositories.([]string); ok {
|
|
||||||
assert.Equal(int(1), len(repos), "the length of repositories should be 1")
|
|
||||||
assert.Equal(repos[0], "library/hello-world", "unexpected repository name")
|
|
||||||
} else {
|
|
||||||
t.Error("the response should not return detail info as detail is false")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------case 4 : response code = 400------------------------//
|
|
||||||
fmt.Println("case 4 : response code = 400,invalid project_id")
|
|
||||||
projectID = "ccc"
|
projectID = "ccc"
|
||||||
httpStatusCode, _, err = apiTest.GetRepos(*admin, projectID, keyword, detail)
|
httpStatusCode, _, err = apiTest.GetRepos(*admin, projectID, keyword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Error whihle get repos by projectID", err.Error())
|
t.Error("Error whihle get repos by projectID", err.Error())
|
||||||
t.Log(err)
|
t.Log(err)
|
||||||
@ -95,8 +76,7 @@ func TestGetReposTags(t *testing.T) {
|
|||||||
//-------------------case 1 : response code = 404------------------------//
|
//-------------------case 1 : response code = 404------------------------//
|
||||||
fmt.Println("case 1 : response code = 404,repo not found")
|
fmt.Println("case 1 : response code = 404,repo not found")
|
||||||
repository := "errorRepos"
|
repository := "errorRepos"
|
||||||
detail := "false"
|
code, _, err := apiTest.GetReposTags(*admin, repository)
|
||||||
code, _, err := apiTest.GetReposTags(*admin, repository, detail)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to get tags of repository %s: %v", repository, err)
|
t.Errorf("failed to get tags of repository %s: %v", repository, err)
|
||||||
} else {
|
} else {
|
||||||
@ -105,33 +85,16 @@ func TestGetReposTags(t *testing.T) {
|
|||||||
//-------------------case 2 : response code = 200------------------------//
|
//-------------------case 2 : response code = 200------------------------//
|
||||||
fmt.Println("case 2 : response code = 200")
|
fmt.Println("case 2 : response code = 200")
|
||||||
repository = "library/hello-world"
|
repository = "library/hello-world"
|
||||||
code, tags, err := apiTest.GetReposTags(*admin, repository, detail)
|
code, tags, err := apiTest.GetReposTags(*admin, repository)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to get tags of repository %s: %v", repository, err)
|
t.Errorf("failed to get tags of repository %s: %v", repository, err)
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(int(200), code, "httpStatusCode should be 200")
|
assert.Equal(int(200), code, "httpStatusCode should be 200")
|
||||||
if tg, ok := tags.([]string); ok {
|
if tg, ok := tags.([]tagResp); ok {
|
||||||
assert.Equal(1, len(tg), fmt.Sprintf("there should be only one tag, but now %v", tg))
|
|
||||||
assert.Equal(tg[0], "latest", "the tag should be latest")
|
|
||||||
} else {
|
|
||||||
t.Error("the tags should be in simple style as the detail is false")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------case 3 : response code = 200------------------------//
|
|
||||||
fmt.Println("case 3 : response code = 200")
|
|
||||||
repository = "library/hello-world"
|
|
||||||
detail = "true"
|
|
||||||
code, tags, err = apiTest.GetReposTags(*admin, repository, detail)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to get tags of repository %s: %v", repository, err)
|
|
||||||
} else {
|
|
||||||
assert.Equal(int(200), code, "httpStatusCode should be 200")
|
|
||||||
if tg, ok := tags.([]detailedTagResp); ok {
|
|
||||||
assert.Equal(1, len(tg), fmt.Sprintf("there should be only one tag, but now %v", tg))
|
assert.Equal(1, len(tg), fmt.Sprintf("there should be only one tag, but now %v", tg))
|
||||||
assert.Equal(tg[0].Tag, "latest", "the tag should be latest")
|
assert.Equal(tg[0].Tag, "latest", "the tag should be latest")
|
||||||
} else {
|
} else {
|
||||||
t.Error("the tags should be in detail style as the detail is true")
|
t.Error("unexpected response")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,38 +153,20 @@ func TestGetReposTop(t *testing.T) {
|
|||||||
apiTest := newHarborAPI()
|
apiTest := newHarborAPI()
|
||||||
|
|
||||||
fmt.Println("Testing ReposTop Get API")
|
fmt.Println("Testing ReposTop Get API")
|
||||||
//-------------------case 1 : response code = 200------------------------//
|
//-------------------case 1 : response code = 400------------------------//
|
||||||
fmt.Println("case 1 : response code = 200")
|
fmt.Println("case 1 : response code = 400,invalid count")
|
||||||
count := "1"
|
count := "cc"
|
||||||
detail := "false"
|
code, _, err := apiTest.GetReposTop(*admin, count)
|
||||||
code, repos, err := apiTest.GetReposTop(*admin, count, detail)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to get the most popular repositories: %v", err)
|
|
||||||
} else {
|
|
||||||
assert.Equal(int(200), code, "response code should be 200")
|
|
||||||
if r, ok := repos.([]*models.TopRepo); ok {
|
|
||||||
assert.Equal(int(1), len(r), "the length should be 1")
|
|
||||||
assert.Equal(r[0].RepoName, "library/docker", "the name of repository should be library/docker")
|
|
||||||
} else {
|
|
||||||
t.Error("the repositories should in simple style as the detail is false")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------------------case 2 : response code = 400------------------------//
|
|
||||||
fmt.Println("case 2 : response code = 400,invalid count")
|
|
||||||
count = "cc"
|
|
||||||
code, _, err = apiTest.GetReposTop(*admin, count, detail)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to get the most popular repositories: %v", err)
|
t.Errorf("failed to get the most popular repositories: %v", err)
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(int(400), code, "response code should be 400")
|
assert.Equal(int(400), code, "response code should be 400")
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------case 3 : response code = 200------------------------//
|
//-------------------case 2 : response code = 200------------------------//
|
||||||
fmt.Println("case 3 : response code = 200")
|
fmt.Println("case 2 : response code = 200")
|
||||||
count = "1"
|
count = "1"
|
||||||
detail = "true"
|
code, repos, err := apiTest.GetReposTop(*admin, count)
|
||||||
code, repos, err = apiTest.GetReposTop(*admin, count, detail)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to get the most popular repositories: %v", err)
|
t.Errorf("failed to get the most popular repositories: %v", err)
|
||||||
} else {
|
} else {
|
||||||
@ -230,7 +175,7 @@ func TestGetReposTop(t *testing.T) {
|
|||||||
assert.Equal(int(1), len(r), "the length should be 1")
|
assert.Equal(int(1), len(r), "the length should be 1")
|
||||||
assert.Equal(r[0].Name, "library/docker", "the name of repository should be library/docker")
|
assert.Equal(r[0].Name, "library/docker", "the name of repository should be library/docker")
|
||||||
} else {
|
} else {
|
||||||
t.Error("the repositories should in detail style as the detail is true")
|
t.Error("unexpected response")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ func checkProjectPermission(userID int, projectID int64) bool {
|
|||||||
return len(roles) > 0
|
return len(roles) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO remove
|
||||||
func hasProjectAdminRole(userID int, projectID int64) bool {
|
func hasProjectAdminRole(userID int, projectID int64) bool {
|
||||||
roles, err := listRoles(userID, projectID)
|
roles, err := listRoles(userID, projectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -24,27 +24,39 @@ import (
|
|||||||
// ProjectManager implements pm.PM interface based on database
|
// ProjectManager implements pm.PM interface based on database
|
||||||
type ProjectManager struct{}
|
type ProjectManager struct{}
|
||||||
|
|
||||||
// IsPublic returns whether the project is public or not
|
// Get ...
|
||||||
func (p *ProjectManager) IsPublic(projectIDOrName interface{}) bool {
|
func (p *ProjectManager) Get(projectIDOrName interface{}) *models.Project {
|
||||||
var project *models.Project
|
|
||||||
var err error
|
|
||||||
switch projectIDOrName.(type) {
|
switch projectIDOrName.(type) {
|
||||||
case string:
|
case string:
|
||||||
name := projectIDOrName.(string)
|
name := projectIDOrName.(string)
|
||||||
project, err = dao.GetProjectByName(name)
|
project, err := dao.GetProjectByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to get project %s: %v", name, err)
|
log.Errorf("failed to get project %s: %v", name, err)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
return project
|
||||||
case int64:
|
case int64:
|
||||||
id := projectIDOrName.(int64)
|
id := projectIDOrName.(int64)
|
||||||
project, err = dao.GetProjectByID(id)
|
project, err := dao.GetProjectByID(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to get project %d: %v", id, err)
|
log.Errorf("failed to get project %d: %v", id, err)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
return project
|
||||||
default:
|
default:
|
||||||
log.Errorf("unsupported type of %v, must be string or int64", projectIDOrName)
|
log.Errorf("unsupported type of %v, must be string or int64", projectIDOrName)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exist ...
|
||||||
|
func (p *ProjectManager) Exist(projectIDOrName interface{}) bool {
|
||||||
|
return p.Get(projectIDOrName) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPublic returns whether the project is public or not
|
||||||
|
func (p *ProjectManager) IsPublic(projectIDOrName interface{}) bool {
|
||||||
|
project := p.Get(projectIDOrName)
|
||||||
if project == nil {
|
if project == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -67,31 +79,15 @@ func (p *ProjectManager) GetRoles(username string, projectIDOrName interface{})
|
|||||||
return roles
|
return roles
|
||||||
}
|
}
|
||||||
|
|
||||||
var projectID int64
|
project := p.Get(projectIDOrName)
|
||||||
switch projectIDOrName.(type) {
|
if project == nil {
|
||||||
case string:
|
|
||||||
name := projectIDOrName.(string)
|
|
||||||
project, err := dao.GetProjectByName(name)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to get project %s: %v", name, err)
|
|
||||||
return roles
|
|
||||||
}
|
|
||||||
|
|
||||||
if project == nil {
|
|
||||||
return roles
|
|
||||||
}
|
|
||||||
projectID = project.ProjectID
|
|
||||||
case int64:
|
|
||||||
projectID = projectIDOrName.(int64)
|
|
||||||
default:
|
|
||||||
log.Errorf("unsupported type of %v, must be string or int64", projectIDOrName)
|
|
||||||
return roles
|
return roles
|
||||||
}
|
}
|
||||||
|
|
||||||
roleList, err := dao.GetUserProjectRoles(user.UserID, projectID)
|
roleList, err := dao.GetUserProjectRoles(user.UserID, project.ProjectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to get roles for user %d to project %d: %v",
|
log.Errorf("failed to get roles for user %d to project %d: %v",
|
||||||
user.UserID, projectID, err)
|
user.UserID, project.ProjectID, err)
|
||||||
return roles
|
return roles
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,3 +104,32 @@ func (p *ProjectManager) GetRoles(username string, projectIDOrName interface{})
|
|||||||
|
|
||||||
return roles
|
return roles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPublic returns all public projects
|
||||||
|
func (p *ProjectManager) GetPublic() []models.Project {
|
||||||
|
projects, err := dao.GetProjects("", 1)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get all public projects: %v", err)
|
||||||
|
return []models.Project{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByMember returns all projects which the user is a member of
|
||||||
|
func (p *ProjectManager) GetByMember(username string) []models.Project {
|
||||||
|
user, err := dao.GetUser(models.User{
|
||||||
|
Username: username,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get user %s: %v", username, err)
|
||||||
|
return []models.Project{}
|
||||||
|
}
|
||||||
|
projects, err := dao.GetUserRelevantProjects(user.UserID, "")
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get projects of %s: %v", username, err)
|
||||||
|
return []models.Project{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects
|
||||||
|
}
|
||||||
|
@ -70,16 +70,44 @@ func TestMain(m *testing.M) {
|
|||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
pm := &ProjectManager{}
|
||||||
|
|
||||||
|
// project name
|
||||||
|
project := pm.Get("library")
|
||||||
|
assert.NotNil(t, project)
|
||||||
|
assert.Equal(t, "library", project.Name)
|
||||||
|
|
||||||
|
// project ID
|
||||||
|
project = pm.Get(int64(1))
|
||||||
|
assert.NotNil(t, project)
|
||||||
|
assert.Equal(t, int64(1), project.ProjectID)
|
||||||
|
|
||||||
|
// non-exist project
|
||||||
|
project = pm.Get("non-exist-project")
|
||||||
|
assert.Nil(t, project)
|
||||||
|
|
||||||
|
// invalid type
|
||||||
|
project = pm.Get(true)
|
||||||
|
assert.Nil(t, project)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExist(t *testing.T) {
|
||||||
|
pm := &ProjectManager{}
|
||||||
|
|
||||||
|
// exist project
|
||||||
|
assert.True(t, pm.Exist("library"))
|
||||||
|
|
||||||
|
// non-exist project
|
||||||
|
assert.False(t, pm.Exist("non-exist-project"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestIsPublic(t *testing.T) {
|
func TestIsPublic(t *testing.T) {
|
||||||
pms := &ProjectManager{}
|
pms := &ProjectManager{}
|
||||||
// project name
|
// public project
|
||||||
assert.True(t, pms.IsPublic("library"))
|
assert.True(t, pms.IsPublic("library"))
|
||||||
// project ID
|
|
||||||
assert.True(t, pms.IsPublic(int64(1)))
|
|
||||||
// non exist project
|
// non exist project
|
||||||
assert.False(t, pms.IsPublic("non_exist_project"))
|
assert.False(t, pms.IsPublic("non_exist_project"))
|
||||||
// invalid type
|
|
||||||
assert.False(t, pms.IsPublic(1))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetRoles(t *testing.T) {
|
func TestGetRoles(t *testing.T) {
|
||||||
@ -89,18 +117,28 @@ func TestGetRoles(t *testing.T) {
|
|||||||
assert.Equal(t, []int{},
|
assert.Equal(t, []int{},
|
||||||
pm.GetRoles("non_exist_user", int64(1)))
|
pm.GetRoles("non_exist_user", int64(1)))
|
||||||
|
|
||||||
// project ID
|
// exist project
|
||||||
assert.Equal(t, []int{common.RoleProjectAdmin},
|
|
||||||
pm.GetRoles("admin", int64(1)))
|
|
||||||
|
|
||||||
// project name
|
|
||||||
assert.Equal(t, []int{common.RoleProjectAdmin},
|
assert.Equal(t, []int{common.RoleProjectAdmin},
|
||||||
pm.GetRoles("admin", "library"))
|
pm.GetRoles("admin", "library"))
|
||||||
|
|
||||||
// non exist project
|
// non-exist project
|
||||||
assert.Equal(t, []int{},
|
assert.Equal(t, []int{},
|
||||||
pm.GetRoles("admin", "non_exist_project"))
|
pm.GetRoles("admin", "non_exist_project"))
|
||||||
|
}
|
||||||
// invalid type
|
|
||||||
assert.Equal(t, []int{}, pm.GetRoles("admin", 1))
|
func TestGetPublic(t *testing.T) {
|
||||||
|
pm := &ProjectManager{}
|
||||||
|
projects := pm.GetPublic()
|
||||||
|
|
||||||
|
assert.NotEqual(t, 0, len(projects))
|
||||||
|
|
||||||
|
for _, project := range projects {
|
||||||
|
assert.Equal(t, 1, project.Public)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetByMember(t *testing.T) {
|
||||||
|
pm := &ProjectManager{}
|
||||||
|
projects := pm.GetByMember("admin")
|
||||||
|
assert.NotEqual(t, 0, len(projects))
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,19 @@
|
|||||||
|
|
||||||
package projectmanager
|
package projectmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
)
|
||||||
|
|
||||||
// ProjectManager is the project mamager which abstracts the operations related
|
// ProjectManager is the project mamager which abstracts the operations related
|
||||||
// to projects
|
// to projects
|
||||||
type ProjectManager interface {
|
type ProjectManager interface {
|
||||||
|
Get(projectIDOrName interface{}) *models.Project
|
||||||
IsPublic(projectIDOrName interface{}) bool
|
IsPublic(projectIDOrName interface{}) bool
|
||||||
|
Exist(projectIDOrName interface{}) bool
|
||||||
GetRoles(username string, projectIDOrName interface{}) []int
|
GetRoles(username string, projectIDOrName interface{}) []int
|
||||||
|
// get all public project
|
||||||
|
GetPublic() []models.Project
|
||||||
|
// get projects which the user is a member of
|
||||||
|
GetByMember(username string) []models.Project
|
||||||
}
|
}
|
||||||
|
@ -71,8 +71,9 @@ func initRouters() {
|
|||||||
beego.Router("/api/users/?:id", &api.UserAPI{})
|
beego.Router("/api/users/?:id", &api.UserAPI{})
|
||||||
beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
|
beego.Router("/api/users/:id([0-9]+)/password", &api.UserAPI{}, "put:ChangePassword")
|
||||||
beego.Router("/api/internal/syncregistry", &api.InternalAPI{}, "post:SyncRegistry")
|
beego.Router("/api/internal/syncregistry", &api.InternalAPI{}, "post:SyncRegistry")
|
||||||
beego.Router("/api/repositories", &api.RepositoryAPI{})
|
beego.Router("/api/repositories", &api.RepositoryAPI{}, "get:Get")
|
||||||
beego.Router("/api/repositories/*/tags/?:tag", &api.RepositoryAPI{}, "delete:Delete")
|
beego.Router("/api/repositories/*", &api.RepositoryAPI{}, "delete:Delete")
|
||||||
|
beego.Router("/api/repositories/*/tags/:tag", &api.RepositoryAPI{}, "delete:Delete")
|
||||||
beego.Router("/api/repositories/*/tags", &api.RepositoryAPI{}, "get:GetTags")
|
beego.Router("/api/repositories/*/tags", &api.RepositoryAPI{}, "get:GetTags")
|
||||||
beego.Router("/api/repositories/*/tags/:tag/manifest", &api.RepositoryAPI{}, "get:GetManifests")
|
beego.Router("/api/repositories/*/tags/:tag/manifest", &api.RepositoryAPI{}, "get:GetManifests")
|
||||||
beego.Router("/api/repositories/*/signatures", &api.RepositoryAPI{}, "get:GetSignatures")
|
beego.Router("/api/repositories/*/signatures", &api.RepositoryAPI{}, "get:GetSignatures")
|
||||||
|
@ -34,14 +34,14 @@ export class RepositoryService {
|
|||||||
params.set('page_size', pageSize + '');
|
params.set('page_size', pageSize + '');
|
||||||
}
|
}
|
||||||
return this.http
|
return this.http
|
||||||
.get(`/api/repositories?project_id=${projectId}&q=${repoName}&detail=1`, {search: params})
|
.get(`/api/repositories?project_id=${projectId}&q=${repoName}`, {search: params})
|
||||||
.map(response=>response)
|
.map(response=>response)
|
||||||
.catch(error=>Observable.throw(error));
|
.catch(error=>Observable.throw(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
listTags(repoName: string): Observable<Tag[]> {
|
listTags(repoName: string): Observable<Tag[]> {
|
||||||
return this.http
|
return this.http
|
||||||
.get(`/api/repositories/${repoName}/tags?detail=1`)
|
.get(`/api/repositories/${repoName}/tags`)
|
||||||
.map(response=>response.json())
|
.map(response=>response.json())
|
||||||
.catch(error=>Observable.throw(error));
|
.catch(error=>Observable.throw(error));
|
||||||
}
|
}
|
||||||
@ -77,7 +77,7 @@ export class RepositoryService {
|
|||||||
|
|
||||||
deleteRepository(repoName: string): Observable<any> {
|
deleteRepository(repoName: string): Observable<any> {
|
||||||
return this.http
|
return this.http
|
||||||
.delete(`/api/repositories/${repoName}/tags`)
|
.delete(`/api/repositories/${repoName}`)
|
||||||
.map(response=>response.status)
|
.map(response=>response.status)
|
||||||
.catch(error=>Observable.throw(error));
|
.catch(error=>Observable.throw(error));
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import 'rxjs/add/operator/toPromise';
|
|||||||
|
|
||||||
import { Repository } from '../repository';
|
import { Repository } from '../repository';
|
||||||
|
|
||||||
export const topRepoEndpoint = "/api/repositories/top?detail=1";
|
export const topRepoEndpoint = "/api/repositories/top";
|
||||||
/**
|
/**
|
||||||
* Declare service to handle the top repositories
|
* Declare service to handle the top repositories
|
||||||
*
|
*
|
||||||
|
Loading…
Reference in New Issue
Block a user