resource目录下的概念们 在看Kubectl的源码时,最头痛的是/pkg/kubectl/resource目录下各种概念。什么是builder,什么是info,什么是visitor。。。如果了解了这些概念,再来看Kubectl源码,那么就能起到事半功倍的效果。本次先对Builder进行较深程度的分析,然后分批次介绍其他概念。概念的理解可能带有一些主观的色彩,还请包涵。
什么是Builder Builder定义在/pkg/kubectl/resource/builder.go中。Builder是Kubectl命令行信息的内部载体,可以通过Builder生成Result对象(后续介绍)。Builder大多方法支持链式调用,即1
2
3
4
5
6
7
8
9
10
resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), runtime.UnstructuredJSONScheme).
NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces).
FilenameParam(enforceNamespace, &options.FilenameOptions).
SelectorParam(selector).
ExportParam(export).
ResourceTypeOrNameArgs(true , args...).
ContinueOnError().
Latest().
Flatten().
Do()
这是因为这些方法返回的对象自身。
先来看下Builder的定义。Builder中有一个mapper(后续介绍),然后就是一些资源方面的字段,这些资源字段都有方法对其进行设置。其中,resources需要和names或selector配合使用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
type Builder struct {
mapper *Mapper
errs []error
paths []Visitor
stream bool
dir bool
selector labels.Selector
selectAll bool
resources []string
namespace string
allNamespace bool
names []string
resourceTuples []resourceTuple
defaultNamespace bool
requireNamespace bool
flatten bool
latest bool
requireObject bool
singleResourceType bool
continueOnError bool
singular bool
export bool
schema validation.Schema
}
NewBuilder() NewBuilder()生成一个Builder,新的Builder只设置了mapper,及标记requireObject为true。1
2
3
4
5
6
7
func NewBuilder (mapper meta.RESTMapper, typer runtime.ObjectTyper, clientMapper ClientMapper, decoder runtime.Decoder) *Builder {
return &Builder{
mapper: &Mapper{typer, mapper, clientMapper, decoder},
requireObject: true ,
}
}
Do() Do()先调用visitorResult()生成Result对象,然后设置Result对象中的visitor。现在我们必需牢记,Builder的Do()可以生成Result。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func (b *Builder) Do () *Result {
r := b.visitorResult()
if r.err != nil {
return r
}
if b.flatten {
r.visitor = NewFlattenListVisitor(r.visitor, b.mapper)
}
helpers := []VisitorFunc{}
if b.defaultNamespace {
helpers = append (helpers, SetNamespace(b.namespace))
}
if b.requireNamespace {
helpers = append (helpers, RequireNamespace(b.namespace))
}
helpers = append (helpers, FilterNamespace)
if b.requireObject {
helpers = append (helpers, RetrieveLazy)
}
r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
if b.continueOnError {
r.visitor = ContinueOnErrorVisitor{r.visitor}
}
return r
}
visitResult() visitResult()会根据Builder中字段的设置情况来生成不同的Result。一般来说b.paths, b.selectors, b.resourceTuples, b.names只需设置一个即可。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func (b *Builder) visitorResult () *Result {
if len (b.errs) > 0 {
return &Result{err: utilerrors.NewAggregate(b.errs)}
}
if b.selectAll {
b.selector = labels.Everything()
}
if len (b.paths) != 0 {
return b.visitByPaths()
}
if b.selector != nil {
return b.visitBySelector()
}
if len (b.resourceTuples) != 0 {
return b.visitByResource()
}
if len (b.names) != 0 {
return b.visitByName()
}
if len (b.resources) != 0 {
return &Result{err: fmt.Errorf("resource(s) were provided, but no name, label selector, or --all flag specified" )}
}
return &Result{err: missingResourceError}
}
visitBySelector() visitBySelector()生成一个Selector visitor,然后把该visitor封装成Result并返回。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//***selector visitor的visitFunc会生成info***//
func (b *Builder) visitBySelector() *Result {
if len(b.names) != 0 {
return &Result{err: fmt.Errorf("name cannot be provided when a selector is specified")}
}
if len(b.resourceTuples) != 0 {
return &Result{err: fmt.Errorf("selectors and the all flag cannot be used when passing resource/name arguments")}
}
if len(b.resources) == 0 {
return &Result{err: fmt.Errorf("at least one resource must be specified to use a selector")}
}
mappings, err := b.resourceMappings()
if err != nil {
return &Result{err: err}
}
visitors := []Visitor{}
for _, mapping := range mappings {
//***生成client***//
client, err := b.mapper.ClientForMapping(mapping)
if err != nil {
return &Result{err: err}
}
selectorNamespace := b.namespace
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
selectorNamespace = ""
}
visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, b.selector, b.export))
}
if b.continueOnError {
return &Result{visitor: EagerVisitorList(visitors), sources: visitors}
}
return &Result{visitor: VisitorList(visitors), sources: visitors}
}
visitByResource() visitByResource()使用resourceTuples生成Info(后续介绍),Info本身也是个visitor,所以把Info打包成Result并返回。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//***通过resource生成info***//
func (b *Builder) visitByResource() *Result {
// if b.singular is false, this could be by default, so double-check length
// of resourceTuples to determine if in fact it is singular or not
isSingular := b.singular
if !isSingular {
isSingular = len(b.resourceTuples) == 1
}
if len(b.resources) != 0 {
return &Result{singular: isSingular, err: fmt.Errorf("you may not specify individual resources and bulk resources in the same call")}
}
// retrieve one client for each resource
mappings, err := b.resourceTupleMappings()
if err != nil {
return &Result{singular: isSingular, err: err}
}
clients := make(map[string]RESTClient)
for _, mapping := range mappings {
s := fmt.Sprintf("%s/%s", mapping.GroupVersionKind.GroupVersion().String(), mapping.Resource)
if _, ok := clients[s]; ok {
continue
}
client, err := b.mapper.ClientForMapping(mapping)
if err != nil {
return &Result{err: err}
}
clients[s] = client
}
items := []Visitor{}
for _, tuple := range b.resourceTuples {
mapping, ok := mappings[tuple.Resource]
if !ok {
return &Result{singular: isSingular, err: fmt.Errorf("resource %q is not recognized: %v", tuple.Resource, mappings)}
}
s := fmt.Sprintf("%s/%s", mapping.GroupVersionKind.GroupVersion().String(), mapping.Resource)
client, ok := clients[s]
if !ok {
return &Result{singular: isSingular, err: fmt.Errorf("could not find a client for resource %q", tuple.Resource)}
}
selectorNamespace := b.namespace
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
selectorNamespace = ""
} else {
if len(b.namespace) == 0 {
errMsg := "namespace may not be empty when retrieving a resource by name"
if b.allNamespace {
errMsg = "a resource cannot be retrieved by name across all namespaces"
}
return &Result{singular: isSingular, err: fmt.Errorf(errMsg)}
}
}
//***生成info***//
info := NewInfo(client, mapping, selectorNamespace, tuple.Name, b.export)
items = append(items, info)
}
var visitors Visitor
if b.continueOnError {
visitors = EagerVisitorList(items)
} else {
visitors = VisitorList(items)
}
return &Result{singular: isSingular, visitor: visitors, sources: items}
}
visitByName() visitByName()先通过b.names来生成info,然后封装成Result返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
func (b *Builder) visitByName () *Result {
isSingular := len (b.names) == 1
if len (b.paths) != 0 {
return &Result{singular: isSingular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well" )}
}
if len (b.resources) == 0 {
return &Result{singular: isSingular, err: fmt.Errorf("you must provide a resource and a resource name together" )}
}
if len (b.resources) > 1 {
return &Result{singular: isSingular, err: fmt.Errorf("you must specify only one resource" )}
}
mappings, err := b.resourceMappings()
if err != nil {
return &Result{singular: isSingular, err: err}
}
mapping := mappings[0 ]
client, err := b.mapper.ClientForMapping(mapping)
if err != nil {
return &Result{err: err}
}
selectorNamespace := b.namespace
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
selectorNamespace = ""
} else {
if len (b.namespace) == 0 {
errMsg := "namespace may not be empty when retrieving a resource by name"
if b.allNamespace {
errMsg = "a resource cannot be retrieved by name across all namespaces"
}
return &Result{singular: isSingular, err: fmt.Errorf(errMsg)}
}
}
visitors := []Visitor{}
for _, name := range b.names {
info := NewInfo(client, mapping, selectorNamespace, name, b.export)
visitors = append (visitors, info)
}
return &Result{singular: isSingular, visitor: VisitorList(visitors), sources: visitors}
}
visitByPaths() visitByPaths()根据b.Paths生成Results。因为b.Paths本身就是个visitor数组。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
func (b *Builder) visitByPaths () *Result {
singular := !b.dir && !b.stream && len (b.paths) == 1
if len (b.resources) != 0 {
return &Result{singular: singular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify resource arguments as well" )}
}
if len (b.names) != 0 {
return &Result{err: fmt.Errorf("name cannot be provided when a path is specified" )}
}
if len (b.resourceTuples) != 0 {
return &Result{err: fmt.Errorf("resource/name arguments cannot be provided when a path is specified" )}
}
var visitors Visitor
if b.continueOnError {
visitors = EagerVisitorList(b.paths)
} else {
visitors = VisitorList(b.paths)
}
if b.latest {
if b.flatten {
visitors = NewFlattenListVisitor(visitors, b.mapper)
}
if b.defaultNamespace {
visitors = NewDecoratedVisitor(visitors, SetNamespace(b.namespace))
}
visitors = NewDecoratedVisitor(visitors, RetrieveLatest)
}
if b.selector != nil {
visitors = NewFilteredVisitor(visitors, FilterBySelector(b.selector))
}
return &Result{singular: singular, visitor: visitors, sources: b.paths}
}
接下来介绍Builder的字段设置函数。
Schema() Schema()设置Builder的schema字段。schema可以对资源名称进行规范检查。1
2
3
4
func (b *Builder) Schema (schema validation.Schema) *Builder {
b.schema = schema
return b
}
FilenameParam() FilenameParam()可以处理不同的输入方式,并设置Paths字段。如kubectl create -f abc.yaml
,则filenameOptions为&{[/home/abc.yaml] false}。 FilenameParam()目前支持标准输入,URL方式,文件方式,对应的支持函数为Stdin(), URL(), Path()。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func (b *Builder) FilenameParam (enforceNamespace bool , filenameOptions *FilenameOptions) *Builder {
recursive := filenameOptions.Recursive
paths := filenameOptions.Filenames
for _, s := range paths {
switch {
case s == "-" :
b.Stdin()
case strings.Index(s, "http://" ) == 0 || strings.Index(s, "https://" ) == 0 :
url, err := url.Parse(s)
if err != nil {
b.errs = append (b.errs, fmt.Errorf("the URL passed to filename %q is not valid: %v" , s, err))
continue
}
b.URL(defaultHttpGetAttempts, url)
default :
if !recursive {
b.singular = true
}
b.Path(recursive, s)
}
}
if enforceNamespace {
b.RequireNamespace()
}
return b
}
Stdin() Stdin()会调用FileVIsitorForSTDIN()生成一个visitor,然后把该visitor加入到paths中。1
2
3
4
5
6
func (b *Builder) Stdin () *Builder {
b.stream = true
b.paths = append (b.paths, FileVisitorForSTDIN(b.mapper, b.schema))
return b
}
URL() URL()先生成一个url visitor,然后把该visitor加入到paths中。1
2
3
4
5
6
7
8
9
10
11
func (b *Builder) URL (httpAttemptCount int , urls ...*url.URL) *Builder {
for _, u := range urls {
b.paths = append (b.paths, &URLVisitor{
URL: u,
StreamVisitor: NewStreamVisitor(nil , b.mapper, u.String(), b.schema),
HttpAttemptCount: httpAttemptCount,
})
}
return b
}
Stream() Stream()先生成一个stream visitor,然后把该visitor加入到paths中。1
2
3
4
5
6
func (b *Builder) Stream (r io.Reader, name string ) *Builder {
b.stream = true
b.paths = append (b.paths, NewStreamVisitor(r, b.mapper, name, b.schema))
return b
}
Path() Path()把路径转换成file visitor,然后加入到paths中。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func (b *Builder) Path (recursive bool , paths ...string ) *Builder {
for _, p := range paths {
_, err := os.Stat(p)
if os.IsNotExist(err) {
b.errs = append (b.errs, fmt.Errorf("the path %q does not exist" , p))
continue
}
if err != nil {
b.errs = append (b.errs, fmt.Errorf("the path %q cannot be accessed: %v" , p, err))
continue
}
visitors, err := ExpandPathsToFileVisitors(b.mapper, p, recursive, FileExtensions, b.schema)
if err != nil {
b.errs = append (b.errs, fmt.Errorf("error reading %q: %v" , p, err))
}
if len (visitors) > 1 {
b.dir = true
}
b.paths = append (b.paths, visitors...)
}
return b
}
ResourceTypes() ResourceTypes()设置Builder的resources字段,如kubectl get pods abc,则resources为pods1
2
3
4
5
6
func (b *Builder) ResourceTypes (types ...string ) *Builder {
b.resources = append (b.resources, types...)
return b
}
ResourceNames() ResourceNames()设置resourceTuples字段,表示需要访问的对象。如果kubectl logs nginx,那么ResourceNames()会把”nginx”和Builder中的resource(logs命令中设置为pods),组成(pods, nginx)加入到resourceTuples中。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func (b *Builder) ResourceNames (resource string , names ...string ) *Builder {
for _, name := range names {
tuple, ok, err := splitResourceTypeName(name)
if err != nil {
b.errs = append (b.errs, err)
return b
}
if ok {
b.resourceTuples = append (b.resourceTuples, tuple)
continue
}
if len (resource) == 0 {
b.errs = append (b.errs, fmt.Errorf("the argument %q must be RESOURCE/NAME" , name))
continue
}
b.resourceTuples = append (b.resourceTuples, resourceTuple{Resource: resource, Name: name})
}
return b
}
SelectorParam() SelectorParam()的参数为string,设置Builder的selector字段。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func (b *Builder) SelectorParam (s string ) *Builder {
selector, err := labels.Parse(s)
if err != nil {
b.errs = append (b.errs, fmt.Errorf("the provided selector %q is not valid: %v" , s, err))
return b
}
if selector.Empty() {
return b
}
if b.selectAll {
b.errs = append (b.errs, fmt.Errorf("found non empty selector %q with previously set 'all' parameter. " , s))
return b
}
return b.Selector(selector)
}
Selector() Selector()的参数为selector,直接设置Builder的selector字段。1
2
3
4
func (b *Builder) Selector (selector labels.Selector) *Builder {
b.selector = selector
return b
}
ExportParam() 用途未知。1
2
3
4
func (b *Builder) ExportParam (export bool ) *Builder {
b.export = export
return b
}
NamespaceParam() NamespaceParam()可以设置Builder的namespace字段。1
2
3
4
5
func (b *Builder) NamespaceParam (namespace string ) *Builder {
b.namespace = namespace
return b
}
DefaultNamespace() DefaultNamespace()可以设置Builder的defaultNamespace字段,表示如果namespace未指定,是否使用default namespace。1
2
3
4
5
func (b *Builder) DefaultNamespace () *Builder {
b.defaultNamespace = true
return b
}
AllNamespaces() AllNamespaces()可以设置Builder的allNamespace和namespace字段,表示访问NamespaceAll。1
2
3
4
5
6
7
8
func (b *Builder) AllNamespaces (allNamespace bool ) *Builder {
if allNamespace {
b.namespace = api.NamespaceAll
}
b.allNamespace = allNamespace
return b
}
RequireNamespace() RequireNamespace()可以设置Builder的requireNamespace字段。1
2
3
4
func (b *Builder) RequireNamespace () *Builder {
b.requireNamespace = true
return b
}
SelectAllParam() SelectAllParam()可以设置Builder的selectAll字段。与selector字段互斥。1
2
3
4
5
6
7
8
func (b *Builder) SelectAllParam (selectAll bool ) *Builder {
if selectAll && b.selector != nil {
b.errs = append (b.errs, fmt.Errorf("setting 'all' parameter but found a non empty selector. " ))
return b
}
b.selectAll = selectAll
return b
}
ResourceTypeOrNameArgs() ResourceTypeOrNameArgs()可以设置Builder的names和resource字段。如执行kubectl get pods/nginx-1487191267-b4w5j rc/abc
,则args为[pods/nginx-1487191267-b4w5j rc/abc]
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
func (b *Builder) ResourceTypeOrNameArgs (allowEmptySelector bool , args ...string ) *Builder {
args = normalizeMultipleResourcesArgs(args)
if ok, err := hasCombinedTypeArgs(args); ok {
if err != nil {
b.errs = append (b.errs, err)
return b
}
for _, s := range args {
tuple, ok, err := splitResourceTypeName(s)
if err != nil {
b.errs = append (b.errs, err)
return b
}
if ok {
b.resourceTuples = append (b.resourceTuples, tuple)
}
}
return b
}
if len (args) > 0 {
args[0 ] = b.ReplaceAliases(args[0 ])
}
switch {
case len (args) > 2 :
b.names = append (b.names, args[1 :]...)
b.ResourceTypes(SplitResourceArgument(args[0 ])...)
case len (args) == 2 :
b.names = append (b.names, args[1 ])
b.ResourceTypes(SplitResourceArgument(args[0 ])...)
case len (args) == 1 :
b.ResourceTypes(SplitResourceArgument(args[0 ])...)
if b.selector == nil && allowEmptySelector {
b.selector = labels.Everything()
}
case len (args) == 0 :
default :
b.errs = append (b.errs, fmt.Errorf("arguments must consist of a resource or a resource and name" ))
}
return b
}
Flatten() Flatten()可以设置Builder的flatten字段。1
2
3
4
func (b *Builder) Flatten () *Builder {
b.flatten = true
return b
}
Latest() Latest()可以设置Builder的latest字段,用来标识获取URL或Path中最新的内容。1
2
3
4
func (b *Builder) Latest () *Builder {
b.latest = true
return b
}
RequireObject() RequireObject()可以设置Builder的requireObject字段。1
2
3
4
func (b *Builder) RequireObject (require bool ) *Builder {
b.requireObject = require
return b
}
ContinueOnError() ContinueOnError()可以设置Builder的continueOnError字段为true,在封装visitor时会用到该字段。1
2
3
4
func (b *Builder) ContinueOnError () *Builder {
b.continueOnError = true
return b
}
mappingFor() mappingFor()使用参数resource,然后依据mapper字段,构建RESTMapping并返回。RESTMapping表示resource和GVK的对应关系。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (b *Builder) mappingFor (resourceArg string ) (*meta.RESTMapping, error) {
fullySpecifiedGVR, groupResource := unversioned.ParseResourceArg(resourceArg)
gvk := unversioned.GroupVersionKind{}
if fullySpecifiedGVR != nil {
gvk, _ = b.mapper.KindFor(*fullySpecifiedGVR)
}
if gvk.Empty() {
var err error
gvk, err = b.mapper.KindFor(groupResource.WithVersion("" ))
if err != nil {
return nil , err
}
}
return b.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
}
resourceMappings() resourceMappings()把Builder中的resource传递给上面的mappingFor()生成RESTMapping。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func (b *Builder) resourceMappings () ([]*meta.RESTMapping, error) {
if len (b.resources) > 1 && b.singleResourceType {
return nil , fmt.Errorf("you may only specify a single resource type" )
}
mappings := []*meta.RESTMapping{}
for _, r := range b.resources {
mapping, err := b.mappingFor(r)
if err != nil {
return nil , err
}
mappings = append (mappings, mapping)
}
return mappings, nil
}
resourceTupleMappings() resourceTupleMappings()把Builder中的resourceTuple转换成RESTMapping。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func (b *Builder) resourceTupleMappings () (map [string ]*meta.RESTMapping, error) {
mappings := make (map [string ]*meta.RESTMapping)
canonical := make (map [string ]struct {})
for _, r := range b.resourceTuples {
if _, ok := mappings[r.Resource]; ok {
continue
}
mapping, err := b.mappingFor(r.Resource)
if err != nil {
return nil , err
}
mappings[mapping.Resource] = mapping
mappings[r.Resource] = mapping
canonical[mapping.Resource] = struct {}{}
}
if len (canonical) > 1 && b.singleResourceType {
return nil , fmt.Errorf("you may only specify a single resource type" )
}
return mappings, nil
}