Rework systeminfo API. (#13606)

This commit rework the systeminfo API under new programming model.
Also fixes #9149

Signed-off-by: Daniel Jiang <jiangd@vmware.com>
This commit is contained in:
Daniel Jiang 2020-11-30 14:15:18 +08:00 committed by GitHub
parent dec12308a1
commit db8ce49133
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 568 additions and 454 deletions

View File

@ -1310,52 +1310,6 @@ paths:
description: No registry found.
'500':
description: Unexpected internal errors.
/systeminfo:
get:
summary: Get general system info
description: |
This API is for retrieving general system info, this can be called by anonymous request.
tags:
- Products
responses:
'200':
description: Get general info successfully.
schema:
$ref: '#/definitions/GeneralInfo'
'500':
description: Unexpected internal error.
/systeminfo/volumes:
get:
summary: Get system volume info (total/free size).
description: |
This endpoint is for retrieving system volume info that only provides for admin user.
tags:
- Products
responses:
'200':
description: Get system volumes successfully.
schema:
$ref: '#/definitions/SystemInfo'
'401':
description: User need to log in first.
'403':
description: User does not have permission of admin role.
'500':
description: Unexpected internal errors.
/systeminfo/getcert:
get:
summary: Get default root certificate.
description: |
This endpoint is for downloading a default root certificate.
tags:
- Products
responses:
'200':
description: Get default root certificate successfully.
'404':
description: Not found the default root certificate.
'500':
description: Unexpected internal errors.
/ldap/ping:
post:
summary: Ping available ldap service.
@ -3754,55 +3708,6 @@ definitions:
comment:
type: string
description: The new comment.
Storage:
type: object
properties:
total:
type: integer
format: int64
description: Total volume size.
free:
type: integer
format: int64
description: Free volume size.
GeneralInfo:
type: object
properties:
with_notary:
type: boolean
description: If the Harbor instance is deployed with nested notary.
with_chartmuseum:
type: boolean
description: If the Harbor instance is deployed with nested chartmuseum.
registry_url:
type: string
description: The url of registry against which the docker command should be issued.
external_url:
type: string
description: The external URL of Harbor, with protocol.
auth_mode:
type: string
description: The auth mode of current Harbor instance.
project_creation_restriction:
type: string
description: 'Indicate who can create projects, it could be ''adminonly'' or ''everyone''.'
self_registration:
type: boolean
description: Indicate whether the Harbor instance enable user to register himself.
has_ca_root:
type: boolean
description: Indicate whether there is a ca root cert file ready for download in the file system.
harbor_version:
type: string
description: The build version of Harbor.
SystemInfo:
type: object
properties:
storage:
type: array
description: The storage of system.
items:
$ref: '#/definitions/Storage'
LdapConf:
type: object
properties:

View File

@ -1925,6 +1925,75 @@ paths:
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
/systeminfo:
get:
summary: Get general system info
description: |
This API is for retrieving general system info, this can be called by anonymous request. Some attributes will be omitted in the response when this API is called by anonymous request.
tags:
- systeminfo
responses:
'200':
description: Get general info successfully.
schema:
$ref: '#/definitions/GeneralInfo'
'500':
$ref: '#/responses/500'
/systeminfo/volumes:
get:
summary: Get system volume info (total/free size).
description: |
This endpoint is for retrieving system volume info that only provides for admin user. Note that the response only reflects the storage status of local disk.
tags:
- systeminfo
responses:
'200':
description: Get system volumes successfully.
schema:
$ref: '#/definitions/SystemInfo'
'401':
$ref: '#/responses/401'
'403':
$ref: '#/responses/403'
'404':
$ref: '#/responses/404'
'500':
$ref: '#/responses/500'
/systeminfo/getcert:
get:
summary: Get default root certificate.
description: |
This endpoint is for downloading a default root certificate.
tags:
- systeminfo
produces:
- application/octet-stream
responses:
'200':
description: Get default root certificate successfully.
schema:
type: file
headers:
Content-Disposition:
description: To set the filename of the downloaded file.
type: string
'404':
description: Not found the default root certificate.
'500':
$ref: '#/responses/500'
/ping:
get:
summary: Ping Harbor to check if it's alive.
description: This API simply replies a pong to indicate the process to handle API is up, disregarding the health status of dependent components.
tags:
- ping
produces:
- text/plain
responses:
'200':
description: The API server is alive
schema:
type: string
parameters:
query:
name: q
@ -3122,3 +3191,108 @@ definitions:
description: The permission of robot account
items:
$ref: '#/definitions/Access'
Storage:
type: object
properties:
total:
type: integer
format: uint64
description: Total volume size.
free:
type: integer
format: uint64
description: Free volume size.
GeneralInfo:
type: object
properties:
with_notary:
type: boolean
x-nullable: true
x-omitempty: true
description: If the Harbor instance is deployed with nested notary.
with_chartmuseum:
type: boolean
x-nullable: true
x-omitempty: true
description: If the Harbor instance is deployed with nested chartmuseum.
registry_url:
type: string
x-nullable: true
x-omitempty: true
description: The url of registry against which the docker command should be issued.
external_url:
type: string
x-nullable: true
x-omitempty: true
description: The external URL of Harbor, with protocol.
auth_mode:
type: string
x-nullable: true
x-omitempty: true
description: The auth mode of current Harbor instance.
project_creation_restriction:
type: string
x-nullable: true
x-omitempty: true
description: 'Indicate who can create projects, it could be ''adminonly'' or ''everyone''.'
self_registration:
type: boolean
x-nullable: true
x-omitempty: true
description: Indicate whether the Harbor instance enable user to register himself.
has_ca_root:
type: boolean
x-nullable: true
x-omitempty: true
description: Indicate whether there is a ca root cert file ready for download in the file system.
harbor_version:
type: string
x-nullable: true
x-omitempty: true
description: The build version of Harbor.
registry_storage_provider_name:
type: string
x-nullable: true
x-omitempty: true
description: The storage provider's name of Harbor registry
read_only:
type: boolean
x-nullable: true
x-omitempty: true
description: The flag to indicate whether Harbor is in readonly mode.
notification_enable:
type: boolean
x-nullable: true
x-omitempty: true
description: The flag to indicate whether notification mechanism is enabled on Harbor instance.
authproxy_settings:
description: The setting of auth proxy this is only available when Harbor relies on authproxy for authentication.
x-nullable: true
x-omitempty: true
$ref: '#/definitions/AuthproxySetting'
AuthproxySetting:
type: object
properties:
endpoint:
type: string
description: The fully qualified URI of login endpoint of authproxy, such as 'https://192.168.1.2:8443/login'
tokenreivew_endpoint:
type: string
description: The fully qualified URI of token review endpoint of authproxy, such as 'https://192.168.1.2:8443/tokenreview'
skip_search:
type: boolean
description: The flag to determine whether Harbor can skip search the user/group when adding him as a member.
verify_cert:
type: boolean
description: The flag to determine whether Harbor should verify the certificate when connecting to the auth proxy.
server_certificate:
type: string
description: The certificate to be pinned when connecting auth proxy.
SystemInfo:
type: object
properties:
storage:
type: array
description: The storage of system.
items:
$ref: '#/definitions/Storage'

View File

@ -16,16 +16,17 @@ package oidc
import (
"encoding/json"
"github.com/goharbor/harbor/src/common"
config2 "github.com/goharbor/harbor/src/common/config"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/core/config"
"github.com/stretchr/testify/assert"
"net/url"
"os"
"strings"
"testing"
"time"
"github.com/goharbor/harbor/src/common"
config2 "github.com/goharbor/harbor/src/common/config"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/core/config"
"github.com/stretchr/testify/assert"
)
func TestMain(m *testing.M) {

View File

@ -0,0 +1,155 @@
// Copyright Project Harbor Authors
//
// 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 systeminfo
import (
"context"
"fmt"
"io"
"os"
"strings"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/systeminfo"
"github.com/goharbor/harbor/src/pkg/systeminfo/imagestorage"
"github.com/goharbor/harbor/src/pkg/version"
)
const defaultRootCert = "/etc/core/ca/ca.crt"
// for UT only
var testRootCertPath = ""
// Ctl is the default instance of the package
var Ctl = NewController()
// Data wraps common systeminfo data
type Data struct {
AuthMode string
SelfRegistration bool
HarborVersion string
AuthProxySettings *models.HTTPAuthProxy
Protected *protectedData
}
type protectedData struct {
WithNotary bool
RegistryURL string
ExtURL string
ProjectCreationRestrict string
HasCARoot bool
RegistryStorageProviderName string
ReadOnly bool
WithChartMuseum bool
NotificationEnable bool
}
// Options provide a set of attributes to control what info should be returned
type Options struct {
// WithProtectedInfo controls if the protected info, which are considered to be sensitive, should be returned
WithProtectedInfo bool
}
// Controller defines the methods needed for systeminfo API
type Controller interface {
// GetInfo consolidates the info of the system by checking settings in DB and env vars
GetInfo(ctx context.Context, opt Options) (*Data, error)
// GetCapacity returns total and free space of the storage in byte
GetCapacity(ctx context.Context) (*imagestorage.Capacity, error)
// GetCA returns a ReadCloser of Harbor's CA if it's configured and accessible from Harbor core
GetCA(ctx context.Context) (io.ReadCloser, error)
}
type controller struct{}
func (c *controller) GetInfo(ctx context.Context, opt Options) (*Data, error) {
logger := log.GetLogger(ctx)
cfg, err := config.GetSystemCfg()
if err != nil {
logger.Errorf("Error occurred getting config: %v", err)
return nil, err
}
res := &Data{
AuthMode: utils.SafeCastString(cfg[common.AUTHMode]),
SelfRegistration: utils.SafeCastBool(cfg[common.SelfRegistration]),
HarborVersion: fmt.Sprintf("%s-%s", version.ReleaseVersion, version.GitCommit),
}
if res.AuthMode == common.HTTPAuth {
if s, err := config.HTTPAuthProxySetting(); err == nil {
res.AuthProxySettings = s
} else {
logger.Warningf("Failed to get auth proxy setting, error: %v", err)
}
}
if !opt.WithProtectedInfo {
return res, nil
}
extURL := cfg[common.ExtEndpoint].(string)
var registryURL string
if l := strings.Split(extURL, "://"); len(l) > 1 {
registryURL = l[1]
} else {
registryURL = l[0]
}
_, caStatErr := os.Stat(defaultRootCert)
enableCADownload := caStatErr == nil && strings.HasPrefix(extURL, "https://")
res.Protected = &protectedData{
WithNotary: config.WithNotary(),
WithChartMuseum: config.WithChartMuseum(),
ReadOnly: config.ReadOnly(),
ExtURL: extURL,
RegistryURL: registryURL,
HasCARoot: enableCADownload,
ProjectCreationRestrict: utils.SafeCastString(cfg[common.ProjectCreationRestriction]),
RegistryStorageProviderName: utils.SafeCastString(cfg[common.RegistryStorageProviderName]),
NotificationEnable: utils.SafeCastBool(cfg[common.NotificationEnable]),
}
return res, nil
}
func (c *controller) GetCapacity(ctx context.Context) (*imagestorage.Capacity, error) {
systeminfo.Init()
return imagestorage.GlobalDriver.Cap()
}
func (c *controller) GetCA(ctx context.Context) (io.ReadCloser, error) {
logger := log.GetLogger(ctx)
path := defaultRootCert
if len(testRootCertPath) > 0 {
path = testRootCertPath
}
if _, err := os.Stat(path); err == nil {
return os.Open(path)
} else if os.IsNotExist(err) {
return nil, errors.NotFoundError(fmt.Errorf("cert not found in path: %s", path))
} else {
logger.Errorf("Failed to stat the cert, path: %s, error: %v", path, err)
return nil, err
}
}
// NewController return an instance of controller
func NewController() Controller {
return &controller{}
}

View File

@ -0,0 +1,103 @@
package systeminfo
import (
"context"
"testing"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/pkg/version"
"github.com/stretchr/testify/suite"
)
type sysInfoCtlTestSuite struct {
suite.Suite
ctl Controller
}
func (s *sysInfoCtlTestSuite) SetupTest() {
version.ReleaseVersion = "test"
version.GitCommit = "fakeid"
conf := map[string]interface{}{
common.AUTHMode: "db_auth",
common.SelfRegistration: true,
common.ExtEndpoint: "https://test.goharbor.io",
common.ProjectCreationRestriction: "everyone",
common.RegistryStorageProviderName: "filesystem",
common.ReadOnly: false,
common.NotificationEnable: false,
common.WithChartMuseum: false,
common.WithNotary: true,
}
config.InitWithSettings(conf)
s.ctl = Ctl
}
func (s *sysInfoCtlTestSuite) TestGetCert() {
assert := s.Assert()
testRootCertPath = "./notexist.crt"
rc, err := s.ctl.GetCA(context.Background())
assert.Nil(rc)
assert.NotNil(err)
assert.True(errors.IsNotFoundErr(err))
}
func (s *sysInfoCtlTestSuite) TestGetInfo() {
assert := s.Assert()
cases := []struct {
withProtected bool
expect Data
}{
{
withProtected: false,
expect: Data{
AuthMode: "db_auth",
HarborVersion: "test-fakeid",
SelfRegistration: true,
},
},
{
withProtected: true,
expect: Data{
AuthMode: "db_auth",
HarborVersion: "test-fakeid",
SelfRegistration: true,
Protected: &protectedData{
WithNotary: true,
RegistryURL: "test.goharbor.io",
ExtURL: "https://test.goharbor.io",
ProjectCreationRestrict: "everyone",
// CI pipeline has it
HasCARoot: true,
RegistryStorageProviderName: "filesystem",
ReadOnly: false,
WithChartMuseum: false,
NotificationEnable: false,
},
},
},
}
for _, tc := range cases {
res, err := s.ctl.GetInfo(context.Background(), Options{
WithProtectedInfo: tc.withProtected,
})
assert.Nil(err)
exp := tc.expect
if exp.Protected == nil {
assert.Nil(res.Protected)
assert.Equal(exp, *res)
} else {
assert.Equal(*exp.Protected, *res.Protected)
exp.Protected = nil
res.Protected = nil
assert.Equal(exp, *res)
}
}
}
func TestControllerSuite(t *testing.T) {
suite.Run(t, &sysInfoCtlTestSuite{})
}

View File

@ -113,9 +113,6 @@ func init() {
beego.Router("/api/registries", &RegistryAPI{}, "get:List;post:Post")
beego.Router("/api/registries/ping", &RegistryAPI{}, "post:Ping")
beego.Router("/api/registries/:id([0-9]+)", &RegistryAPI{}, "get:Get;put:Put;delete:Delete")
beego.Router("/api/systeminfo", &SystemInfoAPI{}, "get:GetGeneralInfo")
beego.Router("/api/systeminfo/volumes", &SystemInfoAPI{}, "get:GetVolumeInfo")
beego.Router("/api/systeminfo/getcert", &SystemInfoAPI{}, "get:GetCert")
beego.Router("/api/ldap/ping", &LdapAPI{}, "post:Ping")
beego.Router("/api/ldap/users/search", &LdapAPI{}, "get:Search")
beego.Router("/api/ldap/groups/search", &LdapAPI{}, "get:SearchGroup")
@ -125,7 +122,6 @@ func init() {
beego.Router("/api/email/ping", &EmailAPI{}, "post:Ping")
beego.Router("/api/labels", &LabelAPI{}, "post:Post;get:List")
beego.Router("/api/labels/:id([0-9]+", &LabelAPI{}, "get:Get;put:Put;delete:Delete")
beego.Router("/api/ping", &SystemInfoAPI{}, "get:Ping")
beego.Router("/api/system/gc/:id", &GCAPI{}, "get:GetGC")
beego.Router("/api/system/gc/:id([0-9]+)/log", &GCAPI{}, "get:GetLog")
beego.Router("/api/system/gc/schedule", &GCAPI{}, "get:Get;put:Put;post:Post")
@ -739,39 +735,6 @@ func (a testapi) UsersDelete(userID int, authInfo usrInfo) (int, error) {
return httpStatusCode, err
}
// Get system volume info
func (a testapi) VolumeInfoGet(authInfo usrInfo) (int, apilib.SystemInfo, error) {
_sling := sling.New().Get(a.basePath)
path := "/api/systeminfo/volumes"
_sling = _sling.Path(path)
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo)
var successPayLoad apilib.SystemInfo
if 200 == httpStatusCode && nil == err {
err = json.Unmarshal(body, &successPayLoad)
}
return httpStatusCode, successPayLoad, err
}
func (a testapi) GetGeneralInfo() (int, []byte, error) {
_sling := sling.New().Get(a.basePath).Path("/api/systeminfo")
return request(_sling, jsonAcceptHeader)
}
func (a testapi) Ping() (int, []byte, error) {
_sling := sling.New().Get(a.basePath).Path("/api/ping")
return request(_sling, jsonAcceptHeader)
}
// Get system cert
func (a testapi) CertGet(authInfo usrInfo) (int, []byte, error) {
_sling := sling.New().Get(a.basePath)
path := "/api/systeminfo/getcert"
_sling = _sling.Path(path)
httpStatusCode, body, err := request(_sling, jsonAcceptHeader, authInfo)
return httpStatusCode, body, err
}
// Post ldap test
func (a testapi) LdapPost(authInfo usrInfo, ldapConf apilib.LdapConf) (int, error) {

View File

@ -1,169 +0,0 @@
// Copyright 2018 Project Harbor Authors
//
// 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 (
"errors"
"fmt"
"net/http"
"os"
"strings"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/core/systeminfo"
"github.com/goharbor/harbor/src/core/systeminfo/imagestorage"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/version"
)
// SystemInfoAPI handle requests for getting system info /api/systeminfo
type SystemInfoAPI struct {
BaseController
}
const defaultRootCert = "/etc/core/ca/ca.crt"
// SystemInfo models for system info.
type SystemInfo struct {
HarborStorage Storage `json:"storage"`
}
// Storage models for storage.
type Storage struct {
Total uint64 `json:"total"`
Free uint64 `json:"free"`
}
// GeneralInfo wraps common systeminfo for anonymous request
type GeneralInfo struct {
WithNotary bool `json:"with_notary"`
AuthMode string `json:"auth_mode"`
AuthProxySettings *models.HTTPAuthProxy `json:"authproxy_settings,omitempty"`
RegistryURL string `json:"registry_url"`
ExtURL string `json:"external_url"`
ProjectCreationRestrict string `json:"project_creation_restriction"`
SelfRegistration bool `json:"self_registration"`
HasCARoot bool `json:"has_ca_root"`
HarborVersion string `json:"harbor_version"`
RegistryStorageProviderName string `json:"registry_storage_provider_name"`
ReadOnly bool `json:"read_only"`
WithChartMuseum bool `json:"with_chartmuseum"`
NotificationEnable bool `json:"notification_enable"`
}
// GetVolumeInfo gets specific volume storage info.
func (sia *SystemInfoAPI) GetVolumeInfo() {
if !sia.SecurityCtx.IsAuthenticated() {
sia.SendUnAuthorizedError(errors.New("UnAuthorized"))
return
}
if !sia.SecurityCtx.IsSysAdmin() {
sia.SendForbiddenError(errors.New(sia.SecurityCtx.GetUsername()))
return
}
systeminfo.Init()
capacity, err := imagestorage.GlobalDriver.Cap()
if err != nil {
log.Errorf("failed to get capacity: %v", err)
sia.SendInternalServerError(fmt.Errorf("failed to get capacity: %v", err))
return
}
systemInfo := SystemInfo{
HarborStorage: Storage{
Total: capacity.Total,
Free: capacity.Free,
},
}
sia.Data["json"] = systemInfo
sia.ServeJSON()
}
// GetCert gets default self-signed certificate.
func (sia *SystemInfoAPI) GetCert() {
if _, err := os.Stat(defaultRootCert); err == nil {
sia.Ctx.Output.Header("Content-Type", "application/octet-stream")
sia.Ctx.Output.Header("Content-Disposition", "attachment; filename=ca.crt")
http.ServeFile(sia.Ctx.ResponseWriter, sia.Ctx.Request, defaultRootCert)
} else if os.IsNotExist(err) {
log.Error("No certificate found.")
sia.SendNotFoundError(errors.New("no certificate found"))
return
} else {
log.Errorf("Unexpected error: %v", err)
sia.SendInternalServerError(fmt.Errorf("unexpected error: %v", err))
return
}
}
// GetGeneralInfo returns the general system info, which is to be called by anonymous user
func (sia *SystemInfoAPI) GetGeneralInfo() {
cfg, err := config.GetSystemCfg()
if err != nil {
log.Errorf("Error occurred getting config: %v", err)
sia.SendInternalServerError(fmt.Errorf("unexpected error: %v", err))
return
}
extURL := cfg[common.ExtEndpoint].(string)
var registryURL string
if l := strings.Split(extURL, "://"); len(l) > 1 {
registryURL = l[1]
} else {
registryURL = l[0]
}
_, caStatErr := os.Stat(defaultRootCert)
enableCADownload := caStatErr == nil && strings.HasPrefix(extURL, "https://")
harborVersion := sia.getVersion()
info := GeneralInfo{
WithNotary: config.WithNotary(),
AuthMode: utils.SafeCastString(cfg[common.AUTHMode]),
ProjectCreationRestrict: utils.SafeCastString(cfg[common.ProjectCreationRestriction]),
SelfRegistration: utils.SafeCastBool(cfg[common.SelfRegistration]),
ExtURL: extURL,
RegistryURL: registryURL,
HasCARoot: enableCADownload,
HarborVersion: harborVersion,
RegistryStorageProviderName: utils.SafeCastString(cfg[common.RegistryStorageProviderName]),
ReadOnly: config.ReadOnly(),
WithChartMuseum: config.WithChartMuseum(),
NotificationEnable: utils.SafeCastBool(cfg[common.NotificationEnable]),
}
if info.AuthMode == common.HTTPAuth {
if s, err := config.HTTPAuthProxySetting(); err == nil {
info.AuthProxySettings = s
} else {
log.Warningf("Failed to get auth proxy setting, error: %v", err)
}
}
sia.Data["json"] = info
sia.ServeJSON()
}
// getVersion gets harbor version.
func (sia *SystemInfoAPI) getVersion() string {
return fmt.Sprintf("%s-%s", version.ReleaseVersion, version.GitCommit)
}
// Ping ping the harbor core service.
func (sia *SystemInfoAPI) Ping() {
sia.Data["json"] = "Pong"
sia.ServeJSON()
}

View File

@ -1,106 +0,0 @@
// Copyright 2018 Project Harbor Authors
//
// 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 (
"encoding/json"
"fmt"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/core/config"
"github.com/stretchr/testify/assert"
"testing"
)
func TestGetVolumeInfo(t *testing.T) {
fmt.Println("Testing Get Volume Info")
assert := assert.New(t)
apiTest := newHarborAPI()
// case 1: get volume info without admin role
CommonAddUser()
code, _, err := apiTest.VolumeInfoGet(*testUser)
if err != nil {
t.Error("Error occurred while get system volume info")
t.Log(err)
} else {
assert.Equal(403, code, "Get system volume info should be 403")
}
// case 2: get volume info with admin role
code, info, err := apiTest.VolumeInfoGet(*admin)
if err != nil {
t.Error("Error occurred while get system volume info")
t.Log(err)
} else {
assert.Equal(200, code, "Get system volume info should be 200")
if info.HarborStorage.Total <= 0 {
assert.Equal(1, info.HarborStorage.Total, "Total storage of system should be larger than 0")
}
if info.HarborStorage.Free <= 0 {
assert.Equal(1, info.HarborStorage.Free, "Free storage of system should be larger than 0")
}
}
}
func TestGetGeneralInfo(t *testing.T) {
config.Upload(map[string]interface{}{
common.ReadOnly: false,
})
apiTest := newHarborAPI()
code, body, err := apiTest.GetGeneralInfo()
assert := assert.New(t)
assert.Nil(err, fmt.Sprintf("Unexpected Error: %v", err))
assert.Equal(200, code, fmt.Sprintf("Unexpected status code: %d", code))
g := &GeneralInfo{}
err = json.Unmarshal(body, g)
assert.Nil(err, fmt.Sprintf("Unexpected Error: %v", err))
assert.Equal(false, g.WithNotary, "with notary should be false")
assert.Equal(true, g.HasCARoot, "has ca root should be true")
assert.NotEmpty(g.HarborVersion, "harbor version should not be empty")
assert.Equal(false, g.ReadOnly, "readonly should be false")
}
func TestGetCert(t *testing.T) {
fmt.Println("Testing Get Cert")
assert := assert.New(t)
apiTest := newHarborAPI()
// case 1: get cert without admin role
code, content, err := apiTest.CertGet(*testUser)
if err != nil {
t.Error("Error occurred while get system cert")
t.Log(err)
} else {
assert.Equal(200, code, "Get system cert should be 200")
assert.Equal("test for ca.crt.\n", string(content), "Get system cert content should be equal")
}
// case 2: get cert with admin role
code, content, err = apiTest.CertGet(*admin)
if err != nil {
t.Error("Error occurred while get system cert")
t.Log(err)
} else {
assert.Equal(200, code, "Get system cert should be 200")
assert.Equal("test for ca.crt.\n", string(content), "Get system cert content should be equal")
}
CommonDelUser()
}
func TestPing(t *testing.T) {
apiTest := newHarborAPI()
code, _, err := apiTest.Ping()
assert := assert.New(t)
assert.Nil(err, fmt.Sprintf("Unexpected Error: %v", err))
assert.Equal(200, code, fmt.Sprintf("Unexpected status code: %d", code))
}

View File

@ -19,8 +19,8 @@ import (
"reflect"
"syscall"
storage "github.com/goharbor/harbor/src/core/systeminfo/imagestorage"
"github.com/goharbor/harbor/src/lib/log"
storage "github.com/goharbor/harbor/src/pkg/systeminfo/imagestorage"
)
const (

View File

@ -17,7 +17,7 @@ package filesystem
import (
"testing"
storage "github.com/goharbor/harbor/src/core/systeminfo/imagestorage"
storage "github.com/goharbor/harbor/src/pkg/systeminfo/imagestorage"
"github.com/stretchr/testify/assert"
)

View File

@ -17,8 +17,8 @@ package systeminfo
import (
"os"
"github.com/goharbor/harbor/src/core/systeminfo/imagestorage"
"github.com/goharbor/harbor/src/core/systeminfo/imagestorage/filesystem"
"github.com/goharbor/harbor/src/pkg/systeminfo/imagestorage"
"github.com/goharbor/harbor/src/pkg/systeminfo/imagestorage/filesystem"
)
// Init image storage driver

View File

@ -87,7 +87,7 @@ export class SignInComponent implements AfterViewChecked, OnInit {
}
}
// Make sure the updated configuration can be loaded
// Before login: Make sure the updated configuration can be loaded
this.appConfigService.load()
.subscribe(updatedConfig => this.appConfig = updatedConfig
, error => {
@ -255,6 +255,14 @@ export class SignInComponent implements AfterViewChecked, OnInit {
this.router.navigateByUrl(this.redirectUrl);
}
this.isCoreServiceAvailable = true;
// after login successfully: Make sure the updated configuration can be loaded
this.appConfigService.load()
.subscribe(updatedConfig => this.appConfig = updatedConfig
, error => {
// Catch the error
console.error("Failed to load bootstrap options with error: ", error);
});
}, error => {
// 403 oidc login no body;
if (this.isOidcLoginMode && error && error.status === 403) {

View File

@ -39,6 +39,8 @@ func New() http.Handler {
RobotAPI: newRobotAPI(),
Robotv1API: newRobotV1API(),
ReplicationAPI: newReplicationAPI(),
SysteminfoAPI: newSystemInfoAPI(),
PingAPI: newPingAPI(),
})
if err != nil {
log.Fatal(err)

View File

@ -0,0 +1,20 @@
package handler
import (
"context"
"github.com/go-openapi/runtime/middleware"
"github.com/goharbor/harbor/src/server/v2.0/restapi/operations/ping"
)
type pingAPI struct {
BaseAPI
}
func newPingAPI() *pingAPI {
return &pingAPI{}
}
func (p *pingAPI) GetPing(ctx context.Context, params ping.GetPingParams) middleware.Responder {
return ping.NewGetPingOK().WithPayload("Pong")
}

View File

@ -0,0 +1,95 @@
package handler
import (
"context"
"github.com/go-openapi/runtime/middleware"
"github.com/goharbor/harbor/src/common/security"
si "github.com/goharbor/harbor/src/controller/systeminfo"
"github.com/goharbor/harbor/src/server/v2.0/models"
"github.com/goharbor/harbor/src/server/v2.0/restapi/operations/systeminfo"
)
type sysInfoAPI struct {
BaseAPI
ctl si.Controller
}
func newSystemInfoAPI() *sysInfoAPI {
return &sysInfoAPI{
ctl: si.Ctl,
}
}
func (s *sysInfoAPI) GetSysteminfo(ctx context.Context, params systeminfo.GetSysteminfoParams) middleware.Responder {
opt := si.Options{}
sc, ok := security.FromContext(ctx)
if ok && sc.IsAuthenticated() {
opt.WithProtectedInfo = true
}
data, err := s.ctl.GetInfo(ctx, opt)
if err != nil {
return s.SendError(ctx, err)
}
return systeminfo.NewGetSysteminfoOK().WithPayload(s.convertInfo(data))
}
func (s *sysInfoAPI) GetSysteminfoGetcert(ctx context.Context, params systeminfo.GetSysteminfoGetcertParams) middleware.Responder {
f, err := s.ctl.GetCA(ctx)
if err != nil {
return s.SendError(ctx, err)
}
return systeminfo.NewGetSysteminfoGetcertOK().WithContentDisposition("attachment; filename=ca.crt").WithPayload(f)
}
func (s *sysInfoAPI) GetSysteminfoVolumes(ctx context.Context, params systeminfo.GetSysteminfoVolumesParams) middleware.Responder {
if err := s.RequireSysAdmin(ctx); err != nil {
return s.SendError(ctx, err)
}
c, err := s.ctl.GetCapacity(ctx)
if err != nil {
return s.SendError(ctx, err)
}
return systeminfo.NewGetSysteminfoVolumesOK().WithPayload(&models.SystemInfo{
Storage: []*models.Storage{
{
Free: c.Free,
Total: c.Total,
},
},
})
}
func (s *sysInfoAPI) convertInfo(d *si.Data) *models.GeneralInfo {
if d == nil {
return nil
}
res := &models.GeneralInfo{
AuthMode: &d.AuthMode,
SelfRegistration: &d.SelfRegistration,
HarborVersion: &d.HarborVersion,
}
if d.AuthProxySettings != nil {
res.AuthproxySettings = &models.AuthproxySetting{
Endpoint: d.AuthProxySettings.Endpoint,
TokenreivewEndpoint: d.AuthProxySettings.TokenReviewEndpoint,
ServerCertificate: d.AuthProxySettings.ServerCertificate,
VerifyCert: d.AuthProxySettings.VerifyCert,
SkipSearch: d.AuthProxySettings.SkipSearch,
}
}
if d.Protected != nil {
res.HasCaRoot = &d.Protected.HasCARoot
res.ProjectCreationRestriction = &d.Protected.ProjectCreationRestrict
res.ExternalURL = &d.Protected.ExtURL
res.RegistryURL = &d.Protected.RegistryURL
res.WithChartmuseum = &d.Protected.WithChartMuseum
res.WithNotary = &d.Protected.WithNotary
res.ReadOnly = &d.Protected.ReadOnly
res.RegistryStorageProviderName = &d.Protected.RegistryStorageProviderName
res.NotificationEnable = &d.Protected.NotificationEnable
}
return res
}

View File

@ -38,7 +38,6 @@ func registerLegacyRoutes() {
beego.Router("/api/"+version+"/ldap/users/import", &api.LdapAPI{}, "post:ImportUser")
beego.Router("/api/"+version+"/email/ping", &api.EmailAPI{}, "post:Ping")
beego.Router("/api/"+version+"/health", &api.HealthAPI{}, "get:CheckHealth")
beego.Router("/api/"+version+"/ping", &api.SystemInfoAPI{}, "get:Ping")
beego.Router("/api/"+version+"/search", &api.SearchAPI{})
beego.Router("/api/"+version+"/projects/:id([0-9]+)/metadatas/?:name", &api.MetadataAPI{}, "get:Get")
beego.Router("/api/"+version+"/projects/:id([0-9]+)/metadatas/", &api.MetadataAPI{}, "post:Post")
@ -73,10 +72,6 @@ func registerLegacyRoutes() {
beego.Router("/api/"+version+"/labels", &api.LabelAPI{}, "post:Post;get:List")
beego.Router("/api/"+version+"/labels/:id([0-9]+)", &api.LabelAPI{}, "get:Get;put:Put;delete:Delete")
beego.Router("/api/"+version+"/systeminfo", &api.SystemInfoAPI{}, "get:GetGeneralInfo")
beego.Router("/api/"+version+"/systeminfo/volumes", &api.SystemInfoAPI{}, "get:GetVolumeInfo")
beego.Router("/api/"+version+"/systeminfo/getcert", &api.SystemInfoAPI{}, "get:GetCert")
beego.Router("/api/"+version+"/registries", &api.RegistryAPI{}, "get:List;post:Post")
beego.Router("/api/"+version+"/registries/:id([0-9]+)", &api.RegistryAPI{}, "get:Get;put:Put;delete:Delete")
beego.Router("/api/"+version+"/registries/ping", &api.RegistryAPI{}, "post:Ping")

View File

@ -1,32 +0,0 @@
/*
* Harbor API
*
* These APIs provide services for manipulating Harbor project.
*
* OpenAPI spec version: 0.3.0
*
* Generated by: https://github.com/swagger-api/swagger-codegen.git
*
* 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 apilib
type SystemInfo struct {
HarborStorage Storage `json:"storage"`
}
type Storage struct {
Total uint64 `json:"total"`
Free uint64 `json:"free"`
}