Merge pull request #55092 from mengqiy/refactor_factory

Automatic merge from submit-queue (batch tested with PRs 55092, 55348, 55095, 55277, 55352). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

refactor build in kubectl factory

Refactor kubectl factory and resource builder.

This will be helpful for `kinflate`.

```release-note
NONE
```

/assign @monopole
This commit is contained in:
Kubernetes Submit Queue 2017-11-08 21:18:16 -08:00 committed by GitHub
commit 2ee10cc73e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 125 additions and 159 deletions

View File

@ -189,31 +189,23 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
changeCause := f.Command(cmd, false)
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
b := f.NewBuilder().
ContinueOnError().
var b *resource.Builder
if o.local {
b = f.NewBuilder().
Local(f.ClientForMapping)
} else {
b = f.NewUnstructuredBuilder().
LabelSelectorParam(o.selector).
ResourceTypeOrNameArgs(o.all, o.resources...).
Latest()
}
r := b.ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(includeUninitialized).
Flatten()
if !o.local {
// call this method here, as it requires an api call
// and will cause the command to fail when there is
// no connection to a server
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
b = b.LabelSelectorParam(o.selector).
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
ResourceTypeOrNameArgs(o.all, o.resources...).
Latest()
} else {
b = b.Local(f.ClientForMapping)
}
r := b.Do()
Flatten().
Do()
if err := r.Err(); err != nil {
return err
}

View File

@ -198,7 +198,7 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti
return err
}
mapper, typer, err := f.UnstructuredObject()
mapper, _, err := f.UnstructuredObject()
if err != nil {
return err
}
@ -213,8 +213,7 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti
// include the uninitialized objects by default if --prune is true
// unless explicitly set --include-uninitialized=false
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, options.Prune)
r := f.NewBuilder().
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
r := f.NewUnstructuredBuilder().
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().

View File

@ -122,19 +122,13 @@ func (o *SetLastAppliedOptions) Complete(f cmdutil.Factory, cmd *cobra.Command)
}
func (o *SetLastAppliedOptions) Validate(f cmdutil.Factory, cmd *cobra.Command) error {
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
r := f.NewBuilder().
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
r := f.NewUnstructuredBuilder().
NamespaceParam(o.Namespace).DefaultNamespace().
FilenameParam(o.EnforceNamespace, &o.FilenameOptions).
Latest().
Flatten().
Do()
err = r.Err()
err := r.Err()
if err != nil {
return err
}

View File

@ -87,13 +87,7 @@ func (o *ViewLastAppliedOptions) Complete(f cmdutil.Factory, args []string) erro
return err
}
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
r := f.NewBuilder().
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
r := f.NewUnstructuredBuilder().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
ResourceTypeOrNameArgs(enforceNamespace, args...).

View File

@ -129,14 +129,14 @@ func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.C
// build the builder
o.builder = f.NewBuilder()
if !o.local {
if o.local {
o.builder = o.builder.Local(f.ClientForMapping)
} else {
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
if err != nil {
return err
}
o.builder = o.builder.Schema(schema)
} else {
o.builder = o.builder.Local(f.ClientForMapping)
}
cmdNamespace, _, err := f.DefaultNamespace()

View File

@ -183,13 +183,12 @@ func RunCreate(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opt
return err
}
mapper, typer, err := f.UnstructuredObject()
mapper, _, err := f.UnstructuredObject()
if err != nil {
return err
}
r := f.NewBuilder().
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
r := f.NewUnstructuredBuilder().
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().

View File

@ -170,15 +170,14 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args
}
// Set up client based support.
mapper, typer, err := f.UnstructuredObject()
mapper, _, err := f.UnstructuredObject()
if err != nil {
return err
}
o.Mapper = mapper
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
r := f.NewBuilder().
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
r := f.NewUnstructuredBuilder().
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).

View File

@ -116,16 +116,10 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a
return cmdutil.UsageErrorf(cmd, "Required resource not specified.")
}
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
// include the uninitialized objects by default
// unless user explicitly set --include-uninitialized=false
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, true)
r := f.NewBuilder().
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
r := f.NewUnstructuredBuilder().
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces).
FilenameParam(enforceNamespace, options).

View File

@ -408,18 +408,12 @@ func RunDiff(f cmdutil.Factory, diff *DiffProgram, options *DiffOptions, from, t
printer := Printer{}
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
r := f.NewBuilder().
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
r := f.NewUnstructuredBuilder().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &options.FilenameOptions).
Flatten().

View File

@ -190,32 +190,25 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
changeCause := f.Command(cmd, false)
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
b := f.NewBuilder().
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(includeUninitialized).
Flatten()
if !o.local {
// call this method here, as it requires an api call
// and will cause the command to fail when there is
// no connection to a server
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
b = b.LabelSelectorParam(o.selector).
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
var b *resource.Builder
if o.local {
b = f.NewBuilder().
Local(f.ClientForMapping)
} else {
b = f.NewUnstructuredBuilder().
LabelSelectorParam(o.selector).
ResourceTypeOrNameArgs(o.all, o.resources...).
Latest()
} else {
b = b.Local(f.ClientForMapping)
}
one := false
r := b.Do().IntoSingleItemImplied(&one)
r := b.ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
IncludeUninitialized(includeUninitialized).
Flatten().
Do().
IntoSingleItemImplied(&one)
if err := r.Err(); err != nil {
return err
}

View File

@ -153,13 +153,7 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
return fmt.Errorf("unable to parse %q: %v", patch, err)
}
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
r := f.NewBuilder().
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
r := f.NewUnstructuredBuilder().
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &options.FilenameOptions).

View File

@ -120,13 +120,12 @@ func RunReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
return fmt.Errorf("--timeout must have --force specified")
}
mapper, typer, err := f.UnstructuredObject()
mapper, _, err := f.UnstructuredObject()
if err != nil {
return err
}
r := f.NewBuilder().
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
r := f.NewUnstructuredBuilder().
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
@ -249,8 +248,7 @@ func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s
return err
}
r = f.NewBuilder().
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
r = f.NewUnstructuredBuilder().
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().

View File

@ -226,13 +226,7 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str
return options.watch(f, cmd, args)
}
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
r := f.NewBuilder().
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
r := f.NewUnstructuredBuilder().
NamespaceParam(options.Namespace).DefaultNamespace().AllNamespaces(options.AllNamespaces).
FilenameParam(options.ExplicitNamespace, &options.FilenameOptions).
LabelSelectorParam(options.LabelSelector).
@ -439,18 +433,12 @@ func (options *GetOptions) raw(f cmdutil.Factory) error {
// watch starts a client-side watch of one or more resources.
// TODO: remove the need for arguments here.
func (options *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
// TODO: this could be better factored
// include uninitialized objects when watching on a single object
// unless explicitly set --include-uninitialized=false
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, len(args) == 2)
r := f.NewBuilder().
Unstructured(f.UnstructuredClientForMapping, mapper, typer).
r := f.NewUnstructuredBuilder().
NamespaceParam(options.Namespace).DefaultNamespace().AllNamespaces(options.AllNamespaces).
FilenameParam(options.ExplicitNamespace, &options.FilenameOptions).
LabelSelectorParam(options.LabelSelector).
@ -462,7 +450,7 @@ func (options *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []s
SingleResourceType().
Latest().
Do()
err = r.Err()
err := r.Err()
if err != nil {
return err
}

View File

@ -238,13 +238,13 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
FilenameParam(enforceNamespace, &o.FilenameOptions).
Flatten()
if !o.Local {
if o.Local {
b = b.Local(f.ClientForMapping)
} else {
b = b.
LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, o.From).
Latest()
} else {
b = b.Local(f.ClientForMapping)
}
infos, err := b.Do().Infos()
@ -302,13 +302,13 @@ func (o *EnvOptions) RunEnv(f cmdutil.Factory) error {
FilenameParam(enforceNamespace, &o.FilenameOptions).
Flatten()
if !o.Local {
if o.Local {
b = b.Local(f.ClientForMapping)
} else {
b = b.
LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, o.Resources...).
Latest()
} else {
b = b.Local(f.ClientForMapping)
}
o.Infos, err = b.Do().Infos()

View File

@ -147,12 +147,7 @@ func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
IncludeUninitialized(includeUninitialized).
Flatten()
if !o.Local {
builder = builder.
LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, o.Resources...).
Latest()
} else {
if o.Local {
// if a --local flag was provided, and a resource was specified in the form
// <resource>/<name>, fail immediately as --local cannot query the api server
// for the specified resource.
@ -161,6 +156,11 @@ func (o *ImageOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
}
builder = builder.Local(f.ClientForMapping)
} else {
builder = builder.
LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, o.Resources...).
Latest()
}
o.Infos, err = builder.Do().Infos()

View File

@ -151,12 +151,7 @@ func (o *ResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
IncludeUninitialized(includeUninitialized).
Flatten()
if !o.Local {
builder = builder.
LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, args...).
Latest()
} else {
if o.Local {
// if a --local flag was provided, and a resource was specified in the form
// <resource>/<name>, fail immediately as --local cannot query the api server
// for the specified resource.
@ -165,6 +160,11 @@ func (o *ResourcesOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
}
builder = builder.Local(f.ClientForMapping)
} else {
builder = builder.
LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, args...).
Latest()
}
o.Infos, err = builder.Do().Infos()

View File

@ -132,11 +132,7 @@ func (o *SelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
IncludeUninitialized(includeUninitialized).
Flatten()
if !o.local {
o.builder = o.builder.
ResourceTypeOrNameArgs(o.all, o.resources...).
Latest()
} else {
if o.local {
// if a --local flag was provided, and a resource was specified in the form
// <resource>/<name>, fail immediately as --local cannot query the api server
// for the specified resource.
@ -145,6 +141,10 @@ func (o *SelectorOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [
}
o.builder = o.builder.Local(f.ClientForMapping)
} else {
o.builder = o.builder.
ResourceTypeOrNameArgs(o.all, o.resources...).
Latest()
}
o.PrintObject = func(obj runtime.Object) error {

View File

@ -133,12 +133,7 @@ func (o *SubjectOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
IncludeUninitialized(includeUninitialized).
Flatten()
if !o.Local {
builder = builder.
LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, args...).
Latest()
} else {
if o.Local {
// if a --local flag was provided, and a resource was specified in the form
// <resource>/<name>, fail immediately as --local cannot query the api server
// for the specified resource.
@ -147,6 +142,11 @@ func (o *SubjectOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
}
builder = builder.Local(f.ClientForMapping)
} else {
builder = builder.
LabelSelectorParam(o.Selector).
ResourceTypeOrNameArgs(o.All, args...).
Latest()
}
o.Infos, err = builder.Do().Infos()

View File

@ -29,6 +29,7 @@ go_library(
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",

View File

@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
@ -481,6 +482,19 @@ func (f *FakeFactory) NewBuilder() *resource.Builder {
return resource.NewBuilder(mapper, f.CategoryExpander(), typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true))
}
func (f *FakeFactory) NewUnstructuredBuilder() *resource.Builder {
mapper, typer, err := f.UnstructuredObject()
if err != nil {
cmdutil.CheckErr(err)
}
return resource.NewBuilder(
mapper,
f.CategoryExpander(),
typer,
resource.ClientMapperFunc(f.UnstructuredClientForMapping),
unstructured.UnstructuredJSONScheme)
}
func (f *FakeFactory) DefaultResourceFilterOptions(cmd *cobra.Command, withNamespace bool) *printers.PrintOptions {
return &printers.PrintOptions{}
}
@ -758,6 +772,19 @@ func (f *fakeAPIFactory) NewBuilder() *resource.Builder {
return resource.NewBuilder(mapper, f.CategoryExpander(), typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true))
}
func (f *fakeAPIFactory) NewUnstructuredBuilder() *resource.Builder {
mapper, typer, err := f.UnstructuredObject()
if err != nil {
cmdutil.CheckErr(err)
}
return resource.NewBuilder(
mapper,
f.CategoryExpander(),
typer,
resource.ClientMapperFunc(f.UnstructuredClientForMapping),
unstructured.UnstructuredJSONScheme)
}
func (f *fakeAPIFactory) SuggestedPodTemplateResources() []schema.GroupResource {
return []schema.GroupResource{}
}

View File

@ -107,12 +107,12 @@ func (o *EditOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args []
if err != nil {
return err
}
mapper, typer, err := f.UnstructuredObject()
mapper, _, err := f.UnstructuredObject()
if err != nil {
return err
}
b := f.NewBuilder().Unstructured(f.UnstructuredClientForMapping, mapper, typer)
b := f.NewUnstructuredBuilder()
if o.EditMode == NormalEditMode || o.EditMode == ApplyEditMode {
// when do normal edit or apply edit we need to always retrieve the latest resource from server
b = b.ResourceTypeOrNameArgs(true, args...).Latest()
@ -132,7 +132,7 @@ func (o *EditOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args []
o.updatedResultGetter = func(data []byte) *resource.Result {
// resource builder to read objects from edited data
return resource.NewBuilder(mapper, f.CategoryExpander(), typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
return f.NewUnstructuredBuilder().
Stream(bytes.NewReader(data), "edited-file").
IncludeUninitialized(includeUninitialized).
ContinueOnError().

View File

@ -243,8 +243,10 @@ type BuilderFactory interface {
PrinterForMapping(cmd *cobra.Command, isLocal bool, outputOpts *printers.OutputOptions, mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinter, error)
// PrintObject prints an api object given command line flags to modify the output format
PrintObject(cmd *cobra.Command, isLocal bool, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error
// One stop shopping for a Builder
// One stop shopping for a structured Builder
NewBuilder() *resource.Builder
// One stop shopping for a unstructured Builder
NewUnstructuredBuilder() *resource.Builder
// PluginLoader provides the implementation to be used to load cli plugins.
PluginLoader() plugins.PluginLoader
// PluginRunner provides the implementation to be used to run cli plugins.

View File

@ -138,8 +138,7 @@ func (f *ring2Factory) PrintObject(cmd *cobra.Command, isLocal bool, mapper meta
return printer.PrintObj(obj, out)
}
// NewBuilder returns a new resource builder.
// Receives a bool flag and avoids remote calls if set to false
// NewBuilder returns a new resource builder for structured api objects.
func (f *ring2Factory) NewBuilder() *resource.Builder {
clientMapperFunc := resource.ClientMapperFunc(f.objectMappingFactory.ClientForMapping)
@ -149,6 +148,18 @@ func (f *ring2Factory) NewBuilder() *resource.Builder {
return resource.NewBuilder(mapper, categoryExpander, typer, clientMapperFunc, f.clientAccessFactory.Decoder(true))
}
// NewUnstructuredBuilder returns a new resource builder for unstructured api objects.
func (f *ring2Factory) NewUnstructuredBuilder() *resource.Builder {
clientMapperFunc := resource.ClientMapperFunc(f.objectMappingFactory.UnstructuredClientForMapping)
mapper, typer, err := f.objectMappingFactory.UnstructuredObject()
if err != nil {
CheckErr(err)
}
categoryExpander := f.objectMappingFactory.CategoryExpander()
return resource.NewBuilder(mapper, categoryExpander, typer, clientMapperFunc, unstructured.UnstructuredJSONScheme)
}
// PluginLoader loads plugins from a path set by the KUBECTL_PLUGINS_PATH env var.
// If this env var is not set, it defaults to
// "~/.kube/plugins", plus

View File

@ -29,7 +29,6 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",

View File

@ -26,7 +26,6 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -170,17 +169,6 @@ func (b *Builder) Local(mapperFunc ClientMapperFunc) *Builder {
return b
}
// Unstructured updates the builder's ClientMapper, RESTMapper,
// ObjectTyper, and codec for working with unstructured api objects
func (b *Builder) Unstructured(mapperFunc ClientMapperFunc, mapper meta.RESTMapper, typer runtime.ObjectTyper) *Builder {
b.mapper.RESTMapper = mapper
b.mapper.ObjectTyper = typer
b.mapper.Decoder = unstructured.UnstructuredJSONScheme
b.mapper.ClientMapper = ClientMapperFunc(mapperFunc)
return b
}
// URL accepts a number of URLs directly.
func (b *Builder) URL(httpAttemptCount int, urls ...*url.URL) *Builder {
for _, u := range urls {