2 Copyright 2014 The Kubernetes Authors.
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
26 utilerrors "k8s.io/apimachinery/pkg/util/errors"
27 "k8s.io/apimachinery/pkg/util/validation"
28 clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
32 ErrNoContext = errors.New("no context chosen")
33 ErrEmptyConfig = errors.New("no configuration has been provided")
34 // message is for consistency with old behavior
35 ErrEmptyCluster = errors.New("cluster has no server defined")
38 type errContextNotFound struct {
42 func (e *errContextNotFound) Error() string {
43 return fmt.Sprintf("context was not found for specified context: %v", e.ContextName)
46 // IsContextNotFound returns a boolean indicating whether the error is known to
47 // report that a context was not found
48 func IsContextNotFound(err error) bool {
52 if _, ok := err.(*errContextNotFound); ok || err == ErrNoContext {
55 return strings.Contains(err.Error(), "context was not found for specified context")
58 // IsEmptyConfig returns true if the provided error indicates the provided configuration
60 func IsEmptyConfig(err error) bool {
61 switch t := err.(type) {
62 case errConfigurationInvalid:
63 return len(t) == 1 && t[0] == ErrEmptyConfig
65 return err == ErrEmptyConfig
68 // errConfigurationInvalid is a set of errors indicating the configuration is invalid.
69 type errConfigurationInvalid []error
71 // errConfigurationInvalid implements error and Aggregate
72 var _ error = errConfigurationInvalid{}
73 var _ utilerrors.Aggregate = errConfigurationInvalid{}
75 func newErrConfigurationInvalid(errs []error) error {
80 return errConfigurationInvalid(errs)
84 // Error implements the error interface
85 func (e errConfigurationInvalid) Error() string {
86 return fmt.Sprintf("invalid configuration: %v", utilerrors.NewAggregate(e).Error())
89 // Errors implements the AggregateError interface
90 func (e errConfigurationInvalid) Errors() []error {
94 // IsConfigurationInvalid returns true if the provided error indicates the configuration is invalid.
95 func IsConfigurationInvalid(err error) bool {
97 case *errContextNotFound, errConfigurationInvalid:
100 return IsContextNotFound(err)
103 // Validate checks for errors in the Config. It does not return early so that it can find as many errors as possible.
104 func Validate(config clientcmdapi.Config) error {
105 validationErrors := make([]error, 0)
107 if clientcmdapi.IsConfigEmpty(&config) {
108 return newErrConfigurationInvalid([]error{ErrEmptyConfig})
111 if len(config.CurrentContext) != 0 {
112 if _, exists := config.Contexts[config.CurrentContext]; !exists {
113 validationErrors = append(validationErrors, &errContextNotFound{config.CurrentContext})
117 for contextName, context := range config.Contexts {
118 validationErrors = append(validationErrors, validateContext(contextName, *context, config)...)
121 for authInfoName, authInfo := range config.AuthInfos {
122 validationErrors = append(validationErrors, validateAuthInfo(authInfoName, *authInfo)...)
125 for clusterName, clusterInfo := range config.Clusters {
126 validationErrors = append(validationErrors, validateClusterInfo(clusterName, *clusterInfo)...)
129 return newErrConfigurationInvalid(validationErrors)
132 // ConfirmUsable looks a particular context and determines if that particular part of the config is useable. There might still be errors in the config,
133 // but no errors in the sections requested or referenced. It does not return early so that it can find as many errors as possible.
134 func ConfirmUsable(config clientcmdapi.Config, passedContextName string) error {
135 validationErrors := make([]error, 0)
137 if clientcmdapi.IsConfigEmpty(&config) {
138 return newErrConfigurationInvalid([]error{ErrEmptyConfig})
141 var contextName string
142 if len(passedContextName) != 0 {
143 contextName = passedContextName
145 contextName = config.CurrentContext
148 if len(contextName) == 0 {
152 context, exists := config.Contexts[contextName]
154 validationErrors = append(validationErrors, &errContextNotFound{contextName})
158 validationErrors = append(validationErrors, validateContext(contextName, *context, config)...)
159 validationErrors = append(validationErrors, validateAuthInfo(context.AuthInfo, *config.AuthInfos[context.AuthInfo])...)
160 validationErrors = append(validationErrors, validateClusterInfo(context.Cluster, *config.Clusters[context.Cluster])...)
163 return newErrConfigurationInvalid(validationErrors)
166 // validateClusterInfo looks for conflicts and errors in the cluster info
167 func validateClusterInfo(clusterName string, clusterInfo clientcmdapi.Cluster) []error {
168 validationErrors := make([]error, 0)
170 emptyCluster := clientcmdapi.NewCluster()
171 if reflect.DeepEqual(*emptyCluster, clusterInfo) {
172 return []error{ErrEmptyCluster}
175 if len(clusterInfo.Server) == 0 {
176 if len(clusterName) == 0 {
177 validationErrors = append(validationErrors, fmt.Errorf("default cluster has no server defined"))
179 validationErrors = append(validationErrors, fmt.Errorf("no server found for cluster %q", clusterName))
182 // Make sure CA data and CA file aren't both specified
183 if len(clusterInfo.CertificateAuthority) != 0 && len(clusterInfo.CertificateAuthorityData) != 0 {
184 validationErrors = append(validationErrors, fmt.Errorf("certificate-authority-data and certificate-authority are both specified for %v. certificate-authority-data will override.", clusterName))
186 if len(clusterInfo.CertificateAuthority) != 0 {
187 clientCertCA, err := os.Open(clusterInfo.CertificateAuthority)
188 defer clientCertCA.Close()
190 validationErrors = append(validationErrors, fmt.Errorf("unable to read certificate-authority %v for %v due to %v", clusterInfo.CertificateAuthority, clusterName, err))
194 return validationErrors
197 // validateAuthInfo looks for conflicts and errors in the auth info
198 func validateAuthInfo(authInfoName string, authInfo clientcmdapi.AuthInfo) []error {
199 validationErrors := make([]error, 0)
201 usingAuthPath := false
202 methods := make([]string, 0, 3)
203 if len(authInfo.Token) != 0 {
204 methods = append(methods, "token")
206 if len(authInfo.Username) != 0 || len(authInfo.Password) != 0 {
207 methods = append(methods, "basicAuth")
210 if len(authInfo.ClientCertificate) != 0 || len(authInfo.ClientCertificateData) != 0 {
211 // Make sure cert data and file aren't both specified
212 if len(authInfo.ClientCertificate) != 0 && len(authInfo.ClientCertificateData) != 0 {
213 validationErrors = append(validationErrors, fmt.Errorf("client-cert-data and client-cert are both specified for %v. client-cert-data will override.", authInfoName))
215 // Make sure key data and file aren't both specified
216 if len(authInfo.ClientKey) != 0 && len(authInfo.ClientKeyData) != 0 {
217 validationErrors = append(validationErrors, fmt.Errorf("client-key-data and client-key are both specified for %v; client-key-data will override", authInfoName))
219 // Make sure a key is specified
220 if len(authInfo.ClientKey) == 0 && len(authInfo.ClientKeyData) == 0 {
221 validationErrors = append(validationErrors, fmt.Errorf("client-key-data or client-key must be specified for %v to use the clientCert authentication method.", authInfoName))
224 if len(authInfo.ClientCertificate) != 0 {
225 clientCertFile, err := os.Open(authInfo.ClientCertificate)
226 defer clientCertFile.Close()
228 validationErrors = append(validationErrors, fmt.Errorf("unable to read client-cert %v for %v due to %v", authInfo.ClientCertificate, authInfoName, err))
231 if len(authInfo.ClientKey) != 0 {
232 clientKeyFile, err := os.Open(authInfo.ClientKey)
233 defer clientKeyFile.Close()
235 validationErrors = append(validationErrors, fmt.Errorf("unable to read client-key %v for %v due to %v", authInfo.ClientKey, authInfoName, err))
240 if authInfo.Exec != nil {
241 if authInfo.AuthProvider != nil {
242 validationErrors = append(validationErrors, fmt.Errorf("authProvider cannot be provided in combination with an exec plugin for %s", authInfoName))
244 if len(authInfo.Exec.Command) == 0 {
245 validationErrors = append(validationErrors, fmt.Errorf("command must be specified for %v to use exec authentication plugin", authInfoName))
247 if len(authInfo.Exec.APIVersion) == 0 {
248 validationErrors = append(validationErrors, fmt.Errorf("apiVersion must be specified for %v to use exec authentication plugin", authInfoName))
250 for _, v := range authInfo.Exec.Env {
251 if len(v.Name) == 0 {
252 validationErrors = append(validationErrors, fmt.Errorf("env variable name must be specified for %v to use exec authentication plugin", authInfoName))
253 } else if len(v.Value) == 0 {
254 validationErrors = append(validationErrors, fmt.Errorf("env variable %s value must be specified for %v to use exec authentication plugin", v.Name, authInfoName))
259 // authPath also provides information for the client to identify the server, so allow multiple auth methods in that case
260 if (len(methods) > 1) && (!usingAuthPath) {
261 validationErrors = append(validationErrors, fmt.Errorf("more than one authentication method found for %v; found %v, only one is allowed", authInfoName, methods))
264 // ImpersonateGroups or ImpersonateUserExtra should be requested with a user
265 if (len(authInfo.ImpersonateGroups) > 0 || len(authInfo.ImpersonateUserExtra) > 0) && (len(authInfo.Impersonate) == 0) {
266 validationErrors = append(validationErrors, fmt.Errorf("requesting groups or user-extra for %v without impersonating a user", authInfoName))
268 return validationErrors
271 // validateContext looks for errors in the context. It is not transitive, so errors in the reference authInfo or cluster configs are not included in this return
272 func validateContext(contextName string, context clientcmdapi.Context, config clientcmdapi.Config) []error {
273 validationErrors := make([]error, 0)
275 if len(contextName) == 0 {
276 validationErrors = append(validationErrors, fmt.Errorf("empty context name for %#v is not allowed", context))
279 if len(context.AuthInfo) == 0 {
280 validationErrors = append(validationErrors, fmt.Errorf("user was not specified for context %q", contextName))
281 } else if _, exists := config.AuthInfos[context.AuthInfo]; !exists {
282 validationErrors = append(validationErrors, fmt.Errorf("user %q was not found for context %q", context.AuthInfo, contextName))
285 if len(context.Cluster) == 0 {
286 validationErrors = append(validationErrors, fmt.Errorf("cluster was not specified for context %q", contextName))
287 } else if _, exists := config.Clusters[context.Cluster]; !exists {
288 validationErrors = append(validationErrors, fmt.Errorf("cluster %q was not found for context %q", context.Cluster, contextName))
291 if len(context.Namespace) != 0 {
292 if len(validation.IsDNS1123Label(context.Namespace)) != 0 {
293 validationErrors = append(validationErrors, fmt.Errorf("namespace %q for context %q does not conform to the kubernetes DNS_LABEL rules", context.Namespace, contextName))
297 return validationErrors