diff --git a/Makefile b/Makefile index 4475113..1b2efb9 100644 --- a/Makefile +++ b/Makefile @@ -13,4 +13,8 @@ install: docker kubectl apply -f install.yaml uninstall: - kubectl delete -f install.yaml \ No newline at end of file + kubectl delete -f install.yaml + +restart: docker + kubectl delete -f install.yaml + kubectl apply -f install.yaml diff --git a/go.mod b/go.mod index 807c815..c36b671 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,14 @@ module k8s-manager go 1.19 require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-gonic/gin v1.8.2 + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/swag v0.19.14 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 github.com/sirupsen/logrus v1.9.0 gorm.io/driver/mysql v1.4.6 @@ -11,17 +18,9 @@ require ( k8s.io/api v0.26.1 k8s.io/apimachinery v0.26.1 k8s.io/client-go v0.26.1 - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect - github.com/gin-contrib/sse v0.1.0 // 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.20.0 // indirect - github.com/go-openapi/swag v0.19.14 // indirect ) require ( - github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.11.2 // indirect @@ -44,6 +43,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/thoas/go-funk v0.9.3 // indirect github.com/ugorji/go/codec v1.2.9 // indirect golang.org/x/crypto v0.6.0 // indirect golang.org/x/net v0.6.0 // indirect diff --git a/go.sum b/go.sum index ab36c39..8c48603 100644 --- a/go.sum +++ b/go.sum @@ -194,6 +194,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw= +github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q= github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/install.yaml b/install.yaml index 02d28b6..54c002d 100644 --- a/install.yaml +++ b/install.yaml @@ -20,6 +20,13 @@ spec: image: docker.educg.net/k8s/manager:dev ports: - containerPort: 8080 + env: + - name: DB_HOST + value: 192.168.131.191 + - name: DB_USER + value: app + - name: DB_PASS + value: 1PQlNf9*9Ulo --- apiVersion: v1 kind: Service @@ -35,4 +42,4 @@ spec: protocol: TCP nodePort: 30001 selector: - app: k8s-manager-deployment \ No newline at end of file + app: k8s-manager \ No newline at end of file diff --git a/main.go b/main.go index ebd4050..b5158ac 100644 --- a/main.go +++ b/main.go @@ -14,7 +14,8 @@ import ( var f embed.FS func main() { - go m.ListPods() + m.InitClient() + r := gin.Default() r.SetFuncMap(template.FuncMap{ @@ -35,6 +36,10 @@ func main() { r.POST("/app", m.JWTAuthMiddleware, m.CreateUpdateDeleteApp) r.PUT("/app", m.JWTAuthMiddleware, m.CreateUpdateDeleteApp) r.DELETE("/app", m.JWTAuthMiddleware, m.CreateUpdateDeleteApp) + + r.POST("/dep", m.JWTAuthMiddleware, m.CreateDeleteDeployment) + r.DELETE("/dep", m.JWTAuthMiddleware, m.CreateDeleteDeployment) + r.GET("/login/:username", Test) err := r.Run() if err != nil { diff --git a/src/app_model.go b/src/app_model.go index 9ee7b40..3b7cb4d 100644 --- a/src/app_model.go +++ b/src/app_model.go @@ -28,6 +28,12 @@ func GetApps(username string) []AppModel { return ans } +func (a *AppModel) Fill() error { + db := DB() + res := db.First(a, a.Id) + return res.Error +} + func (a *AppModel) Update() error { db := DB() db.Save(a) @@ -59,6 +65,9 @@ func (a *AppModel) ContainerName() string { } func (a *AppModel) ServiceName() string { + if strings.TrimSpace(a.SvcName) != "" { + return a.SvcName + } return fmt.Sprintf("%s-service", a.NamePrefix()) } diff --git a/src/app_viewmodel.go b/src/app_viewmodel.go index 6e6e392..6d3d172 100644 --- a/src/app_viewmodel.go +++ b/src/app_viewmodel.go @@ -3,28 +3,71 @@ package k8s_manager import ( "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" + "github.com/thoas/go-funk" + v1 "k8s.io/api/apps/v1" "net/http" + "strings" ) type AppViewModel struct { + *AppModel + Status string +} + +type AppsViewModel struct { Username string - Apps []AppModel + Apps []AppViewModel +} + +func statusString(status *v1.DeploymentStatus, err error) string { + if err != nil { + if strings.Contains(err.Error(), "not found") { + return "Stopped" + } + return err.Error() + } else { + if status.AvailableReplicas > 0 { + return "Running" + } else { + return "Error" + } + } + } func AppPage(c *gin.Context) { username := c.MustGet("username").(string) - vm := &AppViewModel{ + appsvm := funk.Map(GetApps(username), func(a AppModel) AppViewModel { return AppViewModel{AppModel: &a} }).([]AppViewModel) + for i := range appsvm { + a := &appsvm[i] + d, e := K.GetAppStatus(a.AppModel) + a.Status = statusString(d, e) + } + vm := &AppsViewModel{ Username: username, - Apps: GetApps(username), + Apps: appsvm, } c.HTML(http.StatusOK, "app_view.html", vm) } +func AppMiddleware(c *gin.Context) { + app := &AppModel{} + err := c.ShouldBindJSON(&app) + if err == nil { + username := c.MustGet("username") + if app.UserName != username { + c.JSON(http.StatusUnauthorized, gin.H{"msg": "user error."}) + c.Abort() + } + c.Set("app", app) + } +} + func CreateUpdateDeleteApp(c *gin.Context) { app := &AppModel{} err := c.BindJSON(app) if err != nil { - log.WithError(err).Error("AddApp") + log.WithError(err).Error("CreateUpdateDeleteApp") c.JSON(http.StatusBadRequest, gin.H{"msg": "Cannot get data."}) } else { username := c.MustGet("username").(string) diff --git a/src/deployment_manager.go b/src/deployment_manager.go new file mode 100644 index 0000000..f67ccf8 --- /dev/null +++ b/src/deployment_manager.go @@ -0,0 +1,51 @@ +package k8s_manager + +import ( + "github.com/gin-gonic/gin" + "k8s.io/api/apps/v1" + "net/http" + "strconv" + "strings" +) + +var K *K8sClient = nil + +func InitClient() { + K = CreateClient() +} + +type DeployStatusObject struct { + E string `json:"e"` + S *v1.DeploymentStatus `json:"s"` +} + +func CreateDeleteDeployment(c *gin.Context) { + tokenUsername := c.MustGet("username").(string) + id := c.Query("id") + if strings.TrimSpace(id) == "" { + c.JSON(http.StatusBadRequest, gin.H{"msg": "no app id"}) + return + } + appId, err := strconv.Atoi(id) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"msg": "wrong id"}) + return + } + app := AppModel{Id: appId} + if app.Fill() != nil { + c.JSON(http.StatusBadRequest, gin.H{"msg": "wrong id"}) + return + } + if app.UserName != tokenUsername { + c.JSON(http.StatusUnauthorized, gin.H{"msg": "user error."}) + return + } + if c.Request.Method == http.MethodPost { + K.CreateApp(&app) + } else if c.Request.Method == http.MethodDelete { + K.DeleteApp(&app) + } else { + c.JSON(http.StatusMethodNotAllowed, gin.H{"msg": "unsupported method"}) + } + c.JSON(http.StatusOK, gin.H{"msg": "ok"}) +} diff --git a/src/k8s_client.go b/src/k8s_client.go index f293c4c..be58359 100644 --- a/src/k8s_client.go +++ b/src/k8s_client.go @@ -3,8 +3,9 @@ package k8s_manager import ( "context" "fmt" + "github.com/goccy/go-json" log "github.com/sirupsen/logrus" - "k8s.io/api/apps/v1beta1" + appv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -16,7 +17,7 @@ type K8sClient struct { Namespace string } -func InitClient() *K8sClient { +func CreateClient() *K8sClient { var err error config, err := rest.InClusterConfig() if err != nil { @@ -40,17 +41,22 @@ func (c *K8sClient) ListPods() { } } -func (c *K8sClient) CreateApp(app *AppModel) { +func (c *K8sClient) GetAppStatus(app *AppModel) (*appv1.DeploymentStatus, error) { + get, err := c.AppsV1().Deployments(c.Namespace).Get(context.Background(), app.DeploymentName(), metav1.GetOptions{}) + // log.WithError(err).WithField("dep", get).WithField("name", app.DeploymentName()).Info("GetAppStatus") + return &get.Status, err +} +func (c *K8sClient) CreateApp(app *AppModel) { var rep int32 = 1 labelKey, labelValue := app.GetLabel() labels := map[string]string{labelKey: labelValue} - deployment := &v1beta1.Deployment{ + deployment := &appv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: app.DeploymentName(), Labels: labels, }, - Spec: v1beta1.DeploymentSpec{ + Spec: appv1.DeploymentSpec{ Replicas: &rep, Selector: &metav1.LabelSelector{ MatchLabels: labels, @@ -71,8 +77,10 @@ func (c *K8sClient) CreateApp(app *AppModel) { }, }, } - deployment, err := c.AppsV1beta1().Deployments(c.Namespace).Create(context.Background(), deployment, metav1.CreateOptions{}) + deployment, err := c.AppsV1().Deployments(c.Namespace).Create(context.Background(), deployment, metav1.CreateOptions{}) if err != nil { + d, _ := json.Marshal(deployment) + fmt.Println(string(d)) log.WithError(err).Error("Create Deployment error.") } @@ -81,8 +89,9 @@ func (c *K8sClient) CreateApp(app *AppModel) { Name: app.ServiceName(), }, Spec: v1.ServiceSpec{ - Type: v1.ServiceTypeClusterIP, - Selector: labels, + Type: v1.ServiceTypeClusterIP, + ClusterIP: "None", + Selector: labels, }, } @@ -91,3 +100,11 @@ func (c *K8sClient) CreateApp(app *AppModel) { log.WithError(err).Error("Create Service ") } } + +func (c *K8sClient) DeleteApp(app *AppModel) { + err := c.AppsV1().Deployments(c.Namespace).Delete(context.Background(), app.DeploymentName(), metav1.DeleteOptions{}) + if err != nil { + log.WithError(err).Error("Delete error ", app.DeploymentName()) + return + } +} diff --git a/template/app_view.html b/template/app_view.html index 7639789..e9acd07 100644 --- a/template/app_view.html +++ b/template/app_view.html @@ -33,6 +33,7 @@ 镜像名 启动命令 环境变量 + 状态 操作 @@ -46,8 +47,11 @@ {{ $v.Image }} {{ $v.Command }} {{ $v.Env }} + {{ $v.Status }}
+ +
@@ -202,5 +206,17 @@ }) } + function startApp(id) { + axios.post("dep?id=" + id).then(e=>{ + console.log(e.data) + }) + } + + function stopApp(id) { + axios.delete("dep?id=" + id).then(e=>{ + console.log(e.data) + }) + } + \ No newline at end of file