mirror of
https://github.com/goharbor/harbor
synced 2025-04-15 04:12:47 +00:00
546 lines
17 KiB
Go
Executable File
546 lines
17 KiB
Go
Executable File
// 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 config provide config for core api and other modules
|
|
// Before accessing user settings, need to call Load()
|
|
// For system settings, no need to call Load()
|
|
package config
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/goharbor/harbor/src/common"
|
|
comcfg "github.com/goharbor/harbor/src/common/config"
|
|
"github.com/goharbor/harbor/src/common/models"
|
|
"github.com/goharbor/harbor/src/common/secret"
|
|
"github.com/goharbor/harbor/src/common/utils/log"
|
|
"github.com/goharbor/harbor/src/core/promgr"
|
|
"github.com/goharbor/harbor/src/core/promgr/pmsdriver"
|
|
"github.com/goharbor/harbor/src/core/promgr/pmsdriver/admiral"
|
|
"github.com/goharbor/harbor/src/core/promgr/pmsdriver/local"
|
|
)
|
|
|
|
const (
|
|
defaultKeyPath = "/etc/core/key"
|
|
defaultTokenFilePath = "/etc/core/token/tokens.properties"
|
|
defaultRegistryTokenPrivateKeyPath = "/etc/core/private_key.pem"
|
|
)
|
|
|
|
var (
|
|
// SecretStore manages secrets
|
|
SecretStore *secret.Store
|
|
// GlobalProjectMgr is initialized based on the deploy mode
|
|
GlobalProjectMgr promgr.ProjectManager
|
|
keyProvider comcfg.KeyProvider
|
|
// AdmiralClient is initialized only under integration deploy mode
|
|
// and can be passed to project manager as a parameter
|
|
AdmiralClient *http.Client
|
|
// TokenReader is used in integration mode to read token
|
|
TokenReader admiral.TokenReader
|
|
// defined as a var for testing.
|
|
defaultCACertPath = "/etc/core/ca/ca.crt"
|
|
cfgMgr *comcfg.CfgManager
|
|
)
|
|
|
|
// Init configurations
|
|
func Init() error {
|
|
// init key provider
|
|
initKeyProvider()
|
|
|
|
cfgMgr = comcfg.NewDBCfgManager()
|
|
|
|
log.Info("init secret store")
|
|
// init secret store
|
|
initSecretStore()
|
|
log.Info("init project manager based on deploy mode")
|
|
// init project manager based on deploy mode
|
|
if err := initProjectManager(); err != nil {
|
|
log.Errorf("Failed to initialise project manager, error: %v", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// InitWithSettings init config with predefined configs, and optionally overwrite the keyprovider
|
|
func InitWithSettings(cfgs map[string]interface{}, kp ...comcfg.KeyProvider) {
|
|
Init()
|
|
cfgMgr = comcfg.NewInMemoryManager()
|
|
cfgMgr.UpdateConfig(cfgs)
|
|
if len(kp) > 0 {
|
|
keyProvider = kp[0]
|
|
}
|
|
}
|
|
|
|
func initKeyProvider() {
|
|
path := os.Getenv("KEY_PATH")
|
|
if len(path) == 0 {
|
|
path = defaultKeyPath
|
|
}
|
|
log.Infof("key path: %s", path)
|
|
|
|
keyProvider = comcfg.NewFileKeyProvider(path)
|
|
}
|
|
|
|
func initSecretStore() {
|
|
m := map[string]string{}
|
|
m[JobserviceSecret()] = secret.JobserviceUser
|
|
SecretStore = secret.NewStore(m)
|
|
}
|
|
|
|
func initProjectManager() error {
|
|
var driver pmsdriver.PMSDriver
|
|
if WithAdmiral() {
|
|
log.Debugf("Initialising Admiral client with certificate: %s", defaultCACertPath)
|
|
content, err := ioutil.ReadFile(defaultCACertPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pool := x509.NewCertPool()
|
|
if ok := pool.AppendCertsFromPEM(content); !ok {
|
|
return fmt.Errorf("failed to append cert content into cert worker")
|
|
}
|
|
AdmiralClient = &http.Client{
|
|
Transport: &http.Transport{
|
|
Proxy: http.ProxyFromEnvironment,
|
|
TLSClientConfig: &tls.Config{
|
|
RootCAs: pool,
|
|
},
|
|
},
|
|
}
|
|
|
|
// integration with admiral
|
|
log.Info("initializing the project manager based on PMS...")
|
|
path := os.Getenv("SERVICE_TOKEN_FILE_PATH")
|
|
if len(path) == 0 {
|
|
path = defaultTokenFilePath
|
|
}
|
|
log.Infof("service token file path: %s", path)
|
|
TokenReader = &admiral.FileTokenReader{
|
|
Path: path,
|
|
}
|
|
driver = admiral.NewDriver(AdmiralClient, AdmiralEndpoint(), TokenReader)
|
|
} else {
|
|
// standalone
|
|
log.Info("initializing the project manager based on local database...")
|
|
driver = local.NewDriver()
|
|
}
|
|
GlobalProjectMgr = promgr.NewDefaultProjectManager(driver, true)
|
|
return nil
|
|
|
|
}
|
|
|
|
// GetCfgManager return the current config manager
|
|
func GetCfgManager() *comcfg.CfgManager {
|
|
if cfgMgr == nil {
|
|
return comcfg.NewDBCfgManager()
|
|
}
|
|
return cfgMgr
|
|
}
|
|
|
|
// Load configurations
|
|
func Load() error {
|
|
return cfgMgr.Load()
|
|
}
|
|
|
|
// Upload save all system configurations
|
|
func Upload(cfg map[string]interface{}) error {
|
|
return cfgMgr.UpdateConfig(cfg)
|
|
}
|
|
|
|
// GetSystemCfg returns the system configurations
|
|
func GetSystemCfg() (map[string]interface{}, error) {
|
|
sysCfg := cfgMgr.GetAll()
|
|
if len(sysCfg) == 0 {
|
|
return nil, errors.New("can not load system config, the database might be down")
|
|
}
|
|
return sysCfg, nil
|
|
}
|
|
|
|
// AuthMode ...
|
|
func AuthMode() (string, error) {
|
|
err := cfgMgr.Load()
|
|
if err != nil {
|
|
log.Errorf("failed to load config, error %v", err)
|
|
return "db_auth", err
|
|
}
|
|
return cfgMgr.Get(common.AUTHMode).GetString(), nil
|
|
}
|
|
|
|
// TokenPrivateKeyPath returns the path to the key for signing token for registry
|
|
func TokenPrivateKeyPath() string {
|
|
path := os.Getenv("TOKEN_PRIVATE_KEY_PATH")
|
|
if len(path) == 0 {
|
|
path = defaultRegistryTokenPrivateKeyPath
|
|
}
|
|
return path
|
|
}
|
|
|
|
// LDAPConf returns the setting of ldap server
|
|
func LDAPConf() (*models.LdapConf, error) {
|
|
err := cfgMgr.Load()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &models.LdapConf{
|
|
LdapURL: cfgMgr.Get(common.LDAPURL).GetString(),
|
|
LdapSearchDn: cfgMgr.Get(common.LDAPSearchDN).GetString(),
|
|
LdapSearchPassword: cfgMgr.Get(common.LDAPSearchPwd).GetString(),
|
|
LdapBaseDn: cfgMgr.Get(common.LDAPBaseDN).GetString(),
|
|
LdapUID: cfgMgr.Get(common.LDAPUID).GetString(),
|
|
LdapFilter: cfgMgr.Get(common.LDAPFilter).GetString(),
|
|
LdapScope: cfgMgr.Get(common.LDAPScope).GetInt(),
|
|
LdapConnectionTimeout: cfgMgr.Get(common.LDAPTimeout).GetInt(),
|
|
LdapVerifyCert: cfgMgr.Get(common.LDAPVerifyCert).GetBool(),
|
|
}, nil
|
|
}
|
|
|
|
// LDAPGroupConf returns the setting of ldap group search
|
|
func LDAPGroupConf() (*models.LdapGroupConf, error) {
|
|
err := cfgMgr.Load()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &models.LdapGroupConf{
|
|
LdapGroupBaseDN: cfgMgr.Get(common.LDAPGroupBaseDN).GetString(),
|
|
LdapGroupFilter: cfgMgr.Get(common.LDAPGroupSearchFilter).GetString(),
|
|
LdapGroupNameAttribute: cfgMgr.Get(common.LDAPGroupAttributeName).GetString(),
|
|
LdapGroupSearchScope: cfgMgr.Get(common.LDAPGroupSearchScope).GetInt(),
|
|
LdapGroupAdminDN: cfgMgr.Get(common.LDAPGroupAdminDn).GetString(),
|
|
LdapGroupMembershipAttribute: cfgMgr.Get(common.LDAPGroupMembershipAttribute).GetString(),
|
|
}, nil
|
|
}
|
|
|
|
// TokenExpiration returns the token expiration time (in minute)
|
|
func TokenExpiration() (int, error) {
|
|
return cfgMgr.Get(common.TokenExpiration).GetInt(), nil
|
|
}
|
|
|
|
// RobotTokenDuration returns the token expiration time of robot account (in minute)
|
|
func RobotTokenDuration() int {
|
|
return cfgMgr.Get(common.RobotTokenDuration).GetInt()
|
|
}
|
|
|
|
// ExtEndpoint returns the external URL of Harbor: protocol://host:port
|
|
func ExtEndpoint() (string, error) {
|
|
return cfgMgr.Get(common.ExtEndpoint).GetString(), nil
|
|
}
|
|
|
|
// ExtURL returns the external URL: host:port
|
|
func ExtURL() (string, error) {
|
|
endpoint, err := ExtEndpoint()
|
|
if err != nil {
|
|
log.Errorf("failed to load config, error %v", err)
|
|
}
|
|
l := strings.Split(endpoint, "://")
|
|
if len(l) > 0 {
|
|
return l[1], nil
|
|
}
|
|
return endpoint, nil
|
|
}
|
|
|
|
// SecretKey returns the secret key to encrypt the password of target
|
|
func SecretKey() (string, error) {
|
|
return keyProvider.Get(nil)
|
|
}
|
|
|
|
// SelfRegistration returns the enablement of self registration
|
|
func SelfRegistration() (bool, error) {
|
|
return cfgMgr.Get(common.SelfRegistration).GetBool(), nil
|
|
}
|
|
|
|
// RegistryURL ...
|
|
func RegistryURL() (string, error) {
|
|
return cfgMgr.Get(common.RegistryURL).GetString(), nil
|
|
}
|
|
|
|
// InternalJobServiceURL returns jobservice URL for internal communication between Harbor containers
|
|
func InternalJobServiceURL() string {
|
|
return strings.TrimSuffix(cfgMgr.Get(common.JobServiceURL).GetString(), "/")
|
|
}
|
|
|
|
// InternalCoreURL returns the local harbor core url
|
|
func InternalCoreURL() string {
|
|
return strings.TrimSuffix(cfgMgr.Get(common.CoreURL).GetString(), "/")
|
|
}
|
|
|
|
// LocalCoreURL returns the local harbor core url
|
|
func LocalCoreURL() string {
|
|
return cfgMgr.Get(common.CoreLocalURL).GetString()
|
|
}
|
|
|
|
// InternalTokenServiceEndpoint returns token service endpoint for internal communication between Harbor containers
|
|
func InternalTokenServiceEndpoint() string {
|
|
return InternalCoreURL() + "/service/token"
|
|
}
|
|
|
|
// InternalNotaryEndpoint returns notary server endpoint for internal communication between Harbor containers
|
|
// This is currently a conventional value and can be unaccessible when Harbor is not deployed with Notary.
|
|
func InternalNotaryEndpoint() string {
|
|
return cfgMgr.Get(common.NotaryURL).GetString()
|
|
}
|
|
|
|
// InitialAdminPassword returns the initial password for administrator
|
|
func InitialAdminPassword() (string, error) {
|
|
return cfgMgr.Get(common.AdminInitialPassword).GetString(), nil
|
|
}
|
|
|
|
// OnlyAdminCreateProject returns the flag to restrict that only sys admin can create project
|
|
func OnlyAdminCreateProject() (bool, error) {
|
|
return cfgMgr.Get(common.ProjectCreationRestriction).GetString() == common.ProCrtRestrAdmOnly, nil
|
|
}
|
|
|
|
// Email returns email server settings
|
|
func Email() (*models.Email, error) {
|
|
err := cfgMgr.Load()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &models.Email{
|
|
Host: cfgMgr.Get(common.EmailHost).GetString(),
|
|
Port: cfgMgr.Get(common.EmailPort).GetInt(),
|
|
Username: cfgMgr.Get(common.EmailUsername).GetString(),
|
|
Password: cfgMgr.Get(common.EmailPassword).GetString(),
|
|
SSL: cfgMgr.Get(common.EmailSSL).GetBool(),
|
|
From: cfgMgr.Get(common.EmailFrom).GetString(),
|
|
Identity: cfgMgr.Get(common.EmailIdentity).GetString(),
|
|
Insecure: cfgMgr.Get(common.EmailInsecure).GetBool(),
|
|
}, nil
|
|
}
|
|
|
|
// Database returns database settings
|
|
func Database() (*models.Database, error) {
|
|
database := &models.Database{}
|
|
database.Type = cfgMgr.Get(common.DatabaseType).GetString()
|
|
postgresql := &models.PostGreSQL{
|
|
Host: cfgMgr.Get(common.PostGreSQLHOST).GetString(),
|
|
Port: cfgMgr.Get(common.PostGreSQLPort).GetInt(),
|
|
Username: cfgMgr.Get(common.PostGreSQLUsername).GetString(),
|
|
Password: cfgMgr.Get(common.PostGreSQLPassword).GetString(),
|
|
Database: cfgMgr.Get(common.PostGreSQLDatabase).GetString(),
|
|
SSLMode: cfgMgr.Get(common.PostGreSQLSSLMode).GetString(),
|
|
MaxIdleConns: cfgMgr.Get(common.PostGreSQLMaxIdleConns).GetInt(),
|
|
MaxOpenConns: cfgMgr.Get(common.PostGreSQLMaxOpenConns).GetInt(),
|
|
}
|
|
database.PostGreSQL = postgresql
|
|
|
|
return database, nil
|
|
}
|
|
|
|
// CoreSecret returns a secret to mark harbor-core when communicate with
|
|
// other component
|
|
func CoreSecret() string {
|
|
return os.Getenv("CORE_SECRET")
|
|
}
|
|
|
|
// JobserviceSecret returns a secret to mark Jobservice when communicate with
|
|
// other component
|
|
// TODO replace it with method of SecretStore
|
|
func JobserviceSecret() string {
|
|
return os.Getenv("JOBSERVICE_SECRET")
|
|
}
|
|
|
|
// WithNotary returns a bool value to indicate if Harbor's deployed with Notary
|
|
func WithNotary() bool {
|
|
return cfgMgr.Get(common.WithNotary).GetBool()
|
|
}
|
|
|
|
// WithClair returns a bool value to indicate if Harbor's deployed with Clair
|
|
func WithClair() bool {
|
|
return cfgMgr.Get(common.WithClair).GetBool()
|
|
}
|
|
|
|
// ClairEndpoint returns the end point of clair instance, by default it's the one deployed within Harbor.
|
|
func ClairEndpoint() string {
|
|
return cfgMgr.Get(common.ClairURL).GetString()
|
|
}
|
|
|
|
// ClairDB return Clair db info
|
|
func ClairDB() (*models.PostGreSQL, error) {
|
|
clairDB := &models.PostGreSQL{
|
|
Host: cfgMgr.Get(common.ClairDBHost).GetString(),
|
|
Port: cfgMgr.Get(common.ClairDBPort).GetInt(),
|
|
Username: cfgMgr.Get(common.ClairDBUsername).GetString(),
|
|
Password: cfgMgr.Get(common.ClairDBPassword).GetString(),
|
|
Database: cfgMgr.Get(common.ClairDB).GetString(),
|
|
SSLMode: cfgMgr.Get(common.ClairDBSSLMode).GetString(),
|
|
}
|
|
return clairDB, nil
|
|
}
|
|
|
|
// ClairAdapterEndpoint returns the endpoint of clair adapter instance, by default it's the one deployed within Harbor.
|
|
func ClairAdapterEndpoint() string {
|
|
return cfgMgr.Get(common.ClairAdapterURL).GetString()
|
|
}
|
|
|
|
// AdmiralEndpoint returns the URL of admiral, if Harbor is not deployed with admiral it should return an empty string.
|
|
func AdmiralEndpoint() string {
|
|
if cfgMgr.Get(common.AdmiralEndpoint).GetString() == "NA" {
|
|
return ""
|
|
}
|
|
return cfgMgr.Get(common.AdmiralEndpoint).GetString()
|
|
}
|
|
|
|
// ScanAllPolicy returns the policy which controls the scan all.
|
|
func ScanAllPolicy() models.ScanAllPolicy {
|
|
var res models.ScanAllPolicy
|
|
log.Infof("Scan all policy %v", cfgMgr.Get(common.ScanAllPolicy).GetString())
|
|
if err := json.Unmarshal([]byte(cfgMgr.Get(common.ScanAllPolicy).GetString()), &res); err != nil {
|
|
log.Errorf("Failed to unmarshal the value in configuration for Scan All policy, error: %v, returning the default policy", err)
|
|
return models.DefaultScanAllPolicy
|
|
}
|
|
return res
|
|
}
|
|
|
|
// WithAdmiral returns a bool to indicate if Harbor's deployed with admiral.
|
|
func WithAdmiral() bool {
|
|
return len(AdmiralEndpoint()) > 0
|
|
}
|
|
|
|
// UAASettings returns the UAASettings to access UAA service.
|
|
func UAASettings() (*models.UAASettings, error) {
|
|
err := cfgMgr.Load()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
us := &models.UAASettings{
|
|
Endpoint: cfgMgr.Get(common.UAAEndpoint).GetString(),
|
|
ClientID: cfgMgr.Get(common.UAAClientID).GetString(),
|
|
ClientSecret: cfgMgr.Get(common.UAAClientSecret).GetString(),
|
|
VerifyCert: cfgMgr.Get(common.UAAVerifyCert).GetBool(),
|
|
}
|
|
return us, nil
|
|
}
|
|
|
|
// ReadOnly returns a bool to indicates if Harbor is in read only mode.
|
|
func ReadOnly() bool {
|
|
return cfgMgr.Get(common.ReadOnly).GetBool()
|
|
}
|
|
|
|
// WithChartMuseum returns a bool to indicate if chartmuseum is deployed with Harbor.
|
|
func WithChartMuseum() bool {
|
|
return cfgMgr.Get(common.WithChartMuseum).GetBool()
|
|
}
|
|
|
|
// GetChartMuseumEndpoint returns the endpoint of the chartmuseum service
|
|
// otherwise an non nil error is returned
|
|
func GetChartMuseumEndpoint() (string, error) {
|
|
chartEndpoint := strings.TrimSpace(cfgMgr.Get(common.ChartRepoURL).GetString())
|
|
if len(chartEndpoint) == 0 {
|
|
return "", errors.New("empty chartmuseum endpoint")
|
|
}
|
|
return chartEndpoint, nil
|
|
}
|
|
|
|
// GetRedisOfRegURL returns the URL of Redis used by registry
|
|
func GetRedisOfRegURL() string {
|
|
return os.Getenv("_REDIS_URL_REG")
|
|
}
|
|
|
|
// GetPortalURL returns the URL of portal
|
|
func GetPortalURL() string {
|
|
url := os.Getenv("PORTAL_URL")
|
|
if len(url) == 0 {
|
|
return common.DefaultPortalURL
|
|
}
|
|
return url
|
|
}
|
|
|
|
// GetRegistryCtlURL returns the URL of registryctl
|
|
func GetRegistryCtlURL() string {
|
|
url := os.Getenv("REGISTRYCTL_URL")
|
|
if len(url) == 0 {
|
|
return common.DefaultRegistryCtlURL
|
|
}
|
|
return url
|
|
}
|
|
|
|
// GetClairHealthCheckServerURL returns the URL of
|
|
// the health check server of Clair
|
|
func GetClairHealthCheckServerURL() string {
|
|
url := os.Getenv("CLAIR_HEALTH_CHECK_SERVER_URL")
|
|
if len(url) == 0 {
|
|
return common.DefaultClairHealthCheckServerURL
|
|
}
|
|
return url
|
|
}
|
|
|
|
// HTTPAuthProxySetting returns the setting of HTTP Auth proxy. the settings are only meaningful when the auth_mode is
|
|
// set to http_auth
|
|
func HTTPAuthProxySetting() (*models.HTTPAuthProxy, error) {
|
|
if err := cfgMgr.Load(); err != nil {
|
|
return nil, err
|
|
}
|
|
return &models.HTTPAuthProxy{
|
|
Endpoint: cfgMgr.Get(common.HTTPAuthProxyEndpoint).GetString(),
|
|
TokenReviewEndpoint: cfgMgr.Get(common.HTTPAuthProxyTokenReviewEndpoint).GetString(),
|
|
VerifyCert: cfgMgr.Get(common.HTTPAuthProxyVerifyCert).GetBool(),
|
|
SkipSearch: cfgMgr.Get(common.HTTPAuthProxySkipSearch).GetBool(),
|
|
}, nil
|
|
|
|
}
|
|
|
|
// OIDCSetting returns the setting of OIDC provider, currently there's only one OIDC provider allowed for Harbor and it's
|
|
// only effective when auth_mode is set to oidc_auth
|
|
func OIDCSetting() (*models.OIDCSetting, error) {
|
|
if err := cfgMgr.Load(); err != nil {
|
|
return nil, err
|
|
}
|
|
scopeStr := cfgMgr.Get(common.OIDCScope).GetString()
|
|
extEndpoint := strings.TrimSuffix(cfgMgr.Get(common.ExtEndpoint).GetString(), "/")
|
|
scope := []string{}
|
|
for _, s := range strings.Split(scopeStr, ",") {
|
|
scope = append(scope, strings.TrimSpace(s))
|
|
}
|
|
|
|
return &models.OIDCSetting{
|
|
Name: cfgMgr.Get(common.OIDCName).GetString(),
|
|
Endpoint: cfgMgr.Get(common.OIDCEndpoint).GetString(),
|
|
VerifyCert: cfgMgr.Get(common.OIDCVerifyCert).GetBool(),
|
|
ClientID: cfgMgr.Get(common.OIDCCLientID).GetString(),
|
|
ClientSecret: cfgMgr.Get(common.OIDCClientSecret).GetString(),
|
|
GroupsClaim: cfgMgr.Get(common.OIDCGroupsClaim).GetString(),
|
|
RedirectURL: extEndpoint + common.OIDCCallbackPath,
|
|
Scope: scope,
|
|
}, nil
|
|
}
|
|
|
|
// NotificationEnable returns a bool to indicates if notification enabled in harbor
|
|
func NotificationEnable() bool {
|
|
return cfgMgr.Get(common.NotificationEnable).GetBool()
|
|
}
|
|
|
|
// QuotaPerProjectEnable returns a bool to indicates if quota per project enabled in harbor
|
|
func QuotaPerProjectEnable() bool {
|
|
return cfgMgr.Get(common.QuotaPerProjectEnable).GetBool()
|
|
}
|
|
|
|
// QuotaSetting returns the setting of quota.
|
|
func QuotaSetting() (*models.QuotaSetting, error) {
|
|
if err := cfgMgr.Load(); err != nil {
|
|
return nil, err
|
|
}
|
|
return &models.QuotaSetting{
|
|
CountPerProject: cfgMgr.Get(common.CountPerProject).GetInt64(),
|
|
StoragePerProject: cfgMgr.Get(common.StoragePerProject).GetInt64(),
|
|
}, nil
|
|
}
|