...

Source file src/sigs.k8s.io/controller-runtime/pkg/internal/testing/controlplane/plane.go

Documentation: sigs.k8s.io/controller-runtime/pkg/internal/testing/controlplane

     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  }
   260  

View as plain text