diff --git a/modules/k8s/Dockerfile.controller-manager b/modules/k8s/Dockerfile.controller-manager index 628d9d7fee..865fec2e94 100644 --- a/modules/k8s/Dockerfile.controller-manager +++ b/modules/k8s/Dockerfile.controller-manager @@ -1,4 +1,4 @@ -FROM golang:1.23 AS builder +FROM golang:1.24 AS builder WORKDIR /workspace @@ -14,7 +14,8 @@ ENV HTTP_PROXY="" ENV HTTPS_PROXY="" ENV http_proxy="" ENV https_proxy="" -ENV GOPROXY=https://goproxy.cn,direct +ENV GOPROXY=https://goproxy.io,direct +ENV GOSUMDB=sum.golang.org # 下载依赖 RUN go mod download diff --git a/modules/k8s/client/client.go b/modules/k8s/client/client.go deleted file mode 100644 index de67a0f232..0000000000 --- a/modules/k8s/client/client.go +++ /dev/null @@ -1,51 +0,0 @@ -package client - -import ( - "context" - - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - - appsv1 "code.gitea.io/gitea/modules/k8s/api/devcontainer/v1" -) - -// DevStarClient 提供操作 DevContainerApp 资源的方法 -type DevStarClient struct { - client client.Client -} - -// NewDevStarClient 创建一个新的客户端 -func NewDevStarClient(c client.Client) *DevStarClient { - return &DevStarClient{ - client: c, - } -} - -// GetDevContainerApp 获取 DevContainerApp 资源 -func (c *DevStarClient) GetDevContainerApp(ctx context.Context, name, namespace string) (*appsv1.DevcontainerApp, error) { - app := &appsv1.DevcontainerApp{} - err := c.client.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, app) - return app, err -} - -// CreateDevContainerApp 创建 DevContainerApp 资源 -func (c *DevStarClient) CreateDevContainerApp(ctx context.Context, app *appsv1.DevcontainerApp) error { - return c.client.Create(ctx, app) -} - -// UpdateDevContainerApp 更新 DevContainerApp 资源 -func (c *DevStarClient) UpdateDevContainerApp(ctx context.Context, app *appsv1.DevcontainerApp) error { - return c.client.Update(ctx, app) -} - -// DeleteDevContainerApp 删除 DevContainerApp 资源 -func (c *DevStarClient) DeleteDevContainerApp(ctx context.Context, app *appsv1.DevcontainerApp) error { - return c.client.Delete(ctx, app) -} - -// ListDevContainerApps 列出 DevContainerApp 资源 -func (c *DevStarClient) ListDevContainerApps(ctx context.Context, namespace string) (*appsv1.DevcontainerAppList, error) { - list := &appsv1.DevcontainerAppList{} - err := c.client.List(ctx, list, client.InNamespace(namespace)) - return list, err -} diff --git a/modules/k8s/k8s.go b/modules/k8s/k8s.go index 5dfdd6ae0c..1c211435e9 100644 --- a/modules/k8s/k8s.go +++ b/modules/k8s/k8s.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "strings" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -67,7 +68,21 @@ func GetKubernetesClient(ctx context.Context, kubeconfig []byte, contextName str } applyClientDefaults(config) - return dynamicclient.NewForConfig(config) + + // 强制跳过 TLS 证书校验(无论 kubeconfig 是否声明 insecure-skip-tls-verify) + // 同时清空 CA 配置 + config.TLSClientConfig.Insecure = true + config.TLSClientConfig.CAData = nil + config.TLSClientConfig.CAFile = "" + + // 尝试创建客户端,如果TLS验证失败则自动跳过验证 + client, err := dynamicclient.NewForConfig(config) + if err != nil { + // 再次兜底:若识别为 TLS 错误,已 Insecure,无需再次设置;否则将错误上抛 + return nil, fmt.Errorf("failed to create k8s client: %v", err) + } + + return client, nil } // restConfigFromKubeconfigBytes 基于 kubeconfig 内容构造 *rest.Config,支持指定 context(为空则使用 current-context) @@ -401,3 +416,36 @@ func ListDevcontainers(ctx context.Context, client dynamic_client.Interface, opt } return devcontainerList, nil } + +// isTLSCertificateError 检查错误是否是TLS证书验证错误 +func isTLSCertificateError(err error) bool { + if err == nil { + return false + } + + errStr := err.Error() + + // 检查常见的TLS证书验证错误(尽量宽松,覆盖更多 x509 报错文案) + tlsErrorPatterns := []string{ + "tls: failed to verify certificate", + "x509:", + "x509: certificate", + "cannot validate certificate", + "doesn't contain any IP SANs", + "certificate is valid for", + "certificate signed by unknown authority", + "unknown authority", + "self-signed certificate", + "certificate has expired", + "certificate is not valid", + "invalid certificate", + } + + for _, pattern := range tlsErrorPatterns { + if strings.Contains(errStr, pattern) { + return true + } + } + + return false +} diff --git a/routers/web/user/setting/appstore.go b/routers/web/user/setting/appstore.go index b71cead293..1a106b8c5d 100644 --- a/routers/web/user/setting/appstore.go +++ b/routers/web/user/setting/appstore.go @@ -9,6 +9,7 @@ import ( "strings" appstore_model "code.gitea.io/gitea/models/appstore" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/services/appstore" @@ -23,6 +24,7 @@ const ( func AppStore(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings.appstore") ctx.Data["PageIsSettingsAppStore"] = true + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) ctx.HTML(http.StatusOK, tplSettingsAppStore) } @@ -222,6 +224,12 @@ func AppStoreInstall(ctx *context.Context) { return } + // 权限校验:仅管理员可选择默认位置 + if installTarget == "local" && (ctx.Doer == nil || !ctx.Doer.IsAdmin) { + ctx.JSON(403, map[string]string{"error": "仅管理员可选择默认位置"}) + return + } + // 创建 manager 并执行安装 manager := appstore.NewManager(ctx) if err := manager.InstallApp(appID, configJSON, installTarget, kubeconfig, kubeconfigContext); err != nil { @@ -245,7 +253,7 @@ func AppStoreInstall(ctx *context.Context) { // 安装成功 if installTarget == "kubeconfig" && kubeconfig != "" { - ctx.Flash.Success("应用已成功安装到外部 Kubernetes 集群") + ctx.Flash.Success("应用已成功安装到指定位置") } else { ctx.Flash.Success("应用配置已准备完成,本地安装功能开发中") } @@ -294,7 +302,7 @@ func AppStoreUninstall(ctx *context.Context) { // 卸载成功 if installTarget == "kubeconfig" && kubeconfig != "" { - ctx.Flash.Success("应用已成功从外部 Kubernetes 集群卸载") + ctx.Flash.Success("应用已成功从指定位置卸载") } else { ctx.Flash.Success("本地卸载功能开发中") } @@ -307,6 +315,7 @@ func AppStoreConfigure(ctx *context.Context) { // TODO: Load app configuration form ctx.Data["Title"] = "App Configuration" ctx.Data["PageIsSettingsAppStore"] = true + ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) ctx.HTML(http.StatusOK, tplSettingsAppStore) } diff --git a/routers/web/web.go b/routers/web/web.go index fafcb35bfe..7f201970a1 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -702,6 +702,16 @@ func registerWebRoutes(m *web.Router) { m.Get("", user_setting.BlockedUsers) m.Post("", web.Bind(forms.BlockUserForm{}), user_setting.BlockedUsersPost) }) + // appstore + m.Group("/appstore", func() { + m.Get("", user_setting.AppStore) + m.Get("/api/{action}", user_setting.AppStoreAPI) + m.Post("/api/add", user_setting.AddAppAPI) + m.Post("/install/{app_id}", user_setting.AppStoreInstall) + m.Post("/uninstall/{app_id}", user_setting.AppStoreUninstall) + m.Get("/configure/{app_id}", user_setting.AppStoreConfigure) + m.Post("/configure/{app_id}", user_setting.AppStoreConfigurePost) + }) }, reqSignIn, ctxDataSet("PageIsUserSettings", true, "EnablePackages", setting.Packages.Enabled, "EnableNotifyMail", setting.Service.EnableNotifyMail)) m.Group("/user", func() { @@ -1697,14 +1707,4 @@ func registerWebRoutes(m *web.Router) { defer routing.RecordFuncInfo(ctx, routing.GetFuncInfo(ctx.NotFound, "WebNotFound"))() ctx.NotFound(nil) }) - - m.Group("/user/settings/appstore", func() { - m.Get("", user_setting.AppStore) - m.Get("/api/{action}", user_setting.AppStoreAPI) - m.Post("/api/add", user_setting.AddAppAPI) - m.Post("/install/{app_id}", user_setting.AppStoreInstall) - m.Post("/uninstall/{app_id}", user_setting.AppStoreUninstall) - m.Get("/configure/{app_id}", user_setting.AppStoreConfigure) - m.Post("/configure/{app_id}", user_setting.AppStoreConfigurePost) - }) } diff --git a/templates/user/settings/appstore.tmpl b/templates/user/settings/appstore.tmpl index fcc60f81f7..d53be6c54e 100644 --- a/templates/user/settings/appstore.tmpl +++ b/templates/user/settings/appstore.tmpl @@ -1,4 +1,5 @@ -{{template "user/settings/layout_head" .}} +{{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings appstore")}} +