diff --git a/go.mod b/go.mod index 22a2953..f76c4b5 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,9 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/nats-io/nats.go v1.36.0 // indirect + github.com/nats-io/nkeys v0.4.7 // indirect + github.com/nats-io/nuid v1.0.1 // indirect github.com/nyaruka/phonenumbers v1.0.55 // indirect github.com/thoas/go-funk v0.9.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -119,7 +122,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/compress v1.17.2 // indirect github.com/klauspost/pgzip v1.2.5 // indirect github.com/labstack/gommon v0.4.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect diff --git a/go.sum b/go.sum index 7f6857a..a33c8ab 100644 --- a/go.sum +++ b/go.sum @@ -388,6 +388,8 @@ github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= @@ -480,6 +482,12 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/nats.go v1.36.0 h1:suEUPuWzTSse/XhESwqLxXGuj8vGRuPRoG7MoRN/qyU= +github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= +github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= +github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nyaruka/phonenumbers v1.0.55 h1:bj0nTO88Y68KeUQ/n3Lo2KgK7lM1hF7L9NFuwcCl3yg= diff --git a/pkg/api/server/handlers_api.go b/pkg/api/server/handlers_api.go index 22be8cc..3e45dbb 100644 --- a/pkg/api/server/handlers_api.go +++ b/pkg/api/server/handlers_api.go @@ -40,8 +40,9 @@ func (h *handlers) getAppConfig(ctx *fiber.Ctx) error { "message": fmt.Sprintf("Application not found"), }) } + username := ctx.Locals("username").(string) - path := getAppPath(app) + path := getAppPath(username, app) appCfgPath := filepath.Join(path, constants.AppCfgFileName) data, err := os.ReadFile(appCfgPath) if err != nil { @@ -57,7 +58,7 @@ func (h *handlers) getAppConfig(ctx *fiber.Ctx) error { //var appcfg application.AppConfiguration //err = yaml.Unmarshal(data, &appcfg) - appcfg, err := utils.GetAppConfig(data) + appcfg, err := utils.GetAppConfig(username, data) if err != nil { klog.Error("parse app cfg error, ", err) klog.Error(string(data)) @@ -74,6 +75,7 @@ func (h *handlers) getAppConfig(ctx *fiber.Ctx) error { } func (h *handlers) updateAppConfig(ctx *fiber.Ctx) error { + app := ctx.Query("app") if app == "" { return ctx.JSON(fiber.Map{ @@ -81,8 +83,9 @@ func (h *handlers) updateAppConfig(ctx *fiber.Ctx) error { "message": fmt.Sprintf("Application not found"), }) } + username := ctx.Locals("username").(string) - path := getAppPath(app) + path := getAppPath(username, app) appCfgPath := filepath.Join(path, constants.AppCfgFileName) var appcfg oachecker.AppConfiguration @@ -122,7 +125,7 @@ func (h *handlers) updateAppConfig(ctx *fiber.Ctx) error { "message": fmt.Sprintf("Save OlaresManifest.yaml error: %v", err), }) } - err = command.CheckCfg().WithDir(BaseDir).Run(ctx.Context(), app) + err = command.CheckCfg().WithDir(BaseDir).Run(ctx.Context(), username, app) if err != nil { klog.Error("check app cfg error, ", err) return ctx.JSON(fiber.Map{ @@ -138,58 +141,58 @@ func (h *handlers) updateAppConfig(ctx *fiber.Ctx) error { } -func (h *handlers) listMyContainers(ctx *fiber.Ctx) error { - sql := `select a.*, b.pod_selector, b.app_id, b.container_name, c.app_name - from dev_containers a - left join dev_app_containers b on a.id = b.container_id - left join dev_apps c on b.app_id = c.id - order by a.create_time desc` - list := make([]*model.DevContainerInfo, 0) - - err := h.db.DB.Raw(sql).Scan(&list).Error - if err != nil { - klog.Error("exec sql error, ", err, ", ", sql) - return ctx.JSON(fiber.Map{ - "code": http.StatusBadRequest, - "message": fmt.Sprintf("Exec sql failed: %v", err), - }) - } - - unbind := ctx.Query("unbind") == "true" - ret := make([]*model.DevContainerInfo, 0, len(list)) - - for i, c := range list { - ret = append(ret, list[i]) - if c.AppID != nil { - - // filter binding dev container - if unbind { - ret = append(ret[:i], ret[i+1:]...) - continue - } - // container is bind into an app, check the container running status - // ignore error, cause state is not a critical message - state, devPort, _ := container.GetContainerStatus(ctx.Context(), h.kubeConfig, c) - c.State = &state - - port := "/proxy/" + devPort + "/" - c.DevPath = &port - appcfg, err := readAppInfo(filepath.Join(BaseDir, *c.AppName, constants.AppCfgFileName)) - if err != nil { - klog.Error("readCfgFromFile error, ", err) - continue - } - - list[i].Icon = &appcfg.Metadata.Icon - } - } - - return ctx.JSON(fiber.Map{ - "code": http.StatusOK, - "data": ret, - }) - -} +//func (h *handlers) listMyContainers(ctx *fiber.Ctx) error { +// sql := `select a.*, b.pod_selector, b.app_id, b.container_name, c.app_name +// from dev_containers a +// left join dev_app_containers b on a.id = b.container_id +// left join dev_apps c on b.app_id = c.id +// order by a.create_time desc` +// list := make([]*model.DevContainerInfo, 0) +// +// err := h.db.DB.Raw(sql).Scan(&list).Error +// if err != nil { +// klog.Error("exec sql error, ", err, ", ", sql) +// return ctx.JSON(fiber.Map{ +// "code": http.StatusBadRequest, +// "message": fmt.Sprintf("Exec sql failed: %v", err), +// }) +// } +// +// unbind := ctx.Query("unbind") == "true" +// ret := make([]*model.DevContainerInfo, 0, len(list)) +// +// for i, c := range list { +// ret = append(ret, list[i]) +// if c.AppID != nil { +// +// // filter binding dev container +// if unbind { +// ret = append(ret[:i], ret[i+1:]...) +// continue +// } +// // container is bind into an app, check the container running status +// // ignore error, cause state is not a critical message +// state, devPort, _ := container.GetContainerStatus(ctx.Context(), h.kubeConfig, c) +// c.State = &state +// +// port := "/proxy/" + devPort + "/" +// c.DevPath = &port +// appcfg, err := readAppInfo(filepath.Join(BaseDir, *c.AppName, constants.AppCfgFileName)) +// if err != nil { +// klog.Error("readCfgFromFile error, ", err) +// continue +// } +// +// list[i].Icon = &appcfg.Metadata.Icon +// } +// } +// +// return ctx.JSON(fiber.Map{ +// "code": http.StatusOK, +// "data": ret, +// }) +// +//} func (h *handlers) bindContainer(ctx *fiber.Ctx) error { var postData struct { @@ -354,9 +357,10 @@ func (h *handlers) listAppContainersInChart(ctx *fiber.Ctx) error { "message": fmt.Sprintf("Application Not Found"), }) } + username := ctx.Locals("username").(string) appName := fmt.Sprintf("%s-dev", app) - testNamespace := fmt.Sprintf("%s-%s", appName, constants.Owner) + testNamespace := fmt.Sprintf("%s-%s", appName, username) // mock vals values := make(map[string]interface{}) @@ -408,7 +412,7 @@ func (h *handlers) listAppContainersInChart(ctx *fiber.Ctx) error { values["gpu"] = "nvidia" - path := getAppPath(app) + path := getAppPath(username, app) appCfgPath := filepath.Join(path, constants.AppCfgFileName) data, err := os.ReadFile(appCfgPath) if err != nil { @@ -419,7 +423,7 @@ func (h *handlers) listAppContainersInChart(ctx *fiber.Ctx) error { }) } - appcfg, err := utils.GetAppConfig(data) + appcfg, err := utils.GetAppConfig(username, data) if err != nil { klog.Error("parse app cfg error, ", err) @@ -436,7 +440,7 @@ func (h *handlers) listAppContainersInChart(ctx *fiber.Ctx) error { } values["domain"] = entries - manifest, err := helm.DryRun(ctx.Context(), h.kubeConfig, testNamespace, appName, getAppPath(app), values) + manifest, err := helm.DryRun(ctx.Context(), h.kubeConfig, testNamespace, appName, getAppPath(username, app), values) if err != nil { return ctx.JSON(fiber.Map{ "code": http.StatusBadRequest, @@ -475,7 +479,7 @@ func (h *handlers) listAppContainersInChart(ctx *fiber.Ctx) error { containers[i].AppID = pointer.Int(int(da.ID)) if container.IsSysAppDevImage(containers[i].Image) { containers[i].DevPath = pointer.String("/proxy/3000/") - userspace := "user-space-" + constants.Owner + userspace := "os-framework" pods, err := client.CoreV1().Pods(userspace).List(ctx.Context(), metav1.ListOptions{ LabelSelector: containers[i].PodSelector, }) @@ -791,10 +795,10 @@ func (h *handlers) updateDevContainer(ctx *fiber.Ctx) error { }) } -func GetAppContainersInChart(app string) ([]*helm.ContainerInfo, error) { +func GetAppContainersInChart(owner, app string) ([]*helm.ContainerInfo, error) { appName := fmt.Sprintf("%s-dev", app) - testNamespace := fmt.Sprintf("%s-%s", appName, constants.Owner) + testNamespace := fmt.Sprintf("%s-%s", appName, owner) // mock vals values := make(map[string]interface{}) @@ -846,7 +850,7 @@ func GetAppContainersInChart(app string) ([]*helm.ContainerInfo, error) { values["gpu"] = "nvidia" - path := getAppPath(app) + path := getAppPath(owner, app) appCfgPath := filepath.Join(path, constants.AppCfgFileName) data, err := os.ReadFile(appCfgPath) if err != nil { @@ -854,7 +858,7 @@ func GetAppContainersInChart(app string) ([]*helm.ContainerInfo, error) { return nil, err } - appcfg, err := utils.GetAppConfig(data) + appcfg, err := utils.GetAppConfig(owner, data) if err != nil { klog.Error("parse app cfg error, ", err) @@ -872,7 +876,7 @@ func GetAppContainersInChart(app string) ([]*helm.ContainerInfo, error) { return nil, err } - manifest, err := helm.DryRun(context.TODO(), kubeConfig, testNamespace, appName, getAppPath(app), values) + manifest, err := helm.DryRun(context.TODO(), kubeConfig, testNamespace, appName, getAppPath(owner, app), values) if err != nil { return nil, err } @@ -901,7 +905,7 @@ func GetAppContainersInChart(app string) ([]*helm.ContainerInfo, error) { containers[i].AppID = pointer.Int(int(da.ID)) if container.IsSysAppDevImage(containers[i].Image) { containers[i].DevPath = pointer.String("/proxy/3000/") - userspace := "user-space-" + constants.Owner + userspace := "os-framework" pods, err := client.CoreV1().Pods(userspace).List(context.TODO(), metav1.ListOptions{ LabelSelector: containers[i].PodSelector, }) diff --git a/pkg/api/server/handlers_cmd.go b/pkg/api/server/handlers_cmd.go index 21d7da4..bcb1413 100644 --- a/pkg/api/server/handlers_cmd.go +++ b/pkg/api/server/handlers_cmd.go @@ -17,7 +17,6 @@ import ( "strings" "time" - "github.com/beclab/devbox/pkg/constants" "github.com/beclab/devbox/pkg/development/command" "github.com/beclab/devbox/pkg/development/container" "github.com/beclab/devbox/pkg/development/helm" @@ -41,6 +40,9 @@ import ( var regxPattern = "^[a-zA-Z][a-zA-Z0-9 ._-]{0,30}$" func (h *handlers) createDevApp(ctx *fiber.Ctx) error { + + username := ctx.Locals("username").(string) + var config command.CreateConfig err := ctx.BodyParser(&config) if err != nil { @@ -57,7 +59,7 @@ func (h *handlers) createDevApp(ctx *fiber.Ctx) error { } // create app via command cli - err = command.CreateApp().WithDir(BaseDir).Run(ctx.Context(), &config) + err = command.CreateApp().WithDir(BaseDir).Run(ctx.Context(), &config, username) if err != nil { klog.Error("create app chart error, ", err, ", ", config) return ctx.JSON(fiber.Map{ @@ -66,7 +68,7 @@ func (h *handlers) createDevApp(ctx *fiber.Ctx) error { }) } - err = command.CheckCfg().WithDir(BaseDir).Run(ctx.Context(), config.Name) + err = command.CheckCfg().WithDir(BaseDir).Run(ctx.Context(), username, config.Name) if err != nil { e := os.RemoveAll(filepath.Join(BaseDir, config.Name)) if e != nil { @@ -84,6 +86,7 @@ func (h *handlers) createDevApp(ctx *fiber.Ctx) error { AppName: config.Name, DevEnv: config.DevEnv, AppType: db.CommunityApp, + Owner: username, } appId, err := InsertDevApp(&appData) if err != nil { @@ -106,8 +109,9 @@ func (h *handlers) createDevApp(ctx *fiber.Ctx) error { } func (h *handlers) listDevApps(ctx *fiber.Ctx) error { + username := ctx.Locals("username").(string) list := make([]*model.DevApp, 0) - err := h.db.DB.Order("update_time desc").Find(&list).Error + err := h.db.DB.Where("owner=?", username).Order("update_time desc").Find(&list).Error if err != nil { klog.Error("exec sql error, ", err) return ctx.JSON(fiber.Map{ @@ -138,6 +142,7 @@ func (h *handlers) listDevApps(ctx *fiber.Ctx) error { } func (h *handlers) getDevApp(ctx *fiber.Ctx) error { + username := ctx.Locals("username").(string) appName := ctx.Params("name") if len(appName) == 0 { return ctx.JSON(fiber.Map{ @@ -147,7 +152,7 @@ func (h *handlers) getDevApp(ctx *fiber.Ctx) error { } var app *model.DevApp - err := h.db.DB.First(&app).Error + err := h.db.DB.Where("owner = ?", username).First(&app).Error if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { klog.Error("exec sql error, ") return ctx.JSON(fiber.Map{ @@ -183,6 +188,7 @@ func (h *handlers) getDevApp(ctx *fiber.Ctx) error { } func (h *handlers) updateDevAppRepo(ctx *fiber.Ctx) error { + username := ctx.Locals("username").(string) app := make(map[string]string) err := ctx.BodyParser(&app) if err != nil { @@ -202,7 +208,7 @@ func (h *handlers) updateDevAppRepo(ctx *fiber.Ctx) error { }) } - _, err = command.UpdateRepo().WithDir(BaseDir).Run(ctx.Context(), name, false) + _, err = command.UpdateRepo().WithDir(BaseDir).Run(ctx.Context(), username, name, false) if err != nil { klog.Error("command upgraderepo error, ", err, ", ", name) return ctx.JSON(fiber.Map{ @@ -218,6 +224,7 @@ func (h *handlers) updateDevAppRepo(ctx *fiber.Ctx) error { } func (h *handlers) installDevApp(ctx *fiber.Ctx) error { + username := ctx.Locals("username").(string) var err error app := make(map[string]string) err = ctx.BodyParser(&app) @@ -229,14 +236,8 @@ func (h *handlers) installDevApp(ctx *fiber.Ctx) error { }) } - token := ctx.Cookies("auth_token") - if token == "" { - klog.Error("token is empty") - return ctx.JSON(fiber.Map{ - "code": http.StatusUnauthorized, - "message": fmt.Sprintf("Auth token not found"), - }) - } + token := ctx.Locals("auth_token").(string) + name, ok := app["name"] if !ok { @@ -248,13 +249,13 @@ func (h *handlers) installDevApp(ctx *fiber.Ctx) error { } defer func() { if err != nil { - err = UpdateDevAppState(name, abnormal) + err = UpdateDevAppState(username, name, abnormal) if err != nil { klog.Errorf("update app state to abnormal err %v", err) } } }() - err = UpdateDevAppState(name, deploying) + err = UpdateDevAppState(username, name, deploying) if err != nil { return ctx.JSON(fiber.Map{ "code": http.StatusBadRequest, @@ -264,9 +265,9 @@ func (h *handlers) installDevApp(ctx *fiber.Ctx) error { source := app["source"] devName := name + "-dev" - devNamespace := devName + "-" + constants.Owner + devNamespace := devName + "-" + username - err = command.Lint().WithDir(BaseDir).Run(context.TODO(), name) + err = command.Lint().WithDir(BaseDir).Run(context.TODO(), username, name) if err != nil { return ctx.JSON(fiber.Map{ @@ -286,7 +287,7 @@ func (h *handlers) installDevApp(ctx *fiber.Ctx) error { releaseNotExist = true } if !releaseNotExist { - err = WaitForUninstall(devName, token, h.kubeConfig) + err = WaitForUninstall(username, devName, token, h.kubeConfig) if err != nil { return ctx.JSON(fiber.Map{ "code": http.StatusBadRequest, @@ -296,7 +297,7 @@ func (h *handlers) installDevApp(ctx *fiber.Ctx) error { } klog.Info("preinstall, create a labeled namespace for webhook") - _, err = container.CreateOrUpdateDevNamespace(ctx.Context(), h.kubeConfig, devName) + _, err = container.CreateOrUpdateDevNamespace(ctx.Context(), h.kubeConfig, username, devName) if err != nil { return ctx.JSON(fiber.Map{ "code": http.StatusBadRequest, @@ -306,7 +307,7 @@ func (h *handlers) installDevApp(ctx *fiber.Ctx) error { version := "0.0.1" if source != "cli" { klog.Info("auto update repo") - version, err = command.UpdateRepo().WithDir(BaseDir).Run(ctx.Context(), name, releaseNotExist) + version, err = command.UpdateRepo().WithDir(BaseDir).Run(ctx.Context(), username, name, releaseNotExist) if err != nil { klog.Error("command upgraderepo error, ", err, ", ", name) return ctx.JSON(fiber.Map{ @@ -316,7 +317,7 @@ func (h *handlers) installDevApp(ctx *fiber.Ctx) error { } } - _, err = command.Install().Run(ctx.Context(), devName, token, version) + _, err = command.Install().Run(ctx.Context(), username, devName, token, version) if err != nil { klog.Error("command install error, ", err, ", ", name) @@ -326,7 +327,7 @@ func (h *handlers) installDevApp(ctx *fiber.Ctx) error { }) } - err = UpdateDevAppState(name, deployed) + err = UpdateDevAppState(username, name, deployed) if err != nil { return ctx.JSON(fiber.Map{ "code": http.StatusBadRequest, @@ -535,6 +536,8 @@ func (h *handlers) uploadDevAppChart(ctx *fiber.Ctx) error { }) } + username := ctx.Locals("username").(string) + // parse incomming chart tgz/zip file fileHeader, err := ctx.FormFile("chart") if err != nil { @@ -569,7 +572,7 @@ func (h *handlers) uploadDevAppChart(ctx *fiber.Ctx) error { }) } - err = command.Lint().WithDir(untarPath).Run(context.TODO(), app) + err = command.Lint().WithDir(untarPath).Run(context.TODO(), username, app) if err != nil { klog.Error("check chart error, ", err) return ctx.JSON(fiber.Map{ @@ -603,8 +606,9 @@ func (h *handlers) lintDevAppChart(ctx *fiber.Ctx) error { "message": fmt.Sprintf("Application Not Found"), }) } + username := ctx.Locals("username").(string) - err := command.Lint().WithDir(BaseDir).Run(ctx.Context(), app) + err := command.Lint().WithDir(BaseDir).Run(ctx.Context(), username, app) if err != nil { return ctx.JSON(fiber.Map{ "code": http.StatusBadRequest, @@ -619,6 +623,7 @@ func (h *handlers) lintDevAppChart(ctx *fiber.Ctx) error { } func (h *handlers) uninstall(ctx *fiber.Ctx) error { + username := ctx.Locals("username").(string) name := ctx.Params("name") if name == "" { return ctx.JSON(fiber.Map{ @@ -643,7 +648,7 @@ func (h *handlers) uninstall(ctx *fiber.Ctx) error { "message": fmt.Sprintf("Uninstall Failed: %v", err), }) } - err = UpdateDevAppState(name, undeploy) + err = UpdateDevAppState(username, name, undeploy) if err != nil { klog.Errorf("update dev app state to undeploy err %v", err) } @@ -656,6 +661,7 @@ func (h *handlers) uninstall(ctx *fiber.Ctx) error { func (h *handlers) createAppByArchive(ctx *fiber.Ctx) error { override := ctx.Query("override") == "true" + username := ctx.Locals("username").(string) file, err := ctx.FormFile("chart") if err != nil { @@ -684,7 +690,7 @@ func (h *handlers) createAppByArchive(ctx *fiber.Ctx) error { }) } - cfg, err := readCfgFromFile(filepath.Join("/tmp", uniqueId)) + cfg, err := readCfgFromFile(username, filepath.Join("/tmp", uniqueId)) if err != nil { return ctx.JSON(fiber.Map{ "code": http.StatusBadRequest, @@ -698,7 +704,7 @@ func (h *handlers) createAppByArchive(ctx *fiber.Ctx) error { klog.Infof("WithDir: %s\n", filepath.Dir(chartDir)) klog.Infof("chart Base : %s\n", filepath.Base(chartDir)) - err = command.Lint().WithDir(filepath.Dir(chartDir)).Run(context.TODO(), filepath.Base(chartDir)) + err = command.Lint().WithDir(filepath.Dir(chartDir)).Run(context.TODO(), username, filepath.Base(chartDir)) if err != nil { return ctx.JSON(fiber.Map{ "code": http.StatusBadRequest, @@ -779,14 +785,14 @@ func InsertDevApp(app *model.DevApp) (appId int64, err error) { // if err rollback db defer func() { if err != nil { - e := op.DB.Where("app_name = ?", app.AppName).Delete(&model.DevApp{}).Error + e := op.DB.Where("owner = ?", app.Owner).Where("app_name = ?", app.AppName).Delete(&model.DevApp{}).Error if e != nil { klog.Warning("delete to rollback db error, ", err) } } }() var exists *model.DevApp - err = op.DB.Where("app_name = ?", app.AppName).First(&exists).Error + err = op.DB.Where("owner = ?", app.Owner).Where("app_name = ?", app.AppName).First(&exists).Error if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { klog.Error("exec sql error, ", err) return appId, err @@ -810,10 +816,10 @@ func InsertDevApp(app *model.DevApp) (appId int64, err error) { return appId, nil } -func UpdateDevApp(name string, updates map[string]interface{}) (appId int64, err error) { +func UpdateDevApp(owner, name string, updates map[string]interface{}) (appId int64, err error) { op := db.NewDbOperator() var exists *model.DevApp - err = op.DB.Where("app_name = ?", name).First(&exists).Error + err = op.DB.Where("owner = ?", owner).Where("app_name = ?", name).First(&exists).Error if err != nil { return 0, err } @@ -827,11 +833,11 @@ func UpdateDevApp(name string, updates map[string]interface{}) (appId int64, err return appId, nil } -func UpdateDevAppState(name string, state string) error { +func UpdateDevAppState(owner, name string, state string) error { updates := map[string]interface{}{ "state": state, } - _, err := UpdateDevApp(name, updates) + _, err := UpdateDevApp(owner, name, updates) if err != nil { return err } @@ -885,7 +891,7 @@ func uninstall(name, token string) (data map[string]interface{}, err error) { return data, nil } -func WaitForUninstall(name, token string, kubeConfig *rest.Config) error { +func WaitForUninstall(owner, name, token string, kubeConfig *rest.Config) error { _, err := uninstall(name, token) if err != nil { return err @@ -896,7 +902,7 @@ func WaitForUninstall(name, token string, kubeConfig *rest.Config) error { klog.Error(err) return err } - devNamespace := name + "-" + constants.Owner + devNamespace := name + "-" + owner klog.Infof("wait for uninstall: %s", devNamespace) return wait.PollUntilContextTimeout(context.TODO(), time.Second, 5*time.Minute, true, func(ctx context.Context) (done bool, err error) { if err != nil { @@ -920,6 +926,8 @@ type App struct { } func (h *handlers) createApp(ctx *fiber.Ctx) error { + username := ctx.Locals("username").(string) + var app App err := ctx.BodyParser(&app) if err != nil { @@ -945,14 +953,14 @@ func (h *handlers) createApp(ctx *fiber.Ctx) error { "message": fmt.Sprintf("Bad Request: this field must conform to the pattern ^[a-zA-Z][a-zA-Z0-9 ._-]{0,29}$"), }) } - err = h.db.DB.Where("title = ?", app.Title).First(&model.DevApp{}).Error + err = h.db.DB.Where("owner = ?", username).Where("title = ?", app.Title).First(&model.DevApp{}).Error if err == nil { return ctx.JSON(fiber.Map{ "code": http.StatusBadRequest, "message": fmt.Sprintf("create app failed, app ID %s already exists", appName), }) } - err = h.db.DB.Where("app_name = ?", appName).First(&model.DevApp{}).Error + err = h.db.DB.Where("owner = ?", username).Where("app_name = ?", appName).First(&model.DevApp{}).Error if err == nil { return ctx.JSON(fiber.Map{ "code": http.StatusBadRequest, @@ -965,6 +973,7 @@ func (h *handlers) createApp(ctx *fiber.Ctx) error { AppName: appName, AppType: db.CommunityApp, State: empty, + Owner: username, } appId, err := InsertDevApp(&appData) if err != nil { @@ -984,6 +993,7 @@ func (h *handlers) createApp(ctx *fiber.Ctx) error { } func (h *handlers) fillApp(ctx *fiber.Ctx) error { + username := ctx.Locals("username").(string) name := ctx.Params("name") var cfg command.CreateWithOneDockerConfig @@ -1005,7 +1015,7 @@ func (h *handlers) fillApp(ctx *fiber.Ctx) error { at := command.AppTemplate{} at.WithDockerCfg(&cfg).WithDockerDeployment(&cfg). WithDockerService(&cfg).WithDockerChartMetadata(&cfg).WithDockerOwner(&cfg) - err = at.WriteDockerFile(&cfg, BaseDir) + err = at.WriteDockerFile(&cfg, username, BaseDir) if err != nil { klog.Errorf("write docker file err %v", err) e := os.RemoveAll(filepath.Join(BaseDir, name)) @@ -1023,7 +1033,7 @@ func (h *handlers) fillApp(ctx *fiber.Ctx) error { "dev_env": "default", "state": undeploy, } - appId, err := UpdateDevApp(cfg.Name, updates) + appId, err := UpdateDevApp(username, cfg.Name, updates) if err != nil { return ctx.JSON(fiber.Map{ "code": http.StatusBadRequest, @@ -1042,8 +1052,9 @@ func (h *handlers) appState(ctx *fiber.Ctx) error { name := ctx.Params("name") var app *model.DevApp + username := ctx.Locals("username").(string) op := db.NewDbOperator() - err := op.DB.Where("app_name = ?", name).First(&app).Error + err := op.DB.Where("owner = ?", username).Where("app_name = ?", name).First(&app).Error if err != nil { klog.Errorf("get app name=%s err %v", name, err) return ctx.JSON(fiber.Map{ @@ -1060,6 +1071,7 @@ func (h *handlers) appState(ctx *fiber.Ctx) error { } func (h *handlers) fillAppWithExample(ctx *fiber.Ctx) error { + username := ctx.Locals("username").(string) name := ctx.Params("name") var app App @@ -1071,10 +1083,10 @@ func (h *handlers) fillAppWithExample(ctx *fiber.Ctx) error { }) } - err = command.CreateAppWithHelloWorldConfig(BaseDir, name, app.Title) + err = command.CreateAppWithHelloWorldConfig(BaseDir, username, name, app.Title) if err != nil { klog.Errorf("write docker file err %v", err) - e := os.RemoveAll(filepath.Join(BaseDir, name)) + e := os.RemoveAll(filepath.Join(BaseDir, username, name)) if e != nil { klog.Errorf("remove dir %s err %v", name, e) } @@ -1090,7 +1102,7 @@ func (h *handlers) fillAppWithExample(ctx *fiber.Ctx) error { "state": undeploy, } - appId, err := UpdateDevApp(name, updates) + appId, err := UpdateDevApp(username, name, updates) if err != nil { return ctx.JSON(fiber.Map{ "code": http.StatusBadRequest, @@ -1117,6 +1129,7 @@ type BindData struct { } func (h *handlers) fillAppWithDevContainer(ctx *fiber.Ctx) error { + username := ctx.Locals("username").(string) name := ctx.Params("name") var cfg command.CreateDevContainerConfig @@ -1135,7 +1148,7 @@ func (h *handlers) fillAppWithDevContainer(ctx *fiber.Ctx) error { }) } - err = command.CreateAppWithDevConfig(BaseDir, name, &cfg) + err = command.CreateAppWithDevConfig(BaseDir, username, name, &cfg) if err != nil { klog.Errorf("write dev docker file err %v", err) e := os.RemoveAll(filepath.Join(BaseDir, name)) @@ -1154,7 +1167,7 @@ func (h *handlers) fillAppWithDevContainer(ctx *fiber.Ctx) error { "state": undeploy, } - appId, err := UpdateDevApp(name, updates) + appId, err := UpdateDevApp(username, name, updates) if err != nil { return ctx.JSON(fiber.Map{ "code": http.StatusBadRequest, @@ -1162,7 +1175,7 @@ func (h *handlers) fillAppWithDevContainer(ctx *fiber.Ctx) error { }) } - containers, err := GetAppContainersInChart(name) + containers, err := GetAppContainersInChart(username, name) if err != nil || len(containers) == 0 { return ctx.JSON(fiber.Map{ "code": http.StatusBadRequest, diff --git a/pkg/api/server/handlers_files.go b/pkg/api/server/handlers_files.go index f6bd9c4..c13e4f9 100644 --- a/pkg/api/server/handlers_files.go +++ b/pkg/api/server/handlers_files.go @@ -21,9 +21,10 @@ import ( func (h *handlers) getFiles(ctx *fiber.Ctx) error { path := ctx.Params("*1") + username := ctx.Locals("username").(string) file, err := files.NewFileInfo(files.FileOptions{ - Fs: afero.NewBasePathFs(afero.NewOsFs(), BaseDir), + Fs: afero.NewBasePathFs(afero.NewOsFs(), BaseDir+"/"+username), Path: path, Modify: true, Expand: true, @@ -59,8 +60,9 @@ func (h *handlers) saveFile(ctx *fiber.Ctx) error { "message": "Invalid path format", }) } + username := ctx.Locals("username").(string) appName := pathParts[0] - file, err := WriteFileAndLint(ctx.Context(), path, appName, bytes.NewReader(content), command.Lint().WithDir(BaseDir).Run) + file, err := WriteFileAndLint(ctx.Context(), username, path, appName, bytes.NewReader(content), command.Lint().WithDir(BaseDir).Run) if err != nil { return ctx.JSON(fiber.Map{ "code": http.StatusBadRequest, @@ -74,7 +76,7 @@ func (h *handlers) saveFile(ctx *fiber.Ctx) error { }) } -func WriteFileAndLint(ctx context.Context, originFilePath, name string, content io.Reader, lintFunc func(context.Context, string) error) (os.FileInfo, error) { +func WriteFileAndLint(ctx context.Context, owner, originFilePath, name string, content io.Reader, lintFunc func(context.Context, string, string) error) (os.FileInfo, error) { exists := PathExists("/charts/tmp") if !exists { err := os.MkdirAll("/charts/tmp", 0755) @@ -103,8 +105,8 @@ func WriteFileAndLint(ctx context.Context, originFilePath, name string, content return nil, err } - if err = lintFunc(ctx, name); err != nil { - if restoreErr := os.Rename(tempFile.Name(), filepath.Join(BaseDir, originFilePath)); restoreErr != nil { + if err = lintFunc(ctx, owner, name); err != nil { + if restoreErr := os.Rename(tempFile.Name(), filepath.Join(BaseDir, owner, originFilePath)); restoreErr != nil { return nil, fmt.Errorf("lint failed: %v, and restore bak failed: %v", err, restoreErr) } return nil, fmt.Errorf("lint failed: %v", err) diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 9661013..e60bd9b 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -6,7 +6,7 @@ import ( "github.com/beclab/devbox/pkg/store/db" "github.com/beclab/devbox/pkg/webhook" - + "github.com/beclab/devbox/pkg/middlewares" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/logger" @@ -62,10 +62,11 @@ func (s *server) Start() { app.Use(logger.New()) api := app.Group("api") + api.Use(middlewares.TokenAuth()) // commands /api/command command := api.Group("command") - command.Post("/create-app", s.handlers.createDevApp) + //command.Post("/create-app", s.handlers.createDevApp) command.Get("/list-app", s.handlers.listDevApps) command.Get("/apps/:name", s.handlers.getDevApp) command.Post("/update-app-repo", s.handlers.updateDevAppRepo) @@ -95,7 +96,7 @@ func (s *server) Start() { //api.Post("/bind-container", s.handlers.bindContainer) //api.Post("/unbind-container", s.handlers.unbindContainer) api.Get("/list-app-containers", s.handlers.listAppContainersInChart) - api.Get("/list-my-containers", s.handlers.listMyContainers) + //api.Get("/list-my-containers", s.handlers.listMyContainers) //api.Get("/app-cfg", s.handlers.getAppConfig) //api.Post("/app-cfg", s.handlers.updateAppConfig) @@ -109,7 +110,8 @@ func (s *server) Start() { api.Get("/apps/:name/status", s.handlers.appState) api.Get("/dev-containers/:id", s.handlers.getDevContainer) - // webhooks /webhook + + // webhooks /webhook, do not need auth token wh := webhookServer.Group("webhook") wh.Post("/devcontainer", s.webhooks.devcontainer) wh.Post("/imagemanager", s.webhooks.imageManager) diff --git a/pkg/api/server/utils.go b/pkg/api/server/utils.go index d67f199..f2a4a05 100644 --- a/pkg/api/server/utils.go +++ b/pkg/api/server/utils.go @@ -17,8 +17,8 @@ import ( "k8s.io/klog/v2" ) -func getAppPath(app string) string { - return filepath.Join(BaseDir, app) +func getAppPath(owner, app string) string { + return filepath.Join(BaseDir, owner, app) } func UnArchive(src, dstDir string) error { @@ -43,20 +43,20 @@ func CheckDir(dirname string) error { return err } -func readCfgFromFile(chartDir string) (*oachecker.AppConfiguration, error) { +func readCfgFromFile(owner, chartDir string) (*oachecker.AppConfiguration, error) { cfgFile := findAppCfgFile(chartDir) klog.Infof("readCfgFromFile: %s", cfgFile) if len(cfgFile) == 0 { return nil, errors.New("not found OlaresManifest.yaml file") } - appcfg, err := readAppInfo(cfgFile) + appcfg, err := readAppInfo(owner, cfgFile) if err != nil { return nil, err } return appcfg, nil } -func readAppInfo(cfgFile string) (*oachecker.AppConfiguration, error) { +func readAppInfo(owner, cfgFile string) (*oachecker.AppConfiguration, error) { f, err := os.Open(cfgFile) if err != nil { return nil, err @@ -71,7 +71,7 @@ func readAppInfo(cfgFile string) (*oachecker.AppConfiguration, error) { } opts := []func(map[string]interface{}){ oachecker.WithAdmin(admin), - oachecker.WithOwner(constants.Owner), + oachecker.WithOwner(owner), } appcfg, err := oachecker.GetAppConfigurationFromContent(data, opts...) return appcfg, nil diff --git a/pkg/appcfg/application.go b/pkg/appcfg/application.go new file mode 100644 index 0000000..2772e94 --- /dev/null +++ b/pkg/appcfg/application.go @@ -0,0 +1,80 @@ +package appcfg + +import ( + "time" + + "github.com/beclab/oachecker" + "k8s.io/apimachinery/pkg/api/resource" +) + +type AppPermission interface{} + +type AppDataPermission string +type AppCachePermission string +type UserDataPermission string + +type Middleware interface{} + +type SysDataPermission struct { + AppName string `yaml:"appName" json:"appName"` + Port string `yaml:"port" json:"port"` + Svc string `yaml:"svc,omitempty" json:"svc,omitempty"` + Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"` + Group string `yaml:"group" json:"group"` + DataType string `yaml:"dataType" json:"dataType"` + Version string `yaml:"version" json:"version"` + Ops []string `yaml:"ops" json:"ops"` +} + +type AppRequirement struct { + Memory *resource.Quantity + Disk *resource.Quantity + GPU *resource.Quantity + CPU *resource.Quantity +} + +type AppPolicy struct { + EntranceName string `yaml:"entranceName" json:"entranceName"` + URIRegex string `yaml:"uriRegex" json:"uriRegex" description:"uri regular expression"` + Level string `yaml:"level" json:"level"` + OneTime bool `yaml:"oneTime" json:"oneTime"` + Duration time.Duration `yaml:"validDuration" json:"validDuration"` +} + +const ( + AppDataRW AppDataPermission = "appdata-perm" + AppCacheRW AppCachePermission = "appcache-perm" + UserDataRW UserDataPermission = "userdata-perm" +) + +type ApplicationConfig struct { + AppID string + CfgFileVersion string + Namespace string + ChartsName string + RepoURL string + Title string + Version string + Target string + AppName string // name of application displayed on shortcut + OwnerName string // name of owner who installed application + Entrances []oachecker.Entrance + Ports []oachecker.ServicePort + Icon string // base64 icon data + Permission []AppPermission // app permission requests + Requirement AppRequirement + Policies []AppPolicy + Middleware *oachecker.Middleware + AnalyticsEnabled bool + ResetCookieEnabled bool + Dependencies []oachecker.Dependency + AppScope oachecker.AppScope + WsConfig oachecker.WsConfig + Upload oachecker.Upload + OnlyAdmin bool + MobileSupported bool + ApiTimeout *int64 + RunAsUser bool + AllowedOutboundPorts []int + RequiredGPU string +} diff --git a/pkg/development/command/cfg_validate.go b/pkg/development/command/cfg_validate.go index dd3a3de..6536b88 100644 --- a/pkg/development/command/cfg_validate.go +++ b/pkg/development/command/cfg_validate.go @@ -20,7 +20,7 @@ func (c *checkCfg) WithDir(dir string) *checkCfg { return c } -func (c *checkCfg) Run(ctx context.Context, chart string) error { - err := oachecker.CheckChart(filepath.Join(c.baseCommand.dir, chart)) +func (c *checkCfg) Run(ctx context.Context, owner, chart string) error { + err := oachecker.CheckChart(filepath.Join(c.baseCommand.dir, owner, chart)) return err } diff --git a/pkg/development/command/chartmuseum.go b/pkg/development/command/chartmuseum.go index fc436bb..43ff550 100644 --- a/pkg/development/command/chartmuseum.go +++ b/pkg/development/command/chartmuseum.go @@ -5,17 +5,15 @@ import ( "net/http" "time" - "github.com/beclab/devbox/pkg/constants" - "github.com/go-resty/resty/v2" helm_repo "helm.sh/helm/v3/pkg/repo" "sigs.k8s.io/yaml" ) -func getChartVersions(name string) (helm_repo.ChartVersions, error) { +func getChartVersions(owner, name string) (helm_repo.ChartVersions, error) { chartVersions := make(helm_repo.ChartVersions, 0) client := resty.New().SetTimeout(5 * time.Second) - url := fmt.Sprintf("http://chartmuseum-studio.user-space-%s:8080/api/charts/%s", constants.Owner, name) + url := fmt.Sprintf("http://127.0.0.1:8888/%s/api/charts/%s", owner, name) resp, err := client.R().Get(url) if err != nil { return chartVersions, err @@ -29,9 +27,9 @@ func getChartVersions(name string) (helm_repo.ChartVersions, error) { return chartVersions, nil } -func deleteChartVersion(name, version string) error { +func deleteChartVersion(owner, name, version string) error { client := resty.New().SetTimeout(5 * time.Second) - url := fmt.Sprintf("http://chartmuseum-studio.user-space-%s:8080/api/charts/%s/%s", constants.Owner, name, version) + url := fmt.Sprintf("http://127.0.0.1:8888/%s/api/charts/%s/%s", owner, name, version) resp, err := client.R().Delete(url) if err != nil { return err diff --git a/pkg/development/command/createapp.go b/pkg/development/command/createapp.go index 1c9bc4d..ecbb6e6 100644 --- a/pkg/development/command/createapp.go +++ b/pkg/development/command/createapp.go @@ -84,7 +84,7 @@ func (c *createApp) WithDir(dir string) *createApp { return c } -func (c *createApp) Run(ctx context.Context, cfg *CreateConfig) error { +func (c *createApp) Run(ctx context.Context, cfg *CreateConfig, owner string) error { at := AppTemplate{} at.WithAppCfg(cfg).WithDeployment(cfg).WithService(cfg).WithChartMetadata(cfg).WithOwner(cfg) if cfg.Traefik { @@ -98,7 +98,7 @@ func (c *createApp) Run(ctx context.Context, cfg *CreateConfig) error { } } - return at.WriteFile(cfg, baseDir) + return at.WriteFile(cfg, baseDir, owner) } type AppTemplate struct { @@ -898,8 +898,8 @@ func (at *AppTemplate) WithTraefik(cfg *CreateConfig) *AppTemplate { return at } -func (at *AppTemplate) WriteFile(cfg *CreateConfig, baseDir string) (err error) { - path := filepath.Join(baseDir, cfg.Name) +func (at *AppTemplate) WriteFile(cfg *CreateConfig, baseDir string, owner string) (err error) { + path := filepath.Join(baseDir, owner, cfg.Name) if existDir(path) { return os.ErrExist } diff --git a/pkg/development/command/createapp_dev.go b/pkg/development/command/createapp_dev.go index e80fef1..71543e7 100644 --- a/pkg/development/command/createapp_dev.go +++ b/pkg/development/command/createapp_dev.go @@ -21,7 +21,7 @@ var createConfigDev = &CreateWithOneDockerConfig{ NeedRedis: false, } -func CreateAppWithDevConfig(baseDir string, name string, cfg *CreateDevContainerConfig) error { +func CreateAppWithDevConfig(baseDir string, owner, name string, cfg *CreateDevContainerConfig) error { createConfigDev.Name = name createConfigDev.Title = cfg.Title if cfg != nil { @@ -38,7 +38,7 @@ func CreateAppWithDevConfig(baseDir string, name string, cfg *CreateDevContainer at := AppTemplate{} at.WithDockerCfg(createConfigDev).WithDockerDeployment(createConfigDev). WithDockerService(createConfigDev).WithDockerChartMetadata(createConfigDev).WithDockerOwner(createConfigDev) - err := at.WriteDockerFile(createConfigDev, baseDir) + err := at.WriteDockerFile(createConfigDev, owner, baseDir) if err != nil { return err } diff --git a/pkg/development/command/createapp_helloword.go b/pkg/development/command/createapp_helloword.go index 21e0490..7cc6e30 100644 --- a/pkg/development/command/createapp_helloword.go +++ b/pkg/development/command/createapp_helloword.go @@ -12,13 +12,13 @@ var createConfigExample = &CreateWithOneDockerConfig{ NeedRedis: false, } -func CreateAppWithHelloWorldConfig(baseDir string, name, title string) error { +func CreateAppWithHelloWorldConfig(baseDir string, owner, name, title string) error { createConfigExample.Name = name createConfigExample.Title = title at := AppTemplate{} at.WithDockerCfg(createConfigExample).WithDockerDeployment(createConfigExample). WithDockerService(createConfigExample).WithDockerChartMetadata(createConfigExample).WithDockerOwner(createConfigExample) - err := at.WriteDockerFile(createConfigExample, baseDir) + err := at.WriteDockerFile(createConfigExample, owner, baseDir) if err != nil { return err } diff --git a/pkg/development/command/createapp_onedocker.go b/pkg/development/command/createapp_onedocker.go index 2104e79..d12a0be 100644 --- a/pkg/development/command/createapp_onedocker.go +++ b/pkg/development/command/createapp_onedocker.go @@ -70,7 +70,7 @@ func (c *createWithOneDocker) WithDir(dir string) *createWithOneDocker { return c } -func (c *createWithOneDocker) Run(cfg *CreateWithOneDockerConfig) error { +func (c *createWithOneDocker) Run(cfg *CreateWithOneDockerConfig, owner string) error { at := AppTemplate{} at.WithDockerCfg(cfg).WithDockerDeployment(cfg).WithDockerService(cfg).WithDockerChartMetadata(cfg).WithDockerOwner(cfg) @@ -82,7 +82,7 @@ func (c *createWithOneDocker) Run(cfg *CreateWithOneDockerConfig) error { } } - return at.WriteDockerFile(cfg, baseDir) + return at.WriteDockerFile(cfg, owner, baseDir) } func (at *AppTemplate) checkMountPath(mounts map[string]string, prefix string) bool { @@ -456,8 +456,8 @@ func (at *AppTemplate) WithDockerOwner(cfg *CreateWithOneDockerConfig) *AppTempl return at } -func (at *AppTemplate) WriteDockerFile(cfg *CreateWithOneDockerConfig, baseDir string) (err error) { - path := filepath.Join(baseDir, cfg.Name) +func (at *AppTemplate) WriteDockerFile(cfg *CreateWithOneDockerConfig, owner, baseDir string) (err error) { + path := filepath.Join(baseDir, owner, cfg.Name) if existDir(path) { return os.ErrExist } diff --git a/pkg/development/command/install.go b/pkg/development/command/install.go index c69fd5c..2bc30d5 100644 --- a/pkg/development/command/install.go +++ b/pkg/development/command/install.go @@ -2,14 +2,17 @@ package command import ( "context" + "encoding/json" "errors" "fmt" "net/http" "net/http/httputil" + "os" "time" "github.com/emicklei/go-restful/v3" "github.com/go-resty/resty/v2" + "github.com/nats-io/nats.go" "k8s.io/klog/v2" ) @@ -20,23 +23,24 @@ func Install() *install { return &install{} } -func (c *install) Run(ctx context.Context, app string, token string, version string) (string, error) { +func (c *install) Run(ctx context.Context, owner, app string, token string, version string) (string, error) { klog.Infof("run appname: %s", app) - err := c.UploadChartToMarket(ctx, app, token, version) + err := c.UploadChartToMarket(ctx, owner, app, token, version) if err != nil { return "", err } - for i := 0; i < 45; i++ { - klog.Infof("wait for chart %d", i) - time.Sleep(time.Second) + err = c.waitForMarketUpdate(ctx, owner, app, version) + if err != nil { + klog.Errorf("wait market ready app: %s failed", app) + return "", err } // get chart tgz file from storage and push to market // if more than one user upload same name tgz file to market what would happen url := fmt.Sprintf("http://appstore-service.os-framework:81/app-store/api/v2/apps/%s/install", app) - client := resty.New().SetTimeout(5 * time.Second) + client := resty.New().SetTimeout(30 * time.Second) body := map[string]interface{}{ "source": "local", "app_name": app, @@ -64,10 +68,10 @@ func (c *install) Run(ctx context.Context, app string, token string, version str } -func (c *install) UploadChartToMarket(ctx context.Context, app string, token string, version string) error { +func (c *install) UploadChartToMarket(ctx context.Context, owner, app string, token string, version string) error { client := resty.New().SetTimeout(30 * time.Second) - chartFilePath := fmt.Sprintf("/storage/%s-%s.tgz", app, version) + chartFilePath := fmt.Sprintf("/storage/%s/%s-%s.tgz", owner, app, version) klog.Infof("chartFilePath: %s", chartFilePath) resp, err := client.R(). SetHeader("X-Authorization", token). @@ -76,7 +80,7 @@ func (c *install) UploadChartToMarket(ctx context.Context, app string, token str "source": "local", }).Post("http://appstore-service.os-framework:81/app-store/api/v2/apps/upload") if err != nil { - klog.Errorf("upload app %s chart to market failed %w", app, err) + klog.Errorf("upload app %s chart to market failed %v", app, err) return fmt.Errorf("upload app %s chart to market failed %w", app, err) } if resp.StatusCode() != http.StatusOK { @@ -90,3 +94,68 @@ func (c *install) UploadChartToMarket(ctx context.Context, app string, token str klog.Infof("update app %s chart to market success", app) return nil } + +func (c *install) waitForMarketUpdate(ctx context.Context, owner, app, version string) error { + natsHost := os.Getenv("NATS_HOST") + natsPort := os.Getenv("NATS_PORT") + + //subject = os.Getenv("NATS_SUBJECT") + username := os.Getenv("NATS_USERNAME") + password := os.Getenv("NATS_PASSWORD") + + natsURL := fmt.Sprintf("nats://%s:%s", natsHost, natsPort) + nc, err := nats.Connect(natsURL, nats.UserInfo(username, password)) + if err != nil { + klog.Errorf("failed to connect to nats: %s, err=%v", natsURL, err) + return err + } + subject := fmt.Sprintf("os.market.%s", owner) + klog.Infof("subscribe subject: %s", subject) + + msgChan := make(chan *nats.Msg, 1) + timeoutChan := time.After(2 * time.Minute) + + sub, err := nc.ChanSubscribe(subject, msgChan) + if err != nil { + klog.Errorf("failed to subscribe subject: %s,err=%v", subject, err) + return err + } + defer sub.Unsubscribe() + klog.Infof("start to wait market update message, timeout is 2 minutes") + for { + select { + case msg := <-msgChan: + var updateInfo MarketSystemUpdate + if err := json.Unmarshal(msg.Data, &updateInfo); err != nil { + klog.Errorf("failed to Unmarshal %v", err) + continue + } + klog.Infof("message: %#v", updateInfo) + if updateInfo.NotifyType != "market_system_point" { + continue + } + if updateInfo.User != owner { + continue + } + if updateInfo.Extensions["app_name"] != app { + continue + } + if updateInfo.Extensions["app_version"] != version { + continue + } + if updateInfo.Point != "new_app_ready" { + continue + } + + return nil + case <-timeoutChan: + klog.Infof("wait for market app:%s ready timeout after 2 minutes", app) + return fmt.Errorf("wait for market ready timeout") + case <-ctx.Done(): + klog.Infof("wait for market ready context canceled") + return ctx.Err() + } + + } + +} diff --git a/pkg/development/command/lint.go b/pkg/development/command/lint.go index 8b49e6a..065edc0 100644 --- a/pkg/development/command/lint.go +++ b/pkg/development/command/lint.go @@ -20,8 +20,8 @@ func (l *lint) WithDir(dir string) *lint { return l } -func (l *lint) Run(ctx context.Context, chart string) error { - chartPath := filepath.Join(l.baseCommand.dir, chart) +func (l *lint) Run(ctx context.Context, owner, chart string) error { + chartPath := filepath.Join(l.baseCommand.dir, owner, chart) err := oachecker.LintWithDifferentOwnerAdmin(chartPath, "owner", "admin") if err != nil { return err diff --git a/pkg/development/command/token.go b/pkg/development/command/token.go index ba0e689..36cbcb7 100644 --- a/pkg/development/command/token.go +++ b/pkg/development/command/token.go @@ -1,19 +1,7 @@ package command import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "net/http/httputil" - "strconv" "time" - - "github.com/beclab/devbox/pkg/constants" - - "github.com/go-resty/resty/v2" - "golang.org/x/crypto/bcrypt" - "k8s.io/klog/v2" ) type AccessTokenResponse struct { @@ -21,43 +9,3 @@ type AccessTokenResponse struct { ExpiredAt time.Time `json:"expired_at"` } -func GetAccessToken() (string, error) { - timestamp := time.Now().UnixMilli() / 1000 - text := constants.ApiKey + strconv.Itoa(int(timestamp)) + constants.ApiSecret - token, err := bcrypt.GenerateFromPassword([]byte(text), bcrypt.DefaultCost) - if err != nil { - return "", err - } - client := resty.New().SetTimeout(5 * time.Second) - resp, err := client.R().SetBody( - map[string]interface{}{ - "app_key": constants.ApiKey, - "timestamp": timestamp, - "token": string(token), - "perm": map[string]interface{}{ - "group": "service.appstore", - "dataType": "app", - "version": "v1", - "ops": []string{"InstallDevApp", "UninstallDevApp"}, - }, - }).Post(fmt.Sprintf("http://%s/permission/v1alpha1/access", constants.SystemServer)) - if err != nil { - return "", err - } - if resp.StatusCode() != http.StatusOK { - dump, e := httputil.DumpRequest(resp.Request.RawRequest, true) - if e == nil { - klog.Error("request system-server.permission/v1alpha1/access", string(dump)) - } - return "", errors.New(string(resp.Body())) - } - - at := struct { - Data AccessTokenResponse `json:"data"` - }{} - err = json.Unmarshal(resp.Body(), &at) - if err != nil { - return "", err - } - return at.Data.AccessToken, nil -} diff --git a/pkg/development/command/types.go b/pkg/development/command/types.go new file mode 100644 index 0000000..d1dd289 --- /dev/null +++ b/pkg/development/command/types.go @@ -0,0 +1,10 @@ +package command + + +type MarketSystemUpdate struct { + Timestamp int64 `json:"timestamp"` + User string `json:"user"` + NotifyType string `json:"notify_type"` + Point string `json:"point"` + Extensions map[string]string `json:"extensions,omitempty"` // Additional extension information +} diff --git a/pkg/development/command/updaterepo.go b/pkg/development/command/updaterepo.go index 64721f9..7d56bb0 100644 --- a/pkg/development/command/updaterepo.go +++ b/pkg/development/command/updaterepo.go @@ -3,6 +3,7 @@ package command import ( "context" "errors" + "fmt" "os" "path/filepath" "strings" @@ -26,11 +27,11 @@ func (c *updateRepo) WithDir(dir string) *updateRepo { return c } -func (c *updateRepo) Run(ctx context.Context, app string, notExist bool) (string, error) { +func (c *updateRepo) Run(ctx context.Context, owner, app string, notExist bool) (string, error) { if app == "" { return "", errors.New("repo path must be specified") } - realPath := filepath.Join(c.baseCommand.dir, app) + realPath := filepath.Join(c.baseCommand.dir, owner, app) chart, err := helm.LoadChart(realPath) if err != nil { @@ -100,7 +101,7 @@ func (c *updateRepo) Run(ctx context.Context, app string, notExist bool) (string } if !notExist { - err = helm.UpdateAppCfgVersion(realPath, &newVersion) + err = helm.UpdateAppCfgVersion(owner, realPath, &newVersion) if err != nil { klog.Error("update OlaresManifest.yaml metadata.version error, ", err) return "", err @@ -115,12 +116,12 @@ func (c *updateRepo) Run(ctx context.Context, app string, notExist bool) (string } defer appcfgDeferFunc() - err = helm.UpdateAppCfgName(app, realPath) + err = helm.UpdateAppCfgName(owner, app, realPath) if err != nil { return "", err } - output, err := c.baseCommand.run(ctx, "helm", "cm-push", "-f", app, "http://localhost:8888", "--debug") + output, err := c.baseCommand.run(ctx, "helm", "cm-push", "-f", fmt.Sprintf("--context-path=%s", owner), owner+"/"+app, "http://localhost:8888", "--debug") if err != nil { if len(output) > 0 { return "", errors.New(output) @@ -130,7 +131,7 @@ func (c *updateRepo) Run(ctx context.Context, app string, notExist bool) (string result := strings.Split(output, "\n") if len(result) > 0 && result[len(result)-2] == "Done." { if !notExist { - err = deleteOldTgz(app+"-dev", newVersion.String()) + err = deleteOldTgz(owner, app+"-dev", newVersion.String()) if err != nil { klog.Info("delete chartmuseum old tgz error, ", err) } @@ -142,8 +143,8 @@ func (c *updateRepo) Run(ctx context.Context, app string, notExist bool) (string } -func deleteOldTgz(name, notDeleteVersion string) error { - chartVersions, err := getChartVersions(name) +func deleteOldTgz(owner, name, notDeleteVersion string) error { + chartVersions, err := getChartVersions(owner, name) if err != nil { return err } @@ -152,7 +153,7 @@ func deleteOldTgz(name, notDeleteVersion string) error { if cv.Version == notDeleteVersion { continue } - err = deleteChartVersion(name, cv.Version) + err = deleteChartVersion(owner, name, cv.Version) if err != nil { errs = append(errs, err) } diff --git a/pkg/development/container/helper.go b/pkg/development/container/helper.go index 650df98..08b1999 100644 --- a/pkg/development/container/helper.go +++ b/pkg/development/container/helper.go @@ -2,13 +2,9 @@ package container import ( "context" - "errors" - "strconv" "strings" "github.com/beclab/devbox/pkg/constants" - "github.com/beclab/devbox/pkg/store/db/model" - corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -52,71 +48,71 @@ func IsSysAppDevImage(image string) bool { return false } -func GetContainerStatus(ctx context.Context, kubeconfig *rest.Config, container *model.DevContainerInfo) (string, string, error) { - client, err := kubernetes.NewForConfig(kubeconfig) - if err != nil { - klog.Error("get kubernetes client error, ", err) - return UnknownStatus, "", err - } - - namespace := *container.AppName + "-dev-" + constants.Owner - userspace := "user-space-" + constants.Owner - pods, err := client.CoreV1().Pods("").List(ctx, metav1.ListOptions{ - LabelSelector: *container.PodSelector, - }) - - if err != nil { - klog.Error("find pods of container error, ", err) - return UnknownStatus, "", err - } - - for _, p := range pods.Items { - if p.Namespace == namespace || p.Namespace == userspace { - for _, c := range p.Status.ContainerStatuses { - if c.Name == *container.ContainerName { - for _, con := range p.Spec.Containers { - if con.Name == c.Name { - state := UnknownStatus - port := "" - for _, e := range con.Env { - switch { - case e.Name == DevContainerEnv && e.Value == strconv.Itoa(int(container.ID)): - switch { - case c.State.Waiting != nil: - state = "Waiting" - case c.State.Running != nil: - state = "Running" - case c.State.Terminated != nil: - state = "Terminated" - } - - case e.Name == DevContainerPortEnv: - port = e.Value - } - } - - if state != UnknownStatus { - return state, port, nil - } - } - } - } - } - } - } - - klog.Error("container not found, ", *container) - return UnknownStatus, "", errors.New("container not found") -} - -func CreateOrUpdateDevNamespace(ctx context.Context, kubeconfig *rest.Config, app string) (string, error) { +//func GetContainerStatus(ctx context.Context, kubeconfig *rest.Config, container *model.DevContainerInfo) (string, string, error) { +// client, err := kubernetes.NewForConfig(kubeconfig) +// if err != nil { +// klog.Error("get kubernetes client error, ", err) +// return UnknownStatus, "", err +// } +// +// namespace := *container.AppName + "-dev-" + constants.Owner +// userspace := "user-space-" + constants.Owner +// pods, err := client.CoreV1().Pods("").List(ctx, metav1.ListOptions{ +// LabelSelector: *container.PodSelector, +// }) +// +// if err != nil { +// klog.Error("find pods of container error, ", err) +// return UnknownStatus, "", err +// } +// +// for _, p := range pods.Items { +// if p.Namespace == namespace || p.Namespace == userspace { +// for _, c := range p.Status.ContainerStatuses { +// if c.Name == *container.ContainerName { +// for _, con := range p.Spec.Containers { +// if con.Name == c.Name { +// state := UnknownStatus +// port := "" +// for _, e := range con.Env { +// switch { +// case e.Name == DevContainerEnv && e.Value == strconv.Itoa(int(container.ID)): +// switch { +// case c.State.Waiting != nil: +// state = "Waiting" +// case c.State.Running != nil: +// state = "Running" +// case c.State.Terminated != nil: +// state = "Terminated" +// } +// +// case e.Name == DevContainerPortEnv: +// port = e.Value +// } +// } +// +// if state != UnknownStatus { +// return state, port, nil +// } +// } +// } +// } +// } +// } +// } +// +// klog.Error("container not found, ", *container) +// return UnknownStatus, "", errors.New("container not found") +//} + +func CreateOrUpdateDevNamespace(ctx context.Context, kubeconfig *rest.Config, owner, app string) (string, error) { client, err := kubernetes.NewForConfig(kubeconfig) if err != nil { klog.Error("get kubernetes client error, ", err) return "", err } - namespaceName := app + "-" + constants.Owner + namespaceName := app + "-" + owner ns, err := client.CoreV1().Namespaces().Get(ctx, namespaceName, metav1.GetOptions{}) if err != nil { @@ -128,7 +124,7 @@ func CreateOrUpdateDevNamespace(ctx context.Context, kubeconfig *rest.Config, ap ObjectMeta: metav1.ObjectMeta{ Name: namespaceName, Labels: map[string]string{ - constants.DevOwnerLabel: constants.Owner, + constants.DevOwnerLabel: "true", }, }, } @@ -141,7 +137,7 @@ func CreateOrUpdateDevNamespace(ctx context.Context, kubeconfig *rest.Config, ap } } else { retry.RetryOnConflict(retry.DefaultRetry, func() error { - ns.Labels[constants.DevOwnerLabel] = constants.Owner + ns.Labels[constants.DevOwnerLabel] = "true" _, err = client.CoreV1().Namespaces().Update(ctx, ns, metav1.UpdateOptions{}) return err }) diff --git a/pkg/development/envoy/config.go b/pkg/development/envoy/config.go index 0c4ae8c..f07f0d3 100644 --- a/pkg/development/envoy/config.go +++ b/pkg/development/envoy/config.go @@ -2,8 +2,7 @@ package envoy import ( "encoding/json" - - "github.com/beclab/devbox/pkg/constants" + "fmt" envoy_config_bootstrap "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v3" clusterv3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" @@ -24,6 +23,7 @@ import ( type ConfigBuilder struct { containers []*DevcontainerEndpoint websocket bool + owner string } func (cb *ConfigBuilder) WithDevcontainers(containers []*DevcontainerEndpoint) *ConfigBuilder { @@ -120,15 +120,6 @@ func (cb *ConfigBuilder) Build() (string, error) { // filter_chains: FilterChains: []*listenerv3.FilterChain{ { - // - filters: - // - name: envoy.filters.network.http_connection_manager - // typed_config: - // "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - // stat_prefix: desktop_http - // upgrade_configs: - // - upgrade_type: websocket - // skip_xff_append: false - // codec_type: AUTO Filters: []*listenerv3.Filter{ { Name: "envoy.filters.network.http_connection_manager", @@ -143,22 +134,6 @@ func (cb *ConfigBuilder) Build() (string, error) { SkipXffAppend: false, CodecType: http_connection_manager_v3.HttpConnectionManager_AUTO, RouteSpecifier: &http_connection_manager_v3.HttpConnectionManager_RouteConfig{ - - // route_config: - // name: local_route - // virtual_hosts: - // - name: service - // domains: ["*"] - // routes: - // - match: - // prefix: "/proxy/5000" - // route: - // cluster: dev1 - // - match: - // prefix: "/" - // route: - // cluster: original_dst - // timeout: 180s RouteConfig: &routev3.RouteConfiguration{ Name: "local_route", VirtualHosts: []*routev3.VirtualHost{ @@ -170,57 +145,8 @@ func (cb *ConfigBuilder) Build() (string, error) { }, }, }, - - // http_filters: - // - name: envoy.filters.http.ext_authz - // typed_config: - // "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz - // http_service: - // path_prefix: '/api/verify/' - // server_uri: - // uri: authelia-backend.os-framework:9091 - // cluster: authelia - // timeout: 2s - // authorization_request: - // allowed_headers: - // patterns: - // - exact: accept - // - exact: cookie - // - exact: proxy-authorization - // - prefix: x-unauth- - // - exact: x-authorization - // - exact: x-bfl-user - // - exact: terminus-nonce - // headers_to_add: - // - key: X-Forwarded-Method - // value: '%REQ(:METHOD)%' - // - key: X-Forwarded-Proto - // value: '%REQ(:SCHEME)%' - // - key: X-Forwarded-Host - // value: '%REQ(:AUTHORITY)%' - // - key: X-Forwarded-Uri - // value: '%REQ(:PATH)%' - // - key: X-Forwarded-For - // value: '%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%' - // authorization_response: - // allowed_upstream_headers: - // patterns: - // - exact: authorization - // - exact: proxy-authorization - // - prefix: remote- - // - prefix: authelia- - // allowed_client_headers: - // patterns: - // - exact: set-cookie - // allowed_client_headers_on_success: - // patterns: - // - exact: set-cookie - // failure_mode_allow: false - // - name: envoy.filters.http.router - // typed_config: - // "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router HttpFilters: []*http_connection_manager_v3.HttpFilter{ - authFilter(), + authFilter(cb.owner), { Name: "envoy.filters.http.router", ConfigType: &http_connection_manager_v3.HttpFilter_TypedConfig{ @@ -244,10 +170,6 @@ func (cb *ConfigBuilder) Build() (string, error) { } clusters := []*clusterv3.Cluster{ - // - name: original_dst - // connect_timeout: 5000s - // type: ORIGINAL_DST - // lb_policy: CLUSTER_PROVIDED { Name: "original_dst", ClusterDiscoveryType: &clusterv3.Cluster_Type{ @@ -259,22 +181,6 @@ func (cb *ConfigBuilder) Build() (string, error) { LbPolicy: clusterv3.Cluster_CLUSTER_PROVIDED, }, - // - name: authelia - // connect_timeout: 2s - // type: LOGICAL_DNS - // dns_lookup_family: V4_ONLY - // dns_refresh_rate: 600s - // lb_policy: ROUND_ROBIN - // load_assignment: - // cluster_name: authelia - // endpoints: - // - lb_endpoints: - // - endpoint: - // address: - // socket_address: - // address: authelia-backend.os-framework - // port_value: 9091 - { Name: "authelia", ClusterDiscoveryType: &clusterv3.Cluster_Type{ @@ -299,7 +205,7 @@ func (cb *ConfigBuilder) Build() (string, error) { Address: &corev3.Address{ Address: &corev3.Address_SocketAddress{ SocketAddress: &corev3.SocketAddress{ - Address: "authelia-backend.user-system-" + constants.Owner, + Address: fmt.Sprintf("authelia-backend.user-system-%s", cb.owner), PortSpecifier: &corev3.SocketAddress_PortValue{ PortValue: 9091, }, @@ -316,21 +222,6 @@ func (cb *ConfigBuilder) Build() (string, error) { }, } - // - name: ws_original_dst - // connect_timeout: 5000s - // type: LOGICAL_DNS - // dns_lookup_family: V4_ONLY - // dns_refresh_rate: 600s - // lb_policy: ROUND_ROBIN - // load_assignment: - // cluster_name: ws_original_dst - // endpoints: - // - lb_endpoints: - // - endpoint: - // address: - // socket_address: - // address: localhost - // port_value: 40010 if cb.websocket { clusters = append(clusters, &clusterv3.Cluster{ Name: "ws_gateway", @@ -458,7 +349,7 @@ func (cb *ConfigBuilder) Build() (string, error) { return string(config), err } -func authFilter() *http_connection_manager_v3.HttpFilter { +func authFilter(owner string) *http_connection_manager_v3.HttpFilter { return &http_connection_manager_v3.HttpFilter{ Name: "envoy.filters.http.ext_authz", ConfigType: &http_connection_manager_v3.HttpFilter_TypedConfig{ @@ -467,7 +358,7 @@ func authFilter() *http_connection_manager_v3.HttpFilter { HttpService: &envoy_authz_v3.HttpService{ PathPrefix: "/api/verify/", ServerUri: &corev3.HttpUri{ - Uri: "authelia-backend.user-system-" + constants.Owner + ":9091", + Uri: fmt.Sprintf("authelia-backend.user-system-%s:9091", owner), HttpUpstreamType: &corev3.HttpUri_Cluster{ Cluster: "authelia", }, diff --git a/pkg/development/envoy/sidecar.go b/pkg/development/envoy/sidecar.go index c2ad6b6..19faafa 100644 --- a/pkg/development/envoy/sidecar.go +++ b/pkg/development/envoy/sidecar.go @@ -6,19 +6,21 @@ import ( "strconv" "github.com/beclab/oachecker" - corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/beclab/devbox/pkg/appcfg" "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" "k8s.io/utils/pointer" ) func InjectSidecar(ctx context.Context, kubeClient *kubernetes.Clientset, namespace string, - pod *corev1.Pod, devcontainers []*DevcontainerEndpoint, proxyUUID string, appcfg *oachecker.AppConfiguration) error { + pod *corev1.Pod, devcontainers []*DevcontainerEndpoint, proxyUUID string, appConfig *appcfg.ApplicationConfig) error { injected, _ := IsInjectedPod(pod) - sidecarConfig := &ConfigBuilder{} + sidecarConfig := &ConfigBuilder{ + owner: appConfig.OwnerName, + } sidecarConfig.WithDevcontainers(devcontainers) if injected { klog.Info("envoy sidecar injected pod") @@ -26,7 +28,7 @@ func InjectSidecar(ctx context.Context, kubeClient *kubernetes.Clientset, namesp klog.Info("websocket sidecar injected pod") sidecarConfig.WithWebsocket() } - } else if appcfg.Options.WsConfig != nil && appcfg.Options.WsConfig.URL != "" { + } else if appConfig.WsConfig.URL != "" { sidecarConfig.WithWebsocket() } @@ -74,7 +76,7 @@ func InjectSidecar(ctx context.Context, kubeClient *kubernetes.Clientset, namesp pod.Spec.Containers = append(pod.Spec.Containers, getEnvoySidecarContainerSpec(pod)) if sidecarConfig.Websocket() { klog.Info("inject websocket sidecar") - pod.Spec.Containers = append(pod.Spec.Containers, getWebSocketSideCarContainerSpec(appcfg.Options.WsConfig)) + pod.Spec.Containers = append(pod.Spec.Containers, getWebSocketSideCarContainerSpec(&appConfig.WsConfig)) } } return nil diff --git a/pkg/development/helm/chart.go b/pkg/development/helm/chart.go index 0f4b479..8bcce27 100644 --- a/pkg/development/helm/chart.go +++ b/pkg/development/helm/chart.go @@ -13,9 +13,9 @@ import ( "time" "github.com/Masterminds/semver/v3" + "github.com/beclab/devbox/pkg/appcfg" "github.com/beclab/devbox/pkg/constants" "github.com/beclab/devbox/pkg/utils" - "github.com/beclab/oachecker" "gopkg.in/yaml.v2" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" @@ -27,13 +27,17 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + runtimeSchema "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/wait" yamlutil "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" yml "sigs.k8s.io/yaml" ) @@ -113,7 +117,7 @@ func UpdateChartName(chart *chart.Chart, name, path string) error { return nil } -func UpdateAppCfgVersion(path string, version *semver.Version) error { +func UpdateAppCfgVersion(owner, path string, version *semver.Version) error { appCfgYaml := filepath.Join(path, constants.AppCfgFileName) data, err := os.ReadFile(appCfgYaml) if err != nil { @@ -123,7 +127,7 @@ func UpdateAppCfgVersion(path string, version *semver.Version) error { //var appCfg application.AppConfiguration //err = yaml.Unmarshal(data, &appCfg) - appCfg, err := utils.GetAppConfig(data) + appCfg, err := utils.GetAppConfig(owner, data) if err != nil { klog.Error("parse appcfg error, ", err) return err @@ -145,7 +149,7 @@ func UpdateAppCfgVersion(path string, version *semver.Version) error { return nil } -func UpdateAppCfgName(name, path string) error { +func UpdateAppCfgName(owner, name, path string) error { appDevName := name + "-dev" appCfgYaml := filepath.Join(path, constants.AppCfgFileName) data, err := os.ReadFile(appCfgYaml) @@ -156,7 +160,7 @@ func UpdateAppCfgName(name, path string) error { //var appCfg application.AppConfiguration //err = yaml.Unmarshal(data, &appCfg) - appCfg, err := utils.GetAppConfig(data) + appCfg, err := utils.GetAppConfig(owner, data) if err != nil { klog.Error("parse OlaresManifest.yaml error, ", err) return err @@ -393,25 +397,35 @@ func FindContainers(objs []runtime.Object) []*ContainerInfo { return infos } -func GetAppCfg(app string, baseDir string) (*oachecker.AppConfiguration, error) { - if app == "" { - return nil, errors.New("repo path must be specified") +func GetAppCfg(appManagerName string) (*appcfg.ApplicationConfig, error) { + config, err := ctrl.GetConfig() + if err != nil { + klog.Errorf("failed to get kubeconfig %v", err) + return nil, err } - realPath := filepath.Join(baseDir, app) - - appCfgYaml := filepath.Join(realPath, constants.AppCfgFileName) - data, err := os.ReadFile(appCfgYaml) + dynamicClient, err := dynamic.NewForConfig(config) if err != nil { - klog.Error("read OlaresManifest.yaml error, ", err, ", ", appCfgYaml) + klog.Errorf("failed to creat dynamic client %v", err) + return nil, err + } + gvr := runtimeSchema.GroupVersionResource{ + Group: "app.bytetrade.io", + Version: "v1alpha1", + Resource: "applicationmanagers", + } + am, err := dynamicClient.Resource(gvr).Namespace("").Get(context.TODO(), appManagerName, metav1.GetOptions{}) + if am == nil || err != nil { + klog.Errorf("failed to get app manager name=%s, err=%v", appManagerName, err) return nil, err } - var appCfg oachecker.AppConfiguration - err = yaml.Unmarshal(data, &appCfg) + data, _, _ := unstructured.NestedString(am.Object, "spec", "config") + var applicationConfig appcfg.ApplicationConfig + err = json.Unmarshal([]byte(data), &applicationConfig) if err != nil { - klog.Error("parse OlaresManifest.yaml error, ", err) + klog.Errorf("failed to unmarshal application manager config err=%v", err) return nil, err } + return &applicationConfig, nil - return &appCfg, nil } diff --git a/pkg/middlewares/auth.go b/pkg/middlewares/auth.go new file mode 100644 index 0000000..32f46ff --- /dev/null +++ b/pkg/middlewares/auth.go @@ -0,0 +1,88 @@ +package middlewares + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "time" + + "github.com/go-resty/resty/v2" + "github.com/gofiber/fiber/v2" + "k8s.io/klog/v2" +) + +var lldapBaseURL = "http://lldap-service.os-platform:17170" + +type UserInfo struct { + Username string `json:"username"` +} + +type JWTClaims struct { + Exp int64 `json:"exp"` + Iat int64 `json:"iat"` + Username string `json:"username"` +} + +func TokenAuth() fiber.Handler { + return func(c *fiber.Ctx) error { + token := extractToken(c) + if token == "" { + klog.Warningf("token not found in request") + return c.Status(http.StatusUnauthorized).JSON(fiber.Map{ + "code": http.StatusUnauthorized, + "message": "token not found in request", + }) + } + userInfo, err := validateToken(token, token) + if err != nil { + return c.Status(http.StatusUnauthorized).JSON(fiber.Map{ + "code": http.StatusUnauthorized, + "message": "token not found in request", + }) + } + klog.Infof("userInfo.Usernaeme: %s", userInfo.Username) + + c.Locals("username", userInfo.Username) + c.Locals("auth_token", token) + return c.Next() + } +} + +func extractToken(c *fiber.Ctx) string { + authToken := c.Get("X-Authorization") + if authToken != "" { + return authToken + } + authToken = c.Cookies("auth_token") + return authToken +} + +func validateToken(accessToken, validToken string) (*UserInfo, error) { + url := fmt.Sprintf("%s/auth/token/verify", lldapBaseURL) + client := resty.New() + resp, err := client.SetTimeout(30*time.Second).R(). + SetHeader("Content-Type", "application/json"). + SetAuthToken(accessToken). + SetBody(map[string]string{ + "access_token": validToken, + }).Post(url) + if err != nil { + klog.Infof("send request o lldap failed %v", err) + return nil, err + } + if resp.StatusCode() != http.StatusOK { + klog.Infof("request lldap /auth/token/verify not 200, %v, body: %v", resp.StatusCode(), string(resp.Body())) + return nil, errors.New(resp.String()) + } + var response JWTClaims + err = json.Unmarshal(resp.Body(), &response) + if err != nil { + klog.Errorf("unmarshal jwt claims failed: %v", err) + return nil, err + } + userInfo := UserInfo{ + Username: response.Username, + } + return &userInfo, nil +} diff --git a/pkg/store/db/model/dev_app.go b/pkg/store/db/model/dev_app.go index c345d81..85574b5 100644 --- a/pkg/store/db/model/dev_app.go +++ b/pkg/store/db/model/dev_app.go @@ -12,6 +12,7 @@ type DevApp struct { CreateTime time.Time `gorm:"default:CURRENT_TIMESTAMP;column:create_time" json:"createTime"` UpdateTime time.Time `gorm:"default:CURRENT_TIMESTAMP;column:update_time;index:update_time" json:"updateTime"` State string `gorm:"type:varchar(20);column:state" json:"state"` + Owner string `gorm:"type:varchar(20);column:owner" json:"owner"` AppID string `gorm:"-" json:"appID"` Chart string `gorm:"-" json:"chart"` diff --git a/pkg/store/db/operator.go b/pkg/store/db/operator.go index 786bc8a..c9098ec 100644 --- a/pkg/store/db/operator.go +++ b/pkg/store/db/operator.go @@ -80,6 +80,12 @@ func createTableIfNotExists() (err error) { return err } } + if !db.Migrator().HasColumn(&model.DevApp{}, "Owner") { + err = db.Migrator().AddColumn(&model.DevApp{}, "Owner") + if err != nil { + return err + } + } } if !db.Migrator().HasTable(model.DevContainers{}) { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 2c6a211..2fbf51f 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -3,9 +3,7 @@ package utils import ( "context" - "github.com/beclab/devbox/pkg/constants" "github.com/beclab/oachecker" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" @@ -48,14 +46,14 @@ func GetAdminUsername(ctx context.Context) (string, error) { return admin, nil } -func GetAppConfig(data []byte) (*oachecker.AppConfiguration, error) { +func GetAppConfig(owner string, data []byte) (*oachecker.AppConfiguration, error) { admin, err := GetAdminUsername(context.TODO()) if err != nil { return nil, err } opts := []func(map[string]interface{}){ oachecker.WithAdmin(admin), - oachecker.WithOwner(constants.Owner), + oachecker.WithOwner(owner), } appcfg, err := oachecker.GetAppConfigurationFromContent(data, opts...) return appcfg, nil diff --git a/pkg/webhook/funcs.go b/pkg/webhook/funcs.go index 4e99020..a8ae4f8 100644 --- a/pkg/webhook/funcs.go +++ b/pkg/webhook/funcs.go @@ -5,12 +5,12 @@ import ( "encoding/json" "errors" "fmt" + "os" "path/filepath" "sort" "strconv" "strings" - "github.com/beclab/devbox/pkg/constants" "github.com/beclab/devbox/pkg/development/container" "github.com/beclab/devbox/pkg/development/envoy" "github.com/beclab/devbox/pkg/development/helm" @@ -228,8 +228,10 @@ func (wh *Webhook) MutatePodContainers(ctx context.Context, namespace string, ra } pod.Annotations[envoy.UUIDAnnotation] = proxyUUID.String() - realapp := strings.TrimSuffix(app, "-dev") - appcfg, err := helm.GetAppCfg(realapp, baseDir) + appManagerName := fmt.Sprintf("%s-%s", namespace, app) + + //realapp := strings.TrimSuffix(app, "-dev") + appcfg, err := helm.GetAppCfg(appManagerName) if err != nil { return nil, err } @@ -389,7 +391,7 @@ func (wh *Webhook) mutateContainerToDevContainer(ctx context.Context, pod *corev volumeMounts = newVolMnts directoryOrCreateType := corev1.HostPathDirectoryOrCreate - userCacheDir, err := wh.getUserCacheDir(ctx) + //userCacheDir, err := wh.getUserCacheDir(ctx) if err != nil { return nil, err } @@ -399,7 +401,7 @@ func (wh *Webhook) mutateContainerToDevContainer(ctx context.Context, pod *corev VolumeSource: corev1.VolumeSource{ HostPath: &corev1.HostPathVolumeSource{ Type: &directoryOrCreateType, - Path: filepath.Join(userCacheDir, "studio", devcontainer.AppName), + Path: filepath.Join(os.Getenv("ROOTFS_DIR"), "studio", devcontainer.AppName), }, }, }) @@ -419,53 +421,53 @@ func (wh *Webhook) mutateContainerToDevContainer(ctx context.Context, pod *corev return nil, nil } -func (wh *Webhook) getUserspaceDir(ctx context.Context) (string, error) { - namespace := "user-space-" + constants.Owner - bfl, err := wh.KubeClient.AppsV1().StatefulSets(namespace).Get(ctx, "bfl", metav1.GetOptions{}) - if err != nil { - klog.Error("get user's bfl error, ", err) - return "", err - } - - dir, ok := bfl.Annotations["userspace_hostpath"] - if !ok { - klog.Error("user's space not found, ", err) - return "", errors.New("userspace not found") - } - - return dir, nil -} - -func (wh *Webhook) getUserCacheDir(ctx context.Context) (string, error) { - namespace := "user-space-" + constants.Owner - bfl, err := wh.KubeClient.AppsV1().StatefulSets(namespace).Get(ctx, "bfl", metav1.GetOptions{}) - if err != nil { - klog.Error("get user's bfl error, ", err) - return "", err - } - dir, ok := bfl.Annotations["appcache_hostpath"] - if !ok { - klog.Error("user's cache dir not found, ", err) - return "", errors.New("user cache dir not found") - } - return dir, nil -} - -func (wh *Webhook) getUserApplicationDir(ctx context.Context) (string, error) { - dir, err := wh.getUserspaceDir(ctx) - if err != nil { - return "", err - } - return filepath.Join(dir, "Application"), nil -} - -func (wh *Webhook) getUserHomeDir(ctx context.Context) (string, error) { - dir, err := wh.getUserspaceDir(ctx) - if err != nil { - return "", err - } - return filepath.Join(dir, "Home"), nil -} +//func (wh *Webhook) getUserspaceDir(ctx context.Context) (string, error) { +// namespace := "user-space-" + constants.Owner +// bfl, err := wh.KubeClient.AppsV1().StatefulSets(namespace).Get(ctx, "bfl", metav1.GetOptions{}) +// if err != nil { +// klog.Error("get user's bfl error, ", err) +// return "", err +// } +// +// dir, ok := bfl.Annotations["userspace_hostpath"] +// if !ok { +// klog.Error("user's space not found, ", err) +// return "", errors.New("userspace not found") +// } +// +// return dir, nil +//} + +//func (wh *Webhook) getUserCacheDir(ctx context.Context) (string, error) { +// namespace := "user-space-" + constants.Owner +// bfl, err := wh.KubeClient.AppsV1().StatefulSets(namespace).Get(ctx, "bfl", metav1.GetOptions{}) +// if err != nil { +// klog.Error("get user's bfl error, ", err) +// return "", err +// } +// dir, ok := bfl.Annotations["appcache_hostpath"] +// if !ok { +// klog.Error("user's cache dir not found, ", err) +// return "", errors.New("user cache dir not found") +// } +// return dir, nil +//} + +//func (wh *Webhook) getUserApplicationDir(ctx context.Context) (string, error) { +// dir, err := wh.getUserspaceDir(ctx) +// if err != nil { +// return "", err +// } +// return filepath.Join(dir, "Application"), nil +//} + +//func (wh *Webhook) getUserHomeDir(ctx context.Context) (string, error) { +// dir, err := wh.getUserspaceDir(ctx) +// if err != nil { +// return "", err +// } +// return filepath.Join(dir, "Home"), nil +//} func (wh *Webhook) MutateIm(ctx context.Context, raw []byte, proxyUUID uuid.UUID) (patch []byte, err error) { var obj unstructured.Unstructured diff --git a/pkg/webhook/setup.go b/pkg/webhook/setup.go index a7fc858..4f67aa7 100644 --- a/pkg/webhook/setup.go +++ b/pkg/webhook/setup.go @@ -100,7 +100,7 @@ func (wh *Webhook) CreateOrUpdateDevContainerMutatingWebhook() error { { Key: constants.DevOwnerLabel, Operator: metav1.LabelSelectorOpIn, - Values: []string{constants.Owner}, + Values: []string{"true"}, }, }, }, @@ -252,7 +252,7 @@ func (wh *Webhook) CreateOrUpdateImageManagerMutatingWebhook() error { { Key: constants.DevOwnerLabel, Operator: metav1.LabelSelectorOpIn, - Values: []string{constants.Owner}, + Values: []string{"true"}, }, }, },