/* * Copyright (c) Mengning Software. 2025. All rights reserved. * Authors: DevStar Team, panshuxiao * Create: 2025-11-19 * Description: Core business logic for AppStore lifecycle. */ package appstore import ( "context" "encoding/json" "fmt" "net/http" "strings" "time" appstore_model "code.gitea.io/gitea/models/appstore" user_app_instance "code.gitea.io/gitea/models/appstore" applicationv1 "code.gitea.io/gitea/modules/k8s/api/application/v1" "code.gitea.io/gitea/modules/log" gitea_context "code.gitea.io/gitea/services/context" ) // Manager handles app store operations with database type Manager struct { parser *Parser ctx context.Context k8s *K8sManager userID int64 // 当前用户ID } // NewManager creates a new app store manager for database operations func NewManager(ctx *gitea_context.Context, userID int64) *Manager { return &Manager{ parser: NewParser(), ctx: *ctx, k8s: NewK8sManager(*ctx), userID: userID, } } 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 func (m *Manager) ListApps() ([]App, error) { appStores, err := appstore_model.ListAppStores(m.ctx, nil) if err != nil { return nil, &AppStoreError{ Code: "DATABASE_ERROR", Message: "Failed to list apps from database", Details: err.Error(), } } var apps []App for _, appStore := range appStores { if app, err := m.convertAppStoreToApp(appStore); err == nil { apps = append(apps, *app) } } return apps, nil } // ListUserAppInstances returns user's installed application instances func (m *Manager) ListUserAppInstances() ([]App, error) { // 获取用户的应用实例 instances, err := user_app_instance.GetUserAppInstances(m.ctx, m.userID) if err != nil { return nil, &AppStoreError{ Code: "DATABASE_ERROR", Message: "获取用户应用实例失败", Details: err.Error(), } } var apps []App for _, instance := range instances { // 从实例的 MergedApp JSON 中解析应用信息 var app App if err := json.Unmarshal([]byte(instance.MergedApp), &app); err != nil { log.Error("Failed to unmarshal merged app for instance %d: %v", instance.ID, err) continue } apps = append(apps, app) } return apps, nil } // ListAppsFromDevstar 从 devstar.cn 拉取应用列表 func (m *Manager) ListAppsFromDevstar() ([]App, error) { client := &http.Client{Timeout: 10 * time.Second} url := "https://devstar.cn/api/v1/appstore/apps" resp, err := client.Get(url) if err != nil { return nil, &AppStoreError{Code: "REMOTE_ERROR", Message: "Failed to fetch apps from devstar", Details: err.Error()} } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, &AppStoreError{Code: "REMOTE_ERROR", Message: "Invalid status from devstar", Details: resp.Status} } var payload struct { Apps []App `json:"apps"` } if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { return nil, &AppStoreError{Code: "REMOTE_ERROR", Message: "Failed to decode devstar response", Details: err.Error()} } return payload.Apps, nil } // GetApp returns a specific application from database func (m *Manager) GetApp(appID string) (*App, error) { appStore, err := appstore_model.GetAppStoreByAppID(m.ctx, appID) if err != nil { return nil, &AppStoreError{ Code: "APP_NOT_FOUND", Message: "App not found in database", Details: err.Error(), } } return m.convertAppStoreToApp(appStore) } // convertAppStoreToApp convert database AppStore model to App struct func (m *Manager) convertAppStoreToApp(appStore *appstore_model.AppStore) (*App, error) { // 基本信息直接从数据库字段获取 app := App{ ID: appStore.AppID, Name: appStore.Name, Description: appStore.Description, Category: appStore.Category, Tags: appStore.GetTagsList(), Icon: appStore.Icon, Author: appStore.Author, Website: appStore.Website, Repository: appStore.Repository, License: appStore.License, Version: appStore.Version, DeploymentType: appStore.DeploymentType, } // 如果JSONData不为空,交由 parser 解析并合并(DB 字段优先) if appStore.JSONData != "" { merged, err := m.parser.ParseAndMergeApp([]byte(appStore.JSONData), &app) if err != nil { return nil, err } app = *merged } return &app, nil } // convertAppToAppStore converts App struct to database AppStore model func (m *Manager) convertAppToAppStore(app *App) (*appstore_model.AppStore, error) { jsonData, err := json.Marshal(app) if err != nil { return nil, &AppStoreError{ Code: "JSON_MARSHAL_ERROR", Message: "Failed to marshal app to JSON", Details: err.Error(), } } appStore := &appstore_model.AppStore{ AppID: app.ID, Name: app.Name, Description: app.Description, Category: app.Category, Icon: app.Icon, Author: app.Author, Website: app.Website, Repository: app.Repository, License: app.License, Version: app.Version, DeploymentType: app.DeploymentType, JSONData: string(jsonData), IsActive: true, IsOfficial: false, IsVerified: true, } appStore.SetTagsList(app.Tags) return appStore, nil } // SearchApps searches for applications by various criteria func (m *Manager) SearchApps(query string, category string, tags []string) ([]App, error) { apps, err := m.ListApps() if err != nil { return nil, err } var results []App query = strings.ToLower(query) for _, app := range apps { // Check if app matches search criteria matches := false // Text search if query != "" { if strings.Contains(strings.ToLower(app.Name), query) || strings.Contains(strings.ToLower(app.Description), query) || strings.Contains(strings.ToLower(app.Author), query) { matches = true } } else { matches = true } // Category filter if category != "" && app.Category != category { matches = false } // Tags filter if len(tags) > 0 { tagMatch := false for _, searchTag := range tags { for _, appTag := range app.Tags { if strings.EqualFold(appTag, searchTag) { tagMatch = true break } } if tagMatch { break } } if !tagMatch { matches = false } } if matches { results = append(results, app) } } return results, nil } // GetCategories returns all available categories func (m *Manager) GetCategories() ([]string, error) { apps, err := m.ListApps() if err != nil { return nil, err } categories := make(map[string]bool) for _, app := range apps { categories[app.Category] = true } var result []string for category := range categories { result = append(result, category) } return result, nil } // GetTags returns all available tags func (m *Manager) GetTags() ([]string, error) { apps, err := m.ListApps() if err != nil { return nil, err } tags := make(map[string]bool) for _, app := range apps { for _, tag := range app.Tags { tags[tag] = true } } var result []string for tag := range tags { result = append(result, tag) } return result, nil } // PrepareInstallation prepares an application for installation by merging user config // Returns a complete App structure with merged configuration // func (m *Manager) PrepareInstallation(appID string, userConfig UserConfig) (*App, error) { // // Load the application // app, err := m.GetApp(appID) // if err != nil { // return nil, err // } // // Set the app ID and version in user config if not provided // if userConfig.AppID == "" { // userConfig.AppID = appID // } // if userConfig.Version == "" { // userConfig.Version = app.Version // } // // Merge user configuration with app's default configuration // mergedApp, err := m.parser.MergeUserConfig(app, userConfig) // if err != nil { // return nil, err // } // return mergedApp, nil // } // AddApp adds a new application to the database func (m *Manager) AddApp(app *App) error { // Validate the app if err := m.parser.validateAppTemplate(app); err != nil { return err } // Convert to database model appStore, err := m.convertAppToAppStore(app) if err != nil { return err } // Save to database if err := appstore_model.CreateAppStore(m.ctx, appStore); err != nil { return &AppStoreError{ Code: "DATABASE_ERROR", Message: "Failed to create app in database", Details: err.Error(), } } return nil } // AddAppFromJSON 通过 parser 解析原始 JSON 并添加应用 func (m *Manager) AddAppFromJSON(jsonBytes []byte) error { app, err := m.parser.ParseAppTemplate(jsonBytes) if err != nil { return err } return m.AddApp(app) } // UpdateApp updates an existing application in database func (m *Manager) UpdateApp(app *App) error { // Validate the app if err := m.parser.validateApp(app); err != nil { return err } // Get existing app from database existingAppStore, err := appstore_model.GetAppStoreByAppID(m.ctx, app.ID) if err != nil { return &AppStoreError{ Code: "APP_NOT_FOUND", Message: "App not found in database", Details: err.Error(), } } // Convert to database model appStore, err := m.convertAppToAppStore(app) if err != nil { return err } // Preserve database ID and timestamps appStore.ID = existingAppStore.ID appStore.CreatedUnix = existingAppStore.CreatedUnix // Update in database if err := appstore_model.UpdateAppStore(m.ctx, appStore); err != nil { return &AppStoreError{ Code: "DATABASE_ERROR", Message: "Failed to update app in database", Details: err.Error(), } } return nil } // RemoveApp removes an application template from the database func (m *Manager) RemoveApp(appID string) error { // 获取应用模板记录 appStore, err := appstore_model.GetAppStoreByAppID(m.ctx, appID) if err != nil { return &AppStoreError{ Code: "APP_NOT_FOUND", Message: "应用模板不存在", Details: err.Error(), } } // 删除应用模板 if err := appstore_model.DeleteAppStore(m.ctx, appStore.ID); err != nil { return &AppStoreError{ Code: "DATABASE_ERROR", Message: "删除应用模板失败", Details: err.Error(), } } log.Info("Successfully removed app template %s", appID) return nil } // GetAppConfigSchema returns the configuration schema for an application func (m *Manager) GetAppConfigSchema(appID string) (*AppConfig, error) { app, err := m.GetApp(appID) if err != nil { return nil, err } return &app.Config, nil } // ValidateUserConfig validates user configuration against app schema func (m *Manager) ValidateUserConfig(appID string, userConfig UserConfig) error { app, err := m.GetApp(appID) if err != nil { return err } // Set the app ID and version in user config if not provided if userConfig.AppID == "" { userConfig.AppID = appID } if userConfig.Version == "" { userConfig.Version = app.Version } // Try to merge config (this will validate) _, err = m.parser.MergeUserConfig(app, userConfig) return err } // InstallApp installs an application based on the provided parameters func (m *Manager) InstallApp(appID string, configJSON string, installTarget string, k8sURL string, token string) error { // 获取应用信息 app, err := m.GetApp(appID) if err != nil { return &AppStoreError{ Code: "APP_NOT_FOUND", Message: "获取应用失败", Details: err.Error(), } } // 使用 parser 解析和合并用户配置 log.Info("InstallApp: configJSON = %s", configJSON) mergedApp, err := m.parser.ParseAndMergeUserConfig(app, configJSON) if err != nil { return &AppStoreError{ Code: "CONFIG_MERGE_ERROR", Message: "配置合并失败", Details: err.Error(), } } // 确定部署类型 deployType := mergedApp.Deploy.Type if deployType == "" { // 如果 Deploy.Type 为空,根据 DeploymentType 推断 switch mergedApp.DeploymentType { case "kubernetes", "both": deployType = "kubernetes" case "docker": deployType = "docker" } } inputCred := buildCredentialFromInput(installTarget, k8sURL, token) if installTarget == "kubeconfig" { if inputCred == 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{ Code: "KUBERNETES_INSTALL_ERROR", Message: "Kubernetes 安装失败", Details: err.Error(), } } case "docker": // TODO: 实现 Docker 安装逻辑 return &AppStoreError{ Code: "NOT_IMPLEMENTED", Message: "本地 Docker 安装功能开发中", } default: return &AppStoreError{ Code: "NOT_IMPLEMENTED", Message: "本地安装功能开发中", } } // 安装成功后,保存用户应用实例到数据库 mergedAppJSON, err := json.Marshal(mergedApp) if err != nil { log.Error("Failed to marshal merged app: %v", err) return &AppStoreError{ Code: "JSON_MARSHAL_ERROR", Message: "保存应用配置失败", Details: err.Error(), } } instance := &user_app_instance.UserAppInstance{ UserID: m.userID, AppID: appID, InstanceName: mergedApp.Name, UserConfig: configJSON, MergedApp: string(mergedAppJSON), DeployType: deployType, } if inputCred != nil && deployType == "kubernetes" { instance.K8sURL = inputCred.K8sURL instance.K8sToken = inputCred.Token } if err := user_app_instance.CreateUserAppInstance(m.ctx, instance); err != nil { log.Error("Failed to create user app instance: %v", err) return &AppStoreError{ Code: "DATABASE_ERROR", Message: "保存应用实例失败", Details: err.Error(), } } log.Info("Successfully installed app %s for user %d", appID, m.userID) return nil } // UpdateInstalledApp updates an already installed application with new configuration // The update flow mirrors InstallApp: merge config → choose target → call K8s/Docker updater 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) if err != nil { return &AppStoreError{ Code: "DATABASE_ERROR", Message: "获取应用实例失败", Details: err.Error(), } } if instance == nil { return &AppStoreError{ Code: "APP_NOT_INSTALLED", Message: "应用未安装", } } // 获取应用模板信息 app, err := m.GetApp(appID) if err != nil { return &AppStoreError{ Code: "APP_NOT_FOUND", Message: "获取应用失败", Details: err.Error(), } } // 使用 parser 解析和合并用户配置 mergedApp, err := m.parser.ParseAndMergeUserConfig(app, configJSON) if err != nil { return &AppStoreError{ Code: "CONFIG_MERGE_ERROR", Message: "配置合并失败", Details: err.Error(), } } // 确定部署类型 deployType := mergedApp.Deploy.Type if deployType == "" { deployType = instance.DeployType } inputCred := buildCredentialFromInput(installTarget, k8sURL, token) if installTarget == "kubeconfig" && inputCred == nil { return &AppStoreError{ Code: "INVALID_K8S_CREDENTIAL", Message: "自定义 Kubernetes 更新需要提供 API 地址和 Token", } } if installTarget == "kubeconfig" && deployType != "kubernetes" { return &AppStoreError{ Code: "DEPLOYMENT_TYPE_ERROR", Message: "该应用不支持 Kubernetes 部署", } } // 根据部署类型执行更新 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) if err != nil { log.Error("Failed to marshal merged app: %v", err) return &AppStoreError{ Code: "JSON_MARSHAL_ERROR", Message: "保存应用配置失败", Details: err.Error(), } } instance.UserConfig = configJSON instance.MergedApp = string(mergedAppJSON) instance.DeployType = deployType if deployType == "kubernetes" { 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 { log.Error("Failed to update user app instance: %v", err) return &AppStoreError{ Code: "DATABASE_ERROR", Message: "更新应用实例失败", Details: err.Error(), } } log.Info("Successfully updated app %s for user %d", appID, m.userID) return nil } // UninstallApp uninstalls an application // It automatically determines whether to uninstall from external cluster or local cluster // based on the Kubernetes credential stored in the database instance func (m *Manager) UninstallApp(appID string) error { // 获取用户的应用实例 instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID) if err != nil { return &AppStoreError{ Code: "DATABASE_ERROR", Message: "获取应用实例失败", Details: err.Error(), } } if instance == nil { return &AppStoreError{ Code: "APP_NOT_INSTALLED", Message: "应用未安装", } } // 从实例中解析应用信息 var app App if err := json.Unmarshal([]byte(instance.MergedApp), &app); err != nil { return &AppStoreError{ Code: "JSON_UNMARSHAL_ERROR", Message: "解析应用配置失败", Details: err.Error(), } } // 根据数据库中保存的凭据判断卸载方式:存在 URL+Token 则视为外部集群,否则使用默认集群 cred := credentialFromInstance(instance) if instance.DeployType == "kubernetes" { if cred != nil { log.Info("UninstallApp: Uninstalling from external cluster, appID=%s, url=%s", appID, cred.K8sURL) } else { log.Info("UninstallApp: Uninstalling from default cluster, appID=%s", appID) } if err := m.UninstallAppFromKubernetes(&app, cred); 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 { log.Error("Failed to delete user app instance: %v", err) // 注意:这里不返回错误,因为 Kubernetes 资源已经成功卸载 // 数据库记录删除失败不应该影响卸载操作的成功 log.Warn("Kubernetes resources uninstalled successfully, but failed to delete database record for app %s, user %d", appID, m.userID) } log.Info("Successfully uninstalled app %s for user %d", appID, m.userID) return nil } // InstallAppToKubernetes installs an application to a Kubernetes cluster func (m *Manager) InstallAppToKubernetes(app *App, cred *K8sCredential) error { return m.k8s.InstallAppToKubernetes(app, cred) } // UninstallAppFromKubernetes uninstalls an application from a Kubernetes cluster func (m *Manager) UninstallAppFromKubernetes(app *App, cred *K8sCredential) error { return m.k8s.UninstallAppFromKubernetes(app, cred) } // GetAppFromKubernetes gets an application from a Kubernetes cluster func (m *Manager) GetAppFromKubernetes(app *App, cred *K8sCredential) (interface{}, error) { return m.k8s.GetAppFromKubernetes(app, cred) } // ListAppsFromKubernetes lists applications from a Kubernetes cluster func (m *Manager) ListAppsFromKubernetes(app *App, cred *K8sCredential) (*applicationv1.ApplicationList, error) { return m.k8s.ListAppsFromKubernetes(app, cred) } // UpdateAppInKubernetes updates an application in a Kubernetes cluster func (m *Manager) UpdateAppInKubernetes(app *App, cred *K8sCredential) error { return m.k8s.UpdateAppInKubernetes(app, cred) } // IsInstalledAppReady 提供给上层的就绪判断入口,避免直接依赖 k8s 层实现 func (m *Manager) IsInstalledAppReady(status *applicationv1.ApplicationStatus) bool { return m.k8s.IsApplicationReady(status) } // GetAppStatus 获取应用的安装和运行状态 func (m *Manager) GetAppStatus(appID string) (map[string]interface{}, error) { // 获取用户的应用实例 instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID) if err != nil { return nil, &AppStoreError{ Code: "DATABASE_ERROR", Message: "获取应用实例失败", Details: err.Error(), } } if instance == nil { // 应用未安装 return map[string]interface{}{ "phase": "Not Installed", "replicas": 0, "readyReplicas": 0, "ready": false, }, nil } // 从实例中解析应用信息 var app App if err := json.Unmarshal([]byte(instance.MergedApp), &app); err != nil { return nil, &AppStoreError{ Code: "JSON_UNMARSHAL_ERROR", Message: "解析应用配置失败", Details: err.Error(), } } // 检查应用是否支持 Kubernetes 部署 if instance.DeployType == "kubernetes" { // 尝试从 Kubernetes 获取状态 cred := credentialFromInstance(instance) k8sApp, err := m.GetAppFromKubernetes(&app, cred) if err != nil { // 如果应用实例存在于数据库中,但 Kubernetes 中找不到,可能是被暂停了 // 返回暂停状态而不是未安装状态 return map[string]interface{}{ "phase": "Paused", "replicas": 0, "readyReplicas": 0, "ready": false, }, nil } // 应用已安装,返回状态信息 if k8sApp != nil { // 尝试解析 K8s Application 状态 if appData, ok := k8sApp.(*applicationv1.Application); ok { // 使用 Application CRD 的实际状态 phase := string(appData.Status.Phase) replicas := int(appData.Status.Replicas) readyReplicas := int(appData.Status.ReadyReplicas) // 判断是否就绪 ready := m.IsInstalledAppReady(&appData.Status) return map[string]interface{}{ "phase": phase, "replicas": replicas, "readyReplicas": readyReplicas, "ready": ready, }, nil } // 如果无法解析,返回错误 return nil, &AppStoreError{ Code: "STATUS_PARSE_ERROR", Message: "无法解析应用状态数据", Details: fmt.Sprintf("期望 *applicationv1.Application 类型,实际类型: %T", k8sApp), } } } // 默认返回未安装状态 return map[string]interface{}{ "phase": "Not Installed", "replicas": 0, "readyReplicas": 0, "ready": false, }, nil } // StopApp stops an application by setting replicas to 0 func (m *Manager) StopApp(appID string, installTarget string, k8sURL string, token string) error { // 获取用户的应用实例 instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID) if err != nil { return &AppStoreError{ Code: "DATABASE_ERROR", Message: "获取应用实例失败", Details: err.Error(), } } if instance == nil { return &AppStoreError{ Code: "APP_NOT_INSTALLED", Message: "应用未安装", } } // 从实例中解析应用信息 var app App if err := json.Unmarshal([]byte(instance.MergedApp), &app); err != nil { return &AppStoreError{ Code: "JSON_UNMARSHAL_ERROR", Message: "解析应用配置失败", Details: err.Error(), } } // 检查应用是否支持 Kubernetes 部署 if instance.DeployType == "kubernetes" { // 创建应用的副本并设置副本数为 0 stoppedApp := app if stoppedApp.Deploy.Kubernetes != nil { stoppedApp.Deploy.Kubernetes.Replicas = 0 } cred := buildCredentialFromInput(installTarget, k8sURL, token) if cred == nil { cred = credentialFromInstance(instance) } if err := m.UpdateAppInKubernetes(&stoppedApp, cred); err != nil { return &AppStoreError{ Code: "KUBERNETES_STOP_ERROR", Message: "停止应用失败", Details: err.Error(), } } return nil } // 如果应用不支持 Kubernetes 部署,返回错误 return &AppStoreError{ Code: "UNSUPPORTED_DEPLOYMENT_TYPE", Message: fmt.Sprintf("应用部署类型 '%s' 不支持暂停功能,目前仅支持 Kubernetes 部署的应用", instance.DeployType), } } // ResumeApp resumes an application by restoring its original replica count func (m *Manager) ResumeApp(appID string, installTarget string, k8sURL string, token string) error { // 获取用户的应用实例 instance, err := user_app_instance.GetUserAppInstanceByAppID(m.ctx, m.userID, appID) if err != nil { return &AppStoreError{ Code: "DATABASE_ERROR", Message: "获取应用实例失败", Details: err.Error(), } } if instance == nil { return &AppStoreError{ Code: "APP_NOT_INSTALLED", Message: "应用未安装", } } // 从实例中解析应用信息 var app App if err := json.Unmarshal([]byte(instance.MergedApp), &app); err != nil { return &AppStoreError{ Code: "JSON_UNMARSHAL_ERROR", Message: "解析应用配置失败", Details: err.Error(), } } // 检查应用是否支持 Kubernetes 部署 if instance.DeployType == "kubernetes" { // 恢复应用的原始副本数(从 MergedApp 中获取) resumedApp := app if resumedApp.Deploy.Kubernetes != nil { // 如果 MergedApp 中没有指定副本数,默认恢复为 1 if resumedApp.Deploy.Kubernetes.Replicas == 0 { resumedApp.Deploy.Kubernetes.Replicas = 1 } } cred := buildCredentialFromInput(installTarget, k8sURL, token) if cred == nil { cred = credentialFromInstance(instance) } if err := m.UpdateAppInKubernetes(&resumedApp, cred); err != nil { return &AppStoreError{ Code: "KUBERNETES_RESUME_ERROR", Message: "恢复应用失败", Details: err.Error(), } } return nil } // 如果应用不支持 Kubernetes 部署,返回错误 return &AppStoreError{ Code: "UNSUPPORTED_DEPLOYMENT_TYPE", Message: fmt.Sprintf("应用部署类型 '%s' 不支持恢复功能,目前仅支持 Kubernetes 部署的应用", instance.DeployType), } }