kubectl源码分析之apply edit-last-applied

 欢迎关注我的公众号:

 目前刚开始写一个月,一共写了18篇原创文章,文章目录如下:

istio多集群探秘,部署了50次多集群后我得出的结论

istio多集群链路追踪,附实操视频

istio防故障利器,你知道几个,istio新手不要读,太难!

istio业务权限控制,原来可以这么玩

istio实现非侵入压缩,微服务之间如何实现压缩

不懂envoyfilter也敢说精通istio系列-http-rbac-不要只会用AuthorizationPolicy配置权限

不懂envoyfilter也敢说精通istio系列-02-http-corsFilter-不要只会vs

不懂envoyfilter也敢说精通istio系列-03-http-csrf filter-再也不用再代码里写csrf逻辑了

不懂envoyfilter也敢说精通istio系列http-jwt_authn-不要只会RequestAuthorization

不懂envoyfilter也敢说精通istio系列-05-fault-filter-故障注入不止是vs

不懂envoyfilter也敢说精通istio系列-06-http-match-配置路由不只是vs

不懂envoyfilter也敢说精通istio系列-07-负载均衡配置不止是dr

不懂envoyfilter也敢说精通istio系列-08-连接池和断路器

不懂envoyfilter也敢说精通istio系列-09-http-route filter

不懂envoyfilter也敢说精通istio系列-network filter-redis proxy

不懂envoyfilter也敢说精通istio系列-network filter-HttpConnectionManager

不懂envoyfilter也敢说精通istio系列-ratelimit-istio ratelimit完全手册

 

————————————————

//创建edit-last-applied命令
func NewCmdApplyEditLastApplied(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
	o := editor.NewEditOptions(editor.ApplyEditMode, ioStreams)//初始化edit-option结构体

	cmd := &cobra.Command{//创建cobra命令
		Use:                   "edit-last-applied (RESOURCE/NAME | -f FILENAME)",
		DisableFlagsInUseLine: true,
		Short:                 "Edit latest last-applied-configuration annotations of a resource/object",
		Long:                  applyEditLastAppliedLong,
		Example:               applyEditLastAppliedExample,
		Run: func(cmd *cobra.Command, args []string) {
			if err := o.Complete(f, args, cmd); err != nil {//准备
				cmdutil.CheckErr(err)
			}
			if err := o.Run(); err != nil {//运行
				cmdutil.CheckErr(err)
			}
		},
	}

	// bind flag structs
	o.RecordFlags.AddFlags(cmd)//record选项
	o.PrintFlags.AddFlags(cmd)//打印选项

	usage := "to use to edit the resource"
	cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)//文件选项
	cmd.Flags().BoolVar(&o.WindowsLineEndings, "windows-line-endings", o.WindowsLineEndings,
		"Defaults to the line ending native to your platform.")//windows-line-endings选项
	cmdutil.AddIncludeUninitializedFlag(cmd)

	return cmd
}
type EditOptions struct {//edit结构体
	resource.FilenameOptions
	RecordFlags *genericclioptions.RecordFlags

	PrintFlags *genericclioptions.PrintFlags
	ToPrinter  func(string) (printers.ResourcePrinter, error)

	OutputPatch        bool
	WindowsLineEndings bool

	cmdutil.ValidateOptions

	OriginalResult *resource.Result

	EditMode EditMode

	CmdNamespace    string
	ApplyAnnotation bool
	ChangeCause     string

	genericclioptions.IOStreams

	Recorder            genericclioptions.Recorder
	f                   cmdutil.Factory
	editPrinterOptions  *editPrinterOptions
	updatedResultGetter func(data []byte) *resource.Result
}
//准备
func (o *EditOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Command) error {
	var err error

	o.RecordFlags.Complete(cmd)//准备record
	o.Recorder, err = o.RecordFlags.ToRecorder()//recordflag转recorder
	if err != nil {
		return err
	}

	if o.EditMode != NormalEditMode && o.EditMode != EditBeforeCreateMode && o.EditMode != ApplyEditMode {//判断editmode是否合法
		return fmt.Errorf("unsupported edit mode %q", o.EditMode)
	}

	o.editPrinterOptions.Complete(o.PrintFlags)//准备print

	if o.OutputPatch && o.EditMode != NormalEditMode {
		return fmt.Errorf("the edit mode doesn't support output the patch")
	}

	cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()//获取namespace和enforceNamespace
	if err != nil {
		return err
	}
	b := f.NewBuilder().
		Unstructured()
	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()
	}
	r := b.NamespaceParam(cmdNamespace).DefaultNamespace().
		FilenameParam(enforceNamespace, &o.FilenameOptions).
		ContinueOnError().
		Flatten().
		Do()//用builder构造result对象
	err = r.Err()
	if err != nil {
		return err
	}
	o.OriginalResult = r//设置OriginalResult 

	o.updatedResultGetter = func(data []byte) *resource.Result {//设置updatedResultGetter 函数
		// resource builder to read objects from edited data
		return f.NewBuilder().
			Unstructured().
			Stream(bytes.NewReader(data), "edited-file").
			ContinueOnError().
			Flatten().
			Do()
	}

	o.ToPrinter = func(operation string) (printers.ResourcePrinter, error) {//printflag转printer函数
		o.PrintFlags.NamePrintFlags.Operation = operation
		return o.PrintFlags.ToPrinter()
	}

	o.CmdNamespace = cmdNamespace//设置名称空间
	o.f = f

	return nil
}
//运行
func (o *EditOptions) Run() error {
	edit := NewDefaultEditor(editorEnvs())//创建editor
	// editFn is invoked for each edit session (once with a list for normal edit, once for each individual resource in a edit-on-create invocation)
	editFn := func(infos []*resource.Info) error {//edit函数
		var (
			results  = editResults{}
			original = []byte{}
			edited   = []byte{}
			file     string
			err      error
		)

		containsError := false
		// loop until we succeed or cancel editing
		for {
			// get the object we're going to serialize as input to the editor
			var originalObj runtime.Object
			switch len(infos) {//判断info长度
			case 1://如果是1个,则设置originalObj 
				originalObj = infos[0].Object
			default://否则设置list
				l := &unstructured.UnstructuredList{
					Object: map[string]interface{}{
						"kind":       "List",
						"apiVersion": "v1",
						"metadata":   map[string]interface{}{},
					},
				}
				for _, info := range infos {
					l.Items = append(l.Items, *info.Object.(*unstructured.Unstructured))
				}
				originalObj = l//设置originalObj 为list
			}

			// generate the file to edit
			buf := &bytes.Buffer{}//构造buffer
			var w io.Writer = buf//定义writer
			if o.WindowsLineEndings {//如果是windows,包装windows行结尾的writer
				w = crlf.NewCRLFWriter(w)
			}

			if o.editPrinterOptions.addHeader {//如果指定了addHeader,则添加头注释
				results.header.writeTo(w, o.EditMode)
			}

			if !containsError {//如果没错误
				if err := o.editPrinterOptions.PrintObj(originalObj, w); err != nil {//打印原始对象到writer
					return preservedFile(err, results.file, o.ErrOut)//有错误返回
				}
				original = buf.Bytes()//保存原始的数据
			} else {
				// In case of an error, preserve the edited file.
				// Remove the comments (header) from it since we already
				// have included the latest header in the buffer above.
				buf.Write(cmdutil.ManualStrip(edited))
			}

			// launch the editor
			editedDiff := edited
			edited, file, err = edit.LaunchTempFile(fmt.Sprintf("%s-edit-", filepath.Base(os.Args[0])), o.editPrinterOptions.ext, buf)//启动编辑
			if err != nil {
				return preservedFile(err, results.file, o.ErrOut)//保留文件,返回错误
			}
			// If we're retrying the loop because of an error, and no change was made in the file, short-circuit
			if containsError && bytes.Equal(cmdutil.StripComments(editedDiff), cmdutil.StripComments(edited)) {//如果有错误,则返回
				return preservedFile(fmt.Errorf("%s", "Edit cancelled, no valid changes were saved."), file, o.ErrOut)//保留文件,返回错误
			}
			// cleanup any file from the previous pass
			if len(results.file) > 0 {//删除文件
				os.Remove(results.file)
			}
			klog.V(4).Infof("User edited:\n%s", string(edited))

			// Apply validation
			schema, err := o.f.Validator(o.EnableValidation)//构造validator
			if err != nil {
				return preservedFile(err, file, o.ErrOut)// 保留文件,返回错误
			}
			err = schema.ValidateBytes(cmdutil.StripComments(edited))//校验编辑后的文件
			if err != nil {
				results = editResults{
					file: file,
				}
				containsError = true
				fmt.Fprintln(o.ErrOut, results.addError(apierrors.NewInvalid(corev1.SchemeGroupVersion.WithKind("").GroupKind(),
					"", field.ErrorList{field.Invalid(nil, "The edited file failed validation", fmt.Sprintf("%v", err))}), infos[0]))//打印告警
				continue//继续
			}

			// Compare content without comments
			if bytes.Equal(cmdutil.StripComments(original), cmdutil.StripComments(edited)) {//如果原文件和编辑后的文件去掉注释,内容相同
				os.Remove(file)//删除文件
				fmt.Fprintln(o.ErrOut, "Edit cancelled, no changes made.")//打印信息
				return nil//返回
			}

			lines, err := hasLines(bytes.NewBuffer(edited))//判断是否有行
			if err != nil {
				return preservedFile(err, file, o.ErrOut)// 保留文件,返回错误
			}
			if !lines {//没有行
				os.Remove(file)//删除文件
				fmt.Fprintln(o.ErrOut, "Edit cancelled, saved file was empty.")//提示信息
				return nil//返回
			}

			results = editResults{//构造editResults
				file: file,
			}

			// parse the edited file
			updatedInfos, err := o.updatedResultGetter(edited).Infos()//获取编辑后的info
			if err != nil {
				// syntax error
				containsError = true
				results.header.reasons = append(results.header.reasons, editReason{head: fmt.Sprintf("The edited file had a syntax error: %v", err)})//语法错误继续
				continue
			}
			// not a syntax error as it turns out...
			containsError = false
			updatedVisitor := resource.InfoListVisitor(updatedInfos)//访问编辑后的info

			// need to make sure the original namespace wasn't changed while editing
			if err := updatedVisitor.Visit(resource.RequireNamespace(o.CmdNamespace)); err != nil {
				return preservedFile(err, file, o.ErrOut)
			}

			// iterate through all items to apply annotations
			if err := o.visitAnnotation(updatedVisitor); err != nil {
				return preservedFile(err, file, o.ErrOut)
			}

			switch o.EditMode {判断编辑模式
			case NormalEditMode:
				err = o.visitToPatch(infos, updatedVisitor, &results)
			case ApplyEditMode://apply编辑模式
				err = o.visitToApplyEditPatch(infos, updatedVisitor)
			case EditBeforeCreateMode:
				err = o.visitToCreate(updatedVisitor)
			default:
				err = fmt.Errorf("unsupported edit mode %q", o.EditMode)
			}
			if err != nil {
				return preservedFile(err, results.file, o.ErrOut)
			}

			// Handle all possible errors
			//
			// 1. retryable: propose kubectl replace -f
			// 2. notfound: indicate the location of the saved configuration of the deleted resource
			// 3. invalid: retry those on the spot by looping ie. reloading the editor
			if results.retryable > 0 {
				fmt.Fprintf(o.ErrOut, "You can run `%s replace -f %s` to try this update again.\n", filepath.Base(os.Args[0]), file)
				return cmdutil.ErrExit
			}
			if results.notfound > 0 {
				fmt.Fprintf(o.ErrOut, "The edits you made on deleted resources have been saved to %q\n", file)
				return cmdutil.ErrExit
			}

			if len(results.edit) == 0 {
				if results.notfound == 0 {
					os.Remove(file)
				} else {
					fmt.Fprintf(o.Out, "The edits you made on deleted resources have been saved to %q\n", file)
				}
				return nil
			}

			if len(results.header.reasons) > 0 {
				containsError = true
			}
		}
	}

	switch o.EditMode {//判断编辑模式
	// If doing normal edit we cannot use Visit because we need to edit a list for convenience. Ref: #20519
	case NormalEditMode:
		infos, err := o.OriginalResult.Infos()
		if err != nil {
			return err
		}
		if len(infos) == 0 {
			return errors.New("edit cancelled, no objects found")
		}
		return editFn(infos)
	case ApplyEditMode://apply编辑模式
		infos, err := o.OriginalResult.Infos()//获取原始info
		if err != nil {
			return err
		}
		var annotationInfos []*resource.Info
		for i := range infos {//遍历info
			data, err := util.GetOriginalConfiguration(infos[i].Object)//获取原始配置
			if err != nil {
				return err
			}
			if data == nil {
				continue
			}

			tempInfos, err := o.updatedResultGetter(data).Infos()//获取info
			if err != nil {
				return err
			}
			annotationInfos = append(annotationInfos, tempInfos[0])//注解的info
		}
		if len(annotationInfos) == 0 {
			return errors.New("no last-applied-configuration annotation found on resources, to create the annotation, use command `kubectl apply set-last-applied --create-annotation`")
		}
		return editFn(annotationInfos)//执行编辑
	// If doing an edit before created, we don't want a list and instead want the normal behavior as kubectl create.
	case EditBeforeCreateMode:
		return o.OriginalResult.Visit(func(info *resource.Info, err error) error {
			return editFn([]*resource.Info{info})
		})
	default:
		return fmt.Errorf("unsupported edit mode %q", o.EditMode)
	}
}
//应用patch到服务端
func (o *EditOptions) visitToApplyEditPatch(originalInfos []*resource.Info, patchVisitor resource.Visitor) error {
	err := patchVisitor.Visit(func(info *resource.Info, incomingErr error) error {//访问result
		editObjUID, err := meta.NewAccessor().UID(info.Object)//获取uid
		if err != nil {
			return err
		}

		var originalInfo *resource.Info
		for _, i := range originalInfos {//遍历原始的info
			originalObjUID, err := meta.NewAccessor().UID(i.Object)//获取原始uid
			if err != nil {
				return err
			}
			if editObjUID == originalObjUID {//编辑的uid和原始uid相同
				originalInfo = i
				break
			}
		}
		if originalInfo == nil {
			return fmt.Errorf("no original object found for %#v", info.Object)
		}

		originalJS, err := encodeToJSON(originalInfo.Object.(runtime.Unstructured))//把原始的obj转json
		if err != nil {
			return err
		}

		editedJS, err := encodeToJSON(info.Object.(runtime.Unstructured))//编辑后的info转json
		if err != nil {
			return err
		}

		if reflect.DeepEqual(originalJS, editedJS) {//json相同,跳过
			printer, err := o.ToPrinter("skipped")
			if err != nil {
				return err
			}
			return printer.PrintObj(info.Object, o.Out)
		}
		err = o.annotationPatch(info)//应用patch到服务端
		if err != nil {
			return err
		}

		printer, err := o.ToPrinter("edited")// print flag转printer
		if err != nil {
			return err
		}
		return printer.PrintObj(info.Object, o.Out)// 打印对象
	})
	return err
}
func (o *EditOptions) annotationPatch(update *resource.Info) error {
	patch, _, patchType, err := GetApplyPatch(update.Object.(runtime.Unstructured))//获取patch
	if err != nil {
		return err
	}
	mapping := update.ResourceMapping()//获取mapping
	client, err := o.f.UnstructuredClientForMapping(mapping)//获取client
	if err != nil {
		return err
	}
	helper := resource.NewHelper(client, mapping)//构造helper
	_, err = helper.Patch(o.CmdNamespace, update.Name, patchType, patch, nil)//应用patch到服务端
	if err != nil {
		return err
	}
	return nil
}
//获取patch
func GetApplyPatch(obj runtime.Unstructured) ([]byte, []byte, types.PatchType, error) {
	beforeJSON, err := encodeToJSON(obj)//obj转json
	if err != nil {
		return nil, []byte(""), types.MergePatchType, err
	}
	objCopy := obj.DeepCopyObject()//拷贝obj
	accessor := meta.NewAccessor()
	annotations, err := accessor.Annotations(objCopy)//获取annotation
	if err != nil {
		return nil, beforeJSON, types.MergePatchType, err
	}
	if annotations == nil {
		annotations = map[string]string{}
	}
	annotations[corev1.LastAppliedConfigAnnotation] = string(beforeJSON)//设置lastAppliedConfiguration注解
	accessor.SetAnnotations(objCopy, annotations)//设置注解
	afterJSON, err := encodeToJSON(objCopy.(runtime.Unstructured))//对象转json
	if err != nil {
		return nil, beforeJSON, types.MergePatchType, err
	}
	patch, err := jsonpatch.CreateMergePatch(beforeJSON, afterJSON)//创建patch
	return patch, beforeJSON, types.MergePatchType, err
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hxpjava1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值