feat: 仓库迁移,并添加CICD

This commit is contained in:
devad 2022-09-07 09:06:05 +08:00 committed by zhangwei
parent 369a2bf92d
commit 32e69ce521
29 changed files with 4503 additions and 0 deletions

28
.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
*.a
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Files generated by JetBrains IDEs, e.g. IntelliJ IDEA
.idea/
*.iml
bin/
# Vscode files
.vscode/
__debug_bin
log/
cache/
tmp/
testbin/
karmadaConfig/

18
Dockerfile Normal file
View File

@ -0,0 +1,18 @@
FROM alpine:3.16.2
WORKDIR /home
# 修改alpine源为上海交通大学
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.sjtug.sjtu.edu.cn/g' /etc/apk/repositories && \
apk update && \
apk upgrade && \
apk add --no-cache ca-certificates && update-ca-certificates && \
apk add --update tzdata && \
rm -rf /var/cache/apk/*
COPY jcce-schedule /home/
ENV TZ=Asia/Shanghai
EXPOSE 8082
ENTRYPOINT ./jcce-schedule

72
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,72 @@
def JOB_NAME = "${env.JOB_NAME}"
def BUILD_NUMBER = "${env.BUILD_NUMBER}"
def label = "jenkins-${JOB_NAME}-${BUILD_NUMBER}-${UUID.randomUUID().toString()}"
def secret_name = "harbor-auth"
podTemplate(label: label, containers: [
containerTemplate(name: 'golang', image: 'golang:1.18.5-alpine3.16', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'docker', image: 'docker:latest', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'kubectl', image: 'jcce/kubectl:1.23.7', command: 'cat', ttyEnabled: true)
], serviceAccount: 'jenkins', volumes: [
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
node(label) {
def myRepo = checkout scm
// 获取 git commit id 作为镜像标签
def imageTag = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
// 仓库地址
def registryUrl = "hub.jcce-dev.net:8443"
def imageEndpoint = "jcce/jcce-schedule"
// 镜像
def image = "${registryUrl}/${imageEndpoint}:${imageTag}"
def imageLatest = "${registryUrl}/${imageEndpoint}:latest"
stage('单元测试') {
echo "1.测试阶段"
}
stage('代码编译打包') {
try {
container('golang') {
echo "2.代码编译打包阶段"
sh """
export GOPROXY=https://goproxy.cn
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o jcce-schedule ./main.go
"""
}
} catch (exc) {
println "构建失败 - ${currentBuild.fullDisplayName}"
throw(exc)
}
}
stage('构建 Docker 镜像') {
withCredentials([[$class: 'UsernamePasswordMultiBinding',
credentialsId: 'docker-auth',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASSWORD']]) {
container('docker') {
echo "3. 构建 Docker 镜像阶段"
sh('cat /etc/resolv.conf')
sh("docker login '${registryUrl}' -u '${DOCKER_USER}' -p '${DOCKER_PASSWORD}' ")
sh("docker build -t '${image}' -t '${imageLatest}' .")
sh("docker push '${image}'")
sh("docker push '${imageLatest}'")
sh("docker rmi '${image}' '${imageLatest}'")
}
}
}
stage('运行 Kubectl 部署到k8s平台') {
withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
container('kubectl') {
echo "5.部署应用"
sh('mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config')
sh("sed -i 's#IMAGE_NAME#${image}#' deploy/jcce-schedule-deployment.yaml")
sh("sed -i 's#SECRET_NAME#${secret_name}#' deploy/jcce-schedule-deployment.yaml")
sh('kubectl apply -f deploy/')
sh('sleep 3')
echo "6.查看应用"
sh('kubectl get all -n jcce-system -l app=${JOB_NAME}')
}
}
}
}
}

360
app/cluster.go Normal file
View File

@ -0,0 +1,360 @@
package app
import (
"context"
"fmt"
"github.com/gin-gonic/gin"
"github.com/golang/glog"
"github.com/karmada-io/karmada/pkg/karmadactl"
"github.com/karmada-io/karmada/pkg/karmadactl/options"
"github.com/karmada-io/karmada/pkg/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"net/http"
"os"
"strconv"
"strings"
"time"
)
type Cluster struct {
ClusterName string `json:"cluster_name"`
DomainId int32 `json:"domain_id"`
DomainName string `json:"domain_name"`
Version string `json:"version"`
Mode string `json:"mode"`
Labels map[string]string `json:"labels"`
State string `json:"state"`
CreateTime time.Time `json:"create_time"`
Description *string `json:"description"`
NodeNum int32 `json:"node_num"`
}
type JoinRequest struct {
// Name of the member cluster
MemberName string `json:"member_name"`
// ClusterProvider of the member cluster
MemberProvider string `json:"member_provider"`
Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,11,rep,name=labels"`
}
// @Summary 查询集群列表
// @Description 查询集群列表
// @Tags cluster
// @accept json
// @Produce json
// @Param pageNum query int true "页码"
// @Param pageSize query int true "每页数量"
// @Success 200
// @Failure 500
// @Router /api/v1/cluster/list [get]
func ListCluster(c *gin.Context) {
clusterName, _ := c.GetQuery("cluster_name")
clusterList := make([]Cluster, 0)
clusters, err := KarmadaClient.ClusterV1alpha1().Clusters().List(context.TODO(), metav1.ListOptions{})
if err != nil {
glog.Info("failed to retrieve cluster(%s). error: %v", clusters, err)
Response(c, http.StatusBadRequest, "failed to retrieve cluster", err)
}
//遍历集群
for i := 0; i < len(clusters.Items); i++ {
cluster := Cluster{
ClusterName: clusters.Items[i].Name,
State: string(clusters.Items[i].Status.Conditions[0].Status),
CreateTime: clusters.Items[i].CreationTimestamp.Time,
Labels: clusters.Items[i].Labels,
Mode: string(clusters.Items[i].Spec.SyncMode),
Version: clusters.Items[i].Status.KubernetesVersion,
NodeNum: clusters.Items[i].Status.NodeSummary.ReadyNum,
}
rows, err := DB.Query(`SELECT domain_id,domain_name,description FROM joint_domain.domain_cluster dc WHERE cluster_name = ? `, clusters.Items[i].Name)
if err != nil {
Response(c, http.StatusBadRequest, "query failed", err)
return
}
for rows.Next() {
err := rows.Scan(&cluster.DomainId, &cluster.DomainName, &cluster.Description)
if err != nil {
return
}
}
rows.Close()
if len(clusterName) != 0 && !strings.Contains(cluster.ClusterName, clusterName) {
continue
} else {
clusterList = append(clusterList, cluster)
}
}
total := len(clusterList)
page := &Page[Cluster]{}
page.List = clusterList
pageNum, _ := c.GetQuery("pageNum")
pageSize, _ := c.GetQuery("pageSize")
num, _ := strconv.ParseInt(pageNum, 10, 64)
size, _ := strconv.ParseInt(pageSize, 10, 64)
data := Paginator(page, int64(total), num, size)
Response(c, http.StatusOK, "success", data)
}
// ListClusterWithLabel 查询有标签的集群列表
func ListClusterWithLabel(c *gin.Context) {
clusterName, _ := c.GetQuery("cluster_name")
clusterList := make([]Cluster, 0)
clusters, err := KarmadaClient.ClusterV1alpha1().Clusters().List(context.TODO(), metav1.ListOptions{})
if err != nil {
glog.Info("failed to retrieve cluster(%s). error: %v", clusters, err)
Response(c, http.StatusBadRequest, "failed to retrieve cluster", err)
}
for i := 0; i < len(clusters.Items); i++ {
cluster := Cluster{
ClusterName: clusters.Items[i].Name,
State: string(clusters.Items[i].Status.Conditions[0].Status),
CreateTime: clusters.Items[i].CreationTimestamp.Time,
Labels: clusters.Items[i].Labels,
Mode: string(clusters.Items[i].Spec.SyncMode),
Version: clusters.Items[i].Status.KubernetesVersion,
}
rows, err := DB.Query(`SELECT domain_id,domain_name FROM domain_cluster dc WHERE cluster_name = ? `, clusters.Items[i].Name)
if err != nil {
Response(c, http.StatusBadRequest, "query failed", err)
return
}
for rows.Next() {
rows.Scan(&cluster.DomainId, &cluster.DomainName)
}
if len(cluster.Labels) != 0 {
if len(clusterName) > 0 {
if strings.Contains(cluster.ClusterName, clusterName) {
clusterList = append(clusterList, cluster)
} else {
continue
}
} else {
clusterList = append(clusterList, cluster)
}
} else {
continue
}
rows.Close()
}
total := len(clusterList)
page := &Page[Cluster]{}
page.List = clusterList
pageNum, _ := c.GetQuery("pageNum")
pageSize, _ := c.GetQuery("pageSize")
num, _ := strconv.ParseInt(pageNum, 10, 64)
size, _ := strconv.ParseInt(pageSize, 10, 64)
data := Paginator(page, int64(total), num, size)
Response(c, http.StatusOK, "success", data)
}
// UnJoin 刪除集群
func UnJoin(c *gin.Context) {
copyContext := c.Copy()
clusterName, _ := c.GetQuery("cluster_name")
domainId, _ := c.GetQuery("domain_id")
if clusterName == "" || domainId == "" {
Response(c, http.StatusBadRequest, "invalid request params.", "")
return
}
opts := karmadactl.CommandUnjoinOption{
DryRun: false,
ClusterNamespace: options.DefaultKarmadaClusterNamespace,
ClusterName: clusterName,
Wait: options.DefaultKarmadactlCommandDuration,
}
go func() {
glog.Info("异步执行:" + copyContext.Request.URL.Path)
err := karmadactl.UnJoinCluster(ControlPlaneRestConfig, nil, opts)
if err != nil {
glog.Errorf("failed to delete cluster. context: %s, kube-config: %s, error: %v", opts.KarmadaContext, opts.KubeConfig, err)
Response(c, http.StatusInternalServerError, fmt.Sprintf("failed to delete cluster. context: %s, kube-config: %s, error: %v",
opts.KarmadaContext, opts.KubeConfig, err), "")
return
}
//解绑集群和域的关系
_, err = DB.Exec(`delete from domain_cluster where domain_id= ? and cluster_name = ?`, domainId, clusterName)
}()
Response(c, http.StatusOK, "success", "")
}
// Join 新集群加入联邦
func Join(c *gin.Context) {
var (
Labels map[string]string
)
//member集群名称
memberName := c.PostForm("member_name")
if memberName == "" {
Response(c, http.StatusInternalServerError, fmt.Sprintf("failed to join cluster. memberName is null"), "")
return
}
file, _ := c.FormFile("file")
dir, _ := os.Getwd()
memberClusterConfigName := dir + "/karmadaConfig/" + memberName + ".config"
// 上传文件至指定目录
if err := c.SaveUploadedFile(file, memberClusterConfigName); err != nil {
glog.Errorf("File upload failed err %v", err)
Response(c, http.StatusInternalServerError, fmt.Sprintf("File upload failed err: %s", err), "")
return
}
//member集群标签
Labels, _ = jsonToMap(c.PostForm("labels"))
//资源域名称
domainId := c.PostForm("domain_id")
if domainId == "" {
Response(c, http.StatusInternalServerError, fmt.Sprintf("failed to join cluster. domain is null"), "")
return
}
//查询域是否存在
rows, _ := DB.Query("select domain_name from domain where domain_id = ?", domainId)
var memberProvider string
for rows.Next() {
var domainName string
err := rows.Scan(&domainName)
if err != nil {
glog.Errorf("query failed!,error %v", err)
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
memberProvider = domainName
}
rows.Close()
if memberProvider == "" {
Response(c, http.StatusInternalServerError, fmt.Sprintf("failed to join cluster. Domain does not exist"), "")
return
}
opts := karmadactl.CommandJoinOption{
DryRun: false,
ClusterNamespace: "karmada-cluster",
ClusterName: memberName,
ClusterKubeConfig: memberClusterConfigName,
ClusterProvider: memberProvider,
}
memberClusterRestConfig, err := KarmadaConfig.GetRestConfig(opts.KarmadaContext, memberClusterConfigName)
if err != nil {
Response(c, http.StatusInternalServerError, fmt.Sprintf("failed to get member cluster rest config. context: %s, kube-config: %s, error: %v",
opts.KarmadaContext, opts.KubeConfig, err), "")
return
}
err = karmadactl.JoinCluster(ControlPlaneRestConfig, memberClusterRestConfig, opts)
if err != nil {
Response(c, http.StatusInternalServerError, fmt.Sprintf("failed to join cluster. context: %s, kube-config: %s, error: %v",
opts.KarmadaContext, opts.KubeConfig, err), "")
return
}
//将域和集群绑定
description := c.PostForm("description")
_, err = DB.Exec(`insert into domain_cluster( domain_id, domain_name, cluster_name, description) values(?,?,?,?)`, domainId, memberProvider, memberName, description)
if Labels != nil {
// 给集群打上标签
cluster, _, err := util.GetClusterWithKarmadaClient(KarmadaClient, memberName)
if cluster == nil {
Response(c, http.StatusBadRequest, "Get cluster failed", err)
return
}
cluster.Labels = mergeMap(cluster.Labels, Labels)
_, err = KarmadaClient.ClusterV1alpha1().Clusters().Update(context.TODO(), cluster, metav1.UpdateOptions{})
if err != nil {
Response(c, http.StatusBadRequest, "update failed", err)
}
}
Response(c, http.StatusOK, "success", "")
}
// TagCluster 集群打标签
func TagCluster(c *gin.Context) {
var clusterTag Cluster
if err := c.BindJSON(&clusterTag); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", "")
return
}
cluster, _, err := util.GetClusterWithKarmadaClient(KarmadaClient, clusterTag.ClusterName)
if cluster == nil {
Response(c, http.StatusBadRequest, "Get cluster failed", err)
return
}
cluster.Labels = clusterTag.Labels
_, err = KarmadaClient.ClusterV1alpha1().Clusters().Update(context.TODO(), cluster, metav1.UpdateOptions{})
if err != nil {
Response(c, http.StatusBadRequest, "update failed", err)
}
Response(c, http.StatusOK, "success", cluster)
}
// UnTagCluster 集群删除标签
func UnTagCluster(c *gin.Context) {
var clusterTag Cluster
if err := c.BindJSON(&clusterTag); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", "")
return
}
cluster, _, err := util.GetClusterWithKarmadaClient(KarmadaClient, clusterTag.ClusterName)
if cluster == nil {
Response(c, http.StatusBadRequest, "get cluster failed", err)
return
}
deleteMaps(cluster.Labels, clusterTag.Labels)
_, err = KarmadaClient.ClusterV1alpha1().Clusters().Update(context.TODO(), cluster, metav1.UpdateOptions{})
if err != nil {
Response(c, http.StatusInternalServerError, fmt.Sprintf("failed to update cluster. error: %v", err), "")
}
Response(c, http.StatusOK, "success", cluster)
}
// 删除map中的某个key
func deleteMaps(labels map[string]string, keys map[string]string) {
for key := range keys {
delete(labels, key)
}
}
// ListByDomain 根据项目名称、工作负载、域id查询集群
func ListByDomain(c *gin.Context) {
namespaceName, _ := c.GetQuery("namespace")
deploymentName, _ := c.GetQuery("deployment_name")
domainId, _ := c.GetQuery("domain_id")
//
deployJson := GetDeployFromOS(namespaceName, deploymentName, "")
hits, _ := deployJson.Get("hits").Get("hits").Array()
var clusterSet []Cluster
for i := 0; i < len(hits); i++ {
cluster, _ := deployJson.Get("hits").Get("hits").GetIndex(i).Get("_source").Get("metadata").Get("annotations").Get("resource.karmada.io/cached-from-cluster").String()
//获取域列表
rows, _ := DB.Query("select dc.cluster_name,d.domain_name,d.domain_id from domain_cluster dc,domain d where d.domain_id = ? and dc.domain_id = d.domain_id ", domainId)
for rows.Next() {
var clusterName string
var domainName string
var domainIdResult int32
rows.Scan(&clusterName, &domainName, &domainId)
if clusterName == cluster {
cluster := Cluster{
ClusterName: clusterName,
DomainId: domainIdResult,
DomainName: domainName,
}
clusterSet = append(clusterSet, cluster)
}
}
}
Response(c, http.StatusOK, "success", clusterSet)
}

104
app/common.go Normal file
View File

@ -0,0 +1,104 @@
package app
import (
"encoding/json"
"fmt"
"github.com/golang/glog"
)
type sliceError struct {
msg string
}
func (e *sliceError) Error() string {
return e.msg
}
func Errorf(format string, args ...interface{}) error {
msg := fmt.Sprintf(format, args...)
return &sliceError{msg}
}
func mergeMap(maps ...map[string]string) map[string]string {
result := make(map[string]string)
for _, m := range maps {
for k, v := range m {
result[k] = v
}
}
return result
}
func removeDuplicateMap(originals []map[string]string) ([]map[string]string, error) {
temp := map[string]struct{}{}
result := make([]map[string]string, 0, len(originals))
for _, item := range originals {
key := fmt.Sprint(item)
if _, ok := temp[key]; !ok {
temp[key] = struct{}{}
result = append(result, item)
}
}
return result, nil
}
func removeDuplicateMaps(originals []map[string][]string) ([]map[string][]string, error) {
temp := map[string]struct{}{}
result := make([]map[string][]string, 0, len(originals))
for _, item := range originals {
key := fmt.Sprint(item)
if _, ok := temp[key]; !ok {
temp[key] = struct{}{}
result = append(result, item)
}
}
return result, nil
}
// JsonToMap string转map
func jsonToMap(jsonStr string) (map[string]string, error) {
m := make(map[string]string)
err := json.Unmarshal([]byte(jsonStr), &m)
if err != nil {
glog.Errorf("JsonToMap error %v", err)
return nil, err
}
return m, nil
}
func RemoveRepeatedDomain(arr []Domain) (newArr []Domain) {
newArr = make([]Domain, 0)
for i := 0; i < len(arr); i++ {
repeat := false
for j := i + 1; j < len(arr); j++ {
if arr[i].DomainId == arr[j].DomainId {
repeat = true
break
}
}
if !repeat {
newArr = append(newArr, arr[i])
}
}
return
}
func removeDuplicateArr(arr []string) []string {
set := make(map[string]struct{}, len(arr))
j := 0
for _, v := range arr {
_, ok := set[v]
if ok {
continue
}
set[v] = struct{}{}
arr[j] = v
j++
}
return arr[:j]
}

207
app/deployment.go Normal file
View File

@ -0,0 +1,207 @@
package app
import (
"context"
"github.com/bitly/go-simplejson"
"github.com/gin-gonic/gin"
appv1 "k8s.io/api/apps/v1"
coreV1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"net/http"
"strconv"
"sync"
"time"
)
var deployMutex sync.Mutex
type Deployment struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Labels map[string]string `json:"labels"`
Port int32 `json:"port"`
ContainerImage string `json:"container_image"`
ContainerName string `json:"container_name"`
Replicas int32 `json:"replicas"`
Cluster string `json:"cluster"`
Domain string `json:"domain"`
}
// CreateDeployment 创建工作负载
func CreateDeployment(c *gin.Context) {
var dpRequest Deployment
if err := c.BindJSON(&dpRequest); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", "")
return
}
deployMutex.Lock()
defaultLabels := map[string]string{"jcce": "true"}
deployMutex.Unlock()
namespace := dpRequest.Namespace
if dpRequest.Port == 0 {
dpRequest.Port = 80
}
var replicas = dpRequest.Replicas
deployment := &appv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: dpRequest.Name,
Namespace: dpRequest.Namespace,
},
Spec: appv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: defaultLabels,
},
Template: coreV1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Name: dpRequest.Name,
Labels: defaultLabels,
},
Spec: coreV1.PodSpec{
Containers: []coreV1.Container{
{
Name: dpRequest.ContainerName,
Image: dpRequest.ContainerImage,
Ports: []coreV1.ContainerPort{
{
Name: "http",
Protocol: coreV1.ProtocolTCP,
ContainerPort: dpRequest.Port,
},
},
},
},
},
},
},
}
deploymentList, err := ClientSet.AppsV1().Deployments(namespace).Create(context.TODO(), deployment, metav1.CreateOptions{})
if err != nil {
Response(c, http.StatusInternalServerError, "create deployment failed", err)
return
}
Response(c, http.StatusOK, "success", deploymentList)
}
// ListDeployment 根据查询工作负载列表(控制平面)
func ListDeployment(c *gin.Context) {
namespace, _ := c.GetQuery("namespace")
dpList := make([]Deployment, 0)
deploymentList, err := ClientSet.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
Response(c, http.StatusInternalServerError, "list deployment failed", err)
return
}
for _, deploy := range deploymentList.Items {
name := deploy.ObjectMeta.Name
namespace := deploy.ObjectMeta.Namespace
labels := deploy.Labels
containerName := deploy.Spec.Template.Spec.Containers[0].Name
containerImage := deploy.Spec.Template.Spec.Containers[0].Image
replicas := deploy.Spec.Replicas
dp := Deployment{
Name: name,
Namespace: namespace,
Labels: labels,
ContainerImage: containerImage,
ContainerName: containerName,
Replicas: *replicas,
}
dpList = append(dpList, dp)
}
total := len(dpList)
page := &Page[Deployment]{}
page.List = dpList
pageNum, _ := c.GetQuery("pageNum")
pageSize, _ := c.GetQuery("pageSize")
num, _ := strconv.ParseInt(pageNum, 10, 64)
size, _ := strconv.ParseInt(pageSize, 10, 64)
data := Paginator(page, int64(total), num, size)
Response(c, http.StatusOK, "success", data)
//Response(c, http.StatusOK, "success", dpList)
}
// ListClusterDeployment 查询工作负载列表(所有集群)
func ListClusterDeployment(c *gin.Context) {
//TODO 逻辑待完善
ss, _ := KarmadaClient.SearchV1alpha1().ResourceRegistries().Get(context.TODO(), "clustercache-sample", metav1.GetOptions{})
Response(c, http.StatusOK, "success", ss)
}
// DescribeDeployment 查询工作负载列表(所有集群)
func DescribeDeployment(c *gin.Context) {
namespace, _ := c.GetQuery("namespace")
deployName, _ := c.GetQuery("deploy_name")
podList := make([]Pod, 0)
pods := GetPodFromOS(namespace, deployName)
hits, _ := pods.Get("hits").Get("hits").Array()
for i := 0; i < len(hits); i++ {
clusterName, _ := pods.Get("hits").Get("hits").GetIndex(i).Get("_source").Get("metadata").Get("annotations").Get("resource.karmada.io/cached-from-cluster").String()
podName, _ := pods.Get("hits").Get("hits").GetIndex(i).Get("_source").Get("metadata").Get("name").String()
spec := pods.Get("hits").Get("hits").GetIndex(i).Get("_source").Get("spec")
status := pods.Get("hits").Get("hits").GetIndex(i).Get("_source").Get("status")
specString, _ := spec.String()
specJson, _ := simplejson.NewJson([]byte(specString))
statusString, _ := status.String()
statusJson, _ := simplejson.NewJson([]byte(statusString))
ready, _ := statusJson.Get("containerStatuses").Get("ready").String()
restartCount, _ := statusJson.Get("containerStatuses").Get("restartCount").Int64()
startTimeString, _ := statusJson.Get("startTime").String()
startTime, _ := time.Parse("2006-01-02T15:04:05Z", startTimeString)
podIP, _ := statusJson.Get("podIP").String()
nodeName, _ := specJson.Get("nodeName").String()
podStatus, _ := statusJson.Get("phase").String()
containerName, _ := specJson.Get("containers").GetIndex(0).Get("name").String()
containerImage, _ := specJson.Get("containers").GetIndex(0).Get("image").String()
pod := Pod{
Name: podName,
ClusterName: clusterName,
Ready: ready,
Status: podStatus,
Restarts: restartCount,
Age: strconv.FormatFloat(time.Now().Sub(startTime).Hours()/24, 'f', 0, 64) + "d",
IP: podIP,
Node: nodeName,
Namespace: namespace,
ContainerName: containerName,
ContainerImage: containerImage,
}
rows, _ := DB.Query("select dc.domain_id,dc.domain_name,d.longitude,d.latitude from joint_domain.domain_cluster dc,joint_domain.domain d where dc.domain_id = d.domain_id and dc.cluster_name = ?", clusterName)
var domainId int32
var domainName string
var longitude float64
var latitude float64
for rows.Next() {
err := rows.Scan(&domainId, &domainName, &longitude, &latitude)
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
}
pod.DomainName = domainName
podList = append(podList, pod)
}
Response(c, http.StatusOK, "success", podList)
}

251
app/domain.go Normal file
View File

@ -0,0 +1,251 @@
package app
import (
"github.com/gin-gonic/gin"
"github.com/karmada-io/karmada/pkg/util"
"net/http"
"strconv"
)
type Domain struct {
DomainId int32 `json:"domain_id"`
DomainName string `json:"domain_name"`
LabelType string `json:"label_type"`
Location [2]float64 `json:"location"`
Labels []map[string]string `json:"labels"`
Clusters []string `json:"clusters"`
Deployments []string `json:"deployments"`
CPURate float64 `json:"cpu_rate"`
MemoryRate float64 `json:"memory_rate"`
Description string `json:"description"`
LabelId []int32 `json:"labelId"`
}
// CreateDomain 创建域
func CreateDomain(c *gin.Context) {
//获取填写的参数
domain := Domain{}
c.BindJSON(&domain)
sqlCreateDomain := "insert into domain(domain_name,longitude,latitude) values (?,?,?)"
_, err := DB.Exec(sqlCreateDomain, domain.DomainName, domain.Location[0], domain.Location[1])
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
domainIdMax, _ := DB.Query("select max(domain_id) from domain")
defer domainIdMax.Close()
var domainId int32
for domainIdMax.Next() {
var id int32
err := domainIdMax.Scan(&id)
domainId = id
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
}
var keys []string
var values []string
for _, label := range domain.Labels {
for k, v := range label {
keys = append(keys, k)
values = append(values, v)
}
}
for i := 0; i < len(domain.Clusters); i++ {
sqlStr2 := "insert into domain_cluster(domain_id,domain_name,cluster_name,description) values (?,?,?,?)"
_, err := DB.Exec(sqlStr2, domainId, domain.DomainName, domain.Clusters[i], domain.Description)
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
}
for i := 0; i < len(domain.Labels[0]); i++ {
sqlDomainLabel := "insert into domain_label(domain_id,domain_name,label_id,label_type,label_name) values (?,?,?,?,?)"
_, err := DB.Exec(sqlDomainLabel, domainId, domain.DomainName, domain.LabelId[i], keys[i], values[i])
//b, err := DB.Exec(sqlStr3, domainId, domain.DomainName, 4, keys[i], values[i])
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
}
Response(c, http.StatusOK, "success", len(domain.Labels[0]))
}
// ListDomain 查询Domain列表
func ListDomain(c *gin.Context) {
domainName, _ := c.GetQuery("domain_name")
//获取域列表
rows, _ := DB.Query("select domain_id,domain_name,longitude,latitude from domain where domain_name like ?", "%"+domainName+"%")
defer rows.Close()
domainList := make([]Domain, 0)
for rows.Next() {
var dm Domain
var longitude float64
var latitude float64
err := rows.Scan(&dm.DomainId, &dm.DomainName, &longitude, &latitude)
var location = [...]float64{longitude, latitude}
dm.Location = location
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
//获取单个域的标签
sqlDomainLabel := "select d.domain_name,l.label_type,l.label_name from domain d,domain_label l where d.domain_id = l.domain_id and d.domain_id = ?"
rowsLabel, err := DB.Query(sqlDomainLabel, dm.DomainId)
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
domainLabelList := make(map[string]string)
for rowsLabel.Next() {
var domainName string
var labelKey string
var labelValue string
err := rowsLabel.Scan(&domainName, &labelKey, &labelValue)
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
domainLabelList[labelKey] = labelValue
}
dm.Labels = append(dm.Labels, domainLabelList)
//获取单个域下的所有集群
sqlDomainCluster := "select d.domain_name,c.cluster_name from domain d,domain_cluster c where d.domain_id = c.domain_id and d.domain_id = ?"
rowsCluster, err := DB.Query(sqlDomainCluster, dm.DomainId)
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
var clusters []string
for rowsCluster.Next() {
var domainName string
var clusterName string
err := rowsCluster.Scan(&domainName, &clusterName)
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
clusters = append(clusters, clusterName)
}
dm.Clusters = clusters
domainList = append(domainList, dm)
}
Response(c, http.StatusOK, "success", domainList)
}
//DescribeDomain 获取域的详细信息
/*
入参: domainID namespace名称
出参: deployment数量 资源使用量
首先通过domain_cluster表查出该域所有的集群列表遍历集群列表
在单个集群使用namespace查询所有deployment将deploy名称放入上层domain数组
*/
func DescribeDomain(c *gin.Context) {
var domain Domain
domainId, _ := c.GetQuery("domain_id")
namespace, _ := c.GetQuery("namespace")
rows, _ := DB.Query("select d.domain_name,dc.cluster_name,d.longitude,d.latitude from domain_cluster dc,domain d where dc.domain_id = d.domain_id and dc.domain_id = ?", domainId)
var domainName string
var longitude float64
var latitude float64
var totalAllocatableCPU int64
var totalAllocatableMemory int64
var totalAllocatedCPU int64
var totalAllocatedMemory int64
for rows.Next() {
var clusterName string
err := rows.Scan(&domainName, &clusterName, &longitude, &latitude)
if err != nil {
println(err)
}
deploys := GetDeployFromOS(namespace, "", clusterName)
hits, _ := deploys.Get("hits").Get("hits").Array()
for i := 0; i < len(hits); i++ {
deployName, _ := deploys.Get("hits").Get("hits").GetIndex(i).Get("_source").Get("metadata").Get("name").String()
domain.Deployments = append(domain.Deployments, deployName)
}
domain.Clusters = append(domain.Clusters, clusterName)
cluster, _, _ := util.GetClusterWithKarmadaClient(KarmadaClient, clusterName)
allocatableCPU := cluster.Status.ResourceSummary.Allocatable.Cpu().MilliValue()
allocatableMemory := cluster.Status.ResourceSummary.Allocatable.Memory().MilliValue()
allocatedCPU := cluster.Status.ResourceSummary.Allocated.Cpu().MilliValue()
allocatedMemory := cluster.Status.ResourceSummary.Allocated.Memory().MilliValue()
totalAllocatableCPU += allocatableCPU
totalAllocatableMemory += allocatableMemory
totalAllocatedCPU += allocatedCPU
totalAllocatedMemory += allocatedMemory
}
ddId, _ := strconv.ParseInt(domainId, 10, 32)
domain.DomainId = int32(ddId)
domain.DomainName = domainName
domain.Deployments = removeDuplicateArr(domain.Deployments)
domain.Location[0] = longitude
domain.Location[1] = latitude
domain.CPURate = float64(totalAllocatedCPU) / float64(totalAllocatableCPU)
domain.MemoryRate = float64(totalAllocatedMemory) / float64(totalAllocatableMemory)
Response(c, http.StatusOK, "success", domain)
}
// ListByDeployment 根据项目名称和工作负载查询域列表
func ListByDeployment(c *gin.Context) {
domainList := make([]Domain, 0)
namespaceName, _ := c.GetQuery("namespace")
deploymentName, _ := c.GetQuery("deployment_name")
domainList = getDomainsByDeployment(namespaceName, deploymentName)
Response(c, http.StatusOK, "success", domainList)
}
// 通过ns名称+deploy名称查询deploy所在域的信息单独方法供复用
func getDomainsByDeployment(namespaceName string, deploymentName string) (domainList []Domain) {
deployJson := GetDeployFromOS(namespaceName, deploymentName, "")
hits, _ := deployJson.Get("hits").Get("hits").Array()
for i := 0; i < len(hits); i++ {
cluster, _ := deployJson.Get("hits").Get("hits").GetIndex(i).Get("_source").Get("metadata").Get("annotations").Get("resource.karmada.io/cached-from-cluster").String()
domain := Domain{}
//获取域列表
rows, _ := DB.Query("select domain_name,domain_id,cluster_name from domain_cluster where cluster_name = ?", cluster)
var clusters []string
for rows.Next() {
var domainName string
var clusterName string
var domainId int32
err := rows.Scan(&domainName, &domainId, &clusterName)
if err != nil {
return nil
}
domain.DomainName = domainName
domain.DomainId = domainId
clusters = append(clusters, clusterName)
}
domain.Clusters = clusters
domainList = append(domainList, domain)
}
//去重
domainList = RemoveRepeatedDomain(domainList)
return domainList
}

111
app/label.go Normal file
View File

@ -0,0 +1,111 @@
package app
import (
"github.com/gin-gonic/gin"
"net/http"
"strconv"
"time"
)
type Label struct {
LabelId int32 `json:"label_id"`
LabelTypeId int32 `json:"label_type_id"`
LabelType *string `json:"label_type"`
LabelName string `json:"label_name"`
CreateTime *string `json:"create_time"`
UpdateTime *string `json:"update_time"`
}
// CreateLabel 创建域标
func CreateLabel(c *gin.Context) {
var j Label
if err := c.BindJSON(&j); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", err)
return
}
_, err := DB.Exec(`INSERT label(label_type_id,label_name,create_time,update_time) VALUES (?,?,?,?)`, j.LabelTypeId, j.LabelName, time.Now(), time.Now())
if err != nil {
Response(c, http.StatusBadRequest, "insert failed", err)
return
}
Response(c, http.StatusOK, "success", nil)
}
// DeleteLabel 删除域标
func DeleteLabel(c *gin.Context) {
var j Label
if err := c.BindJSON(&j); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", "")
return
}
_, err := DB.Exec(`DELETE FROM label WHERE label_id = ?`, j.LabelId)
if err != nil {
Response(c, http.StatusBadRequest, "insert failed", err)
return
}
Response(c, http.StatusOK, "success", nil)
}
// ListLabel 查询域标列表
func ListLabel(c *gin.Context) {
labelName, _ := c.GetQuery("label_name")
LabelList := make([]Label, 0)
rows, err := DB.Query(`SELECT l.label_id,l.label_type_id ,lt.label_type ,l.label_name, l.create_time,l.update_time FROM label l,label_type lt WHERE l.label_type_id = lt.label_type_id and l.label_name like ?`, "%"+labelName+"%")
defer rows.Close()
if err != nil {
Response(c, http.StatusBadRequest, "query failed", err)
return
}
for rows.Next() {
var lt Label
var createTimeString string
var updateTimeString string
err := rows.Scan(&lt.LabelId, &lt.LabelTypeId, &lt.LabelType, &lt.LabelName, &createTimeString, &updateTimeString)
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
lt.CreateTime = &createTimeString
lt.UpdateTime = &updateTimeString
LabelList = append(LabelList, lt)
}
total := len(LabelList)
page := &Page[Label]{}
page.List = LabelList
pageNum, _ := c.GetQuery("pageNum")
pageSize, _ := c.GetQuery("pageSize")
num, _ := strconv.ParseInt(pageNum, 10, 64)
size, _ := strconv.ParseInt(pageSize, 10, 64)
data := Paginator(page, int64(total), num, size)
Response(c, http.StatusOK, "success", data)
//Response(c, http.StatusOK, "success", LabelList)
}
// UpdateLabel 更新域标
func UpdateLabel(c *gin.Context) {
var j Label
if err := c.BindJSON(&j); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", err)
return
}
_, err := DB.Exec(`UPDATE label SET label_type_id = ?,label_name = ?,update_time = ? WHERE label_id = ?`, j.LabelTypeId, j.LabelName, time.Now(), j.LabelId)
if err != nil {
Response(c, http.StatusBadRequest, "update failed", err)
return
}
Response(c, http.StatusOK, "success", nil)
}

99
app/labelType.go Normal file
View File

@ -0,0 +1,99 @@
package app
import (
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
type LabelType struct {
LabelTypeId int32 `json:"label_type_id"`
LabelType string `json:"label_type"`
MultiCheck *bool `json:"multi_check"`
}
// CreateLabelType 创建域标类型
func CreateLabelType(c *gin.Context) {
var j LabelType
if err := c.BindJSON(&j); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", err)
return
}
_, err := DB.Exec(`INSERT label_type(label_type,multi_check) VALUES (?,?)`, j.LabelType, j.MultiCheck)
if err != nil {
Response(c, http.StatusBadRequest, "insert failed", err)
return
}
Response(c, http.StatusOK, "success", nil)
}
// DeleteLabelType 删除域标类型
func DeleteLabelType(c *gin.Context) {
var j LabelType
if err := c.BindJSON(&j); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", err)
return
}
_, err := DB.Exec(`DELETE FROM label_type WHERE label_type_id = ?`, j.LabelTypeId)
if err != nil {
Response(c, http.StatusBadRequest, "insert failed", err)
return
}
Response(c, http.StatusOK, "success", nil)
}
// ListLabelType 查询域标类型
func ListLabelType(c *gin.Context) {
labelType, _ := c.GetQuery("label_type")
LabelTypeList := make([]LabelType, 0)
rows, err := DB.Query(`SELECT label_type_id,label_type,multi_check FROM label_type WHERE label_type like ?`, "%"+labelType+"%")
if err != nil {
Response(c, http.StatusBadRequest, "query failed", err)
return
}
defer rows.Close()
for rows.Next() {
var lt LabelType
err := rows.Scan(&lt.LabelTypeId, &lt.LabelType, &lt.MultiCheck)
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
LabelTypeList = append(LabelTypeList, lt)
}
total := len(LabelTypeList)
page := &Page[LabelType]{}
page.List = LabelTypeList
pageNum, _ := c.GetQuery("pageNum")
pageSize, _ := c.GetQuery("pageSize")
num, _ := strconv.ParseInt(pageNum, 10, 64)
size, _ := strconv.ParseInt(pageSize, 10, 64)
data := Paginator(page, int64(total), num, size)
Response(c, http.StatusOK, "success", data)
//Response(c, http.StatusOK, "success", LabelTypeList)
}
// UpdateLabelType 更新域标类型
func UpdateLabelType(c *gin.Context) {
var j LabelType
if err := c.BindJSON(&j); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", err)
return
}
_, err := DB.Exec(`UPDATE label_type SET label_type = ?,multi_check = ? WHERE label_type_id = ?`, j.LabelType, j.MultiCheck, j.LabelTypeId)
if err != nil {
Response(c, http.StatusBadRequest, "update failed", err)
return
}
Response(c, http.StatusOK, "success", nil)
}

99
app/nacos.go Normal file
View File

@ -0,0 +1,99 @@
package app
import (
"github.com/nacos-group/nacos-sdk-go/clients"
"github.com/nacos-group/nacos-sdk-go/clients/config_client"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/vo"
"gopkg.in/yaml.v3"
"log"
"os"
)
type Config struct {
App struct {
Name string `yaml:"name"`
Version string `yaml:"version"`
}
OpenSearch struct {
Url string `yaml:"url"`
UserName string `yaml:"username"`
PassWord string `yaml:"password"`
}
Mysql struct {
Url string `yaml:"url"`
MaxOpenConn int `yaml:"max-open-conn"`
MaxIdleConn int `yaml:"max-idle-conn"`
}
Karmada struct {
ConfigPath string `yaml:"config-path"`
MemberConfigPath string `yaml:"member-config-path"`
}
}
func GetNacosConfig() Config {
configClient := GetClient()
// 获取配置
dataId := "dispatch_center_test"
group := "dispatch"
content, err := configClient.GetConfig(vo.ConfigParam{
DataId: dataId,
Group: group})
if err != nil {
log.Fatalf("获取%s配置失败: %s", dataId, err.Error())
}
var config = Config{}
err = yaml.Unmarshal([]byte(content), &config)
if err != nil {
log.Fatalf("解析%s配置失败: %s", dataId, err.Error())
}
CreateKarmadaHostConfig()
return config
}
func GetClient() config_client.IConfigClient {
serverConfig := []constant.ServerConfig{
{
IpAddr: "10.101.15.6",
Port: 8848,
},
}
// 创建clientConfig
clientConfig := constant.ClientConfig{
NamespaceId: "zqj", // 如果需要支持多namespace我们可以场景多个client,它们有不同的NamespaceId。当namespace是public时此处填空字符串。
TimeoutMs: 5000,
NotLoadCacheAtStart: true,
LogLevel: "debug",
}
// 创建动态配置客户端
configClient, err := clients.CreateConfigClient(map[string]interface{}{
"serverConfigs": serverConfig,
"clientConfig": clientConfig,
})
if err != nil {
log.Fatalf("初始化nacos失败: %s", err.Error())
}
return configClient
}
// CreateKarmadaHostConfig 从nacos读取配置创建karmada-host集群配置文件
func CreateKarmadaHostConfig() {
configName := "karmada-host"
client := GetClient()
content, err := client.GetConfig(vo.ConfigParam{
DataId: "karmada-host",
Group: "dispatch",
})
if err != nil {
log.Fatalf("获取%s配置失败: %s", "golang", err.Error())
}
dir, _ := os.Getwd()
err = os.MkdirAll(dir+"/karmadaConfig", 0766)
if err != nil {
log.Fatalf("nacos 配置目录创建失败,error:%s", err.Error())
}
clusterConfig, err := os.Create("karmadaConfig/" + configName)
defer clusterConfig.Close()
clusterConfig.Write([]byte(content))
}

280
app/namespace.go Normal file
View File

@ -0,0 +1,280 @@
package app
import (
"context"
"github.com/gin-gonic/gin"
"github.com/karmada-io/karmada/pkg/util"
coreV1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"net/http"
"strconv"
"time"
)
type Namespace struct {
NsName string `json:"ns_name"`
State string `json:"state"`
Age string `json:"age"`
CrossDomain bool `json:"cross_domain"`
Domains []Domain `json:"domains"`
Clusters []string `json:"clusters"`
Deployments []string `json:"deployments"`
RequirePodNum int32 `json:"require_pod_num"`
AvailablePodNum int32 `json:"available_pod_num"`
Alias string `json:"alias"`
Describe string `json:"describe"`
}
type DomainResult struct {
DomainName string `json:"domain_name"`
Location [2]float64 `json:"location"`
}
// CreateNamespace 创建命名空间(项目)
func CreateNamespace(c *gin.Context) {
var j Namespace
labels := make(map[string]string)
labels["jcce"] = "true"
if err := c.BindJSON(&j); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", "")
return
}
sqlStr1 := "INSERT INTO joint_domain.namespace (namespace_name, alias, `describe`) VALUES(?,?,?)"
_, err := DB.Exec(sqlStr1, j.NsName, j.Alias, j.Describe)
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
ns := coreV1.Namespace{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: j.NsName,
Labels: labels,
},
Spec: coreV1.NamespaceSpec{},
Status: coreV1.NamespaceStatus{},
}
nsResult, err := ClientSet.CoreV1().Namespaces().Create(context.TODO(), &ns, metav1.CreateOptions{})
if err != nil {
println(err)
}
Response(c, http.StatusOK, "success", nsResult)
}
// ListNamespace 查询Namespace列表
func ListNamespace(ctx *gin.Context) {
opts := metav1.ListOptions{
TypeMeta: metav1.TypeMeta{},
LabelSelector: "jcce=true",
Watch: false,
AllowWatchBookmarks: false,
TimeoutSeconds: nil,
Limit: 0,
}
nsResult, _ := ClientSet.CoreV1().Namespaces().List(context.TODO(), opts)
var nsList []Namespace
for _, ns := range nsResult.Items {
deploymentList, err := ClientSet.AppsV1().Deployments(ns.Name).List(context.TODO(), metav1.ListOptions{})
if err != nil {
return
}
var totalRequired int32
var totalAvailable int32
domainList := make([]Domain, 0)
for _, deploy := range deploymentList.Items {
//从OpenSearch中获取deployment相关信息
domains := getDomainsByDeployment(ns.Name, deploy.Name)
for _, domain := range domains {
domainList = append(domainList, domain)
}
replicaRequired := *deploy.Spec.Replicas
replicaAvailable := deploy.Status.AvailableReplicas
totalRequired += replicaRequired
totalAvailable += replicaAvailable
}
domainList = RemoveRepeatedDomain(domainList)
crossDomain := false
if len(domainList) > 1 {
crossDomain = true
}
ns := Namespace{
NsName: ns.Name,
State: string(ns.Status.Phase),
Age: strconv.FormatFloat(time.Now().Sub(ns.CreationTimestamp.Time).Hours()/24, 'f', 0, 64) + "d",
CrossDomain: crossDomain,
RequirePodNum: totalRequired,
AvailablePodNum: totalAvailable,
}
nsList = append(nsList, ns)
}
total := len(nsList)
page := &Page[Namespace]{}
page.List = nsList
pageNum, _ := ctx.GetQuery("pageNum")
pageSize, _ := ctx.GetQuery("pageSize")
num, _ := strconv.ParseInt(pageNum, 10, 64)
size, _ := strconv.ParseInt(pageSize, 10, 64)
data := Paginator(page, int64(total), num, size)
Response(ctx, http.StatusOK, "success", data)
}
// DescribeNamespace 查询Namespace详情
func DescribeNamespace(c *gin.Context) {
namespace, _ := c.GetQuery("namespace")
optsGet := metav1.GetOptions{
TypeMeta: metav1.TypeMeta{},
ResourceVersion: "",
}
nsDescribe, _ := ClientSet.CoreV1().Namespaces().Get(context.TODO(), namespace, optsGet)
//集群工作负载分配到的所有集群和域的集合,这里的数字可以从控制平面来取
deploymentList, err := ClientSet.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
return
}
var totalRequired int32
var totalAvailable int32
for _, deploy := range deploymentList.Items {
replicaRequired := *deploy.Spec.Replicas
replicaAvailable := deploy.Status.AvailableReplicas
totalRequired += replicaRequired
totalAvailable += replicaAvailable
}
//通过OpenSearch查询该ns下的分布在不同集群的所有deployment
deploys := GetDeployFromOS(namespace, "", "")
hits, _ := deploys.Get("hits").Get("hits").Array()
//集群详情中包含集群下所有的工作负载列表,所有工作负载所在的集群列表,以及所有集群所在的域列表
var deploySet []string
var clusterSet []string
var domainSet []Domain
for i := 0; i < len(hits); i++ {
clusterName, _ := deploys.Get("hits").Get("hits").GetIndex(i).Get("_source").Get("metadata").Get("annotations").Get("resource.karmada.io/cached-from-cluster").String()
deployName, _ := deploys.Get("hits").Get("hits").GetIndex(i).Get("_source").Get("metadata").Get("name").String()
//通过cluster name从数据库获取 其域名
rows, _ := DB.Query("select dc.domain_id,dc.domain_name,d.longitude,d.latitude from domain_cluster dc,domain d where dc.domain_id = d.domain_id and dc.cluster_name = ?", clusterName)
clusterSet = append(clusterSet, clusterName)
var domainId int32
var domainName string
var longitude float64
var latitude float64
for rows.Next() {
err := rows.Scan(&domainId, &domainName, &longitude, &latitude)
if err != nil {
return
}
}
deploySet = append(deploySet, deployName+":"+clusterName)
/////////
var totalAllocatableCPU int64
var totalAllocatableMemory int64
var totalAllocatedCPU int64
var totalAllocatedMemory int64
var domain Domain
deploys := GetDeployFromOS(namespace, "", clusterName)
hits, _ := deploys.Get("hits").Get("hits").Array()
for i := 0; i < len(hits); i++ {
deployName, _ := deploys.Get("hits").Get("hits").GetIndex(i).Get("_source").Get("metadata").Get("name").String()
domain.Deployments = append(domain.Deployments, deployName)
}
domain.Clusters = append(domain.Clusters, clusterName)
cluster, _, _ := util.GetClusterWithKarmadaClient(KarmadaClient, clusterName)
allocatableCPU := cluster.Status.ResourceSummary.Allocatable.Cpu().MilliValue()
allocatableMemory := cluster.Status.ResourceSummary.Allocatable.Memory().MilliValue()
allocatedCPU := cluster.Status.ResourceSummary.Allocated.Cpu().MilliValue()
allocatedMemory := cluster.Status.ResourceSummary.Allocated.Memory().MilliValue()
totalAllocatableCPU += allocatableCPU
totalAllocatableMemory += allocatableMemory
totalAllocatedCPU += allocatedCPU
totalAllocatedMemory += allocatedMemory
domain.DomainId = domainId
domain.DomainName = domainName
domain.Deployments = removeDuplicateArr(domain.Deployments)
domain.Location[0] = longitude
domain.Location[1] = latitude
domain.CPURate = float64(totalAllocatedCPU) / float64(totalAllocatableCPU)
domain.MemoryRate = float64(totalAllocatedMemory) / float64(totalAllocatableMemory)
domainSet = append(domainSet, domain)
}
//域和集群 结果去重
clusterSet = removeDuplicateArr(clusterSet)
domainSet = RemoveRepeatedDomain(domainSet)
nsResult := Namespace{
NsName: nsDescribe.Name,
State: string(nsDescribe.Status.Phase),
Age: strconv.FormatFloat(time.Now().Sub(nsDescribe.CreationTimestamp.Time).Hours()/24, 'f', 0, 64) + "d",
RequirePodNum: totalRequired,
AvailablePodNum: totalAvailable,
Deployments: deploySet,
Domains: domainSet,
Clusters: clusterSet,
}
Response(c, http.StatusOK, "success", nsResult)
}
// UpdateNamespace 更新命名空间(项目)
func UpdateNamespace(c *gin.Context) {
var j Namespace
if err := c.BindJSON(&j); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", "")
return
}
ns := coreV1.Namespace{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: j.NsName,
},
Spec: coreV1.NamespaceSpec{},
Status: coreV1.NamespaceStatus{},
}
nsResult, err := ClientSet.CoreV1().Namespaces().Update(context.TODO(), &ns, metav1.UpdateOptions{})
if err != nil {
println(err)
}
Response(c, http.StatusOK, "success", nsResult)
}
// DeleteNamespace 删除命名空间(项目)
func DeleteNamespace(c *gin.Context) {
var j Namespace
if err := c.BindJSON(&j); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", "")
return
}
err := ClientSet.CoreV1().Namespaces().Delete(context.TODO(), j.NsName, metav1.DeleteOptions{})
if err != nil {
Response(c, http.StatusInternalServerError, "delete namespace "+j.NsName+"failed", "")
println(err)
}
Response(c, http.StatusOK, "success", "")
}

67
app/node.go Normal file
View File

@ -0,0 +1,67 @@
package app
import (
"github.com/gin-gonic/gin"
)
type Node struct {
NodeName string `json:"nodeName"`
ClusterName string `json:"clusterName"`
State string `json:"state"`
Age string `json:"age"`
Roles string `json:"roles"`
Version string `json:"version"`
Ip string `json:"ip"`
}
// ListNode 查询节点列表
func ListNode(c *gin.Context) {
//TODO 调整为karmada-search
//nodes := make([]Node, 0)
//cmdKmd := karmadactl.NewKarmadaCtlCommand("karmadactl", "karmadactl")
//cmdKmd.SetArgs([]string{"get", "nodes"})
//if err := cmdKmd.Execute(); err != nil {
// println(err)
//}
//result := karmadactl.GetResult
//for i := 0; i < len(result); i++ {
// cluster := result[i].Cluster
// listData, err := json.Marshal(result[i].Info.Object)
// var respNode *simplejson.Json
// respNode, err = simplejson.NewJson(listData)
// if err != nil {
// panic("解析失败")
// }
//
// for i := 0; i < len(respNode.Get("rows").MustArray()); i++ {
// nodeJson := respNode.Get("rows").GetIndex(i).Get("cells")
// nodeName, _ := nodeJson.GetIndex(0).String()
// status, _ := nodeJson.GetIndex(1).String()
// role, _ := nodeJson.GetIndex(2).String()
// age, _ := nodeJson.GetIndex(3).String()
// version, _ := nodeJson.GetIndex(4).String()
// ip, _ := nodeJson.GetIndex(5).String()
// node := Node{
// NodeName: nodeName,
// ClusterName: cluster,
// State: status,
// Roles: role,
// Age: age,
// Version: version,
// Ip: ip,
// }
// nodes = append(nodes, node)
// }
//}
//total := len(nodes)
//page := &Page[Node]{}
//page.List = nodes
//pageNum, _ := c.GetQuery("pageNum")
//pageSize, _ := c.GetQuery("pageSize")
//num, _ := strconv.ParseInt(pageNum, 10, 64)
//size, _ := strconv.ParseInt(pageSize, 10, 64)
//data := Paginator(page, int64(total), num, size)
//Response(c, http.StatusOK, "success", data)
}

126
app/opensearch.go Normal file
View File

@ -0,0 +1,126 @@
package app
import (
"bytes"
"context"
"github.com/bitly/go-simplejson"
"github.com/opensearch-project/opensearch-go/v2/opensearchapi"
"strings"
)
func GetDeployFromOS(namespaceName string, deploymentName string, clusterName string) *simplejson.Json {
var content *strings.Reader
if clusterName == "" && namespaceName != "" && deploymentName != "" {
content = strings.NewReader(`{
"query":{
"bool":{
"must":[
{
"match_phrase":{
"metadata.name": "` + deploymentName + `"
}
},
{
"match_phrase":{
"metadata.namespace": "` + namespaceName + `"
}
}
]
}
},
"_source": ["metadata.annotations.resource.karmada.io/cached-from-cluster",
"metadata.name",
"metadata.namespace",
"spec",
"status"]
}`)
} else if deploymentName == "" && clusterName == "" && namespaceName != "" {
content = strings.NewReader(`{
"query":{
"match_phrase":{
"metadata.namespace": "` + namespaceName + `"
}
},
"_source": ["metadata.annotations.resource.karmada.io/cached-from-cluster",
"metadata.name",
"metadata.namespace",
"spec",
"status"]
}`)
} else if deploymentName == "" && clusterName != "" && namespaceName != "" {
content = strings.NewReader(`{
"query":{
"bool":{
"must":[
{
"match_phrase":{
"metadata.annotations.resource.karmada.io/cached-from-cluster": "` + clusterName + `"
}
},
{
"match_phrase":{
"metadata.namespace": "` + namespaceName + `"
}
}
]
}
},
"_source": ["metadata.annotations.resource.karmada.io/cached-from-cluster",
"metadata.name",
"metadata.namespace",
"spec",
"status"]
}`)
}
//从OpenSearch根据ns名称和deploy名称查询相关信息
searchReq := opensearchapi.SearchRequest{
Index: []string{"kubernetes-deployment"},
Body: content,
}
searchResp, _ := searchReq.Do(context.Background(), OpenSearchClient)
bufDeploy := new(bytes.Buffer)
bufDeploy.ReadFrom(searchResp.Body)
deployJson, _ := simplejson.NewJson([]byte(bufDeploy.String()))
return deployJson
}
func GetPodFromOS(namespaceName string, deploymentName string) *simplejson.Json {
var content *strings.Reader
content = strings.NewReader(`{
"query":{
"bool":{
"must":[
{
"match_phrase":{
"metadata.namespace": "` + namespaceName + `"
}
},
{
"match_phrase":{
"metadata.name": "` + deploymentName + `"
}
}
]
}
}
}
`)
//从OpenSearch根据ns名称和deploy名称查询相关信息
searchReq := opensearchapi.SearchRequest{
Index: []string{"kubernetes-pod"},
Body: content,
}
searchResp, _ := searchReq.Do(context.Background(), OpenSearchClient)
bufPod := new(bytes.Buffer)
bufPod.ReadFrom(searchResp.Body)
podJson, _ := simplejson.NewJson([]byte(bufPod.String()))
return podJson
}

121
app/overridePolicy.go Normal file
View File

@ -0,0 +1,121 @@
package app
import (
"context"
"github.com/gin-gonic/gin"
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"net/http"
)
type OverridePolicy struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
ResourceName string `json:"resource_name"`
Kind string `json:"kind"`
Labels map[string]string `json:"labels"`
ReplicaDivisionPreference string `json:"replica_division_preference"`
ReplicaSchedulingType string `json:"replica_scheduling_type"`
}
// CreateOverridePolicies 查询override策略列表
func CreateOverridePolicies(c *gin.Context) {
var policyRequest OverridePolicy
if err := c.BindJSON(&policyRequest); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", "")
return
}
policy := &policyv1alpha1.OverridePolicy{
ObjectMeta: v1.ObjectMeta{
Name: policyRequest.Name,
Namespace: policyRequest.Namespace,
},
Spec: policyv1alpha1.OverrideSpec{
ResourceSelectors: []policyv1alpha1.ResourceSelector{
{
APIVersion: "apps/v1",
Kind: policyRequest.Kind,
Name: policyRequest.ResourceName,
},
},
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
TargetCluster: nil,
Overriders: policyv1alpha1.Overriders{
Plaintext: []policyv1alpha1.PlaintextOverrider{
{
Path: "/metadata/namespace",
Operator: "replace",
Value: apiextensionsv1.JSON{Raw: []byte("default")},
},
},
},
},
},
},
}
overrideList, err := KarmadaClient.PolicyV1alpha1().OverridePolicies(policyRequest.Namespace).Create(context.TODO(), policy, v1.CreateOptions{})
if err != nil {
Response(c, http.StatusInternalServerError, "create policy failed", err)
return
}
Response(c, http.StatusOK, "success", overrideList)
}
// ListOverridePolicies 查询重写策略列表
func ListOverridePolicies(c *gin.Context) {
var overridePolicyRequest OverridePolicy
if err := c.BindJSON(&overridePolicyRequest); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", err)
return
}
overridePolicyList := make([]OverridePolicy, 0)
propagationList, _ := KarmadaClient.PolicyV1alpha1().PropagationPolicies(overridePolicyRequest.Namespace).List(context.TODO(), v1.ListOptions{})
for i := 0; i < len(propagationList.Items); i++ {
name := propagationList.Items[i].ObjectMeta.Name
namespace := propagationList.Items[i].ObjectMeta.Namespace
labels := &propagationList.Items[i].Spec.Placement.ClusterAffinity.LabelSelector.MatchLabels
resourceName := propagationList.Items[i].Spec.ResourceSelectors[0].Name
kind := propagationList.Items[i].Spec.ResourceSelectors[0].Kind
rdp := string(propagationList.Items[i].Spec.Placement.ReplicaScheduling.ReplicaDivisionPreference)
rst := string(propagationList.Items[i].Spec.Placement.ReplicaScheduling.ReplicaSchedulingType)
pp := OverridePolicy{
Name: name,
Namespace: namespace,
Labels: *labels,
ResourceName: resourceName,
Kind: kind,
ReplicaDivisionPreference: rdp,
ReplicaSchedulingType: rst,
}
overridePolicyList = append(overridePolicyList, pp)
}
Response(c, http.StatusOK, "success", nil)
}
// UpdateOverridePolicies 查询重写策略列表
func UpdateOverridePolicies(c *gin.Context) {
//todo
Response(c, http.StatusOK, "success", nil)
}
// DeleteOverridePolicies 查询重写策略列表
func DeleteOverridePolicies(c *gin.Context) {
//todo
Response(c, http.StatusOK, "success", nil)
}

71
app/overview.go Normal file
View File

@ -0,0 +1,71 @@
package app
import (
"context"
"github.com/gin-gonic/gin"
"github.com/golang/glog"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"net/http"
)
type Overview struct {
Domain int `json:"domain"`
Cluster int `json:"cluster"`
Pod int32 `json:"pod"`
}
func ResourceCount(c *gin.Context) {
overview := &Overview{}
//纳管资源域、纳管集群总计、容器创建数量 资源域
rows, _ := DB.Query("select count(*) domain from domain")
for rows.Next() {
err := rows.Scan(&overview.Domain)
if err != nil {
glog.Errorf("query failed!,error %v", err)
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
}
// 集群数量
clusters, err := KarmadaClient.ClusterV1alpha1().Clusters().List(context.TODO(), metav1.ListOptions{})
if err != nil {
glog.Info("failed to retrieve cluster(%s). error: %v", clusters, err)
Response(c, http.StatusBadRequest, "failed to retrieve cluster", err)
}
overview.Cluster = len(clusters.Items)
//容器数量
overview.Pod = countPod()
Response(c, http.StatusOK, "success", *overview)
}
func countPod() int32 {
var podSum int32
opts := metav1.ListOptions{
TypeMeta: metav1.TypeMeta{},
LabelSelector: "jcce=true",
FieldSelector: "",
Watch: false,
AllowWatchBookmarks: false,
ResourceVersion: "",
ResourceVersionMatch: "",
TimeoutSeconds: nil,
Limit: 0,
Continue: "",
}
nsResult, _ := ClientSet.CoreV1().Namespaces().List(context.TODO(), opts)
for _, ns := range nsResult.Items {
deploymentList, err := ClientSet.AppsV1().Deployments(ns.Name).List(context.TODO(), metav1.ListOptions{})
if err != nil {
panic("解析失败")
}
var totalAvailable int32
for _, deploy := range deploymentList.Items {
replicaAvailable := deploy.Status.AvailableReplicas
totalAvailable += replicaAvailable
}
podSum += totalAvailable
}
return podSum
}

61
app/page.go Normal file
View File

@ -0,0 +1,61 @@
package app
type Page[T any] struct {
// 当前页码
PageNum int64 `json:"pageNum"`
//每页显示条数
PageSize int64 `json:"pageSize"`
// 总数量
Total int64 `json:"total"`
// 总页数
TotalPage int64 `json:"totalPage"`
// 数据列表
List []T `json:"list"`
}
// Paginator 生成新的分页数据对象
func Paginator[T any](items *Page[T], total, pageNum, pageSize int64) *Page[T] {
var (
pageStart int64
pageEnd int64
)
if pageNum <= 0 {
pageNum = 1
}
if pageSize <= 0 {
pageSize = 10
}
//总页数
totalPages := total / pageSize
if (total % pageSize) > 0 {
totalPages += 1
}
if pageNum*pageSize < total {
pageEnd = pageNum * pageSize
pageStart = pageEnd - pageSize
} else {
pageEnd = total
pageStart = pageSize * (totalPages - 1)
}
if pageEnd > total {
pageEnd = total
}
if total <= 0 {
items.List = make([]T, 0)
} else {
items.List = items.List[pageStart:pageEnd]
}
return &Page[T]{
PageNum: pageNum,
PageSize: pageSize,
TotalPage: totalPages,
Total: total,
List: items.List,
}
}

28
app/pod.go Normal file
View File

@ -0,0 +1,28 @@
package app
import (
"github.com/gin-gonic/gin"
"net/http"
)
type Pod struct {
Name string `json:"name"`
ClusterName string `json:"cluster_name"`
DomainName string `json:"domain_name"`
Ready string `json:"ready"`
Status string `json:"status"`
Restarts int64 `json:"restarts"`
Age string `json:"age"`
IP string `json:"IP"`
Node string `json:"node"`
Namespace string `json:"namespace"`
ContainerImage string `json:"container_image"`
ContainerName string `json:"container_name"`
}
// ListPod 查询Pod列表
func ListPod(c *gin.Context) {
Response(c, http.StatusOK, "success", nil)
}

235
app/propagationPolicy.go Normal file
View File

@ -0,0 +1,235 @@
package app
import (
"context"
"fmt"
"github.com/bitly/go-simplejson"
"github.com/gin-gonic/gin"
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"net/http"
"strconv"
"strings"
)
type PropagationPolicy struct {
TemplateId string `json:"template_id"`
TemplateName string `json:"template_name"`
Name string `json:"name"`
Namespace string `json:"namespace"`
ResourceName string `json:"resource_name"`
CrossDomain bool `json:"cross_domain"`
Kind string `json:"kind"`
Labels map[string]string `json:"labels"`
LabelString *string `json:"label_string"`
ReplicaSchedulingType *string `json:"replica_scheduling_type"`
ReplicaDivisionPreference *string `json:"replica_division_preference"`
ClusterPreference *string `json:"cluster_preference"`
}
// CreatePropagationPolicies 创建集群分发策略实例
func CreatePropagationPolicies(c *gin.Context) {
var policyRequest PropagationPolicy
if err := c.BindJSON(&policyRequest); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", "")
return
}
var pp PropagationPolicy
//获取模板数据
sqlPolicyTemplate := "select template_id,template_name,labels,schedule_type,division_preference,cluster_preference from propagation_policy_template where template_id = ?"
err := DB.QueryRow(sqlPolicyTemplate, policyRequest.TemplateId).Scan(&pp.TemplateId, &pp.TemplateName, &pp.LabelString, &pp.ReplicaSchedulingType, &pp.ReplicaDivisionPreference, &pp.ClusterPreference)
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
labelsJson, err := simplejson.NewJson([]byte(*pp.LabelString))
labelsMapInterface, _ := labelsJson.Map()
labelsMap := make(map[string]string, len(labelsMapInterface))
for k, v := range labelsMapInterface {
labelsMap[k] = fmt.Sprint(v)
}
rst := policyv1alpha1.ReplicaSchedulingStrategy{}
//复制分配类型 否则为 Divided 根据下层策略分配
if *pp.ReplicaSchedulingType == "Duplicated" {
rst.ReplicaSchedulingType = policyv1alpha1.ReplicaSchedulingTypeDuplicated
} else if *pp.ReplicaDivisionPreference == "Aggregated" {
//划分-聚合 Aggregated 否则为 Weighted根据权重进行分配
rst.ReplicaSchedulingType = policyv1alpha1.ReplicaSchedulingTypeDivided
rst.ReplicaDivisionPreference = policyv1alpha1.ReplicaDivisionPreferenceAggregated
} else if *pp.ClusterPreference == "DynamicWeight" {
//划分-权重-动态 DynamicWeight 否则为静态
rst.ReplicaSchedulingType = policyv1alpha1.ReplicaSchedulingTypeDivided
rst.ReplicaDivisionPreference = policyv1alpha1.ReplicaDivisionPreferenceWeighted
rst.WeightPreference = &policyv1alpha1.ClusterPreferences{
DynamicWeight: policyv1alpha1.DynamicWeightByAvailableReplicas,
}
} else {
//划分-权重-静态
rst.ReplicaSchedulingType = policyv1alpha1.ReplicaSchedulingTypeDivided
rst.ReplicaDivisionPreference = policyv1alpha1.ReplicaDivisionPreferenceWeighted
}
policy := &policyv1alpha1.PropagationPolicy{
ObjectMeta: v1.ObjectMeta{
Name: policyRequest.Name,
Namespace: policyRequest.Namespace,
},
Spec: policyv1alpha1.PropagationSpec{
ResourceSelectors: []policyv1alpha1.ResourceSelector{
{
APIVersion: "apps/v1",
Kind: policyRequest.Kind,
Name: policyRequest.ResourceName,
},
},
Placement: policyv1alpha1.Placement{
ClusterAffinity: &policyv1alpha1.ClusterAffinity{
LabelSelector: &v1.LabelSelector{
MatchLabels: labelsMap,
},
},
ReplicaScheduling: &rst,
},
},
}
propagationList, err := KarmadaClient.PolicyV1alpha1().PropagationPolicies(policyRequest.Namespace).Create(context.TODO(), policy, v1.CreateOptions{})
if err != nil {
Response(c, http.StatusInternalServerError, "create policy failed", err)
return
}
//数据库记录policy实例
_, err = DB.Exec(`INSERT INTO joint_domain.propagation_policy(template_id, policy_name, namespace, resource_name) VALUES(?,?,?,?)`, policyRequest.TemplateId, policyRequest.Name, policyRequest.Namespace, policyRequest.ResourceName)
if err != nil {
Response(c, http.StatusInternalServerError, "insert db failed", err)
return
}
Response(c, http.StatusOK, "success", propagationList)
}
// ListPropagationPolicies 查询集群分发策略列表
func ListPropagationPolicies(c *gin.Context) {
labelKey, _ := c.GetQuery("label_key")
labelValue, _ := c.GetQuery("label_value")
policyName, _ := c.GetQuery("policy_name")
opts := v1.ListOptions{
TypeMeta: v1.TypeMeta{},
LabelSelector: labelKey + "=" + labelValue,
FieldSelector: "",
Watch: false,
AllowWatchBookmarks: false,
ResourceVersion: "",
ResourceVersionMatch: "",
TimeoutSeconds: nil,
Limit: 0,
Continue: "",
}
nsResult, _ := ClientSet.CoreV1().Namespaces().List(context.TODO(), opts)
proPolicyList := make([]PropagationPolicy, 0)
for i := 0; i < len(nsResult.Items); i++ {
propagationList, _ := KarmadaClient.PolicyV1alpha1().PropagationPolicies(nsResult.Items[i].Name).List(context.TODO(), v1.ListOptions{})
for i := 0; i < len(propagationList.Items); i++ {
name := propagationList.Items[i].ObjectMeta.Name
namespace := propagationList.Items[i].ObjectMeta.Namespace
labels := &propagationList.Items[i].Spec.Placement.ClusterAffinity.LabelSelector.MatchLabels
resourceName := propagationList.Items[i].Spec.ResourceSelectors[0].Name
kind := propagationList.Items[i].Spec.ResourceSelectors[0].Kind
rdp := string(propagationList.Items[i].Spec.Placement.ReplicaScheduling.ReplicaDivisionPreference)
rst := string(propagationList.Items[i].Spec.Placement.ReplicaScheduling.ReplicaSchedulingType)
//判断是否跨域
deployJson := GetDeployFromOS(namespace, resourceName, "")
hits, _ := deployJson.Get("hits").Get("hits").Array()
var domainSet []string
for i := 0; i < len(hits); i++ {
cluster, _ := deployJson.Get("hits").Get("hits").GetIndex(i).Get("_source").Get("metadata").Get("annotations").Get("resource.karmada.io/cached-from-cluster").String()
//获取域列表
rows, _ := DB.Query("select domain_name,domain_id,cluster_name from domain_cluster where cluster_name = ?", cluster)
for rows.Next() {
var domainName string
var clusterName string
var domainId int32
rows.Scan(&domainName, &domainId, &clusterName)
domainSet = append(domainSet, domainName)
}
}
domainSet = removeDuplicateArr(domainSet)
crossDomain := false
if len(domainSet) > 1 {
crossDomain = true
}
pp := PropagationPolicy{
Name: name,
CrossDomain: crossDomain,
Namespace: namespace,
Labels: *labels,
ResourceName: resourceName,
Kind: kind,
ReplicaDivisionPreference: &rdp,
ReplicaSchedulingType: &rst,
}
//获取分发实例对应的模板信息
sqlPolicy := "SELECT ppt.template_id, ppt.template_name FROM joint_domain.propagation_policy pp,joint_domain.propagation_policy_template ppt WHERE pp.template_id =ppt.template_id and pp.policy_name= ?"
err := DB.QueryRow(sqlPolicy, name).Scan(&pp.TemplateId, &pp.TemplateName)
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
//模糊查询
if strings.Contains(pp.Name, policyName) {
proPolicyList = append(proPolicyList, pp)
} else {
continue
}
}
}
total := len(proPolicyList)
page := &Page[PropagationPolicy]{}
page.List = proPolicyList
pageNum, _ := c.GetQuery("pageNum")
pageSize, _ := c.GetQuery("pageSize")
num, _ := strconv.ParseInt(pageNum, 10, 64)
size, _ := strconv.ParseInt(pageSize, 10, 64)
data := Paginator(page, int64(total), num, size)
Response(c, http.StatusOK, "success", data)
}
// UpdatePropagationPolicies 更新集群分发策略
func UpdatePropagationPolicies(c *gin.Context) {
Response(c, http.StatusOK, "success", nil)
}
// DeletePropagationPolicies 删除集群分发策略
func DeletePropagationPolicies(c *gin.Context) {
var policyRequest PropagationPolicy
if err := c.BindJSON(&policyRequest); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", "")
return
}
err := KarmadaClient.PolicyV1alpha1().PropagationPolicies(policyRequest.Namespace).Delete(context.TODO(), policyRequest.Name, v1.DeleteOptions{})
if err != nil {
Response(c, http.StatusInternalServerError, "delete policy failed", err)
return
}
Response(c, http.StatusOK, "success", nil)
}

View File

@ -0,0 +1,104 @@
package app
import (
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
type PropagationPolicyTemplate struct {
TemplateId int32 `json:"template_id"`
TemplateName string `json:"template_name"`
Labels *string `json:"labels"`
ScheduleType *string `json:"schedule_type"`
DivisionPreference *string `json:"division_preference"`
ClusterPreference *string `json:"cluster_preference"`
}
// CreatePropagationPolicyTemplate 创建策略模板
func CreatePropagationPolicyTemplate(c *gin.Context) {
var j PropagationPolicyTemplate
if err := c.BindJSON(&j); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", err)
return
}
_, err := DB.Exec(`INSERT propagation_policy_template(template_name, labels, schedule_type, division_preference, cluster_preference)VALUES (?,?,?,?,?)`, j.TemplateName, j.Labels, j.ScheduleType, j.DivisionPreference, j.ClusterPreference)
if err != nil {
Response(c, http.StatusBadRequest, "insert failed", err)
return
}
Response(c, http.StatusOK, "success", nil)
}
// DeletePropagationPolicyTemplate 删除策略模板
func DeletePropagationPolicyTemplate(c *gin.Context) {
var j PropagationPolicyTemplate
if err := c.BindJSON(&j); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", err)
return
}
_, err := DB.Exec(`DELETE FROM propagation_policy_template WHERE template_id = ?`, j.TemplateId)
if err != nil {
Response(c, http.StatusBadRequest, "insert failed", err)
return
}
Response(c, http.StatusOK, "success", nil)
}
// ListPropagationPolicyTemplate 查询分发策略模板
func ListPropagationPolicyTemplate(c *gin.Context) {
pptName, _ := c.GetQuery("template_name")
PptList := make([]PropagationPolicyTemplate, 0)
rows, err := DB.Query(`SELECT * FROM propagation_policy_template ppt where ppt.template_name like ?`, "%"+pptName+"%")
defer rows.Close()
if err != nil {
Response(c, http.StatusBadRequest, "query failed", err)
return
}
for rows.Next() {
var ppt PropagationPolicyTemplate
err := rows.Scan(&ppt.TemplateId, &ppt.TemplateName, &ppt.Labels, &ppt.ScheduleType, &ppt.DivisionPreference, &ppt.ClusterPreference)
if err != nil {
Response(c, http.StatusBadRequest, "query failed!", err)
return
}
PptList = append(PptList, ppt)
}
total := len(PptList)
page := &Page[PropagationPolicyTemplate]{}
page.List = PptList
pageNum, _ := c.GetQuery("pageNum")
pageSize, _ := c.GetQuery("pageSize")
num, _ := strconv.ParseInt(pageNum, 10, 64)
size, _ := strconv.ParseInt(pageSize, 10, 64)
data := Paginator(page, int64(total), num, size)
Response(c, http.StatusOK, "success", data)
//Response(c, http.StatusOK, "success", PptList)
}
// UpdatePropagationPolicyTemplate 更新策略模板
func UpdatePropagationPolicyTemplate(c *gin.Context) {
var j PropagationPolicyTemplate
if err := c.BindJSON(&j); err != nil {
Response(c, http.StatusBadRequest, "invalid request params.", err)
return
}
_, err := DB.Exec(`UPDATE propagation_policy_template SET template_name = ?, labels = ?, schedule_type = ?, division_preference = ?, cluster_preference = ? WHERE template_id = ?`, j.TemplateName, j.Labels, j.ScheduleType, j.DivisionPreference, j.ClusterPreference, j.TemplateId)
if err != nil {
Response(c, http.StatusBadRequest, "update failed", err)
return
}
Response(c, http.StatusOK, "success", nil)
}

15
app/response.go Normal file
View File

@ -0,0 +1,15 @@
package app
import (
"github.com/gin-gonic/gin"
"net/http"
)
func Response(c *gin.Context, code int, msg interface{}, data interface{}) {
c.JSON(http.StatusOK, map[string]interface{}{
"code": code,
"msg": msg,
"data": data,
})
return
}

147
app/router.go Normal file
View File

@ -0,0 +1,147 @@
package app
import (
"crypto/tls"
"database/sql"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
karmadaclientset "github.com/karmada-io/karmada/pkg/generated/clientset/versioned"
"github.com/karmada-io/karmada/pkg/karmadactl"
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
"github.com/opensearch-project/opensearch-go/v2"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
_ "jcc-schedule/docs"
kubeclient "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"net/http"
"os"
)
var DB *sql.DB
var KarmadaClient *karmadaclientset.Clientset
var KarmadaConfig karmadactl.KarmadaConfig
var ControlPlaneRestConfig *rest.Config
var ClientSet *kubeclient.Clientset
var OpenSearchClient *opensearch.Client
var ConfigNacos = GetNacosConfig()
func InitRouter() *gin.Engine {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
//mysql连接池
dsn := ConfigNacos.Mysql.Url
DB, _ = sql.Open("mysql", dsn)
DB.SetMaxOpenConns(int(ConfigNacos.Mysql.MaxOpenConn))
DB.SetMaxIdleConns(int(ConfigNacos.Mysql.MaxIdleConn))
//Karmada Client
dir, _ := os.Getwd()
KarmadaConfig = karmadactl.NewKarmadaConfig(clientcmd.NewDefaultPathOptions())
KarmadaConfig.GetClientConfig("", dir+"/karmadaConfig/karmada-host")
ControlPlaneRestConfig, _ = KarmadaConfig.GetRestConfig("", dir+"/karmadaConfig/karmada-host")
KarmadaClient = karmadaclientset.NewForConfigOrDie(ControlPlaneRestConfig)
ClientSet, _ = utils.NewClientSet(ControlPlaneRestConfig)
// 初始化OpenSearch客户端
OpenSearchClient, _ = opensearch.NewClient(opensearch.Config{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
Addresses: []string{ConfigNacos.OpenSearch.Url},
Username: ConfigNacos.OpenSearch.UserName,
Password: ConfigNacos.OpenSearch.PassWord,
})
//api分组
api := r.Group("/api")
v1 := api.Group("/v1")
{
//Label
labelType := v1.Group("labelType")
labelType.POST("/create", CreateLabelType)
labelType.POST("/delete", DeleteLabelType)
labelType.GET("/list", ListLabelType)
labelType.POST("/update", UpdateLabelType)
//Label
label := v1.Group("label")
label.POST("/create", CreateLabel)
label.POST("/delete", DeleteLabel)
label.GET("/list", ListLabel)
label.POST("/update", UpdateLabel)
//Namespace
namespace := v1.Group("namespace")
namespace.GET("/list", ListNamespace)
namespace.GET("/describe", DescribeNamespace)
namespace.POST("/create", CreateNamespace)
namespace.POST("/delete", DeleteNamespace)
namespace.POST("/update", UpdateNamespace)
//Domain
domain := v1.Group("domain")
domain.POST("/create", CreateDomain)
domain.GET("/describe", DescribeDomain)
domain.GET("/list", ListDomain)
domain.GET("/listByDeployment", ListByDeployment)
//Pod
pod := v1.Group("pod")
pod.GET("/list", ListPod)
//Cluster
cluster := v1.Group("cluster")
cluster.GET("/list", ListCluster)
cluster.GET("/listWithLabel", ListClusterWithLabel)
cluster.POST("/tag", TagCluster)
cluster.POST("/unTag", UnTagCluster)
cluster.POST("/join", Join)
cluster.DELETE("/unJoin", UnJoin)
cluster.GET("/listByDomain", ListByDomain)
//Node
node := v1.Group("node")
node.GET("/list", ListNode)
//Deployment
deployment := v1.Group("deployment")
deployment.POST("/create", CreateDeployment)
deployment.GET("/list", ListDeployment)
deployment.GET("/describe", DescribeDeployment)
deployment.GET("/listGlobal", ListClusterDeployment)
//PropagationPolicyTemplate
propagationPolicyTemplate := v1.Group("propagationPolicyTemplate")
propagationPolicyTemplate.POST("/create", CreatePropagationPolicyTemplate)
propagationPolicyTemplate.GET("/list", ListPropagationPolicyTemplate)
propagationPolicyTemplate.POST("/delete", DeletePropagationPolicyTemplate)
propagationPolicyTemplate.POST("/update", UpdatePropagationPolicyTemplate)
//PropagationPolicies
propagationPolicy := v1.Group("propagationPolicy")
propagationPolicy.POST("/create", CreatePropagationPolicies)
propagationPolicy.GET("/list", ListPropagationPolicies)
propagationPolicy.POST("/delete", DeletePropagationPolicies)
propagationPolicy.POST("/update", UpdatePropagationPolicies)
//OverridePolicies
overridePolicy := v1.Group("overridePolicy")
overridePolicy.GET("/list", ListOverridePolicies)
overridePolicy.POST("/create", CreateOverridePolicies)
overridePolicy.POST("/delete", DeleteOverridePolicies)
overridePolicy.POST("/update", UpdateOverridePolicies)
//Overview
overview := v1.Group("resource")
overview.GET("/count", ResourceCount)
}
return r
}

View File

@ -0,0 +1,44 @@
kind: Deployment
apiVersion: apps/v1
metadata:
name: jcce-schedule-deployment
namespace: jcce-system
labels:
k8s-app: jcce-schedule
spec:
replicas: 1
selector:
matchLabels:
k8s-app: jcce-schedule
template:
metadata:
name: jcce-schedule
labels:
k8s-app: jcce-schedule
spec:
imagePullSecrets:
- name: SECRET_NAME
containers:
- name: jcce-schedule
image: IMAGE_NAME
resources: {}
imagePullPolicy: Always
securityContext:
privileged: false
procMount: Default
ports:
- containerPort: 80
volumeMounts: []
volumes: []
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
securityContext: {}
schedulerName: default-scheduler
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
revisionHistoryLimit: 10
progressDeadlineSeconds: 600

View File

@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
namespace: jcce-system
name: jcce-schedule-service
labels:
k8s-service: jcce-schedule
spec:
selector:
k8s-app: jcce-schedule
ports:
- name: web
protocol: TCP
port: 8082
targetPort: 8082
type: ClusterIP

75
docs/docs.go Normal file
View File

@ -0,0 +1,75 @@
// Package docs GENERATED BY SWAG; DO NOT EDIT
// This file was generated by swaggo/swag
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/api/v1/cluster/list": {
"get": {
"description": "查询集群列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"cluster"
],
"summary": "查询集群列表",
"parameters": [
{
"type": "integer",
"description": "页码",
"name": "pageNum",
"in": "query",
"required": true
},
{
"type": "integer",
"description": "每页数量",
"name": "pageSize",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
},
"500": {
"description": "Internal Server Error"
}
}
}
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "1.0",
Host: "",
BasePath: "",
Schemes: []string{},
Title: "jcc调度中心",
Description: "jcc",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

50
docs/swagger.json Normal file
View File

@ -0,0 +1,50 @@
{
"swagger": "2.0",
"info": {
"description": "jcc",
"title": "jcc调度中心",
"contact": {},
"version": "1.0"
},
"paths": {
"/api/v1/cluster/list": {
"get": {
"description": "查询集群列表",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"cluster"
],
"summary": "查询集群列表",
"parameters": [
{
"type": "integer",
"description": "页码",
"name": "pageNum",
"in": "query",
"required": true
},
{
"type": "integer",
"description": "每页数量",
"name": "pageSize",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
},
"500": {
"description": "Internal Server Error"
}
}
}
}
}
}

33
docs/swagger.yaml Normal file
View File

@ -0,0 +1,33 @@
info:
contact: {}
description: jcc
title: jcc调度中心
version: "1.0"
paths:
/api/v1/cluster/list:
get:
consumes:
- application/json
description: 查询集群列表
parameters:
- description: 页码
in: query
name: pageNum
required: true
type: integer
- description: 每页数量
in: query
name: pageSize
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
"500":
description: Internal Server Error
summary: 查询集群列表
tags:
- cluster
swagger: "2.0"

143
go.mod Normal file
View File

@ -0,0 +1,143 @@
module jcc-schedule
go 1.18
require (
github.com/bitly/go-simplejson v0.5.0
github.com/gin-gonic/gin v1.8.1
github.com/go-sql-driver/mysql v1.6.0
github.com/golang/glog v1.0.0
github.com/karmada-io/karmada v1.3.0
github.com/nacos-group/nacos-sdk-go v1.1.2
github.com/opensearch-project/opensearch-go/v2 v2.1.0
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
github.com/swaggo/gin-swagger v1.5.3
github.com/swaggo/swag v1.8.5
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.25.0
k8s.io/apiextensions-apiserver v0.25.0
k8s.io/apimachinery v0.25.0
k8s.io/client-go v0.25.0
)
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/camelcase v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/fvbommel/sortorder v1.0.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.10.0 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.1.2 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/olekukonko/tablewriter v0.0.4 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/russross/blackfriday v1.5.2 // indirect
github.com/spf13/cobra v1.4.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.0 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.19.1 // indirect
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
golang.org/x/tools v0.1.12 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
google.golang.org/grpc v1.47.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.3 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/square/go-jose.v2 v2.2.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/apiserver v0.25.0 // indirect
k8s.io/cli-runtime v0.24.2 // indirect
k8s.io/cluster-bootstrap v0.24.2 // indirect
k8s.io/component-base v0.25.0 // indirect
k8s.io/klog/v2 v2.70.1 // indirect
k8s.io/kube-aggregator v0.24.2 // indirect
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
k8s.io/kubectl v0.24.2 // indirect
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
sigs.k8s.io/cluster-api v1.0.1 // indirect
sigs.k8s.io/controller-runtime v0.12.2 // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/kustomize/api v0.11.4 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.6 // indirect
sigs.k8s.io/mcs-api v0.1.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

1524
go.sum Normal file

File diff suppressed because it is too large Load Diff

14
main.go Normal file
View File

@ -0,0 +1,14 @@
package main
import (
"jcc-schedule/app"
)
// @title jcc调度中心
// @version 1.0
// @description jcc
func main() {
router := app.InitRouter()
_ = router.Run(":8082")
}