Files
devstar/services/appstore/manager.go
2025-11-25 14:24:05 +08:00

986 lines
27 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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),
}
}