sigs.k8s.io/controller-runtime@v0.18.2/pkg/internal/testing/controlplane/plane.go (about) 1 /* 2 Copyright 2021 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 controlplane 18 19 import ( 20 "fmt" 21 "net/url" 22 "os" 23 24 kerrors "k8s.io/apimachinery/pkg/util/errors" 25 "k8s.io/client-go/rest" 26 "sigs.k8s.io/controller-runtime/pkg/internal/testing/certs" 27 ) 28 29 // NewTinyCA creates a new a tiny CA utility for provisioning serving certs and client certs FOR TESTING ONLY. 30 // Don't use this for anything else! 31 var NewTinyCA = certs.NewTinyCA 32 33 // ControlPlane is a struct that knows how to start your test control plane. 34 // 35 // Right now, that means Etcd and your APIServer. This is likely to increase in 36 // future. 37 type ControlPlane struct { 38 APIServer *APIServer 39 Etcd *Etcd 40 41 // Kubectl will override the default asset search path for kubectl 42 KubectlPath string 43 44 // for the deprecated methods (Kubectl, etc) 45 defaultUserCfg *rest.Config 46 defaultUserKubectl *KubeCtl 47 } 48 49 // Start will start your control plane processes. To stop them, call Stop(). 50 func (f *ControlPlane) Start() (retErr error) { 51 if f.Etcd == nil { 52 f.Etcd = &Etcd{} 53 } 54 if err := f.Etcd.Start(); err != nil { 55 return err 56 } 57 defer func() { 58 if retErr != nil { 59 _ = f.Etcd.Stop() 60 } 61 }() 62 63 if f.APIServer == nil { 64 f.APIServer = &APIServer{} 65 } 66 f.APIServer.EtcdURL = f.Etcd.URL 67 if err := f.APIServer.Start(); err != nil { 68 return err 69 } 70 defer func() { 71 if retErr != nil { 72 _ = f.APIServer.Stop() 73 } 74 }() 75 76 // provision the default user -- can be removed when the related 77 // methods are removed. The default user has admin permissions to 78 // mimic legacy no-authz setups. 79 user, err := f.AddUser(User{Name: "default", Groups: []string{"system:masters"}}, &rest.Config{}) 80 if err != nil { 81 return fmt.Errorf("unable to provision the default (legacy) user: %w", err) 82 } 83 kubectl, err := user.Kubectl() 84 if err != nil { 85 return fmt.Errorf("unable to provision the default (legacy) kubeconfig: %w", err) 86 } 87 f.defaultUserCfg = user.Config() 88 f.defaultUserKubectl = kubectl 89 return nil 90 } 91 92 // Stop will stop your control plane processes, and clean up their data. 93 func (f *ControlPlane) Stop() error { 94 var errList []error 95 96 if f.APIServer != nil { 97 if err := f.APIServer.Stop(); err != nil { 98 errList = append(errList, err) 99 } 100 } 101 102 if f.Etcd != nil { 103 if err := f.Etcd.Stop(); err != nil { 104 errList = append(errList, err) 105 } 106 } 107 108 return kerrors.NewAggregate(errList) 109 } 110 111 // APIURL returns the URL you should connect to to talk to your API server. 112 // 113 // If insecure serving is configured, this will contain the insecure port. 114 // Otherwise, it will contain the secure port. 115 // 116 // Deprecated: use AddUser instead, or APIServer.{Ins|S}ecureServing.URL if 117 // you really want just the URL. 118 func (f *ControlPlane) APIURL() *url.URL { 119 return f.APIServer.URL 120 } 121 122 // KubeCtl returns a pre-configured KubeCtl, ready to connect to this 123 // ControlPlane. 124 // 125 // Deprecated: use AddUser & AuthenticatedUser.Kubectl instead. 126 func (f *ControlPlane) KubeCtl() *KubeCtl { 127 return f.defaultUserKubectl 128 } 129 130 // RESTClientConfig returns a pre-configured restconfig, ready to connect to 131 // this ControlPlane. 132 // 133 // Deprecated: use AddUser & AuthenticatedUser.Config instead. 134 func (f *ControlPlane) RESTClientConfig() (*rest.Config, error) { 135 return f.defaultUserCfg, nil 136 } 137 138 // AuthenticatedUser contains access information for an provisioned user, 139 // including REST config, kubeconfig contents, and access to a KubeCtl instance. 140 // 141 // It's not "safe" to use the methods on this till after the API server has been 142 // started (due to certificate initialization and such). The various methods will 143 // panic if this is done. 144 type AuthenticatedUser struct { 145 // cfg is the rest.Config for connecting to the API server. It's lazily initialized. 146 cfg *rest.Config 147 // cfgIsComplete indicates the cfg has had late-initialized fields (e.g. 148 // API server CA data) initialized. 149 cfgIsComplete bool 150 151 // apiServer is a handle to the APIServer that's used when finalizing cfg 152 // and producing the kubectl instance. 153 plane *ControlPlane 154 155 // kubectl is our existing, provisioned kubectl. We don't provision one 156 // till someone actually asks for it. 157 kubectl *KubeCtl 158 } 159 160 // Config returns the REST config that can be used to connect to the API server 161 // as this user. 162 // 163 // Will panic if used before the API server is started. 164 func (u *AuthenticatedUser) Config() *rest.Config { 165 // NB(directxman12): we choose to panic here for ergonomics sake, and because there's 166 // not really much you can do to "handle" this error. This machinery is intended to be 167 // used in tests anyway, so panicing is not a particularly big deal. 168 if u.cfgIsComplete { 169 return u.cfg 170 } 171 if len(u.plane.APIServer.SecureServing.CA) == 0 { 172 panic("the API server has not yet been started, please do that before accessing connection details") 173 } 174 175 u.cfg.CAData = u.plane.APIServer.SecureServing.CA 176 u.cfg.Host = u.plane.APIServer.SecureServing.URL("https", "/").String() 177 u.cfgIsComplete = true 178 return u.cfg 179 } 180 181 // KubeConfig returns a KubeConfig that's roughly equivalent to this user's REST config. 182 // 183 // Will panic if used before the API server is started. 184 func (u AuthenticatedUser) KubeConfig() ([]byte, error) { 185 // NB(directxman12): we don't return the actual API object to avoid yet another 186 // piece of kubernetes API in our public API, and also because generally the thing 187 // you want to do with this is just write it out to a file for external debugging 188 // purposes, etc. 189 return KubeConfigFromREST(u.Config()) 190 } 191 192 // Kubectl returns a KubeCtl instance for talking to the API server as this user. It uses 193 // a kubeconfig equivalent to that returned by .KubeConfig. 194 // 195 // Will panic if used before the API server is started. 196 func (u *AuthenticatedUser) Kubectl() (*KubeCtl, error) { 197 if u.kubectl != nil { 198 return u.kubectl, nil 199 } 200 if len(u.plane.APIServer.CertDir) == 0 { 201 panic("the API server has not yet been started, please do that before accessing connection details") 202 } 203 204 // cleaning this up is handled when our tmpDir is deleted 205 out, err := os.CreateTemp(u.plane.APIServer.CertDir, "*.kubecfg") 206 if err != nil { 207 return nil, fmt.Errorf("unable to create file for kubeconfig: %w", err) 208 } 209 defer out.Close() 210 contents, err := KubeConfigFromREST(u.Config()) 211 if err != nil { 212 return nil, err 213 } 214 if _, err := out.Write(contents); err != nil { 215 return nil, fmt.Errorf("unable to write kubeconfig to disk at %s: %w", out.Name(), err) 216 } 217 k := &KubeCtl{ 218 Path: u.plane.KubectlPath, 219 } 220 k.Opts = append(k.Opts, fmt.Sprintf("--kubeconfig=%s", out.Name())) 221 u.kubectl = k 222 return k, nil 223 } 224 225 // AddUser provisions a new user in the cluster. It uses the APIServer's authentication 226 // strategy -- see APIServer.SecureServing.Authn. 227 // 228 // Unlike AddUser, it's safe to pass a nil rest.Config here if you have no 229 // particular opinions about the config. 230 // 231 // The default authentication strategy is not guaranteed to any specific strategy, but it is 232 // guaranteed to be callable both before and after Start has been called (but, as noted in the 233 // AuthenticatedUser docs, the given user objects are only valid after Start has been called). 234 func (f *ControlPlane) AddUser(user User, baseConfig *rest.Config) (*AuthenticatedUser, error) { 235 if f.GetAPIServer().SecureServing.Authn == nil { 236 return nil, fmt.Errorf("no API server authentication is configured yet. The API server defaults one when Start is called, did you mean to use that?") 237 } 238 239 if baseConfig == nil { 240 baseConfig = &rest.Config{} 241 } 242 cfg, err := f.GetAPIServer().SecureServing.AddUser(user, baseConfig) 243 if err != nil { 244 return nil, err 245 } 246 247 return &AuthenticatedUser{ 248 cfg: cfg, 249 plane: f, 250 }, nil 251 } 252 253 // GetAPIServer returns this ControlPlane's APIServer, initializing it if necessary. 254 func (f *ControlPlane) GetAPIServer() *APIServer { 255 if f.APIServer == nil { 256 f.APIServer = &APIServer{} 257 } 258 return f.APIServer 259 }