Files
devstar-create-from-template/services/appstore/manager.go
2025-08-25 15:46:12 +08:00

550 lines
15 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 2024 The Devstar Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package appstore
import (
"context"
"encoding/json"
"net/http"
"strings"
"time"
appstore_model "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
}
// NewManager creates a new app store manager for database operations
func NewManager(ctx *gitea_context.Context) *Manager {
return &Manager{
parser: NewParser(),
ctx: *ctx,
k8s: NewK8sManager(*ctx),
}
}
// 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
}
// 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.ToLower(appTag) == strings.ToLower(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.validateApp(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.ParseApp(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 from the database
func (m *Manager) RemoveApp(appID string) error {
// Get existing app from database
existingAppStore, err := appstore_model.GetAppStoreByAppID(m.ctx, appID)
if err != nil {
return &AppStoreError{
Code: "APP_NOT_FOUND",
Message: "App not found in database",
Details: err.Error(),
}
}
// Delete from database
if err := appstore_model.DeleteAppStore(m.ctx, existingAppStore.ID); err != nil {
return &AppStoreError{
Code: "DATABASE_ERROR",
Message: "Failed to delete app from database",
Details: err.Error(),
}
}
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, kubeconfig string, kubeconfigContext 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(),
}
}
// 根据安装目标和应用实际部署类型决定安装方式
if installTarget == "kubeconfig" && kubeconfig != "" {
// 安装到外部 Kubernetes 集群
if err := m.InstallAppToKubernetes(mergedApp, []byte(kubeconfig), kubeconfigContext); err != nil {
return &AppStoreError{
Code: "KUBERNETES_INSTALL_ERROR",
Message: "Kubernetes 安装失败",
Details: err.Error(),
}
}
} else {
// 根据应用实际部署类型决定本地安装方式
log.Info("InstallApp: mergedApp.Deploy.Type = %s", mergedApp.Deploy.Type)
switch mergedApp.Deploy.Type {
case "kubernetes":
// 应用要部署到 Kubernetes安装到本地 K8s 集群
if err := m.InstallAppToKubernetes(mergedApp, nil, ""); err != nil {
return &AppStoreError{
Code: "KUBERNETES_INSTALL_ERROR",
Message: "本地 Kubernetes 安装失败",
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: "本地安装功能开发中",
}
}
}
return nil
}
// UninstallApp uninstalls an application based on the provided parameters
func (m *Manager) UninstallApp(appID string, installTarget string, kubeconfig string, kubeconfigContext string) error {
// 获取应用信息
app, err := m.GetApp(appID)
if err != nil {
return &AppStoreError{
Code: "APP_NOT_FOUND",
Message: "获取应用失败",
Details: err.Error(),
}
}
// 根据安装目标和应用实际部署类型决定卸载方式
if installTarget == "kubeconfig" && kubeconfig != "" {
// 从外部 Kubernetes 集群卸载
if err := m.UninstallAppFromKubernetes(app, []byte(kubeconfig), kubeconfigContext); err != nil {
return &AppStoreError{
Code: "KUBERNETES_UNINSTALL_ERROR",
Message: "Kubernetes 卸载失败",
Details: err.Error(),
}
}
} else {
// 根据应用实际部署类型决定本地卸载方式
switch app.Deploy.Type {
case "kubernetes":
// 应用部署在 Kubernetes从本地 K8s 集群卸载
if err := m.UninstallAppFromKubernetes(app, nil, ""); err != nil {
return &AppStoreError{
Code: "KUBERNETES_UNINSTALL_ERROR",
Message: "本地 Kubernetes 卸载失败",
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: "本地卸载功能开发中",
}
}
}
return nil
}
// InstallAppToKubernetes installs an application to a Kubernetes cluster
func (m *Manager) InstallAppToKubernetes(app *App, kubeconfig []byte, contextName string) error {
return m.k8s.InstallAppToKubernetes(app, kubeconfig, contextName)
}
// UninstallAppFromKubernetes uninstalls an application from a Kubernetes cluster
func (m *Manager) UninstallAppFromKubernetes(app *App, kubeconfig []byte, contextName string) error {
return m.k8s.UninstallAppFromKubernetes(app, kubeconfig, contextName)
}
// GetAppFromKubernetes gets an application from a Kubernetes cluster
func (m *Manager) GetAppFromKubernetes(app *App, kubeconfig []byte, contextName string) (interface{}, error) {
return m.k8s.GetAppFromKubernetes(app, kubeconfig, contextName)
}
// ListAppsFromKubernetes lists applications from a Kubernetes cluster
func (m *Manager) ListAppsFromKubernetes(app *App, kubeconfig []byte, contextName string) (*applicationv1.ApplicationList, error) {
return m.k8s.ListAppsFromKubernetes(app, kubeconfig, contextName)
}
// UpdateAppInKubernetes updates an application in a Kubernetes cluster
func (m *Manager) UpdateAppInKubernetes(app *App, kubeconfig []byte, contextName string) error {
return m.k8s.UpdateAppInKubernetes(app, kubeconfig, contextName)
}