k8s客户端获取从kubeconfig迁移到token

This commit is contained in:
panshuxiao
2025-11-25 14:24:05 +08:00
repo.diff.parent f08a0d02c3
repo.diff.commit 1d6ba90c2f
repo.diff.stats_desc%!(EXTRA int=10, int=323, int=202)

4
go.mod
repo.diff.view_file

@@ -161,10 +161,10 @@ require (
github.com/google/gnostic-models v0.6.9 // indirect github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/term v0.32.0 // indirect golang.org/x/term v0.32.0 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
@@ -179,9 +179,7 @@ require (
dario.cat/mergo v1.0.1 // indirect dario.cat/mergo v1.0.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
github.com/ArtisanCloud/PowerLibs/v3 v3.3.2
github.com/ArtisanCloud/PowerSocialite/v3 v3.0.8 // indirect github.com/ArtisanCloud/PowerSocialite/v3 v3.0.8 // indirect
github.com/ArtisanCloud/PowerWeChat/v3 v3.4.21
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
github.com/DataDog/zstd v1.5.7 // indirect github.com/DataDog/zstd v1.5.7 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect

2
go.sum
repo.diff.view_file

@@ -871,6 +871,8 @@ golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ug
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=

repo.diff.view_file

@@ -27,9 +27,11 @@ type UserAppInstance struct {
MergedApp string `xorm:"TEXT"` // 合并后的完整应用配置JSON MergedApp string `xorm:"TEXT"` // 合并后的完整应用配置JSON
// 部署信息 // 部署信息
DeployType string `xorm:"NOT NULL"` // 实际部署类型 (kubernetes/docker) DeployType string `xorm:"NOT NULL"` // 实际部署类型 (kubernetes/docker)
Kubeconfig string `xorm:"TEXT"` // Kubeconfig内容加密存储
KubeconfigContext string // Kubeconfig context // Kubernetes 凭据URL + Token
K8sURL string `xorm:"TEXT"`
K8sToken string `xorm:"TEXT"`
// 元数据 // 元数据
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`

repo.diff.view_file

@@ -0,0 +1,29 @@
/*
* Copyright (c) Mengning Software. 2025. All rights reserved.
* Authors: DevStar Team, panshuxiao
* Create: 2025-11-24
* Description: Migration dv5 adding Kubernetes credential columns.
*/
package devstar_v1_0
import (
"xorm.io/xorm"
)
// AddK8sCredentialColumns adds k8s_url and k8s_token columns to user_app_instance.
func AddK8sCredentialColumns(x *xorm.Engine) error {
if _, err := x.Exec("ALTER TABLE user_app_instance ADD COLUMN k8s_url TEXT DEFAULT ''"); err != nil {
return ErrMigrateDevstarDatabase{
Step: "add column 'k8s_url' to user_app_instance",
Message: err.Error(),
}
}
if _, err := x.Exec("ALTER TABLE user_app_instance ADD COLUMN k8s_token TEXT DEFAULT ''"); err != nil {
return ErrMigrateDevstarDatabase{
Step: "add column 'k8s_token' to user_app_instance",
Message: err.Error(),
}
}
return nil
}

repo.diff.view_file

@@ -92,6 +92,52 @@ func GetKubernetesClient(ctx context.Context, kubeconfig []byte, contextName str
return client, nil return client, nil
} }
// GetKubernetesClientWithToken 通过用户提供的 k8sURL 和 token 获取动态客户端
// 如果 k8sURL 和 token 为空,则优先使用配置文件中的 K8sConfig.Url 和 K8sConfig.Token
// 如果配置文件也未配置,则返回错误(不回退到 kubeconfig
func GetKubernetesClientWithToken(ctx context.Context, k8sURL, token string) (dynamicclient.Interface, error) {
// 如果未提供 URL 和 token优先使用配置文件中的设置
if k8sURL == "" || token == "" {
// 优先使用配置文件中的 K8s 配置
if setting.K8sConfig.Enable && setting.K8sConfig.Url != "" && setting.K8sConfig.Token != "" {
k8sURL = setting.K8sConfig.Url
token = setting.K8sConfig.Token
log.Info("使用配置文件中的 K8s 配置: URL=%s", k8sURL)
}
}
// 如果仍然没有 URL 和 token直接返回错误
if k8sURL == "" || token == "" {
return nil, fmt.Errorf("k8sURL and token are required, neither provided nor found in config")
}
// 使用 token 认证创建配置
config := &clientgorest.Config{
Host: k8sURL,
BearerToken: token,
TLSClientConfig: clientgorest.TLSClientConfig{
Insecure: true,
},
}
applyClientDefaults(config)
// 强制跳过 TLS 证书校验(与 GetKubernetesClient 保持一致)
// 同时清空 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 // restConfigFromKubeconfigBytes 基于 kubeconfig 内容构造 *rest.Config支持指定 context为空则使用 current-context
func restConfigFromKubeconfigBytes(kubeconfig []byte, contextName string) (*clientgorest.Config, error) { func restConfigFromKubeconfigBytes(kubeconfig []byte, contextName string) (*clientgorest.Config, error) {

repo.diff.view_file

@@ -257,8 +257,8 @@ func AppStoreInstall(ctx *context.Context) {
appID := ctx.FormString("app_id") appID := ctx.FormString("app_id")
configJSON := ctx.FormString("config") configJSON := ctx.FormString("config")
installTarget := ctx.FormString("install_target") installTarget := ctx.FormString("install_target")
kubeconfig := ctx.FormString("kubeconfig") k8sURL := ctx.FormString("k8s_url")
kubeconfigContext := ctx.FormString("kubeconfig_context") token := ctx.FormString("k8s_token")
if appID == "" { if appID == "" {
ctx.JSON(400, map[string]string{"error": "应用ID不能为空"}) ctx.JSON(400, map[string]string{"error": "应用ID不能为空"})
@@ -273,7 +273,7 @@ func AppStoreInstall(ctx *context.Context) {
// 创建 manager 并执行安装 // 创建 manager 并执行安装
manager := appstore.NewManager(ctx, ctx.Doer.ID) manager := appstore.NewManager(ctx, ctx.Doer.ID)
if err := manager.InstallApp(appID, configJSON, installTarget, kubeconfig, kubeconfigContext); err != nil { if err := manager.InstallApp(appID, configJSON, installTarget, k8sURL, token); err != nil {
// 根据错误类型返回相应的状态码和消息 // 根据错误类型返回相应的状态码和消息
if appErr, ok := err.(*appstore.AppStoreError); ok { if appErr, ok := err.(*appstore.AppStoreError); ok {
switch appErr.Code { switch appErr.Code {
@@ -293,7 +293,7 @@ func AppStoreInstall(ctx *context.Context) {
} }
// 安装成功 // 安装成功
if installTarget == "kubeconfig" && kubeconfig != "" { if installTarget == "kubeconfig" && k8sURL != "" && token != "" {
ctx.Flash.Success("应用已成功安装到自定义位置") ctx.Flash.Success("应用已成功安装到自定义位置")
} else { } else {
ctx.Flash.Success("应用已成功安装到默认位置") ctx.Flash.Success("应用已成功安装到默认位置")
@@ -324,8 +324,8 @@ func AppStoreUpdate(ctx *context.Context) {
appID := ctx.FormString("app_id") appID := ctx.FormString("app_id")
configJSON := ctx.FormString("config") configJSON := ctx.FormString("config")
installTarget := ctx.FormString("install_target") installTarget := ctx.FormString("install_target")
kubeconfig := ctx.FormString("kubeconfig") k8sURL := ctx.FormString("k8s_url")
kubeconfigContext := ctx.FormString("kubeconfig_context") token := ctx.FormString("k8s_token")
if appID == "" { if appID == "" {
ctx.JSON(400, map[string]string{"error": "应用ID不能为空"}) ctx.JSON(400, map[string]string{"error": "应用ID不能为空"})
@@ -340,7 +340,7 @@ func AppStoreUpdate(ctx *context.Context) {
// 创建 manager 并执行更新 // 创建 manager 并执行更新
manager := appstore.NewManager(ctx, ctx.Doer.ID) manager := appstore.NewManager(ctx, ctx.Doer.ID)
if err := manager.UpdateInstalledApp(appID, configJSON, installTarget, kubeconfig, kubeconfigContext); err != nil { if err := manager.UpdateInstalledApp(appID, configJSON, installTarget, k8sURL, token); err != nil {
// 根据错误类型返回相应的状态码和消息 // 根据错误类型返回相应的状态码和消息
if appErr, ok := err.(*appstore.AppStoreError); ok { if appErr, ok := err.(*appstore.AppStoreError); ok {
switch appErr.Code { switch appErr.Code {
@@ -360,7 +360,7 @@ func AppStoreUpdate(ctx *context.Context) {
} }
// 更新成功 // 更新成功
if installTarget == "kubeconfig" && kubeconfig != "" { if installTarget == "kubeconfig" && k8sURL != "" && token != "" {
ctx.Flash.Success("应用已成功更新到自定义位置") ctx.Flash.Success("应用已成功更新到自定义位置")
} else { } else {
ctx.Flash.Success("应用已成功更新到默认位置") ctx.Flash.Success("应用已成功更新到默认位置")
@@ -396,7 +396,7 @@ func AppStoreUninstall(ctx *context.Context) {
} }
// 创建 manager 并执行卸载 // 创建 manager 并执行卸载
// UninstallApp 会自动从数据库读取 kubeconfig 判断是外部集群还是本地集群 // UninstallApp 会自动从数据库读取保存的 Kubernetes 凭据判断是外部集群还是本地集群
manager := appstore.NewManager(ctx, ctx.Doer.ID) manager := appstore.NewManager(ctx, ctx.Doer.ID)
if err := manager.UninstallApp(appID); err != nil { if err := manager.UninstallApp(appID); err != nil {
// 根据错误类型返回相应的状态码和消息 // 根据错误类型返回相应的状态码和消息
@@ -445,8 +445,8 @@ func AppStoreResume(ctx *context.Context) {
// 解析表单数据 // 解析表单数据
appID := ctx.FormString("app_id") appID := ctx.FormString("app_id")
installTarget := ctx.FormString("install_target") installTarget := ctx.FormString("install_target")
kubeconfig := ctx.FormString("kubeconfig") k8sURL := ctx.FormString("k8s_url")
kubeconfigContext := ctx.FormString("kubeconfig_context") token := ctx.FormString("k8s_token")
if appID == "" { if appID == "" {
ctx.JSON(400, map[string]string{"error": "应用ID不能为空"}) ctx.JSON(400, map[string]string{"error": "应用ID不能为空"})
@@ -455,7 +455,7 @@ func AppStoreResume(ctx *context.Context) {
// 创建 manager 并执行恢复 // 创建 manager 并执行恢复
manager := appstore.NewManager(ctx, ctx.Doer.ID) manager := appstore.NewManager(ctx, ctx.Doer.ID)
if err := manager.ResumeApp(appID, installTarget, []byte(kubeconfig), kubeconfigContext); err != nil { if err := manager.ResumeApp(appID, installTarget, k8sURL, token); err != nil {
// 根据错误类型返回相应的状态码和消息 // 根据错误类型返回相应的状态码和消息
if appErr, ok := err.(*appstore.AppStoreError); ok { if appErr, ok := err.(*appstore.AppStoreError); ok {
switch appErr.Code { switch appErr.Code {
@@ -475,7 +475,7 @@ func AppStoreResume(ctx *context.Context) {
} }
// 恢复成功 // 恢复成功
if installTarget == "kubeconfig" && kubeconfig != "" { if installTarget == "kubeconfig" && k8sURL != "" && token != "" {
ctx.Flash.Success("应用已成功在指定位置恢复") ctx.Flash.Success("应用已成功在指定位置恢复")
} else { } else {
ctx.Flash.Success("应用已成功恢复") ctx.Flash.Success("应用已成功恢复")
@@ -506,8 +506,8 @@ func AppStoreStop(ctx *context.Context) {
// 解析表单数据 // 解析表单数据
appID := ctx.FormString("app_id") appID := ctx.FormString("app_id")
installTarget := ctx.FormString("install_target") installTarget := ctx.FormString("install_target")
kubeconfig := ctx.FormString("kubeconfig") k8sURL := ctx.FormString("k8s_url")
kubeconfigContext := ctx.FormString("kubeconfig_context") token := ctx.FormString("k8s_token")
if appID == "" { if appID == "" {
ctx.JSON(400, map[string]string{"error": "应用ID不能为空"}) ctx.JSON(400, map[string]string{"error": "应用ID不能为空"})
@@ -516,7 +516,7 @@ func AppStoreStop(ctx *context.Context) {
// 创建 manager 并执行暂停 // 创建 manager 并执行暂停
manager := appstore.NewManager(ctx, ctx.Doer.ID) manager := appstore.NewManager(ctx, ctx.Doer.ID)
if err := manager.StopApp(appID, installTarget, []byte(kubeconfig), kubeconfigContext); err != nil { if err := manager.StopApp(appID, installTarget, k8sURL, token); err != nil {
// 根据错误类型返回相应的状态码和消息 // 根据错误类型返回相应的状态码和消息
if appErr, ok := err.(*appstore.AppStoreError); ok { if appErr, ok := err.(*appstore.AppStoreError); ok {
switch appErr.Code { switch appErr.Code {
@@ -536,7 +536,7 @@ func AppStoreStop(ctx *context.Context) {
} }
// 暂停成功 // 暂停成功
if installTarget == "kubeconfig" && kubeconfig != "" { if installTarget == "kubeconfig" && k8sURL != "" && token != "" {
ctx.Flash.Success("应用已成功在指定位置暂停") ctx.Flash.Success("应用已成功在指定位置暂停")
} else { } else {
ctx.Flash.Success("应用已成功暂停") ctx.Flash.Success("应用已成功暂停")

repo.diff.view_file

@@ -1,6 +1,6 @@
{ {
"id": "mengningsoftware-2", "id": "mengningsoftware",
"name": "mengningsoftware-2", "name": "mengningsoftware",
"description": "High-performance HTTP server and reverse proxy", "description": "High-performance HTTP server and reverse proxy",
"category": "web-server", "category": "web-server",
"tags": ["web", "proxy", "http", "server"], "tags": ["web", "proxy", "http", "server"],

repo.diff.view_file

@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/k8s/application" "code.gitea.io/gitea/modules/k8s/application"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
dynamicclient "k8s.io/client-go/dynamic"
) )
// K8sManager handles Kubernetes-specific application operations // K8sManager handles Kubernetes-specific application operations
@@ -23,6 +24,12 @@ type K8sManager struct {
ctx context.Context ctx context.Context
} }
// K8sCredential describes how to reach a Kubernetes cluster.
type K8sCredential struct {
K8sURL string
Token string
}
// NewK8sManager creates a new Kubernetes manager // NewK8sManager creates a new Kubernetes manager
func NewK8sManager(ctx context.Context) *K8sManager { func NewK8sManager(ctx context.Context) *K8sManager {
return &K8sManager{ return &K8sManager{
@@ -30,8 +37,16 @@ func NewK8sManager(ctx context.Context) *K8sManager {
} }
} }
// buildK8sClient creates a dynamic client from the provided credential.
func (km *K8sManager) buildK8sClient(cred *K8sCredential) (dynamicclient.Interface, error) {
if cred != nil && cred.K8sURL != "" && cred.Token != "" {
return k8s.GetKubernetesClientWithToken(km.ctx, cred.K8sURL, cred.Token)
}
return k8s.GetKubernetesClientWithToken(km.ctx, "", "")
}
// InstallAppToKubernetes installs an application to a Kubernetes cluster // InstallAppToKubernetes installs an application to a Kubernetes cluster
func (km *K8sManager) InstallAppToKubernetes(app *App, kubeconfig []byte, contextName string) error { func (km *K8sManager) InstallAppToKubernetes(app *App, cred *K8sCredential) error {
// Validate that the app supports Kubernetes deployment // Validate that the app supports Kubernetes deployment
// 优先检查实际部署类型,如果为空则检查支持的部署类型 // 优先检查实际部署类型,如果为空则检查支持的部署类型
if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" { if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" {
@@ -48,7 +63,7 @@ func (km *K8sManager) InstallAppToKubernetes(app *App, kubeconfig []byte, contex
} }
// Get Kubernetes client // Get Kubernetes client
k8sClient, err := k8s.GetKubernetesClient(km.ctx, kubeconfig, contextName) k8sClient, err := km.buildK8sClient(cred)
if err != nil { if err != nil {
return &AppStoreError{ return &AppStoreError{
Code: "KUBERNETES_CLIENT_ERROR", Code: "KUBERNETES_CLIENT_ERROR",
@@ -81,7 +96,7 @@ func (km *K8sManager) InstallAppToKubernetes(app *App, kubeconfig []byte, contex
} }
// UninstallAppFromKubernetes uninstalls an application from a Kubernetes cluster // UninstallAppFromKubernetes uninstalls an application from a Kubernetes cluster
func (km *K8sManager) UninstallAppFromKubernetes(app *App, kubeconfig []byte, contextName string) error { func (km *K8sManager) UninstallAppFromKubernetes(app *App, cred *K8sCredential) error {
// Validate that the app supports Kubernetes deployment // Validate that the app supports Kubernetes deployment
// 优先检查实际部署类型,如果为空则检查支持的部署类型 // 优先检查实际部署类型,如果为空则检查支持的部署类型
if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" { if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" {
@@ -98,7 +113,7 @@ func (km *K8sManager) UninstallAppFromKubernetes(app *App, kubeconfig []byte, co
} }
// Get Kubernetes client // Get Kubernetes client
k8sClient, err := k8s.GetKubernetesClient(km.ctx, kubeconfig, contextName) k8sClient, err := km.buildK8sClient(cred)
if err != nil { if err != nil {
return &AppStoreError{ return &AppStoreError{
Code: "KUBERNETES_CLIENT_ERROR", Code: "KUBERNETES_CLIENT_ERROR",
@@ -131,7 +146,7 @@ func (km *K8sManager) UninstallAppFromKubernetes(app *App, kubeconfig []byte, co
} }
// GetAppFromKubernetes gets an application from a Kubernetes cluster // GetAppFromKubernetes gets an application from a Kubernetes cluster
func (km *K8sManager) GetAppFromKubernetes(app *App, kubeconfig []byte, contextName string) (interface{}, error) { func (km *K8sManager) GetAppFromKubernetes(app *App, cred *K8sCredential) (interface{}, error) {
// Validate that the app supports Kubernetes deployment // Validate that the app supports Kubernetes deployment
// 优先检查实际部署类型,如果为空则检查支持的部署类型 // 优先检查实际部署类型,如果为空则检查支持的部署类型
if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" { if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" {
@@ -148,7 +163,7 @@ func (km *K8sManager) GetAppFromKubernetes(app *App, kubeconfig []byte, contextN
} }
// Get Kubernetes client // Get Kubernetes client
k8sClient, err := k8s.GetKubernetesClient(km.ctx, kubeconfig, contextName) k8sClient, err := km.buildK8sClient(cred)
if err != nil { if err != nil {
return nil, &AppStoreError{ return nil, &AppStoreError{
Code: "KUBERNETES_CLIENT_ERROR", Code: "KUBERNETES_CLIENT_ERROR",
@@ -172,7 +187,7 @@ func (km *K8sManager) GetAppFromKubernetes(app *App, kubeconfig []byte, contextN
} }
// ListAppsFromKubernetes lists applications from a Kubernetes cluster // ListAppsFromKubernetes lists applications from a Kubernetes cluster
func (km *K8sManager) ListAppsFromKubernetes(app *App, kubeconfig []byte, contextName string) (*applicationv1.ApplicationList, error) { func (km *K8sManager) ListAppsFromKubernetes(app *App, cred *K8sCredential) (*applicationv1.ApplicationList, error) {
// Validate that the app supports Kubernetes deployment // Validate that the app supports Kubernetes deployment
// 优先检查实际部署类型,如果为空则检查支持的部署类型 // 优先检查实际部署类型,如果为空则检查支持的部署类型
if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" { if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" {
@@ -189,7 +204,7 @@ func (km *K8sManager) ListAppsFromKubernetes(app *App, kubeconfig []byte, contex
} }
// Get Kubernetes client // Get Kubernetes client
k8sClient, err := k8s.GetKubernetesClient(km.ctx, kubeconfig, contextName) k8sClient, err := km.buildK8sClient(cred)
if err != nil { if err != nil {
return nil, &AppStoreError{ return nil, &AppStoreError{
Code: "KUBERNETES_CLIENT_ERROR", Code: "KUBERNETES_CLIENT_ERROR",
@@ -213,7 +228,7 @@ func (km *K8sManager) ListAppsFromKubernetes(app *App, kubeconfig []byte, contex
} }
// UpdateAppInKubernetes updates an application in a Kubernetes cluster // UpdateAppInKubernetes updates an application in a Kubernetes cluster
func (km *K8sManager) UpdateAppInKubernetes(app *App, kubeconfig []byte, contextName string) error { func (km *K8sManager) UpdateAppInKubernetes(app *App, cred *K8sCredential) error {
// Validate that the app supports Kubernetes deployment // Validate that the app supports Kubernetes deployment
// 优先检查实际部署类型,如果为空则检查支持的部署类型 // 优先检查实际部署类型,如果为空则检查支持的部署类型
if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" { if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" {
@@ -230,7 +245,7 @@ func (km *K8sManager) UpdateAppInKubernetes(app *App, kubeconfig []byte, context
} }
// Get Kubernetes client // Get Kubernetes client
k8sClient, err := k8s.GetKubernetesClient(km.ctx, kubeconfig, contextName) k8sClient, err := km.buildK8sClient(cred)
if err != nil { if err != nil {
return &AppStoreError{ return &AppStoreError{
Code: "KUBERNETES_CLIENT_ERROR", Code: "KUBERNETES_CLIENT_ERROR",
@@ -240,7 +255,7 @@ func (km *K8sManager) UpdateAppInKubernetes(app *App, kubeconfig []byte, context
} }
// 先获取现有的应用,以保留元数据(特别是 resourceVersion // 先获取现有的应用,以保留元数据(特别是 resourceVersion
existingApp, err := km.GetAppFromKubernetes(app, kubeconfig, contextName) existingApp, err := km.GetAppFromKubernetes(app, cred)
if err != nil { if err != nil {
// 如果应用不存在,尝试创建新应用 // 如果应用不存在,尝试创建新应用
log.Warn("Application not found in Kubernetes, attempting to create new application: %v", err) log.Warn("Application not found in Kubernetes, attempting to create new application: %v", err)

repo.diff.view_file

@@ -40,6 +40,29 @@ func NewManager(ctx *gitea_context.Context, userID int64) *Manager {
} }
} }
func buildCredentialFromInput(installTarget, k8sURL, token string) *K8sCredential {
if installTarget == "kubeconfig" && k8sURL != "" && token != "" {
return &K8sCredential{
K8sURL: k8sURL,
Token: token,
}
}
return nil
}
func credentialFromInstance(instance *user_app_instance.UserAppInstance) *K8sCredential {
if instance == nil {
return nil
}
if instance.K8sURL != "" && instance.K8sToken != "" {
return &K8sCredential{
K8sURL: instance.K8sURL,
Token: instance.K8sToken,
}
}
return nil
}
// ListApps returns all available applications from database // ListApps returns all available applications from database
func (m *Manager) ListApps() ([]App, error) { func (m *Manager) ListApps() ([]App, error) {
appStores, err := appstore_model.ListAppStores(m.ctx, nil) appStores, err := appstore_model.ListAppStores(m.ctx, nil)
@@ -220,7 +243,7 @@ func (m *Manager) SearchApps(query string, category string, tags []string) ([]Ap
tagMatch := false tagMatch := false
for _, searchTag := range tags { for _, searchTag := range tags {
for _, appTag := range app.Tags { for _, appTag := range app.Tags {
if strings.ToLower(appTag) == strings.ToLower(searchTag) { if strings.EqualFold(appTag, searchTag) {
tagMatch = true tagMatch = true
break break
} }
@@ -439,7 +462,7 @@ func (m *Manager) ValidateUserConfig(appID string, userConfig UserConfig) error
} }
// InstallApp installs an application based on the provided parameters // InstallApp installs an application based on the provided parameters
func (m *Manager) InstallApp(appID string, configJSON string, installTarget string, kubeconfig string, kubeconfigContext string) error { func (m *Manager) InstallApp(appID string, configJSON string, installTarget string, k8sURL string, token string) error {
// 获取应用信息 // 获取应用信息
app, err := m.GetApp(appID) app, err := m.GetApp(appID)
if err != nil { if err != nil {
@@ -465,50 +488,51 @@ func (m *Manager) InstallApp(appID string, configJSON string, installTarget stri
deployType := mergedApp.Deploy.Type deployType := mergedApp.Deploy.Type
if deployType == "" { if deployType == "" {
// 如果 Deploy.Type 为空,根据 DeploymentType 推断 // 如果 Deploy.Type 为空,根据 DeploymentType 推断
if mergedApp.DeploymentType == "kubernetes" || mergedApp.DeploymentType == "both" { switch mergedApp.DeploymentType {
case "kubernetes", "both":
deployType = "kubernetes" deployType = "kubernetes"
} else if mergedApp.DeploymentType == "docker" { case "docker":
deployType = "docker" deployType = "docker"
} }
} }
// 根据安装目标和应用实际部署类型决定安装方式 inputCred := buildCredentialFromInput(installTarget, k8sURL, token)
if installTarget == "kubeconfig" && kubeconfig != "" { if installTarget == "kubeconfig" {
// 安装到外部 Kubernetes 集群 if inputCred == nil {
if err := m.InstallAppToKubernetes(mergedApp, []byte(kubeconfig), kubeconfigContext); err != nil { return &AppStoreError{
Code: "INVALID_K8S_CREDENTIAL",
Message: "自定义 Kubernetes 安装需要提供 API 地址和 Token",
}
}
if deployType != "kubernetes" {
return &AppStoreError{
Code: "DEPLOYMENT_TYPE_ERROR",
Message: "该应用不支持 Kubernetes 部署",
}
}
}
// 根据应用实际部署类型决定安装方式
log.Info("InstallApp: mergedApp.Deploy.Type = %s", mergedApp.Deploy.Type)
switch deployType {
case "kubernetes":
if err := m.InstallAppToKubernetes(mergedApp, inputCred); err != nil {
return &AppStoreError{ return &AppStoreError{
Code: "KUBERNETES_INSTALL_ERROR", Code: "KUBERNETES_INSTALL_ERROR",
Message: "Kubernetes 安装失败", Message: "Kubernetes 安装失败",
Details: err.Error(), Details: err.Error(),
} }
} }
} else { case "docker":
// 根据应用实际部署类型决定本地安装方式 // TODO: 实现 Docker 安装逻辑
log.Info("InstallApp: mergedApp.Deploy.Type = %s", mergedApp.Deploy.Type) return &AppStoreError{
switch deployType { Code: "NOT_IMPLEMENTED",
case "kubernetes": Message: "本地 Docker 安装功能开发中",
// 应用要部署到 Kubernetes安装到本地 K8s 集群 }
if err := m.InstallAppToKubernetes(mergedApp, nil, ""); err != nil { default:
return &AppStoreError{ return &AppStoreError{
Code: "KUBERNETES_INSTALL_ERROR", Code: "NOT_IMPLEMENTED",
Message: "本地 Kubernetes 安装失败", Message: "本地安装功能开发中",
Details: err.Error(),
}
}
case "docker":
// 应用要部署到 Docker安装到本地 Docker
// TODO: 实现 Docker 安装逻辑
return &AppStoreError{
Code: "NOT_IMPLEMENTED",
Message: "本地 Docker 安装功能开发中",
}
default:
// 未知部署类型,默认尝试 Docker
// TODO: 实现 Docker 安装逻辑
return &AppStoreError{
Code: "NOT_IMPLEMENTED",
Message: "本地安装功能开发中",
}
} }
} }
@@ -524,14 +548,16 @@ func (m *Manager) InstallApp(appID string, configJSON string, installTarget stri
} }
instance := &user_app_instance.UserAppInstance{ instance := &user_app_instance.UserAppInstance{
UserID: m.userID, UserID: m.userID,
AppID: appID, AppID: appID,
InstanceName: mergedApp.Name, // 使用应用名称作为实例名称 InstanceName: mergedApp.Name,
UserConfig: configJSON, UserConfig: configJSON,
MergedApp: string(mergedAppJSON), MergedApp: string(mergedAppJSON),
DeployType: deployType, DeployType: deployType,
Kubeconfig: kubeconfig, }
KubeconfigContext: kubeconfigContext, if inputCred != nil && deployType == "kubernetes" {
instance.K8sURL = inputCred.K8sURL
instance.K8sToken = inputCred.Token
} }
if err := user_app_instance.CreateUserAppInstance(m.ctx, instance); err != nil { if err := user_app_instance.CreateUserAppInstance(m.ctx, instance); err != nil {
@@ -549,7 +575,7 @@ func (m *Manager) InstallApp(appID string, configJSON string, installTarget stri
// UpdateInstalledApp updates an already installed application with new configuration // UpdateInstalledApp updates an already installed application with new configuration
// The update flow mirrors InstallApp: merge config → choose target → call K8s/Docker updater // The update flow mirrors InstallApp: merge config → choose target → call K8s/Docker updater
func (m *Manager) UpdateInstalledApp(appID string, configJSON string, installTarget string, kubeconfig string, kubeconfigContext string) error { func (m *Manager) UpdateInstalledApp(appID string, configJSON string, installTarget string, k8sURL string, token string) error {
// 获取用户的应用实例 // 获取用户的应用实例
instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID) instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID)
if err != nil { if err != nil {
@@ -589,31 +615,38 @@ func (m *Manager) UpdateInstalledApp(appID string, configJSON string, installTar
// 确定部署类型 // 确定部署类型
deployType := mergedApp.Deploy.Type deployType := mergedApp.Deploy.Type
if deployType == "" { if deployType == "" {
deployType = instance.DeployType // 使用实例中保存的部署类型 deployType = instance.DeployType
} }
// 根据安装目标和应用实际部署类型决定更新方式 inputCred := buildCredentialFromInput(installTarget, k8sURL, token)
if installTarget == "kubeconfig" && kubeconfig != "" { if installTarget == "kubeconfig" && inputCred == nil {
// 更新外部 Kubernetes 集群中的应用实例 return &AppStoreError{
if deployType == "kubernetes" { Code: "INVALID_K8S_CREDENTIAL",
if err := m.UpdateAppInKubernetes(mergedApp, []byte(kubeconfig), kubeconfigContext); err != nil { Message: "自定义 Kubernetes 更新需要提供 API 地址和 Token",
return &AppStoreError{Code: "KUBERNETES_UPDATE_ERROR", Message: "Kubernetes 更新失败", Details: err.Error()}
}
} else {
return &AppStoreError{Code: "NOT_IMPLEMENTED", Message: "外部环境 Docker 更新功能开发中"}
} }
} else { }
// 本地更新(依据应用部署类型) if installTarget == "kubeconfig" && deployType != "kubernetes" {
if deployType == "kubernetes" { return &AppStoreError{
if err := m.UpdateAppInKubernetes(mergedApp, nil, ""); err != nil { Code: "DEPLOYMENT_TYPE_ERROR",
return &AppStoreError{Code: "KUBERNETES_UPDATE_ERROR", Message: "本地 Kubernetes 更新失败", Details: err.Error()} Message: "该应用不支持 Kubernetes 部署",
}
} else {
return &AppStoreError{Code: "NOT_IMPLEMENTED", Message: "本地 Docker 更新功能开发中"}
} }
} }
// 更新成功后,更新用户应用实例 // 根据部署类型执行更新
switch deployType {
case "kubernetes":
targetCred := inputCred
if targetCred == nil {
targetCred = credentialFromInstance(instance)
}
if err := m.UpdateAppInKubernetes(mergedApp, targetCred); err != nil {
return &AppStoreError{Code: "KUBERNETES_UPDATE_ERROR", Message: "Kubernetes 更新失败", Details: err.Error()}
}
default:
return &AppStoreError{Code: "NOT_IMPLEMENTED", Message: "本地 Docker 更新功能开发中"}
}
// 更新成功后,写回数据库
mergedAppJSON, err := json.Marshal(mergedApp) mergedAppJSON, err := json.Marshal(mergedApp)
if err != nil { if err != nil {
log.Error("Failed to marshal merged app: %v", err) log.Error("Failed to marshal merged app: %v", err)
@@ -627,8 +660,18 @@ func (m *Manager) UpdateInstalledApp(appID string, configJSON string, installTar
instance.UserConfig = configJSON instance.UserConfig = configJSON
instance.MergedApp = string(mergedAppJSON) instance.MergedApp = string(mergedAppJSON)
instance.DeployType = deployType instance.DeployType = deployType
instance.Kubeconfig = kubeconfig if deployType == "kubernetes" {
instance.KubeconfigContext = kubeconfigContext if inputCred != nil {
instance.K8sURL = inputCred.K8sURL
instance.K8sToken = inputCred.Token
} else if installTarget != "kubeconfig" {
instance.K8sURL = ""
instance.K8sToken = ""
}
} else {
instance.K8sURL = ""
instance.K8sToken = ""
}
if err := user_app_instance.UpdateUserAppInstance(m.ctx, instance); err != nil { if err := user_app_instance.UpdateUserAppInstance(m.ctx, instance); err != nil {
log.Error("Failed to update user app instance: %v", err) log.Error("Failed to update user app instance: %v", err)
@@ -645,7 +688,7 @@ func (m *Manager) UpdateInstalledApp(appID string, configJSON string, installTar
// UninstallApp uninstalls an application // UninstallApp uninstalls an application
// It automatically determines whether to uninstall from external cluster or local cluster // It automatically determines whether to uninstall from external cluster or local cluster
// based on the kubeconfig stored in the database instance // based on the Kubernetes credential stored in the database instance
func (m *Manager) UninstallApp(appID string) error { func (m *Manager) UninstallApp(appID string) error {
// 获取用户的应用实例 // 获取用户的应用实例
instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID) instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID)
@@ -673,45 +716,28 @@ func (m *Manager) UninstallApp(appID string) error {
} }
} }
// 根据数据库实例中保存的 kubeconfig 判断卸载方式 // 根据数据库中保存的凭据判断卸载方式:存在 URL+Token 则视为外部集群,否则使用默认集群
// 如果数据库中有 kubeconfig说明是安装在外部集群的使用外部集群卸载 cred := credentialFromInstance(instance)
// 如果数据库中没有 kubeconfig说明是安装在本地集群的使用本地卸载 if instance.DeployType == "kubernetes" {
if instance.Kubeconfig != "" { if cred != nil {
// 从外部 Kubernetes 集群卸载 log.Info("UninstallApp: Uninstalling from external cluster, appID=%s, url=%s", appID, cred.K8sURL)
if instance.DeployType == "kubernetes" {
log.Info("UninstallApp: Uninstalling from external cluster, appID=%s, kubeconfig length=%d", appID, len(instance.Kubeconfig))
if err := m.UninstallAppFromKubernetes(&app, []byte(instance.Kubeconfig), instance.KubeconfigContext); err != nil {
return &AppStoreError{
Code: "KUBERNETES_UNINSTALL_ERROR",
Message: "Kubernetes 卸载失败",
Details: err.Error(),
}
}
} else { } else {
log.Info("UninstallApp: Uninstalling from default cluster, appID=%s", appID)
}
if err := m.UninstallAppFromKubernetes(&app, cred); err != nil {
return &AppStoreError{ return &AppStoreError{
Code: "NOT_IMPLEMENTED", Code: "KUBERNETES_UNINSTALL_ERROR",
Message: "外部环境 Docker 卸载功能开发中", Message: "Kubernetes 卸载失败",
Details: err.Error(),
} }
} }
} else { } else {
// 本地卸载(依据实例中保存的部署类型) return &AppStoreError{
if instance.DeployType == "kubernetes" { Code: "NOT_IMPLEMENTED",
log.Info("UninstallApp: Uninstalling from local cluster, appID=%s", appID) Message: "本地 Docker 卸载功能开发中",
if err := m.UninstallAppFromKubernetes(&app, nil, ""); err != nil {
return &AppStoreError{
Code: "KUBERNETES_UNINSTALL_ERROR",
Message: "本地 Kubernetes 卸载失败",
Details: err.Error(),
}
}
} else {
return &AppStoreError{
Code: "NOT_IMPLEMENTED",
Message: "本地 Docker 卸载功能开发中",
}
} }
} }
// 卸载成功后,删除用户应用实例 // 卸载成功后,删除用户应用实例
if err := user_app_instance.DeleteUserAppInstanceByAppID(m.ctx, m.userID, appID); err != nil { if err := user_app_instance.DeleteUserAppInstanceByAppID(m.ctx, m.userID, appID); err != nil {
log.Error("Failed to delete user app instance: %v", err) log.Error("Failed to delete user app instance: %v", err)
@@ -725,28 +751,28 @@ func (m *Manager) UninstallApp(appID string) error {
} }
// InstallAppToKubernetes installs an application to a Kubernetes cluster // InstallAppToKubernetes installs an application to a Kubernetes cluster
func (m *Manager) InstallAppToKubernetes(app *App, kubeconfig []byte, contextName string) error { func (m *Manager) InstallAppToKubernetes(app *App, cred *K8sCredential) error {
return m.k8s.InstallAppToKubernetes(app, kubeconfig, contextName) return m.k8s.InstallAppToKubernetes(app, cred)
} }
// UninstallAppFromKubernetes uninstalls an application from a Kubernetes cluster // UninstallAppFromKubernetes uninstalls an application from a Kubernetes cluster
func (m *Manager) UninstallAppFromKubernetes(app *App, kubeconfig []byte, contextName string) error { func (m *Manager) UninstallAppFromKubernetes(app *App, cred *K8sCredential) error {
return m.k8s.UninstallAppFromKubernetes(app, kubeconfig, contextName) return m.k8s.UninstallAppFromKubernetes(app, cred)
} }
// GetAppFromKubernetes gets an application from a Kubernetes cluster // GetAppFromKubernetes gets an application from a Kubernetes cluster
func (m *Manager) GetAppFromKubernetes(app *App, kubeconfig []byte, contextName string) (interface{}, error) { func (m *Manager) GetAppFromKubernetes(app *App, cred *K8sCredential) (interface{}, error) {
return m.k8s.GetAppFromKubernetes(app, kubeconfig, contextName) return m.k8s.GetAppFromKubernetes(app, cred)
} }
// ListAppsFromKubernetes lists applications from a Kubernetes cluster // ListAppsFromKubernetes lists applications from a Kubernetes cluster
func (m *Manager) ListAppsFromKubernetes(app *App, kubeconfig []byte, contextName string) (*applicationv1.ApplicationList, error) { func (m *Manager) ListAppsFromKubernetes(app *App, cred *K8sCredential) (*applicationv1.ApplicationList, error) {
return m.k8s.ListAppsFromKubernetes(app, kubeconfig, contextName) return m.k8s.ListAppsFromKubernetes(app, cred)
} }
// UpdateAppInKubernetes updates an application in a Kubernetes cluster // UpdateAppInKubernetes updates an application in a Kubernetes cluster
func (m *Manager) UpdateAppInKubernetes(app *App, kubeconfig []byte, contextName string) error { func (m *Manager) UpdateAppInKubernetes(app *App, cred *K8sCredential) error {
return m.k8s.UpdateAppInKubernetes(app, kubeconfig, contextName) return m.k8s.UpdateAppInKubernetes(app, cred)
} }
// IsInstalledAppReady 提供给上层的就绪判断入口,避免直接依赖 k8s 层实现 // IsInstalledAppReady 提供给上层的就绪判断入口,避免直接依赖 k8s 层实现
@@ -788,14 +814,9 @@ func (m *Manager) GetAppStatus(appID string) (map[string]interface{}, error) {
// 检查应用是否支持 Kubernetes 部署 // 检查应用是否支持 Kubernetes 部署
if instance.DeployType == "kubernetes" { if instance.DeployType == "kubernetes" {
// 尝试从 Kubernetes 获取状态 // 尝试从 Kubernetes 获取状态
var kubeconfig []byte cred := credentialFromInstance(instance)
var contextName string
if instance.Kubeconfig != "" {
kubeconfig = []byte(instance.Kubeconfig)
contextName = instance.KubeconfigContext
}
k8sApp, err := m.GetAppFromKubernetes(&app, kubeconfig, contextName) k8sApp, err := m.GetAppFromKubernetes(&app, cred)
if err != nil { if err != nil {
// 如果应用实例存在于数据库中,但 Kubernetes 中找不到,可能是被暂停了 // 如果应用实例存在于数据库中,但 Kubernetes 中找不到,可能是被暂停了
// 返回暂停状态而不是未安装状态 // 返回暂停状态而不是未安装状态
@@ -845,7 +866,7 @@ func (m *Manager) GetAppStatus(appID string) (map[string]interface{}, error) {
} }
// StopApp stops an application by setting replicas to 0 // StopApp stops an application by setting replicas to 0
func (m *Manager) StopApp(appID string, installTarget string, kubeconfig []byte, kubeconfigContext string) error { func (m *Manager) StopApp(appID string, installTarget string, k8sURL string, token string) error {
// 获取用户的应用实例 // 获取用户的应用实例
instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID) instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID)
if err != nil { if err != nil {
@@ -880,8 +901,12 @@ func (m *Manager) StopApp(appID string, installTarget string, kubeconfig []byte,
stoppedApp.Deploy.Kubernetes.Replicas = 0 stoppedApp.Deploy.Kubernetes.Replicas = 0
} }
// 调用 k8s 层的 UpdateAppInKubernetes 函数来停止应用 cred := buildCredentialFromInput(installTarget, k8sURL, token)
if err := m.k8s.UpdateAppInKubernetes(&stoppedApp, kubeconfig, kubeconfigContext); err != nil { if cred == nil {
cred = credentialFromInstance(instance)
}
if err := m.UpdateAppInKubernetes(&stoppedApp, cred); err != nil {
return &AppStoreError{ return &AppStoreError{
Code: "KUBERNETES_STOP_ERROR", Code: "KUBERNETES_STOP_ERROR",
Message: "停止应用失败", Message: "停止应用失败",
@@ -899,7 +924,7 @@ func (m *Manager) StopApp(appID string, installTarget string, kubeconfig []byte,
} }
// ResumeApp resumes an application by restoring its original replica count // ResumeApp resumes an application by restoring its original replica count
func (m *Manager) ResumeApp(appID string, installTarget string, kubeconfig []byte, kubeconfigContext string) error { func (m *Manager) ResumeApp(appID string, installTarget string, k8sURL string, token string) error {
// 获取用户的应用实例 // 获取用户的应用实例
instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID) instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID)
if err != nil { if err != nil {
@@ -937,8 +962,12 @@ func (m *Manager) ResumeApp(appID string, installTarget string, kubeconfig []byt
} }
} }
// 调用 k8s 层的 UpdateAppInKubernetes 函数来恢复应用 cred := buildCredentialFromInput(installTarget, k8sURL, token)
if err := m.k8s.UpdateAppInKubernetes(&resumedApp, kubeconfig, kubeconfigContext); err != nil { if cred == nil {
cred = credentialFromInstance(instance)
}
if err := m.UpdateAppInKubernetes(&resumedApp, cred); err != nil {
return &AppStoreError{ return &AppStoreError{
Code: "KUBERNETES_RESUME_ERROR", Code: "KUBERNETES_RESUME_ERROR",
Message: "恢复应用失败", Message: "恢复应用失败",

repo.diff.view_file

@@ -274,18 +274,18 @@
<div class="field"> <div class="field">
<div class="ui radio checkbox"> <div class="ui radio checkbox">
<input type="radio" name="installTargetRadio" value="kubeconfig" {{if not .IsAdmin}}checked{{end}}> <input type="radio" name="installTargetRadio" value="kubeconfig" {{if not .IsAdmin}}checked{{end}}>
<label>自定义位置Kubeconfig</label> <label>自定义位置Kubernetes URL + Token</label>
</div> </div>
</div> </div>
</div> </div>
<div id="kubeconfig-fields" style="display:none;"> <div id="k8s-credential-fields" style="display:none;">
<div class="field"> <div class="field">
<label>Kubeconfig粘贴内容</label> <label>Kubernetes API 地址</label>
<textarea id="kubeconfig-content" rows="8" placeholder="粘贴 kubeconfig 内容"></textarea> <input type="text" id="k8s-url" placeholder="例如https://your-cluster:6443">
</div> </div>
<div class="field"> <div class="field">
<label>Context 名称(可选)</label> <label>Bearer Token</label>
<input type="text" id="kubeconfig-context" placeholder="不填则使用 current-context"> <textarea id="k8s-token" rows="4" placeholder="粘贴访问该集群的 Bearer Token"></textarea>
</div> </div>
</div> </div>
</div> </div>
@@ -377,10 +377,10 @@ let filteredApps = [];
let currentApp = null; let currentApp = null;
let currentAppStatus = null; // 存储当前应用的运行状态 let currentAppStatus = null; // 存储当前应用的运行状态
let storeSource = 'local'; // local | devstar let storeSource = 'local'; // local | devstar
// 安装位置local | kubeconfig // 安装位置local | remote
let installTarget = 'local'; let installTarget = 'local';
let installKubeconfigContent = ''; let installK8sURL = '';
let installKubeconfigContext = ''; let installK8sToken = '';
// Initialize the page // Initialize the page
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
@@ -420,7 +420,7 @@ function setupInstallTargetUI() {
const radioButtons = document.querySelectorAll('#install-modal input[name="installTargetRadio"]'); const radioButtons = document.querySelectorAll('#install-modal input[name="installTargetRadio"]');
radioButtons.forEach(radio => { radioButtons.forEach(radio => {
radio.addEventListener('change', function() { radio.addEventListener('change', function() {
const kubeconfigFields = document.querySelector('#install-modal #kubeconfig-fields'); const kubeconfigFields = document.querySelector('#install-modal #k8s-credential-fields');
if (kubeconfigFields) { if (kubeconfigFields) {
kubeconfigFields.style.display = (this.value === 'kubeconfig') ? 'block' : 'none'; kubeconfigFields.style.display = (this.value === 'kubeconfig') ? 'block' : 'none';
} }
@@ -428,17 +428,17 @@ function setupInstallTargetUI() {
installTarget = this.value; installTarget = this.value;
}); });
}); });
// kubeconfig 内容变化时,同步到全局变量 // Kubernetes URL/Token 变化时,同步到全局变量
const kcContentEl = document.querySelector('#install-modal #kubeconfig-content'); const kcContentEl = document.querySelector('#install-modal #k8s-url');
if (kcContentEl) { if (kcContentEl) {
kcContentEl.addEventListener('input', function() { kcContentEl.addEventListener('input', function() {
installKubeconfigContent = this.value.trim(); installK8sURL = this.value.trim();
}); });
} }
const kcContextEl = document.querySelector('#install-modal #kubeconfig-context'); const kcContextEl = document.querySelector('#install-modal #k8s-token');
if (kcContextEl) { if (kcContextEl) {
kcContextEl.addEventListener('input', function() { kcContextEl.addEventListener('input', function() {
installKubeconfigContext = this.value.trim(); installK8sToken = this.value.trim();
}); });
} }
} }
@@ -813,14 +813,14 @@ function showInstallModal(appId) {
// 追加安装位置区块已保留在模板中,这里只同步状态 // 追加安装位置区块已保留在模板中,这里只同步状态
const radios = document.querySelectorAll('#install-modal input[name="installTargetRadio"]'); const radios = document.querySelectorAll('#install-modal input[name="installTargetRadio"]');
radios.forEach(r => { r.checked = (r.value === installTarget); }); radios.forEach(r => { r.checked = (r.value === installTarget); });
const kubeconfigFields = document.querySelector('#install-modal #kubeconfig-fields'); const kubeconfigFields = document.querySelector('#install-modal #k8s-credential-fields');
if (kubeconfigFields) { if (kubeconfigFields) {
kubeconfigFields.style.display = (installTarget === 'kubeconfig') ? 'block' : 'none'; kubeconfigFields.style.display = (installTarget === 'kubeconfig') ? 'block' : 'none';
} }
const kcContent = document.querySelector('#install-modal #kubeconfig-content'); const kcContent = document.querySelector('#install-modal #k8s-url');
const kcContext = document.querySelector('#install-modal #kubeconfig-context'); const kcContext = document.querySelector('#install-modal #k8s-token');
if (kcContent) kcContent.value = installKubeconfigContent || ''; if (kcContent) kcContent.value = installK8sURL || '';
if (kcContext) kcContext.value = installKubeconfigContext || ''; if (kcContext) kcContext.value = installK8sToken || '';
const modal = document.getElementById('install-modal'); const modal = document.getElementById('install-modal');
if (modal) { if (modal) {
@@ -932,14 +932,14 @@ function showUpdateModal(appId) {
// 设置安装位置表单 // 设置安装位置表单
const radios = document.querySelectorAll('#install-modal input[name="installTargetRadio"]'); const radios = document.querySelectorAll('#install-modal input[name="installTargetRadio"]');
radios.forEach(r => { r.checked = (r.value === installTarget); }); radios.forEach(r => { r.checked = (r.value === installTarget); });
const kubeconfigFields = document.querySelector('#install-modal #kubeconfig-fields'); const kubeconfigFields = document.querySelector('#install-modal #k8s-credential-fields');
if (kubeconfigFields) { if (kubeconfigFields) {
kubeconfigFields.style.display = (installTarget === 'kubeconfig') ? 'block' : 'none'; kubeconfigFields.style.display = (installTarget === 'kubeconfig') ? 'block' : 'none';
} }
const kcContent = document.querySelector('#install-modal #kubeconfig-content'); const kcContent = document.querySelector('#install-modal #k8s-url');
const kcContext = document.querySelector('#install-modal #kubeconfig-context'); const kcContext = document.querySelector('#install-modal #k8s-token');
if (kcContent) kcContent.value = installKubeconfigContent || ''; if (kcContent) kcContent.value = installK8sURL || '';
if (kcContext) kcContext.value = installKubeconfigContext || ''; if (kcContext) kcContext.value = installK8sToken || '';
// 修改安装按钮文字为"更新" // 修改安装按钮文字为"更新"
const installBtn = document.querySelector('#install-modal .ui.primary.button[onclick="installApp()"]'); const installBtn = document.querySelector('#install-modal .ui.primary.button[onclick="installApp()"]');
@@ -978,12 +978,12 @@ function updateApp() {
const selectedTarget = document.querySelector('#install-modal input[name="installTargetRadio"]:checked'); const selectedTarget = document.querySelector('#install-modal input[name="installTargetRadio"]:checked');
installTarget = selectedTarget ? selectedTarget.value : 'local'; installTarget = selectedTarget ? selectedTarget.value : 'local';
if (installTarget === 'kubeconfig') { if (installTarget === 'kubeconfig') {
const kcContentEl = document.querySelector('#install-modal #kubeconfig-content'); const kcContentEl = document.querySelector('#install-modal #k8s-url');
const kcContextEl = document.querySelector('#install-modal #kubeconfig-context'); const kcContextEl = document.querySelector('#install-modal #k8s-token');
installKubeconfigContent = kcContentEl ? kcContentEl.value.trim() : ''; installK8sURL = kcContentEl ? kcContentEl.value.trim() : '';
installKubeconfigContext = kcContextEl ? kcContextEl.value.trim() : ''; installK8sToken = kcContextEl ? kcContextEl.value.trim() : '';
if (!installKubeconfigContent) { if (!installK8sURL || !installK8sToken) {
alert('请输入 kubeconfig 内容'); alert('请输入 Kubernetes API 地址和 Token');
return; return;
} }
config.deploy = { type: 'kubernetes' }; config.deploy = { type: 'kubernetes' };
@@ -1036,14 +1036,14 @@ function updateApp() {
if (installTarget === 'kubeconfig') { if (installTarget === 'kubeconfig') {
const kcInput = document.createElement('input'); const kcInput = document.createElement('input');
kcInput.type = 'hidden'; kcInput.type = 'hidden';
kcInput.name = 'kubeconfig'; kcInput.name = 'k8s_url';
kcInput.value = installKubeconfigContent; kcInput.value = installK8sURL;
updateForm.appendChild(kcInput); updateForm.appendChild(kcInput);
const kctxInput = document.createElement('input'); const kctxInput = document.createElement('input');
kctxInput.type = 'hidden'; kctxInput.type = 'hidden';
kctxInput.name = 'kubeconfig_context'; kctxInput.name = 'k8s_token';
kctxInput.value = installKubeconfigContext; kctxInput.value = installK8sToken;
updateForm.appendChild(kctxInput); updateForm.appendChild(kctxInput);
} }
@@ -1306,12 +1306,12 @@ async function installApp() {
const selectedTarget = document.querySelector('#install-modal input[name="installTargetRadio"]:checked'); const selectedTarget = document.querySelector('#install-modal input[name="installTargetRadio"]:checked');
installTarget = selectedTarget ? selectedTarget.value : 'local'; installTarget = selectedTarget ? selectedTarget.value : 'local';
if (installTarget === 'kubeconfig') { if (installTarget === 'kubeconfig') {
const kcContentEl = document.querySelector('#install-modal #kubeconfig-content'); const kcContentEl = document.querySelector('#install-modal #k8s-url');
const kcContextEl = document.querySelector('#install-modal #kubeconfig-context'); const kcContextEl = document.querySelector('#install-modal #k8s-token');
installKubeconfigContent = kcContentEl ? kcContentEl.value.trim() : ''; installK8sURL = kcContentEl ? kcContentEl.value.trim() : '';
installKubeconfigContext = kcContextEl ? kcContextEl.value.trim() : ''; installK8sToken = kcContextEl ? kcContextEl.value.trim() : '';
if (!installKubeconfigContent) { if (!installK8sURL || !installK8sToken) {
alert('请输入 kubeconfig 内容'); alert('请输入 Kubernetes API 地址和 Token');
return; return;
} }
config.deploy = { type: 'kubernetes' }; config.deploy = { type: 'kubernetes' };
@@ -1359,13 +1359,13 @@ async function installApp() {
if (installTarget === 'kubeconfig') { if (installTarget === 'kubeconfig') {
const kcInput = document.createElement('input'); const kcInput = document.createElement('input');
kcInput.type = 'hidden'; kcInput.type = 'hidden';
kcInput.name = 'kubeconfig'; kcInput.name = 'k8s_url';
kcInput.value = installKubeconfigContent; kcInput.value = installK8sURL;
installForm.appendChild(kcInput); installForm.appendChild(kcInput);
const kctxInput = document.createElement('input'); const kctxInput = document.createElement('input');
kctxInput.type = 'hidden'; kctxInput.type = 'hidden';
kctxInput.name = 'kubeconfig_context'; kctxInput.name = 'k8s_token';
kctxInput.value = installKubeconfigContext; kctxInput.value = installK8sToken;
installForm.appendChild(kctxInput); installForm.appendChild(kctxInput);
} }
// 提交安装表单 // 提交安装表单