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 (
|
|
|
|
"encoding/json"
|
2016-02-24 07:31:52 +01:00
|
|
|
"net/http"
|
2016-04-15 07:17:32 +02:00
|
|
|
"os"
|
2016-02-01 12:59:10 +01:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/vmware/harbor/dao"
|
|
|
|
"github.com/vmware/harbor/models"
|
|
|
|
svc_utils "github.com/vmware/harbor/service/utils"
|
2016-03-28 02:50:09 +02:00
|
|
|
"github.com/vmware/harbor/utils/log"
|
2016-04-15 07:17:32 +02:00
|
|
|
"github.com/vmware/harbor/utils/registry"
|
|
|
|
"github.com/vmware/harbor/utils/registry/auth"
|
2016-02-01 12:59:10 +01:00
|
|
|
)
|
|
|
|
|
2016-02-26 11:35:55 +01:00
|
|
|
// 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.
|
|
|
|
// For repostiories, we won't check the session in this API due to search functionality, querying manifest will be contorlled by
|
|
|
|
// the security of registry
|
2016-02-01 12:59:10 +01:00
|
|
|
type RepositoryAPI struct {
|
|
|
|
BaseAPI
|
2016-02-25 06:40:08 +01:00
|
|
|
userID int
|
2016-02-01 12:59:10 +01:00
|
|
|
username string
|
2016-04-15 07:17:32 +02:00
|
|
|
registry *registry.Registry
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
|
|
|
|
2016-02-26 11:35:55 +01:00
|
|
|
// Prepare will set a non existent user ID in case the request tries to view repositories under a project he doesn't has permission.
|
2016-02-01 12:59:10 +01:00
|
|
|
func (ra *RepositoryAPI) Prepare() {
|
2016-02-25 06:40:08 +01:00
|
|
|
userID, ok := ra.GetSession("userId").(int)
|
2016-02-01 12:59:10 +01:00
|
|
|
if !ok {
|
2016-02-26 04:26:54 +01:00
|
|
|
ra.userID = dao.NonExistUserID
|
2016-02-01 12:59:10 +01:00
|
|
|
} else {
|
2016-02-25 06:40:08 +01:00
|
|
|
ra.userID = userID
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
|
|
|
username, ok := ra.GetSession("username").(string)
|
|
|
|
if !ok {
|
2016-03-28 02:50:09 +02:00
|
|
|
log.Warning("failed to get username from session")
|
2016-02-01 12:59:10 +01:00
|
|
|
ra.username = ""
|
|
|
|
} else {
|
|
|
|
ra.username = username
|
|
|
|
}
|
2016-04-15 07:17:32 +02:00
|
|
|
|
|
|
|
var client *http.Client
|
|
|
|
|
|
|
|
//no session, initialize a standard auth handler
|
|
|
|
if ra.userID == dao.NonExistUserID && len(ra.username) == 0 {
|
|
|
|
credential := &auth.Credential{}
|
|
|
|
username, password, ok := ra.Ctx.Request.BasicAuth()
|
|
|
|
if ok {
|
|
|
|
credential.Username = username
|
|
|
|
credential.Password = password
|
|
|
|
}
|
|
|
|
client = registry.NewClientStandardAuthHandlerEmbeded(credential)
|
|
|
|
log.Debug("initializing standard auth handler")
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// session works, initialize a username auth handler
|
|
|
|
username := ra.username
|
|
|
|
if len(username) == 0 {
|
|
|
|
user, err := dao.GetUser(models.User{
|
|
|
|
UserID: ra.userID,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("error occurred whiling geting user for initializing a username auth handler: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
username = user.Username
|
|
|
|
}
|
|
|
|
|
|
|
|
client = registry.NewClientUsernameAuthHandlerEmbeded(username)
|
|
|
|
log.Debug("initializing username auth handler: %s", username)
|
|
|
|
}
|
|
|
|
|
|
|
|
endpoint := os.Getenv("REGISTRY_URL")
|
|
|
|
r, err := registry.New(endpoint, client)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("error occurred while initializing auth handler for repository API: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ra.registry = r
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
|
|
|
|
2016-02-26 11:35:55 +01:00
|
|
|
// Get ...
|
2016-02-01 12:59:10 +01:00
|
|
|
func (ra *RepositoryAPI) Get() {
|
2016-02-25 06:40:08 +01:00
|
|
|
projectID, err0 := ra.GetInt64("project_id")
|
2016-02-01 12:59:10 +01:00
|
|
|
if err0 != nil {
|
2016-03-28 02:50:09 +02:00
|
|
|
log.Errorf("Failed to get project id, error: %v", err0)
|
2016-02-24 07:31:52 +01:00
|
|
|
ra.RenderError(http.StatusBadRequest, "Invalid project id")
|
2016-02-01 12:59:10 +01:00
|
|
|
return
|
|
|
|
}
|
2016-02-26 04:26:54 +01:00
|
|
|
p, err := dao.GetProjectByID(projectID)
|
2016-02-01 12:59:10 +01:00
|
|
|
if err != nil {
|
2016-03-28 02:50:09 +02:00
|
|
|
log.Errorf("Error occurred in GetProjectById, error: %v", err)
|
2016-02-24 07:31:52 +01:00
|
|
|
ra.CustomAbort(http.StatusInternalServerError, "Internal error.")
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
|
|
|
if p == nil {
|
2016-03-28 02:50:09 +02:00
|
|
|
log.Warningf("Project with Id: %d does not exist", projectID)
|
2016-02-24 07:31:52 +01:00
|
|
|
ra.RenderError(http.StatusNotFound, "")
|
2016-02-01 12:59:10 +01:00
|
|
|
return
|
|
|
|
}
|
2016-02-26 11:35:55 +01:00
|
|
|
if p.Public == 0 && !checkProjectPermission(ra.userID, projectID) {
|
2016-02-24 07:31:52 +01:00
|
|
|
ra.RenderError(http.StatusForbidden, "")
|
2016-02-01 12:59:10 +01:00
|
|
|
return
|
|
|
|
}
|
2016-04-15 07:17:32 +02:00
|
|
|
|
2016-02-01 12:59:10 +01:00
|
|
|
repoList, err := svc_utils.GetRepoFromCache()
|
|
|
|
if err != nil {
|
2016-03-28 02:50:09 +02:00
|
|
|
log.Errorf("Failed to get repo from cache, error: %v", err)
|
2016-02-24 07:31:52 +01:00
|
|
|
ra.RenderError(http.StatusInternalServerError, "internal sever error")
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
2016-04-15 07:17:32 +02:00
|
|
|
|
2016-02-01 12:59:10 +01:00
|
|
|
projectName := p.Name
|
|
|
|
q := ra.GetString("q")
|
|
|
|
var resp []string
|
|
|
|
if len(q) > 0 {
|
|
|
|
for _, r := range repoList {
|
|
|
|
if strings.Contains(r, "/") && strings.Contains(r[strings.LastIndex(r, "/")+1:], q) && r[0:strings.LastIndex(r, "/")] == projectName {
|
|
|
|
resp = append(resp, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ra.Data["json"] = resp
|
|
|
|
} else if len(projectName) > 0 {
|
|
|
|
for _, r := range repoList {
|
|
|
|
if strings.Contains(r, "/") && r[0:strings.LastIndex(r, "/")] == projectName {
|
|
|
|
resp = append(resp, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ra.Data["json"] = resp
|
|
|
|
} else {
|
|
|
|
ra.Data["json"] = repoList
|
|
|
|
}
|
|
|
|
ra.ServeJSON()
|
|
|
|
}
|
|
|
|
|
2016-04-15 07:17:32 +02:00
|
|
|
// Delete ...
|
|
|
|
func (ra *RepositoryAPI) Delete() {
|
|
|
|
repoName := ra.GetString("repo_name")
|
|
|
|
if len(repoName) == 0 {
|
|
|
|
ra.CustomAbort(http.StatusBadRequest, "repo_name is nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
tags := []string{}
|
|
|
|
tag := ra.GetString("tag")
|
|
|
|
if len(tag) == 0 {
|
|
|
|
tagList, err := ra.registry.ListTag(repoName)
|
|
|
|
if err != nil {
|
|
|
|
e, ok := registry.ParseError(err)
|
|
|
|
if ok {
|
|
|
|
log.Info(e)
|
|
|
|
ra.CustomAbort(e.StatusCode, e.Message)
|
|
|
|
} else {
|
|
|
|
log.Error(err)
|
|
|
|
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
tags = append(tags, tagList...)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
tags = append(tags, tag)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, t := range tags {
|
|
|
|
if err := ra.registry.DeleteTag(repoName, t); err != nil {
|
|
|
|
e, ok := registry.ParseError(err)
|
|
|
|
if ok {
|
|
|
|
ra.CustomAbort(e.StatusCode, e.Message)
|
|
|
|
} else {
|
|
|
|
log.Error(err)
|
|
|
|
ra.CustomAbort(http.StatusInternalServerError, "internal error")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
log.Infof("delete tag: %s %s", repoName, t)
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
log.Debug("refreshing catalog cache")
|
|
|
|
if err := svc_utils.RefreshCatalogCache(); err != nil {
|
|
|
|
log.Errorf("error occurred while refresh catalog cache: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-02-26 11:35:55 +01:00
|
|
|
type tag struct {
|
2016-02-26 07:24:44 +01:00
|
|
|
Name string `json:"name"`
|
2016-02-01 12:59:10 +01:00
|
|
|
Tags []string `json:"tags"`
|
|
|
|
}
|
|
|
|
|
2016-02-26 11:35:55 +01:00
|
|
|
type histroyItem struct {
|
2016-02-01 12:59:10 +01:00
|
|
|
V1Compatibility string `json:"v1Compatibility"`
|
|
|
|
}
|
|
|
|
|
2016-02-26 11:35:55 +01:00
|
|
|
type manifest struct {
|
2016-02-01 12:59:10 +01:00
|
|
|
Name string `json:"name"`
|
|
|
|
Tag string `json:"tag"`
|
|
|
|
Architecture string `json:"architecture"`
|
|
|
|
SchemaVersion int `json:"schemaVersion"`
|
2016-02-26 11:35:55 +01:00
|
|
|
History []histroyItem `json:"history"`
|
2016-02-01 12:59:10 +01:00
|
|
|
}
|
|
|
|
|
2016-02-26 11:35:55 +01:00
|
|
|
// GetTags handles GET /api/repositories/tags
|
2016-02-01 12:59:10 +01:00
|
|
|
func (ra *RepositoryAPI) GetTags() {
|
|
|
|
|
|
|
|
var tags []string
|
|
|
|
|
|
|
|
repoName := ra.GetString("repo_name")
|
2016-04-15 07:17:32 +02:00
|
|
|
result, err := registry.APIGet(registry.BuildRegistryURL(repoName, "tags", "list"), ra.username)
|
2016-02-01 12:59:10 +01:00
|
|
|
if err != nil {
|
2016-03-28 02:50:09 +02:00
|
|
|
log.Errorf("Failed to get repo tags, repo name: %s, error: %v", repoName, err)
|
2016-02-24 07:31:52 +01:00
|
|
|
ra.RenderError(http.StatusInternalServerError, "Failed to get repo tags")
|
2016-02-01 12:59:10 +01:00
|
|
|
} else {
|
2016-02-26 11:35:55 +01:00
|
|
|
t := tag{}
|
2016-02-01 12:59:10 +01:00
|
|
|
json.Unmarshal(result, &t)
|
|
|
|
tags = t.Tags
|
|
|
|
}
|
|
|
|
ra.Data["json"] = tags
|
|
|
|
ra.ServeJSON()
|
|
|
|
}
|
|
|
|
|
2016-02-26 11:35:55 +01:00
|
|
|
// GetManifests handles GET /api/repositories/manifests
|
2016-02-01 12:59:10 +01:00
|
|
|
func (ra *RepositoryAPI) GetManifests() {
|
|
|
|
repoName := ra.GetString("repo_name")
|
|
|
|
tag := ra.GetString("tag")
|
|
|
|
|
|
|
|
item := models.RepoItem{}
|
|
|
|
|
2016-04-15 07:17:32 +02:00
|
|
|
result, err := registry.APIGet(registry.BuildRegistryURL(repoName, "manifests", tag), ra.username)
|
2016-02-01 12:59:10 +01:00
|
|
|
if err != nil {
|
2016-03-28 02:50:09 +02:00
|
|
|
log.Errorf("Failed to get manifests for repo, repo name: %s, tag: %s, error: %v", repoName, tag, err)
|
2016-02-24 07:31:52 +01:00
|
|
|
ra.RenderError(http.StatusInternalServerError, "Internal Server Error")
|
2016-02-01 12:59:10 +01:00
|
|
|
return
|
|
|
|
}
|
2016-02-26 11:35:55 +01:00
|
|
|
mani := manifest{}
|
2016-02-25 06:40:08 +01:00
|
|
|
err = json.Unmarshal(result, &mani)
|
|
|
|
if err != nil {
|
2016-03-28 02:50:09 +02:00
|
|
|
log.Errorf("Failed to decode json from response for manifests, repo name: %s, tag: %s, error: %v", repoName, tag, err)
|
2016-02-25 06:40:08 +01:00
|
|
|
ra.RenderError(http.StatusInternalServerError, "Internal Server Error")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
v1Compatibility := mani.History[0].V1Compatibility
|
|
|
|
|
|
|
|
err = json.Unmarshal([]byte(v1Compatibility), &item)
|
|
|
|
if err != nil {
|
2016-03-28 02:50:09 +02:00
|
|
|
log.Errorf("Failed to decode V1 field for repo, repo name: %s, tag: %s, error: %v", repoName, tag, err)
|
2016-02-25 06:40:08 +01:00
|
|
|
ra.RenderError(http.StatusInternalServerError, "Internal Server Error")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
item.CreatedStr = item.Created.Format("2006-01-02 15:04:05")
|
|
|
|
item.DurationDays = strconv.Itoa(int(time.Since(item.Created).Hours()/24)) + " days"
|
2016-02-01 12:59:10 +01:00
|
|
|
|
|
|
|
ra.Data["json"] = item
|
|
|
|
ra.ServeJSON()
|
|
|
|
}
|