You are an expert in writing code in many different languages. Your goal is to perform code completion for the following code, keeping in mind the rest of the code and the file meta data.
    Metadata details:
    filename: dryrunclient.go
    path of file: ./repos/kubeedge/vendor/k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient
    
    the code of the file until where you have to start completion: /*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package apiclient

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"strings"

	"github.com/pkg/errors"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	clientset "k8s.io/client-go/kubernetes"
	fakeclientset "k8s.io/client-go/kubernetes/fake"
	core "k8s.io/client-go/testing"
	bootstrapapi "k8s.io/cluster-bootstrap/token/api"

	kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)

// DryRunGetter is an interface that must be supplied to the NewDryRunClient function in order to construct a fully functional fake dryrun clientset
type DryRunGetter interface {
	HandleGetAction(core.GetAction) (bool, runtime.Object, error)
	HandleListAction(core.ListAction) (bool, runtime.Object, error)
}

// MarshalFunc takes care of converting any object to a byte array for displaying the object to the user
type MarshalFunc func(runtime.Object, schema.GroupVersion) ([]byte, error)

// DefaultMarshalFunc is the default MarshalFunc used; uses YAML to print objects to the user
func DefaultMarshalFunc(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) {
	return kubeadmutil.MarshalToYaml(obj, gv)
}

// DryRunClientOptions specifies options to pass to NewDryRunClientWithOpts in order to get a dryrun clientset
type DryRunClientOptions struct {
	Writer          io.Writer
	Getter          DryRunGetter
	Pre
    
func NewDryRunClientWithOpts(opts DryRunClientOptions) clientset.Interface {
	// Build a chain of reactors to act like a normal clientset; but log everything that is happening and don't change any state
	client := fakeclientset.NewSimpleClientset()

	// Build the chain of reactors. Order matters; first item here will be invoked first on match, then the second one will be evaluated, etc.
	defaultReactorChain := []core.Reactor{
		// Log everything that happens. Default the object if it's about to be created/updated so that the logged object is representative.
		&core.SimpleReactor{
			Verb:     "*",
			Resource: "*",
			Reaction: func(action core.Action) (bool, runtime.Object, error) {
				logDryRunAction(action, opts.Writer, opts.MarshalFunc)

				return false, nil, nil
			},
		},
		// Let the DryRunGetter implementation take care of all GET requests.
		// The DryRunGetter implementation may call a real API Server behind the scenes or just fake everything
		&core.SimpleReactor{
			Verb:     "get",
			Resource: "*",
			Reaction: func(action core.Action) (bool, runtime.Object, error) {
				getAction, ok := action.(core.GetAction)
				if !ok {
					// If the GetAction cast fails, this could be an ActionImpl with a "get" verb.
					// Such actions could be invoked from any of the fake discovery calls, such as ServerVersion().
					// Attempt the cast to ActionImpl and construct a GetActionImpl from it.
					actionImpl, ok := action.(core.ActionImpl)
					if ok {
						getAction = core.GetActionImpl{ActionImpl: actionImpl}
					} else {
						// something's wrong, we can't handle this event
						return true, nil, errors.New("can't cast get reactor event action object to GetAction interface")
					}
				}
				handled, obj, err := opts.Getter.HandleGetAction(getAction)

				if opts.PrintGETAndLIST {
					// Print the marshalled object format with one tab indentation
					objBytes, err := opts.MarshalFunc(obj, action.GetResource().GroupVersion())
					if err == nil {
						fmt.Println("[dryrun] Returning faked GET response:")
						PrintBytesWithLinePrefix(opts.Writer, objBytes, "\t")
					}
				}

				return handled, obj, err
			},
		},
		// Let the DryRunGetter implementation take care of all GET requests.
		// The DryRunGetter implementation may call a real API Server behind the scenes or just fake everything
		&core.SimpleReactor{
			Verb:     "list",
			Resource: "*",
			Reaction: func(action core.Action) (bool, runtime.Object, error) {
				listAction, ok := action.(core.ListAction)
				if !ok {
					// something's wrong, we can't handle this event
					return true, nil, errors.New("can't cast list reactor event action object to ListAction interface")
				}
				handled, objs, err := opts.Getter.HandleListAction(listAction)

				if opts.PrintGETAndLIST {
					// Print the marshalled object format with one tab indentation
					objBytes, err := opts.MarshalFunc(objs, action.GetResource().GroupVersion())
					if err == nil {
						fmt.Println("[dryrun] Returning faked LIST response:")
						PrintBytesWithLinePrefix(opts.Writer, objBytes, "\t")
					}
				}

				return handled, objs, err
			},
		},
		// For the verbs that modify anything on the server; just return the object if present and exit successfully
		&core.SimpleReactor{
			Verb:     "create",
			Resource: "*",
			Reaction: func(action core.Action) (bool, runtime.Object, error) {
				objAction, ok := action.(actionWithObject)
				if obj := objAction.GetObject(); ok && obj != nil {
					if secret, ok := obj.(*v1.Secret); ok {
						if secret.Namespace == metav1.NamespaceSystem && strings.HasPrefix(secret.Name, bootstrapapi.BootstrapTokenSecretPrefix) {
							// bypass bootstrap token secret create event so that it can be persisted to the backing data store
							// this secret should be readable during the uploadcerts init phase if it has already been created
							return false, nil, nil
						}
					}
				}
				return successfulModificationReactorFunc(action)
			},
		},
		&core.SimpleReactor{
			Verb:     "update",
			Resource: "*",
			Reaction: successfulModificationReactorFunc,
		},
		&core.SimpleReactor{
			Verb:     "delete",
			Resource: "*",
			Reaction: successfulModificationReactorFunc,
		},
		&core.SimpleReactor{
			Verb:     "delete-collection",
			Resource: "*",
			Reaction: successfulModificationReactorFunc,
		},
		&core.SimpleReactor{
			Verb:     "patch",
			Resource: "*",
			Reaction: successfulModificationReactorFunc,
		},
	}

	// The chain of reactors will look like this:
	// opts.PrependReactors | defaultReactorChain | opts.AppendReactors | client.Fake.ReactionChain (default reactors for the fake clientset)
	fullReactorChain := append(opts.PrependReactors, defaultReactorChain...)
	fullReactorChain = append(fullReactorChain, opts.AppendReactors...)

	// Prepend the reaction chain with our reactors. Important, these MUST be prepended; not appended due to how the fake clientset works by default
	client.Fake.ReactionChain = append(fullReactorChain, client.Fake.ReactionChain...)
	return client
}