mirror of
https://github.com/kubernetes-sigs/prometheus-adapter.git
synced 2026-04-07 10:17:51 +00:00
vendor dependencies
This commit is contained in:
parent
604208ef4f
commit
72abf135d6
1156 changed files with 78178 additions and 105799 deletions
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
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.
|
||||
|
|
@ -14,11 +14,9 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package flag
|
||||
package dryrun
|
||||
|
||||
// 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
|
||||
// IsDryRun returns true if the DryRun flag is an actual dry-run.
|
||||
func IsDryRun(flag []string) bool {
|
||||
return len(flag) > 0
|
||||
}
|
||||
108
vendor/k8s.io/apiserver/pkg/util/feature/feature_gate.go
generated
vendored
108
vendor/k8s.io/apiserver/pkg/util/feature/feature_gate.go
generated
vendored
|
|
@ -24,8 +24,8 @@ import (
|
|||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
type Feature string
|
||||
|
|
@ -51,12 +51,23 @@ var (
|
|||
allAlphaGate: setUnsetAlphaGates,
|
||||
}
|
||||
|
||||
// DefaultMutableFeatureGate is a mutable version of DefaultFeatureGate.
|
||||
// Only top-level commands/options setup and the k8s.io/apiserver/pkg/util/feature/testing package should make use of this.
|
||||
// Tests that need to modify feature gates for the duration of their test should use:
|
||||
// defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.<FeatureName>, <value>)()
|
||||
DefaultMutableFeatureGate MutableFeatureGate = NewFeatureGate()
|
||||
|
||||
// DefaultFeatureGate is a shared global FeatureGate.
|
||||
DefaultFeatureGate FeatureGate = NewFeatureGate()
|
||||
// Top-level commands/options setup that needs to modify this feature gate should use DefaultMutableFeatureGate.
|
||||
DefaultFeatureGate FeatureGate = DefaultMutableFeatureGate
|
||||
)
|
||||
|
||||
type FeatureSpec struct {
|
||||
Default bool
|
||||
// Default is the default enablement state for the feature
|
||||
Default bool
|
||||
// LockToDefault indicates that the feature is locked to its default and cannot be changed
|
||||
LockToDefault bool
|
||||
// PreRelease indicates the maturity level of the feature
|
||||
PreRelease prerelease
|
||||
}
|
||||
|
||||
|
|
@ -72,9 +83,23 @@ const (
|
|||
Deprecated = prerelease("DEPRECATED")
|
||||
)
|
||||
|
||||
// FeatureGate parses and stores flag gates for known features from
|
||||
// a string like feature1=true,feature2=false,...
|
||||
// FeatureGate indicates whether a given feature is enabled or not
|
||||
type FeatureGate interface {
|
||||
// Enabled returns true if the key is enabled.
|
||||
Enabled(key Feature) bool
|
||||
// KnownFeatures returns a slice of strings describing the FeatureGate's known features.
|
||||
KnownFeatures() []string
|
||||
// DeepCopy returns a deep copy of the FeatureGate object, such that gates can be
|
||||
// set on the copy without mutating the original. This is useful for validating
|
||||
// config against potential feature gate changes before committing those changes.
|
||||
DeepCopy() MutableFeatureGate
|
||||
}
|
||||
|
||||
// MutableFeatureGate parses and stores flag gates for known features from
|
||||
// a string like feature1=true,feature2=false,...
|
||||
type MutableFeatureGate interface {
|
||||
FeatureGate
|
||||
|
||||
// AddFlag adds a flag for setting global feature gates to the specified FlagSet.
|
||||
AddFlag(fs *pflag.FlagSet)
|
||||
// Set parses and stores flag gates for known features
|
||||
|
|
@ -82,16 +107,8 @@ type FeatureGate interface {
|
|||
Set(value string) error
|
||||
// SetFromMap stores flag gates for known features from a map[string]bool or returns an error
|
||||
SetFromMap(m map[string]bool) error
|
||||
// Enabled returns true if the key is enabled.
|
||||
Enabled(key Feature) bool
|
||||
// Add adds features to the featureGate.
|
||||
Add(features map[Feature]FeatureSpec) error
|
||||
// KnownFeatures returns a slice of strings describing the FeatureGate's known features.
|
||||
KnownFeatures() []string
|
||||
// DeepCopy returns a deep copy of the FeatureGate object, such that gates can be
|
||||
// set on the copy without mutating the original. This is useful for validating
|
||||
// config against potential feature gate changes before committing those changes.
|
||||
DeepCopy() FeatureGate
|
||||
}
|
||||
|
||||
// featureGate implements FeatureGate as well as pflag.Value for flag parsing.
|
||||
|
|
@ -145,54 +162,24 @@ func NewFeatureGate() *featureGate {
|
|||
// Set parses a string of the form "key1=value1,key2=value2,..." into a
|
||||
// map[string]bool of known keys or returns an error.
|
||||
func (f *featureGate) Set(value string) error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
// Copy existing state
|
||||
known := map[Feature]FeatureSpec{}
|
||||
for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
|
||||
known[k] = v
|
||||
}
|
||||
enabled := map[Feature]bool{}
|
||||
for k, v := range f.enabled.Load().(map[Feature]bool) {
|
||||
enabled[k] = v
|
||||
}
|
||||
|
||||
m := make(map[string]bool)
|
||||
for _, s := range strings.Split(value, ",") {
|
||||
if len(s) == 0 {
|
||||
continue
|
||||
}
|
||||
arr := strings.SplitN(s, "=", 2)
|
||||
k := Feature(strings.TrimSpace(arr[0]))
|
||||
featureSpec, ok := known[k]
|
||||
if !ok {
|
||||
return fmt.Errorf("unrecognized key: %s", k)
|
||||
}
|
||||
k := strings.TrimSpace(arr[0])
|
||||
if len(arr) != 2 {
|
||||
return fmt.Errorf("missing bool value for %s", k)
|
||||
}
|
||||
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)
|
||||
}
|
||||
enabled[k] = boolValue
|
||||
if boolValue && featureSpec.PreRelease == Deprecated {
|
||||
glog.Warningf("enabling deprecated feature gate %s", k)
|
||||
}
|
||||
|
||||
// Handle "special" features like "all alpha gates"
|
||||
if fn, found := f.special[k]; found {
|
||||
fn(known, enabled, boolValue)
|
||||
return fmt.Errorf("invalid value of %s=%s, err: %v", k, v, err)
|
||||
}
|
||||
m[k] = boolValue
|
||||
}
|
||||
|
||||
// Persist changes
|
||||
f.known.Store(known)
|
||||
f.enabled.Store(enabled)
|
||||
|
||||
glog.V(1).Infof("feature gates: %v", enabled)
|
||||
return nil
|
||||
return f.SetFromMap(m)
|
||||
}
|
||||
|
||||
// SetFromMap stores flag gates for known features from a map[string]bool or returns an error
|
||||
|
|
@ -212,22 +199,31 @@ func (f *featureGate) SetFromMap(m map[string]bool) error {
|
|||
|
||||
for k, v := range m {
|
||||
k := Feature(k)
|
||||
_, ok := known[k]
|
||||
featureSpec, ok := known[k]
|
||||
if !ok {
|
||||
return fmt.Errorf("unrecognized key: %s", k)
|
||||
return fmt.Errorf("unrecognized feature gate: %s", k)
|
||||
}
|
||||
if featureSpec.LockToDefault && featureSpec.Default != v {
|
||||
return fmt.Errorf("cannot set feature gate %v to %v, feature is locked to %v", k, v, featureSpec.Default)
|
||||
}
|
||||
enabled[k] = v
|
||||
// Handle "special" features like "all alpha gates"
|
||||
if fn, found := f.special[k]; found {
|
||||
fn(known, enabled, v)
|
||||
}
|
||||
|
||||
if featureSpec.PreRelease == Deprecated {
|
||||
klog.Warningf("Setting deprecated feature gate %s=%t. It will be removed in a future release.", k, v)
|
||||
} else if featureSpec.PreRelease == GA {
|
||||
klog.Warningf("Setting GA feature gate %s=%t. It will be removed in a future release.", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Persist changes
|
||||
f.known.Store(known)
|
||||
f.enabled.Store(enabled)
|
||||
|
||||
glog.V(1).Infof("feature gates: %v", f.enabled)
|
||||
klog.V(1).Infof("feature gates: %v", f.enabled)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -302,14 +298,14 @@ func (f *featureGate) AddFlag(fs *pflag.FlagSet) {
|
|||
}
|
||||
|
||||
// KnownFeatures returns a slice of strings describing the FeatureGate's known features.
|
||||
// Deprecated and GA features are hidden from the list.
|
||||
func (f *featureGate) KnownFeatures() []string {
|
||||
var known []string
|
||||
for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
|
||||
pre := ""
|
||||
if v.PreRelease != GA {
|
||||
pre = fmt.Sprintf("%s - ", v.PreRelease)
|
||||
if v.PreRelease == GA || v.PreRelease == Deprecated {
|
||||
continue
|
||||
}
|
||||
known = append(known, fmt.Sprintf("%s=true|false (%sdefault=%t)", k, pre, v.Default))
|
||||
known = append(known, fmt.Sprintf("%s=true|false (%s - default=%t)", k, v.PreRelease, v.Default))
|
||||
}
|
||||
sort.Strings(known)
|
||||
return known
|
||||
|
|
@ -318,7 +314,7 @@ func (f *featureGate) KnownFeatures() []string {
|
|||
// DeepCopy returns a deep copy of the FeatureGate object, such that gates can be
|
||||
// set on the copy without mutating the original. This is useful for validating
|
||||
// config against potential feature gate changes before committing those changes.
|
||||
func (f *featureGate) DeepCopy() FeatureGate {
|
||||
func (f *featureGate) DeepCopy() MutableFeatureGate {
|
||||
// Copy existing state.
|
||||
known := map[Feature]FeatureSpec{}
|
||||
for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
|
||||
|
|
|
|||
105
vendor/k8s.io/apiserver/pkg/util/flag/ciphersuites_flag.go
generated
vendored
105
vendor/k8s.io/apiserver/pkg/util/flag/ciphersuites_flag.go
generated
vendored
|
|
@ -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
|
||||
}
|
||||
102
vendor/k8s.io/apiserver/pkg/util/flag/colon_separated_multimap_string_string.go
generated
vendored
102
vendor/k8s.io/apiserver/pkg/util/flag/colon_separated_multimap_string_string.go
generated
vendored
|
|
@ -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
|
||||
}
|
||||
53
vendor/k8s.io/apiserver/pkg/util/flag/configuration_map.go
generated
vendored
53
vendor/k8s.io/apiserver/pkg/util/flag/configuration_map.go
generated
vendored
|
|
@ -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"
|
||||
}
|
||||
54
vendor/k8s.io/apiserver/pkg/util/flag/flags.go
generated
vendored
54
vendor/k8s.io/apiserver/pkg/util/flag/flags.go
generated
vendored
|
|
@ -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/golang/glog"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// 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)
|
||||
glog.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) {
|
||||
glog.V(2).Infof("FLAG: --%s=%q", flag.Name, flag.Value)
|
||||
})
|
||||
}
|
||||
82
vendor/k8s.io/apiserver/pkg/util/flag/langle_separated_map_string_string.go
generated
vendored
82
vendor/k8s.io/apiserver/pkg/util/flag/langle_separated_map_string_string.go
generated
vendored
|
|
@ -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
|
||||
}
|
||||
90
vendor/k8s.io/apiserver/pkg/util/flag/map_string_bool.go
generated
vendored
90
vendor/k8s.io/apiserver/pkg/util/flag/map_string_bool.go
generated
vendored
|
|
@ -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
|
||||
}
|
||||
112
vendor/k8s.io/apiserver/pkg/util/flag/map_string_string.go
generated
vendored
112
vendor/k8s.io/apiserver/pkg/util/flag/map_string_string.go
generated
vendored
|
|
@ -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
|
||||
}
|
||||
113
vendor/k8s.io/apiserver/pkg/util/flag/namedcertkey_flag.go
generated
vendored
113
vendor/k8s.io/apiserver/pkg/util/flag/namedcertkey_flag.go
generated
vendored
|
|
@ -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, ";") + "]"
|
||||
}
|
||||
41
vendor/k8s.io/apiserver/pkg/util/flag/noop.go
generated
vendored
41
vendor/k8s.io/apiserver/pkg/util/flag/noop.go
generated
vendored
|
|
@ -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"
|
||||
}
|
||||
56
vendor/k8s.io/apiserver/pkg/util/flag/string_flag.go
generated
vendored
56
vendor/k8s.io/apiserver/pkg/util/flag/string_flag.go
generated
vendored
|
|
@ -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"
|
||||
}
|
||||
83
vendor/k8s.io/apiserver/pkg/util/flag/tristate.go
generated
vendored
83
vendor/k8s.io/apiserver/pkg/util/flag/tristate.go
generated
vendored
|
|
@ -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
|
||||
}
|
||||
}
|
||||
69
vendor/k8s.io/apiserver/pkg/util/logs/logs.go
generated
vendored
69
vendor/k8s.io/apiserver/pkg/util/logs/logs.go
generated
vendored
|
|
@ -1,69 +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 logs
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
)
|
||||
|
||||
const logFlushFreqFlagName = "log-flush-frequency"
|
||||
|
||||
var logFlushFreq = pflag.Duration(logFlushFreqFlagName, 5*time.Second, "Maximum number of seconds between log flushes")
|
||||
|
||||
// TODO(thockin): This is temporary until we agree on log dirs and put those into each cmd.
|
||||
func init() {
|
||||
flag.Set("logtostderr", "true")
|
||||
}
|
||||
|
||||
// AddFlags registers this package's flags on arbitrary FlagSets, such that they point to the
|
||||
// same value as the global flags.
|
||||
func AddFlags(fs *pflag.FlagSet) {
|
||||
fs.AddFlag(pflag.Lookup(logFlushFreqFlagName))
|
||||
}
|
||||
|
||||
// GlogWriter serves as a bridge between the standard log package and the glog package.
|
||||
type GlogWriter struct{}
|
||||
|
||||
// Write implements the io.Writer interface.
|
||||
func (writer GlogWriter) Write(data []byte) (n int, err error) {
|
||||
glog.Info(string(data))
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
// InitLogs initializes logs the way we want for kubernetes.
|
||||
func InitLogs() {
|
||||
log.SetOutput(GlogWriter{})
|
||||
log.SetFlags(0)
|
||||
// The default glog flush interval is 5 seconds.
|
||||
go wait.Forever(glog.Flush, *logFlushFreq)
|
||||
}
|
||||
|
||||
// FlushLogs flushes logs immediately.
|
||||
func FlushLogs() {
|
||||
glog.Flush()
|
||||
}
|
||||
|
||||
// NewLogger creates a new log.Logger which sends logs to glog.Info.
|
||||
func NewLogger(prefix string) *log.Logger {
|
||||
return log.New(GlogWriter{}, prefix, 0)
|
||||
}
|
||||
94
vendor/k8s.io/apiserver/pkg/util/openapi/proto.go
generated
vendored
94
vendor/k8s.io/apiserver/pkg/util/openapi/proto.go
generated
vendored
|
|
@ -18,29 +18,17 @@ package openapi
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
openapi_v2 "github.com/googleapis/gnostic/OpenAPIv2"
|
||||
"github.com/googleapis/gnostic/compiler"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/kube-openapi/pkg/util/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
// groupVersionKindExtensionKey is the key used to lookup the
|
||||
// GroupVersionKind value for an object definition from the
|
||||
// definition's "extensions" map.
|
||||
groupVersionKindExtensionKey = "x-kubernetes-group-version-kind"
|
||||
)
|
||||
|
||||
// ToProtoSchema builds the proto formatted schema from an OpenAPI spec
|
||||
func ToProtoSchema(openAPIDefinitions *spec.Definitions, gvk schema.GroupVersionKind) (proto.Schema, error) {
|
||||
openAPISpec := newMinimalValidOpenAPISpec()
|
||||
openAPISpec.Definitions = *openAPIDefinitions
|
||||
|
||||
// ToProtoModels builds the proto formatted models from OpenAPI spec
|
||||
func ToProtoModels(openAPISpec *spec.Swagger) (proto.Models, error) {
|
||||
specBytes, err := json.MarshalIndent(openAPISpec, " ", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -62,81 +50,5 @@ func ToProtoSchema(openAPIDefinitions *spec.Definitions, gvk schema.GroupVersion
|
|||
return nil, err
|
||||
}
|
||||
|
||||
for _, modelName := range models.ListModels() {
|
||||
model := models.LookupModel(modelName)
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("the ListModels function returned a model that can't be looked-up")
|
||||
}
|
||||
gvkList := parseGroupVersionKind(model)
|
||||
for _, modelGVK := range gvkList {
|
||||
if modelGVK == gvk {
|
||||
return model, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no model found with a %v tag matching %v", groupVersionKindExtensionKey, gvk)
|
||||
}
|
||||
|
||||
// newMinimalValidOpenAPISpec creates a minimal openapi spec with only the required fields filled in
|
||||
func newMinimalValidOpenAPISpec() *spec.Swagger {
|
||||
return &spec.Swagger{
|
||||
SwaggerProps: spec.SwaggerProps{
|
||||
Swagger: "2.0",
|
||||
Info: &spec.Info{
|
||||
InfoProps: spec.InfoProps{
|
||||
Title: "Kubernetes",
|
||||
Version: "0.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// parseGroupVersionKind gets and parses GroupVersionKind from the extension. Returns empty if it doesn't have one.
|
||||
func parseGroupVersionKind(s proto.Schema) []schema.GroupVersionKind {
|
||||
extensions := s.GetExtensions()
|
||||
|
||||
gvkListResult := []schema.GroupVersionKind{}
|
||||
|
||||
// Get the extensions
|
||||
gvkExtension, ok := extensions[groupVersionKindExtensionKey]
|
||||
if !ok {
|
||||
return []schema.GroupVersionKind{}
|
||||
}
|
||||
|
||||
// gvk extension must be a list of at least 1 element.
|
||||
gvkList, ok := gvkExtension.([]interface{})
|
||||
if !ok {
|
||||
return []schema.GroupVersionKind{}
|
||||
}
|
||||
|
||||
for _, gvk := range gvkList {
|
||||
// gvk extension list must be a map with group, version, and
|
||||
// kind fields
|
||||
gvkMap, ok := gvk.(map[interface{}]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
group, ok := gvkMap["group"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
version, ok := gvkMap["version"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
kind, ok := gvkMap["kind"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
gvkListResult = append(gvkListResult, schema.GroupVersionKind{
|
||||
Group: group,
|
||||
Version: version,
|
||||
Kind: kind,
|
||||
})
|
||||
}
|
||||
|
||||
return gvkListResult
|
||||
return models, nil
|
||||
}
|
||||
|
|
|
|||
89
vendor/k8s.io/apiserver/pkg/util/trace/trace.go
generated
vendored
89
vendor/k8s.io/apiserver/pkg/util/trace/trace.go
generated
vendored
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 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 trace
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
type traceStep struct {
|
||||
stepTime time.Time
|
||||
msg string
|
||||
}
|
||||
|
||||
type Trace struct {
|
||||
name string
|
||||
startTime time.Time
|
||||
steps []traceStep
|
||||
}
|
||||
|
||||
func New(name string) *Trace {
|
||||
return &Trace{name, time.Now(), nil}
|
||||
}
|
||||
|
||||
func (t *Trace) Step(msg string) {
|
||||
if t.steps == nil {
|
||||
// traces almost always have less than 6 steps, do this to avoid more than a single allocation
|
||||
t.steps = make([]traceStep, 0, 6)
|
||||
}
|
||||
t.steps = append(t.steps, traceStep{time.Now(), msg})
|
||||
}
|
||||
|
||||
func (t *Trace) Log() {
|
||||
// an explicit logging request should dump all the steps out at the higher level
|
||||
t.logWithStepThreshold(0)
|
||||
}
|
||||
|
||||
func (t *Trace) logWithStepThreshold(stepThreshold time.Duration) {
|
||||
var buffer bytes.Buffer
|
||||
tracenum := rand.Int31()
|
||||
endTime := time.Now()
|
||||
|
||||
totalTime := endTime.Sub(t.startTime)
|
||||
buffer.WriteString(fmt.Sprintf("Trace[%d]: %q (started: %v) (total time: %v):\n", tracenum, t.name, t.startTime, totalTime))
|
||||
lastStepTime := t.startTime
|
||||
for _, step := range t.steps {
|
||||
stepDuration := step.stepTime.Sub(lastStepTime)
|
||||
if stepThreshold == 0 || stepDuration > stepThreshold || glog.V(4) {
|
||||
buffer.WriteString(fmt.Sprintf("Trace[%d]: [%v] [%v] %v\n", tracenum, step.stepTime.Sub(t.startTime), stepDuration, step.msg))
|
||||
}
|
||||
lastStepTime = step.stepTime
|
||||
}
|
||||
stepDuration := endTime.Sub(lastStepTime)
|
||||
if stepThreshold == 0 || stepDuration > stepThreshold || glog.V(4) {
|
||||
buffer.WriteString(fmt.Sprintf("Trace[%d]: [%v] [%v] END\n", tracenum, endTime.Sub(t.startTime), stepDuration))
|
||||
}
|
||||
|
||||
glog.Info(buffer.String())
|
||||
}
|
||||
|
||||
func (t *Trace) LogIfLong(threshold time.Duration) {
|
||||
if time.Since(t.startTime) >= threshold {
|
||||
// if any step took more than it's share of the total allowed time, it deserves a higher log level
|
||||
stepThreshold := threshold / time.Duration(len(t.steps)+1)
|
||||
t.logWithStepThreshold(stepThreshold)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Trace) TotalTime() time.Duration {
|
||||
return time.Since(t.startTime)
|
||||
}
|
||||
211
vendor/k8s.io/apiserver/pkg/util/webhook/authentication.go
generated
vendored
Normal file
211
vendor/k8s.io/apiserver/pkg/util/webhook/authentication.go
generated
vendored
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
// AuthenticationInfoResolverWrapper can be used to inject Dial function to the
|
||||
// rest.Config generated by the resolver.
|
||||
type AuthenticationInfoResolverWrapper func(AuthenticationInfoResolver) AuthenticationInfoResolver
|
||||
|
||||
// NewDefaultAuthenticationInfoResolverWrapper builds a default authn resolver wrapper
|
||||
func NewDefaultAuthenticationInfoResolverWrapper(
|
||||
proxyTransport *http.Transport,
|
||||
kubeapiserverClientConfig *rest.Config) AuthenticationInfoResolverWrapper {
|
||||
|
||||
webhookAuthResolverWrapper := func(delegate AuthenticationInfoResolver) AuthenticationInfoResolver {
|
||||
return &AuthenticationInfoResolverDelegator{
|
||||
ClientConfigForFunc: func(server string) (*rest.Config, error) {
|
||||
if server == "kubernetes.default.svc" {
|
||||
return kubeapiserverClientConfig, nil
|
||||
}
|
||||
return delegate.ClientConfigFor(server)
|
||||
},
|
||||
ClientConfigForServiceFunc: func(serviceName, serviceNamespace string) (*rest.Config, error) {
|
||||
if serviceName == "kubernetes" && serviceNamespace == corev1.NamespaceDefault {
|
||||
return kubeapiserverClientConfig, nil
|
||||
}
|
||||
ret, err := delegate.ClientConfigForService(serviceName, serviceNamespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if proxyTransport != nil && proxyTransport.DialContext != nil {
|
||||
ret.Dial = proxyTransport.DialContext
|
||||
}
|
||||
return ret, err
|
||||
},
|
||||
}
|
||||
}
|
||||
return webhookAuthResolverWrapper
|
||||
}
|
||||
|
||||
// AuthenticationInfoResolver builds rest.Config base on the server or service
|
||||
// name and service namespace.
|
||||
type AuthenticationInfoResolver interface {
|
||||
// ClientConfigFor builds rest.Config based on the server.
|
||||
ClientConfigFor(server string) (*rest.Config, error)
|
||||
// ClientConfigForService builds rest.Config based on the serviceName and
|
||||
// serviceNamespace.
|
||||
ClientConfigForService(serviceName, serviceNamespace string) (*rest.Config, error)
|
||||
}
|
||||
|
||||
// AuthenticationInfoResolverDelegator implements AuthenticationInfoResolver.
|
||||
type AuthenticationInfoResolverDelegator struct {
|
||||
ClientConfigForFunc func(server string) (*rest.Config, error)
|
||||
ClientConfigForServiceFunc func(serviceName, serviceNamespace string) (*rest.Config, error)
|
||||
}
|
||||
|
||||
// ClientConfigFor returns client config for given server.
|
||||
func (a *AuthenticationInfoResolverDelegator) ClientConfigFor(server string) (*rest.Config, error) {
|
||||
return a.ClientConfigForFunc(server)
|
||||
}
|
||||
|
||||
// ClientConfigForService returns client config for given service.
|
||||
func (a *AuthenticationInfoResolverDelegator) ClientConfigForService(serviceName, serviceNamespace string) (*rest.Config, error) {
|
||||
return a.ClientConfigForServiceFunc(serviceName, serviceNamespace)
|
||||
}
|
||||
|
||||
type defaultAuthenticationInfoResolver struct {
|
||||
kubeconfig clientcmdapi.Config
|
||||
}
|
||||
|
||||
// NewDefaultAuthenticationInfoResolver generates an AuthenticationInfoResolver
|
||||
// that builds rest.Config based on the kubeconfig file. kubeconfigFile is the
|
||||
// path to the kubeconfig.
|
||||
func NewDefaultAuthenticationInfoResolver(kubeconfigFile string) (AuthenticationInfoResolver, error) {
|
||||
if len(kubeconfigFile) == 0 {
|
||||
return &defaultAuthenticationInfoResolver{}, nil
|
||||
}
|
||||
|
||||
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||
loadingRules.ExplicitPath = kubeconfigFile
|
||||
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
|
||||
clientConfig, err := loader.RawConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &defaultAuthenticationInfoResolver{kubeconfig: clientConfig}, nil
|
||||
}
|
||||
|
||||
func (c *defaultAuthenticationInfoResolver) ClientConfigFor(server string) (*rest.Config, error) {
|
||||
return c.clientConfig(server)
|
||||
}
|
||||
|
||||
func (c *defaultAuthenticationInfoResolver) ClientConfigForService(serviceName, serviceNamespace string) (*rest.Config, error) {
|
||||
return c.clientConfig(serviceName + "." + serviceNamespace + ".svc")
|
||||
}
|
||||
|
||||
func (c *defaultAuthenticationInfoResolver) clientConfig(target string) (*rest.Config, error) {
|
||||
// exact match
|
||||
if authConfig, ok := c.kubeconfig.AuthInfos[target]; ok {
|
||||
return restConfigFromKubeconfig(authConfig)
|
||||
}
|
||||
|
||||
// star prefixed match
|
||||
serverSteps := strings.Split(target, ".")
|
||||
for i := 1; i < len(serverSteps); i++ {
|
||||
nickName := "*." + strings.Join(serverSteps[i:], ".")
|
||||
if authConfig, ok := c.kubeconfig.AuthInfos[nickName]; ok {
|
||||
return restConfigFromKubeconfig(authConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// if we're trying to hit the kube-apiserver and there wasn't an explicit config, use the in-cluster config
|
||||
if target == "kubernetes.default.svc" {
|
||||
// if we can find an in-cluster-config use that. If we can't, fall through.
|
||||
inClusterConfig, err := rest.InClusterConfig()
|
||||
if err == nil {
|
||||
return setGlobalDefaults(inClusterConfig), nil
|
||||
}
|
||||
}
|
||||
|
||||
// star (default) match
|
||||
if authConfig, ok := c.kubeconfig.AuthInfos["*"]; ok {
|
||||
return restConfigFromKubeconfig(authConfig)
|
||||
}
|
||||
|
||||
// use the current context from the kubeconfig if possible
|
||||
if len(c.kubeconfig.CurrentContext) > 0 {
|
||||
if currContext, ok := c.kubeconfig.Contexts[c.kubeconfig.CurrentContext]; ok {
|
||||
if len(currContext.AuthInfo) > 0 {
|
||||
if currAuth, ok := c.kubeconfig.AuthInfos[currContext.AuthInfo]; ok {
|
||||
return restConfigFromKubeconfig(currAuth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// anonymous
|
||||
return setGlobalDefaults(&rest.Config{}), nil
|
||||
}
|
||||
|
||||
func restConfigFromKubeconfig(configAuthInfo *clientcmdapi.AuthInfo) (*rest.Config, error) {
|
||||
config := &rest.Config{}
|
||||
|
||||
// blindly overwrite existing values based on precedence
|
||||
if len(configAuthInfo.Token) > 0 {
|
||||
config.BearerToken = configAuthInfo.Token
|
||||
} else if len(configAuthInfo.TokenFile) > 0 {
|
||||
tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.BearerToken = string(tokenBytes)
|
||||
config.BearerTokenFile = configAuthInfo.TokenFile
|
||||
}
|
||||
if len(configAuthInfo.Impersonate) > 0 {
|
||||
config.Impersonate = rest.ImpersonationConfig{
|
||||
UserName: configAuthInfo.Impersonate,
|
||||
Groups: configAuthInfo.ImpersonateGroups,
|
||||
Extra: configAuthInfo.ImpersonateUserExtra,
|
||||
}
|
||||
}
|
||||
if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
|
||||
config.CertFile = configAuthInfo.ClientCertificate
|
||||
config.CertData = configAuthInfo.ClientCertificateData
|
||||
config.KeyFile = configAuthInfo.ClientKey
|
||||
config.KeyData = configAuthInfo.ClientKeyData
|
||||
}
|
||||
if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
|
||||
config.Username = configAuthInfo.Username
|
||||
config.Password = configAuthInfo.Password
|
||||
}
|
||||
if configAuthInfo.AuthProvider != nil {
|
||||
return nil, fmt.Errorf("auth provider not supported")
|
||||
}
|
||||
|
||||
return setGlobalDefaults(config), nil
|
||||
}
|
||||
|
||||
func setGlobalDefaults(config *rest.Config) *rest.Config {
|
||||
config.UserAgent = "kube-apiserver-admission"
|
||||
config.Timeout = 30 * time.Second
|
||||
|
||||
return config
|
||||
}
|
||||
198
vendor/k8s.io/apiserver/pkg/util/webhook/client.go
generated
vendored
Normal file
198
vendor/k8s.io/apiserver/pkg/util/webhook/client.go
generated
vendored
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
"github.com/hashicorp/golang-lru"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCacheSize = 200
|
||||
)
|
||||
|
||||
// ClientConfig defines parameters required for creating a hook client.
|
||||
type ClientConfig struct {
|
||||
Name string
|
||||
URL string
|
||||
CABundle []byte
|
||||
Service *ClientConfigService
|
||||
}
|
||||
|
||||
// ClientConfigService defines service discovery parameters of the webhook.
|
||||
type ClientConfigService struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Path string
|
||||
}
|
||||
|
||||
// ClientManager builds REST clients to talk to webhooks. It caches the clients
|
||||
// to avoid duplicate creation.
|
||||
type ClientManager struct {
|
||||
authInfoResolver AuthenticationInfoResolver
|
||||
serviceResolver ServiceResolver
|
||||
negotiatedSerializer runtime.NegotiatedSerializer
|
||||
cache *lru.Cache
|
||||
}
|
||||
|
||||
// NewClientManager creates a clientManager.
|
||||
func NewClientManager(gv schema.GroupVersion, addToSchemaFunc func(s *runtime.Scheme) error) (ClientManager, error) {
|
||||
cache, err := lru.New(defaultCacheSize)
|
||||
if err != nil {
|
||||
return ClientManager{}, err
|
||||
}
|
||||
hookScheme := runtime.NewScheme()
|
||||
if err := addToSchemaFunc(hookScheme); err != nil {
|
||||
return ClientManager{}, err
|
||||
}
|
||||
return ClientManager{
|
||||
cache: cache,
|
||||
negotiatedSerializer: serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{
|
||||
Serializer: serializer.NewCodecFactory(hookScheme).LegacyCodec(gv),
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetAuthenticationInfoResolverWrapper sets the
|
||||
// AuthenticationInfoResolverWrapper.
|
||||
func (cm *ClientManager) SetAuthenticationInfoResolverWrapper(wrapper AuthenticationInfoResolverWrapper) {
|
||||
if wrapper != nil {
|
||||
cm.authInfoResolver = wrapper(cm.authInfoResolver)
|
||||
}
|
||||
}
|
||||
|
||||
// SetAuthenticationInfoResolver sets the AuthenticationInfoResolver.
|
||||
func (cm *ClientManager) SetAuthenticationInfoResolver(resolver AuthenticationInfoResolver) {
|
||||
cm.authInfoResolver = resolver
|
||||
}
|
||||
|
||||
// SetServiceResolver sets the ServiceResolver.
|
||||
func (cm *ClientManager) SetServiceResolver(sr ServiceResolver) {
|
||||
if sr != nil {
|
||||
cm.serviceResolver = sr
|
||||
}
|
||||
}
|
||||
|
||||
// Validate checks if ClientManager is properly set up.
|
||||
func (cm *ClientManager) Validate() error {
|
||||
var errs []error
|
||||
if cm.negotiatedSerializer == nil {
|
||||
errs = append(errs, fmt.Errorf("the clientManager requires a negotiatedSerializer"))
|
||||
}
|
||||
if cm.serviceResolver == nil {
|
||||
errs = append(errs, fmt.Errorf("the clientManager requires a serviceResolver"))
|
||||
}
|
||||
if cm.authInfoResolver == nil {
|
||||
errs = append(errs, fmt.Errorf("the clientManager requires an authInfoResolver"))
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// HookClient get a RESTClient from the cache, or constructs one based on the
|
||||
// webhook configuration.
|
||||
func (cm *ClientManager) HookClient(cc ClientConfig) (*rest.RESTClient, error) {
|
||||
ccWithNoName := cc
|
||||
ccWithNoName.Name = ""
|
||||
cacheKey, err := json.Marshal(ccWithNoName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if client, ok := cm.cache.Get(string(cacheKey)); ok {
|
||||
return client.(*rest.RESTClient), nil
|
||||
}
|
||||
|
||||
complete := func(cfg *rest.Config) (*rest.RESTClient, error) {
|
||||
// Combine CAData from the config with any existing CA bundle provided
|
||||
if len(cfg.TLSClientConfig.CAData) > 0 {
|
||||
cfg.TLSClientConfig.CAData = append(cfg.TLSClientConfig.CAData, '\n')
|
||||
}
|
||||
cfg.TLSClientConfig.CAData = append(cfg.TLSClientConfig.CAData, cc.CABundle...)
|
||||
|
||||
cfg.ContentConfig.NegotiatedSerializer = cm.negotiatedSerializer
|
||||
cfg.ContentConfig.ContentType = runtime.ContentTypeJSON
|
||||
client, err := rest.UnversionedRESTClientFor(cfg)
|
||||
if err == nil {
|
||||
cm.cache.Add(string(cacheKey), client)
|
||||
}
|
||||
return client, err
|
||||
}
|
||||
|
||||
if cc.Service != nil {
|
||||
restConfig, err := cm.authInfoResolver.ClientConfigForService(cc.Service.Name, cc.Service.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg := rest.CopyConfig(restConfig)
|
||||
serverName := cc.Service.Name + "." + cc.Service.Namespace + ".svc"
|
||||
host := serverName + ":443"
|
||||
cfg.Host = "https://" + host
|
||||
cfg.APIPath = cc.Service.Path
|
||||
// Set the server name if not already set
|
||||
if len(cfg.TLSClientConfig.ServerName) == 0 {
|
||||
cfg.TLSClientConfig.ServerName = serverName
|
||||
}
|
||||
|
||||
delegateDialer := cfg.Dial
|
||||
if delegateDialer == nil {
|
||||
var d net.Dialer
|
||||
delegateDialer = d.DialContext
|
||||
}
|
||||
cfg.Dial = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
if addr == host {
|
||||
u, err := cm.serviceResolver.ResolveEndpoint(cc.Service.Namespace, cc.Service.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr = u.Host
|
||||
}
|
||||
return delegateDialer(ctx, network, addr)
|
||||
}
|
||||
|
||||
return complete(cfg)
|
||||
}
|
||||
|
||||
if cc.URL == "" {
|
||||
return nil, &ErrCallingWebhook{WebhookName: cc.Name, Reason: errors.New("webhook configuration must have either service or URL")}
|
||||
}
|
||||
|
||||
u, err := url.Parse(cc.URL)
|
||||
if err != nil {
|
||||
return nil, &ErrCallingWebhook{WebhookName: cc.Name, Reason: fmt.Errorf("Unparsable URL: %v", err)}
|
||||
}
|
||||
|
||||
restConfig, err := cm.authInfoResolver.ClientConfigFor(u.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := rest.CopyConfig(restConfig)
|
||||
cfg.Host = u.Scheme + "://" + u.Host
|
||||
cfg.APIPath = u.Path
|
||||
|
||||
return complete(cfg)
|
||||
}
|
||||
34
vendor/k8s.io/apiserver/pkg/util/webhook/error.go
generated
vendored
Normal file
34
vendor/k8s.io/apiserver/pkg/util/webhook/error.go
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
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 webhook
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ErrCallingWebhook is returned for transport-layer errors calling webhooks. It
|
||||
// represents a failure to talk to the webhook, not the webhook rejecting a
|
||||
// request.
|
||||
type ErrCallingWebhook struct {
|
||||
WebhookName string
|
||||
Reason error
|
||||
}
|
||||
|
||||
func (e *ErrCallingWebhook) Error() string {
|
||||
if e.Reason != nil {
|
||||
return fmt.Sprintf("failed calling webhook %q: %v", e.WebhookName, e.Reason)
|
||||
}
|
||||
return fmt.Sprintf("failed calling webhook %q; no further details available", e.WebhookName)
|
||||
}
|
||||
16
vendor/k8s.io/apiserver/pkg/util/webhook/gencerts.sh
generated
vendored
16
vendor/k8s.io/apiserver/pkg/util/webhook/gencerts.sh
generated
vendored
|
|
@ -88,12 +88,12 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This file was generated using openssl by the gencerts.sh script
|
||||
// and holds raw certificates for the webhook tests.
|
||||
|
||||
package webhook
|
||||
EOF
|
||||
|
||||
echo "// This file was generated using openssl by the gencerts.sh script" >> $outfile
|
||||
echo "// and holds raw certificates for the webhook tests." >> $outfile
|
||||
echo "" >> $outfile
|
||||
echo "package webhook" >> $outfile
|
||||
for file in caKey caCert badCAKey badCACert serverKey serverCert clientKey clientCert; do
|
||||
data=$(cat ${file}.pem)
|
||||
echo "" >> $outfile
|
||||
|
|
@ -101,7 +101,7 @@ for file in caKey caCert badCAKey badCACert serverKey serverCert clientKey clien
|
|||
done
|
||||
|
||||
# Clean up after we're done.
|
||||
rm *.pem
|
||||
rm *.csr
|
||||
rm *.srl
|
||||
rm *.conf
|
||||
rm ./*.pem
|
||||
rm ./*.csr
|
||||
rm ./*.srl
|
||||
rm ./*.conf
|
||||
|
|
|
|||
46
vendor/k8s.io/apiserver/pkg/util/webhook/serviceresolver.go
generated
vendored
Normal file
46
vendor/k8s.io/apiserver/pkg/util/webhook/serviceresolver.go
generated
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// ServiceResolver knows how to convert a service reference into an actual location.
|
||||
type ServiceResolver interface {
|
||||
ResolveEndpoint(namespace, name string) (*url.URL, error)
|
||||
}
|
||||
|
||||
type defaultServiceResolver struct{}
|
||||
|
||||
// NewDefaultServiceResolver creates a new default server resolver.
|
||||
func NewDefaultServiceResolver() ServiceResolver {
|
||||
return &defaultServiceResolver{}
|
||||
}
|
||||
|
||||
// ResolveEndpoint constructs a service URL from a given namespace and name
|
||||
// note that the name and namespace are required and by default all created addresses use HTTPS scheme.
|
||||
// for example:
|
||||
// name=ross namespace=andromeda resolves to https://ross.andromeda.svc:443
|
||||
func (sr defaultServiceResolver) ResolveEndpoint(namespace, name string) (*url.URL, error) {
|
||||
if len(name) == 0 || len(namespace) == 0 {
|
||||
return nil, errors.New("cannot resolve an empty service name or namespace")
|
||||
}
|
||||
return &url.URL{Scheme: "https", Host: fmt.Sprintf("%s.%s.svc:443", name, namespace)}, nil
|
||||
}
|
||||
101
vendor/k8s.io/apiserver/pkg/util/webhook/validation.go
generated
vendored
Normal file
101
vendor/k8s.io/apiserver/pkg/util/webhook/validation.go
generated
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// ValidateWebhookURL validates webhook's URL.
|
||||
func ValidateWebhookURL(fldPath *field.Path, URL string, forceHttps bool) field.ErrorList {
|
||||
var allErrors field.ErrorList
|
||||
const form = "; desired format: https://host[/path]"
|
||||
if u, err := url.Parse(URL); err != nil {
|
||||
allErrors = append(allErrors, field.Required(fldPath, "url must be a valid URL: "+err.Error()+form))
|
||||
} else {
|
||||
if forceHttps && u.Scheme != "https" {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath, u.Scheme, "'https' is the only allowed URL scheme"+form))
|
||||
}
|
||||
if len(u.Host) == 0 {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath, u.Host, "host must be provided"+form))
|
||||
}
|
||||
if u.User != nil {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath, u.User.String(), "user information is not permitted in the URL"))
|
||||
}
|
||||
if len(u.Fragment) != 0 {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath, u.Fragment, "fragments are not permitted in the URL"))
|
||||
}
|
||||
if len(u.RawQuery) != 0 {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath, u.RawQuery, "query parameters are not permitted in the URL"))
|
||||
}
|
||||
}
|
||||
return allErrors
|
||||
}
|
||||
|
||||
func ValidateWebhookService(fldPath *field.Path, namespace, name string, path *string) field.ErrorList {
|
||||
var allErrors field.ErrorList
|
||||
|
||||
if len(name) == 0 {
|
||||
allErrors = append(allErrors, field.Required(fldPath.Child("name"), "service name is required"))
|
||||
}
|
||||
|
||||
if len(namespace) == 0 {
|
||||
allErrors = append(allErrors, field.Required(fldPath.Child("namespace"), "service namespace is required"))
|
||||
}
|
||||
|
||||
if path == nil {
|
||||
return allErrors
|
||||
}
|
||||
|
||||
// TODO: replace below with url.Parse + verifying that host is empty?
|
||||
|
||||
urlPath := *path
|
||||
if urlPath == "/" || len(urlPath) == 0 {
|
||||
return allErrors
|
||||
}
|
||||
if urlPath == "//" {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, "segment[0] may not be empty"))
|
||||
return allErrors
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(urlPath, "/") {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, "must start with a '/'"))
|
||||
}
|
||||
|
||||
urlPathToCheck := urlPath[1:]
|
||||
if strings.HasSuffix(urlPathToCheck, "/") {
|
||||
urlPathToCheck = urlPathToCheck[:len(urlPathToCheck)-1]
|
||||
}
|
||||
steps := strings.Split(urlPathToCheck, "/")
|
||||
for i, step := range steps {
|
||||
if len(step) == 0 {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, fmt.Sprintf("segment[%d] may not be empty", i)))
|
||||
continue
|
||||
}
|
||||
failures := validation.IsDNS1123Subdomain(step)
|
||||
for _, failure := range failures {
|
||||
allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, fmt.Sprintf("segment[%d]: %v", i, failure)))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrors
|
||||
}
|
||||
4
vendor/k8s.io/apiserver/pkg/util/webhook/webhook.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/util/webhook/webhook.go
generated
vendored
|
|
@ -37,7 +37,7 @@ const defaultRequestTimeout = 30 * time.Second
|
|||
|
||||
type GenericWebhook struct {
|
||||
RestClient *rest.RESTClient
|
||||
initialBackoff time.Duration
|
||||
InitialBackoff time.Duration
|
||||
}
|
||||
|
||||
// NewGenericWebhook creates a new GenericWebhook from the provided kubeconfig file.
|
||||
|
|
@ -83,7 +83,7 @@ func newGenericWebhook(scheme *runtime.Scheme, codecFactory serializer.CodecFact
|
|||
// it returns an error for which apierrors.SuggestsClientDelay() or apierrors.IsInternalError() returns true.
|
||||
func (g *GenericWebhook) WithExponentialBackoff(webhookFn func() rest.Result) rest.Result {
|
||||
var result rest.Result
|
||||
WithExponentialBackoff(g.initialBackoff, func() error {
|
||||
WithExponentialBackoff(g.InitialBackoff, func() error {
|
||||
result = webhookFn()
|
||||
return result.Error()
|
||||
})
|
||||
|
|
|
|||
10
vendor/k8s.io/apiserver/pkg/util/wsstream/conn.go
generated
vendored
10
vendor/k8s.io/apiserver/pkg/util/wsstream/conn.go
generated
vendored
|
|
@ -25,8 +25,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/net/websocket"
|
||||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
|
@ -137,7 +137,7 @@ type ChannelProtocolConfig struct {
|
|||
// channels.
|
||||
func NewDefaultChannelProtocols(channels []ChannelType) map[string]ChannelProtocolConfig {
|
||||
return map[string]ChannelProtocolConfig{
|
||||
"": {Binary: true, Channels: channels},
|
||||
"": {Binary: true, Channels: channels},
|
||||
ChannelWebSocketProtocol: {Binary: true, Channels: channels},
|
||||
Base64ChannelWebSocketProtocol: {Binary: false, Channels: channels},
|
||||
}
|
||||
|
|
@ -251,7 +251,7 @@ func (conn *Conn) handle(ws *websocket.Conn) {
|
|||
var data []byte
|
||||
if err := websocket.Message.Receive(ws, &data); err != nil {
|
||||
if err != io.EOF {
|
||||
glog.Errorf("Error on socket receive: %v", err)
|
||||
klog.Errorf("Error on socket receive: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
@ -264,11 +264,11 @@ func (conn *Conn) handle(ws *websocket.Conn) {
|
|||
}
|
||||
data = data[1:]
|
||||
if int(channel) >= len(conn.channels) {
|
||||
glog.V(6).Infof("Frame is targeted for a reader %d that is not valid, possible protocol error", channel)
|
||||
klog.V(6).Infof("Frame is targeted for a reader %d that is not valid, possible protocol error", channel)
|
||||
continue
|
||||
}
|
||||
if _, err := conn.channels[channel].DataFromSocket(data); err != nil {
|
||||
glog.Errorf("Unable to write frame to %d: %v\n%s", channel, err, string(data))
|
||||
klog.Errorf("Unable to write frame to %d: %v\n%s", channel, err, string(data))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
vendor/k8s.io/apiserver/pkg/util/wsstream/stream.go
generated
vendored
2
vendor/k8s.io/apiserver/pkg/util/wsstream/stream.go
generated
vendored
|
|
@ -48,7 +48,7 @@ type ReaderProtocolConfig struct {
|
|||
// subprotocols "", "channel.k8s.io", "base64.channel.k8s.io".
|
||||
func NewDefaultReaderProtocols() map[string]ReaderProtocolConfig {
|
||||
return map[string]ReaderProtocolConfig{
|
||||
"": {Binary: true},
|
||||
"": {Binary: true},
|
||||
binaryWebSocketProtocol: {Binary: true},
|
||||
base64BinaryWebSocketProtocol: {Binary: false},
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue