...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient/dryrunclient.go

Documentation: k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient

     1  /*
     2  Copyright 2017 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 apiclient
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"fmt"
    23  	"io"
    24  	"strings"
    25  
    26  	"github.com/pkg/errors"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	clientset "k8s.io/client-go/kubernetes"
    33  	fakeclientset "k8s.io/client-go/kubernetes/fake"
    34  	core "k8s.io/client-go/testing"
    35  	bootstrapapi "k8s.io/cluster-bootstrap/token/api"
    36  
    37  	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
    38  )
    39  
    40  // DryRunGetter is an interface that must be supplied to the NewDryRunClient function in order to construct a fully functional fake dryrun clientset
    41  type DryRunGetter interface {
    42  	HandleGetAction(core.GetAction) (bool, runtime.Object, error)
    43  	HandleListAction(core.ListAction) (bool, runtime.Object, error)
    44  }
    45  
    46  // MarshalFunc takes care of converting any object to a byte array for displaying the object to the user
    47  type MarshalFunc func(runtime.Object, schema.GroupVersion) ([]byte, error)
    48  
    49  // DefaultMarshalFunc is the default MarshalFunc used; uses YAML to print objects to the user
    50  func DefaultMarshalFunc(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) {
    51  	return kubeadmutil.MarshalToYaml(obj, gv)
    52  }
    53  
    54  // DryRunClientOptions specifies options to pass to NewDryRunClientWithOpts in order to get a dryrun clientset
    55  type DryRunClientOptions struct {
    56  	Writer          io.Writer
    57  	Getter          DryRunGetter
    58  	PrependReactors []core.Reactor
    59  	AppendReactors  []core.Reactor
    60  	MarshalFunc     MarshalFunc
    61  	PrintGETAndLIST bool
    62  }
    63  
    64  // GetDefaultDryRunClientOptions returns the default DryRunClientOptions values
    65  func GetDefaultDryRunClientOptions(drg DryRunGetter, w io.Writer) DryRunClientOptions {
    66  	return DryRunClientOptions{
    67  		Writer:          w,
    68  		Getter:          drg,
    69  		PrependReactors: []core.Reactor{},
    70  		AppendReactors:  []core.Reactor{},
    71  		MarshalFunc:     DefaultMarshalFunc,
    72  		PrintGETAndLIST: false,
    73  	}
    74  }
    75  
    76  // actionWithName is the generic interface for an action that has a name associated with it
    77  // This just makes it easier to catch all actions that has a name; instead of hard-coding all request that has it associated
    78  type actionWithName interface {
    79  	core.Action
    80  	GetName() string
    81  }
    82  
    83  // actionWithObject is the generic interface for an action that has an object associated with it
    84  // This just makes it easier to catch all actions that has an object; instead of hard-coding all request that has it associated
    85  type actionWithObject interface {
    86  	core.Action
    87  	GetObject() runtime.Object
    88  }
    89  
    90  // NewDryRunClient is a wrapper for NewDryRunClientWithOpts using some default values
    91  func NewDryRunClient(drg DryRunGetter, w io.Writer) clientset.Interface {
    92  	return NewDryRunClientWithOpts(GetDefaultDryRunClientOptions(drg, w))
    93  }
    94  
    95  // NewDryRunClientWithOpts returns a clientset.Interface that can be used normally for talking to the Kubernetes API.
    96  // This client doesn't apply changes to the backend. The client gets GET/LIST values from the DryRunGetter implementation.
    97  // This client logs all I/O to the writer w in YAML format
    98  func NewDryRunClientWithOpts(opts DryRunClientOptions) clientset.Interface {
    99  	// Build a chain of reactors to act like a normal clientset; but log everything that is happening and don't change any state
   100  	client := fakeclientset.NewSimpleClientset()
   101  
   102  	// Build the chain of reactors. Order matters; first item here will be invoked first on match, then the second one will be evaluated, etc.
   103  	defaultReactorChain := []core.Reactor{
   104  		// Log everything that happens. Default the object if it's about to be created/updated so that the logged object is representative.
   105  		&core.SimpleReactor{
   106  			Verb:     "*",
   107  			Resource: "*",
   108  			Reaction: func(action core.Action) (bool, runtime.Object, error) {
   109  				logDryRunAction(action, opts.Writer, opts.MarshalFunc)
   110  
   111  				return false, nil, nil
   112  			},
   113  		},
   114  		// Let the DryRunGetter implementation take care of all GET requests.
   115  		// The DryRunGetter implementation may call a real API Server behind the scenes or just fake everything
   116  		&core.SimpleReactor{
   117  			Verb:     "get",
   118  			Resource: "*",
   119  			Reaction: func(action core.Action) (bool, runtime.Object, error) {
   120  				getAction, ok := action.(core.GetAction)
   121  				if !ok {
   122  					// If the GetAction cast fails, this could be an ActionImpl with a "get" verb.
   123  					// Such actions could be invoked from any of the fake discovery calls, such as ServerVersion().
   124  					// Attempt the cast to ActionImpl and construct a GetActionImpl from it.
   125  					actionImpl, ok := action.(core.ActionImpl)
   126  					if ok {
   127  						getAction = core.GetActionImpl{ActionImpl: actionImpl}
   128  					} else {
   129  						// something's wrong, we can't handle this event
   130  						return true, nil, errors.New("can't cast get reactor event action object to GetAction interface")
   131  					}
   132  				}
   133  				handled, obj, err := opts.Getter.HandleGetAction(getAction)
   134  
   135  				if opts.PrintGETAndLIST {
   136  					// Print the marshalled object format with one tab indentation
   137  					objBytes, err := opts.MarshalFunc(obj, action.GetResource().GroupVersion())
   138  					if err == nil {
   139  						fmt.Println("[dryrun] Returning faked GET response:")
   140  						PrintBytesWithLinePrefix(opts.Writer, objBytes, "\t")
   141  					}
   142  				}
   143  
   144  				return handled, obj, err
   145  			},
   146  		},
   147  		// Let the DryRunGetter implementation take care of all GET requests.
   148  		// The DryRunGetter implementation may call a real API Server behind the scenes or just fake everything
   149  		&core.SimpleReactor{
   150  			Verb:     "list",
   151  			Resource: "*",
   152  			Reaction: func(action core.Action) (bool, runtime.Object, error) {
   153  				listAction, ok := action.(core.ListAction)
   154  				if !ok {
   155  					// something's wrong, we can't handle this event
   156  					return true, nil, errors.New("can't cast list reactor event action object to ListAction interface")
   157  				}
   158  				handled, objs, err := opts.Getter.HandleListAction(listAction)
   159  
   160  				if opts.PrintGETAndLIST {
   161  					// Print the marshalled object format with one tab indentation
   162  					objBytes, err := opts.MarshalFunc(objs, action.GetResource().GroupVersion())
   163  					if err == nil {
   164  						fmt.Println("[dryrun] Returning faked LIST response:")
   165  						PrintBytesWithLinePrefix(opts.Writer, objBytes, "\t")
   166  					}
   167  				}
   168  
   169  				return handled, objs, err
   170  			},
   171  		},
   172  		// For the verbs that modify anything on the server; just return the object if present and exit successfully
   173  		&core.SimpleReactor{
   174  			Verb:     "create",
   175  			Resource: "*",
   176  			Reaction: func(action core.Action) (bool, runtime.Object, error) {
   177  				objAction, ok := action.(actionWithObject)
   178  				if obj := objAction.GetObject(); ok && obj != nil {
   179  					if secret, ok := obj.(*v1.Secret); ok {
   180  						if secret.Namespace == metav1.NamespaceSystem && strings.HasPrefix(secret.Name, bootstrapapi.BootstrapTokenSecretPrefix) {
   181  							// bypass bootstrap token secret create event so that it can be persisted to the backing data store
   182  							// this secret should be readable during the uploadcerts init phase if it has already been created
   183  							return false, nil, nil
   184  						}
   185  					}
   186  				}
   187  				return successfulModificationReactorFunc(action)
   188  			},
   189  		},
   190  		&core.SimpleReactor{
   191  			Verb:     "update",
   192  			Resource: "*",
   193  			Reaction: successfulModificationReactorFunc,
   194  		},
   195  		&core.SimpleReactor{
   196  			Verb:     "delete",
   197  			Resource: "*",
   198  			Reaction: successfulModificationReactorFunc,
   199  		},
   200  		&core.SimpleReactor{
   201  			Verb:     "delete-collection",
   202  			Resource: "*",
   203  			Reaction: successfulModificationReactorFunc,
   204  		},
   205  		&core.SimpleReactor{
   206  			Verb:     "patch",
   207  			Resource: "*",
   208  			Reaction: successfulModificationReactorFunc,
   209  		},
   210  	}
   211  
   212  	// The chain of reactors will look like this:
   213  	// opts.PrependReactors | defaultReactorChain | opts.AppendReactors | client.Fake.ReactionChain (default reactors for the fake clientset)
   214  	fullReactorChain := append(opts.PrependReactors, defaultReactorChain...)
   215  	fullReactorChain = append(fullReactorChain, opts.AppendReactors...)
   216  
   217  	// Prepend the reaction chain with our reactors. Important, these MUST be prepended; not appended due to how the fake clientset works by default
   218  	client.Fake.ReactionChain = append(fullReactorChain, client.Fake.ReactionChain...)
   219  	return client
   220  }
   221  
   222  // successfulModificationReactorFunc is a no-op that just returns the POSTed/PUTed value if present; but does nothing to edit any backing data store.
   223  func successfulModificationReactorFunc(action core.Action) (bool, runtime.Object, error) {
   224  	objAction, ok := action.(actionWithObject)
   225  	if ok {
   226  		return true, objAction.GetObject(), nil
   227  	}
   228  	return true, nil, nil
   229  }
   230  
   231  // logDryRunAction logs the action that was recorded by the "catch-all" (*,*) reactor and tells the user what would have happened in an user-friendly way
   232  func logDryRunAction(action core.Action, w io.Writer, marshalFunc MarshalFunc) {
   233  
   234  	group := action.GetResource().Group
   235  	if len(group) == 0 {
   236  		group = "core"
   237  	}
   238  	fmt.Fprintf(w, "[dryrun] Would perform action %s on resource %q in API group \"%s/%s\"\n", strings.ToUpper(action.GetVerb()), action.GetResource().Resource, group, action.GetResource().Version)
   239  
   240  	namedAction, ok := action.(actionWithName)
   241  	if ok {
   242  		fmt.Fprintf(w, "[dryrun] Resource name: %q\n", namedAction.GetName())
   243  	}
   244  
   245  	objAction, ok := action.(actionWithObject)
   246  	if ok && objAction.GetObject() != nil {
   247  		// Print the marshalled object with a tab indentation
   248  		objBytes, err := marshalFunc(objAction.GetObject(), action.GetResource().GroupVersion())
   249  		if err == nil {
   250  			fmt.Println("[dryrun] Attached object:")
   251  			PrintBytesWithLinePrefix(w, objBytes, "\t")
   252  		}
   253  	}
   254  
   255  	patchAction, ok := action.(core.PatchAction)
   256  	if ok {
   257  		// Replace all occurrences of \" with a simple " when printing
   258  		fmt.Fprintf(w, "[dryrun] Attached patch:\n\t%s\n", strings.Replace(string(patchAction.GetPatch()), `\"`, `"`, -1))
   259  	}
   260  }
   261  
   262  // PrintBytesWithLinePrefix prints objBytes to writer w with linePrefix in the beginning of every line
   263  func PrintBytesWithLinePrefix(w io.Writer, objBytes []byte, linePrefix string) {
   264  	scanner := bufio.NewScanner(bytes.NewReader(objBytes))
   265  	for scanner.Scan() {
   266  		fmt.Fprintf(w, "%s%s\n", linePrefix, scanner.Text())
   267  	}
   268  }
   269  

View as plain text