Introduce kuberc as new flag to customize defaulting and define aliases in kubectl (#125230)

This commit is contained in:
Arda Güçlü 2025-02-11 23:05:58 +03:00 committed by GitHub
parent 3d342e9b74
commit c7a90b670c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 4603 additions and 0 deletions

View File

@ -1241,6 +1241,10 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"k8s.io/kube-scheduler/config/v1.ScoringStrategy": schema_k8sio_kube_scheduler_config_v1_ScoringStrategy(ref),
"k8s.io/kube-scheduler/config/v1.UtilizationShapePoint": schema_k8sio_kube_scheduler_config_v1_UtilizationShapePoint(ref),
"k8s.io/kube-scheduler/config/v1.VolumeBindingArgs": schema_k8sio_kube_scheduler_config_v1_VolumeBindingArgs(ref),
"k8s.io/kubectl/pkg/config/v1alpha1.AliasOverride": schema_kubectl_pkg_config_v1alpha1_AliasOverride(ref),
"k8s.io/kubectl/pkg/config/v1alpha1.CommandOverride": schema_kubectl_pkg_config_v1alpha1_CommandOverride(ref),
"k8s.io/kubectl/pkg/config/v1alpha1.CommandOverrideFlag": schema_kubectl_pkg_config_v1alpha1_CommandOverrideFlag(ref),
"k8s.io/kubectl/pkg/config/v1alpha1.Preference": schema_kubectl_pkg_config_v1alpha1_Preference(ref),
"k8s.io/kubelet/config/v1.CredentialProvider": schema_k8sio_kubelet_config_v1_CredentialProvider(ref),
"k8s.io/kubelet/config/v1.CredentialProviderConfig": schema_k8sio_kubelet_config_v1_CredentialProviderConfig(ref),
"k8s.io/kubelet/config/v1.ExecEnvVar": schema_k8sio_kubelet_config_v1_ExecEnvVar(ref),
@ -63549,6 +63553,238 @@ func schema_k8sio_kube_scheduler_config_v1_VolumeBindingArgs(ref common.Referenc
}
}
func schema_kubectl_pkg_config_v1alpha1_AliasOverride(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "AliasOverride stores the alias definitions.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"name": {
SchemaProps: spec.SchemaProps{
Description: "Name is the name of alias that can only include alphabetical characters If the alias name conflicts with the built-in command, built-in command will be used.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"command": {
SchemaProps: spec.SchemaProps{
Description: "Command is the single or set of commands to execute, such as \"set env\" or \"create\"",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"prependArgs": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "PrependArgs stores the arguments such as resource names, etc. These arguments are inserted after the alias name.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
"appendArgs": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "AppendArgs stores the arguments such as resource names, etc. These arguments are appended to the USER_ARGS.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
"flags": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "Flag is allocated to store the flag definitions of alias. Flag only modifies the default value of the flag and if user explicitly passes a value, explicit one is used.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/kubectl/pkg/config/v1alpha1.CommandOverrideFlag"),
},
},
},
},
},
},
Required: []string{"name", "command"},
},
},
Dependencies: []string{
"k8s.io/kubectl/pkg/config/v1alpha1.CommandOverrideFlag"},
}
}
func schema_kubectl_pkg_config_v1alpha1_CommandOverride(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "CommandOverride stores the commands and their associated flag's default values.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"command": {
SchemaProps: spec.SchemaProps{
Description: "Command refers to a command whose flag's default value is changed.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"flags": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "Flags is a list of flags storing different default values.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/kubectl/pkg/config/v1alpha1.CommandOverrideFlag"),
},
},
},
},
},
},
Required: []string{"command", "flags"},
},
},
Dependencies: []string{
"k8s.io/kubectl/pkg/config/v1alpha1.CommandOverrideFlag"},
}
}
func schema_kubectl_pkg_config_v1alpha1_CommandOverrideFlag(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "CommandOverrideFlag stores the name and the specified default value of the flag.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"name": {
SchemaProps: spec.SchemaProps{
Description: "Flag name (long form, without dashes).",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"default": {
SchemaProps: spec.SchemaProps{
Description: "In a string format of a default value. It will be parsed by kubectl to the compatible value of the flag.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"name", "default"},
},
},
}
}
func schema_kubectl_pkg_config_v1alpha1_Preference(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "Preference stores elements of KubeRC configuration file",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"overrides": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "overrides allows changing default flag values of commands. This is especially useful, when user doesn't want to explicitly set flags each time.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/kubectl/pkg/config/v1alpha1.CommandOverride"),
},
},
},
},
},
"aliases": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "aliases allows defining command aliases for existing kubectl commands, with optional default flag values. If the alias name collides with a built-in command, built-in command always takes precedence. Flag overrides defined in the overrides section do NOT apply to aliases for the same command. kubectl [ALIAS NAME] [USER_FLAGS] [USER_EXPLICIT_ARGS] expands to kubectl [COMMAND] # built-in command alias points to\n [KUBERC_PREPEND_ARGS]\n [USER_FLAGS]\n [KUBERC_FLAGS] # rest of the flags that are not passed by user in [USER_FLAGS]\n [USER_EXPLICIT_ARGS]\n [KUBERC_APPEND_ARGS]\ne.g. - name: runx\n command: run\n flags:\n - name: image\n default: nginx\n appendArgs:\n - --\n - custom-arg1\nFor example, if user invokes \"kubectl runx test-pod\" command, this will be expanded to \"kubectl run --image=nginx test-pod -- custom-arg1\" - name: getn\n command: get\n flags:\n - name: output\n default: wide\n prependArgs:\n - node\n\"kubectl getn control-plane-1\" expands to \"kubectl get node control-plane-1 --output=wide\" \"kubectl getn control-plane-1 --output=json\" expands to \"kubectl get node --output=json control-plane-1\"",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/kubectl/pkg/config/v1alpha1.AliasOverride"),
},
},
},
},
},
},
Required: []string{"overrides", "aliases"},
},
},
Dependencies: []string{
"k8s.io/kubectl/pkg/config/v1alpha1.AliasOverride", "k8s.io/kubectl/pkg/config/v1alpha1.CommandOverride"},
}
}
func schema_k8sio_kubelet_config_v1_CredentialProvider(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{

View File

@ -73,6 +73,7 @@ import (
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/cmd/version"
"k8s.io/kubectl/pkg/cmd/wait"
"k8s.io/kubectl/pkg/kuberc"
utilcomp "k8s.io/kubectl/pkg/util/completion"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
@ -361,6 +362,11 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
flags.BoolVar(&warningsAsErrors, "warnings-as-errors", warningsAsErrors, "Treat warnings received from the server as errors and exit with a non-zero exit code")
pref := kuberc.NewPreferences()
if cmdutil.KubeRC.IsEnabled() {
pref.AddFlags(flags)
}
kubeConfigFlags := o.ConfigFlags
if kubeConfigFlags == nil {
kubeConfigFlags = defaultConfigFlags().WithWarningPrinter(o.IOStreams)
@ -490,6 +496,15 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
// Stop warning about normalization of flags. That makes it possible to
// add the klog flags later.
cmds.SetGlobalNormalizationFunc(cliflag.WordSepNormalizeFunc)
if cmdutil.KubeRC.IsEnabled() {
_, err := pref.Apply(cmds, o.Arguments, o.IOStreams.ErrOut)
if err != nil {
fmt.Fprintf(o.IOStreams.ErrOut, "error occurred while applying preferences %v\n", err)
os.Exit(1)
}
}
return cmds
}

View File

@ -432,6 +432,7 @@ const (
PortForwardWebsockets FeatureGate = "KUBECTL_PORT_FORWARD_WEBSOCKETS"
// DebugCustomProfile should be dropped in 1.34
DebugCustomProfile FeatureGate = "KUBECTL_DEBUG_CUSTOM_PROFILE"
KubeRC FeatureGate = "KUBECTL_KUBERC"
)
// IsEnabled returns true iff environment variable is set to true.

View File

@ -0,0 +1,12 @@
# See the OWNERS docs at https://go.k8s.io/owners
# Disable inheritance as this is an api owners file
options:
no_parent_owners: true
approvers:
- api-approvers
reviewers:
- api-reviewers
- sig-cli-reviewers
labels:
- kind/api-change

View File

@ -0,0 +1,20 @@
/*
Copyright 2024 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.
*/
// +k8s:deepcopy-gen=package
// +groupName=kubectl.config.k8s.io
package config // Package config import "k8s.io/kubectl/pkg/config"

View File

@ -0,0 +1,32 @@
/*
Copyright 2024 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 install installs the experimental API group, making it available as
// an option to all of the API encoding/decoding machinery.
package install
import (
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/kubectl/pkg/config"
"k8s.io/kubectl/pkg/config/v1alpha1"
)
// Install registers the API group and adds types to a scheme
func Install(scheme *runtime.Scheme) {
utilruntime.Must(config.AddToScheme(scheme))
utilruntime.Must(v1alpha1.AddToScheme(scheme))
}

View File

@ -0,0 +1,44 @@
/*
Copyright 2024 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 config
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name used in this package
const GroupName = "kubectl.config.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
var (
// SchemeBuilder is the scheme builder with scheme init functions to run for this API package
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
// AddToScheme is a global function that registers this API group & version to a scheme
AddToScheme = SchemeBuilder.AddToScheme
)
// addKnownTypes registers known types to the given scheme
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Preference{},
)
return nil
}

View File

@ -0,0 +1,103 @@
/*
Copyright 2024 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 config
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Preference stores elements of KubeRC configuration file
type Preference struct {
metav1.TypeMeta
// overrides allows changing default flag values of commands.
// This is especially useful, when user doesn't want to explicitly
// set flags each time.
// +optional
Overrides []CommandOverride
// aliases allows defining command aliases for existing kubectl commands, with optional default flag values.
// If the alias name collides with a built-in command, built-in command always takes precedence.
// Flag overrides defined in the overrides section do NOT apply to aliases for the same command.
// kubectl [ALIAS NAME] [USER_FLAGS] [USER_EXPLICIT_ARGS] expands to
// kubectl [COMMAND] # built-in command alias points to
// [KUBERC_PREPEND_ARGS]
// [USER_FLAGS]
// [KUBERC_FLAGS] # rest of the flags that are not passed by user in [USER_FLAGS]
// [USER_EXPLICIT_ARGS]
// [KUBERC_APPEND_ARGS]
// e.g.
// - name: runx
// command: run
// flags:
// - name: image
// default: nginx
// appendArgs:
// - --
// - custom-arg1
// For example, if user invokes "kubectl runx test-pod" command,
// this will be expanded to "kubectl run --image=nginx test-pod -- custom-arg1"
// - name: getn
// command: get
// flags:
// - name: output
// default: wide
// prependArgs:
// - node
// "kubectl getn control-plane-1" expands to "kubectl get node control-plane-1 --output=wide"
// "kubectl getn control-plane-1 --output=json" expands to "kubectl get node --output=json control-plane-1"
// +optional
Aliases []AliasOverride
}
// AliasOverride stores the alias definitions.
type AliasOverride struct {
// Name is the name of alias that can only include alphabetical characters
// If the alias name conflicts with the built-in command,
// built-in command will be used.
Name string
// Command is the single or set of commands to execute, such as "set env" or "create"
Command string
// PrependArgs stores the arguments such as resource names, etc.
// These arguments are inserted after the alias name.
PrependArgs []string
// AppendArgs stores the arguments such as resource names, etc.
// These arguments are appended to the USER_ARGS.
AppendArgs []string
// Flag is allocated to store the flag definitions of alias
Flags []CommandOverrideFlag
}
// CommandOverride stores the commands and their associated flag's
// default values.
type CommandOverride struct {
// Command refers to a command whose flag's default value is changed.
Command string
// Flags is a list of flags storing different default values.
Flags []CommandOverrideFlag
}
// CommandOverrideFlag stores the name and the specified default
// value of the flag.
type CommandOverrideFlag struct {
// Flag name (long form, without dashes).
Name string `json:"name"`
// In a string format of a default value. It will be parsed
// by kubectl to the compatible value of the flag.
Default string `json:"default"`
}

View File

@ -0,0 +1,23 @@
/*
Copyright 2024 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.
*/
// +k8s:deepcopy-gen=package
// +k8s:openapi-gen=true
// +groupName=kubectl.config.k8s.io
// +k8s:conversion-gen=k8s.io/kubectl/pkg/config
// +k8s:defaulter-gen=TypeMeta
package v1alpha1 // Package v1alpha1 import "k8s.io/kubectl/pkg/config/v1alpha1"

View File

@ -0,0 +1,50 @@
/*
Copyright 2024 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 v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name used in this package
const GroupName = "kubectl.config.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
var (
SchemeBuilder runtime.SchemeBuilder
localSchemeBuilder = &SchemeBuilder
AddToScheme = localSchemeBuilder.AddToScheme
)
func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes)
}
// addKnownTypes registers known types to the given scheme
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Preference{},
)
return nil
}

View File

@ -0,0 +1,109 @@
/*
Copyright 2024 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 v1alpha1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Preference stores elements of KubeRC configuration file
type Preference struct {
metav1.TypeMeta `json:",inline"`
// overrides allows changing default flag values of commands.
// This is especially useful, when user doesn't want to explicitly
// set flags each time.
// +listType=atomic
Overrides []CommandOverride `json:"overrides"`
// aliases allows defining command aliases for existing kubectl commands, with optional default flag values.
// If the alias name collides with a built-in command, built-in command always takes precedence.
// Flag overrides defined in the overrides section do NOT apply to aliases for the same command.
// kubectl [ALIAS NAME] [USER_FLAGS] [USER_EXPLICIT_ARGS] expands to
// kubectl [COMMAND] # built-in command alias points to
// [KUBERC_PREPEND_ARGS]
// [USER_FLAGS]
// [KUBERC_FLAGS] # rest of the flags that are not passed by user in [USER_FLAGS]
// [USER_EXPLICIT_ARGS]
// [KUBERC_APPEND_ARGS]
// e.g.
// - name: runx
// command: run
// flags:
// - name: image
// default: nginx
// appendArgs:
// - --
// - custom-arg1
// For example, if user invokes "kubectl runx test-pod" command,
// this will be expanded to "kubectl run --image=nginx test-pod -- custom-arg1"
// - name: getn
// command: get
// flags:
// - name: output
// default: wide
// prependArgs:
// - node
// "kubectl getn control-plane-1" expands to "kubectl get node control-plane-1 --output=wide"
// "kubectl getn control-plane-1 --output=json" expands to "kubectl get node --output=json control-plane-1"
// +listType=atomic
Aliases []AliasOverride `json:"aliases"`
}
// AliasOverride stores the alias definitions.
type AliasOverride struct {
// Name is the name of alias that can only include alphabetical characters
// If the alias name conflicts with the built-in command,
// built-in command will be used.
Name string `json:"name"`
// Command is the single or set of commands to execute, such as "set env" or "create"
Command string `json:"command"`
// PrependArgs stores the arguments such as resource names, etc.
// These arguments are inserted after the alias name.
// +listType=atomic
PrependArgs []string `json:"prependArgs,omitempty"`
// AppendArgs stores the arguments such as resource names, etc.
// These arguments are appended to the USER_ARGS.
// +listType=atomic
AppendArgs []string `json:"appendArgs,omitempty"`
// Flag is allocated to store the flag definitions of alias.
// Flag only modifies the default value of the flag and if
// user explicitly passes a value, explicit one is used.
// +listType=atomic
Flags []CommandOverrideFlag `json:"flags,omitempty"`
}
// CommandOverride stores the commands and their associated flag's
// default values.
type CommandOverride struct {
// Command refers to a command whose flag's default value is changed.
Command string `json:"command"`
// Flags is a list of flags storing different default values.
// +listType=atomic
Flags []CommandOverrideFlag `json:"flags"`
}
// CommandOverrideFlag stores the name and the specified default
// value of the flag.
type CommandOverrideFlag struct {
// Flag name (long form, without dashes).
Name string `json:"name"`
// In a string format of a default value. It will be parsed
// by kubectl to the compatible value of the flag.
Default string `json:"default"`
}

View File

@ -0,0 +1,174 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by conversion-gen. DO NOT EDIT.
package v1alpha1
import (
unsafe "unsafe"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
config "k8s.io/kubectl/pkg/config"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
if err := s.AddGeneratedConversionFunc((*AliasOverride)(nil), (*config.AliasOverride)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_AliasOverride_To_config_AliasOverride(a.(*AliasOverride), b.(*config.AliasOverride), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.AliasOverride)(nil), (*AliasOverride)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_AliasOverride_To_v1alpha1_AliasOverride(a.(*config.AliasOverride), b.(*AliasOverride), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*CommandOverride)(nil), (*config.CommandOverride)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_CommandOverride_To_config_CommandOverride(a.(*CommandOverride), b.(*config.CommandOverride), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.CommandOverride)(nil), (*CommandOverride)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_CommandOverride_To_v1alpha1_CommandOverride(a.(*config.CommandOverride), b.(*CommandOverride), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*CommandOverrideFlag)(nil), (*config.CommandOverrideFlag)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_CommandOverrideFlag_To_config_CommandOverrideFlag(a.(*CommandOverrideFlag), b.(*config.CommandOverrideFlag), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.CommandOverrideFlag)(nil), (*CommandOverrideFlag)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_CommandOverrideFlag_To_v1alpha1_CommandOverrideFlag(a.(*config.CommandOverrideFlag), b.(*CommandOverrideFlag), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*Preference)(nil), (*config.Preference)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha1_Preference_To_config_Preference(a.(*Preference), b.(*config.Preference), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.Preference)(nil), (*Preference)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_Preference_To_v1alpha1_Preference(a.(*config.Preference), b.(*Preference), scope)
}); err != nil {
return err
}
return nil
}
func autoConvert_v1alpha1_AliasOverride_To_config_AliasOverride(in *AliasOverride, out *config.AliasOverride, s conversion.Scope) error {
out.Name = in.Name
out.Command = in.Command
out.PrependArgs = *(*[]string)(unsafe.Pointer(&in.PrependArgs))
out.AppendArgs = *(*[]string)(unsafe.Pointer(&in.AppendArgs))
out.Flags = *(*[]config.CommandOverrideFlag)(unsafe.Pointer(&in.Flags))
return nil
}
// Convert_v1alpha1_AliasOverride_To_config_AliasOverride is an autogenerated conversion function.
func Convert_v1alpha1_AliasOverride_To_config_AliasOverride(in *AliasOverride, out *config.AliasOverride, s conversion.Scope) error {
return autoConvert_v1alpha1_AliasOverride_To_config_AliasOverride(in, out, s)
}
func autoConvert_config_AliasOverride_To_v1alpha1_AliasOverride(in *config.AliasOverride, out *AliasOverride, s conversion.Scope) error {
out.Name = in.Name
out.Command = in.Command
out.PrependArgs = *(*[]string)(unsafe.Pointer(&in.PrependArgs))
out.AppendArgs = *(*[]string)(unsafe.Pointer(&in.AppendArgs))
out.Flags = *(*[]CommandOverrideFlag)(unsafe.Pointer(&in.Flags))
return nil
}
// Convert_config_AliasOverride_To_v1alpha1_AliasOverride is an autogenerated conversion function.
func Convert_config_AliasOverride_To_v1alpha1_AliasOverride(in *config.AliasOverride, out *AliasOverride, s conversion.Scope) error {
return autoConvert_config_AliasOverride_To_v1alpha1_AliasOverride(in, out, s)
}
func autoConvert_v1alpha1_CommandOverride_To_config_CommandOverride(in *CommandOverride, out *config.CommandOverride, s conversion.Scope) error {
out.Command = in.Command
out.Flags = *(*[]config.CommandOverrideFlag)(unsafe.Pointer(&in.Flags))
return nil
}
// Convert_v1alpha1_CommandOverride_To_config_CommandOverride is an autogenerated conversion function.
func Convert_v1alpha1_CommandOverride_To_config_CommandOverride(in *CommandOverride, out *config.CommandOverride, s conversion.Scope) error {
return autoConvert_v1alpha1_CommandOverride_To_config_CommandOverride(in, out, s)
}
func autoConvert_config_CommandOverride_To_v1alpha1_CommandOverride(in *config.CommandOverride, out *CommandOverride, s conversion.Scope) error {
out.Command = in.Command
out.Flags = *(*[]CommandOverrideFlag)(unsafe.Pointer(&in.Flags))
return nil
}
// Convert_config_CommandOverride_To_v1alpha1_CommandOverride is an autogenerated conversion function.
func Convert_config_CommandOverride_To_v1alpha1_CommandOverride(in *config.CommandOverride, out *CommandOverride, s conversion.Scope) error {
return autoConvert_config_CommandOverride_To_v1alpha1_CommandOverride(in, out, s)
}
func autoConvert_v1alpha1_CommandOverrideFlag_To_config_CommandOverrideFlag(in *CommandOverrideFlag, out *config.CommandOverrideFlag, s conversion.Scope) error {
out.Name = in.Name
out.Default = in.Default
return nil
}
// Convert_v1alpha1_CommandOverrideFlag_To_config_CommandOverrideFlag is an autogenerated conversion function.
func Convert_v1alpha1_CommandOverrideFlag_To_config_CommandOverrideFlag(in *CommandOverrideFlag, out *config.CommandOverrideFlag, s conversion.Scope) error {
return autoConvert_v1alpha1_CommandOverrideFlag_To_config_CommandOverrideFlag(in, out, s)
}
func autoConvert_config_CommandOverrideFlag_To_v1alpha1_CommandOverrideFlag(in *config.CommandOverrideFlag, out *CommandOverrideFlag, s conversion.Scope) error {
out.Name = in.Name
out.Default = in.Default
return nil
}
// Convert_config_CommandOverrideFlag_To_v1alpha1_CommandOverrideFlag is an autogenerated conversion function.
func Convert_config_CommandOverrideFlag_To_v1alpha1_CommandOverrideFlag(in *config.CommandOverrideFlag, out *CommandOverrideFlag, s conversion.Scope) error {
return autoConvert_config_CommandOverrideFlag_To_v1alpha1_CommandOverrideFlag(in, out, s)
}
func autoConvert_v1alpha1_Preference_To_config_Preference(in *Preference, out *config.Preference, s conversion.Scope) error {
out.Overrides = *(*[]config.CommandOverride)(unsafe.Pointer(&in.Overrides))
out.Aliases = *(*[]config.AliasOverride)(unsafe.Pointer(&in.Aliases))
return nil
}
// Convert_v1alpha1_Preference_To_config_Preference is an autogenerated conversion function.
func Convert_v1alpha1_Preference_To_config_Preference(in *Preference, out *config.Preference, s conversion.Scope) error {
return autoConvert_v1alpha1_Preference_To_config_Preference(in, out, s)
}
func autoConvert_config_Preference_To_v1alpha1_Preference(in *config.Preference, out *Preference, s conversion.Scope) error {
out.Overrides = *(*[]CommandOverride)(unsafe.Pointer(&in.Overrides))
out.Aliases = *(*[]AliasOverride)(unsafe.Pointer(&in.Aliases))
return nil
}
// Convert_config_Preference_To_v1alpha1_Preference is an autogenerated conversion function.
func Convert_config_Preference_To_v1alpha1_Preference(in *config.Preference, out *Preference, s conversion.Scope) error {
return autoConvert_config_Preference_To_v1alpha1_Preference(in, out, s)
}

View File

@ -0,0 +1,133 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AliasOverride) DeepCopyInto(out *AliasOverride) {
*out = *in
if in.PrependArgs != nil {
in, out := &in.PrependArgs, &out.PrependArgs
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AppendArgs != nil {
in, out := &in.AppendArgs, &out.AppendArgs
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Flags != nil {
in, out := &in.Flags, &out.Flags
*out = make([]CommandOverrideFlag, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AliasOverride.
func (in *AliasOverride) DeepCopy() *AliasOverride {
if in == nil {
return nil
}
out := new(AliasOverride)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommandOverride) DeepCopyInto(out *CommandOverride) {
*out = *in
if in.Flags != nil {
in, out := &in.Flags, &out.Flags
*out = make([]CommandOverrideFlag, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOverride.
func (in *CommandOverride) DeepCopy() *CommandOverride {
if in == nil {
return nil
}
out := new(CommandOverride)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommandOverrideFlag) DeepCopyInto(out *CommandOverrideFlag) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOverrideFlag.
func (in *CommandOverrideFlag) DeepCopy() *CommandOverrideFlag {
if in == nil {
return nil
}
out := new(CommandOverrideFlag)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Preference) DeepCopyInto(out *Preference) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Overrides != nil {
in, out := &in.Overrides, &out.Overrides
*out = make([]CommandOverride, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Aliases != nil {
in, out := &in.Aliases, &out.Aliases
*out = make([]AliasOverride, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Preference.
func (in *Preference) DeepCopy() *Preference {
if in == nil {
return nil
}
out := new(Preference)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Preference) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@ -0,0 +1,33 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by defaulter-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
return nil
}

View File

@ -0,0 +1,133 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package config
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AliasOverride) DeepCopyInto(out *AliasOverride) {
*out = *in
if in.PrependArgs != nil {
in, out := &in.PrependArgs, &out.PrependArgs
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.AppendArgs != nil {
in, out := &in.AppendArgs, &out.AppendArgs
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Flags != nil {
in, out := &in.Flags, &out.Flags
*out = make([]CommandOverrideFlag, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AliasOverride.
func (in *AliasOverride) DeepCopy() *AliasOverride {
if in == nil {
return nil
}
out := new(AliasOverride)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommandOverride) DeepCopyInto(out *CommandOverride) {
*out = *in
if in.Flags != nil {
in, out := &in.Flags, &out.Flags
*out = make([]CommandOverrideFlag, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOverride.
func (in *CommandOverride) DeepCopy() *CommandOverride {
if in == nil {
return nil
}
out := new(CommandOverride)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommandOverrideFlag) DeepCopyInto(out *CommandOverrideFlag) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOverrideFlag.
func (in *CommandOverrideFlag) DeepCopy() *CommandOverrideFlag {
if in == nil {
return nil
}
out := new(CommandOverrideFlag)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Preference) DeepCopyInto(out *Preference) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Overrides != nil {
in, out := &in.Overrides, &out.Overrides
*out = make([]CommandOverride, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Aliases != nil {
in, out := &in.Aliases, &out.Aliases
*out = make([]AliasOverride, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Preference.
func (in *Preference) DeepCopy() *Preference {
if in == nil {
return nil
}
out := new(Preference)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Preference) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@ -0,0 +1,458 @@
/*
Copyright 2025 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 kuberc
import (
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"k8s.io/kubectl/pkg/config"
kuberc "k8s.io/kubectl/pkg/config/install"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)
const RecommendedKubeRCFileName = "kuberc"
var (
RecommendedConfigDir = filepath.Join(homedir.HomeDir(), clientcmd.RecommendedHomeDir)
RecommendedKubeRCFile = filepath.Join(RecommendedConfigDir, RecommendedKubeRCFileName)
aliasNameRegex = regexp.MustCompile("^[a-zA-Z]+$")
shortHandRegex = regexp.MustCompile("^-[a-zA-Z]+$")
scheme = runtime.NewScheme()
strictCodecs = serializer.NewCodecFactory(scheme, serializer.EnableStrict)
lenientCodecs = serializer.NewCodecFactory(scheme, serializer.DisableStrict)
)
func init() {
kuberc.Install(scheme)
}
// PreferencesHandler is responsible for setting default flags
// arguments based on user's kuberc configuration.
type PreferencesHandler interface {
AddFlags(flags *pflag.FlagSet)
Apply(rootCmd *cobra.Command, args []string, errOut io.Writer) ([]string, error)
}
// Preferences stores the kuberc file coming either from environment variable
// or file from set in flag or the default kuberc path.
type Preferences struct {
getPreferencesFunc func(kuberc string, errOut io.Writer) (*config.Preference, error)
aliases map[string]struct{}
}
// NewPreferences returns initialized Prefrences object.
func NewPreferences() PreferencesHandler {
return &Preferences{
getPreferencesFunc: DefaultGetPreferences,
aliases: make(map[string]struct{}),
}
}
type aliasing struct {
appendArgs []string
prependArgs []string
flags []config.CommandOverrideFlag
command *cobra.Command
}
// AddFlags adds kuberc related flags into the command.
func (p *Preferences) AddFlags(flags *pflag.FlagSet) {
flags.String("kuberc", "", "Path to the kuberc file to use for preferences. This can be disabled by exporting KUBECTL_KUBERC=false.")
}
// Apply firstly applies the aliases in the preferences file and secondly overrides
// the default values of flags.
func (p *Preferences) Apply(rootCmd *cobra.Command, args []string, errOut io.Writer) ([]string, error) {
if len(args) <= 1 {
return args, nil
}
kubercPath, err := getExplicitKuberc(args)
if err != nil {
return args, err
}
kuberc, err := p.getPreferencesFunc(kubercPath, errOut)
if err != nil {
return args, fmt.Errorf("kuberc error %w", err)
}
if kuberc == nil {
return args, nil
}
err = validate(kuberc)
if err != nil {
return args, err
}
args, err = p.applyAliases(rootCmd, kuberc, args, errOut)
if err != nil {
return args, err
}
err = p.applyOverrides(rootCmd, kuberc, args, errOut)
if err != nil {
return args, err
}
return args, nil
}
// applyOverrides finds the command and sets the defaulted flag values in kuberc.
func (p *Preferences) applyOverrides(rootCmd *cobra.Command, kuberc *config.Preference, args []string, errOut io.Writer) error {
args = args[1:]
cmd, _, err := rootCmd.Find(args)
if err != nil {
return nil
}
for _, c := range kuberc.Overrides {
parsedCmds := strings.Fields(c.Command)
overrideCmd, _, err := rootCmd.Find(parsedCmds)
if err != nil {
fmt.Fprintf(errOut, "Warning: command %q not found to set kuberc override\n", c.Command)
continue
}
if overrideCmd.Name() != cmd.Name() {
continue
}
if _, ok := p.aliases[cmd.Name()]; ok {
return fmt.Errorf("alias %s can not be overridden", cmd.Name())
}
// This function triggers merging the persistent flags in the parent commands.
_ = cmd.InheritedFlags()
allShorthands := make(map[string]struct{})
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
if flag.Shorthand != "" {
allShorthands[flag.Shorthand] = struct{}{}
}
})
for _, fl := range c.Flags {
existingFlag := cmd.Flag(fl.Name)
if existingFlag == nil {
return fmt.Errorf("invalid flag %s for command %s", fl.Name, c.Command)
}
if searchInArgs(existingFlag.Name, existingFlag.Shorthand, allShorthands, args) {
// Don't modify the value implicitly, if it is passed in args explicitly
continue
}
err = cmd.Flags().Set(fl.Name, fl.Default)
if err != nil {
return fmt.Errorf("could not apply override value %s to flag %s in command %s err: %w", fl.Default, fl.Name, c.Command, err)
}
}
}
return nil
}
// applyAliases firstly appends all defined aliases in kuberc file to the root command.
// Since there may be several alias definitions belonging to the same command, it extracts the
// alias that is currently executed from args. After that it sets the flag definitions in alias as default values
// of the command. Lastly, others parameters (e.g. resources, etc.) that are passed as arguments in kuberc
// is appended into the command args.
func (p *Preferences) applyAliases(rootCmd *cobra.Command, kuberc *config.Preference, args []string, errOut io.Writer) ([]string, error) {
_, _, err := rootCmd.Find(args[1:])
if err == nil {
// Command is found, no need to continue for aliasing
return args, nil
}
var aliasArgs *aliasing
var commandName string // first "non-flag" arguments
var commandIndex int
for index, arg := range args[1:] {
if !strings.HasPrefix(arg, "-") {
commandName = arg
commandIndex = index + 1
break
}
}
for _, alias := range kuberc.Aliases {
p.aliases[alias.Name] = struct{}{}
if alias.Name != commandName {
continue
}
// do not allow shadowing built-ins
if _, _, err := rootCmd.Find([]string{alias.Name}); err == nil {
fmt.Fprintf(errOut, "Warning: Setting alias %q to a built-in command is not supported\n", alias.Name)
break
}
commands := strings.Fields(alias.Command)
existingCmd, flags, err := rootCmd.Find(commands)
if err != nil {
return args, fmt.Errorf("command %q not found to set alias %q: %v", alias.Command, alias.Name, flags)
}
newCmd := *existingCmd
newCmd.Use = alias.Name
newCmd.Aliases = []string{}
aliasCmd := &newCmd
aliasArgs = &aliasing{
prependArgs: alias.PrependArgs,
appendArgs: alias.AppendArgs,
flags: alias.Flags,
command: aliasCmd,
}
break
}
if aliasArgs == nil {
// pursue with the current behavior.
// This might be a built-in command, external plugin, etc.
return args, nil
}
rootCmd.AddCommand(aliasArgs.command)
foundAliasCmd, _, err := rootCmd.Find([]string{commandName})
if err != nil {
return args, nil
}
// This function triggers merging the persistent flags in the parent commands.
_ = foundAliasCmd.InheritedFlags()
allShorthands := make(map[string]struct{})
foundAliasCmd.Flags().VisitAll(func(flag *pflag.Flag) {
if flag.Shorthand != "" {
allShorthands[flag.Shorthand] = struct{}{}
}
})
for _, fl := range aliasArgs.flags {
existingFlag := foundAliasCmd.Flag(fl.Name)
if existingFlag == nil {
return args, fmt.Errorf("invalid alias flag %s in alias %s", fl.Name, args[0])
}
if searchInArgs(existingFlag.Name, existingFlag.Shorthand, allShorthands, args) {
// Don't modify the value implicitly, if it is passed in args explicitly
continue
}
err = foundAliasCmd.Flags().Set(fl.Name, fl.Default)
if err != nil {
return args, fmt.Errorf("could not apply value %s to flag %s in alias %s err: %w", fl.Default, fl.Name, args[0], err)
}
}
if len(aliasArgs.prependArgs) > 0 {
// prependArgs defined in kuberc should be inserted after the alias name.
if commandIndex+1 >= len(args) {
// command is the last item, we simply append just like appendArgs
args = append(args, aliasArgs.prependArgs...)
} else {
args = append(args[:commandIndex+1], append(aliasArgs.prependArgs, args[commandIndex+1:]...)...)
}
}
if len(aliasArgs.appendArgs) > 0 {
// appendArgs defined in kuberc should be appended to actual args.
args = append(args, aliasArgs.appendArgs...)
}
// Cobra (command.go#L1078) appends only root command's args into the actual args and ignores the others.
// We are appending the additional args defined in kuberc in here and
// expect that it will be passed along to the actual command.
rootCmd.SetArgs(args[1:])
return args, nil
}
// DefaultGetPreferences returns KubeRCConfiguration.
// If users sets kuberc file explicitly in --kuberc flag, it has the highest
// priority. If not specified, it looks for in KUBERC environment variable.
// If KUBERC is also not set, it falls back to default .kuberc file at the same location
// where kubeconfig's defaults are residing in.
// If KUBERC is set to "off", kuberc will be turned off and original behaviors in kubectl will be applied.
func DefaultGetPreferences(kuberc string, errOut io.Writer) (*config.Preference, error) {
if val := os.Getenv("KUBERC"); val == "off" {
if kuberc != "" {
return nil, fmt.Errorf("disabling kuberc via KUBERC=off and passing kuberc flag are mutually exclusive")
}
return nil, nil
}
kubeRCFile := RecommendedKubeRCFile
explicitly := false
if kuberc != "" {
kubeRCFile = kuberc
explicitly = true
}
if kubeRCFile == "" && os.Getenv("KUBERC") != "" {
kubeRCFile = os.Getenv("KUBERC")
explicitly = true
}
preference, err := decodePreference(kubeRCFile)
switch {
case explicitly && preference != nil && runtime.IsStrictDecodingError(err):
// if explicitly requested, just warn about strict decoding errors if we got a usable Preference object back
fmt.Fprintf(errOut, "kuberc: ignoring strict decoding error in %s: %v", kubeRCFile, err)
return preference, nil
case explicitly && err != nil:
// if explicitly requested, error on any error other than a StrictDecodingError
return nil, fmt.Errorf("kuberc: %w", err)
case !explicitly && os.IsNotExist(err):
// if not explicitly requested, silently ignore missing kuberc
return nil, nil
case !explicitly && err != nil:
// if not explicitly requested, only warn on any other error
fmt.Fprintf(errOut, "kuberc: no preferences loaded from %s: %v", kubeRCFile, err)
return nil, nil
default:
return preference, nil
}
}
// Normally, we should extract this value directly from kuberc flag.
// However, flag values are set during the command execution and
// we are in very early stages to prepare commands prior to execute them.
// Besides, we only need kuberc flag value in this stage.
func getExplicitKuberc(args []string) (string, error) {
var kubercPath string
for i, arg := range args {
if arg == "--" {
// flags after "--" does not represent any flag of
// the command. We should short cut the iteration in here.
break
}
if arg == "--kuberc" {
if i+1 < len(args) {
kubercPath = args[i+1]
break
}
return "", fmt.Errorf("kuberc file is not found")
} else if strings.Contains(arg, "--kuberc=") {
parg := strings.Split(arg, "=")
if len(parg) > 1 && parg[1] != "" {
kubercPath = parg[1]
break
}
return "", fmt.Errorf("kuberc file is not found")
}
}
if kubercPath == "" {
return "", nil
}
return kubercPath, nil
}
// searchInArgs searches the given key in the args and returns
// true, if it finds. Otherwise, it returns false.
func searchInArgs(flagName string, shorthand string, allShorthands map[string]struct{}, args []string) bool {
for _, arg := range args {
// if flag is set in args in "--flag value" or "--flag=value" format,
// we should return it as found
if fmt.Sprintf("--%s", flagName) == arg || strings.HasPrefix(arg, fmt.Sprintf("--%s=", flagName)) {
return true
}
if shorthand == "" {
continue
}
// shorthand can be in "-n value" or "-nvalue" format
// it is guaranteed that shorthand is one letter. So that
// checking just the prefix -oyaml also finds --output.
if strings.HasPrefix(arg, fmt.Sprintf("-%s", shorthand)) {
return true
}
if !shortHandRegex.MatchString(arg) {
continue
}
// remove prefix "-"
arg = arg[1:]
// short hands can be in a combined "-abc" format.
// First we need to ensure that all the values are shorthand to safely search ours.
// Because we know that "-abcvalue" is not valid. So that we need to be sure that if we find
// "b" it correctly refers to the shorthand "b" not arbitrary value "-cargb".
arbitraryFound := false
for _, runeValue := range shorthand {
if _, ok := allShorthands[string(runeValue)]; !ok {
arbitraryFound = true
break
}
}
if arbitraryFound {
continue
}
// verified that all values are short hand. Now search ours
if strings.Contains(arg, shorthand) {
return true
}
}
return false
}
func validate(plugin *config.Preference) error {
validateFlag := func(flags []config.CommandOverrideFlag) error {
for _, flag := range flags {
if strings.HasPrefix(flag.Name, "-") {
return fmt.Errorf("flag name %s should be in long form without dashes", flag.Name)
}
}
return nil
}
aliases := make(map[string]struct{})
for _, alias := range plugin.Aliases {
if !aliasNameRegex.MatchString(alias.Name) {
return fmt.Errorf("invalid alias name, can only include alphabetical characters")
}
if err := validateFlag(alias.Flags); err != nil {
return err
}
if _, ok := aliases[alias.Name]; ok {
return fmt.Errorf("duplicate alias name %s", alias.Name)
}
aliases[alias.Name] = struct{}{}
}
for _, override := range plugin.Overrides {
if err := validateFlag(override.Flags); err != nil {
return err
}
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
/*
Copyright 2024 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 kuberc
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"k8s.io/klog/v2"
"k8s.io/apimachinery/pkg/runtime/schema"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/kubectl/pkg/config"
)
// decodePreference iterates over the yamls in kuberc file to find the supported kuberc version.
// Once it finds, it returns the compatible kuberc object as well as accumulated errors during the iteration.
func decodePreference(kubercFile string) (*config.Preference, error) {
kubercBytes, err := os.ReadFile(kubercFile)
if err != nil {
return nil, err
}
attemptedItems := 0
reader := utilyaml.NewYAMLReader(bufio.NewReader(bytes.NewBuffer(kubercBytes)))
for {
doc, readErr := reader.Read()
if errors.Is(readErr, io.EOF) {
// no more entries, expected when we reach the end of the file
break
}
if readErr != nil {
// other errors are fatal
return nil, readErr
}
if len(bytes.TrimSpace(doc)) == 0 {
// empty item, ignore
continue
}
// remember we attempted
attemptedItems++
pref, gvk, strictDecodeErr := strictCodecs.UniversalDecoder().Decode(doc, nil, nil)
if strictDecodeErr != nil {
var lenientDecodeErr error
pref, gvk, lenientDecodeErr = lenientCodecs.UniversalDecoder().Decode(doc, nil, nil)
if lenientDecodeErr != nil {
// both strict and lenient failed
// verbose log the error with the most information about this item and continue
klog.V(5).Infof("kuberc: strict decoding error for entry %d in %s: %v", attemptedItems, kubercFile, strictDecodeErr)
continue
}
}
// check expected GVK, if bad, verbose log and continue
expectedGK := schema.GroupKind{
Group: config.SchemeGroupVersion.Group,
Kind: "Preference",
}
if gvk.GroupKind() != expectedGK {
klog.V(5).Infof("kuberc: unexpected GroupVersionKind for entry %d in %s: %v", attemptedItems, kubercFile, gvk)
continue
}
// check expected go type, if bad, verbose log and continue
preferences, ok := pref.(*config.Preference)
if !ok {
klog.V(5).Infof("kuberc: unexpected object type %T for entry %d in %s", pref, attemptedItems, kubercFile)
continue
}
// we have a usable preferences to return
klog.V(5).Infof("kuberc: successfully decoded entry %d in %s", attemptedItems, kubercFile)
return preferences, strictDecodeErr
}
if attemptedItems > 0 {
return nil, fmt.Errorf("no valid preferences found in %s, use --v=5 to see details", kubercFile)
}
// empty doc
klog.V(5).Infof("kuberc: no preferences found in %s", kubercFile)
return nil, nil
}

196
test/cmd/kuberc.sh Executable file
View File

@ -0,0 +1,196 @@
#!/usr/bin/env bash
# Copyright 2025 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.
set -o errexit
set -o nounset
set -o pipefail
run_kuberc_tests() {
set -o nounset
set -o errexit
create_and_use_new_namespace
kube::log::status "Testing kuberc"
# Enable KUBERC feature
export KUBECTL_KUBERC=true
cat > "${TMPDIR:-/tmp}"/kuberc_file << EOF
apiVersion: kubectl.config.k8s.io/v1alpha1
kind: Preference
aliases:
- name: crns
command: create namespace
appendArgs:
- test-kuberc-ns
- name: getn
command: get
prependArgs:
- namespace
flags:
- name: output
default: wide
- name: crole
command: create role
flags:
- name: verb
default: get,watch
- name: getrole
command: get
flags:
- name: output
default: json
- name: runx
command: run
flags:
- name: image
default: nginx
- name: labels
default: app=test,env=test
- name: env
default: DNS_DOMAIN=test
- name: namespace
default: test-kuberc-ns
appendArgs:
- test-pod-2
- --
- custom-arg1
- custom-arg2
- name: setx
command: set image
appendArgs:
- pod/test-pod-2
- test-pod-2=busybox
overrides:
- command: apply
flags:
- name: server-side
default: "true"
- name: dry-run
default: "server"
- name: validate
default: "strict"
- command: delete
flags:
- name: interactive
default: "true"
- command: get
flags:
- name: namespace
default: "test-kuberc-ns"
- name: output
default: "json"
EOF
# Pre-condition: the test-kuberc-ns namespace does not exist
kube::test::get_object_assert 'namespaces' "{{range.items}}{{ if eq ${id_field:?} \"test-kuberc-ns\" }}found{{end}}{{end}}:" ':'
# Alias command crns successfully creates namespace
kubectl crns --kuberc="${TMPDIR:-/tmp}"/kuberc_file
# Post-condition: namespace 'test-kuberc-ns' is created.
kube::test::get_object_assert 'namespaces/test-kuberc-ns' "{{$id_field}}" 'test-kuberc-ns'
# Alias command crns successfully creates namespace
kubectl getn --kuberc="${TMPDIR:-/tmp}"/kuberc_file test-kuberc-ns
# Post-condition: namespace 'test-kuberc-ns' is created.
kube::test::get_object_assert 'namespaces/test-kuberc-ns' "{{$id_field}}" 'test-kuberc-ns'
# Alias command crns successfully creates namespace
kubectl getn test-kuberc-ns --output=json --kuberc="${TMPDIR:-/tmp}"/kuberc_file
# Post-condition: namespace 'test-kuberc-ns' is created.
kube::test::get_object_assert 'namespaces/test-kuberc-ns' "{{$id_field}}" 'test-kuberc-ns'
# check array flags are appended after implicit defaults
kubectl crole testkubercrole --verb=list --namespace test-kuberc-ns --resource=pods --kuberc="${TMPDIR:-/tmp}"/kuberc_file
output_message=$(kubectl getrole role/testkubercrole -n test-kuberc-ns -oyaml --kuberc="${TMPDIR:-/tmp}"/kuberc_file)
kube::test::if_has_string "${output_message}" 'list'
kube::test::if_has_not_string "${output_message}" 'watch' 'get'
# Post-condition: remove role
kubectl delete role testkubercrole --namespace=test-kuberc-ns
# Alias run command creates a pod with the given configurations
kubectl runx --kuberc "${TMPDIR:-/tmp}"/kuberc_file
# Post-Condition: assertion object exists
kube::test::get_object_assert 'pod/test-pod-2 --namespace=test-kuberc-ns' "{{$id_field}}" 'test-pod-2'
# Not explicitly pass namespace to assure that default flag value is used
output_message=$(kubectl get pod/test-pod-2 2>&1 "${kube_flags[@]:?}" --kuberc="${TMPDIR:-/tmp}"/kuberc_file)
kube::test::if_has_string "${output_message}" 'nginx' 'app=test' 'env=test' 'DNS_DOMAIN=test' 'custom-arg1'
# output flag is defaulted to json and assure that it is correct format
kube::test::if_has_string "${output_message}" '{'
# pass explicit invalid namespace to assure that it takes precedence over the value in kuberc
output_message=$(! kubectl get pod/test-pod-2 -n kube-system 2>&1 "${kube_flags[@]:?}" --kuberc="${TMPDIR:-/tmp}"/kuberc_file)
kube::test::if_has_string "${output_message}" 'pods "test-pod-2" not found'
# Alias set env command sets new env var
kubectl setx --kuberc="${TMPDIR:-/tmp}"/kuberc_file -n test-kuberc-ns
# explicitly pass same namespace also defined in kuberc
output_message=$(kubectl get pod/test-pod-2 -n test-kuberc-ns 2>&1 "${kube_flags[@]:?}" --kuberc="${TMPDIR:-/tmp}"/kuberc_file)
kube::test::if_has_string "${output_message}" 'busybox'
kube::test::if_has_not_string "${output_message}" 'nginx'
# default overrides should prevent actual apply as they are all dry-run=server
# also assure that explicit flags are also passed
output_message=$(kubectl apply -n test-kuberc-ns -f hack/testdata/pod.yaml --kuberc="${TMPDIR:-/tmp}"/kuberc_file)
kube::test::if_has_string "${output_message}" 'serverside-applied (server dry run)'
# interactive flag is defaulted to true and prompted as no
output_message=$(kubectl delete pod/test-pod-2 -n test-kuberc-ns <<< $'n\n' --kuberc="${TMPDIR:-/tmp}"/kuberc_file)
kube::test::if_has_string "${output_message}" 'pod/test-pod-2'
# assure that it is not deleted
output_message=$(kubectl get pod/test-pod-2 2>&1 "${kube_flags[@]:?}" --kuberc="${TMPDIR:-/tmp}"/kuberc_file)
kube::test::if_has_string "${output_message}" "test-pod-2"
cat > "${TMPDIR:-/tmp}"/kuberc_file_multi << EOF
---
apiVersion: kubectl.config.k8s.io/v1alpha1
kind: Preference
overrides:
- command: get
flags:
- name: namespace
default: "test-kuberc-ns"
- name: output
default: "json"
unknown: invalid
---
apiVersion: kubectl.config.k8s.io/notexist
kind: Preference
overrides:
- command: get
flags:
- name: namespace
default: "test-kuberc-ns"
- name: output
default: "json"
EOF
# assure that it is not deleted
output_message=$(kubectl get pod/test-pod-2 2>&1 "${kube_flags[@]:?}" --kuberc="${TMPDIR:-/tmp}"/kuberc_file_multi)
# assure that correct kuberc is found and printed in output_message
kube::test::if_has_string "${output_message}" "test-pod-2"
# assure that warning message is also printed for the notexist kuberc version
kube::test::if_has_string "${output_message}" "strict decoding error" "unknown"
# explicitly overwriting the value that is also defaulted in kuberc and
# assure that explicit value supersedes
output_message=$(kubectl delete namespace/test-kuberc-ns --interactive=false --kuberc="${TMPDIR:-/tmp}"/kuberc_file)
kube::test::if_has_string "${output_message}" 'namespace "test-kuberc-ns" deleted'
unset KUBECTL_KUBERC
set +o nounset
set +o errexit
}

View File

@ -48,6 +48,7 @@ source "${KUBE_ROOT}/test/cmd/generic-resources.sh"
source "${KUBE_ROOT}/test/cmd/get.sh"
source "${KUBE_ROOT}/test/cmd/help.sh"
source "${KUBE_ROOT}/test/cmd/kubeconfig.sh"
source "${KUBE_ROOT}/test/cmd/kuberc.sh"
source "${KUBE_ROOT}/test/cmd/node-management.sh"
source "${KUBE_ROOT}/test/cmd/plugins.sh"
source "${KUBE_ROOT}/test/cmd/proxy.sh"
@ -1056,5 +1057,11 @@ runTests() {
record_command run_kubectl_debug_netadmin_node_tests
fi
#######################
# kuberc #
#######################
record_command run_kuberc_tests
cleanup_tests
}