当前位置: 首页 > article >正文

2.7 createCmd中的visitor访问者设计模式

本节重点总结 :

  • visitor访问者模式简介
  • kubectl中的visitor应用

visitor访问者模式简介

  • 访问者模式(Visitor Pattern)是一种将数据结构与数据操作分离的设计模式,
  • 指封装一些作用于某种数据结构中的各元素的操作,
  • 可以在不改变数据结构的前提下定义作用于这些元素的新的操作,
  • 属于行为型设计模式。

访问者模式主要适用于以下应用场景:

  • (1)数据结构稳定,作用于数据结构的操作经常变化的场景。
  • (2)需要数据结构与数据操作分离的场景。
  • (3)需要对不同数据类型(元素)进行操作,而不使用分支判断具体类型的场景。

访问者模式的优点

  • (1)解耦了数据结构与数据操作,使得操作集合可以独立变化。
  • (2)可以通过扩展访问者角色,实现对数据集的不同操作,程序扩展性更好。
  • (3)元素具体类型并非单一,访问者均可操作。
  • (4)各角色职责分离,符合单一职责原则。

访问者模式的缺点

  • (1)无法增加元素类型:若系统数据结构对象易于变化,
    • 经常有新的数据对象增加进来,
    • 则访问者类必须增加对应元素类型的操作,违背了开闭原则。
  • (2)具体元素变更困难:具体元素增加属性、删除属性等操作,
    • 会导致对应的访问者类需要进行相应的修改,
    • 尤其当有大量访问者类时,修改范围太大。
  • (3)违背依赖倒置原则:为了达到“区别对待”,
    • 访问者角色依赖的是具体元素类型,而不是抽象。

kubectl 中的访问者模式

  • 在kubectl中多个Visitor是来访问一个数据结构的不同部分
  • 这种情况下,数据结构有点像一个数据库,而各个Visitor会成为一个个小应用

Visitor接口和VisitorFunc定义

  • 位置 D:\go_path\src\github.com\kubernetes\kubernetes\staging\src\k8s.io\cli-runtime\pkg\resource\interfaces.go
// Visitor lets clients walk a list of resources.
type Visitor interface {
	Visit(VisitorFunc) error
}
// VisitorFunc implements the Visitor interface for a matching function.
// If there was a problem walking a list of resources, the incoming error
// will describe the problem and the function can decide how to handle that error.
// A nil returned indicates to accept an error to continue loops even when errors happen.
// This is useful for ignoring certain kinds of errors or aggregating errors in some way.
type VisitorFunc func(*Info, error) error
  • result的Visit方法
func (r *Result) Visit(fn VisitorFunc) error {
	if r.err != nil {
		return r.err
	}
	err := r.visitor.Visit(fn)
	return utilerrors.FilterOut(err, r.ignoreErrors...)
}
  • 具体的visitor的visit方法定义,参数都是一个VisitorFunc的fn
// Visit in a FileVisitor is just taking care of opening/closing files
func (v *FileVisitor) Visit(fn VisitorFunc) error {
	var f *os.File
	if v.Path == constSTDINstr {
		f = os.Stdin
	} else {
		var err error
		f, err = os.Open(v.Path)
		if err != nil {
			return err
		}
		defer f.Close()
	}

	// TODO: Consider adding a flag to force to UTF16, apparently some
	// Windows tools don't write the BOM
	utf16bom := unicode.BOMOverride(unicode.UTF8.NewDecoder())
	v.StreamVisitor.Reader = transform.NewReader(f, utf16bom)

	return v.StreamVisitor.Visit(fn)
}

kubectl create中 通过Builder模式创建visitor并执行的过程

FilenameParam解析 -f 文件参数,创建一个visitor

  • 位置 D:\go_path\src\github.com\kubernetes\kubernetes\staging\src\k8s.io\cli-runtime\pkg\resource\builder.go

validate校验-f参数

func (o *FilenameOptions) validate() []error {
	var errs []error
	if len(o.Filenames) > 0 && len(o.Kustomize) > 0 {
		errs = append(errs, fmt.Errorf("only one of -f or -k can be specified"))
	}
	if len(o.Kustomize) > 0 && o.Recursive {
		errs = append(errs, fmt.Errorf("the -k flag can't be used with -f or -R"))
	}
	return errs
}
  • -k代表使用 Kustomize配置
  • 如果 -f -k都存在报错 only one of -f or -k can be specified
kubectl create -f rule.yaml  -k rule.yaml 
error: only one of -f or -k can be specified
  • -k不支持递归 -R
kubectl create    -k rule.yaml  -R
error: the -k flag can't be used with -f or -R

调用path解析文件

	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.singleItemImplied = true
			}
			b.Path(recursive, s)
		}
	}
  • 遍历 -f传入的paths
  • 如果 是- 代表从标准输入传入
  • 如果是http开头的代表从远端http接口读取,调用b.URL
  • 默认是文件 ,调用b.Path解析

b.Path调用ExpandPathsToFileVisitors生成visitor

func ExpandPathsToFileVisitors(mapper *mapper, paths string, recursive bool, extensions []string, schema ContentValidator) ([]Visitor, error) {
	var visitors []Visitor
	err := filepath.Walk(paths, func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if fi.IsDir() {
			if path != paths && !recursive {
				return filepath.SkipDir
			}
			return nil
		}
		// Don't check extension if the filepath was passed explicitly
		if path != paths && ignoreFile(path, extensions) {
			return nil
		}

		visitor := &FileVisitor{
			Path:          path,
			StreamVisitor: NewStreamVisitor(nil, mapper, path, schema),
		}

		visitors = append(visitors, visitor)
		return nil
	})

	if err != nil {
		return nil, err
	}
	return visitors, nil
}

底层调用的StreamVisitor,把对应的visit方法注册到visitor中

  • 位置 D:\go_path\src\github.com\kubernetes\kubernetes\staging\src\k8s.io\cli-runtime\pkg\resource\visitor.go
func (v *StreamVisitor) Visit(fn VisitorFunc) error {
	d := yaml.NewYAMLOrJSONDecoder(v.Reader, 4096)
	for {
		ext := runtime.RawExtension{}
		if err := d.Decode(&ext); err != nil {
			if err == io.EOF {
				return nil
			}
			return fmt.Errorf("error parsing %s: %v", v.Source, err)
		}
		// TODO: This needs to be able to handle object in other encodings and schemas.
		ext.Raw = bytes.TrimSpace(ext.Raw)
		if len(ext.Raw) == 0 || bytes.Equal(ext.Raw, []byte("null")) {
			continue
		}
		if err := ValidateSchema(ext.Raw, v.Schema); err != nil {
			return fmt.Errorf("error validating %q: %v", v.Source, err)
		}
		info, err := v.infoForData(ext.Raw, v.Source)
		if err != nil {
			if fnErr := fn(info, err); fnErr != nil {
				return fnErr
			}
			continue
		}
		if err := fn(info, nil); err != nil {
			return err
		}
	}
}
  • 用jsonYamlDecoder解析文件
  • ValidateSchema会解析文件中的字段进行校验,比如我们把spec故意写成aspec
 kubectl apply -f rule.yaml 
error: error validating "rule.yaml": error validating data: [ValidationError(PrometheusRule): unknown field "aspec" in com.coreos.monitoring.v1.PrometheusRule, ValidationError(PrometheusRule): missing required field "spec" in com.coreos.monitoring.v1.PrometheusRule]; if you choose to ignore these errors, turn validation off with --validate=false
  • infoForData将解析结果转换为Info对象

创建Info。object 就是k8s的对象

  • D:\go_path\src\github.com\kubernetes\kubernetes\staging\src\k8s.io\cli-runtime\pkg\resource\mapper.go
  • m.decoder.Decode解析出object 和gvk对象
  • 其中object代表就是k8s的对象
  • gvk是 Group/Version/Kind的缩写
func (m *mapper) infoForData(data []byte, source string) (*Info, error) {
	obj, gvk, err := m.decoder.Decode(data, nil, nil)
	if err != nil {
		return nil, fmt.Errorf("unable to decode %q: %v", source, err)
	}

	name, _ := metadataAccessor.Name(obj)
	namespace, _ := metadataAccessor.Namespace(obj)
	resourceVersion, _ := metadataAccessor.ResourceVersion(obj)

	ret := &Info{
		Source:          source,
		Namespace:       namespace,
		Name:            name,
		ResourceVersion: resourceVersion,

		Object: obj,
	}

	if m.localFn == nil || !m.localFn() {
		restMapper, err := m.restMapperFn()
		if err != nil {
			return nil, err
		}
		mapping, err := restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
		if err != nil {
			return nil, fmt.Errorf("unable to recognize %q: %v", source, err)
		}
		ret.Mapping = mapping

		client, err := m.clientFn(gvk.GroupVersion())
		if err != nil {
			return nil, fmt.Errorf("unable to connect to a server to handle %q: %v", mapping.Resource, err)
		}
		ret.Client = client
	}

	return ret, nil
}

k8s对象object讲解

Object k8s对象

  • 文档地址 https://kubernetes.io/zh/docs/concepts/overview/working-with-objects/kubernetes-objects/
  • D:\go_path\src\github.com\kubernetes\kubernetes\staging\src\k8s.io\apimachinery\pkg\runtime\interfaces.go
// Object interface must be supported by all API types registered with Scheme. Since objects in a scheme are
// expected to be serialized to the wire, the interface an Object must provide to the Scheme allows
// serializers to set the kind, version, and group the object is represented as. An Object may choose
// to return a no-op ObjectKindAccessor in cases where it is not expected to be serialized.
type Object interface {
	GetObjectKind() schema.ObjectKind
	DeepCopyObject() Object
}
作用
  • Kubernetes 对象 是持久化的实体
  • Kubernetes 使用这些实体去表示整个集群的状态。特别地,它们描述了如下信息:
    • 哪些容器化应用在运行(以及在哪些节点上)
    • 可以被应用使用的资源
    • 关于应用运行时表现的策略,比如重启策略、升级策略,以及容错策略
  • 操作 Kubernetes 对象,无论是创建、修改,或者删除, 需要使用 Kubernetes API
期望状态
  • Kubernetes 对象是 “目标性记录” 一旦创建对象,Kubernetes 系统将持续工作以确保对象存在
  • 通过创建对象,本质上是在告知 Kubernetes 系统,所需要的集群工作负载看起来是什么样子的, 这就是 Kubernetes 集群的 期望状态(Desired State)
对象规约(Spec)与状态(Status)
  • 几乎每个 Kubernetes 对象包含两个嵌套的对象字段,它们负责管理对象的配置: 对象 spec(规约) 和 对象 status(状态)
  • 对于具有 spec 的对象,你必须在创建对象时设置其内容,描述你希望对象所具有的特征: 期望状态(Desired State) 。
  • status 描述了对象的 当前状态(Current State),它是由 Kubernetes 系统和组件 设置并更新的。在任何时刻,Kubernetes 控制平面 都一直积极地管理着对象的实际状态,以使之与期望状态相匹配。
yaml中的必须字段
  • 在想要创建的 Kubernetes 对象对应的 .yaml 文件中,需要配置如下的字段:
    • apiVersion - 创建该对象所使用的 Kubernetes API 的版本
    • kind - 想要创建的对象的类别
    • metadata - 帮助唯一性标识对象的一些数据,包括一个 name 字符串、UID 和可选的 namespace

Do中创建一批visitor

func (b *Builder) Do() *Result {
	r := b.visitorResult()
	r.mapper = b.Mapper()
	if r.err != nil {
		return r
	}
	if b.flatten {
		r.visitor = NewFlattenListVisitor(r.visitor, b.objectTyper, 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)
	}
	if b.continueOnError {
		r.visitor = NewDecoratedVisitor(ContinueOnErrorVisitor{r.visitor}, helpers...)
	} else {
		r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
	}
	return r
}

helpers代表一批VisitorFunc

  • 比如校验namespace的 RequireNamespace
func RequireNamespace(namespace string) VisitorFunc {
	return func(info *Info, err error) error {
		if err != nil {
			return err
		}
		if !info.Namespaced() {
			return nil
		}
		if len(info.Namespace) == 0 {
			info.Namespace = namespace
			UpdateObjectNamespace(info, nil)
			return nil
		}
		if info.Namespace != namespace {
			return fmt.Errorf("the namespace from the provided object %q does not match the namespace %q. You must pass '--namespace=%s' to perform this operation.", info.Namespace, namespace, info.Namespace)
		}
		return nil
	}
}

创建带装饰器的visitor DecoratedVisitor

	if b.continueOnError {
		r.visitor = NewDecoratedVisitor(ContinueOnErrorVisitor{r.visitor}, helpers...)
	} else {
		r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
	}
  • 对应的visit方法
func (v DecoratedVisitor) Visit(fn VisitorFunc) error {
	return v.visitor.Visit(func(info *Info, err error) error {
		if err != nil {
			return err
		}
		for i := range v.decorators {
			if err := v.decorators[i](info, nil); err != nil {
				return err
			}
		}
		return fn(info, nil)
	})
}

visitor的调用

Visit调用链分析

  • 外层调用 result.Visit方法,内部的func
	err = r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}
		if err := util.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info.Object, scheme.DefaultJSONEncoder()); err != nil {
			return cmdutil.AddSourceToErr("creating", info.Source, err)
		}

		if err := o.Recorder.Record(info.Object); err != nil {
			klog.V(4).Infof("error recording current command: %v", err)
		}

		if o.DryRunStrategy != cmdutil.DryRunClient {
			if o.DryRunStrategy == cmdutil.DryRunServer {
				if err := o.DryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil {
					return cmdutil.AddSourceToErr("creating", info.Source, err)
				}
			}
			obj, err := resource.
				NewHelper(info.Client, info.Mapping).
				DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
				WithFieldManager(o.fieldManager).
				Create(info.Namespace, true, info.Object)
			if err != nil {
				return cmdutil.AddSourceToErr("creating", info.Source, err)
			}
			info.Refresh(obj, true)
		}

		count++

		return o.PrintObj(info.Object)
	})
  • visitor接口中的调用方法
func (r *Result) Visit(fn VisitorFunc) error {
	if r.err != nil {
		return r.err
	}
	err := r.visitor.Visit(fn)
	return utilerrors.FilterOut(err, r.ignoreErrors...)
}
  • 最终的调用就是前面注册的各个visitor的 Visit方法

外层VisitorFunc分析

  • 如果出错就返回错误
  • DryRunStrategy 代表试运行策略
    • 默认为None代表不试运行
    • client代表客户端试运行 ,不发送请求到server
    • server点服务端试运行,发送请求,但是如果会改变状态就话就不做
  • 最终调用 Create创建资源 ,然后调用o.PrintObj(info.Object)打印结果
func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}
		if err := util.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info.Object, scheme.DefaultJSONEncoder()); err != nil {
			return cmdutil.AddSourceToErr("creating", info.Source, err)
		}

		if err := o.Recorder.Record(info.Object); err != nil {
			klog.V(4).Infof("error recording current command: %v", err)
		}

		if o.DryRunStrategy != cmdutil.DryRunClient {
			if o.DryRunStrategy == cmdutil.DryRunServer {
				if err := o.DryRunVerifier.HasSupport(info.Mapping.GroupVersionKind); err != nil {
					return cmdutil.AddSourceToErr("creating", info.Source, err)
				}
			}
			obj, err := resource.
				NewHelper(info.Client, info.Mapping).
				DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
				WithFieldManager(o.fieldManager).
				Create(info.Namespace, true, info.Object)
			if err != nil {
				return cmdutil.AddSourceToErr("creating", info.Source, err)
			}
			info.Refresh(obj, true)
		}

		count++

		return o.PrintObj(info.Object)
	}

本节重点总结 :

  • visitor访问者模式简介
  • kubectl中的visitor应用

http://www.kler.cn/a/516880.html

相关文章:

  • UDP 广播组播点播的区别及联系
  • 关于CAN(FD)转以太网详细介绍
  • 重构开源LLM分类:从二分到三分的转变
  • 1.CSS的三大特性
  • 智能化加速标准和协议的更新并推动验证IP(VIP)在芯片设计中的更广泛应用
  • centos9编译安装opensips 二【进阶篇-定制目录+模块】推荐
  • 基于微信小程序的设备故障报修管理系统设计与实现(LW+源码+讲解)
  • stm8s单片机(四)时钟输出与蜂鸣器实验
  • 【飞牛云访问本地NAS数据】手机飞牛云APP异地远程连接fnOS随时随地访问私有应用不受网络限制
  • vim如何设置制表符表示的空格数量
  • DRG/DIP 2.0时代下基于PostgreSQL的成本管理实践与探索(上)
  • mysql 学习3 SQL语句--整体概述。SQL通用语法;DDL创建数据库,查看数据库,删除数据库,使用数据库;
  • VS C++ 配置OPENCV环境
  • AI语言模型竞争加剧:新秀崛起 格局生变
  • ES Module 标准规范
  • C++|开源日志库log4cpp和glog
  • 【云南省乡镇界】面图层shp格式arcgis数据乡镇名称和编码+wgs84坐标无偏移内容测评
  • 【javaSE】内部类(来自类和对象的补充)
  • vue(33) : 安装组件出错解决
  • 在 Ubuntu 上的 Docker 容器中安装 MySQL
  • 订单状态定时处理、来单提醒和客户催单(day10)
  • k8s优雅重启
  • 【GORM】事务,嵌套事务,保存点事务的使用,简单电商平台go案例
  • 力扣动态规划-5【算法学习day.99】
  • 快速排序:一种高效的排序算法
  • Python 预训练:打通视觉与大语言模型应用壁垒——Python预训练视觉和大语言模型