mirror of https://github.com/k3s-io/k3s
Add FeatureGates field to KubeletConfiguration
parent
3a1c329b0d
commit
a38566dac6
|
@ -19,6 +19,7 @@ package options
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/apis/componentconfig"
|
"k8s.io/kubernetes/pkg/apis/componentconfig"
|
||||||
|
@ -179,7 +180,8 @@ func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) {
|
||||||
fs.StringVar(&s.VolumePluginDir, "volume-plugin-dir", s.VolumePluginDir, "<Warning: Alpha feature> The full path of the directory in which to search for additional third party volume plugins")
|
fs.StringVar(&s.VolumePluginDir, "volume-plugin-dir", s.VolumePluginDir, "<Warning: Alpha feature> The full path of the directory in which to search for additional third party volume plugins")
|
||||||
fs.StringVar(&s.CloudProvider, "cloud-provider", s.CloudProvider, "The provider for cloud services. By default, kubelet will attempt to auto-detect the cloud provider. Specify empty string for running with no cloud provider. [default=auto-detect]")
|
fs.StringVar(&s.CloudProvider, "cloud-provider", s.CloudProvider, "The provider for cloud services. By default, kubelet will attempt to auto-detect the cloud provider. Specify empty string for running with no cloud provider. [default=auto-detect]")
|
||||||
fs.StringVar(&s.CloudConfigFile, "cloud-config", s.CloudConfigFile, "The path to the cloud provider configuration file. Empty string for no configuration file.")
|
fs.StringVar(&s.CloudConfigFile, "cloud-config", s.CloudConfigFile, "The path to the cloud provider configuration file. Empty string for no configuration file.")
|
||||||
utilconfig.DefaultFeatureGate.AddFlag(fs)
|
fs.StringVar(&s.FeatureGates, "feature-gates", s.FeatureGates, "A set of key=value pairs that describe feature gates for alpha/experimental features. "+
|
||||||
|
"Options are:\n"+strings.Join(utilconfig.DefaultFeatureGate.KnownFeatures(), "\n"))
|
||||||
|
|
||||||
fs.StringVar(&s.KubeletCgroups, "resource-container", s.KubeletCgroups, "Optional absolute name of the resource-only container to create and run the Kubelet in.")
|
fs.StringVar(&s.KubeletCgroups, "resource-container", s.KubeletCgroups, "Optional absolute name of the resource-only container to create and run the Kubelet in.")
|
||||||
fs.MarkDeprecated("resource-container", "Use --kubelet-cgroups instead. Will be removed in a future version.")
|
fs.MarkDeprecated("resource-container", "Use --kubelet-cgroups instead. Will be removed in a future version.")
|
||||||
|
|
|
@ -321,6 +321,12 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.KubeletDeps) (err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set feature gates based on the value in KubeletConfiguration
|
||||||
|
err = utilconfig.DefaultFeatureGate.Set(s.KubeletConfiguration.FeatureGates)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Register current configuration with /configz endpoint
|
// Register current configuration with /configz endpoint
|
||||||
cfgz, cfgzErr := initConfigz(&s.KubeletConfiguration)
|
cfgz, cfgzErr := initConfigz(&s.KubeletConfiguration)
|
||||||
if utilconfig.DefaultFeatureGate.DynamicKubeletConfig() {
|
if utilconfig.DefaultFeatureGate.DynamicKubeletConfig() {
|
||||||
|
@ -339,6 +345,11 @@ func run(s *options.KubeletServer, kubeDeps *kubelet.KubeletDeps) (err error) {
|
||||||
} else {
|
} else {
|
||||||
setConfigz(cfgz, &s.KubeletConfiguration)
|
setConfigz(cfgz, &s.KubeletConfiguration)
|
||||||
}
|
}
|
||||||
|
// Update feature gates from the new config
|
||||||
|
err = utilconfig.DefaultFeatureGate.Set(s.KubeletConfiguration.FeatureGates)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -452,6 +452,9 @@ type KubeletConfiguration struct {
|
||||||
// Whitelist of unsafe sysctls or sysctl patterns (ending in *).
|
// Whitelist of unsafe sysctls or sysctl patterns (ending in *).
|
||||||
// +optional
|
// +optional
|
||||||
AllowedUnsafeSysctls []string `json:"experimentalAllowedUnsafeSysctls,omitempty"`
|
AllowedUnsafeSysctls []string `json:"experimentalAllowedUnsafeSysctls,omitempty"`
|
||||||
|
// featureGates is a string of comma-separated key=value pairs that describe feature
|
||||||
|
// gates for alpha/experimental features.
|
||||||
|
FeatureGates string `json:"featureGates"`
|
||||||
// How to integrate with runtime. If set to cri, kubelet will switch to
|
// How to integrate with runtime. If set to cri, kubelet will switch to
|
||||||
// using the new Container Runtine Interface.
|
// using the new Container Runtine Interface.
|
||||||
// +optional
|
// +optional
|
||||||
|
|
|
@ -491,6 +491,9 @@ type KubeletConfiguration struct {
|
||||||
// Resource isolation might be lacking and pod might influence each other on the same node.
|
// Resource isolation might be lacking and pod might influence each other on the same node.
|
||||||
// +optional
|
// +optional
|
||||||
AllowedUnsafeSysctls []string `json:"allowedUnsafeSysctls,omitempty"`
|
AllowedUnsafeSysctls []string `json:"allowedUnsafeSysctls,omitempty"`
|
||||||
|
// featureGates is a string of comma-separated key=value pairs that describe feature
|
||||||
|
// gates for alpha/experimental features.
|
||||||
|
FeatureGates string `json:"featureGates,omitempty"`
|
||||||
// How to integrate with runtime. If set to CRI, kubelet will switch to
|
// How to integrate with runtime. If set to CRI, kubelet will switch to
|
||||||
// using the new Container Runtine Interface.
|
// using the new Container Runtine Interface.
|
||||||
// +optional
|
// +optional
|
||||||
|
|
|
@ -401,6 +401,7 @@ func autoConvert_v1alpha1_KubeletConfiguration_To_componentconfig_KubeletConfigu
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
out.AllowedUnsafeSysctls = *(*[]string)(unsafe.Pointer(&in.AllowedUnsafeSysctls))
|
out.AllowedUnsafeSysctls = *(*[]string)(unsafe.Pointer(&in.AllowedUnsafeSysctls))
|
||||||
|
out.FeatureGates = in.FeatureGates
|
||||||
out.ExperimentalRuntimeIntegrationType = in.ExperimentalRuntimeIntegrationType
|
out.ExperimentalRuntimeIntegrationType = in.ExperimentalRuntimeIntegrationType
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -568,6 +569,7 @@ func autoConvert_componentconfig_KubeletConfiguration_To_v1alpha1_KubeletConfigu
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
out.AllowedUnsafeSysctls = *(*[]string)(unsafe.Pointer(&in.AllowedUnsafeSysctls))
|
out.AllowedUnsafeSysctls = *(*[]string)(unsafe.Pointer(&in.AllowedUnsafeSysctls))
|
||||||
|
out.FeatureGates = in.FeatureGates
|
||||||
out.ExperimentalRuntimeIntegrationType = in.ExperimentalRuntimeIntegrationType
|
out.ExperimentalRuntimeIntegrationType = in.ExperimentalRuntimeIntegrationType
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -457,6 +457,7 @@ func DeepCopy_v1alpha1_KubeletConfiguration(in interface{}, out interface{}, c *
|
||||||
} else {
|
} else {
|
||||||
out.AllowedUnsafeSysctls = nil
|
out.AllowedUnsafeSysctls = nil
|
||||||
}
|
}
|
||||||
|
out.FeatureGates = in.FeatureGates
|
||||||
out.ExperimentalRuntimeIntegrationType = in.ExperimentalRuntimeIntegrationType
|
out.ExperimentalRuntimeIntegrationType = in.ExperimentalRuntimeIntegrationType
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -387,6 +387,7 @@ func DeepCopy_componentconfig_KubeletConfiguration(in interface{}, out interface
|
||||||
} else {
|
} else {
|
||||||
out.AllowedUnsafeSysctls = nil
|
out.AllowedUnsafeSysctls = nil
|
||||||
}
|
}
|
||||||
|
out.FeatureGates = in.FeatureGates
|
||||||
out.ExperimentalRuntimeIntegrationType = in.ExperimentalRuntimeIntegrationType
|
out.ExperimentalRuntimeIntegrationType = in.ExperimentalRuntimeIntegrationType
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2970,6 +2970,13 @@ var OpenAPIDefinitions *common.OpenAPIDefinitions = &common.OpenAPIDefinitions{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"featureGates": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "featureGates is a string of comma-separated key=value pairs that describe feature gates for alpha/experimental features.",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
"experimentalRuntimeIntegrationType": {
|
"experimentalRuntimeIntegrationType": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Description: "How to integrate with runtime. If set to cri, kubelet will switch to using the new Container Runtine Interface.",
|
Description: "How to integrate with runtime. If set to cri, kubelet will switch to using the new Container Runtine Interface.",
|
||||||
|
@ -2978,7 +2985,7 @@ var OpenAPIDefinitions *common.OpenAPIDefinitions = &common.OpenAPIDefinitions{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Required: []string{"TypeMeta", "podManifestPath", "syncFrequency", "fileCheckFrequency", "httpCheckFrequency", "manifestURL", "manifestURLHeader", "enableServer", "address", "port", "readOnlyPort", "tlsCertFile", "tlsPrivateKeyFile", "certDirectory", "authentication", "authorization", "hostnameOverride", "podInfraContainerImage", "dockerEndpoint", "rootDirectory", "seccompProfileRoot", "allowPrivileged", "hostNetworkSources", "hostPIDSources", "hostIPCSources", "registryPullQPS", "registryBurst", "eventRecordQPS", "eventBurst", "enableDebuggingHandlers", "minimumGCAge", "maxPerPodContainerCount", "maxContainerCount", "cAdvisorPort", "healthzPort", "healthzBindAddress", "oomScoreAdj", "registerNode", "clusterDomain", "masterServiceNamespace", "clusterDNS", "streamingConnectionIdleTimeout", "nodeStatusUpdateFrequency", "imageMinimumGCAge", "imageGCHighThresholdPercent", "imageGCLowThresholdPercent", "lowDiskSpaceThresholdMB", "volumeStatsAggPeriod", "networkPluginName", "networkPluginMTU", "networkPluginDir", "cniConfDir", "cniBinDir", "volumePluginDir", "containerRuntime", "remoteRuntimeEndpoint", "remoteImageEndpoint", "experimentalMounterPath", "experimentalMounterRootfsPath", "lockFilePath", "exitOnLockContention", "hairpinMode", "babysitDaemons", "maxPods", "nvidiaGPUs", "dockerExecHandlerName", "podCIDR", "resolvConf", "cpuCFSQuota", "containerized", "maxOpenFiles", "reconcileCIDR", "registerSchedulable", "contentType", "kubeAPIQPS", "kubeAPIBurst", "serializeImagePulls", "nodeLabels", "nonMasqueradeCIDR", "enableCustomMetrics", "podsPerCore", "enableControllerAttachDetach", "systemReserved", "kubeReserved", "protectKernelDefaults", "makeIPTablesUtilChains", "iptablesMasqueradeBit", "iptablesDropBit"},
|
Required: []string{"TypeMeta", "podManifestPath", "syncFrequency", "fileCheckFrequency", "httpCheckFrequency", "manifestURL", "manifestURLHeader", "enableServer", "address", "port", "readOnlyPort", "tlsCertFile", "tlsPrivateKeyFile", "certDirectory", "authentication", "authorization", "hostnameOverride", "podInfraContainerImage", "dockerEndpoint", "rootDirectory", "seccompProfileRoot", "allowPrivileged", "hostNetworkSources", "hostPIDSources", "hostIPCSources", "registryPullQPS", "registryBurst", "eventRecordQPS", "eventBurst", "enableDebuggingHandlers", "minimumGCAge", "maxPerPodContainerCount", "maxContainerCount", "cAdvisorPort", "healthzPort", "healthzBindAddress", "oomScoreAdj", "registerNode", "clusterDomain", "masterServiceNamespace", "clusterDNS", "streamingConnectionIdleTimeout", "nodeStatusUpdateFrequency", "imageMinimumGCAge", "imageGCHighThresholdPercent", "imageGCLowThresholdPercent", "lowDiskSpaceThresholdMB", "volumeStatsAggPeriod", "networkPluginName", "networkPluginMTU", "networkPluginDir", "cniConfDir", "cniBinDir", "volumePluginDir", "containerRuntime", "remoteRuntimeEndpoint", "remoteImageEndpoint", "experimentalMounterPath", "experimentalMounterRootfsPath", "lockFilePath", "exitOnLockContention", "hairpinMode", "babysitDaemons", "maxPods", "nvidiaGPUs", "dockerExecHandlerName", "podCIDR", "resolvConf", "cpuCFSQuota", "containerized", "maxOpenFiles", "reconcileCIDR", "registerSchedulable", "contentType", "kubeAPIQPS", "kubeAPIBurst", "serializeImagePulls", "nodeLabels", "nonMasqueradeCIDR", "enableCustomMetrics", "podsPerCore", "enableControllerAttachDetach", "systemReserved", "kubeReserved", "protectKernelDefaults", "makeIPTablesUtilChains", "iptablesMasqueradeBit", "iptablesDropBit", "featureGates"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Dependencies: []string{
|
Dependencies: []string{
|
||||||
|
@ -14714,6 +14721,13 @@ var OpenAPIDefinitions *common.OpenAPIDefinitions = &common.OpenAPIDefinitions{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"featureGates": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "featureGates is a string of comma-separated key=value pairs that describe feature gates for alpha/experimental features.",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
"experimentalRuntimeIntegrationType": {
|
"experimentalRuntimeIntegrationType": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Description: "How to integrate with runtime. If set to CRI, kubelet will switch to using the new Container Runtine Interface.",
|
Description: "How to integrate with runtime. If set to CRI, kubelet will switch to using the new Container Runtine Interface.",
|
||||||
|
@ -14722,7 +14736,7 @@ var OpenAPIDefinitions *common.OpenAPIDefinitions = &common.OpenAPIDefinitions{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Required: []string{"TypeMeta", "podManifestPath", "syncFrequency", "fileCheckFrequency", "httpCheckFrequency", "manifestURL", "manifestURLHeader", "enableServer", "address", "port", "readOnlyPort", "tlsCertFile", "tlsPrivateKeyFile", "certDirectory", "authentication", "authorization", "hostnameOverride", "podInfraContainerImage", "dockerEndpoint", "rootDirectory", "seccompProfileRoot", "allowPrivileged", "hostNetworkSources", "hostPIDSources", "hostIPCSources", "registryPullQPS", "registryBurst", "eventRecordQPS", "eventBurst", "enableDebuggingHandlers", "minimumGCAge", "maxPerPodContainerCount", "maxContainerCount", "cAdvisorPort", "healthzPort", "healthzBindAddress", "oomScoreAdj", "registerNode", "clusterDomain", "masterServiceNamespace", "clusterDNS", "streamingConnectionIdleTimeout", "nodeStatusUpdateFrequency", "imageMinimumGCAge", "imageGCHighThresholdPercent", "imageGCLowThresholdPercent", "lowDiskSpaceThresholdMB", "volumeStatsAggPeriod", "networkPluginName", "networkPluginDir", "cniConfDir", "cniBinDir", "networkPluginMTU", "volumePluginDir", "cloudProvider", "cloudConfigFile", "kubeletCgroups", "runtimeCgroups", "systemCgroups", "cgroupRoot", "containerRuntime", "remoteRuntimeEndpoint", "remoteImageEndpoint", "runtimeRequestTimeout", "rktPath", "experimentalMounterPath", "experimentalMounterRootfsPath", "rktAPIEndpoint", "rktStage1Image", "lockFilePath", "exitOnLockContention", "hairpinMode", "babysitDaemons", "maxPods", "nvidiaGPUs", "dockerExecHandlerName", "podCIDR", "resolvConf", "cpuCFSQuota", "containerized", "maxOpenFiles", "reconcileCIDR", "registerSchedulable", "contentType", "kubeAPIQPS", "kubeAPIBurst", "serializeImagePulls", "outOfDiskTransitionFrequency", "nodeIP", "nodeLabels", "nonMasqueradeCIDR", "enableCustomMetrics", "evictionHard", "evictionSoft", "evictionSoftGracePeriod", "evictionPressureTransitionPeriod", "evictionMaxPodGracePeriod", "evictionMinimumReclaim", "podsPerCore", "enableControllerAttachDetach", "systemReserved", "kubeReserved", "protectKernelDefaults", "makeIPTablesUtilChains", "iptablesMasqueradeBit", "iptablesDropBit"},
|
Required: []string{"TypeMeta", "podManifestPath", "syncFrequency", "fileCheckFrequency", "httpCheckFrequency", "manifestURL", "manifestURLHeader", "enableServer", "address", "port", "readOnlyPort", "tlsCertFile", "tlsPrivateKeyFile", "certDirectory", "authentication", "authorization", "hostnameOverride", "podInfraContainerImage", "dockerEndpoint", "rootDirectory", "seccompProfileRoot", "allowPrivileged", "hostNetworkSources", "hostPIDSources", "hostIPCSources", "registryPullQPS", "registryBurst", "eventRecordQPS", "eventBurst", "enableDebuggingHandlers", "minimumGCAge", "maxPerPodContainerCount", "maxContainerCount", "cAdvisorPort", "healthzPort", "healthzBindAddress", "oomScoreAdj", "registerNode", "clusterDomain", "masterServiceNamespace", "clusterDNS", "streamingConnectionIdleTimeout", "nodeStatusUpdateFrequency", "imageMinimumGCAge", "imageGCHighThresholdPercent", "imageGCLowThresholdPercent", "lowDiskSpaceThresholdMB", "volumeStatsAggPeriod", "networkPluginName", "networkPluginDir", "cniConfDir", "cniBinDir", "networkPluginMTU", "volumePluginDir", "cloudProvider", "cloudConfigFile", "kubeletCgroups", "runtimeCgroups", "systemCgroups", "cgroupRoot", "containerRuntime", "remoteRuntimeEndpoint", "remoteImageEndpoint", "runtimeRequestTimeout", "rktPath", "experimentalMounterPath", "experimentalMounterRootfsPath", "rktAPIEndpoint", "rktStage1Image", "lockFilePath", "exitOnLockContention", "hairpinMode", "babysitDaemons", "maxPods", "nvidiaGPUs", "dockerExecHandlerName", "podCIDR", "resolvConf", "cpuCFSQuota", "containerized", "maxOpenFiles", "reconcileCIDR", "registerSchedulable", "contentType", "kubeAPIQPS", "kubeAPIBurst", "serializeImagePulls", "outOfDiskTransitionFrequency", "nodeIP", "nodeLabels", "nonMasqueradeCIDR", "enableCustomMetrics", "evictionHard", "evictionSoft", "evictionSoftGracePeriod", "evictionPressureTransitionPeriod", "evictionMaxPodGracePeriod", "evictionMinimumReclaim", "podsPerCore", "enableControllerAttachDetach", "systemReserved", "kubeReserved", "protectKernelDefaults", "makeIPTablesUtilChains", "iptablesMasqueradeBit", "iptablesDropBit", "featureGates"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Dependencies: []string{
|
Dependencies: []string{
|
||||||
|
|
|
@ -85,6 +85,8 @@ const (
|
||||||
// a string like feature1=true,feature2=false,...
|
// a string like feature1=true,feature2=false,...
|
||||||
type FeatureGate interface {
|
type FeatureGate interface {
|
||||||
AddFlag(fs *pflag.FlagSet)
|
AddFlag(fs *pflag.FlagSet)
|
||||||
|
Set(value string) error
|
||||||
|
KnownFeatures() []string
|
||||||
|
|
||||||
// Every feature gate should add method here following this template:
|
// Every feature gate should add method here following this template:
|
||||||
//
|
//
|
||||||
|
@ -104,7 +106,7 @@ type FeatureGate interface {
|
||||||
// alpha: v1.3
|
// alpha: v1.3
|
||||||
DynamicVolumeProvisioning() bool
|
DynamicVolumeProvisioning() bool
|
||||||
|
|
||||||
// owner: mtaufen
|
// owner: @mtaufen
|
||||||
// alpha: v1.4
|
// alpha: v1.4
|
||||||
DynamicKubeletConfig() bool
|
DynamicKubeletConfig() bool
|
||||||
}
|
}
|
||||||
|
@ -208,6 +210,14 @@ func (f *featureGate) lookup(key string) bool {
|
||||||
|
|
||||||
// AddFlag adds a flag for setting global feature gates to the specified FlagSet.
|
// AddFlag adds a flag for setting global feature gates to the specified FlagSet.
|
||||||
func (f *featureGate) AddFlag(fs *pflag.FlagSet) {
|
func (f *featureGate) AddFlag(fs *pflag.FlagSet) {
|
||||||
|
known := f.KnownFeatures()
|
||||||
|
fs.Var(f, flagName, ""+
|
||||||
|
"A set of key=value pairs that describe feature gates for alpha/experimental features. "+
|
||||||
|
"Options are:\n"+strings.Join(known, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a string describing the FeatureGate's known features.
|
||||||
|
func (f *featureGate) KnownFeatures() []string {
|
||||||
var known []string
|
var known []string
|
||||||
for k, v := range f.known {
|
for k, v := range f.known {
|
||||||
pre := ""
|
pre := ""
|
||||||
|
@ -217,7 +227,5 @@ func (f *featureGate) AddFlag(fs *pflag.FlagSet) {
|
||||||
known = append(known, fmt.Sprintf("%s=true|false (%sdefault=%t)", k, pre, v.enabled))
|
known = append(known, fmt.Sprintf("%s=true|false (%sdefault=%t)", k, pre, v.enabled))
|
||||||
}
|
}
|
||||||
sort.Strings(known)
|
sort.Strings(known)
|
||||||
fs.Var(f, flagName, ""+
|
return known
|
||||||
"A set of key=value pairs that describe feature gates for alpha/experimental features. "+
|
|
||||||
"Options are:\n"+strings.Join(known, "\n"))
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue