【k8s多集群管理平台开发实践】六、client-go实现k8s的cronjob的列表、创建cronjob、yaml配置更新

发布于:2024-05-08 ⋅ 阅读:(40) ⋅ 点赞:(0)

简介

本章节主要讲解通过client-go实现cronjob的列表显示、界面创建cronjob,读取yaml配置并更改。功能主要有后端部分:控制器代码、模型部分代码、路由配置。前端部分:cronjobList.html的html代码,cronjobCreate.html,cronjobYaml.html这几部分代码组成。

一.cronjob的列表实现

1.1.controllers控制器代码

该列表支持传递集群ID、命名空间、cronjob名称来进行过滤查询

func (this *CronjobController) List() {
	clusterId := this.GetString("clusterId")
	nameSpace := this.GetString("nameSpace")
	cronjobName := this.GetString("cronjobName")
	labels := this.GetString("labels")
	labelsKV := strings.Split(labels, ":")
	var labelsKey, labelsValue string
	if len(labelsKV) == 2 {
		labelsKey = labelsKV[0]
		labelsValue = labelsKV[1]
	}

	dxList, err := m.CronjobList(clusterId, nameSpace, cronjobName, labelsKey, labelsValue)
	msg := "success"
	code := 0
	count := len(dxList)
	if err != nil {
		log.Printf("[ERROR] CronjobList error:%s \n", err)
		msg = err.Error()
		code = -1
	}

	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg, "count": count, "data": &dxList}
	//this.Data["json"] = &datas
	this.ServeJSON()
}

1.2.models模型代码

先定义一个结构体Cronjob将需要显示的信息整理出来,然后通过common.ClientSet(kubeconfig).BatchV1().CronJobs(namespace).List 读取出cronjob的列表,并将需要的信息赋值到结构体Cronjob,并追加到结构体列表var bbb = make([]Cronjob, 0),注意所使用的api版本,版本高于1.26的就用v1版本,否则用V1beta1.

type Cronjob struct {
	CronjobName      string `json:"cronjobName"`
	NameSpace        string `json:"nameSpace"`
	Labels           string `json:"labels"`
	Annotations      string `json:"annotations"`
	ImgUrl           string `json:"imgUrl"`
	Suspend          string `json:"suspend"`
	Schedule         string `json:"schedule"`
	Active           int    `json:"active"`
	CreateTime       string `json:"createTime"`
	LastScheduleTime string `json:"lastScheduleTime"`
}

func CronjobList(kubeconfig, namespace, cronjobName string, labelsKey, labelsValue string) ([]Cronjob, error) {
	if namespace == "" {
		//namespace = corev1.NamespaceDefault
		namespace = corev1.NamespaceAll
	}
	var bbb = make([]Cronjob, 0)

	//设置标签过滤的ListOptions
	var listOptions = metav1.ListOptions{}
	if labelsKey != "" && labelsValue != "" {
		listOptions = metav1.ListOptions{
			LabelSelector: fmt.Sprintf("%s=%s", labelsKey, labelsValue),
		}
	}

	//BatchV1 会提示the server could not find the requested resource,改成BatchV1beta1 后正常,注意k8s版本,大于1.26的就用v1版本
	//xList, err := common.ClientSet(kubeconfig).BatchV1beta1().CronJobs(namespace).List(context.TODO(), listOptions)
	xList, err := common.ClientSet(kubeconfig).BatchV1().CronJobs(namespace).List(context.TODO(), listOptions) 

	if err != nil {
		log.Printf("[ERROR] ListCronjobError err:%v", err)
		return bbb, err
	}

	for _, vv := range xList.Items {
		//根据名称过滤
		if cronjobName != "" {
			if !strings.Contains(vv.Name, cronjobName) {
				continue
			}
		}

		var labelsStr, imgUrlStr string
		for k1, v1 := range vv.ObjectMeta.Labels {
			labelsStr += fmt.Sprintf("%s:%s,", k1, v1)
		}
		if len(labelsStr) > 0 {
			labelsStr = labelsStr[0 : len(labelsStr)-1]
		}
		for _, v2 := range vv.Spec.JobTemplate.Spec.Template.Spec.Containers {
			imgUrlStr += fmt.Sprintf("%s,", v2.Image)
		}

		if len(imgUrlStr) > 0 {
			imgUrlStr = imgUrlStr[0 : len(imgUrlStr)-1]
		}

		var lastScheduleTime string
		if vv.Status.LastScheduleTime != nil {
			lastScheduleTime = vv.Status.LastScheduleTime.Format("2006-01-02 15:04:05")
		}

		xItems := &Cronjob{
			CronjobName: vv.Name,
			NameSpace:   vv.Namespace,
			Labels:      labelsStr,
			ImgUrl:      imgUrlStr,
			Suspend:     fmt.Sprintf("%v", *vv.Spec.Suspend),
			Schedule:    vv.Spec.Schedule,
			Active:      len(vv.Status.Active),
			CreateTime:  vv.CreationTimestamp.Format("2006-01-02 15:04:05"),
			//LastScheduleTime: vv.Status.LastScheduleTime.Format("2006-01-02 15:04:05"),
			LastScheduleTime: lastScheduleTime,
		}
		bbb = append(bbb, *xItems)
	}
	return bbb, err
}

二.界面创建cronjob

2.1.controllers控制器代码

控制器函数接收前端html的表单传递过来的json 请求头,并传递给模型中的函数CronjobCreate来处理。

func (this *CronjobController) Create() {
	clusterId := this.GetString("clusterId")
	code := 0
	msg := "success"
	log.Println(string(this.Ctx.Input.RequestBody))
	err := m.CronjobCreate(clusterId, this.Ctx.Input.RequestBody)
	if err != nil {
		code = -1
		msg = err.Error()
		log.Printf("[ERROR] configmap Create Fail:%s\n", err)
	}
	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg}
	this.ServeJSON()
}

2.2.models模分代码

通过控制器部分传递过来的body内容,进行json解析。首先创建一个 cronjob := &batchv1.CronJob{cronjob的结构体,然后将解析json的值赋值到结构体,并调用Create(context.TODO(), cronjob, metav1.CreateOptions{}) 函数进行创建。

func CronjobCreate(kubeconfig string, bodys []byte) error {
    //解析json并取出相关值
	gp := gjson.ParseBytes(bodys)

	clusterId := gp.Get("clusterId").String()
	if kubeconfig == "" {
		kubeconfig = clusterId
	}
	cronjobName := gp.Get("cronjobName").String()
	nameSpace := gp.Get("nameSpace").String()
	var pullPolicy corev1.PullPolicy
	imagePullPolicy := gp.Get("imagePullPolicy").String()
	switch imagePullPolicy {
	case "Never":
		pullPolicy = corev1.PullNever
	case "IfNotPresent":
		pullPolicy = corev1.PullIfNotPresent
	default:
		pullPolicy = corev1.PullAlways
	}
	imageUrl := gp.Get("imageUrl").String()

	resourceLimitCheck := gp.Get("resourceLimitCheck").String()
	periodCheck := gp.Get("periodCheck").String()
	taskSetCheck := gp.Get("taskSetCheck").String()

	schedule := gp.Get("schedule").String()
	if schedule == "" {
		schedule = "* * * * *"
	}

	labelsMap := map[string]string{
		"app": cronjobName,
	}
	for _, vv := range gp.Get("lables").Array() {
		labelsMap[vv.Get("key").String()] = vv.Get("value").String()
	}
    //定义一个cronjob的实例,然后进行赋值,
	cronjob := &batchv1.CronJob{
		ObjectMeta: metav1.ObjectMeta{
			Name:      cronjobName,
			Namespace: nameSpace,
			Labels:    labelsMap,
		},
		Spec: batchv1.CronJobSpec{
			Schedule: schedule, // 定时任务表达式
			JobTemplate: batchv1.JobTemplateSpec{
				Spec: batchv1.JobSpec{ //v1版本有JobSpec,beta1版本没有
					Template: corev1.PodTemplateSpec{
						Spec: corev1.PodSpec{
							Containers: []corev1.Container{
								{
									Name:            cronjobName,
									Image:           imageUrl,
									ImagePullPolicy: pullPolicy,
								},
							},
							RestartPolicy: corev1.RestartPolicyNever,
						},
					},
				},
			},
		},
	}

	commandStr := gp.Get("command").Str
	if commandStr != "" {
		commandArry := strings.Split(commandStr, ",")
		cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Command = commandArry
	}
	argsStr := gp.Get("args").Str
	if argsStr != "" {
		argsArry := strings.Split(argsStr, ",")
		cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Args = argsArry
	}
    //将定义的cronjob实例进行创建
	jobClient := common.ClientSet(kubeconfig).BatchV1().CronJobs(nameSpace)
	_, err := jobClient.Create(context.TODO(), cronjob, metav1.CreateOptions{})
	if err != nil {
		return err
	}
	return nil
}

三.读取cronjob的yaml配置并更新

3.1.controllers控制器代码

通过传递的集群名称、命名空间、cronjob的名称通过GetCronjobYaml函数处理

//读取yaml配置
func (this *CronjobController) Yaml() {
	clusterId := this.GetString("clusterId")
	namespace := this.GetString("nameSpace")
	cronjobName := this.GetString("cronjobName")

	yamlStr, _ := m.GetCronjobYaml(clusterId, namespace, cronjobName)
	this.Ctx.WriteString(yamlStr)
}

//更新yaml配置
func (this *CronjobController) ModifyByYaml() {
	clusterId := this.GetString("clusterId")
	code := 0
	msg := "success"
	bodyByte := []byte(strings.ReplaceAll(string(this.Ctx.Input.RequestBody), "%25", "%"))
	err := m.CronjobYamlModify(clusterId, bodyByte)
	if err != nil {
		code = -1
		msg = err.Error()
		log.Printf("[ERROR] cronjob ModifyByYaml Fail:%s\n", err)
	}
	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg}
	this.ServeJSON()
}
3.2.models模型代码

该列表可以通过传递

//读取cronjob实例并转化成yaml配置文件
func GetCronjobYaml(kubeconfig, namespace, cronjobName string) (string, error) {
	jobClient := common.ClientSet(kubeconfig).BatchV1().CronJobs(namespace)
	jobInstance, err := jobClient.Get(context.TODO(), cronjobName, metav1.GetOptions{})
    //将cronjob实例进行解构
	jobUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(jobInstance)
	if err != nil {
		return "", err
	}
	//并将解构后的数据序列化成yaml格式
	yamlBytes, err := yaml.Marshal(jobUnstructured)
	if err != nil {
		return "", err
	}
	//fmt.Println(string(yamlBytes))
	return string(yamlBytes), nil
}

//更新yaml配置
func CronjobYamlModify(kubeconfig string, yamlData []byte) error {
	data, err := yamlutil.ToJSON(yamlData)
	if err != nil {
		return err
	}
	cronjob := &batchv1.CronJob{}
	//将读取的yaml配置反序列化成cronjob实例
	err = json.Unmarshal(data, cronjob)
	if err != nil {
		return err
	}
    //调用Update函数更新
	namespace := cronjob.ObjectMeta.Namespace
	//cronjobName := cronjob.ObjectMeta.Name
	jobClient := common.ClientSet(kubeconfig).BatchV1().CronJobs(namespace)
	_, err = jobClient.Update(context.TODO(), cronjob, metav1.UpdateOptions{})
	return err
}

四.路由设置

4.1.路由设置

将这段代码添加到routers/route.go中

	//cronjob
	beego.Router("/xkube/cronjob/v1/List", &controllers.CronjobController{}, "*:List")
	beego.Router("/xkube/cronjob/v1/Create", &controllers.CronjobController{}, "*:Create")
	beego.Router("/xkube/cronjob/v1/ModifyByYaml", &controllers.CronjobController{}, "*:ModifyByYaml")
	beego.Router("/xkube/cronjob/v1/Yaml", &controllers.CronjobController{}, "*:Yaml")

五.前端代码

5.1.列表部分html代码

5.1 cronjobList.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>cronjob列表</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/lib/layui-v2.6.3/css/layui.css" media="all">
    <link rel="stylesheet" href="/css/public.css" media="all">
    <script type="text/javascript" src="/lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
    <script src="/lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
    <script src="/js/xkube.js?v=1" charset="utf-8"></script>
    <script src="/js/lay-config.js?v=1.0.4" charset="utf-8"></script>
<style type="text/css">
  .layui-table-cell {
    height: auto;
    line-height: 15px !important;
    text-overflow: inherit;
    overflow: ellipsis;
    white-space: normal;
  }
  .layui-table-cell .layui-table-tool-panel li {
    word-break: break-word;
  }
</style>
</head>
<body>
<div class="layuimini-container">
    <div class="layuimini-main">
        <script type="text/html" id="toolbarDemo">
            <div class="layui-btn-container">
                <button class="layui-btn layui-btn-normal layui-btn-sm data-add-btn" lay-event="createCronjob"><i class="layui-icon">&#xe61f;</i>创建cronjob</button>
            </div>
        </script>

        <table class="layui-table" id="currentTableId" lay-filter="currentTableFilter"></table>

        <script type="text/html" id="currentTableBar">
            <a class="layui-btn layui-btn-normal layui-btn-sm" lay-event="viewYaml">yaml编辑</a>
        </script>
    </div>
</div>

</body>

<script type="text/html" id="TagTpl">
    {{# if (d.labels != "") { }}
            {{# layui.each(d.labels.split(','), function(index, item){ }}
                {{# if(index == 0) { }}
                        <span>{{ item }}</span>
                {{# }else{ }}
                        <br><span>{{ item }}</span>
                {{# } }}  
            {{# });  }}
    {{# }else{  }}
            <span></span>
    {{# } }}
</script>	
<script type="text/html" id="ImgTpl">
    {{# if (d.imgUrl != "") { }}
            {{# layui.each(d.imgUrl.split(','), function(index, item){ }}
                {{# if(index == 0) { }}
                        <span>{{ item }}</span>
                {{# }else{ }}
                        <br><span>{{ item }}</span>
                {{# } }}  
            {{# });  }}
    {{# }else{  }}
            <span></span>
    {{# } }}
</script>	
<script type="text/html" id="suspendTpl">
  {{# if ( d.suspend == 'false' ) { }}
     <span style="color:#218868">{{ d.suspend}}</span>
	{{# } else { }}
    <span style="color:#FF5722">{{ d.suspend}}</span>
{{# } }}
</script>
<script type="text/html" id="sactiveTpl">
  {{# if ( d.active > 0 ) { }}
     <span style="color:#218868">d.active</span>
	{{# } else { }}
    <span style="color:#FF5722">{{ d.active}}</span>
{{# } }}
</script>
<script>

var clusterId = getQueryString("clusterId");
if (clusterId == null) {
	clusterId = getCookie("clusterId")
}

var cronjobApi = "v1";
    layui.use(['form', 'table','miniTab'], function () {
        var $ = layui.jquery,
            form = layui.form,
            table = layui.table;
            miniTab = layui.miniTab,
            miniTab.listen();

            form.render();

        table.render({
            elem: '#currentTableId',
            initSort: {field:'createTime', type:'desc'},
            url: '/cronjob/'+cronjobApi+'/List?clusterId='+clusterId,
            toolbar: '#toolbarDemo',
            defaultToolbar: ['filter', 'exports', 'print', {
                title: '提示',
                layEvent: 'LAYTABLE_TIPS',
                icon: 'layui-icon-tips'
            }],
            parseData: function(res) { //实现加载全部数据后再分页
            	if(this.page.curr) {
            		result=res.data.slice(this.limit*(this.page.curr-1),this.limit*this.page.curr);
            	}else{
            	  result=res.data.slice(0,this.limit);
              }
              return {
              	"code": res.code,
              	"msg":'',
              	"count":res.count,
              	"data":result
              };
            },
            cols: [[
                //{type: "checkbox", width: 50},
                {field: 'cronjobName', title: '名称'},
                {field: 'nameSpace', title: '命名空间', sort: true},
                {field: 'labes', title: '标签', sort: true,templet: '#TagTpl',hide:true},
                {field: 'imgUrl', title: '镜像地址', sort: true,templet: '#ImgTpl'},
                {field: 'suspend',  title: '暂停', sort: true,templet: '#suspendTpl'},
                {field: 'schedule', minWidth: 200, title: '计划', sort: true},
                {field: 'active',title: '活跃',width:120, sort: true,templet: '#activeTpl'},
                {field: 'createTime', title: '创建时间',hide:true},
                {field: 'lastScheduleTime', width:180, title: '最近调度'},
                {title: '操作', minWidth: 360, toolbar: '#currentTableBar', align: "center"}
            ]],
            //size:'lg',
            limits: [25, 50, 100],
            limit: 25,
            page: true
        });

        /**
         * toolbar监听事件
         */
        table.on('toolbar(currentTableFilter)', function (obj) {
            if (obj.event === 'createCronjob') {  // 监听添加操作
                var index = layer.open({
                    title: '创建',
                    type: 2,
                    shade: 0.2,
                    maxmin:true,
                    shadeClose: true,
                    area: ['60%', '90%'],
                    content: '/page/xkube/cronjobCreate.html?v=1'
                    //end: function(){
                    //	window.parent.location.reload();//关闭open打开的页面时,刷新父页面
                    //}
                });
                $(window).on("resize", function () {
                    layer.full(index);
                });
            }
        });

        table.on('tool(currentTableFilter)', function (obj) {
            var data = obj.data;
            if (obj.event === 'viewYaml') {
                var index = layer.open({
                    title: 'yaml',
                    type: 2,
                    shade: 0.2,
                    maxmin:true,
                    shadeClose: true,
                    area: ['45%', '92%'],
                    content: '/page/xkube/cronjobYaml.html?clusterId='+clusterId+'&nameSpace='+data.nameSpace+'&cronjobName='+data.cronjobName,
                });
                $(window).on("resize", function () {
                    layer.full(index);
                });
                return false;
            }
        });

    });
</script>
</html>
5.2.创建表单html代码

5.2 cronjobCreate.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>创建</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/lib/layui-v2.6.3/css/layui.css" media="all">
    <link rel="stylesheet" href="/css/public.css" media="all">
    <script type="text/javascript" src="/lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
    <script src="/lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
    <script src="/js/xkube.js?v=1" charset="utf-8"></script>
    <style>
        body {
            background-color: #ffffff;
        }
    </style>
</head>
<body>
<div class="layuimini-container">
    <div class="layuimini-main">
        <blockquote class="layui-elem-quote layui-text">
            帮助中心:<a href="#" target="_blank">添加说明文档</a>
        </blockquote>
        <form class="layui-form layui-form-pane" action="">

            <div class="layui-form-item">
                <div class="layui-inline">
                    <label class="layui-form-label">当前集群</label>
                    <div class="layui-input-inline">
                        <select name="clusterId" lay-filter="cluster_Id" lay-search="" id="cluster_Id">
                          <option value="">请选择集群</option>
                        </select>
                    </div>
                </div>
            </div>

            <div class="layui-form-item">
                <label class="layui-form-label required">名称</label>
                <div class="layui-input-inline">
                    <input type="text" name="cronjobName"  lay-verify="required" lay-reqtext="不能为空"  placeholder="不能为空" value="" class="layui-input">
                </div>

                <label class="layui-form-label">命名空间</label>
                <div class="layui-input-inline">
                  <select name="nameSpace" lay-filter="name_Space" lay-search="" id="name_Space">
  		            </select>
                </div>
                <div class="layui-form-mid layui-word-aux"><span style="color:red">*</span></div>
            </div>
            <div class="layui-form-item">
                <label class="layui-form-label">镜像策略</label>
                  <div class="layui-input-block">
                      <input type="radio" name="imagePullPolicy" value="Always" title="Always" checked="">
                      <input type="radio" name="imagePullPolicy" value="IfNotPresent" title="IfNotPresent">
                      <input type="radio" name="imagePullPolicy" value="Never" title="Never">
                </div>
            </div>
            <div class="layui-form-item">
                <label class="layui-form-label required">镜像地址</label>
                <div class="layui-input-inline" style="width:380px">
                    <input type="text" name="imageUrl"  lay-verify="required" lay-reqtext="不能为空"  placeholder="不能为空" value="" class="layui-input">
                </div>
                <div class="layui-form-mid layui-word-aux"><span style="color:red">*</span></div>
            </div>

            <div class="layui-form-item">
                <label class="layui-form-label">定时规则</label>
                <div class="layui-input-inline" style="width:380px">  
                    <input type="text" name="schedule" placeholder="* * * * *" lay-verify="required" lay-reqtext="不能为空" value="" class="layui-input">
                </div>
                <div class="layui-form-mid layui-word-aux"><span style="color:red">*</span></div>              
            </div>
            <div class="layui-form-item">
                <label class="layui-form-label required">启动执行</label>
                <div class="layui-input-inline">
                    <input type="text" name="command" placeholder="命令" value="" class="layui-input">
                </div>
                <div class="layui-input-inline">
                    <input type="text" name="args" placeholder="参数" value="" class="layui-input">
                </div>
            </div>

            <div class="labelsTpl">
              <div class="layui-form-item">
                  <label class="layui-form-label">标签</label>
                  <div class="layui-input-inline">  
                      <input type="text" id="labels_key0" name="labels_key[]" placeholder="key" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline">  
                      <input type="text" id="labels_value0" name="labels_value[]" placeholder="value" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline">  
                      <button class="layui-btn layui-btn-normal" style="width:100px" id="newaddbtn"><i class="layui-icon layui-icon-add-circle"></i></button>
                  </div>                
              </div> 
            </div>
  <br>
            <div class="layui-form-item">
                <div class="layui-input-block">
                    <button class="layui-btn layui-btn-normal" lay-submit lay-filter="saveBtn">确认保存</button>
                </div>
            </div>
        </form>
    </div>
</div>
</body>
<script>

    //标签删除
    var TplIndex = 0;
    function delTpl(id) {
      $("#tpl-"+id).remove();
      TplIndex--;
    }

var clusterId = getQueryString("clusterId");
if (clusterId == null) {
	clusterId = getCookie("clusterId")
}

var cronjobApi = "v1";

    layui.use(['form'], function () {
        var form = layui.form,
            layer = layui.layer,
            $ = layui.$;

        //labes add
        $('#newaddbtn').on("click",function(){
          TplIndex++;
          var addTpl =
              '<div class="layui-form-item" id="tpl-'+TplIndex+'">' +
                  '<label class="layui-form-label">标签</label>' +
                  '<div class="layui-input-inline">' +
        	              '<input type="text" name="labels_key[]" id="labels_key'+TplIndex+'" placeholder="key" value="" class="layui-input">' +
                  '</div>' +    
                  '<div class="layui-input-inline">' +  
                      '<input type="text" name="labels_value[]" id="labels_value'+TplIndex+'" placeholder="value" value="" class="layui-input">' +
                  '</div>' +
                  '<div class="layui-input-inline">' +  
                      '<input class="layui-btn layui-btn-normal layui-bg-orange layui-icon" style="width:100px" type="button" id="newDelbtn" value="&#xe616;" οnclick="delTpl('+TplIndex+');" />' +
                  '</div>' +                
              '</div>'
            $('.labelsTpl').append(addTpl);   
          form.render(); 
          return false;   
        });

        //监听提交
        form.on('submit(saveBtn)', function (data) {
                data.field.cronjobName = data.field.cronjobName.replace(/^\s*|\s*$/g,""); //替换空格
                data.field.imageUrl = data.field.imageUrl.replace(/^\s*|\s*$/g,""); //替换空格
                data.field.command = data.field.command.replace(/,|\s|;|\r|\n/g,",");//将空格等符号替换成逗号
                data.field.args = data.field.args.replace(/,|\s|;|\r|\n/g,",");//将空格等符号替换成逗号
                
                //lables 处理
                var labelsArry = [];
                for (var i=0;i<=TplIndex;i++) {
                  //delete data.field.lables_key[i];                  
                  //delete data.field.labels_value[i];
                  var kk = document.getElementById("labels_key"+i).value;
                  var vv = document.getElementById("labels_value"+i).value; 
                  if ( kk != "" && vv != "") {
                    labelsArry.push(
                      {
                        key:kk,
                        value:vv,
                      }
                    )
                  }
                }
                if (labelsArry.length > 0) {
                  data.field.lables = labelsArry;
                }
					      //console.log(data.field);
			          layer.confirm('确定添加?', {icon: 3, title:'提示',yes: function(index){
                     var index2 = layer.load(0, {shade: false});
                     layer.msg('稍等片刻');
                     $.ajax({
                       url: '/cronjob/'+cronjobApi+'/Create',
                       type: "post",
                       data: JSON.stringify(data.field),
                       dataType: "json",
                       success: function (resp) {
                                layer.close(index2);
                                 if(resp.code == 0){
                                    layer.msg('添加成功', {icon: 1});
									                  //window.location.reload();
                                 }else{
                                    layer.msg(resp.msg,{icon:2});
                                 }
                        }
                      });		  	  
                  },
                  cancel: function(index, layero){ 
                    layer.close(index);
                    layer.close(index2);
		                console.log("不操作");
                  } 
                });
              return false;
        });

    });
</script>
</html>
5.3.显示yaml配置的html代码

5.3 cronjobYaml.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>yaml编辑</title>
    <meta name="renderer" content="webkit">
    <link rel="stylesheet" href="/lib/layui-v2.6.3/css/layui.css" media="all">
    <link rel="stylesheet" href="/css/public.css" media="all">
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
		<link
			rel="stylesheet"
			data-name="vs/editor/editor.main"
			href="/monaco-editor/min/vs/editor/editor.main.css"
		/>
    <script src="/js/xkube.js?v=1" charset="utf-8"></script>
    <script type="text/javascript" src="/lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
</head>
<body>
<div class="layuimini-container">
    <div class="layuimini-main">
        <div id="container" style="width: 100%; height:460px; border: 1px solid grey"></div>
        <br>
        <fieldset class="table-search-fieldset">
            <legend></legend>
            <button class="layui-btn layui-btn-sm" id="SaveBtn" >保存更新</button>
            <button class="layui-btn layui-btn-normal layui-btn-sm">下载</button>
        </fieldset>   
    </div>
</div>
<script src="/lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script>
	var require = { paths: { vs: '/monaco-editor/min/vs' } };
</script>
<script src="/monaco-editor/min/vs/loader.js"></script>
<script src="/monaco-editor/min/vs/editor/editor.main.nls.js"></script>
<script src="/monaco-editor/min/vs/editor/editor.main.js"></script>
<script>

var clusterId = getQueryString("clusterId");
if (clusterId == null) {
	clusterId = getCookie("clusterId")
}

var cronjobApi = "v1";

    layui.use(['form', 'table','code'], function () {
        var $ = layui.jquery;
        var form = layui.form,
            layer = layui.layer;
        
    		var editor = monaco.editor.create(document.getElementById('container'), {
    			value: '',
    			language: 'yaml',
    			//automaticLayout: true,
    			minimap: {enabled: false},
    			wordWrap: 'on',
    			theme: 'vs-dark'
    		});

    		$.ajax({
    		    url: '/cronjob/'+cronjobApi+'/Yaml'+location.search,
    		    type: "GET",
    		    success: function (resp) {
    			  //codeyaml = resp;
    			  editor.setValue(resp);
    			}
    		});	

        $('#SaveBtn').on("click",function(){
              var yamlBody = editor.getValue();
              yamlBody = yamlBody.replace(/%/g,"%25");
              layer.confirm('确定修改?', {icon: 3, title:'提示',yes: function(index){
                  $.ajax({
                   url: '/cronjob/'+cronjobApi+'/ModifyByYaml'+location.search,
                   type: "POST",
                   data: yamlBody,
                   dataType: "json",
                   success: function (resp) {
                      layer.msg(resp.msg);
                    }
                  });
              },
              cancel: function(index, layero){ 
                  layer.close(index);
              } 
            });
        });
    });
</script>

</body>
</html>

六.完整代码

6.1.控制器完整代码

6.1 cronjob.go,放到controllers下

// cronjob.go
package controllers

import (
	"log"
	m "myk8s/models"
	"strings"

	beego "github.com/beego/beego/v2/server/web"
	//"github.com/tidwall/gjson"
)

type CronjobController struct {
	beego.Controller
}

func (this *CronjobController) List() {
	clusterId := this.GetString("clusterId")
	nameSpace := this.GetString("nameSpace")
	cronjobName := this.GetString("cronjobName")
	labels := this.GetString("labels")
	labelsKV := strings.Split(labels, ":")
	var labelsKey, labelsValue string
	if len(labelsKV) == 2 {
		labelsKey = labelsKV[0]
		labelsValue = labelsKV[1]
	}

	dxList, err := m.CronjobList(clusterId, nameSpace, cronjobName, labelsKey, labelsValue)
	msg := "success"
	code := 0
	count := len(dxList)
	if err != nil {
		log.Printf("[ERROR] CronjobList error:%s \n", err)
		msg = err.Error()
		code = -1
	}

	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg, "count": count, "data": &dxList}
	//this.Data["json"] = &datas
	this.ServeJSON()
}

func (this *CronjobController) Create() {
	clusterId := this.GetString("clusterId")
	code := 0
	msg := "success"
	log.Println(string(this.Ctx.Input.RequestBody))
	err := m.CronjobCreate(clusterId, this.Ctx.Input.RequestBody)
	if err != nil {
		code = -1
		msg = err.Error()
		log.Printf("[ERROR] configmap Create Fail:%s\n", err)
	}
	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg}
	this.ServeJSON()
}

func (this *CronjobController) ModifyByYaml() {
	clusterId := this.GetString("clusterId")
	code := 0
	msg := "success"
	bodyByte := []byte(strings.ReplaceAll(string(this.Ctx.Input.RequestBody), "%25", "%"))
	err := m.CronjobYamlModify(clusterId, bodyByte)
	if err != nil {
		code = -1
		msg = err.Error()
		log.Printf("[ERROR] cronjob ModifyByYaml Fail:%s\n", err)
	}
	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg}
	this.ServeJSON()
}

func (this *CronjobController) Yaml() {
	clusterId := this.GetString("clusterId")
	namespace := this.GetString("nameSpace")
	cronjobName := this.GetString("cronjobName")

	yamlStr, _ := m.GetCronjobYaml(clusterId, namespace, cronjobName)
	this.Ctx.WriteString(yamlStr)
}

6.2.模型完整代码

6.2 cronjobModel.go放到models下

// cronjobModel.go
package models

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"myk8s/common"
	"strings"

	"github.com/tidwall/gjson"
	"sigs.k8s.io/yaml"

	yamlutil "k8s.io/apimachinery/pkg/util/yaml"

	batchv1 "k8s.io/api/batch/v1" //注意有的地方要用beta1版本才行
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
)

type Cronjob struct {
	CronjobName      string `json:"cronjobName"`
	NameSpace        string `json:"nameSpace"`
	Labels           string `json:"labels"`
	Annotations      string `json:"annotations"`
	ImgUrl           string `json:"imgUrl"`
	Suspend          string `json:"suspend"`
	Schedule         string `json:"schedule"`
	Active           int    `json:"active"`
	CreateTime       string `json:"createTime"`
	LastScheduleTime string `json:"lastScheduleTime"`
}

func CronjobList(kubeconfig, namespace, cronjobName string, labelsKey, labelsValue string) ([]Cronjob, error) {
	if namespace == "" {
		//namespace = corev1.NamespaceDefault
		namespace = corev1.NamespaceAll
	}
	var bbb = make([]Cronjob, 0)

	//设置ListOptions
	var listOptions = metav1.ListOptions{}
	if labelsKey != "" && labelsValue != "" {
		listOptions = metav1.ListOptions{
			LabelSelector: fmt.Sprintf("%s=%s", labelsKey, labelsValue),
		}
	}

	//BatchV1 会提示the server could not find the requested resource,改成BatchV1beta1 后正常
	//xList, err := common.ClientSet(kubeconfig).BatchV1beta1().CronJobs(namespace).List(context.TODO(), listOptions) //zx-pcauto ok,yt-pcauto异常:the server could not find the requested resource
	xList, err := common.ClientSet(kubeconfig).BatchV1().CronJobs(namespace).List(context.TODO(), listOptions) //yt-pcauto ok,zx-pcauto 异常:the server could not find the requested resource

	if err != nil {
		log.Printf("[ERROR] ListCronjobError err:%v", err)
		return bbb, err
	}

	for _, vv := range xList.Items {
		//搜索
		if cronjobName != "" {
			if !strings.Contains(vv.Name, cronjobName) {
				continue
			}
		}

		var labelsStr, imgUrlStr string
		for k1, v1 := range vv.ObjectMeta.Labels {
			labelsStr += fmt.Sprintf("%s:%s,", k1, v1)
		}
		if len(labelsStr) > 0 {
			labelsStr = labelsStr[0 : len(labelsStr)-1]
		}
		for _, v2 := range vv.Spec.JobTemplate.Spec.Template.Spec.Containers {
			imgUrlStr += fmt.Sprintf("%s,", v2.Image)
		}

		if len(imgUrlStr) > 0 {
			imgUrlStr = imgUrlStr[0 : len(imgUrlStr)-1]
		}

		var lastScheduleTime string
		if vv.Status.LastScheduleTime != nil {
			lastScheduleTime = vv.Status.LastScheduleTime.Format("2006-01-02 15:04:05")
		}

		xItems := &Cronjob{
			CronjobName: vv.Name,
			NameSpace:   vv.Namespace,
			Labels:      labelsStr,
			ImgUrl:      imgUrlStr,
			Suspend:     fmt.Sprintf("%v", *vv.Spec.Suspend),
			Schedule:    vv.Spec.Schedule,
			Active:      len(vv.Status.Active),
			CreateTime:  vv.CreationTimestamp.Format("2006-01-02 15:04:05"),
			//LastScheduleTime: vv.Status.LastScheduleTime.Format("2006-01-02 15:04:05"),
			LastScheduleTime: lastScheduleTime,
		}
		bbb = append(bbb, *xItems)
	}
	return bbb, err
}

func CronjobCreate(kubeconfig string, bodys []byte) error {
	gp := gjson.ParseBytes(bodys)

	clusterId := gp.Get("clusterId").String()
	if kubeconfig == "" {
		kubeconfig = clusterId
	}
	cronjobName := gp.Get("cronjobName").String()
	nameSpace := gp.Get("nameSpace").String()
	var pullPolicy corev1.PullPolicy
	imagePullPolicy := gp.Get("imagePullPolicy").String()
	switch imagePullPolicy {
	case "Never":
		pullPolicy = corev1.PullNever
	case "IfNotPresent":
		pullPolicy = corev1.PullIfNotPresent
	default:
		pullPolicy = corev1.PullAlways
	}
	imageUrl := gp.Get("imageUrl").String()

	resourceLimitCheck := gp.Get("resourceLimitCheck").String()
	periodCheck := gp.Get("periodCheck").String()
	taskSetCheck := gp.Get("taskSetCheck").String()

	schedule := gp.Get("schedule").String()
	if schedule == "" {
		schedule = "* * * * *"
	}

	labelsMap := map[string]string{
		"app": cronjobName,
	}
	for _, vv := range gp.Get("lables").Array() {
		labelsMap[vv.Get("key").String()] = vv.Get("value").String()
	}

	cronjob := &batchv1.CronJob{
		ObjectMeta: metav1.ObjectMeta{
			Name:      cronjobName,
			Namespace: nameSpace,
			Labels:    labelsMap,
		},
		Spec: batchv1.CronJobSpec{
			Schedule: schedule, // 定时任务表达式
			JobTemplate: batchv1.JobTemplateSpec{
				Spec: batchv1.JobSpec{ //v1版本有JobSpec,beta1版本没有
					Template: corev1.PodTemplateSpec{
						Spec: corev1.PodSpec{
							Containers: []corev1.Container{
								{
									Name:            cronjobName,
									Image:           imageUrl,
									ImagePullPolicy: pullPolicy,
								},
							},
							RestartPolicy: corev1.RestartPolicyNever,
						},
					},
				},
			},
		},
	}

	commandStr := gp.Get("command").Str
	if commandStr != "" {
		commandArry := strings.Split(commandStr, ",")
		cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Command = commandArry
	}
	argsStr := gp.Get("args").Str
	if argsStr != "" {
		argsArry := strings.Split(argsStr, ",")
		cronjob.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Args = argsArry
	}

	jobClient := common.ClientSet(kubeconfig).BatchV1().CronJobs(nameSpace)
	_, err := jobClient.Create(context.TODO(), cronjob, metav1.CreateOptions{})
	if err != nil {
		return err
	}
	return nil
}

func CronjobYamlModify(kubeconfig string, yamlData []byte) error {
	data, err := yamlutil.ToJSON(yamlData)
	if err != nil {
		return err
	}
	cronjob := &batchv1.CronJob{}
	err = json.Unmarshal(data, cronjob)
	if err != nil {
		return err
	}

	namespace := cronjob.ObjectMeta.Namespace
	//cronjobName := cronjob.ObjectMeta.Name
	jobClient := common.ClientSet(kubeconfig).BatchV1().CronJobs(namespace)
	_, err = jobClient.Update(context.TODO(), cronjob, metav1.UpdateOptions{})
	return err
}

func GetCronjobYaml(kubeconfig, namespace, cronjobName string) (string, error) {
	jobClient := common.ClientSet(kubeconfig).BatchV1().CronJobs(namespace)
	jobInstance, err := jobClient.Get(context.TODO(), cronjobName, metav1.GetOptions{})

	jobUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(jobInstance)
	if err != nil {
		return "", err
	}
	yamlBytes, err := yaml.Marshal(jobUnstructured)
	if err != nil {
		return "", err
	}
	//fmt.Println(string(yamlBytes))
	return string(yamlBytes), nil
}

七.效果图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述