mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 18:25:56 +01:00
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:
parent
dec12308a1
commit
db8ce49133
@ -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:
|
||||
|
@ -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'
|
||||
|
@ -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) {
|
||||
|
155
src/controller/systeminfo/controller.go
Normal file
155
src/controller/systeminfo/controller.go
Normal 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{}
|
||||
}
|
103
src/controller/systeminfo/controller_test.go
Normal file
103
src/controller/systeminfo/controller_test.go
Normal 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{})
|
||||
}
|
@ -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) {
|
||||
|
||||
|
@ -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()
|
||||
}
|
@ -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))
|
||||
}
|
@ -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 (
|
@ -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"
|
||||
)
|
||||
|
@ -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
|
@ -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) {
|
||||
|
@ -39,6 +39,8 @@ func New() http.Handler {
|
||||
RobotAPI: newRobotAPI(),
|
||||
Robotv1API: newRobotV1API(),
|
||||
ReplicationAPI: newReplicationAPI(),
|
||||
SysteminfoAPI: newSystemInfoAPI(),
|
||||
PingAPI: newPingAPI(),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
20
src/server/v2.0/handler/ping.go
Normal file
20
src/server/v2.0/handler/ping.go
Normal 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")
|
||||
}
|
95
src/server/v2.0/handler/systeminfo.go
Normal file
95
src/server/v2.0/handler/systeminfo.go
Normal 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
|
||||
|
||||
}
|
@ -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")
|
||||
|
@ -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"`
|
||||
}
|
Loading…
Reference in New Issue
Block a user