feat: kubesphere 4.0 (#6115)

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

---------

Signed-off-by: ci-bot <ci-bot@kubesphere.io>
Co-authored-by: ks-ci-bot <ks-ci-bot@example.com>
Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
KubeSphere CI Bot
2024-09-06 11:05:52 +08:00
committed by GitHub
parent b5015ec7b9
commit 447a51f08b
8557 changed files with 546695 additions and 1146174 deletions

View File

@@ -20,9 +20,9 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
openapiclient "k8s.io/client-go/openapi"
restclient "k8s.io/client-go/rest"
"k8s.io/kubectl/pkg/util/openapi"
"k8s.io/kubectl/pkg/validation"
@@ -61,9 +61,12 @@ type Factory interface {
UnstructuredClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error)
// Returns a schema that can validate objects stored on disk.
Validator(validationDirective string, verifier *resource.QueryParamVerifier) (validation.Schema, error)
// OpenAPISchema returns the parsed openapi schema definition
OpenAPISchema() (openapi.Resources, error)
// OpenAPIGetter returns a getter for the openapi schema document
OpenAPIGetter() discovery.OpenAPISchemaInterface
Validator(validationDirective string) (validation.Schema, error)
// Used for retrieving openapi v2 resources.
openapi.OpenAPIResourcesGetter
// OpenAPIV3Schema returns a client for fetching parsed schemas for
// any group version
OpenAPIV3Client() (openapiclient.Client, error)
}

View File

@@ -30,10 +30,11 @@ import (
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
openapiclient "k8s.io/client-go/openapi"
"k8s.io/client-go/openapi/cached"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubectl/pkg/util/openapi"
openapivalidation "k8s.io/kubectl/pkg/util/openapi/validation"
"k8s.io/kubectl/pkg/validation"
)
@@ -42,7 +43,7 @@ type factoryImpl struct {
// Caches OpenAPI document and parsed resources
openAPIParser *openapi.CachedOpenAPIParser
openAPIGetter *openapi.CachedOpenAPIGetter
oapi *openapi.CachedOpenAPIGetter
parser sync.Once
getter sync.Once
}
@@ -142,7 +143,7 @@ func (f *factoryImpl) UnstructuredClientForMapping(mapping *meta.RESTMapping) (r
return restclient.RESTClientFor(cfg)
}
func (f *factoryImpl) Validator(validationDirective string, verifier *resource.QueryParamVerifier) (validation.Schema, error) {
func (f *factoryImpl) Validator(validationDirective string) (validation.Schema, error) {
// client-side schema validation is only performed
// when the validationDirective is strict.
// If the directive is warn, we rely on the ParamVerifyingSchema
@@ -153,22 +154,34 @@ func (f *factoryImpl) Validator(validationDirective string, verifier *resource.Q
return validation.NullSchema{}, nil
}
resources, err := f.OpenAPISchema()
schema := validation.ConjunctiveSchema{
validation.NewSchemaValidation(f),
validation.NoDoubleKeySchema{},
}
dynamicClient, err := f.DynamicClient()
if err != nil {
return nil, err
}
schema := validation.ConjunctiveSchema{
openapivalidation.NewSchemaValidation(resources),
validation.NoDoubleKeySchema{},
// Create the FieldValidationVerifier for use in the ParamVerifyingSchema.
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return nil, err
}
return validation.NewParamVerifyingSchema(schema, verifier, string(validationDirective)), nil
// Memory-cache the OpenAPI V3 responses. The disk cache behavior is determined by
// the discovery client.
oapiV3Client := cached.NewClient(discoveryClient.OpenAPIV3())
queryParam := resource.QueryParamFieldValidation
primary := resource.NewQueryParamVerifierV3(dynamicClient, oapiV3Client, queryParam)
secondary := resource.NewQueryParamVerifier(dynamicClient, f.openAPIGetter(), queryParam)
fallback := resource.NewFallbackQueryParamVerifier(primary, secondary)
return validation.NewParamVerifyingSchema(schema, fallback, string(validationDirective)), nil
}
// OpenAPISchema returns metadata and structural information about
// Kubernetes object definitions.
func (f *factoryImpl) OpenAPISchema() (openapi.Resources, error) {
openAPIGetter := f.OpenAPIGetter()
openAPIGetter := f.openAPIGetter()
if openAPIGetter == nil {
return nil, errors.New("no openapi getter")
}
@@ -176,21 +189,30 @@ func (f *factoryImpl) OpenAPISchema() (openapi.Resources, error) {
// Lazily initialize the OpenAPIParser once
f.parser.Do(func() {
// Create the caching OpenAPIParser
f.openAPIParser = openapi.NewOpenAPIParser(f.OpenAPIGetter())
f.openAPIParser = openapi.NewOpenAPIParser(f.openAPIGetter())
})
// Delegate to the OpenAPIPArser
return f.openAPIParser.Parse()
}
func (f *factoryImpl) OpenAPIGetter() discovery.OpenAPISchemaInterface {
func (f *factoryImpl) openAPIGetter() discovery.OpenAPISchemaInterface {
discovery, err := f.clientGetter.ToDiscoveryClient()
if err != nil {
return nil
}
f.getter.Do(func() {
f.openAPIGetter = openapi.NewOpenAPIGetter(discovery)
f.oapi = openapi.NewOpenAPIGetter(discovery)
})
return f.openAPIGetter
return f.oapi
}
func (f *factoryImpl) OpenAPIV3Client() (openapiclient.Client, error) {
discovery, err := f.clientGetter.ToDiscoveryClient()
if err != nil {
return nil, err
}
return cached.NewClient(discovery.OpenAPIV3()), nil
}

View File

@@ -422,6 +422,30 @@ func GetPodRunningTimeoutFlag(cmd *cobra.Command) (time.Duration, error) {
return timeout, nil
}
type FeatureGate string
const (
ApplySet FeatureGate = "KUBECTL_APPLYSET"
CmdPluginAsSubcommand FeatureGate = "KUBECTL_ENABLE_CMD_SHADOW"
InteractiveDelete FeatureGate = "KUBECTL_INTERACTIVE_DELETE"
OpenAPIV3Patch FeatureGate = "KUBECTL_OPENAPIV3_PATCH"
RemoteCommandWebsockets FeatureGate = "KUBECTL_REMOTE_COMMAND_WEBSOCKETS"
)
// IsEnabled returns true iff environment variable is set to true.
// All other cases, it returns false.
func (f FeatureGate) IsEnabled() bool {
return strings.ToLower(os.Getenv(string(f))) == "true"
}
// IsDisabled returns true iff environment variable is set to false.
// All other cases, it returns true.
// This function is used for the cases where feature is enabled by default,
// but it may be needed to provide a way to ability to disable this feature.
func (f FeatureGate) IsDisabled() bool {
return strings.ToLower(os.Getenv(string(f))) == "false"
}
func AddValidateFlags(cmd *cobra.Command) {
cmd.Flags().String(
"validate",
@@ -499,8 +523,25 @@ func AddLabelSelectorFlagVar(cmd *cobra.Command, p *string) {
cmd.Flags().StringVarP(p, "selector", "l", *p, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Matching objects must satisfy all of the specified label constraints.")
}
func AddPruningFlags(cmd *cobra.Command, prune *bool, pruneAllowlist *[]string, pruneWhitelist *[]string, all *bool, applySetRef *string) {
// Flags associated with the original allowlist-based alpha
cmd.Flags().StringArrayVar(pruneAllowlist, "prune-allowlist", *pruneAllowlist, "Overwrite the default allowlist with <group/version/kind> for --prune")
cmd.Flags().StringArrayVar(pruneWhitelist, "prune-whitelist", *pruneWhitelist, "Overwrite the default whitelist with <group/version/kind> for --prune") // TODO: Remove this in kubectl 1.28 or later
_ = cmd.Flags().MarkDeprecated("prune-whitelist", "Use --prune-allowlist instead.")
cmd.Flags().BoolVar(all, "all", *all, "Select all resources in the namespace of the specified resource types.")
// Flags associated with the new ApplySet-based alpha
if ApplySet.IsEnabled() {
cmd.Flags().StringVar(applySetRef, "applyset", *applySetRef, "[alpha] The name of the ApplySet that tracks which resources are being managed, for the purposes of determining what to prune. Live resources that are part of the ApplySet but have been removed from the provided configs will be deleted. Format: [RESOURCE][.GROUP]/NAME. A Secret will be used if no resource or group is specified.")
cmd.Flags().BoolVar(prune, "prune", *prune, "Automatically delete previously applied resource objects that do not appear in the provided configs. For alpha1, use with either -l or --all. For alpha2, use with --applyset.")
} else {
// different docs for the shared --prune flag if only alpha1 is enabled
cmd.Flags().BoolVar(prune, "prune", *prune, "Automatically delete resource objects, that do not appear in the configs and are created by either apply or create --save-config. Should be used with either -l or --all.")
}
}
func AddSubresourceFlags(cmd *cobra.Command, subresource *string, usage string, allowedSubresources ...string) {
cmd.Flags().StringVar(subresource, "subresource", "", fmt.Sprintf("%s Must be one of %v. This flag is alpha and may change in the future.", usage, allowedSubresources))
cmd.Flags().StringVar(subresource, "subresource", "", fmt.Sprintf("%s Must be one of %v. This flag is beta and may change in the future.", usage, allowedSubresources))
CheckErr(cmd.RegisterFlagCompletionFunc("subresource", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
return allowedSubresources, cobra.ShellCompDirectiveNoFileComp
}))

View File

@@ -29,7 +29,7 @@ import (
authorizationv1 "k8s.io/api/authorization/v1"
authorizationv1beta1 "k8s.io/api/authorization/v1beta1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
certificatesv1 "k8s.io/api/certificates/v1"
@@ -69,7 +69,7 @@ func init() {
utilruntime.Must(Scheme.SetVersionPriority(appsv1beta1.SchemeGroupVersion, appsv1beta2.SchemeGroupVersion, appsv1.SchemeGroupVersion))
utilruntime.Must(Scheme.SetVersionPriority(authenticationv1.SchemeGroupVersion, authenticationv1beta1.SchemeGroupVersion))
utilruntime.Must(Scheme.SetVersionPriority(authorizationv1.SchemeGroupVersion, authorizationv1beta1.SchemeGroupVersion))
utilruntime.Must(Scheme.SetVersionPriority(autoscalingv1.SchemeGroupVersion, autoscalingv2beta1.SchemeGroupVersion))
utilruntime.Must(Scheme.SetVersionPriority(autoscalingv1.SchemeGroupVersion, autoscalingv2.SchemeGroupVersion))
utilruntime.Must(Scheme.SetVersionPriority(batchv1.SchemeGroupVersion, batchv1beta1.SchemeGroupVersion))
utilruntime.Must(Scheme.SetVersionPriority(certificatesv1.SchemeGroupVersion, certificatesv1beta1.SchemeGroupVersion))
utilruntime.Must(Scheme.SetVersionPriority(extensionsv1beta1.SchemeGroupVersion))

View File

@@ -24,8 +24,10 @@ import (
"fmt"
"os"
"strings"
"sync"
"github.com/chai2010/gettext-go"
gettext "github.com/chai2010/gettext-go"
"k8s.io/klog/v2"
)
@@ -52,6 +54,56 @@ var knownTranslations = map[string][]string{
},
}
var (
lazyLoadTranslationsOnce sync.Once
LoadTranslationsFunc = func() error {
return LoadTranslations("kubectl", nil)
}
translationsLoaded bool
)
// SetLoadTranslationsFunc sets the function called to lazy load translations.
// It must be called in an init() func that occurs BEFORE any i18n.T() calls are made by any package. You can
// accomplish this by creating a separate package containing your init() func, and then importing that package BEFORE
// any other packages that call i18n.T().
//
// Example Usage:
//
// package myi18n
//
// import "k8s.io/kubectl/pkg/util/i18n"
//
// func init() {
// if err := i18n.SetLoadTranslationsFunc(loadCustomTranslations); err != nil {
// panic(err)
// }
// }
//
// func loadCustomTranslations() error {
// // Load your custom translations here...
// }
//
// And then in your main or root command package, import your custom package like this:
//
// import (
// // Other imports that don't need i18n...
// _ "example.com/myapp/myi18n"
// // Other imports that do need i18n...
// )
func SetLoadTranslationsFunc(f func() error) error {
if translationsLoaded {
return errors.New("translations have already been loaded")
}
LoadTranslationsFunc = func() error {
if err := f(); err != nil {
return err
}
translationsLoaded = true
return nil
}
return nil
}
func loadSystemLanguage() string {
// Implements the following locale priority order: LC_ALL, LC_MESSAGES, LANG
// Similarly to: https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html
@@ -128,13 +180,26 @@ func LoadTranslations(root string, getLanguageFn func() string) error {
gettext.BindLocale(gettext.New("k8s", root+".zip", buf.Bytes()))
gettext.SetDomain("k8s")
gettext.SetLanguage(langStr)
translationsLoaded = true
return nil
}
func lazyLoadTranslations() {
lazyLoadTranslationsOnce.Do(func() {
if translationsLoaded {
return
}
if err := LoadTranslationsFunc(); err != nil {
klog.Warning("Failed to load translations")
}
})
}
// T translates a string, possibly substituting arguments into it along
// the way. If len(args) is > 0, args1 is assumed to be the plural value
// and plural translation is used.
func T(defaultValue string, args ...int) string {
lazyLoadTranslations()
if len(args) == 0 {
return gettext.PGettext("", defaultValue)
}

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# Copyright 2017 The Kubernetes Authors.
#

View File

@@ -712,14 +712,10 @@ msgid ""
"\n"
"\t\t# Create an interactive debugging session in pod mypod and immediately "
"attach to it.\n"
"\t\t# (requires the EphemeralContainers feature to be enabled in the "
"cluster)\n"
"\t\tkubectl debug mypod -it --image=busybox\n"
"\n"
"\t\t# Create a debug container named debugger using a custom automated "
"debugging image.\n"
"\t\t# (requires the EphemeralContainers feature to be enabled in the "
"cluster)\n"
"\t\tkubectl debug --image=myproj/debug-tools -c debugger mypod\n"
"\n"
"\t\t# Create a copy of mypod adding a debug container and attach to it\n"
@@ -746,14 +742,10 @@ msgstr ""
"\n"
"\t\t# Create an interactive debugging session in pod mypod and immediately "
"attach to it.\n"
"\t\t# (requires the EphemeralContainers feature to be enabled in the "
"cluster)\n"
"\t\tkubectl debug mypod -it --image=busybox\n"
"\n"
"\t\t# Create a debug container named debugger using a custom automated "
"debugging image.\n"
"\t\t# (requires the EphemeralContainers feature to be enabled in the "
"cluster)\n"
"\t\tkubectl debug --image=myproj/debug-tools -c debugger mypod\n"
"\n"
"\t\t# Create a copy of mypod adding a debug container and attach to it\n"

View File

@@ -712,14 +712,10 @@ msgid ""
"\n"
"\t\t# Create an interactive debugging session in pod mypod and immediately "
"attach to it.\n"
"\t\t# (requires the EphemeralContainers feature to be enabled in the "
"cluster)\n"
"\t\tkubectl debug mypod -it --image=busybox\n"
"\n"
"\t\t# Create a debug container named debugger using a custom automated "
"debugging image.\n"
"\t\t# (requires the EphemeralContainers feature to be enabled in the "
"cluster)\n"
"\t\tkubectl debug --image=myproj/debug-tools -c debugger mypod\n"
"\n"
"\t\t# Create a copy of mypod adding a debug container and attach to it\n"
@@ -746,14 +742,10 @@ msgstr ""
"\n"
"\t\t# Create an interactive debugging session in pod mypod and immediately "
"attach to it.\n"
"\t\t# (requires the EphemeralContainers feature to be enabled in the "
"cluster)\n"
"\t\tkubectl debug mypod -it --image=busybox\n"
"\n"
"\t\t# Create a debug container named debugger using a custom automated "
"debugging image.\n"
"\t\t# (requires the EphemeralContainers feature to be enabled in the "
"cluster)\n"
"\t\tkubectl debug --image=myproj/debug-tools -c debugger mypod\n"
"\n"
"\t\t# Create a copy of mypod adding a debug container and attach to it\n"

View File

@@ -2167,7 +2167,7 @@ msgstr "Kubectl controlla il gestore cluster di Kubernetes"
#~ msgstr ""
#~ "\n"
#~ "\t\tCrea un autoscaler che automaticamente sceglie e imposta il numero di "
#~ "pod che vengono eseguiti in un cluster di kubernets.\n"
#~ "pod che vengono eseguiti in un cluster di kubernetes.\n"
#~ "\n"
#~ "\t\tEsegue una ricerca di un Deployment, ReplicaSet o "
#~ "ReplicationController per nome e crea un autoscaler che utilizza la "

File diff suppressed because it is too large Load Diff

View File

@@ -31,7 +31,7 @@ msgid ""
msgstr ""
"\n"
"\t\t # 显示所有节点的指标\n"
"\t\t kubectl top ode\n"
"\t\t kubectl top node\n"
"\n"
"\t\t # 显示指定节点的指标\n"
"\t\t kubectl top node NODE_NAME"

View File

@@ -1,27 +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 openapi
import "k8s.io/kube-openapi/pkg/validation/spec"
// PrintColumnsKey is the key that defines which columns should be printed
const PrintColumnsKey = "x-kubernetes-print-columns"
// GetPrintColumns looks for the open API extension for the display columns.
func GetPrintColumns(extensions spec.Extensions) (string, bool) {
return extensions.GetString(PrintColumnsKey)
}

View File

@@ -17,13 +17,20 @@ limitations under the License.
package openapi
import (
openapi_v2 "github.com/google/gnostic/openapiv2"
openapi_v2 "github.com/google/gnostic-models/openapiv2"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/util/proto"
"sigs.k8s.io/yaml"
)
// OpenAPIResourcesGetter represents a function to return
// OpenAPI V2 resource specifications. Used for lazy-loading
// these resource specifications.
type OpenAPIResourcesGetter interface {
OpenAPISchema() (Resources, error)
}
// Resources interface describe a resources provider, that can give you
// resource based on group-version-kind.
type Resources interface {

View File

@@ -19,7 +19,7 @@ package openapi
import (
"sync"
openapi_v2 "github.com/google/gnostic/openapiv2"
openapi_v2 "github.com/google/gnostic-models/openapiv2"
"k8s.io/client-go/discovery"
)

View File

@@ -1,172 +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 resource
import (
"fmt"
"math"
"strconv"
"strings"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/sets"
)
// PodRequestsAndLimits returns a dictionary of all defined resources summed up for all
// containers of the pod. If pod overhead is non-nil, the pod overhead is added to the
// total container resource requests and to the total container limits which have a
// non-zero quantity.
func PodRequestsAndLimits(pod *corev1.Pod) (reqs, limits corev1.ResourceList) {
reqs, limits = corev1.ResourceList{}, corev1.ResourceList{}
for _, container := range pod.Spec.Containers {
addResourceList(reqs, container.Resources.Requests)
addResourceList(limits, container.Resources.Limits)
}
// init containers define the minimum of any resource
for _, container := range pod.Spec.InitContainers {
maxResourceList(reqs, container.Resources.Requests)
maxResourceList(limits, container.Resources.Limits)
}
// Add overhead for running a pod to the sum of requests and to non-zero limits:
if pod.Spec.Overhead != nil {
addResourceList(reqs, pod.Spec.Overhead)
for name, quantity := range pod.Spec.Overhead {
if value, ok := limits[name]; ok && !value.IsZero() {
value.Add(quantity)
limits[name] = value
}
}
}
return
}
// addResourceList adds the resources in newList to list
func addResourceList(list, new corev1.ResourceList) {
for name, quantity := range new {
if value, ok := list[name]; !ok {
list[name] = quantity.DeepCopy()
} else {
value.Add(quantity)
list[name] = value
}
}
}
// maxResourceList sets list to the greater of list/newList for every resource
// either list
func maxResourceList(list, new corev1.ResourceList) {
for name, quantity := range new {
if value, ok := list[name]; !ok {
list[name] = quantity.DeepCopy()
continue
} else {
if quantity.Cmp(value) > 0 {
list[name] = quantity.DeepCopy()
}
}
}
}
// ExtractContainerResourceValue extracts the value of a resource
// in an already known container
func ExtractContainerResourceValue(fs *corev1.ResourceFieldSelector, container *corev1.Container) (string, error) {
divisor := resource.Quantity{}
if divisor.Cmp(fs.Divisor) == 0 {
divisor = resource.MustParse("1")
} else {
divisor = fs.Divisor
}
switch fs.Resource {
case "limits.cpu":
return convertResourceCPUToString(container.Resources.Limits.Cpu(), divisor)
case "limits.memory":
return convertResourceMemoryToString(container.Resources.Limits.Memory(), divisor)
case "limits.ephemeral-storage":
return convertResourceEphemeralStorageToString(container.Resources.Limits.StorageEphemeral(), divisor)
case "requests.cpu":
return convertResourceCPUToString(container.Resources.Requests.Cpu(), divisor)
case "requests.memory":
return convertResourceMemoryToString(container.Resources.Requests.Memory(), divisor)
case "requests.ephemeral-storage":
return convertResourceEphemeralStorageToString(container.Resources.Requests.StorageEphemeral(), divisor)
}
// handle extended standard resources with dynamic names
// example: requests.hugepages-<pageSize> or limits.hugepages-<pageSize>
if strings.HasPrefix(fs.Resource, "requests.") {
resourceName := corev1.ResourceName(strings.TrimPrefix(fs.Resource, "requests."))
if IsHugePageResourceName(resourceName) {
return convertResourceHugePagesToString(container.Resources.Requests.Name(resourceName, resource.BinarySI), divisor)
}
}
if strings.HasPrefix(fs.Resource, "limits.") {
resourceName := corev1.ResourceName(strings.TrimPrefix(fs.Resource, "limits."))
if IsHugePageResourceName(resourceName) {
return convertResourceHugePagesToString(container.Resources.Limits.Name(resourceName, resource.BinarySI), divisor)
}
}
return "", fmt.Errorf("Unsupported container resource : %v", fs.Resource)
}
// convertResourceCPUToString converts cpu value to the format of divisor and returns
// ceiling of the value.
func convertResourceCPUToString(cpu *resource.Quantity, divisor resource.Quantity) (string, error) {
c := int64(math.Ceil(float64(cpu.MilliValue()) / float64(divisor.MilliValue())))
return strconv.FormatInt(c, 10), nil
}
// convertResourceMemoryToString converts memory value to the format of divisor and returns
// ceiling of the value.
func convertResourceMemoryToString(memory *resource.Quantity, divisor resource.Quantity) (string, error) {
m := int64(math.Ceil(float64(memory.Value()) / float64(divisor.Value())))
return strconv.FormatInt(m, 10), nil
}
// convertResourceHugePagesToString converts hugepages value to the format of divisor and returns
// ceiling of the value.
func convertResourceHugePagesToString(hugePages *resource.Quantity, divisor resource.Quantity) (string, error) {
m := int64(math.Ceil(float64(hugePages.Value()) / float64(divisor.Value())))
return strconv.FormatInt(m, 10), nil
}
// convertResourceEphemeralStorageToString converts ephemeral storage value to the format of divisor and returns
// ceiling of the value.
func convertResourceEphemeralStorageToString(ephemeralStorage *resource.Quantity, divisor resource.Quantity) (string, error) {
m := int64(math.Ceil(float64(ephemeralStorage.Value()) / float64(divisor.Value())))
return strconv.FormatInt(m, 10), nil
}
var standardContainerResources = sets.NewString(
string(corev1.ResourceCPU),
string(corev1.ResourceMemory),
string(corev1.ResourceEphemeralStorage),
)
// IsStandardContainerResourceName returns true if the container can make a resource request
// for the specified resource
func IsStandardContainerResourceName(str string) bool {
return standardContainerResources.Has(str) || IsHugePageResourceName(corev1.ResourceName(str))
}
// IsHugePageResourceName returns true if the resource name has the huge page
// resource prefix.
func IsHugePageResourceName(name corev1.ResourceName) bool {
return strings.HasPrefix(string(name), corev1.ResourceHugePagesPrefix)
}

View File

@@ -58,7 +58,9 @@ func (r *ASCIIRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering
lines := []string{}
for _, line := range strings.Split(string(node.Literal), linebreak) {
trimmed := strings.Trim(line, " \t")
indented := r.Indentation + trimmed
// Adding 4 times of indentation will let blackfriday to accept
// this literal as Code or CodeBlock again in next invocation
indented := strings.Repeat(r.Indentation, 4) + trimmed
lines = append(lines, indented)
}
w.Write([]byte(strings.Join(lines, linebreak)))

View File

@@ -126,6 +126,7 @@ func (templater *templater) templateFuncs(exposedFlags ...string) template.FuncM
"isRootCmd": templater.isRootCmd,
"optionsCmdFor": templater.optionsCmdFor,
"usageLine": templater.usageLine,
"reverseParentsNames": templater.reverseParentsNames,
"exposed": func(c *cobra.Command) *flag.FlagSet {
exposed := flag.NewFlagSet("exposed", flag.ContinueOnError)
if len(exposedFlags) > 0 {
@@ -172,6 +173,15 @@ func (t *templater) rootCmdName(c *cobra.Command) string {
return t.rootCmd(c).CommandPath()
}
func (t *templater) reverseParentsNames(c *cobra.Command) []string {
reverseParentsNames := []string{}
parents := t.parents(c)
for i := len(parents) - 1; i >= 0; i-- {
reverseParentsNames = append(reverseParentsNames, parents[i].Name())
}
return reverseParentsNames
}
func (t *templater) isRootCmd(c *cobra.Command) bool {
return t.rootCmd(c) == c
}

View File

@@ -28,7 +28,8 @@ const (
`{{$visibleFlags := visibleFlags (flagsNotIntersected .LocalFlags .PersistentFlags)}}` +
`{{$explicitlyExposedFlags := exposed .}}` +
`{{$optionsCmdFor := optionsCmdFor .}}` +
`{{$usageLine := usageLine .}}`
`{{$usageLine := usageLine .}}` +
`{{$reverseParentsNames := reverseParentsNames .}}`
// SectionAliases is the help template section that displays command aliases.
SectionAliases = `{{if gt .Aliases 0}}Aliases:
@@ -61,7 +62,7 @@ const (
{{end}}`
// SectionTipsHelp is the help template section that displays the '--help' hint.
SectionTipsHelp = `{{if .HasSubCommands}}Use "{{$rootCmd}} <command> --help" for more information about a given command.
SectionTipsHelp = `{{if .HasSubCommands}}Use "{{range $reverseParentsNames}}{{.}} {{end}}<command> --help" for more information about a given command.
{{end}}`
// SectionTipsGlobalOptions is the help template section that displays the 'options' hint for displaying global flags.

View File

@@ -19,7 +19,8 @@ package term
import (
"io"
"os"
"runtime"
"k8s.io/cli-runtime/pkg/printers"
"github.com/moby/term"
@@ -56,46 +57,23 @@ type TTY struct {
// IsTerminalIn returns true if t.In is a terminal. Does not check /dev/tty
// even if TryDev is set.
func (t TTY) IsTerminalIn() bool {
return IsTerminal(t.In)
return printers.IsTerminal(t.In)
}
// IsTerminalOut returns true if t.Out is a terminal. Does not check /dev/tty
// even if TryDev is set.
func (t TTY) IsTerminalOut() bool {
return IsTerminal(t.Out)
return printers.IsTerminal(t.Out)
}
// IsTerminal returns whether the passed object is a terminal or not
func IsTerminal(i interface{}) bool {
_, terminal := term.GetFdInfo(i)
return terminal
}
// IsTerminal returns whether the passed object is a terminal or not.
// Deprecated: use printers.IsTerminal instead.
var IsTerminal = printers.IsTerminal
// AllowsColorOutput returns true if the specified writer is a terminal and
// the process environment indicates color output is supported and desired.
func AllowsColorOutput(w io.Writer) bool {
if !IsTerminal(w) {
return false
}
// https://en.wikipedia.org/wiki/Computer_terminal#Dumb_terminals
if os.Getenv("TERM") == "dumb" {
return false
}
// https://no-color.org/
if _, nocolor := os.LookupEnv("NO_COLOR"); nocolor {
return false
}
// On Windows WT_SESSION is set by the modern terminal component.
// Older terminals have poor support for UTF-8, VT escape codes, etc.
if runtime.GOOS == "windows" && os.Getenv("WT_SESSION") == "" {
return false
}
return true
}
// Deprecated: use printers.AllowsColorOutput instead.
var AllowsColorOutput = printers.AllowsColorOutput
// Safe invokes the provided function and will attempt to ensure that when the
// function returns (or a termination signal is sent) that the terminal state

View File

@@ -26,7 +26,6 @@ import (
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/klog/v2"
schemavalidation "k8s.io/kubectl/pkg/util/openapi/validation"
)
// Schema is an interface that knows how to validate an API object serialized to a byte array.
@@ -127,12 +126,12 @@ type paramVerifyingSchema struct {
// ValidateBytes validates bytes per a ParamVerifyingSchema
func (c *paramVerifyingSchema) ValidateBytes(data []byte) error {
obj, err := schemavalidation.Parse(data)
obj, err := parse(data)
if err != nil {
return err
}
gvk, errs := schemavalidation.GetObjectKind(obj)
gvk, errs := getObjectKind(obj)
if errs != nil {
return utilerrors.NewAggregate(errs)
}

View File

@@ -27,28 +27,28 @@ import (
"k8s.io/kubectl/pkg/util/openapi"
)
// SchemaValidation validates the object against an OpenAPI schema.
type SchemaValidation struct {
resources openapi.Resources
// schemaValidation validates the object against an OpenAPI schema.
type schemaValidation struct {
resourcesGetter openapi.OpenAPIResourcesGetter
}
// NewSchemaValidation creates a new SchemaValidation that can be used
// NewSchemaValidation creates a new Schema that can be used
// to validate objects.
func NewSchemaValidation(resources openapi.Resources) *SchemaValidation {
return &SchemaValidation{
resources: resources,
func NewSchemaValidation(resourcesGetter openapi.OpenAPIResourcesGetter) Schema {
return &schemaValidation{
resourcesGetter: resourcesGetter,
}
}
// ValidateBytes will validates the object against using the Resources
// object.
func (v *SchemaValidation) ValidateBytes(data []byte) error {
obj, err := Parse(data)
func (v *schemaValidation) ValidateBytes(data []byte) error {
obj, err := parse(data)
if err != nil {
return err
}
gvk, errs := GetObjectKind(obj)
gvk, errs := getObjectKind(obj)
if errs != nil {
return utilerrors.NewAggregate(errs)
}
@@ -56,11 +56,10 @@ func (v *SchemaValidation) ValidateBytes(data []byte) error {
if (gvk == schema.GroupVersionKind{Version: "v1", Kind: "List"}) {
return utilerrors.NewAggregate(v.validateList(obj))
}
return utilerrors.NewAggregate(v.validateResource(obj, gvk))
}
func (v *SchemaValidation) validateList(object interface{}) []error {
func (v *schemaValidation) validateList(object interface{}) []error {
fields, ok := object.(map[string]interface{})
if !ok || fields == nil {
return []error{errors.New("invalid object to validate")}
@@ -71,7 +70,7 @@ func (v *SchemaValidation) validateList(object interface{}) []error {
return []error{errors.New("invalid object to validate")}
}
for _, item := range fields["items"].([]interface{}) {
if gvk, errs := GetObjectKind(item); errs != nil {
if gvk, errs := getObjectKind(item); errs != nil {
allErrors = append(allErrors, errs...)
} else {
allErrors = append(allErrors, v.validateResource(item, gvk)...)
@@ -80,8 +79,13 @@ func (v *SchemaValidation) validateList(object interface{}) []error {
return allErrors
}
func (v *SchemaValidation) validateResource(obj interface{}, gvk schema.GroupVersionKind) []error {
resource := v.resources.LookupResource(gvk)
func (v *schemaValidation) validateResource(obj interface{}, gvk schema.GroupVersionKind) []error {
// This lazy-loads the OpenAPI V2 specifications, caching the specs.
resources, err := v.resourcesGetter.OpenAPISchema()
if err != nil {
return []error{err}
}
resource := resources.LookupResource(gvk)
if resource == nil {
// resource is not present, let's just skip validation.
return nil
@@ -90,7 +94,7 @@ func (v *SchemaValidation) validateResource(obj interface{}, gvk schema.GroupVer
return validation.ValidateModel(obj, resource, gvk.Kind)
}
func Parse(data []byte) (interface{}, error) {
func parse(data []byte) (interface{}, error) {
var obj interface{}
out, err := yaml.ToJSON(data)
if err != nil {
@@ -102,7 +106,7 @@ func Parse(data []byte) (interface{}, error) {
return obj, nil
}
func GetObjectKind(object interface{}) (schema.GroupVersionKind, []error) {
func getObjectKind(object interface{}) (schema.GroupVersionKind, []error) {
var listErrors []error
fields, ok := object.(map[string]interface{})
if !ok || fields == nil {