Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / k8s.io / client-go / tools / clientcmd / validation.go
1 /*
2 Copyright 2014 The Kubernetes Authors.
3
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
7
8     http://www.apache.org/licenses/LICENSE-2.0
9
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.
15 */
16
17 package clientcmd
18
19 import (
20         "errors"
21         "fmt"
22         "os"
23         "reflect"
24         "strings"
25
26         utilerrors "k8s.io/apimachinery/pkg/util/errors"
27         "k8s.io/apimachinery/pkg/util/validation"
28         clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
29 )
30
31 var (
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")
36 )
37
38 type errContextNotFound struct {
39         ContextName string
40 }
41
42 func (e *errContextNotFound) Error() string {
43         return fmt.Sprintf("context was not found for specified context: %v", e.ContextName)
44 }
45
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 {
49         if err == nil {
50                 return false
51         }
52         if _, ok := err.(*errContextNotFound); ok || err == ErrNoContext {
53                 return true
54         }
55         return strings.Contains(err.Error(), "context was not found for specified context")
56 }
57
58 // IsEmptyConfig returns true if the provided error indicates the provided configuration
59 // is empty.
60 func IsEmptyConfig(err error) bool {
61         switch t := err.(type) {
62         case errConfigurationInvalid:
63                 return len(t) == 1 && t[0] == ErrEmptyConfig
64         }
65         return err == ErrEmptyConfig
66 }
67
68 // errConfigurationInvalid is a set of errors indicating the configuration is invalid.
69 type errConfigurationInvalid []error
70
71 // errConfigurationInvalid implements error and Aggregate
72 var _ error = errConfigurationInvalid{}
73 var _ utilerrors.Aggregate = errConfigurationInvalid{}
74
75 func newErrConfigurationInvalid(errs []error) error {
76         switch len(errs) {
77         case 0:
78                 return nil
79         default:
80                 return errConfigurationInvalid(errs)
81         }
82 }
83
84 // Error implements the error interface
85 func (e errConfigurationInvalid) Error() string {
86         return fmt.Sprintf("invalid configuration: %v", utilerrors.NewAggregate(e).Error())
87 }
88
89 // Errors implements the AggregateError interface
90 func (e errConfigurationInvalid) Errors() []error {
91         return e
92 }
93
94 // IsConfigurationInvalid returns true if the provided error indicates the configuration is invalid.
95 func IsConfigurationInvalid(err error) bool {
96         switch err.(type) {
97         case *errContextNotFound, errConfigurationInvalid:
98                 return true
99         }
100         return IsContextNotFound(err)
101 }
102
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)
106
107         if clientcmdapi.IsConfigEmpty(&config) {
108                 return newErrConfigurationInvalid([]error{ErrEmptyConfig})
109         }
110
111         if len(config.CurrentContext) != 0 {
112                 if _, exists := config.Contexts[config.CurrentContext]; !exists {
113                         validationErrors = append(validationErrors, &errContextNotFound{config.CurrentContext})
114                 }
115         }
116
117         for contextName, context := range config.Contexts {
118                 validationErrors = append(validationErrors, validateContext(contextName, *context, config)...)
119         }
120
121         for authInfoName, authInfo := range config.AuthInfos {
122                 validationErrors = append(validationErrors, validateAuthInfo(authInfoName, *authInfo)...)
123         }
124
125         for clusterName, clusterInfo := range config.Clusters {
126                 validationErrors = append(validationErrors, validateClusterInfo(clusterName, *clusterInfo)...)
127         }
128
129         return newErrConfigurationInvalid(validationErrors)
130 }
131
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)
136
137         if clientcmdapi.IsConfigEmpty(&config) {
138                 return newErrConfigurationInvalid([]error{ErrEmptyConfig})
139         }
140
141         var contextName string
142         if len(passedContextName) != 0 {
143                 contextName = passedContextName
144         } else {
145                 contextName = config.CurrentContext
146         }
147
148         if len(contextName) == 0 {
149                 return ErrNoContext
150         }
151
152         context, exists := config.Contexts[contextName]
153         if !exists {
154                 validationErrors = append(validationErrors, &errContextNotFound{contextName})
155         }
156
157         if exists {
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])...)
161         }
162
163         return newErrConfigurationInvalid(validationErrors)
164 }
165
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)
169
170         emptyCluster := clientcmdapi.NewCluster()
171         if reflect.DeepEqual(*emptyCluster, clusterInfo) {
172                 return []error{ErrEmptyCluster}
173         }
174
175         if len(clusterInfo.Server) == 0 {
176                 if len(clusterName) == 0 {
177                         validationErrors = append(validationErrors, fmt.Errorf("default cluster has no server defined"))
178                 } else {
179                         validationErrors = append(validationErrors, fmt.Errorf("no server found for cluster %q", clusterName))
180                 }
181         }
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))
185         }
186         if len(clusterInfo.CertificateAuthority) != 0 {
187                 clientCertCA, err := os.Open(clusterInfo.CertificateAuthority)
188                 defer clientCertCA.Close()
189                 if err != nil {
190                         validationErrors = append(validationErrors, fmt.Errorf("unable to read certificate-authority %v for %v due to %v", clusterInfo.CertificateAuthority, clusterName, err))
191                 }
192         }
193
194         return validationErrors
195 }
196
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)
200
201         usingAuthPath := false
202         methods := make([]string, 0, 3)
203         if len(authInfo.Token) != 0 {
204                 methods = append(methods, "token")
205         }
206         if len(authInfo.Username) != 0 || len(authInfo.Password) != 0 {
207                 methods = append(methods, "basicAuth")
208         }
209
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))
214                 }
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))
218                 }
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))
222                 }
223
224                 if len(authInfo.ClientCertificate) != 0 {
225                         clientCertFile, err := os.Open(authInfo.ClientCertificate)
226                         defer clientCertFile.Close()
227                         if err != nil {
228                                 validationErrors = append(validationErrors, fmt.Errorf("unable to read client-cert %v for %v due to %v", authInfo.ClientCertificate, authInfoName, err))
229                         }
230                 }
231                 if len(authInfo.ClientKey) != 0 {
232                         clientKeyFile, err := os.Open(authInfo.ClientKey)
233                         defer clientKeyFile.Close()
234                         if err != nil {
235                                 validationErrors = append(validationErrors, fmt.Errorf("unable to read client-key %v for %v due to %v", authInfo.ClientKey, authInfoName, err))
236                         }
237                 }
238         }
239
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))
243                 }
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))
246                 }
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))
249                 }
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))
255                         }
256                 }
257         }
258
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))
262         }
263
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))
267         }
268         return validationErrors
269 }
270
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)
274
275         if len(contextName) == 0 {
276                 validationErrors = append(validationErrors, fmt.Errorf("empty context name for %#v is not allowed", context))
277         }
278
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))
283         }
284
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))
289         }
290
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))
294                 }
295         }
296
297         return validationErrors
298 }