Эх сурвалжийг харах

增加通用的一些配置项

duyong mac 7 сар өмнө
parent
commit
2d91f5a440

+ 70 - 0
app/options/config_simple.yaml

@@ -0,0 +1,70 @@
+# RESTful 服务配置
+server:
+  mode: debug # server mode: release, debug, test,默认 release
+  healthz: true # 是否开启健康检查,如果开启会安装 /healthz 路由,默认 true
+  middlewares: recovery,logger,secure,nocache,cors,dump # 加载的 gin 中间件列表,多个中间件,逗号(,)隔开
+  max-ping-count: 3 # http 服务启动后,自检尝试次数,默认 3
+# GRPC 服务配置
+grpc:
+  bind-address: ${IAM_APISERVER_GRPC_BIND_ADDRESS} # grpc 安全模式的 IP 地址,默认 0.0.0.0
+  bind-port: ${IAM_APISERVER_GRPC_BIND_PORT} # grpc 安全模式的端口号,默认 8081
+# HTTP 配置
+insecure:
+  bind-address: ${IAM_APISERVER_INSECURE_BIND_ADDRESS} # 绑定的不安全 IP 地址,设置为 0.0.0.0 表示使用全部网络接口,默认为 127.0.0.1
+  bind-port: ${IAM_APISERVER_INSECURE_BIND_PORT} # 提供非安全认证的监听端口,默认为 8080
+# HTTPS 配置
+secure:
+  bind-address: ${IAM_APISERVER_SECURE_BIND_ADDRESS} # HTTPS 安全模式的 IP 地址,默认为 0.0.0.0
+  bind-port: ${IAM_APISERVER_SECURE_BIND_PORT} # 使用 HTTPS 安全模式的端口号,设置为 0 表示不启用 HTTPS,默认为 8443
+  tls:
+    #cert-dir: .iam/cert # TLS 证书所在的目录,默认值为 /var/run/iam
+    #pair-name: iam # TLS 私钥对名称,默认 iam
+    cert-key:
+      cert-file: ${IAM_APISERVER_SECURE_TLS_CERT_KEY_CERT_FILE} # 包含 x509 证书的文件路径,用 HTTPS 认证
+      private-key-file: ${IAM_APISERVER_SECURE_TLS_CERT_KEY_PRIVATE_KEY_FILE} # TLS 私钥
+# MySQL 数据库相关配置
+mysql:
+  host: ${MARIADB_HOST} # MySQL 机器 ip 和端口,默认 127.0.0.1:3306
+  username: ${MARIADB_USERNAME} # MySQL 用户名(建议授权最小权限集)
+  password: ${MARIADB_PASSWORD} # MySQL 用户密码
+  database: ${MARIADB_DATABASE} # iam 系统所用的数据库名
+  max-idle-connections: 100 # MySQL 最大空闲连接数,默认 100
+  max-open-connections: 100 # MySQL 最大打开的连接数,默认 100
+  max-connection-life-time: 10s # 空闲连接最大存活时间,默认 10s
+  log-level: 4 # GORM log level, 1: silent, 2:error, 3:warn, 4:info
+# Redis 配置
+redis:
+  host: ${REDIS_HOST} # redis 地址,默认 127.0.0.1:6379
+  port: ${REDIS_PORT} # redis 端口,默认 6379
+  password: ${REDIS_PASSWORD} # redis 密码
+  #addrs:
+  #master-name: # redis 集群 master 名称
+  #username: # redis 登录用户名
+  #database: # redis 数据库
+  #optimisation-max-idle:  # redis 连接池中的最大空闲连接数
+  #optimisation-max-active: # 最大活跃连接数
+  #timeout: # 连接 redis 时的超时时间
+  #enable-cluster: # 是否开启集群模式
+  #use-ssl: # 是否启用 TLS
+  #ssl-insecure-skip-verify: # 当连接 redis 时允许使用自签名证书
+# JWT 配置
+jwt:
+  realm: JWT # jwt 标识
+  key: dfVpOK8LZeJLZHYmHdb1VdyRrACKpqoo # 服务端密钥
+  timeout: 24h # token 过期时间(小时)
+  max-refresh: 24h # token 更新时间(小时)
+
+log:
+  name: apiserver # Logger的名字
+  development: true # 是否是开发模式。如果是开发模式,会对DPanicLevel进行堆栈跟踪。
+  level: debug # 日志级别,优先级从低到高依次为:debug, info, warn, error, dpanic, panic, fatal。
+  format: console # 支持的日志输出格式,目前支持console和json两种。console其实就是text格式。
+  enable-color: true # 是否开启颜色输出,true:是,false:否
+  disable-caller: false # 是否开启 caller,如果开启会在日志中显示调用日志所在的文件、函数和行号
+  disable-stacktrace: false # 是否再panic及以上级别禁止打印堆栈信息
+  output-paths: ${IAM_LOG_DIR}/iam-apiserver.log,stdout # 支持输出到多个输出,逗号分开。支持输出到标准输出(stdout)和文件。
+  error-output-paths: ${IAM_LOG_DIR}/iam-apiserver.error.log # zap内部(非业务)错误日志输出路径,多个输出,逗号分开
+
+feature:
+  enable-metrics: true # 开启 metrics, router:  /metrics
+  profiling: true # 开启性能分析, 可以通过 <host>:<port>/debug/pprof/地址查看程序栈、线程等系统信息,默认值为 true

+ 32 - 0
app/options/feature.go

@@ -0,0 +1,32 @@
+package options
+
+import "github.com/spf13/pflag"
+
+// FeatureOptions 开启性能分析和指标选项
+type FeatureOptions struct {
+	EnableProfiling bool `json:"profiling"      mapstructure:"profiling"`
+	EnableMetrics   bool `json:"enable-metrics" mapstructure:"enable-metrics"`
+}
+
+func NewFeatureOptions() *FeatureOptions {
+	return &FeatureOptions{
+		EnableMetrics:   false,
+		EnableProfiling: false,
+	}
+}
+
+func (o *FeatureOptions) Validate() []error {
+	return []error{}
+}
+
+func (o *FeatureOptions) AddFlags(fs *pflag.FlagSet) {
+	if fs == nil {
+		return
+	}
+
+	fs.BoolVar(&o.EnableProfiling, "feature.profiling", o.EnableProfiling,
+		"Enable profiling via web interface host:port/debug/pprof/")
+
+	fs.BoolVar(&o.EnableMetrics, "feature.enable-metrics", o.EnableMetrics,
+		"Enables metrics on the apiserver at /metrics")
+}

+ 50 - 0
app/options/grpc.go

@@ -0,0 +1,50 @@
+package options
+
+import (
+	"fmt"
+	"github.com/spf13/pflag"
+)
+
+// GRPCOptions grpc基础配置
+type GRPCOptions struct {
+	BindAddress string `json:"bind-address" mapstructure:"bind-address"`
+	BindPort    int    `json:"bind-port"    mapstructure:"bind-port"`
+	MaxMsgSize  int    `json:"max-msg-size" mapstructure:"max-msg-size"`
+}
+
+func NewGRPCOptions() *GRPCOptions {
+	return &GRPCOptions{
+		BindAddress: "0.0.0.0",
+		BindPort:    8081,
+		MaxMsgSize:  4 * 1024 * 1024,
+	}
+}
+
+func (s *GRPCOptions) Validate() []error {
+	var errors []error
+
+	if s.BindPort < 0 || s.BindPort > 65535 {
+		errors = append(
+			errors,
+			fmt.Errorf(
+				"--insecure-port %v must be between 0 and 65535, inclusive. 0 for turning off insecure (HTTP) port",
+				s.BindPort,
+			),
+		)
+	}
+
+	return errors
+}
+
+func (s *GRPCOptions) AddFlags(fs *pflag.FlagSet) {
+	fs.StringVar(&s.BindAddress, "grpc.bind-address", s.BindAddress, ""+
+		"The IP address on which to serve the --grpc.bind-port(set to 0.0.0.0 for all IPv4 interfaces and :: for all IPv6 interfaces).")
+
+	fs.IntVar(&s.BindPort, "grpc.bind-port", s.BindPort, ""+
+		"The port on which to serve unsecured, unauthenticated grpc access. It is assumed "+
+		"that firewall rules are set up such that this port is not reachable from outside of "+
+		"the deployed machine and that port 443 on the iam public address is proxied to this "+
+		"port. This is performed by nginx in the default setup. Set to zero to disable.")
+
+	fs.IntVar(&s.MaxMsgSize, "grpc.max-msg-size", s.MaxMsgSize, "gRPC max message size.")
+}

+ 46 - 0
app/options/insecure_serving.go

@@ -0,0 +1,46 @@
+package options
+
+import (
+	"fmt"
+	"github.com/spf13/pflag"
+)
+
+// InsecureServingOptions 不安全的服务配置信息
+type InsecureServingOptions struct {
+	BindAddress string `json:"bind-address" mapstructure:"bind-address"`
+	BindPort    int    `json:"bind-port"    mapstructure:"bind-port"`
+}
+
+func NewInsecureServingOptions() *InsecureServingOptions {
+	return &InsecureServingOptions{
+		BindAddress: "127.0.0.1",
+		BindPort:    8080,
+	}
+}
+
+func (s *InsecureServingOptions) Validate() []error {
+	var errors []error
+
+	if s.BindPort < 0 || s.BindPort > 65535 {
+		errors = append(
+			errors,
+			fmt.Errorf(
+				"--insecure.bind-port %v must be between 0 and 65535, inclusive. 0 for turning off insecure (HTTP) port",
+				s.BindPort,
+			),
+		)
+	}
+
+	return errors
+}
+
+func (s *InsecureServingOptions) AddFlags(fs *pflag.FlagSet) {
+	fs.StringVar(&s.BindAddress, "insecure.bind-address", s.BindAddress, ""+
+		"The IP address on which to serve the --insecure.bind-port "+
+		"(set to 0.0.0.0 for all IPv4 interfaces and :: for all IPv6 interfaces).")
+	fs.IntVar(&s.BindPort, "insecure.bind-port", s.BindPort, ""+
+		"The port on which to serve unsecured, unauthenticated access. It is assumed "+
+		"that firewall rules are set up such that this port is not reachable from outside of "+
+		"the deployed machine and that port 443 on the iam public address is proxied to this "+
+		"port. This is performed by nginx in the default setup. Set to zero to disable.")
+}

+ 45 - 0
app/options/jwt.go

@@ -0,0 +1,45 @@
+package options
+
+import (
+	"time"
+
+	"github.com/spf13/pflag"
+)
+
+// JwtOptions jwt配置
+type JwtOptions struct {
+	Realm      string        `json:"realm"       mapstructure:"realm"`
+	Key        string        `json:"key"         mapstructure:"key"`
+	Timeout    time.Duration `json:"timeout"     mapstructure:"timeout"`
+	MaxRefresh time.Duration `json:"max-refresh" mapstructure:"max-refresh"`
+}
+
+// NewJwtOptions creates a JwtOptions object with default parameters.
+func NewJwtOptions() *JwtOptions {
+
+	return &JwtOptions{
+		Realm:      "JWT",
+		Key:        "abc123",
+		Timeout:    24 * time.Hour,
+		MaxRefresh: 24 * time.Hour,
+	}
+}
+
+func (s *JwtOptions) Validate() []error {
+	var errs []error
+
+	return errs
+}
+
+func (s *JwtOptions) AddFlags(fs *pflag.FlagSet) {
+	if fs == nil {
+		return
+	}
+
+	fs.StringVar(&s.Realm, "jwt.realm", s.Realm, "Realm name to display to the user.")
+	fs.StringVar(&s.Key, "jwt.key", s.Key, "Private key used to sign jwt token.")
+	fs.DurationVar(&s.Timeout, "jwt.timeout", s.Timeout, "JWT token timeout.")
+
+	fs.DurationVar(&s.MaxRefresh, "jwt.max-refresh", s.MaxRefresh, ""+
+		"This field allows clients to refresh their token until MaxRefresh has passed.")
+}

+ 90 - 0
app/options/log.go

@@ -0,0 +1,90 @@
+package options
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/spf13/pflag"
+	"go.uber.org/zap/zapcore"
+	"strings"
+)
+
+const (
+	flagLevel             = "log.level"
+	flagDisableCaller     = "log.disable-caller"
+	flagDisableStacktrace = "log.disable-stacktrace"
+	flagFormat            = "log.format"
+	flagEnableColor       = "log.enable-color"
+	flagOutputPaths       = "log.output-paths"
+	flagErrorOutputPaths  = "log.error-output-paths"
+	flagDevelopment       = "log.development"
+	flagName              = "log.name"
+
+	consoleFormat = "console"
+	jsonFormat    = "json"
+)
+
+type LogOptions struct {
+	OutputPaths       []string `json:"output-paths"       mapstructure:"output-paths"`
+	ErrorOutputPaths  []string `json:"error-output-paths" mapstructure:"error-output-paths"`
+	Level             string   `json:"level"              mapstructure:"level"`
+	Format            string   `json:"format"             mapstructure:"format"`
+	DisableCaller     bool     `json:"disable-caller"     mapstructure:"disable-caller"`
+	DisableStacktrace bool     `json:"disable-stacktrace" mapstructure:"disable-stacktrace"`
+	EnableColor       bool     `json:"enable-color"       mapstructure:"enable-color"`
+	Development       bool     `json:"development"        mapstructure:"development"`
+	Name              string   `json:"name"               mapstructure:"name"`
+}
+
+func NewLogOptions() *LogOptions {
+	return &LogOptions{
+		Level:             zapcore.InfoLevel.String(),
+		DisableCaller:     false,
+		DisableStacktrace: false,
+		Format:            consoleFormat,
+		EnableColor:       false,
+		Development:       false,
+		OutputPaths:       []string{"stdout"},
+		ErrorOutputPaths:  []string{"stderr"},
+	}
+}
+
+func (o *LogOptions) Validate() []error {
+	var errs []error
+
+	var zapLevel zapcore.Level
+	if err := zapLevel.UnmarshalText([]byte(o.Level)); err != nil {
+		errs = append(errs, err)
+	}
+
+	format := strings.ToLower(o.Format)
+	if format != consoleFormat && format != jsonFormat {
+		errs = append(errs, fmt.Errorf("not a valid log format: %q", o.Format))
+	}
+
+	return errs
+}
+
+func (o *LogOptions) AddFlags(fs *pflag.FlagSet) {
+	fs.StringVar(&o.Level, flagLevel, o.Level, "Minimum log output `LEVEL`.")
+	fs.BoolVar(&o.DisableCaller, flagDisableCaller, o.DisableCaller, "Disable output of caller information in the log.")
+	fs.BoolVar(&o.DisableStacktrace, flagDisableStacktrace,
+		o.DisableStacktrace, "Disable the log to record a stack trace for all messages at or above panic level.")
+	fs.StringVar(&o.Format, flagFormat, o.Format, "Log output `FORMAT`, support plain or json format.")
+	fs.BoolVar(&o.EnableColor, flagEnableColor, o.EnableColor, "Enable output ansi colors in plain format logs.")
+	fs.StringSliceVar(&o.OutputPaths, flagOutputPaths, o.OutputPaths, "Output paths of log.")
+	fs.StringSliceVar(&o.ErrorOutputPaths, flagErrorOutputPaths, o.ErrorOutputPaths, "Error output paths of log.")
+	fs.BoolVar(
+		&o.Development,
+		flagDevelopment,
+		o.Development,
+		"Development puts the logger in development mode, which changes "+
+			"the behavior of DPanicLevel and takes stacktraces more liberally.",
+	)
+	fs.StringVar(&o.Name, flagName, o.Name, "The name of the logger.")
+}
+
+func (o *LogOptions) String() string {
+	data, _ := json.Marshal(o)
+
+	return string(data)
+}

+ 64 - 0
app/options/mysql.go

@@ -0,0 +1,64 @@
+package options
+
+import (
+	"time"
+
+	"github.com/spf13/pflag"
+)
+
+// MySQLOptions Mysql配置
+type MySQLOptions struct {
+	Host                  string        `json:"host,omitempty"                     mapstructure:"host"`
+	Username              string        `json:"username,omitempty"                 mapstructure:"username"`
+	Password              string        `json:"-"                                  mapstructure:"password"`
+	Database              string        `json:"database"                           mapstructure:"database"`
+	MaxIdleConnections    int           `json:"max-idle-connections,omitempty"     mapstructure:"max-idle-connections"`
+	MaxOpenConnections    int           `json:"max-open-connections,omitempty"     mapstructure:"max-open-connections"`
+	MaxConnectionLifeTime time.Duration `json:"max-connection-life-time,omitempty" mapstructure:"max-connection-life-time"`
+	LogLevel              int           `json:"log-level"                          mapstructure:"log-level"`
+}
+
+func NewMySQLOptions() *MySQLOptions {
+	return &MySQLOptions{
+		Host:                  "127.0.0.1:3306",
+		Username:              "",
+		Password:              "",
+		Database:              "",
+		MaxIdleConnections:    100,
+		MaxOpenConnections:    100,
+		MaxConnectionLifeTime: time.Duration(10) * time.Second,
+		LogLevel:              1, // Silent
+	}
+}
+
+func (o *MySQLOptions) Validate() []error {
+	var errs []error
+
+	return errs
+}
+
+func (o *MySQLOptions) AddFlags(fs *pflag.FlagSet) {
+	fs.StringVar(&o.Host, "mysql.host", o.Host, ""+
+		"MySQL service host address. If left blank, the following related mysql comopts will be ignored.")
+
+	fs.StringVar(&o.Username, "mysql.username", o.Username, ""+
+		"Username for access to mysql service.")
+
+	fs.StringVar(&o.Password, "mysql.password", o.Password, ""+
+		"Password for access to mysql, should be used pair with password.")
+
+	fs.StringVar(&o.Database, "mysql.database", o.Database, ""+
+		"Database name for the server to use.")
+
+	fs.IntVar(&o.MaxIdleConnections, "mysql.max-idle-connections", o.MaxOpenConnections, ""+
+		"Maximum idle connections allowed to connect to mysql.")
+
+	fs.IntVar(&o.MaxOpenConnections, "mysql.max-open-connections", o.MaxOpenConnections, ""+
+		"Maximum open connections allowed to connect to mysql.")
+
+	fs.DurationVar(&o.MaxConnectionLifeTime, "mysql.max-connection-life-time", o.MaxConnectionLifeTime, ""+
+		"Maximum connection life time allowed to connect to mysql.")
+
+	fs.IntVar(&o.LogLevel, "mysql.log-mode", o.LogLevel, ""+
+		"Specify gorm log level.")
+}

+ 81 - 0
app/options/redis.go

@@ -0,0 +1,81 @@
+package options
+
+import (
+	"github.com/spf13/pflag"
+)
+
+// RedisOptions redis配置
+type RedisOptions struct {
+	Host                  string   `json:"host"                     mapstructure:"host"                     description:"Redis service host address"`
+	Port                  int      `json:"port"`
+	Addrs                 []string `json:"addrs"                    mapstructure:"addrs"`
+	Username              string   `json:"username"                 mapstructure:"username"`
+	Password              string   `json:"password"                 mapstructure:"password"`
+	Database              int      `json:"database"                 mapstructure:"database"`
+	MasterName            string   `json:"master-name"              mapstructure:"master-name"`
+	MaxIdle               int      `json:"optimisation-max-idle"    mapstructure:"optimisation-max-idle"`
+	MaxActive             int      `json:"optimisation-max-active"  mapstructure:"optimisation-max-active"`
+	Timeout               int      `json:"timeout"                  mapstructure:"timeout"`
+	EnableCluster         bool     `json:"enable-cluster"           mapstructure:"enable-cluster"`
+	UseSSL                bool     `json:"use-ssl"                  mapstructure:"use-ssl"`
+	SSLInsecureSkipVerify bool     `json:"ssl-insecure-skip-verify" mapstructure:"ssl-insecure-skip-verify"`
+}
+
+func NewRedisOptions() *RedisOptions {
+	return &RedisOptions{
+		Host:                  "127.0.0.1",
+		Port:                  6379,
+		Addrs:                 []string{},
+		Username:              "",
+		Password:              "",
+		Database:              0,
+		MasterName:            "",
+		MaxIdle:               2000,
+		MaxActive:             4000,
+		Timeout:               0,
+		EnableCluster:         false,
+		UseSSL:                false,
+		SSLInsecureSkipVerify: false,
+	}
+}
+
+func (o *RedisOptions) Validate() []error {
+	errs := []error{}
+
+	return errs
+}
+
+func (o *RedisOptions) AddFlags(fs *pflag.FlagSet) {
+	fs.StringVar(&o.Host, "redis.host", o.Host, "Hostname of your Redis server.")
+	fs.IntVar(&o.Port, "redis.port", o.Port, "The port the Redis server is listening on.")
+	fs.StringSliceVar(&o.Addrs, "redis.addrs", o.Addrs, "A set of redis address(format: 127.0.0.1:6379).")
+	fs.StringVar(&o.Username, "redis.username", o.Username, "Username for access to redis service.")
+	fs.StringVar(&o.Password, "redis.password", o.Password, "Optional auth password for Redis db.")
+
+	fs.IntVar(&o.Database, "redis.database", o.Database, ""+
+		"By default, the database is 0. Setting the database is not supported with redis cluster. "+
+		"As such, if you have --redis.enable-cluster=true, then this value should be omitted or explicitly set to 0.")
+
+	fs.StringVar(&o.MasterName, "redis.master-name", o.MasterName, "The name of master redis instance.")
+
+	fs.IntVar(&o.MaxIdle, "redis.optimisation-max-idle", o.MaxIdle, ""+
+		"This setting will configure how many connections are maintained in the pool when idle (no traffic). "+
+		"Set the --redis.optimisation-max-active to something large, we usually leave it at around 2000 for "+
+		"HA deployments.")
+
+	fs.IntVar(&o.MaxActive, "redis.optimisation-max-active", o.MaxActive, ""+
+		"In order to not over commit connections to the Redis server, we may limit the total "+
+		"number of active connections to Redis. We recommend for production use to set this to around 4000.")
+
+	fs.IntVar(&o.Timeout, "redis.timeout", o.Timeout, "Timeout (in seconds) when connecting to redis service.")
+
+	fs.BoolVar(&o.EnableCluster, "redis.enable-cluster", o.EnableCluster, ""+
+		"If you are using Redis cluster, enable it here to enable the slots mode.")
+
+	fs.BoolVar(&o.UseSSL, "redis.use-ssl", o.UseSSL, ""+
+		"If set, IAM will assume the connection to Redis is encrypted. "+
+		"(use with Redis providers that support in-transit encryption).")
+
+	fs.BoolVar(&o.SSLInsecureSkipVerify, "redis.ssl-insecure-skip-verify", o.SSLInsecureSkipVerify, ""+
+		"Allows usage of self-signed certificates when connecting to an encrypted Redis database.")
+}

+ 127 - 0
app/options/secure_serving.go

@@ -0,0 +1,127 @@
+package options
+
+import (
+	"fmt"
+	"path"
+
+	"github.com/spf13/pflag"
+)
+
+// SecureServingOptions 安全的服务配置
+type SecureServingOptions struct {
+	BindAddress string `json:"bind-address" mapstructure:"bind-address"`
+	BindPort    int    `json:"bind-port"    mapstructure:"bind-port"`
+	// Required set to true means that BindPort cannot be zero.
+	Required   bool
+	ServerCert GeneratableKeyCert `json:"tls"          mapstructure:"tls"`
+}
+
+// CertKey 证书文件结构
+type CertKey struct {
+	// CertFile is a file containing a PEM-encoded certificate, and possibly the complete certificate chain
+	CertFile string `json:"cert-file"        mapstructure:"cert-file"`
+	// KeyFile is a file containing a PEM-encoded private key for the certificate specified by CertFile
+	KeyFile string `json:"private-key-file" mapstructure:"private-key-file"`
+}
+
+// GeneratableKeyCert 证书相关配置项
+type GeneratableKeyCert struct {
+	// CertKey allows setting an explicit cert/key file to use.
+	CertKey CertKey `json:"cert-key" mapstructure:"cert-key"`
+
+	// CertDirectory specifies a directory to write generated certificates to if CertFile/KeyFile aren't explicitly set.
+	// PairName is used to determine the filenames within CertDirectory.
+	// If CertDirectory and PairName are not set, an in-memory certificate will be generated.
+	CertDirectory string `json:"cert-dir"  mapstructure:"cert-dir"`
+	// PairName is the name which will be used with CertDirectory to make a cert and key filenames.
+	// It becomes CertDirectory/PairName.crt and CertDirectory/PairName.key
+	PairName string `json:"pair-name" mapstructure:"pair-name"`
+}
+
+// NewSecureServingOptions creates a SecureServingOptions object with default parameters.
+func NewSecureServingOptions() *SecureServingOptions {
+	return &SecureServingOptions{
+		BindAddress: "0.0.0.0",
+		BindPort:    8443,
+		Required:    true,
+		ServerCert: GeneratableKeyCert{
+			PairName:      "dy-admin",
+			CertDirectory: "/var/run/dy-admin",
+		},
+	}
+}
+
+func (s *SecureServingOptions) Validate() []error {
+	if s == nil {
+		return nil
+	}
+
+	var errors []error
+
+	if s.Required && s.BindPort < 1 || s.BindPort > 65535 {
+		errors = append(
+			errors,
+			fmt.Errorf(
+				"--secure.bind-port %v must be between 1 and 65535, inclusive. It cannot be turned off with 0",
+				s.BindPort,
+			),
+		)
+	} else if s.BindPort < 0 || s.BindPort > 65535 {
+		errors = append(errors, fmt.Errorf("--secure.bind-port %v must be between 0 and 65535, inclusive. 0 for turning off secure port", s.BindPort))
+	}
+
+	return errors
+}
+
+func (s *SecureServingOptions) AddFlags(fs *pflag.FlagSet) {
+	fs.StringVar(&s.BindAddress, "secure.bind-address", s.BindAddress, ""+
+		"The IP address on which to listen for the --secure.bind-port port. The "+
+		"associated interface(s) must be reachable by the rest of the engine, and by CLI/web "+
+		"clients. If blank, all interfaces will be used (0.0.0.0 for all IPv4 interfaces and :: for all IPv6 interfaces).")
+	desc := "The port on which to serve HTTPS with authentication and authorization."
+	if s.Required {
+		desc += " It cannot be switched off with 0."
+	} else {
+		desc += " If 0, don't serve HTTPS at all."
+	}
+	fs.IntVar(&s.BindPort, "secure.bind-port", s.BindPort, desc)
+
+	fs.StringVar(&s.ServerCert.CertDirectory, "secure.tls.cert-dir", s.ServerCert.CertDirectory, ""+
+		"The directory where the TLS certs are located. "+
+		"If --secure.tls.cert-key.cert-file and --secure.tls.cert-key.private-key-file are provided, "+
+		"this flag will be ignored.")
+
+	fs.StringVar(&s.ServerCert.PairName, "secure.tls.pair-name", s.ServerCert.PairName, ""+
+		"The name which will be used with --secure.tls.cert-dir to make a cert and key filenames. "+
+		"It becomes <cert-dir>/<pair-name>.crt and <cert-dir>/<pair-name>.key")
+
+	fs.StringVar(&s.ServerCert.CertKey.CertFile, "secure.tls.cert-key.cert-file", s.ServerCert.CertKey.CertFile, ""+
+		"File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated "+
+		"after server cert).")
+
+	fs.StringVar(&s.ServerCert.CertKey.KeyFile, "secure.tls.cert-key.private-key-file",
+		s.ServerCert.CertKey.KeyFile, ""+
+			"File containing the default x509 private key matching --secure.tls.cert-key.cert-file.")
+}
+
+// Complete fills in any fields not set that are required to have valid data.
+func (s *SecureServingOptions) Complete() error {
+	if s == nil || s.BindPort == 0 {
+		return nil
+	}
+
+	keyCert := &s.ServerCert.CertKey
+	if len(keyCert.CertFile) != 0 || len(keyCert.KeyFile) != 0 {
+		return nil
+	}
+
+	if len(s.ServerCert.CertDirectory) > 0 {
+		if len(s.ServerCert.PairName) == 0 {
+			return fmt.Errorf("--secure.tls.pair-name is required if --secure.tls.cert-dir is set")
+		}
+		keyCert.CertFile = path.Join(s.ServerCert.CertDirectory, s.ServerCert.PairName+".crt")
+		keyCert.KeyFile = path.Join(s.ServerCert.CertDirectory, s.ServerCert.PairName+".key")
+	}
+
+	return nil
+}

+ 40 - 0
app/options/server_run.go

@@ -0,0 +1,40 @@
+package options
+
+import (
+	"github.com/spf13/pflag"
+)
+
+// ServerRunOptions 服务运行配置
+type ServerRunOptions struct {
+	Mode        string   `json:"mode"        mapstructure:"mode"`
+	Healthz     bool     `json:"healthz"     mapstructure:"healthz"`
+	Middlewares []string `json:"middlewares" mapstructure:"middlewares"`
+}
+
+func NewServerRunOptions() *ServerRunOptions {
+
+	return &ServerRunOptions{
+		Mode:        "release",
+		Healthz:     false,
+		Middlewares: []string{},
+	}
+}
+
+func (s *ServerRunOptions) Validate() []error {
+	var errors []error
+
+	return errors
+}
+
+func (s *ServerRunOptions) AddFlags(fs *pflag.FlagSet) {
+	// Note: the weird ""+ in below lines seems to be the only way to get gofmt to
+	// arrange these text blocks sensibly. Grrr.
+	fs.StringVar(&s.Mode, "server.mode", s.Mode, ""+
+		"Start the server in a specified server mode. Supported server mode: debug, test, release.")
+
+	fs.BoolVar(&s.Healthz, "server.healthz", s.Healthz, ""+
+		"Add self readiness check and install /healthz router.")
+
+	fs.StringSliceVar(&s.Middlewares, "server.middlewares", s.Middlewares, ""+
+		"List of allowed middlewares for server, comma separated. If this list is empty default middlewares will be used.")
+}