mirror of
https://gitlab.com/psuapp/psu.git
synced 2024-08-30 18:12:34 +00:00
Rename configs to settings
This commit is contained in:
parent
796c16166e
commit
8d0bd7bc41
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -18,7 +18,7 @@ PSU_KEY=value
|
||||
...
|
||||
```
|
||||
|
||||
With configurations (if applicable):
|
||||
With settings (if applicable):
|
||||
|
||||
```yaml
|
||||
key: value
|
||||
|
@ -12,9 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- A Custom User-Agent header is sent on requests to the Portainer server to identify the client.
|
||||
- Supported platforms and architectures linux 32/64 bit, darwin 32/64 bit, windows 32/64 bit, and arm7 32/64 bit.
|
||||
- `completion` command to print Bash completion script.
|
||||
- `config set` command to set configuration options.
|
||||
- `config get` command to get configuration options.
|
||||
- `config list|ls` command to print configuration options.
|
||||
- `setting set` command to set configuration options.
|
||||
- `setting get` command to get configuration options.
|
||||
- `setting list|ls` command to print configuration options.
|
||||
- `--format` flag to select output format from "table", "json" or a custom Go template. Defaults to "table".
|
||||
- `container access` command to set access control for containers.
|
||||
- `--admins` flag to limit access to administrators.
|
||||
@ -70,7 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- `--public` flag to give access to all users.
|
||||
- `-h, --help` flags on each command to print its help.
|
||||
- `-A, --auth-token` global flag to set Portainer auth token.
|
||||
- `--config` global flag to set the path to a configuration file. Supported file formats are JSON, TOML, YAML, HCL, envfile and Java properties config files. Defaults to "$HOME/.psu.yaml".
|
||||
- `--settings` global flag to set the path to a configuration file. Supported file formats are JSON, TOML, YAML, HCL, envfile and Java properties config files. Defaults to "$HOME/.psu.yaml".
|
||||
- `-h, --help` global flag to print global help.
|
||||
- `--log-format` global flag to set log format from "text" and "json". Defaults to "text".
|
||||
- `-v, --log-level` global flag to set log level from "panic", "faltal", "error", "warning", "info", "debug" and "trace". Defaults to "info".
|
||||
|
@ -1,8 +1,6 @@
|
||||
FROM alpine:3.10
|
||||
|
||||
ENV PSU_AUTH_TOKEN=""
|
||||
ENV PSU_CONFIG=""
|
||||
ENV PSU_CONFIG_LIST_FORMAT=""
|
||||
ENV PSU_CONTAINER_ACCESS_ADMINS=""
|
||||
ENV PSU_CONTAINER_ACCESS_ENDPOINT=""
|
||||
ENV PSU_CONTAINER_ACCESS_PRIVATE=""
|
||||
@ -30,6 +28,8 @@ ENV PSU_SERVICE_ACCESS_ADMINS=""
|
||||
ENV PSU_SERVICE_ACCESS_ENDPOINT=""
|
||||
ENV PSU_SERVICE_ACCESS_PRIVATE=""
|
||||
ENV PSU_SERVICE_ACCESS_PUBLIC=""
|
||||
ENV PSU_SETTING_LIST_FORMAT=""
|
||||
ENV PSU_SETTINGS_FILE=""
|
||||
ENV PSU_STACK_ACCESS_ADMINS=""
|
||||
ENV PSU_STACK_ACCESS_ENDPOINT=""
|
||||
ENV PSU_STACK_ACCESS_PRIVATE=""
|
||||
|
@ -1,15 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// configCmd represents the config command
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Manage configs",
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(configCmd)
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/greenled/portainer-stack-utils/common"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// configGetCmd represents the config get command
|
||||
var configGetCmd = &cobra.Command{
|
||||
Use: "get <key>",
|
||||
Short: "Get config",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
keyExists := common.CheckConfigKeyExists(args[0])
|
||||
if !keyExists {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"key": args[0],
|
||||
"suggestions": "try looking up the available configuration keys: psu config ls",
|
||||
}).Fatal("unknown configuration key")
|
||||
}
|
||||
|
||||
// Get config
|
||||
value, configGettingErr := getConfig(args[0])
|
||||
common.CheckError(configGettingErr)
|
||||
fmt.Println(value)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
configCmd.AddCommand(configGetCmd)
|
||||
}
|
||||
|
||||
func getConfig(key string) (value interface{}, err error) {
|
||||
newViper, err := common.LoadCofig()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
value = newViper.Get(key)
|
||||
|
||||
return
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/greenled/portainer-stack-utils/common"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// configSetCmd represents the config set command
|
||||
var configSetCmd = &cobra.Command{
|
||||
Use: "set <key> <value>",
|
||||
Short: "Set config",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
keyExists := common.CheckConfigKeyExists(args[0])
|
||||
if !keyExists {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"key": args[0],
|
||||
"suggestions": "try looking up the available configuration keys: psu config ls",
|
||||
}).Fatal("unknown configuration key")
|
||||
}
|
||||
|
||||
// Set config
|
||||
configSettingErr := setConfig(args[0], args[1])
|
||||
common.CheckError(configSettingErr)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
configCmd.AddCommand(configSetCmd)
|
||||
}
|
||||
|
||||
func setConfig(key string, value string) (err error) {
|
||||
newViper, err := common.LoadCofig()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
newViper.Set(key, value)
|
||||
|
||||
// Make sure the config file exists
|
||||
_, err = os.Create(newViper.ConfigFileUsed())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Write te config file
|
||||
err = newViper.WriteConfig()
|
||||
|
||||
return
|
||||
}
|
@ -35,8 +35,8 @@ var loginCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
// Save auth token
|
||||
configSettingErr := setConfig("auth-token", authToken)
|
||||
common.CheckError(configSettingErr)
|
||||
settingErr := setSetting("auth-token", authToken)
|
||||
common.CheckError(settingErr)
|
||||
},
|
||||
}
|
||||
|
||||
|
22
cmd/root.go
22
cmd/root.go
@ -14,7 +14,7 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var cfgFile string
|
||||
var settingsFile string
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
@ -33,12 +33,12 @@ func Execute() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig, initLogger)
|
||||
cobra.OnInitialize(initSettings, initLogger)
|
||||
|
||||
rootCmd.SetVersionTemplate("{{ version }}\n")
|
||||
cobra.AddTemplateFunc("version", version.BuildVersionString)
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "Config file. (default \"$HOME/.psu.yaml)\"")
|
||||
rootCmd.PersistentFlags().StringVar(&settingsFile, "settings-file", "", "Settings file. (default \"$HOME/.psu.yaml)\"")
|
||||
rootCmd.PersistentFlags().StringP("log-level", "v", "info", "Log level. One of trace, debug, info, warning, error, fatal or panic.")
|
||||
rootCmd.PersistentFlags().String("log-format", "text", "Log format. One of text or json.")
|
||||
rootCmd.PersistentFlags().BoolP("insecure", "i", false, "Skip Portainer SSL certificate verification.")
|
||||
@ -47,7 +47,7 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringP("password", "p", "", "Portainer password.")
|
||||
rootCmd.PersistentFlags().StringP("auth-token", "A", "", "Portainer auth token.")
|
||||
rootCmd.PersistentFlags().DurationP("timeout", "t", 0, "Waiting time before aborting (like 100ms, 30s, 1h20m).")
|
||||
viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
|
||||
viper.BindPFlag("settings-file", rootCmd.PersistentFlags().Lookup("settings-file"))
|
||||
viper.BindPFlag("log-level", rootCmd.PersistentFlags().Lookup("log-level"))
|
||||
viper.BindPFlag("log-format", rootCmd.PersistentFlags().Lookup("log-format"))
|
||||
viper.BindPFlag("insecure", rootCmd.PersistentFlags().Lookup("insecure"))
|
||||
@ -58,11 +58,11 @@ func init() {
|
||||
viper.BindPFlag("auth-token", rootCmd.PersistentFlags().Lookup("auth-token"))
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if cfgFile != "" {
|
||||
// Use config file from the flag.
|
||||
viper.SetConfigFile(cfgFile)
|
||||
// initSettings reads in setting file and ENV variables if set.
|
||||
func initSettings() {
|
||||
if settingsFile != "" {
|
||||
// Use setting file from the flag.
|
||||
viper.SetConfigFile(settingsFile)
|
||||
} else {
|
||||
// Find home directory.
|
||||
home, err := homedir.Dir()
|
||||
@ -71,7 +71,7 @@ func initConfig() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Search config in home directory with name ".psu" (without extension).
|
||||
// Search settings file in home directory with name ".psu" (without extension).
|
||||
viper.AddConfigPath(home)
|
||||
viper.SetConfigName(".psu")
|
||||
}
|
||||
@ -82,7 +82,7 @@ func initConfig() {
|
||||
replacer := strings.NewReplacer("-", "_", ".", "_")
|
||||
viper.SetEnvKeyReplacer(replacer)
|
||||
|
||||
// If a config file is found, read it in.
|
||||
// If a setting file is found, read it in.
|
||||
viper.ReadInConfig()
|
||||
}
|
||||
|
||||
|
15
cmd/setting.go
Normal file
15
cmd/setting.go
Normal file
@ -0,0 +1,15 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// settingCmd represents the config command
|
||||
var settingCmd = &cobra.Command{
|
||||
Use: "setting",
|
||||
Short: "Manage settings",
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(settingCmd)
|
||||
}
|
44
cmd/settingGet.go
Normal file
44
cmd/settingGet.go
Normal file
@ -0,0 +1,44 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/greenled/portainer-stack-utils/common"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// settingGetCmd represents the setting get command
|
||||
var settingGetCmd = &cobra.Command{
|
||||
Use: "get <key>",
|
||||
Short: "Get setting",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
keyExists := common.CheckSettingKeyExists(args[0])
|
||||
if !keyExists {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"key": args[0],
|
||||
"suggestions": "try looking up the available setting keys: psu setting ls",
|
||||
}).Fatal("unknown setting key")
|
||||
}
|
||||
|
||||
// Get setting
|
||||
value, err := getSetting(args[0])
|
||||
common.CheckError(err)
|
||||
fmt.Println(value)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
settingCmd.AddCommand(settingGetCmd)
|
||||
}
|
||||
|
||||
func getSetting(key string) (value interface{}, err error) {
|
||||
newViper, err := common.LoadSettings()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
value = newViper.Get(key)
|
||||
|
||||
return
|
||||
}
|
@ -15,56 +15,56 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// configListCmd represents the list command
|
||||
var configListCmd = &cobra.Command{
|
||||
// settingListCmd represents the setting list command
|
||||
var settingListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List configs",
|
||||
Short: "List settings",
|
||||
Aliases: []string{"ls"},
|
||||
Example: ` Print configs in a table format:
|
||||
psu config ls
|
||||
Example: ` Print settings in a table format:
|
||||
psu setting ls
|
||||
|
||||
Print available config keys:
|
||||
psu config ls --format "{{ .Key }}"
|
||||
Print available setting keys:
|
||||
psu setting ls --format "{{ .Key }}"
|
||||
|
||||
Print configs in a yaml|properties format:
|
||||
psu config ls --format "{{ .Key }}:{{ if .CurrentValue }} {{ .CurrentValue }}{{ end }}"
|
||||
Print settings in a yaml|properties format:
|
||||
psu setting ls --format "{{ .Key }}:{{ if .CurrentValue }} {{ .CurrentValue }}{{ end }}"
|
||||
|
||||
Print available environment variables:
|
||||
psu config ls --format "{{ .EnvironmentVariable }}"
|
||||
psu setting ls --format "{{ .EnvironmentVariable }}"
|
||||
|
||||
Print configs in a dotenv format:
|
||||
psu config ls --format "{{ .EnvironmentVariable }}={{ if .CurrentValue }}{{ .CurrentValue }}{{ end }}"`,
|
||||
Print settings in a dotenv format:
|
||||
psu setting ls --format "{{ .EnvironmentVariable }}={{ if .CurrentValue }}{{ .CurrentValue }}{{ end }}"`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Get alphabetically ordered list of config keys
|
||||
// Get alphabetically ordered list of setting keys
|
||||
keys := viper.AllKeys()
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
return keys[i] < keys[j]
|
||||
})
|
||||
|
||||
// Create config objects
|
||||
var configs []config
|
||||
// Create setting objects
|
||||
var settings []setting
|
||||
for _, key := range keys {
|
||||
envvar := strings.Replace(key, "-", "_", -1)
|
||||
envvar = strings.Replace(envvar, ".", "_", -1)
|
||||
envvar = strings.ToUpper(envvar)
|
||||
envvar = "PSU_" + envvar
|
||||
configs = append(configs, config{
|
||||
settings = append(settings, setting{
|
||||
Key: key,
|
||||
EnvironmentVariable: envvar,
|
||||
CurrentValue: viper.Get(key),
|
||||
})
|
||||
}
|
||||
|
||||
switch viper.GetString("config.list.format") {
|
||||
switch viper.GetString("setting.list.format") {
|
||||
case "table":
|
||||
// Print configs in a table format
|
||||
// Print settings in a table format
|
||||
writer, err := common.NewTabWriter([]string{
|
||||
"KEY",
|
||||
"ENV VAR",
|
||||
"CURRENT VALUE",
|
||||
})
|
||||
common.CheckError(err)
|
||||
for _, c := range configs {
|
||||
for _, c := range settings {
|
||||
_, err := fmt.Fprintln(writer, fmt.Sprintf(
|
||||
"%s\t%s\t%v",
|
||||
c.Key,
|
||||
@ -76,15 +76,15 @@ var configListCmd = &cobra.Command{
|
||||
flushErr := writer.Flush()
|
||||
common.CheckError(flushErr)
|
||||
case "json":
|
||||
// Print configs in a json format
|
||||
statusJSONBytes, err := json.Marshal(configs)
|
||||
// Print settings in a json format
|
||||
statusJSONBytes, err := json.Marshal(settings)
|
||||
common.CheckError(err)
|
||||
fmt.Println(string(statusJSONBytes))
|
||||
default:
|
||||
// Print configs in a custom format
|
||||
template, templateParsingErr := template.New("configTpl").Parse(viper.GetString("config.list.format"))
|
||||
// Print settings in a custom format
|
||||
template, templateParsingErr := template.New("settingTpl").Parse(viper.GetString("setting.list.format"))
|
||||
common.CheckError(templateParsingErr)
|
||||
for _, c := range configs {
|
||||
for _, c := range settings {
|
||||
templateExecutionErr := template.Execute(os.Stdout, c)
|
||||
common.CheckError(templateExecutionErr)
|
||||
fmt.Println()
|
||||
@ -94,15 +94,15 @@ var configListCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
configCmd.AddCommand(configListCmd)
|
||||
settingCmd.AddCommand(settingListCmd)
|
||||
|
||||
configListCmd.Flags().String("format", "table", `Output format. Can be "table", "json" or a Go template.`)
|
||||
viper.BindPFlag("config.list.format", configListCmd.Flags().Lookup("format"))
|
||||
settingListCmd.Flags().String("format", "table", `Output format. Can be "table", "json" or a Go template.`)
|
||||
viper.BindPFlag("setting.list.format", settingListCmd.Flags().Lookup("format"))
|
||||
|
||||
configListCmd.SetUsageTemplate(configListCmd.UsageTemplate() + common.GetFormatHelp(config{}))
|
||||
settingListCmd.SetUsageTemplate(settingListCmd.UsageTemplate() + common.GetFormatHelp(setting{}))
|
||||
}
|
||||
|
||||
type config struct {
|
||||
type setting struct {
|
||||
Key string
|
||||
EnvironmentVariable string
|
||||
CurrentValue interface{}
|
55
cmd/settingSet.go
Normal file
55
cmd/settingSet.go
Normal file
@ -0,0 +1,55 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/greenled/portainer-stack-utils/common"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// settingSetCmd represents the setting set command
|
||||
var settingSetCmd = &cobra.Command{
|
||||
Use: "set <key> <value>",
|
||||
Short: "Set setting",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
keyExists := common.CheckSettingKeyExists(args[0])
|
||||
if !keyExists {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"key": args[0],
|
||||
"suggestions": "try looking up the available setting keys: psu setting ls",
|
||||
}).Fatal("unknown setting key")
|
||||
}
|
||||
|
||||
// Set setting
|
||||
err := setSetting(args[0], args[1])
|
||||
common.CheckError(err)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
settingCmd.AddCommand(settingSetCmd)
|
||||
}
|
||||
|
||||
func setSetting(key string, value string) (err error) {
|
||||
newViper, err := common.LoadSettings()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
newViper.Set(key, value)
|
||||
|
||||
// Make sure the setting file exists
|
||||
_, err = os.Create(newViper.ConfigFileUsed())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Write te setting file
|
||||
err = newViper.WriteConfig()
|
||||
|
||||
return
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// LoadCofig loads the configuration file currently used by viper into a new viper instance
|
||||
func LoadCofig() (v *viper.Viper, err error) {
|
||||
// Set config file name
|
||||
var configFile string
|
||||
if viper.ConfigFileUsed() != "" {
|
||||
// Use config file from viper
|
||||
configFile = viper.ConfigFileUsed()
|
||||
} else {
|
||||
// Find home directory
|
||||
var home string
|
||||
home, err = homedir.Dir()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Use $HOME/.psu.yaml
|
||||
configFile = fmt.Sprintf("%s%s.psu.yaml", home, string(os.PathSeparator))
|
||||
}
|
||||
v = viper.New()
|
||||
v.SetConfigFile(configFile)
|
||||
|
||||
// Read config from file
|
||||
err = v.ReadInConfig()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CheckConfigKeyExists checks a given configuration key exists in the default viper
|
||||
func CheckConfigKeyExists(key string) (keyExists bool) {
|
||||
for _, k := range viper.AllKeys() {
|
||||
if k == key {
|
||||
keyExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
47
common/settings.go
Normal file
47
common/settings.go
Normal file
@ -0,0 +1,47 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// LoadSettings loads the settings file currently used by viper into a new viper instance
|
||||
func LoadSettings() (v *viper.Viper, err error) {
|
||||
// Set settings file name
|
||||
var settingsFile string
|
||||
if viper.ConfigFileUsed() != "" {
|
||||
// Use settings file from viper
|
||||
settingsFile = viper.ConfigFileUsed()
|
||||
} else {
|
||||
// Find home directory
|
||||
var home string
|
||||
home, err = homedir.Dir()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Use $HOME/.psu.yaml
|
||||
settingsFile = fmt.Sprintf("%s%s.psu.yaml", home, string(os.PathSeparator))
|
||||
}
|
||||
v = viper.New()
|
||||
v.SetConfigFile(settingsFile)
|
||||
|
||||
// Read settings from file
|
||||
err = v.ReadInConfig()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CheckSettingKeyExists checks a given setting key exists in the default viper
|
||||
func CheckSettingKeyExists(key string) (keyExists bool) {
|
||||
for _, k := range viper.AllKeys() {
|
||||
if k == key {
|
||||
keyExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
Loading…
Reference in New Issue
Block a user