kubectl源码分析之apply edit-last-applied
发布一个k8s部署视频:https://edu.csdn.net/course/detail/26967课程内容:各种k8s部署方式。包括minikube部署,kubeadm部署,kubeasz部署,rancher部署,k3s部署。包括开发测试环境部署k8s,和生产环境部署k8s。腾讯课堂连接地址https://ke.qq.com/course/478827?taid=43731099314622
欢迎关注我的公众号:
目前刚开始写一个月,一共写了18篇原创文章,文章目录如下:
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
}
更多推荐
所有评论(0)