Moved flag and globalflag
Moved all flag code from `staging/src/k8s.io/apiserver/pkg/util/[flag|globalflag]` to `component-base/cli/[flag|globalflag]` except for the term function because of unwanted dependencies. Kubernetes-commit: 7744f908306e5131be5a94815ac76a7cba6454f2
This commit is contained in:
parent
c8e68b89a1
commit
c7299d9da0
|
|
@ -25,18 +25,18 @@ import (
|
|||
"k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/apiserver/pkg/server/resourceconfig"
|
||||
serverstore "k8s.io/apiserver/pkg/server/storage"
|
||||
utilflag "k8s.io/apiserver/pkg/util/flag"
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
)
|
||||
|
||||
// APIEnablementOptions contains the options for which resources to turn on and off.
|
||||
// Given small aggregated API servers, this option isn't required for "normal" API servers
|
||||
type APIEnablementOptions struct {
|
||||
RuntimeConfig utilflag.ConfigurationMap
|
||||
RuntimeConfig cliflag.ConfigurationMap
|
||||
}
|
||||
|
||||
func NewAPIEnablementOptions() *APIEnablementOptions {
|
||||
return &APIEnablementOptions{
|
||||
RuntimeConfig: make(utilflag.ConfigurationMap),
|
||||
RuntimeConfig: make(cliflag.ConfigurationMap),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import (
|
|||
"testing"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
utilflag "k8s.io/apiserver/pkg/util/flag"
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
)
|
||||
|
||||
type fakeGroupRegisty struct{}
|
||||
|
|
@ -42,28 +42,28 @@ func TestAPIEnablementOptionsValidate(t *testing.T) {
|
|||
{
|
||||
name: "test when invalid key with only api/all=false",
|
||||
testOptions: &APIEnablementOptions{
|
||||
RuntimeConfig: utilflag.ConfigurationMap{"api/all": "false"},
|
||||
RuntimeConfig: cliflag.ConfigurationMap{"api/all": "false"},
|
||||
},
|
||||
expectErr: "invalid key with only api/all=false",
|
||||
},
|
||||
{
|
||||
name: "test when ConfigurationMap key is invalid",
|
||||
testOptions: &APIEnablementOptions{
|
||||
RuntimeConfig: utilflag.ConfigurationMap{"apiall": "false"},
|
||||
RuntimeConfig: cliflag.ConfigurationMap{"apiall": "false"},
|
||||
},
|
||||
expectErr: "runtime-config invalid key",
|
||||
},
|
||||
{
|
||||
name: "test when unknown api groups",
|
||||
testOptions: &APIEnablementOptions{
|
||||
RuntimeConfig: utilflag.ConfigurationMap{"api/v1": "true"},
|
||||
RuntimeConfig: cliflag.ConfigurationMap{"api/v1": "true"},
|
||||
},
|
||||
expectErr: "unknown api groups",
|
||||
},
|
||||
{
|
||||
name: "test when valid api groups",
|
||||
testOptions: &APIEnablementOptions{
|
||||
RuntimeConfig: utilflag.ConfigurationMap{"apiregistration.k8s.io/v1beta1": "true"},
|
||||
RuntimeConfig: cliflag.ConfigurationMap{"apiregistration.k8s.io/v1beta1": "true"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ import (
|
|||
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apiserver/pkg/server"
|
||||
utilflag "k8s.io/apiserver/pkg/util/flag"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
)
|
||||
|
||||
type SecureServingOptions struct {
|
||||
|
|
@ -54,7 +54,7 @@ type SecureServingOptions struct {
|
|||
// ServerCert is the TLS cert info for serving secure traffic
|
||||
ServerCert GeneratableKeyCert
|
||||
// SNICertKeys are named CertKeys for serving secure traffic with SNI support.
|
||||
SNICertKeys []utilflag.NamedCertKey
|
||||
SNICertKeys []cliflag.NamedCertKey
|
||||
// CipherSuites is the list of allowed cipher suites for the server.
|
||||
// Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants).
|
||||
CipherSuites []string
|
||||
|
|
@ -165,18 +165,18 @@ func (s *SecureServingOptions) AddFlags(fs *pflag.FlagSet) {
|
|||
fs.StringVar(&s.ServerCert.CertKey.KeyFile, "tls-private-key-file", s.ServerCert.CertKey.KeyFile,
|
||||
"File containing the default x509 private key matching --tls-cert-file.")
|
||||
|
||||
tlsCipherPossibleValues := utilflag.TLSCipherPossibleValues()
|
||||
tlsCipherPossibleValues := cliflag.TLSCipherPossibleValues()
|
||||
fs.StringSliceVar(&s.CipherSuites, "tls-cipher-suites", s.CipherSuites,
|
||||
"Comma-separated list of cipher suites for the server. "+
|
||||
"If omitted, the default Go cipher suites will be use. "+
|
||||
"Possible values: "+strings.Join(tlsCipherPossibleValues, ","))
|
||||
|
||||
tlsPossibleVersions := utilflag.TLSPossibleVersions()
|
||||
tlsPossibleVersions := cliflag.TLSPossibleVersions()
|
||||
fs.StringVar(&s.MinTLSVersion, "tls-min-version", s.MinTLSVersion,
|
||||
"Minimum TLS version supported. "+
|
||||
"Possible values: "+strings.Join(tlsPossibleVersions, ", "))
|
||||
|
||||
fs.Var(utilflag.NewNamedCertKeyArray(&s.SNICertKeys), "tls-sni-cert-key", ""+
|
||||
fs.Var(cliflag.NewNamedCertKeyArray(&s.SNICertKeys), "tls-sni-cert-key", ""+
|
||||
"A pair of x509 certificate and private key file paths, optionally suffixed with a list of "+
|
||||
"domain patterns which are fully qualified domain names, possibly with prefixed wildcard "+
|
||||
"segments. If no domain patterns are provided, the names of the certificate are "+
|
||||
|
|
@ -234,7 +234,7 @@ func (s *SecureServingOptions) ApplyTo(config **server.SecureServingInfo) error
|
|||
}
|
||||
|
||||
if len(s.CipherSuites) != 0 {
|
||||
cipherSuites, err := utilflag.TLSCipherSuites(s.CipherSuites)
|
||||
cipherSuites, err := cliflag.TLSCipherSuites(s.CipherSuites)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -242,7 +242,7 @@ func (s *SecureServingOptions) ApplyTo(config **server.SecureServingInfo) error
|
|||
}
|
||||
|
||||
var err error
|
||||
c.MinTLSVersion, err = utilflag.TLSVersion(s.MinTLSVersion)
|
||||
c.MinTLSVersion, err = cliflag.TLSVersion(s.MinTLSVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,9 +44,9 @@ import (
|
|||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/apiserver/pkg/server"
|
||||
. "k8s.io/apiserver/pkg/server"
|
||||
utilflag "k8s.io/apiserver/pkg/util/flag"
|
||||
"k8s.io/client-go/discovery"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
)
|
||||
|
||||
func setUp(t *testing.T) Config {
|
||||
|
|
@ -417,7 +417,7 @@ func TestServerRunWithSNI(t *testing.T) {
|
|||
caCerts := []*x509.Certificate{ca}
|
||||
|
||||
// create SNI certs
|
||||
var namedCertKeys []utilflag.NamedCertKey
|
||||
var namedCertKeys []cliflag.NamedCertKey
|
||||
serverSig, err := certFileSignature(serverCertBundleFile, serverKeyFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get server cert signature: %v", err)
|
||||
|
|
@ -434,7 +434,7 @@ func TestServerRunWithSNI(t *testing.T) {
|
|||
t.Fatalf("failed to create SNI cert %d: %v", j, err)
|
||||
}
|
||||
|
||||
namedCertKeys = append(namedCertKeys, utilflag.NamedCertKey{
|
||||
namedCertKeys = append(namedCertKeys, cliflag.NamedCertKey{
|
||||
KeyFile: keyFile,
|
||||
CertFile: certBundleFile,
|
||||
Names: c.explicitNames,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
serverstore "k8s.io/apiserver/pkg/server/storage"
|
||||
utilflag "k8s.io/apiserver/pkg/util/flag"
|
||||
cliflag "k8s.io/component-base/cli/flag"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ func MergeResourceEncodingConfigs(
|
|||
// not registered in group, then it will fail.
|
||||
func MergeAPIResourceConfigs(
|
||||
defaultAPIResourceConfig *serverstore.ResourceConfig,
|
||||
resourceConfigOverrides utilflag.ConfigurationMap,
|
||||
resourceConfigOverrides cliflag.ConfigurationMap,
|
||||
registry GroupVersionRegistry,
|
||||
) (*serverstore.ResourceConfig, error) {
|
||||
resourceConfig := defaultAPIResourceConfig
|
||||
|
|
@ -134,7 +134,7 @@ func MergeAPIResourceConfigs(
|
|||
return resourceConfig, nil
|
||||
}
|
||||
|
||||
func getRuntimeConfigValue(overrides utilflag.ConfigurationMap, apiKey string, defaultValue bool) (bool, error) {
|
||||
func getRuntimeConfigValue(overrides cliflag.ConfigurationMap, apiKey string, defaultValue bool) (bool, error) {
|
||||
flagValue, ok := overrides[apiKey]
|
||||
if ok {
|
||||
if flagValue == "" {
|
||||
|
|
@ -150,7 +150,7 @@ func getRuntimeConfigValue(overrides utilflag.ConfigurationMap, apiKey string, d
|
|||
}
|
||||
|
||||
// ParseGroups takes in resourceConfig and returns parsed groups.
|
||||
func ParseGroups(resourceConfig utilflag.ConfigurationMap) ([]string, error) {
|
||||
func ParseGroups(resourceConfig cliflag.ConfigurationMap) ([]string, error) {
|
||||
groups := []string{}
|
||||
for key := range resourceConfig {
|
||||
if key == "api/all" {
|
||||
|
|
|
|||
|
|
@ -1,105 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// ciphers maps strings into tls package cipher constants in
|
||||
// https://golang.org/pkg/crypto/tls/#pkg-constants
|
||||
var ciphers = map[string]uint16{
|
||||
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
|
||||
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
}
|
||||
|
||||
func TLSCipherPossibleValues() []string {
|
||||
cipherKeys := sets.NewString()
|
||||
for key := range ciphers {
|
||||
cipherKeys.Insert(key)
|
||||
}
|
||||
return cipherKeys.List()
|
||||
}
|
||||
|
||||
func TLSCipherSuites(cipherNames []string) ([]uint16, error) {
|
||||
if len(cipherNames) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
ciphersIntSlice := make([]uint16, 0)
|
||||
for _, cipher := range cipherNames {
|
||||
intValue, ok := ciphers[cipher]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Cipher suite %s not supported or doesn't exist", cipher)
|
||||
}
|
||||
ciphersIntSlice = append(ciphersIntSlice, intValue)
|
||||
}
|
||||
return ciphersIntSlice, nil
|
||||
}
|
||||
|
||||
var versions = map[string]uint16{
|
||||
"VersionTLS10": tls.VersionTLS10,
|
||||
"VersionTLS11": tls.VersionTLS11,
|
||||
"VersionTLS12": tls.VersionTLS12,
|
||||
}
|
||||
|
||||
func TLSPossibleVersions() []string {
|
||||
versionsKeys := sets.NewString()
|
||||
for key := range versions {
|
||||
versionsKeys.Insert(key)
|
||||
}
|
||||
return versionsKeys.List()
|
||||
}
|
||||
|
||||
func TLSVersion(versionName string) (uint16, error) {
|
||||
if len(versionName) == 0 {
|
||||
return DefaultTLSVersion(), nil
|
||||
}
|
||||
if version, ok := versions[versionName]; ok {
|
||||
return version, nil
|
||||
}
|
||||
return 0, fmt.Errorf("unknown tls version %q", versionName)
|
||||
}
|
||||
|
||||
func DefaultTLSVersion() uint16 {
|
||||
// Can't use SSLv3 because of POODLE and BEAST
|
||||
// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
|
||||
// Can't use TLSv1.1 because of RC4 cipher usage
|
||||
return tls.VersionTLS12
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"go/importer"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStrToUInt16(t *testing.T) {
|
||||
tests := []struct {
|
||||
flag []string
|
||||
expected []uint16
|
||||
expected_error bool
|
||||
}{
|
||||
{
|
||||
// Happy case
|
||||
flag: []string{"TLS_RSA_WITH_RC4_128_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_RC4_128_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"},
|
||||
expected: []uint16{tls.TLS_RSA_WITH_RC4_128_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
|
||||
expected_error: false,
|
||||
},
|
||||
{
|
||||
// One flag only
|
||||
flag: []string{"TLS_RSA_WITH_RC4_128_SHA"},
|
||||
expected: []uint16{tls.TLS_RSA_WITH_RC4_128_SHA},
|
||||
expected_error: false,
|
||||
},
|
||||
{
|
||||
// Empty flag
|
||||
flag: []string{},
|
||||
expected: nil,
|
||||
expected_error: false,
|
||||
},
|
||||
{
|
||||
// Duplicated flag
|
||||
flag: []string{"TLS_RSA_WITH_RC4_128_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_RC4_128_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_RC4_128_SHA"},
|
||||
expected: []uint16{tls.TLS_RSA_WITH_RC4_128_SHA, tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_RC4_128_SHA},
|
||||
expected_error: false,
|
||||
},
|
||||
{
|
||||
// Invalid flag
|
||||
flag: []string{"foo"},
|
||||
expected: nil,
|
||||
expected_error: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
uIntFlags, err := TLSCipherSuites(test.flag)
|
||||
if reflect.DeepEqual(uIntFlags, test.expected) == false {
|
||||
t.Errorf("%d: expected %+v, got %+v", i, test.expected, uIntFlags)
|
||||
}
|
||||
if test.expected_error && err == nil {
|
||||
t.Errorf("%d: expecting error, got %+v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstantMaps(t *testing.T) {
|
||||
pkg, err := importer.Default().Import("crypto/tls")
|
||||
if err != nil {
|
||||
fmt.Printf("error: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
discoveredVersions := map[string]bool{}
|
||||
discoveredCiphers := map[string]bool{}
|
||||
for _, declName := range pkg.Scope().Names() {
|
||||
if strings.HasPrefix(declName, "VersionTLS") {
|
||||
discoveredVersions[declName] = true
|
||||
}
|
||||
if strings.HasPrefix(declName, "TLS_RSA_") || strings.HasPrefix(declName, "TLS_ECDHE_") {
|
||||
discoveredCiphers[declName] = true
|
||||
}
|
||||
}
|
||||
|
||||
for k := range discoveredCiphers {
|
||||
if _, ok := ciphers[k]; !ok {
|
||||
t.Errorf("discovered cipher tls.%s not in ciphers map", k)
|
||||
}
|
||||
}
|
||||
for k := range ciphers {
|
||||
if _, ok := discoveredCiphers[k]; !ok {
|
||||
t.Errorf("ciphers map has %s not in tls package", k)
|
||||
}
|
||||
}
|
||||
for k := range discoveredVersions {
|
||||
if _, ok := versions[k]; !ok {
|
||||
t.Errorf("discovered version tls.%s not in version map", k)
|
||||
}
|
||||
}
|
||||
for k := range versions {
|
||||
if _, ok := discoveredVersions[k]; !ok {
|
||||
t.Errorf("versions map has %s not in tls package", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ColonSeparatedMultimapStringString supports setting a map[string][]string from an encoding
|
||||
// that separates keys from values with ':' and separates key-value pairs with ','.
|
||||
// A key can be repeated multiple times, in which case the values are appended to a
|
||||
// slice of strings associated with that key. Items in the list associated with a given
|
||||
// key will appear in the order provided.
|
||||
// For example: `a:hello,b:again,c:world,b:beautiful` results in `{"a": ["hello"], "b": ["again", "beautiful"], "c": ["world"]}`
|
||||
// The first call to Set will clear the map before adding entries; subsequent calls will simply append to the map.
|
||||
// This makes it possible to override default values with a command-line option rather than appending to defaults,
|
||||
// while still allowing the distribution of key-value pairs across multiple flag invocations.
|
||||
// For example: `--flag "a:hello" --flag "b:again" --flag "b:beautiful" --flag "c:world"` results in `{"a": ["hello"], "b": ["again", "beautiful"], "c": ["world"]}`
|
||||
type ColonSeparatedMultimapStringString struct {
|
||||
Multimap *map[string][]string
|
||||
initialized bool // set to true after the first Set call
|
||||
}
|
||||
|
||||
// NewColonSeparatedMultimapStringString takes a pointer to a map[string][]string and returns the
|
||||
// ColonSeparatedMultimapStringString flag parsing shim for that map.
|
||||
func NewColonSeparatedMultimapStringString(m *map[string][]string) *ColonSeparatedMultimapStringString {
|
||||
return &ColonSeparatedMultimapStringString{Multimap: m}
|
||||
}
|
||||
|
||||
// Set implements github.com/spf13/pflag.Value
|
||||
func (m *ColonSeparatedMultimapStringString) Set(value string) error {
|
||||
if m.Multimap == nil {
|
||||
return fmt.Errorf("no target (nil pointer to map[string][]string)")
|
||||
}
|
||||
if !m.initialized || *m.Multimap == nil {
|
||||
// clear default values, or allocate if no existing map
|
||||
*m.Multimap = make(map[string][]string)
|
||||
m.initialized = true
|
||||
}
|
||||
for _, pair := range strings.Split(value, ",") {
|
||||
if len(pair) == 0 {
|
||||
continue
|
||||
}
|
||||
kv := strings.SplitN(pair, ":", 2)
|
||||
if len(kv) != 2 {
|
||||
return fmt.Errorf("malformed pair, expect string:string")
|
||||
}
|
||||
k := strings.TrimSpace(kv[0])
|
||||
v := strings.TrimSpace(kv[1])
|
||||
(*m.Multimap)[k] = append((*m.Multimap)[k], v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements github.com/spf13/pflag.Value
|
||||
func (m *ColonSeparatedMultimapStringString) String() string {
|
||||
type kv struct {
|
||||
k string
|
||||
v string
|
||||
}
|
||||
kvs := make([]kv, 0, len(*m.Multimap))
|
||||
for k, vs := range *m.Multimap {
|
||||
for i := range vs {
|
||||
kvs = append(kvs, kv{k: k, v: vs[i]})
|
||||
}
|
||||
}
|
||||
// stable sort by keys, order of values should be preserved
|
||||
sort.SliceStable(kvs, func(i, j int) bool {
|
||||
return kvs[i].k < kvs[j].k
|
||||
})
|
||||
pairs := make([]string, 0, len(kvs))
|
||||
for i := range kvs {
|
||||
pairs = append(pairs, fmt.Sprintf("%s:%s", kvs[i].k, kvs[i].v))
|
||||
}
|
||||
return strings.Join(pairs, ",")
|
||||
}
|
||||
|
||||
// Type implements github.com/spf13/pflag.Value
|
||||
func (m *ColonSeparatedMultimapStringString) Type() string {
|
||||
return "colonSeparatedMultimapStringString"
|
||||
}
|
||||
|
||||
// Empty implements OmitEmpty
|
||||
func (m *ColonSeparatedMultimapStringString) Empty() bool {
|
||||
return len(*m.Multimap) == 0
|
||||
}
|
||||
|
|
@ -1,242 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringColonSeparatedMultimapStringString(t *testing.T) {
|
||||
var nilMap map[string][]string
|
||||
cases := []struct {
|
||||
desc string
|
||||
m *ColonSeparatedMultimapStringString
|
||||
expect string
|
||||
}{
|
||||
{"nil", NewColonSeparatedMultimapStringString(&nilMap), ""},
|
||||
{"empty", NewColonSeparatedMultimapStringString(&map[string][]string{}), ""},
|
||||
{"empty key", NewColonSeparatedMultimapStringString(
|
||||
&map[string][]string{
|
||||
"": {"foo"},
|
||||
}),
|
||||
":foo"},
|
||||
{"one key", NewColonSeparatedMultimapStringString(
|
||||
&map[string][]string{
|
||||
"one": {"foo"},
|
||||
}),
|
||||
"one:foo"},
|
||||
{"two keys", NewColonSeparatedMultimapStringString(
|
||||
&map[string][]string{
|
||||
"one": {"foo"},
|
||||
"two": {"bar"},
|
||||
}),
|
||||
"one:foo,two:bar"},
|
||||
{"two keys, multiple items in one key", NewColonSeparatedMultimapStringString(
|
||||
&map[string][]string{
|
||||
"one": {"foo", "baz"},
|
||||
"two": {"bar"},
|
||||
}),
|
||||
"one:foo,one:baz,two:bar"},
|
||||
{"three keys, multiple items in one key", NewColonSeparatedMultimapStringString(
|
||||
&map[string][]string{
|
||||
"a": {"hello"},
|
||||
"b": {"again", "beautiful"},
|
||||
"c": {"world"},
|
||||
}),
|
||||
"a:hello,b:again,b:beautiful,c:world"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
str := c.m.String()
|
||||
if c.expect != str {
|
||||
t.Fatalf("expect %q but got %q", c.expect, str)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetColonSeparatedMultimapStringString(t *testing.T) {
|
||||
var nilMap map[string][]string
|
||||
cases := []struct {
|
||||
desc string
|
||||
vals []string
|
||||
start *ColonSeparatedMultimapStringString
|
||||
expect *ColonSeparatedMultimapStringString
|
||||
err string
|
||||
}{
|
||||
// we initialize the map with a default key that should be cleared by Set
|
||||
{"clears defaults", []string{""},
|
||||
NewColonSeparatedMultimapStringString(&map[string][]string{"default": {}}),
|
||||
&ColonSeparatedMultimapStringString{
|
||||
initialized: true,
|
||||
Multimap: &map[string][]string{}}, ""},
|
||||
// make sure we still allocate for "initialized" multimaps where Multimap was initially set to a nil map
|
||||
{"allocates map if currently nil", []string{""},
|
||||
&ColonSeparatedMultimapStringString{initialized: true, Multimap: &nilMap},
|
||||
&ColonSeparatedMultimapStringString{
|
||||
initialized: true,
|
||||
Multimap: &map[string][]string{},
|
||||
}, ""},
|
||||
// for most cases, we just reuse nilMap, which should be allocated by Set, and is reset before each test case
|
||||
{"empty", []string{""},
|
||||
NewColonSeparatedMultimapStringString(&nilMap),
|
||||
&ColonSeparatedMultimapStringString{
|
||||
initialized: true,
|
||||
Multimap: &map[string][]string{}}, ""},
|
||||
{"empty key", []string{":foo"},
|
||||
NewColonSeparatedMultimapStringString(&nilMap),
|
||||
&ColonSeparatedMultimapStringString{
|
||||
initialized: true,
|
||||
Multimap: &map[string][]string{
|
||||
"": {"foo"},
|
||||
}}, ""},
|
||||
{"one key", []string{"one:foo"},
|
||||
NewColonSeparatedMultimapStringString(&nilMap),
|
||||
&ColonSeparatedMultimapStringString{
|
||||
initialized: true,
|
||||
Multimap: &map[string][]string{
|
||||
"one": {"foo"},
|
||||
}}, ""},
|
||||
{"two keys", []string{"one:foo,two:bar"},
|
||||
NewColonSeparatedMultimapStringString(&nilMap),
|
||||
&ColonSeparatedMultimapStringString{
|
||||
initialized: true,
|
||||
Multimap: &map[string][]string{
|
||||
"one": {"foo"},
|
||||
"two": {"bar"},
|
||||
}}, ""},
|
||||
{"two keys with space", []string{"one:foo, two:bar"},
|
||||
NewColonSeparatedMultimapStringString(&nilMap),
|
||||
&ColonSeparatedMultimapStringString{
|
||||
initialized: true,
|
||||
Multimap: &map[string][]string{
|
||||
"one": {"foo"},
|
||||
"two": {"bar"},
|
||||
}}, ""},
|
||||
{"two keys, multiple items in one key", []string{"one: foo, two:bar, one:baz"},
|
||||
NewColonSeparatedMultimapStringString(&nilMap),
|
||||
&ColonSeparatedMultimapStringString{
|
||||
initialized: true,
|
||||
Multimap: &map[string][]string{
|
||||
"one": {"foo", "baz"},
|
||||
"two": {"bar"},
|
||||
}}, ""},
|
||||
{"three keys, multiple items in one key", []string{"a:hello,b:again,c:world,b:beautiful"},
|
||||
NewColonSeparatedMultimapStringString(&nilMap),
|
||||
&ColonSeparatedMultimapStringString{
|
||||
initialized: true,
|
||||
Multimap: &map[string][]string{
|
||||
"a": {"hello"},
|
||||
"b": {"again", "beautiful"},
|
||||
"c": {"world"},
|
||||
}}, ""},
|
||||
{"three keys, multiple items in one key, multiple Set invocations", []string{"a:hello,b:again", "c:world", "b:beautiful"},
|
||||
NewColonSeparatedMultimapStringString(&nilMap),
|
||||
&ColonSeparatedMultimapStringString{
|
||||
initialized: true,
|
||||
Multimap: &map[string][]string{
|
||||
"a": {"hello"},
|
||||
"b": {"again", "beautiful"},
|
||||
"c": {"world"},
|
||||
}}, ""},
|
||||
{"missing value", []string{"a"},
|
||||
NewColonSeparatedMultimapStringString(&nilMap),
|
||||
nil,
|
||||
"malformed pair, expect string:string"},
|
||||
{"no target", []string{"a:foo"},
|
||||
NewColonSeparatedMultimapStringString(nil),
|
||||
nil,
|
||||
"no target (nil pointer to map[string][]string)"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
nilMap = nil
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
var err error
|
||||
for _, val := range c.vals {
|
||||
err = c.start.Set(val)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if c.err != "" {
|
||||
if err == nil || err.Error() != c.err {
|
||||
t.Fatalf("expect error %s but got %v", c.err, err)
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(c.expect, c.start) {
|
||||
t.Fatalf("expect %#v but got %#v", c.expect, c.start)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTripColonSeparatedMultimapStringString(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
vals []string
|
||||
expect string
|
||||
}{
|
||||
{"empty", []string{""}, ""},
|
||||
{"empty key", []string{":foo"}, ":foo"},
|
||||
{"one key", []string{"one:foo"}, "one:foo"},
|
||||
{"two keys", []string{"one:foo,two:bar"}, "one:foo,two:bar"},
|
||||
{"two keys, multiple items in one key", []string{"one:foo, two:bar, one:baz"}, "one:foo,one:baz,two:bar"},
|
||||
{"three keys, multiple items in one key", []string{"a:hello,b:again,c:world,b:beautiful"}, "a:hello,b:again,b:beautiful,c:world"},
|
||||
{"three keys, multiple items in one key, multiple Set invocations", []string{"a:hello,b:again", "c:world", "b:beautiful"}, "a:hello,b:again,b:beautiful,c:world"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
m := NewColonSeparatedMultimapStringString(&map[string][]string{})
|
||||
for _, val := range c.vals {
|
||||
if err := m.Set(val); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
str := m.String()
|
||||
if c.expect != str {
|
||||
t.Fatalf("expect %q but got %q", c.expect, str)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyColonSeparatedMultimapStringString(t *testing.T) {
|
||||
var nilMap map[string][]string
|
||||
cases := []struct {
|
||||
desc string
|
||||
val *ColonSeparatedMultimapStringString
|
||||
expect bool
|
||||
}{
|
||||
{"nil", NewColonSeparatedMultimapStringString(&nilMap), true},
|
||||
{"empty", NewColonSeparatedMultimapStringString(&map[string][]string{}), true},
|
||||
{"populated", NewColonSeparatedMultimapStringString(&map[string][]string{"foo": {}}), false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
result := c.val.Empty()
|
||||
if result != c.expect {
|
||||
t.Fatalf("expect %t but got %t", c.expect, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ConfigurationMap map[string]string
|
||||
|
||||
func (m *ConfigurationMap) String() string {
|
||||
pairs := []string{}
|
||||
for k, v := range *m {
|
||||
pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
sort.Strings(pairs)
|
||||
return strings.Join(pairs, ",")
|
||||
}
|
||||
|
||||
func (m *ConfigurationMap) Set(value string) error {
|
||||
for _, s := range strings.Split(value, ",") {
|
||||
if len(s) == 0 {
|
||||
continue
|
||||
}
|
||||
arr := strings.SplitN(s, "=", 2)
|
||||
if len(arr) == 2 {
|
||||
(*m)[strings.TrimSpace(arr[0])] = strings.TrimSpace(arr[1])
|
||||
} else {
|
||||
(*m)[strings.TrimSpace(arr[0])] = ""
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*ConfigurationMap) Type() string {
|
||||
return "mapStringString"
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
goflag "flag"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// WordSepNormalizeFunc changes all flags that contain "_" separators
|
||||
func WordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
if strings.Contains(name, "_") {
|
||||
return pflag.NormalizedName(strings.Replace(name, "_", "-", -1))
|
||||
}
|
||||
return pflag.NormalizedName(name)
|
||||
}
|
||||
|
||||
// WarnWordSepNormalizeFunc changes and warns for flags that contain "_" separators
|
||||
func WarnWordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
if strings.Contains(name, "_") {
|
||||
nname := strings.Replace(name, "_", "-", -1)
|
||||
klog.Warningf("%s is DEPRECATED and will be removed in a future version. Use %s instead.", name, nname)
|
||||
|
||||
return pflag.NormalizedName(nname)
|
||||
}
|
||||
return pflag.NormalizedName(name)
|
||||
}
|
||||
|
||||
// InitFlags normalizes, parses, then logs the command line flags
|
||||
func InitFlags() {
|
||||
pflag.CommandLine.SetNormalizeFunc(WordSepNormalizeFunc)
|
||||
pflag.CommandLine.AddGoFlagSet(goflag.CommandLine)
|
||||
pflag.Parse()
|
||||
pflag.VisitAll(func(flag *pflag.Flag) {
|
||||
klog.V(2).Infof("FLAG: --%s=%q", flag.Name, flag.Value)
|
||||
})
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LangleSeparatedMapStringString can be set from the command line with the format `--flag "string<string"`.
|
||||
// Multiple comma-separated key-value pairs in a single invocation are supported. For example: `--flag "a<foo,b<bar"`.
|
||||
// Multiple flag invocations are supported. For example: `--flag "a<foo" --flag "b<foo"`.
|
||||
type LangleSeparatedMapStringString struct {
|
||||
Map *map[string]string
|
||||
initialized bool // set to true after first Set call
|
||||
}
|
||||
|
||||
// NewLangleSeparatedMapStringString takes a pointer to a map[string]string and returns the
|
||||
// LangleSeparatedMapStringString flag parsing shim for that map
|
||||
func NewLangleSeparatedMapStringString(m *map[string]string) *LangleSeparatedMapStringString {
|
||||
return &LangleSeparatedMapStringString{Map: m}
|
||||
}
|
||||
|
||||
// String implements github.com/spf13/pflag.Value
|
||||
func (m *LangleSeparatedMapStringString) String() string {
|
||||
pairs := []string{}
|
||||
for k, v := range *m.Map {
|
||||
pairs = append(pairs, fmt.Sprintf("%s<%s", k, v))
|
||||
}
|
||||
sort.Strings(pairs)
|
||||
return strings.Join(pairs, ",")
|
||||
}
|
||||
|
||||
// Set implements github.com/spf13/pflag.Value
|
||||
func (m *LangleSeparatedMapStringString) Set(value string) error {
|
||||
if m.Map == nil {
|
||||
return fmt.Errorf("no target (nil pointer to map[string]string)")
|
||||
}
|
||||
if !m.initialized || *m.Map == nil {
|
||||
// clear default values, or allocate if no existing map
|
||||
*m.Map = make(map[string]string)
|
||||
m.initialized = true
|
||||
}
|
||||
for _, s := range strings.Split(value, ",") {
|
||||
if len(s) == 0 {
|
||||
continue
|
||||
}
|
||||
arr := strings.SplitN(s, "<", 2)
|
||||
if len(arr) != 2 {
|
||||
return fmt.Errorf("malformed pair, expect string<string")
|
||||
}
|
||||
k := strings.TrimSpace(arr[0])
|
||||
v := strings.TrimSpace(arr[1])
|
||||
(*m.Map)[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type implements github.com/spf13/pflag.Value
|
||||
func (*LangleSeparatedMapStringString) Type() string {
|
||||
return "mapStringString"
|
||||
}
|
||||
|
||||
// Empty implements OmitEmpty
|
||||
func (m *LangleSeparatedMapStringString) Empty() bool {
|
||||
return len(*m.Map) == 0
|
||||
}
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringLangleSeparatedMapStringString(t *testing.T) {
|
||||
var nilMap map[string]string
|
||||
cases := []struct {
|
||||
desc string
|
||||
m *LangleSeparatedMapStringString
|
||||
expect string
|
||||
}{
|
||||
{"nil", NewLangleSeparatedMapStringString(&nilMap), ""},
|
||||
{"empty", NewLangleSeparatedMapStringString(&map[string]string{}), ""},
|
||||
{"one key", NewLangleSeparatedMapStringString(&map[string]string{"one": "foo"}), "one<foo"},
|
||||
{"two keys", NewLangleSeparatedMapStringString(&map[string]string{"one": "foo", "two": "bar"}), "one<foo,two<bar"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
str := c.m.String()
|
||||
if c.expect != str {
|
||||
t.Fatalf("expect %q but got %q", c.expect, str)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetLangleSeparatedMapStringString(t *testing.T) {
|
||||
var nilMap map[string]string
|
||||
cases := []struct {
|
||||
desc string
|
||||
vals []string
|
||||
start *LangleSeparatedMapStringString
|
||||
expect *LangleSeparatedMapStringString
|
||||
err string
|
||||
}{
|
||||
// we initialize the map with a default key that should be cleared by Set
|
||||
{"clears defaults", []string{""},
|
||||
NewLangleSeparatedMapStringString(&map[string]string{"default": ""}),
|
||||
&LangleSeparatedMapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{},
|
||||
}, ""},
|
||||
// make sure we still allocate for "initialized" maps where Map was initially set to a nil map
|
||||
{"allocates map if currently nil", []string{""},
|
||||
&LangleSeparatedMapStringString{initialized: true, Map: &nilMap},
|
||||
&LangleSeparatedMapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{},
|
||||
}, ""},
|
||||
// for most cases, we just reuse nilMap, which should be allocated by Set, and is reset before each test case
|
||||
{"empty", []string{""},
|
||||
NewLangleSeparatedMapStringString(&nilMap),
|
||||
&LangleSeparatedMapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{},
|
||||
}, ""},
|
||||
{"one key", []string{"one<foo"},
|
||||
NewLangleSeparatedMapStringString(&nilMap),
|
||||
&LangleSeparatedMapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{"one": "foo"},
|
||||
}, ""},
|
||||
{"two keys", []string{"one<foo,two<bar"},
|
||||
NewLangleSeparatedMapStringString(&nilMap),
|
||||
&LangleSeparatedMapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{"one": "foo", "two": "bar"},
|
||||
}, ""},
|
||||
{"two keys, multiple Set invocations", []string{"one<foo", "two<bar"},
|
||||
NewLangleSeparatedMapStringString(&nilMap),
|
||||
&LangleSeparatedMapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{"one": "foo", "two": "bar"},
|
||||
}, ""},
|
||||
{"two keys with space", []string{"one<foo, two<bar"},
|
||||
NewLangleSeparatedMapStringString(&nilMap),
|
||||
&LangleSeparatedMapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{"one": "foo", "two": "bar"},
|
||||
}, ""},
|
||||
{"empty key", []string{"<foo"},
|
||||
NewLangleSeparatedMapStringString(&nilMap),
|
||||
&LangleSeparatedMapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{"": "foo"},
|
||||
}, ""},
|
||||
{"missing value", []string{"one"},
|
||||
NewLangleSeparatedMapStringString(&nilMap),
|
||||
nil,
|
||||
"malformed pair, expect string<string"},
|
||||
{"no target", []string{"a:foo"},
|
||||
NewLangleSeparatedMapStringString(nil),
|
||||
nil,
|
||||
"no target (nil pointer to map[string]string)"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
nilMap = nil
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
var err error
|
||||
for _, val := range c.vals {
|
||||
err = c.start.Set(val)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if c.err != "" {
|
||||
if err == nil || err.Error() != c.err {
|
||||
t.Fatalf("expect error %s but got %v", c.err, err)
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(c.expect, c.start) {
|
||||
t.Fatalf("expect %#v but got %#v", c.expect, c.start)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyLangleSeparatedMapStringString(t *testing.T) {
|
||||
var nilMap map[string]string
|
||||
cases := []struct {
|
||||
desc string
|
||||
val *LangleSeparatedMapStringString
|
||||
expect bool
|
||||
}{
|
||||
{"nil", NewLangleSeparatedMapStringString(&nilMap), true},
|
||||
{"empty", NewLangleSeparatedMapStringString(&map[string]string{}), true},
|
||||
{"populated", NewLangleSeparatedMapStringString(&map[string]string{"foo": ""}), false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
result := c.val.Empty()
|
||||
if result != c.expect {
|
||||
t.Fatalf("expect %t but got %t", c.expect, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MapStringBool can be set from the command line with the format `--flag "string=bool"`.
|
||||
// Multiple comma-separated key-value pairs in a single invocation are supported. For example: `--flag "a=true,b=false"`.
|
||||
// Multiple flag invocations are supported. For example: `--flag "a=true" --flag "b=false"`.
|
||||
type MapStringBool struct {
|
||||
Map *map[string]bool
|
||||
initialized bool
|
||||
}
|
||||
|
||||
// NewMapStringBool takes a pointer to a map[string]string and returns the
|
||||
// MapStringBool flag parsing shim for that map
|
||||
func NewMapStringBool(m *map[string]bool) *MapStringBool {
|
||||
return &MapStringBool{Map: m}
|
||||
}
|
||||
|
||||
// String implements github.com/spf13/pflag.Value
|
||||
func (m *MapStringBool) String() string {
|
||||
if m == nil || m.Map == nil {
|
||||
return ""
|
||||
}
|
||||
pairs := []string{}
|
||||
for k, v := range *m.Map {
|
||||
pairs = append(pairs, fmt.Sprintf("%s=%t", k, v))
|
||||
}
|
||||
sort.Strings(pairs)
|
||||
return strings.Join(pairs, ",")
|
||||
}
|
||||
|
||||
// Set implements github.com/spf13/pflag.Value
|
||||
func (m *MapStringBool) Set(value string) error {
|
||||
if m.Map == nil {
|
||||
return fmt.Errorf("no target (nil pointer to map[string]bool)")
|
||||
}
|
||||
if !m.initialized || *m.Map == nil {
|
||||
// clear default values, or allocate if no existing map
|
||||
*m.Map = make(map[string]bool)
|
||||
m.initialized = true
|
||||
}
|
||||
for _, s := range strings.Split(value, ",") {
|
||||
if len(s) == 0 {
|
||||
continue
|
||||
}
|
||||
arr := strings.SplitN(s, "=", 2)
|
||||
if len(arr) != 2 {
|
||||
return fmt.Errorf("malformed pair, expect string=bool")
|
||||
}
|
||||
k := strings.TrimSpace(arr[0])
|
||||
v := strings.TrimSpace(arr[1])
|
||||
boolValue, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value of %s: %s, err: %v", k, v, err)
|
||||
}
|
||||
(*m.Map)[k] = boolValue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type implements github.com/spf13/pflag.Value
|
||||
func (*MapStringBool) Type() string {
|
||||
return "mapStringBool"
|
||||
}
|
||||
|
||||
// Empty implements OmitEmpty
|
||||
func (m *MapStringBool) Empty() bool {
|
||||
return len(*m.Map) == 0
|
||||
}
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringMapStringBool(t *testing.T) {
|
||||
var nilMap map[string]bool
|
||||
cases := []struct {
|
||||
desc string
|
||||
m *MapStringBool
|
||||
expect string
|
||||
}{
|
||||
{"nil", NewMapStringBool(&nilMap), ""},
|
||||
{"empty", NewMapStringBool(&map[string]bool{}), ""},
|
||||
{"one key", NewMapStringBool(&map[string]bool{"one": true}), "one=true"},
|
||||
{"two keys", NewMapStringBool(&map[string]bool{"one": true, "two": false}), "one=true,two=false"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
str := c.m.String()
|
||||
if c.expect != str {
|
||||
t.Fatalf("expect %q but got %q", c.expect, str)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetMapStringBool(t *testing.T) {
|
||||
var nilMap map[string]bool
|
||||
cases := []struct {
|
||||
desc string
|
||||
vals []string
|
||||
start *MapStringBool
|
||||
expect *MapStringBool
|
||||
err string
|
||||
}{
|
||||
// we initialize the map with a default key that should be cleared by Set
|
||||
{"clears defaults", []string{""},
|
||||
NewMapStringBool(&map[string]bool{"default": true}),
|
||||
&MapStringBool{
|
||||
initialized: true,
|
||||
Map: &map[string]bool{},
|
||||
}, ""},
|
||||
// make sure we still allocate for "initialized" maps where Map was initially set to a nil map
|
||||
{"allocates map if currently nil", []string{""},
|
||||
&MapStringBool{initialized: true, Map: &nilMap},
|
||||
&MapStringBool{
|
||||
initialized: true,
|
||||
Map: &map[string]bool{},
|
||||
}, ""},
|
||||
// for most cases, we just reuse nilMap, which should be allocated by Set, and is reset before each test case
|
||||
{"empty", []string{""},
|
||||
NewMapStringBool(&nilMap),
|
||||
&MapStringBool{
|
||||
initialized: true,
|
||||
Map: &map[string]bool{},
|
||||
}, ""},
|
||||
{"one key", []string{"one=true"},
|
||||
NewMapStringBool(&nilMap),
|
||||
&MapStringBool{
|
||||
initialized: true,
|
||||
Map: &map[string]bool{"one": true},
|
||||
}, ""},
|
||||
{"two keys", []string{"one=true,two=false"},
|
||||
NewMapStringBool(&nilMap),
|
||||
&MapStringBool{
|
||||
initialized: true,
|
||||
Map: &map[string]bool{"one": true, "two": false},
|
||||
}, ""},
|
||||
{"two keys, multiple Set invocations", []string{"one=true", "two=false"},
|
||||
NewMapStringBool(&nilMap),
|
||||
&MapStringBool{
|
||||
initialized: true,
|
||||
Map: &map[string]bool{"one": true, "two": false},
|
||||
}, ""},
|
||||
{"two keys with space", []string{"one=true, two=false"},
|
||||
NewMapStringBool(&nilMap),
|
||||
&MapStringBool{
|
||||
initialized: true,
|
||||
Map: &map[string]bool{"one": true, "two": false},
|
||||
}, ""},
|
||||
{"empty key", []string{"=true"},
|
||||
NewMapStringBool(&nilMap),
|
||||
&MapStringBool{
|
||||
initialized: true,
|
||||
Map: &map[string]bool{"": true},
|
||||
}, ""},
|
||||
{"missing value", []string{"one"},
|
||||
NewMapStringBool(&nilMap),
|
||||
nil,
|
||||
"malformed pair, expect string=bool"},
|
||||
{"non-boolean value", []string{"one=foo"},
|
||||
NewMapStringBool(&nilMap),
|
||||
nil,
|
||||
`invalid value of one: foo, err: strconv.ParseBool: parsing "foo": invalid syntax`},
|
||||
{"no target", []string{"one=true"},
|
||||
NewMapStringBool(nil),
|
||||
nil,
|
||||
"no target (nil pointer to map[string]bool)"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
nilMap = nil
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
var err error
|
||||
for _, val := range c.vals {
|
||||
err = c.start.Set(val)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if c.err != "" {
|
||||
if err == nil || err.Error() != c.err {
|
||||
t.Fatalf("expect error %s but got %v", c.err, err)
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(c.expect, c.start) {
|
||||
t.Fatalf("expect %#v but got %#v", c.expect, c.start)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyMapStringBool(t *testing.T) {
|
||||
var nilMap map[string]bool
|
||||
cases := []struct {
|
||||
desc string
|
||||
val *MapStringBool
|
||||
expect bool
|
||||
}{
|
||||
{"nil", NewMapStringBool(&nilMap), true},
|
||||
{"empty", NewMapStringBool(&map[string]bool{}), true},
|
||||
{"populated", NewMapStringBool(&map[string]bool{"foo": true}), false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
result := c.val.Empty()
|
||||
if result != c.expect {
|
||||
t.Fatalf("expect %t but got %t", c.expect, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MapStringString can be set from the command line with the format `--flag "string=string"`.
|
||||
// Multiple flag invocations are supported. For example: `--flag "a=foo" --flag "b=bar"`. If this is desired
|
||||
// to be the only type invocation `NoSplit` should be set to true.
|
||||
// Multiple comma-separated key-value pairs in a single invocation are supported if `NoSplit`
|
||||
// is set to false. For example: `--flag "a=foo,b=bar"`.
|
||||
type MapStringString struct {
|
||||
Map *map[string]string
|
||||
initialized bool
|
||||
NoSplit bool
|
||||
}
|
||||
|
||||
// NewMapStringString takes a pointer to a map[string]string and returns the
|
||||
// MapStringString flag parsing shim for that map
|
||||
func NewMapStringString(m *map[string]string) *MapStringString {
|
||||
return &MapStringString{Map: m}
|
||||
}
|
||||
|
||||
// NewMapStringString takes a pointer to a map[string]string and sets `NoSplit`
|
||||
// value to `true` and returns the MapStringString flag parsing shim for that map
|
||||
func NewMapStringStringNoSplit(m *map[string]string) *MapStringString {
|
||||
return &MapStringString{
|
||||
Map: m,
|
||||
NoSplit: true,
|
||||
}
|
||||
}
|
||||
|
||||
// String implements github.com/spf13/pflag.Value
|
||||
func (m *MapStringString) String() string {
|
||||
if m == nil || m.Map == nil {
|
||||
return ""
|
||||
}
|
||||
pairs := []string{}
|
||||
for k, v := range *m.Map {
|
||||
pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
sort.Strings(pairs)
|
||||
return strings.Join(pairs, ",")
|
||||
}
|
||||
|
||||
// Set implements github.com/spf13/pflag.Value
|
||||
func (m *MapStringString) Set(value string) error {
|
||||
if m.Map == nil {
|
||||
return fmt.Errorf("no target (nil pointer to map[string]string)")
|
||||
}
|
||||
if !m.initialized || *m.Map == nil {
|
||||
// clear default values, or allocate if no existing map
|
||||
*m.Map = make(map[string]string)
|
||||
m.initialized = true
|
||||
}
|
||||
|
||||
// account for comma-separated key-value pairs in a single invocation
|
||||
if !m.NoSplit {
|
||||
for _, s := range strings.Split(value, ",") {
|
||||
if len(s) == 0 {
|
||||
continue
|
||||
}
|
||||
arr := strings.SplitN(s, "=", 2)
|
||||
if len(arr) != 2 {
|
||||
return fmt.Errorf("malformed pair, expect string=string")
|
||||
}
|
||||
k := strings.TrimSpace(arr[0])
|
||||
v := strings.TrimSpace(arr[1])
|
||||
(*m.Map)[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// account for only one key-value pair in a single invocation
|
||||
arr := strings.SplitN(value, "=", 2)
|
||||
if len(arr) != 2 {
|
||||
return fmt.Errorf("malformed pair, expect string=string")
|
||||
}
|
||||
k := strings.TrimSpace(arr[0])
|
||||
v := strings.TrimSpace(arr[1])
|
||||
(*m.Map)[k] = v
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// Type implements github.com/spf13/pflag.Value
|
||||
func (*MapStringString) Type() string {
|
||||
return "mapStringString"
|
||||
}
|
||||
|
||||
// Empty implements OmitEmpty
|
||||
func (m *MapStringString) Empty() bool {
|
||||
return len(*m.Map) == 0
|
||||
}
|
||||
|
|
@ -1,181 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringMapStringString(t *testing.T) {
|
||||
var nilMap map[string]string
|
||||
cases := []struct {
|
||||
desc string
|
||||
m *MapStringString
|
||||
expect string
|
||||
}{
|
||||
{"nil", NewMapStringString(&nilMap), ""},
|
||||
{"empty", NewMapStringString(&map[string]string{}), ""},
|
||||
{"one key", NewMapStringString(&map[string]string{"one": "foo"}), "one=foo"},
|
||||
{"two keys", NewMapStringString(&map[string]string{"one": "foo", "two": "bar"}), "one=foo,two=bar"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
str := c.m.String()
|
||||
if c.expect != str {
|
||||
t.Fatalf("expect %q but got %q", c.expect, str)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetMapStringString(t *testing.T) {
|
||||
var nilMap map[string]string
|
||||
cases := []struct {
|
||||
desc string
|
||||
vals []string
|
||||
start *MapStringString
|
||||
expect *MapStringString
|
||||
err string
|
||||
}{
|
||||
// we initialize the map with a default key that should be cleared by Set
|
||||
{"clears defaults", []string{""},
|
||||
NewMapStringString(&map[string]string{"default": ""}),
|
||||
&MapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{},
|
||||
NoSplit: false,
|
||||
}, ""},
|
||||
// make sure we still allocate for "initialized" maps where Map was initially set to a nil map
|
||||
{"allocates map if currently nil", []string{""},
|
||||
&MapStringString{initialized: true, Map: &nilMap},
|
||||
&MapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{},
|
||||
NoSplit: false,
|
||||
}, ""},
|
||||
// for most cases, we just reuse nilMap, which should be allocated by Set, and is reset before each test case
|
||||
{"empty", []string{""},
|
||||
NewMapStringString(&nilMap),
|
||||
&MapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{},
|
||||
NoSplit: false,
|
||||
}, ""},
|
||||
{"one key", []string{"one=foo"},
|
||||
NewMapStringString(&nilMap),
|
||||
&MapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{"one": "foo"},
|
||||
NoSplit: false,
|
||||
}, ""},
|
||||
{"two keys", []string{"one=foo,two=bar"},
|
||||
NewMapStringString(&nilMap),
|
||||
&MapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{"one": "foo", "two": "bar"},
|
||||
NoSplit: false,
|
||||
}, ""},
|
||||
{"one key, multi flag invocation only", []string{"one=foo,bar"},
|
||||
NewMapStringStringNoSplit(&nilMap),
|
||||
&MapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{"one": "foo,bar"},
|
||||
NoSplit: true,
|
||||
}, ""},
|
||||
{"two keys, multi flag invocation only", []string{"one=foo,bar", "two=foo,bar"},
|
||||
NewMapStringStringNoSplit(&nilMap),
|
||||
&MapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{"one": "foo,bar", "two": "foo,bar"},
|
||||
NoSplit: true,
|
||||
}, ""},
|
||||
{"two keys, multiple Set invocations", []string{"one=foo", "two=bar"},
|
||||
NewMapStringString(&nilMap),
|
||||
&MapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{"one": "foo", "two": "bar"},
|
||||
NoSplit: false,
|
||||
}, ""},
|
||||
{"two keys with space", []string{"one=foo, two=bar"},
|
||||
NewMapStringString(&nilMap),
|
||||
&MapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{"one": "foo", "two": "bar"},
|
||||
NoSplit: false,
|
||||
}, ""},
|
||||
{"empty key", []string{"=foo"},
|
||||
NewMapStringString(&nilMap),
|
||||
&MapStringString{
|
||||
initialized: true,
|
||||
Map: &map[string]string{"": "foo"},
|
||||
NoSplit: false,
|
||||
}, ""},
|
||||
{"missing value", []string{"one"},
|
||||
NewMapStringString(&nilMap),
|
||||
nil,
|
||||
"malformed pair, expect string=string"},
|
||||
{"no target", []string{"a:foo"},
|
||||
NewMapStringString(nil),
|
||||
nil,
|
||||
"no target (nil pointer to map[string]string)"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
nilMap = nil
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
var err error
|
||||
for _, val := range c.vals {
|
||||
err = c.start.Set(val)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if c.err != "" {
|
||||
if err == nil || err.Error() != c.err {
|
||||
t.Fatalf("expect error %s but got %v", c.err, err)
|
||||
}
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(c.expect, c.start) {
|
||||
t.Fatalf("expect %#v but got %#v", c.expect, c.start)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyMapStringString(t *testing.T) {
|
||||
var nilMap map[string]string
|
||||
cases := []struct {
|
||||
desc string
|
||||
val *MapStringString
|
||||
expect bool
|
||||
}{
|
||||
{"nil", NewMapStringString(&nilMap), true},
|
||||
{"empty", NewMapStringString(&map[string]string{}), true},
|
||||
{"populated", NewMapStringString(&map[string]string{"foo": ""}), false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.desc, func(t *testing.T) {
|
||||
result := c.val.Empty()
|
||||
if result != c.expect {
|
||||
t.Fatalf("expect %t but got %t", c.expect, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NamedCertKey is a flag value parsing "certfile,keyfile" and "certfile,keyfile:name,name,name".
|
||||
type NamedCertKey struct {
|
||||
Names []string
|
||||
CertFile, KeyFile string
|
||||
}
|
||||
|
||||
var _ flag.Value = &NamedCertKey{}
|
||||
|
||||
func (nkc *NamedCertKey) String() string {
|
||||
s := nkc.CertFile + "," + nkc.KeyFile
|
||||
if len(nkc.Names) > 0 {
|
||||
s = s + ":" + strings.Join(nkc.Names, ",")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (nkc *NamedCertKey) Set(value string) error {
|
||||
cs := strings.SplitN(value, ":", 2)
|
||||
var keycert string
|
||||
if len(cs) == 2 {
|
||||
var names string
|
||||
keycert, names = strings.TrimSpace(cs[0]), strings.TrimSpace(cs[1])
|
||||
if names == "" {
|
||||
return errors.New("empty names list is not allowed")
|
||||
}
|
||||
nkc.Names = nil
|
||||
for _, name := range strings.Split(names, ",") {
|
||||
nkc.Names = append(nkc.Names, strings.TrimSpace(name))
|
||||
}
|
||||
} else {
|
||||
nkc.Names = nil
|
||||
keycert = strings.TrimSpace(cs[0])
|
||||
}
|
||||
cs = strings.Split(keycert, ",")
|
||||
if len(cs) != 2 {
|
||||
return errors.New("expected comma separated certificate and key file paths")
|
||||
}
|
||||
nkc.CertFile = strings.TrimSpace(cs[0])
|
||||
nkc.KeyFile = strings.TrimSpace(cs[1])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*NamedCertKey) Type() string {
|
||||
return "namedCertKey"
|
||||
}
|
||||
|
||||
// NamedCertKeyArray is a flag value parsing NamedCertKeys, each passed with its own
|
||||
// flag instance (in contrast to comma separated slices).
|
||||
type NamedCertKeyArray struct {
|
||||
value *[]NamedCertKey
|
||||
changed bool
|
||||
}
|
||||
|
||||
var _ flag.Value = &NamedCertKey{}
|
||||
|
||||
// NewNamedKeyCertArray creates a new NamedCertKeyArray with the internal value
|
||||
// pointing to p.
|
||||
func NewNamedCertKeyArray(p *[]NamedCertKey) *NamedCertKeyArray {
|
||||
return &NamedCertKeyArray{
|
||||
value: p,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *NamedCertKeyArray) Set(val string) error {
|
||||
nkc := NamedCertKey{}
|
||||
err := nkc.Set(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !a.changed {
|
||||
*a.value = []NamedCertKey{nkc}
|
||||
a.changed = true
|
||||
} else {
|
||||
*a.value = append(*a.value, nkc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *NamedCertKeyArray) Type() string {
|
||||
return "namedCertKey"
|
||||
}
|
||||
|
||||
func (a *NamedCertKeyArray) String() string {
|
||||
nkcs := make([]string, 0, len(*a.value))
|
||||
for i := range *a.value {
|
||||
nkcs = append(nkcs, (*a.value)[i].String())
|
||||
}
|
||||
return "[" + strings.Join(nkcs, ";") + "]"
|
||||
}
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func TestNamedCertKeyArrayFlag(t *testing.T) {
|
||||
tests := []struct {
|
||||
args []string
|
||||
def []NamedCertKey
|
||||
expected []NamedCertKey
|
||||
parseError string
|
||||
}{
|
||||
{
|
||||
args: []string{},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
args: []string{"foo.crt,foo.key"},
|
||||
expected: []NamedCertKey{{
|
||||
KeyFile: "foo.key",
|
||||
CertFile: "foo.crt",
|
||||
}},
|
||||
},
|
||||
{
|
||||
args: []string{" foo.crt , foo.key "},
|
||||
expected: []NamedCertKey{{
|
||||
KeyFile: "foo.key",
|
||||
CertFile: "foo.crt",
|
||||
}},
|
||||
},
|
||||
{
|
||||
args: []string{"foo.crt,foo.key:abc"},
|
||||
expected: []NamedCertKey{{
|
||||
KeyFile: "foo.key",
|
||||
CertFile: "foo.crt",
|
||||
Names: []string{"abc"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
args: []string{"foo.crt,foo.key: abc "},
|
||||
expected: []NamedCertKey{{
|
||||
KeyFile: "foo.key",
|
||||
CertFile: "foo.crt",
|
||||
Names: []string{"abc"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
args: []string{"foo.crt,foo.key:"},
|
||||
parseError: "empty names list is not allowed",
|
||||
},
|
||||
{
|
||||
args: []string{""},
|
||||
parseError: "expected comma separated certificate and key file paths",
|
||||
},
|
||||
{
|
||||
args: []string{" "},
|
||||
parseError: "expected comma separated certificate and key file paths",
|
||||
},
|
||||
{
|
||||
args: []string{"a,b,c"},
|
||||
parseError: "expected comma separated certificate and key file paths",
|
||||
},
|
||||
{
|
||||
args: []string{"foo.crt,foo.key:abc,def,ghi"},
|
||||
expected: []NamedCertKey{{
|
||||
KeyFile: "foo.key",
|
||||
CertFile: "foo.crt",
|
||||
Names: []string{"abc", "def", "ghi"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
args: []string{"foo.crt,foo.key:*.*.*"},
|
||||
expected: []NamedCertKey{{
|
||||
KeyFile: "foo.key",
|
||||
CertFile: "foo.crt",
|
||||
Names: []string{"*.*.*"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
args: []string{"foo.crt,foo.key", "bar.crt,bar.key"},
|
||||
expected: []NamedCertKey{{
|
||||
KeyFile: "foo.key",
|
||||
CertFile: "foo.crt",
|
||||
}, {
|
||||
KeyFile: "bar.key",
|
||||
CertFile: "bar.crt",
|
||||
}},
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
fs := pflag.NewFlagSet("testNamedCertKeyArray", pflag.ContinueOnError)
|
||||
var nkcs []NamedCertKey
|
||||
nkcs = append(nkcs, test.def...)
|
||||
|
||||
fs.Var(NewNamedCertKeyArray(&nkcs), "tls-sni-cert-key", "usage")
|
||||
|
||||
args := []string{}
|
||||
for _, a := range test.args {
|
||||
args = append(args, fmt.Sprintf("--tls-sni-cert-key=%s", a))
|
||||
}
|
||||
|
||||
err := fs.Parse(args)
|
||||
if test.parseError != "" {
|
||||
if err == nil {
|
||||
t.Errorf("%d: expected error %q, got nil", i, test.parseError)
|
||||
} else if !strings.Contains(err.Error(), test.parseError) {
|
||||
t.Errorf("%d: expected error %q, got %q", i, test.parseError, err)
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Errorf("%d: expected nil error, got %v", i, err)
|
||||
}
|
||||
if !reflect.DeepEqual(nkcs, test.expected) {
|
||||
t.Errorf("%d: expected %+v, got %+v", i, test.expected, nkcs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
goflag "flag"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// NoOp implements goflag.Value and plfag.Value,
|
||||
// but has a noop Set implementation
|
||||
type NoOp struct{}
|
||||
|
||||
var _ goflag.Value = NoOp{}
|
||||
var _ pflag.Value = NoOp{}
|
||||
|
||||
func (NoOp) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (NoOp) Set(val string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NoOp) Type() string {
|
||||
return "NoOp"
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
// OmitEmpty is an interface for flags to report whether their underlying value
|
||||
// is "empty." If a flag implements OmitEmpty and returns true for a call to Empty(),
|
||||
// it is assumed that flag may be omitted from the command line.
|
||||
type OmitEmpty interface {
|
||||
Empty() bool
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// NamedFlagSets stores named flag sets in the order of calling FlagSet.
|
||||
type NamedFlagSets struct {
|
||||
// Order is an ordered list of flag set names.
|
||||
Order []string
|
||||
// FlagSets stores the flag sets by name.
|
||||
FlagSets map[string]*pflag.FlagSet
|
||||
}
|
||||
|
||||
// FlagSet returns the flag set with the given name and adds it to the
|
||||
// ordered name list if it is not in there yet.
|
||||
func (nfs *NamedFlagSets) FlagSet(name string) *pflag.FlagSet {
|
||||
if nfs.FlagSets == nil {
|
||||
nfs.FlagSets = map[string]*pflag.FlagSet{}
|
||||
}
|
||||
if _, ok := nfs.FlagSets[name]; !ok {
|
||||
nfs.FlagSets[name] = pflag.NewFlagSet(name, pflag.ExitOnError)
|
||||
nfs.Order = append(nfs.Order, name)
|
||||
}
|
||||
return nfs.FlagSets[name]
|
||||
}
|
||||
|
||||
// PrintSections prints the given names flag sets in sections, with the maximal given column number.
|
||||
// If cols is zero, lines are not wrapped.
|
||||
func PrintSections(w io.Writer, fss NamedFlagSets, cols int) {
|
||||
for _, name := range fss.Order {
|
||||
fs := fss.FlagSets[name]
|
||||
if !fs.HasFlags() {
|
||||
continue
|
||||
}
|
||||
|
||||
wideFS := pflag.NewFlagSet("", pflag.ExitOnError)
|
||||
wideFS.AddFlagSet(fs)
|
||||
|
||||
var zzz string
|
||||
if cols > 24 {
|
||||
zzz = strings.Repeat("z", cols-24)
|
||||
wideFS.Int(zzz, 0, strings.Repeat("z", cols-24))
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "\n%s flags:\n\n%s", strings.ToUpper(name[:1])+name[1:], wideFS.FlagUsagesWrapped(cols))
|
||||
|
||||
if cols > 24 {
|
||||
i := strings.Index(buf.String(), zzz)
|
||||
lines := strings.Split(buf.String()[:i], "\n")
|
||||
fmt.Fprint(w, strings.Join(lines[:len(lines)-1], "\n"))
|
||||
fmt.Fprintln(w)
|
||||
} else {
|
||||
fmt.Fprint(w, buf.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TerminalSize returns the current width and height of the user's terminal. If it isn't a terminal,
|
||||
// nil is returned. On error, zero values are returned for width and height.
|
||||
// Usually w must be the stdout of the process. Stderr won't work.
|
||||
func TerminalSize(w io.Writer) (int, int, error) {
|
||||
outFd, isTerminal := term.GetFdInfo(w)
|
||||
if !isTerminal {
|
||||
return 0, 0, fmt.Errorf("given writer is no terminal")
|
||||
}
|
||||
winsize, err := term.GetWinsize(outFd)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return int(winsize.Width), int(winsize.Height), nil
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
// StringFlag is a string flag compatible with flags and pflags that keeps track of whether it had a value supplied or not.
|
||||
type StringFlag struct {
|
||||
// If Set has been invoked this value is true
|
||||
provided bool
|
||||
// The exact value provided on the flag
|
||||
value string
|
||||
}
|
||||
|
||||
func NewStringFlag(defaultVal string) StringFlag {
|
||||
return StringFlag{value: defaultVal}
|
||||
}
|
||||
|
||||
func (f *StringFlag) Default(value string) {
|
||||
f.value = value
|
||||
}
|
||||
|
||||
func (f StringFlag) String() string {
|
||||
return f.value
|
||||
}
|
||||
|
||||
func (f StringFlag) Value() string {
|
||||
return f.value
|
||||
}
|
||||
|
||||
func (f *StringFlag) Set(value string) error {
|
||||
f.value = value
|
||||
f.provided = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f StringFlag) Provided() bool {
|
||||
return f.provided
|
||||
}
|
||||
|
||||
func (f *StringFlag) Type() string {
|
||||
return "string"
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Tristate is a flag compatible with flags and pflags that
|
||||
// keeps track of whether it had a value supplied or not.
|
||||
type Tristate int
|
||||
|
||||
const (
|
||||
Unset Tristate = iota // 0
|
||||
True
|
||||
False
|
||||
)
|
||||
|
||||
func (f *Tristate) Default(value bool) {
|
||||
*f = triFromBool(value)
|
||||
}
|
||||
|
||||
func (f Tristate) String() string {
|
||||
b := boolFromTri(f)
|
||||
return fmt.Sprintf("%t", b)
|
||||
}
|
||||
|
||||
func (f Tristate) Value() bool {
|
||||
b := boolFromTri(f)
|
||||
return b
|
||||
}
|
||||
|
||||
func (f *Tristate) Set(value string) error {
|
||||
boolVal, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*f = triFromBool(boolVal)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f Tristate) Provided() bool {
|
||||
if f != Unset {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *Tristate) Type() string {
|
||||
return "tristate"
|
||||
}
|
||||
|
||||
func boolFromTri(t Tristate) bool {
|
||||
if t == True {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func triFromBool(b bool) Tristate {
|
||||
if b {
|
||||
return True
|
||||
} else {
|
||||
return False
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package globalflag
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/component-base/logs"
|
||||
)
|
||||
|
||||
// AddGlobalFlags explicitly registers flags that libraries (klog, verflag, etc.) register
|
||||
// against the global flagsets from "flag" and "k8s.io/klog".
|
||||
// We do this in order to prevent unwanted flags from leaking into the component's flagset.
|
||||
func AddGlobalFlags(fs *pflag.FlagSet, name string) {
|
||||
addGlogFlags(fs)
|
||||
logs.AddFlags(fs)
|
||||
|
||||
fs.BoolP("help", "h", false, fmt.Sprintf("help for %s", name))
|
||||
}
|
||||
|
||||
// addGlogFlags explicitly registers flags that klog libraries(k8s.io/klog) register.
|
||||
func addGlogFlags(fs *pflag.FlagSet) {
|
||||
// lookup flags of klog libraries in global flag set and re-register the values with our flagset
|
||||
Register(fs, "logtostderr")
|
||||
Register(fs, "alsologtostderr")
|
||||
Register(fs, "v")
|
||||
Register(fs, "skip_headers")
|
||||
Register(fs, "stderrthreshold")
|
||||
Register(fs, "vmodule")
|
||||
Register(fs, "log_backtrace_at")
|
||||
Register(fs, "log_dir")
|
||||
Register(fs, "log_file")
|
||||
}
|
||||
|
||||
// normalize replaces underscores with hyphens
|
||||
// we should always use hyphens instead of underscores when registering component flags
|
||||
func normalize(s string) string {
|
||||
return strings.Replace(s, "_", "-", -1)
|
||||
}
|
||||
|
||||
// Register adds a flag to local that targets the Value associated with the Flag named globalName in flag.CommandLine.
|
||||
func Register(local *pflag.FlagSet, globalName string) {
|
||||
if f := flag.CommandLine.Lookup(globalName); f != nil {
|
||||
pflagFlag := pflag.PFlagFromGoFlag(f)
|
||||
pflagFlag.Name = normalize(pflagFlag.Name)
|
||||
local.AddFlag(pflagFlag)
|
||||
} else {
|
||||
panic(fmt.Sprintf("failed to find flag in global flagset (flag): %s", globalName))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package globalflag
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
apiserverflag "k8s.io/apiserver/pkg/util/flag"
|
||||
)
|
||||
|
||||
func TestAddGlobalFlags(t *testing.T) {
|
||||
namedFlagSets := &apiserverflag.NamedFlagSets{}
|
||||
nfs := namedFlagSets.FlagSet("global")
|
||||
AddGlobalFlags(nfs, "test-cmd")
|
||||
|
||||
actualFlag := []string{}
|
||||
nfs.VisitAll(func(flag *pflag.Flag) {
|
||||
actualFlag = append(actualFlag, flag.Name)
|
||||
})
|
||||
|
||||
// Get all flags from flags.CommandLine, except flag `test.*`.
|
||||
wantedFlag := []string{"help"}
|
||||
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
||||
pflag.VisitAll(func(flag *pflag.Flag) {
|
||||
if !strings.Contains(flag.Name, "test.") {
|
||||
wantedFlag = append(wantedFlag, normalize(flag.Name))
|
||||
}
|
||||
})
|
||||
sort.Strings(wantedFlag)
|
||||
|
||||
if !reflect.DeepEqual(wantedFlag, actualFlag) {
|
||||
t.Errorf("[Default]: expected %+v, got %+v", wantedFlag, actualFlag)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
expectedFlag []string
|
||||
matchExpected bool
|
||||
}{
|
||||
{
|
||||
// Happy case
|
||||
expectedFlag: []string{"alsologtostderr", "help", "log-backtrace-at", "log-dir", "log-file", "log-flush-frequency", "logtostderr", "skip-headers", "stderrthreshold", "v", "vmodule"},
|
||||
matchExpected: false,
|
||||
},
|
||||
{
|
||||
// Missing flag
|
||||
expectedFlag: []string{"logtostderr", "log-dir"},
|
||||
matchExpected: true,
|
||||
},
|
||||
{
|
||||
// Empty flag
|
||||
expectedFlag: []string{},
|
||||
matchExpected: true,
|
||||
},
|
||||
{
|
||||
// Invalid flag
|
||||
expectedFlag: []string{"foo"},
|
||||
matchExpected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
if reflect.DeepEqual(test.expectedFlag, actualFlag) == test.matchExpected {
|
||||
t.Errorf("[%d]: expected %+v, got %+v", i, test.expectedFlag, actualFlag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package term
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/pkg/term"
|
||||
)
|
||||
|
||||
// TerminalSize returns the current width and height of the user's terminal. If it isn't a terminal,
|
||||
// nil is returned. On error, zero values are returned for width and height.
|
||||
// Usually w must be the stdout of the process. Stderr won't work.
|
||||
func TerminalSize(w io.Writer) (int, int, error) {
|
||||
outFd, isTerminal := term.GetFdInfo(w)
|
||||
if !isTerminal {
|
||||
return 0, 0, fmt.Errorf("given writer is no terminal")
|
||||
}
|
||||
winsize, err := term.GetWinsize(outFd)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return int(winsize.Width), int(winsize.Height), nil
|
||||
}
|
||||
Loading…
Reference in New Issue