mirror of
https://github.com/goharbor/harbor.git
synced 2024-10-01 06:47:33 +02:00
Merge pull request #5321 from stonezdj/from_1.5.2
Add SafeCast function and set default value for some system configure -- target master
This commit is contained in:
commit
392d95bf7b
@ -31,7 +31,7 @@ func ListCfgs(w http.ResponseWriter, r *http.Request) {
|
|||||||
handleInternalServerError(w)
|
handleInternalServerError(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
systemcfg.AddMissedKey(cfg)
|
||||||
if err = writeJSON(w, cfg); err != nil {
|
if err = writeJSON(w, cfg); err != nil {
|
||||||
log.Errorf("failed to write response: %v", err)
|
log.Errorf("failed to write response: %v", err)
|
||||||
return
|
return
|
||||||
@ -52,7 +52,6 @@ func UpdateCfgs(w http.ResponseWriter, r *http.Request) {
|
|||||||
handleBadRequestError(w, err.Error())
|
handleBadRequestError(w, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = systemcfg.CfgStore.Write(m); err != nil {
|
if err = systemcfg.CfgStore.Write(m); err != nil {
|
||||||
log.Errorf("failed to update system configurations: %v", err)
|
log.Errorf("failed to update system configurations: %v", err)
|
||||||
handleInternalServerError(w)
|
handleInternalServerError(w)
|
||||||
@ -68,7 +67,6 @@ func ResetCfgs(w http.ResponseWriter, r *http.Request) {
|
|||||||
handleInternalServerError(w)
|
handleInternalServerError(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := systemcfg.CfgStore.Write(cfgs); err != nil {
|
if err := systemcfg.CfgStore.Write(cfgs); err != nil {
|
||||||
log.Errorf("failed to write system configurations to storage: %v", err)
|
log.Errorf("failed to write system configurations to storage: %v", err)
|
||||||
handleInternalServerError(w)
|
handleInternalServerError(w)
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
comcfg "github.com/vmware/harbor/src/common/config"
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
"github.com/vmware/harbor/src/common/dao"
|
"github.com/vmware/harbor/src/common/dao"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -258,36 +259,30 @@ func Init() (err error) {
|
|||||||
if err := dao.InitDatabase(db); err != nil {
|
if err := dao.InitDatabase(db); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := initCfgStore(); err != nil {
|
if err := initCfgStore(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgs := map[string]interface{}{}
|
|
||||||
//Use reload key to avoid reset customed setting after restart
|
//Use reload key to avoid reset customed setting after restart
|
||||||
curCfgs, err := CfgStore.Read()
|
curCfgs, err := CfgStore.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
loadAll := isLoadAll(curCfgs[common.ReloadKey])
|
loadAll := isLoadAll(curCfgs)
|
||||||
if !loadAll {
|
if curCfgs == nil {
|
||||||
cfgs = curCfgs
|
curCfgs = map[string]interface{}{}
|
||||||
if cfgs == nil {
|
|
||||||
log.Info("configurations read from storage driver are null, will load them from environment variables")
|
|
||||||
loadAll = true
|
|
||||||
cfgs = map[string]interface{}{}
|
|
||||||
}
|
}
|
||||||
}
|
//restart: only repeatload envs will be load
|
||||||
|
//reload_config: all envs will be reload except the skiped envs
|
||||||
if err = LoadFromEnv(cfgs, loadAll); err != nil {
|
if err = LoadFromEnv(curCfgs, loadAll); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
AddMissedKey(curCfgs)
|
||||||
return CfgStore.Write(cfgs)
|
return CfgStore.Write(curCfgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isLoadAll(curReloadKey interface{}) bool {
|
func isLoadAll(cfg map[string]interface{}) bool {
|
||||||
return strings.EqualFold(os.Getenv("RESET"), "true") && os.Getenv("RELOAD_KEY") != curReloadKey
|
return cfg == nil || strings.EqualFold(os.Getenv("RESET"), "true") && os.Getenv("RELOAD_KEY") != cfg[common.ReloadKey]
|
||||||
}
|
}
|
||||||
|
|
||||||
func initCfgStore() (err error) {
|
func initCfgStore() (err error) {
|
||||||
@ -328,7 +323,6 @@ func initCfgStore() (err error) {
|
|||||||
// only used when migrating harbor release before v1.3
|
// only used when migrating harbor release before v1.3
|
||||||
// after v1.3 there is always a db configuration before migrate.
|
// after v1.3 there is always a db configuration before migrate.
|
||||||
validLdapScope(jsonconfig, true)
|
validLdapScope(jsonconfig, true)
|
||||||
|
|
||||||
err = CfgStore.Write(jsonconfig)
|
err = CfgStore.Write(jsonconfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to update old configuration to database")
|
log.Error("Failed to update old configuration to database")
|
||||||
@ -418,11 +412,11 @@ func GetDatabaseFromCfg(cfg map[string]interface{}) *models.Database {
|
|||||||
database := &models.Database{}
|
database := &models.Database{}
|
||||||
database.Type = cfg[common.DatabaseType].(string)
|
database.Type = cfg[common.DatabaseType].(string)
|
||||||
postgresql := &models.PostGreSQL{}
|
postgresql := &models.PostGreSQL{}
|
||||||
postgresql.Host = cfg[common.PostGreSQLHOST].(string)
|
postgresql.Host = utils.SafeCastString(cfg[common.PostGreSQLHOST])
|
||||||
postgresql.Port = int(cfg[common.PostGreSQLPort].(int))
|
postgresql.Port = int(utils.SafeCastInt(cfg[common.PostGreSQLPort]))
|
||||||
postgresql.Username = cfg[common.PostGreSQLUsername].(string)
|
postgresql.Username = utils.SafeCastString(cfg[common.PostGreSQLUsername])
|
||||||
postgresql.Password = cfg[common.PostGreSQLPassword].(string)
|
postgresql.Password = utils.SafeCastString(cfg[common.PostGreSQLPassword])
|
||||||
postgresql.Database = cfg[common.PostGreSQLDatabase].(string)
|
postgresql.Database = utils.SafeCastString(cfg[common.PostGreSQLDatabase])
|
||||||
database.PostGreSQL = postgresql
|
database.PostGreSQL = postgresql
|
||||||
return database
|
return database
|
||||||
}
|
}
|
||||||
@ -448,3 +442,26 @@ func validLdapScope(cfg map[string]interface{}, isMigrate bool) {
|
|||||||
cfg[ldapScopeKey] = ldapScope
|
cfg[ldapScopeKey] = ldapScope
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//AddMissedKey ... If the configure key is missing in the cfg map, add default value to it
|
||||||
|
func AddMissedKey(cfg map[string]interface{}) {
|
||||||
|
|
||||||
|
for k, v := range common.HarborStringKeysMap {
|
||||||
|
if _, exist := cfg[k]; !exist {
|
||||||
|
cfg[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range common.HarborNumKeysMap {
|
||||||
|
if _, exist := cfg[k]; !exist {
|
||||||
|
cfg[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range common.HarborBoolKeysMap {
|
||||||
|
if _, exist := cfg[k]; !exist {
|
||||||
|
cfg[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -135,8 +135,10 @@ func TestIsLoadAll(t *testing.T) {
|
|||||||
if err := os.Setenv("RESET", "True"); err != nil {
|
if err := os.Setenv("RESET", "True"); err != nil {
|
||||||
t.Fatalf("failed to set env: %v", err)
|
t.Fatalf("failed to set env: %v", err)
|
||||||
}
|
}
|
||||||
assert.False(t, isLoadAll("123456"))
|
cfg1 := map[string]interface{}{common.ReloadKey: "123456"}
|
||||||
assert.True(t, isLoadAll("654321"))
|
cfg2 := map[string]interface{}{common.ReloadKey: "654321"}
|
||||||
|
assert.False(t, isLoadAll(cfg1))
|
||||||
|
assert.True(t, isLoadAll(cfg2))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadFromEnvWithReloadConfigInvalidSkipPattern(t *testing.T) {
|
func TestLoadFromEnvWithReloadConfigInvalidSkipPattern(t *testing.T) {
|
||||||
@ -258,3 +260,31 @@ func TestValidLdapScope(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
func Test_AddMissingKey(t *testing.T) {
|
||||||
|
|
||||||
|
cfg := map[string]interface{}{
|
||||||
|
common.LDAPURL: "sampleurl",
|
||||||
|
common.EmailPort: 555,
|
||||||
|
common.LDAPVerifyCert: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
cfg map[string]interface{}
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
}{
|
||||||
|
{"Add default value", args{cfg}},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
AddMissedKey(tt.args.cfg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := cfg[common.LDAPBaseDN]; !ok {
|
||||||
|
t.Errorf("Can not found default value for %v", common.LDAPBaseDN)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -111,3 +111,87 @@ const (
|
|||||||
LdapGroupAdminDn = "ldap_group_admin_dn"
|
LdapGroupAdminDn = "ldap_group_admin_dn"
|
||||||
DefaultRegistryControllerEndpoint = "http://registryctl:8080"
|
DefaultRegistryControllerEndpoint = "http://registryctl:8080"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Shared variable, not allowed to modify
|
||||||
|
var (
|
||||||
|
// the keys of configurations which user can modify in PUT method and user can
|
||||||
|
// get in GET method
|
||||||
|
HarborValidKeys = []string{
|
||||||
|
AUTHMode,
|
||||||
|
SelfRegistration,
|
||||||
|
LDAPURL,
|
||||||
|
LDAPSearchDN,
|
||||||
|
LDAPSearchPwd,
|
||||||
|
LDAPBaseDN,
|
||||||
|
LDAPUID,
|
||||||
|
LDAPFilter,
|
||||||
|
LDAPScope,
|
||||||
|
LDAPTimeout,
|
||||||
|
LDAPVerifyCert,
|
||||||
|
LDAPGroupAttributeName,
|
||||||
|
LDAPGroupBaseDN,
|
||||||
|
LDAPGroupSearchFilter,
|
||||||
|
LDAPGroupSearchScope,
|
||||||
|
EmailHost,
|
||||||
|
EmailPort,
|
||||||
|
EmailUsername,
|
||||||
|
EmailPassword,
|
||||||
|
EmailFrom,
|
||||||
|
EmailSSL,
|
||||||
|
EmailIdentity,
|
||||||
|
EmailInsecure,
|
||||||
|
ProjectCreationRestriction,
|
||||||
|
TokenExpiration,
|
||||||
|
ScanAllPolicy,
|
||||||
|
UAAClientID,
|
||||||
|
UAAClientSecret,
|
||||||
|
UAAEndpoint,
|
||||||
|
UAAVerifyCert,
|
||||||
|
ReadOnly,
|
||||||
|
}
|
||||||
|
|
||||||
|
//value is default value
|
||||||
|
HarborStringKeysMap = map[string]string{
|
||||||
|
AUTHMode: "db_auth",
|
||||||
|
LDAPURL: "",
|
||||||
|
LDAPSearchDN: "",
|
||||||
|
LDAPSearchPwd: "",
|
||||||
|
LDAPBaseDN: "",
|
||||||
|
LDAPUID: "",
|
||||||
|
LDAPFilter: "",
|
||||||
|
LDAPGroupAttributeName: "",
|
||||||
|
LDAPGroupBaseDN: "",
|
||||||
|
LDAPGroupSearchFilter: "",
|
||||||
|
EmailHost: "smtp.mydomain.com",
|
||||||
|
EmailUsername: "sample_admin@mydomain.com",
|
||||||
|
EmailPassword: "abc",
|
||||||
|
EmailFrom: "admin <sample_admin@mydomain.com>",
|
||||||
|
EmailIdentity: "",
|
||||||
|
ProjectCreationRestriction: ProCrtRestrEveryone,
|
||||||
|
UAAClientID: "",
|
||||||
|
UAAEndpoint: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
HarborNumKeysMap = map[string]int{
|
||||||
|
EmailPort: 25,
|
||||||
|
LDAPScope: 2,
|
||||||
|
LDAPTimeout: 5,
|
||||||
|
LDAPGroupSearchScope: 2,
|
||||||
|
TokenExpiration: 30,
|
||||||
|
}
|
||||||
|
|
||||||
|
HarborBoolKeysMap = map[string]bool{
|
||||||
|
EmailSSL: false,
|
||||||
|
EmailInsecure: false,
|
||||||
|
SelfRegistration: true,
|
||||||
|
LDAPVerifyCert: true,
|
||||||
|
UAAVerifyCert: true,
|
||||||
|
ReadOnly: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
HarborPasswordKeys = []string{
|
||||||
|
EmailPassword,
|
||||||
|
LDAPSearchPwd,
|
||||||
|
UAAClientSecret,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -167,3 +167,35 @@ func ParseProjectIDOrName(value interface{}) (int64, string, error) {
|
|||||||
}
|
}
|
||||||
return id, name, nil
|
return id, name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//SafeCastString -- cast a object to string saftely
|
||||||
|
func SafeCastString(value interface{}) string {
|
||||||
|
if result, ok := value.(string); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
//SafeCastInt --
|
||||||
|
func SafeCastInt(value interface{}) int {
|
||||||
|
if result, ok := value.(int); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//SafeCastBool --
|
||||||
|
func SafeCastBool(value interface{}) bool {
|
||||||
|
if result, ok := value.(bool); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
//SafeCastFloat64 --
|
||||||
|
func SafeCastFloat64(value interface{}) float64 {
|
||||||
|
if result, ok := value.(float64); ok {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
@ -248,3 +248,91 @@ func TestConvertMapToStruct(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSafeCastString(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
value interface{}
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"nil value", args{nil}, ""},
|
||||||
|
{"normal string", args{"sample"}, "sample"},
|
||||||
|
{"wrong type", args{12}, ""},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := SafeCastString(tt.args.value); got != tt.want {
|
||||||
|
t.Errorf("SafeCastString() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSafeCastBool(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
value interface{}
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"nil value", args{nil}, false},
|
||||||
|
{"normal bool", args{true}, true},
|
||||||
|
{"wrong type", args{"true"}, false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := SafeCastBool(tt.args.value); got != tt.want {
|
||||||
|
t.Errorf("SafeCastBool() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSafeCastInt(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
value interface{}
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{"nil value", args{nil}, 0},
|
||||||
|
{"normal int", args{1234}, 1234},
|
||||||
|
{"wrong type", args{"sample"}, 0},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := SafeCastInt(tt.args.value); got != tt.want {
|
||||||
|
t.Errorf("SafeCastInt() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSafeCastFloat64(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
value interface{}
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want float64
|
||||||
|
}{
|
||||||
|
{"nil value", args{nil}, 0},
|
||||||
|
{"normal float64", args{12.34}, 12.34},
|
||||||
|
{"wrong type", args{false}, 0},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := SafeCastFloat64(tt.args.value); got != tt.want {
|
||||||
|
t.Errorf("SafeCastFloat64() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -26,88 +26,6 @@ import (
|
|||||||
"github.com/vmware/harbor/src/ui/config"
|
"github.com/vmware/harbor/src/ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// the keys of configurations which user can modify in PUT method and user can
|
|
||||||
// get in GET method
|
|
||||||
validKeys = []string{
|
|
||||||
common.AUTHMode,
|
|
||||||
common.SelfRegistration,
|
|
||||||
common.LDAPURL,
|
|
||||||
common.LDAPSearchDN,
|
|
||||||
common.LDAPSearchPwd,
|
|
||||||
common.LDAPBaseDN,
|
|
||||||
common.LDAPUID,
|
|
||||||
common.LDAPFilter,
|
|
||||||
common.LDAPScope,
|
|
||||||
common.LDAPTimeout,
|
|
||||||
common.LDAPVerifyCert,
|
|
||||||
common.LDAPGroupAttributeName,
|
|
||||||
common.LDAPGroupBaseDN,
|
|
||||||
common.LDAPGroupSearchFilter,
|
|
||||||
common.LDAPGroupSearchScope,
|
|
||||||
common.EmailHost,
|
|
||||||
common.EmailPort,
|
|
||||||
common.EmailUsername,
|
|
||||||
common.EmailPassword,
|
|
||||||
common.EmailFrom,
|
|
||||||
common.EmailSSL,
|
|
||||||
common.EmailIdentity,
|
|
||||||
common.EmailInsecure,
|
|
||||||
common.ProjectCreationRestriction,
|
|
||||||
common.TokenExpiration,
|
|
||||||
common.ScanAllPolicy,
|
|
||||||
common.UAAClientID,
|
|
||||||
common.UAAClientSecret,
|
|
||||||
common.UAAEndpoint,
|
|
||||||
common.UAAVerifyCert,
|
|
||||||
common.ReadOnly,
|
|
||||||
}
|
|
||||||
|
|
||||||
stringKeys = []string{
|
|
||||||
common.AUTHMode,
|
|
||||||
common.LDAPURL,
|
|
||||||
common.LDAPSearchDN,
|
|
||||||
common.LDAPSearchPwd,
|
|
||||||
common.LDAPBaseDN,
|
|
||||||
common.LDAPUID,
|
|
||||||
common.LDAPFilter,
|
|
||||||
common.LDAPGroupAttributeName,
|
|
||||||
common.LDAPGroupBaseDN,
|
|
||||||
common.LDAPGroupSearchFilter,
|
|
||||||
common.EmailHost,
|
|
||||||
common.EmailUsername,
|
|
||||||
common.EmailPassword,
|
|
||||||
common.EmailFrom,
|
|
||||||
common.EmailIdentity,
|
|
||||||
common.ProjectCreationRestriction,
|
|
||||||
common.UAAClientID,
|
|
||||||
common.UAAEndpoint,
|
|
||||||
}
|
|
||||||
|
|
||||||
numKeys = []string{
|
|
||||||
common.EmailPort,
|
|
||||||
common.LDAPScope,
|
|
||||||
common.LDAPTimeout,
|
|
||||||
common.LDAPGroupSearchScope,
|
|
||||||
common.TokenExpiration,
|
|
||||||
}
|
|
||||||
|
|
||||||
boolKeys = []string{
|
|
||||||
common.EmailSSL,
|
|
||||||
common.EmailInsecure,
|
|
||||||
common.SelfRegistration,
|
|
||||||
common.LDAPVerifyCert,
|
|
||||||
common.UAAVerifyCert,
|
|
||||||
common.ReadOnly,
|
|
||||||
}
|
|
||||||
|
|
||||||
passwordKeys = []string{
|
|
||||||
common.EmailPassword,
|
|
||||||
common.LDAPSearchPwd,
|
|
||||||
common.UAAClientSecret,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConfigAPI ...
|
// ConfigAPI ...
|
||||||
type ConfigAPI struct {
|
type ConfigAPI struct {
|
||||||
BaseController
|
BaseController
|
||||||
@ -140,7 +58,7 @@ func (c *ConfigAPI) Get() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cfgs := map[string]interface{}{}
|
cfgs := map[string]interface{}{}
|
||||||
for _, k := range validKeys {
|
for _, k := range common.HarborValidKeys {
|
||||||
if v, ok := configs[k]; ok {
|
if v, ok := configs[k]; ok {
|
||||||
cfgs[k] = v
|
cfgs[k] = v
|
||||||
}
|
}
|
||||||
@ -162,7 +80,7 @@ func (c *ConfigAPI) Put() {
|
|||||||
c.DecodeJSONReq(&m)
|
c.DecodeJSONReq(&m)
|
||||||
|
|
||||||
cfg := map[string]interface{}{}
|
cfg := map[string]interface{}{}
|
||||||
for _, k := range validKeys {
|
for _, k := range common.HarborValidKeys {
|
||||||
if v, ok := m[k]; ok {
|
if v, ok := m[k]; ok {
|
||||||
cfg[k] = v
|
cfg[k] = v
|
||||||
}
|
}
|
||||||
@ -205,7 +123,7 @@ func (c *ConfigAPI) Reset() {
|
|||||||
|
|
||||||
func validateCfg(c map[string]interface{}) (bool, error) {
|
func validateCfg(c map[string]interface{}) (bool, error) {
|
||||||
strMap := map[string]string{}
|
strMap := map[string]string{}
|
||||||
for _, k := range stringKeys {
|
for k := range common.HarborStringKeysMap {
|
||||||
if _, ok := c[k]; !ok {
|
if _, ok := c[k]; !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -215,7 +133,7 @@ func validateCfg(c map[string]interface{}) (bool, error) {
|
|||||||
strMap[k] = c[k].(string)
|
strMap[k] = c[k].(string)
|
||||||
}
|
}
|
||||||
numMap := map[string]int{}
|
numMap := map[string]int{}
|
||||||
for _, k := range numKeys {
|
for k := range common.HarborNumKeysMap {
|
||||||
if _, ok := c[k]; !ok {
|
if _, ok := c[k]; !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -225,7 +143,7 @@ func validateCfg(c map[string]interface{}) (bool, error) {
|
|||||||
numMap[k] = int(c[k].(float64))
|
numMap[k] = int(c[k].(float64))
|
||||||
}
|
}
|
||||||
boolMap := map[string]bool{}
|
boolMap := map[string]bool{}
|
||||||
for _, k := range boolKeys {
|
for k := range common.HarborBoolKeysMap {
|
||||||
if _, ok := c[k]; !ok {
|
if _, ok := c[k]; !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -327,7 +245,7 @@ func validateCfg(c map[string]interface{}) (bool, error) {
|
|||||||
func convertForGet(cfg map[string]interface{}) (map[string]*value, error) {
|
func convertForGet(cfg map[string]interface{}) (map[string]*value, error) {
|
||||||
result := map[string]*value{}
|
result := map[string]*value{}
|
||||||
|
|
||||||
for _, k := range passwordKeys {
|
for _, k := range common.HarborPasswordKeys {
|
||||||
if _, ok := cfg[k]; ok {
|
if _, ok := cfg[k]; ok {
|
||||||
delete(cfg, k)
|
delete(cfg, k)
|
||||||
}
|
}
|
||||||
|
@ -167,17 +167,17 @@ func (sia *SystemInfoAPI) GetGeneralInfo() {
|
|||||||
_, caStatErr := os.Stat(defaultRootCert)
|
_, caStatErr := os.Stat(defaultRootCert)
|
||||||
harborVersion := sia.getVersion()
|
harborVersion := sia.getVersion()
|
||||||
info := GeneralInfo{
|
info := GeneralInfo{
|
||||||
AdmiralEndpoint: cfg[common.AdmiralEndpoint].(string),
|
AdmiralEndpoint: utils.SafeCastString(cfg[common.AdmiralEndpoint]),
|
||||||
WithAdmiral: config.WithAdmiral(),
|
WithAdmiral: config.WithAdmiral(),
|
||||||
WithNotary: config.WithNotary(),
|
WithNotary: config.WithNotary(),
|
||||||
WithClair: config.WithClair(),
|
WithClair: config.WithClair(),
|
||||||
AuthMode: cfg[common.AUTHMode].(string),
|
AuthMode: utils.SafeCastString(cfg[common.AUTHMode]),
|
||||||
ProjectCreationRestrict: cfg[common.ProjectCreationRestriction].(string),
|
ProjectCreationRestrict: utils.SafeCastString(cfg[common.ProjectCreationRestriction]),
|
||||||
SelfRegistration: cfg[common.SelfRegistration].(bool),
|
SelfRegistration: utils.SafeCastBool(cfg[common.SelfRegistration]),
|
||||||
RegistryURL: registryURL,
|
RegistryURL: registryURL,
|
||||||
HasCARoot: caStatErr == nil,
|
HasCARoot: caStatErr == nil,
|
||||||
HarborVersion: harborVersion,
|
HarborVersion: harborVersion,
|
||||||
RegistryStorageProviderName: cfg[common.RegistryStorageProviderName].(string),
|
RegistryStorageProviderName: utils.SafeCastString(cfg[common.RegistryStorageProviderName]),
|
||||||
ReadOnly: config.ReadOnly(),
|
ReadOnly: config.ReadOnly(),
|
||||||
}
|
}
|
||||||
if info.WithClair {
|
if info.WithClair {
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
comcfg "github.com/vmware/harbor/src/common/config"
|
comcfg "github.com/vmware/harbor/src/common/config"
|
||||||
"github.com/vmware/harbor/src/common/models"
|
"github.com/vmware/harbor/src/common/models"
|
||||||
"github.com/vmware/harbor/src/common/secret"
|
"github.com/vmware/harbor/src/common/secret"
|
||||||
|
"github.com/vmware/harbor/src/common/utils"
|
||||||
"github.com/vmware/harbor/src/common/utils/log"
|
"github.com/vmware/harbor/src/common/utils/log"
|
||||||
"github.com/vmware/harbor/src/ui/promgr"
|
"github.com/vmware/harbor/src/ui/promgr"
|
||||||
"github.com/vmware/harbor/src/ui/promgr/pmsdriver"
|
"github.com/vmware/harbor/src/ui/promgr/pmsdriver"
|
||||||
@ -187,7 +188,7 @@ func AuthMode() (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return cfg[common.AUTHMode].(string), nil
|
return utils.SafeCastString(cfg[common.AUTHMode]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenPrivateKeyPath returns the path to the key for signing token for registry
|
// TokenPrivateKeyPath returns the path to the key for signing token for registry
|
||||||
@ -206,14 +207,14 @@ func LDAPConf() (*models.LdapConf, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ldapConf := &models.LdapConf{}
|
ldapConf := &models.LdapConf{}
|
||||||
ldapConf.LdapURL = cfg[common.LDAPURL].(string)
|
ldapConf.LdapURL = utils.SafeCastString(cfg[common.LDAPURL])
|
||||||
ldapConf.LdapSearchDn = cfg[common.LDAPSearchDN].(string)
|
ldapConf.LdapSearchDn = utils.SafeCastString(cfg[common.LDAPSearchDN])
|
||||||
ldapConf.LdapSearchPassword = cfg[common.LDAPSearchPwd].(string)
|
ldapConf.LdapSearchPassword = utils.SafeCastString(cfg[common.LDAPSearchPwd])
|
||||||
ldapConf.LdapBaseDn = cfg[common.LDAPBaseDN].(string)
|
ldapConf.LdapBaseDn = utils.SafeCastString(cfg[common.LDAPBaseDN])
|
||||||
ldapConf.LdapUID = cfg[common.LDAPUID].(string)
|
ldapConf.LdapUID = utils.SafeCastString(cfg[common.LDAPUID])
|
||||||
ldapConf.LdapFilter = cfg[common.LDAPFilter].(string)
|
ldapConf.LdapFilter = utils.SafeCastString(cfg[common.LDAPFilter])
|
||||||
ldapConf.LdapScope = int(cfg[common.LDAPScope].(float64))
|
ldapConf.LdapScope = int(utils.SafeCastFloat64(cfg[common.LDAPScope]))
|
||||||
ldapConf.LdapConnectionTimeout = int(cfg[common.LDAPTimeout].(float64))
|
ldapConf.LdapConnectionTimeout = int(utils.SafeCastFloat64(cfg[common.LDAPTimeout]))
|
||||||
if cfg[common.LDAPVerifyCert] != nil {
|
if cfg[common.LDAPVerifyCert] != nil {
|
||||||
ldapConf.LdapVerifyCert = cfg[common.LDAPVerifyCert].(bool)
|
ldapConf.LdapVerifyCert = cfg[common.LDAPVerifyCert].(bool)
|
||||||
} else {
|
} else {
|
||||||
@ -233,13 +234,13 @@ func LDAPGroupConf() (*models.LdapGroupConf, error) {
|
|||||||
|
|
||||||
ldapGroupConf := &models.LdapGroupConf{LdapGroupSearchScope: 2}
|
ldapGroupConf := &models.LdapGroupConf{LdapGroupSearchScope: 2}
|
||||||
if _, ok := cfg[common.LDAPGroupBaseDN]; ok {
|
if _, ok := cfg[common.LDAPGroupBaseDN]; ok {
|
||||||
ldapGroupConf.LdapGroupBaseDN = cfg[common.LDAPGroupBaseDN].(string)
|
ldapGroupConf.LdapGroupBaseDN = utils.SafeCastString(cfg[common.LDAPGroupBaseDN])
|
||||||
}
|
}
|
||||||
if _, ok := cfg[common.LDAPGroupSearchFilter]; ok {
|
if _, ok := cfg[common.LDAPGroupSearchFilter]; ok {
|
||||||
ldapGroupConf.LdapGroupFilter = cfg[common.LDAPGroupSearchFilter].(string)
|
ldapGroupConf.LdapGroupFilter = utils.SafeCastString(cfg[common.LDAPGroupSearchFilter])
|
||||||
}
|
}
|
||||||
if _, ok := cfg[common.LDAPGroupAttributeName]; ok {
|
if _, ok := cfg[common.LDAPGroupAttributeName]; ok {
|
||||||
ldapGroupConf.LdapGroupNameAttribute = cfg[common.LDAPGroupAttributeName].(string)
|
ldapGroupConf.LdapGroupNameAttribute = utils.SafeCastString(cfg[common.LDAPGroupAttributeName])
|
||||||
}
|
}
|
||||||
if _, ok := cfg[common.LDAPGroupSearchScope]; ok {
|
if _, ok := cfg[common.LDAPGroupSearchScope]; ok {
|
||||||
if scopeStr, ok := cfg[common.LDAPGroupSearchScope].(string); ok {
|
if scopeStr, ok := cfg[common.LDAPGroupSearchScope].(string); ok {
|
||||||
@ -262,7 +263,7 @@ func TokenExpiration() (int, error) {
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return int(cfg[common.TokenExpiration].(float64)), nil
|
return int(utils.SafeCastFloat64(cfg[common.TokenExpiration])), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtEndpoint returns the external URL of Harbor: protocol://host:port
|
// ExtEndpoint returns the external URL of Harbor: protocol://host:port
|
||||||
@ -271,7 +272,7 @@ func ExtEndpoint() (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return cfg[common.ExtEndpoint].(string), nil
|
return utils.SafeCastString(cfg[common.ExtEndpoint]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtURL returns the external URL: host:port
|
// ExtURL returns the external URL: host:port
|
||||||
@ -298,7 +299,7 @@ func SelfRegistration() (bool, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return cfg[common.SelfRegistration].(bool), nil
|
return utils.SafeCastBool(cfg[common.SelfRegistration]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegistryURL ...
|
// RegistryURL ...
|
||||||
@ -307,7 +308,7 @@ func RegistryURL() (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return cfg[common.RegistryURL].(string), nil
|
return utils.SafeCastString(cfg[common.RegistryURL]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InternalJobServiceURL returns jobservice URL for internal communication between Harbor containers
|
// InternalJobServiceURL returns jobservice URL for internal communication between Harbor containers
|
||||||
@ -321,7 +322,7 @@ func InternalJobServiceURL() string {
|
|||||||
if cfg[common.JobServiceURL] == nil {
|
if cfg[common.JobServiceURL] == nil {
|
||||||
return common.DefaultJobserviceEndpoint
|
return common.DefaultJobserviceEndpoint
|
||||||
}
|
}
|
||||||
return strings.TrimSuffix(cfg[common.JobServiceURL].(string), "/")
|
return strings.TrimSuffix(utils.SafeCastString(cfg[common.JobServiceURL]), "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
// InternalUIURL returns the local ui url
|
// InternalUIURL returns the local ui url
|
||||||
@ -331,7 +332,7 @@ func InternalUIURL() string {
|
|||||||
log.Warningf("Failed to Get job service UI URL from backend, error: %v, will return default value.")
|
log.Warningf("Failed to Get job service UI URL from backend, error: %v, will return default value.")
|
||||||
return common.DefaultUIEndpoint
|
return common.DefaultUIEndpoint
|
||||||
}
|
}
|
||||||
return strings.TrimSuffix(cfg[common.UIURL].(string), "/")
|
return strings.TrimSuffix(utils.SafeCastString(cfg[common.UIURL]), "/")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,7 +352,7 @@ func InternalNotaryEndpoint() string {
|
|||||||
if cfg[common.NotaryURL] == nil {
|
if cfg[common.NotaryURL] == nil {
|
||||||
return common.DefaultNotaryEndpoint
|
return common.DefaultNotaryEndpoint
|
||||||
}
|
}
|
||||||
return cfg[common.NotaryURL].(string)
|
return utils.SafeCastString(cfg[common.NotaryURL])
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitialAdminPassword returns the initial password for administrator
|
// InitialAdminPassword returns the initial password for administrator
|
||||||
@ -360,7 +361,7 @@ func InitialAdminPassword() (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return cfg[common.AdminInitialPassword].(string), nil
|
return utils.SafeCastString(cfg[common.AdminInitialPassword]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnlyAdminCreateProject returns the flag to restrict that only sys admin can create project
|
// OnlyAdminCreateProject returns the flag to restrict that only sys admin can create project
|
||||||
@ -369,7 +370,7 @@ func OnlyAdminCreateProject() (bool, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
return cfg[common.ProjectCreationRestriction].(string) == common.ProCrtRestrAdmOnly, nil
|
return utils.SafeCastString(cfg[common.ProjectCreationRestriction]) == common.ProCrtRestrAdmOnly, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Email returns email server settings
|
// Email returns email server settings
|
||||||
@ -380,14 +381,14 @@ func Email() (*models.Email, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
email := &models.Email{}
|
email := &models.Email{}
|
||||||
email.Host = cfg[common.EmailHost].(string)
|
email.Host = utils.SafeCastString(cfg[common.EmailHost])
|
||||||
email.Port = int(cfg[common.EmailPort].(float64))
|
email.Port = int(utils.SafeCastFloat64(cfg[common.EmailPort]))
|
||||||
email.Username = cfg[common.EmailUsername].(string)
|
email.Username = utils.SafeCastString(cfg[common.EmailUsername])
|
||||||
email.Password = cfg[common.EmailPassword].(string)
|
email.Password = utils.SafeCastString(cfg[common.EmailPassword])
|
||||||
email.SSL = cfg[common.EmailSSL].(bool)
|
email.SSL = utils.SafeCastBool(cfg[common.EmailSSL])
|
||||||
email.From = cfg[common.EmailFrom].(string)
|
email.From = utils.SafeCastString(cfg[common.EmailFrom])
|
||||||
email.Identity = cfg[common.EmailIdentity].(string)
|
email.Identity = utils.SafeCastString(cfg[common.EmailIdentity])
|
||||||
email.Insecure = cfg[common.EmailInsecure].(bool)
|
email.Insecure = utils.SafeCastBool(cfg[common.EmailInsecure])
|
||||||
|
|
||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
@ -403,11 +404,11 @@ func Database() (*models.Database, error) {
|
|||||||
database.Type = cfg[common.DatabaseType].(string)
|
database.Type = cfg[common.DatabaseType].(string)
|
||||||
|
|
||||||
postgresql := &models.PostGreSQL{}
|
postgresql := &models.PostGreSQL{}
|
||||||
postgresql.Host = cfg[common.PostGreSQLHOST].(string)
|
postgresql.Host = utils.SafeCastString(cfg[common.PostGreSQLHOST])
|
||||||
postgresql.Port = int(cfg[common.PostGreSQLPort].(float64))
|
postgresql.Port = int(utils.SafeCastFloat64(cfg[common.PostGreSQLPort]))
|
||||||
postgresql.Username = cfg[common.PostGreSQLUsername].(string)
|
postgresql.Username = utils.SafeCastString(cfg[common.PostGreSQLUsername])
|
||||||
postgresql.Password = cfg[common.PostGreSQLPassword].(string)
|
postgresql.Password = utils.SafeCastString(cfg[common.PostGreSQLPassword])
|
||||||
postgresql.Database = cfg[common.PostGreSQLDatabase].(string)
|
postgresql.Database = utils.SafeCastString(cfg[common.PostGreSQLDatabase])
|
||||||
database.PostGreSQL = postgresql
|
database.PostGreSQL = postgresql
|
||||||
|
|
||||||
return database, nil
|
return database, nil
|
||||||
@ -433,7 +434,7 @@ func WithNotary() bool {
|
|||||||
log.Warningf("Failed to get configuration, will return WithNotary == false")
|
log.Warningf("Failed to get configuration, will return WithNotary == false")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return cfg[common.WithNotary].(bool)
|
return utils.SafeCastBool(cfg[common.WithNotary])
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithClair returns a bool value to indicate if Harbor's deployed with Clair
|
// WithClair returns a bool value to indicate if Harbor's deployed with Clair
|
||||||
@ -443,7 +444,7 @@ func WithClair() bool {
|
|||||||
log.Errorf("Failed to get configuration, will return WithClair == false")
|
log.Errorf("Failed to get configuration, will return WithClair == false")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return cfg[common.WithClair].(bool)
|
return utils.SafeCastBool(cfg[common.WithClair])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClairEndpoint returns the end point of clair instance, by default it's the one deployed within Harbor.
|
// ClairEndpoint returns the end point of clair instance, by default it's the one deployed within Harbor.
|
||||||
@ -453,7 +454,7 @@ func ClairEndpoint() string {
|
|||||||
log.Errorf("Failed to get configuration, use default clair endpoint")
|
log.Errorf("Failed to get configuration, use default clair endpoint")
|
||||||
return common.DefaultClairEndpoint
|
return common.DefaultClairEndpoint
|
||||||
}
|
}
|
||||||
return cfg[common.ClairURL].(string)
|
return utils.SafeCastString(cfg[common.ClairURL])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClairDB return Clair db info
|
// ClairDB return Clair db info
|
||||||
@ -464,11 +465,11 @@ func ClairDB() (*models.PostGreSQL, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
clairDB := &models.PostGreSQL{}
|
clairDB := &models.PostGreSQL{}
|
||||||
clairDB.Host = cfg[common.ClairDBHost].(string)
|
clairDB.Host = utils.SafeCastString(cfg[common.ClairDBHost])
|
||||||
clairDB.Port = int(cfg[common.ClairDBPort].(float64))
|
clairDB.Port = int(utils.SafeCastFloat64(cfg[common.ClairDBPort]))
|
||||||
clairDB.Username = cfg[common.ClairDBUsername].(string)
|
clairDB.Username = utils.SafeCastString(cfg[common.ClairDBUsername])
|
||||||
clairDB.Password = cfg[common.ClairDBPassword].(string)
|
clairDB.Password = utils.SafeCastString(cfg[common.ClairDBPassword])
|
||||||
clairDB.Database = cfg[common.ClairDB].(string)
|
clairDB.Database = utils.SafeCastString(cfg[common.ClairDB])
|
||||||
return clairDB, nil
|
return clairDB, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,7 +484,7 @@ func AdmiralEndpoint() string {
|
|||||||
if e, ok := cfg[common.AdmiralEndpoint].(string); !ok || e == "NA" {
|
if e, ok := cfg[common.AdmiralEndpoint].(string); !ok || e == "NA" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return cfg[common.AdmiralEndpoint].(string)
|
return utils.SafeCastString(cfg[common.AdmiralEndpoint])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScanAllPolicy returns the policy which controls the scan all.
|
// ScanAllPolicy returns the policy which controls the scan all.
|
||||||
@ -522,10 +523,10 @@ func UAASettings() (*models.UAASettings, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
us := &models.UAASettings{
|
us := &models.UAASettings{
|
||||||
Endpoint: cfg[common.UAAEndpoint].(string),
|
Endpoint: utils.SafeCastString(cfg[common.UAAEndpoint]),
|
||||||
ClientID: cfg[common.UAAClientID].(string),
|
ClientID: utils.SafeCastString(cfg[common.UAAClientID]),
|
||||||
ClientSecret: cfg[common.UAAClientSecret].(string),
|
ClientSecret: utils.SafeCastString(cfg[common.UAAClientSecret]),
|
||||||
VerifyCert: cfg[common.UAAVerifyCert].(bool),
|
VerifyCert: utils.SafeCastBool(cfg[common.UAAVerifyCert]),
|
||||||
}
|
}
|
||||||
return us, nil
|
return us, nil
|
||||||
}
|
}
|
||||||
@ -537,5 +538,5 @@ func ReadOnly() bool {
|
|||||||
log.Errorf("Failed to get configuration, will return false as read only, error: %v", err)
|
log.Errorf("Failed to get configuration, will return false as read only, error: %v", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return cfg[common.ReadOnly].(bool)
|
return utils.SafeCastBool(cfg[common.ReadOnly])
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user