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

306 lines
9.5 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: Coordinates Kubernetes operations for installs.
*/
package appstore
import (
"context"
"fmt"
k8s "code.gitea.io/gitea/modules/k8s"
applicationv1 "code.gitea.io/gitea/modules/k8s/api/application/v1"
"code.gitea.io/gitea/modules/k8s/application"
"code.gitea.io/gitea/modules/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
dynamicclient "k8s.io/client-go/dynamic"
)
// K8sManager handles Kubernetes-specific application operations
type K8sManager struct {
ctx context.Context
}
// K8sCredential describes how to reach a Kubernetes cluster.
type K8sCredential struct {
K8sURL string
Token string
}
// NewK8sManager creates a new Kubernetes manager
func NewK8sManager(ctx context.Context) *K8sManager {
return &K8sManager{
ctx: ctx,
}
}
// 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
func (km *K8sManager) InstallAppToKubernetes(app *App, cred *K8sCredential) error {
// Validate that the app supports Kubernetes deployment
// 优先检查实际部署类型,如果为空则检查支持的部署类型
if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" {
return &AppStoreError{
Code: "DEPLOYMENT_TYPE_ERROR",
Message: "Application is not configured for Kubernetes deployment",
}
}
if app.Deploy.Type == "" && app.DeploymentType != "kubernetes" && app.DeploymentType != "both" {
return &AppStoreError{
Code: "DEPLOYMENT_TYPE_ERROR",
Message: "Application does not support Kubernetes deployment",
}
}
// Get Kubernetes client
k8sClient, err := km.buildK8sClient(cred)
if err != nil {
return &AppStoreError{
Code: "KUBERNETES_CLIENT_ERROR",
Message: "Failed to create Kubernetes client",
Details: err.Error(),
}
}
// Build Kubernetes create options
createOptions, err := BuildK8sCreateOptions(app)
if err != nil {
return &AppStoreError{
Code: "KUBERNETES_OPTIONS_ERROR",
Message: "Failed to build Kubernetes create options",
Details: err.Error(),
}
}
// Create the application in Kubernetes
_, err = application.CreateApplication(km.ctx, k8sClient, createOptions)
if err != nil {
return &AppStoreError{
Code: "KUBERNETES_CREATE_ERROR",
Message: "Failed to create application in Kubernetes",
Details: err.Error(),
}
}
return nil
}
// UninstallAppFromKubernetes uninstalls an application from a Kubernetes cluster
func (km *K8sManager) UninstallAppFromKubernetes(app *App, cred *K8sCredential) error {
// Validate that the app supports Kubernetes deployment
// 优先检查实际部署类型,如果为空则检查支持的部署类型
if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" {
return &AppStoreError{
Code: "DEPLOYMENT_TYPE_ERROR",
Message: "Application is not configured for Kubernetes deployment",
}
}
if app.Deploy.Type == "" && app.DeploymentType != "kubernetes" && app.DeploymentType != "both" {
return &AppStoreError{
Code: "DEPLOYMENT_TYPE_ERROR",
Message: "Application does not support Kubernetes deployment",
}
}
// Get Kubernetes client
k8sClient, err := km.buildK8sClient(cred)
if err != nil {
return &AppStoreError{
Code: "KUBERNETES_CLIENT_ERROR",
Message: "Failed to create Kubernetes client",
Details: err.Error(),
}
}
// Build Kubernetes delete options
deleteOptions, err := BuildK8sDeleteOptions(app, "")
if err != nil {
return &AppStoreError{
Code: "KUBERNETES_OPTIONS_ERROR",
Message: "Failed to build Kubernetes delete options",
Details: err.Error(),
}
}
// Delete the application from Kubernetes
err = application.DeleteApplication(km.ctx, k8sClient, deleteOptions)
if err != nil {
return &AppStoreError{
Code: "KUBERNETES_DELETE_ERROR",
Message: "Failed to delete application from Kubernetes",
Details: err.Error(),
}
}
return nil
}
// GetAppFromKubernetes gets an application from a Kubernetes cluster
func (km *K8sManager) GetAppFromKubernetes(app *App, cred *K8sCredential) (interface{}, error) {
// Validate that the app supports Kubernetes deployment
// 优先检查实际部署类型,如果为空则检查支持的部署类型
if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" {
return nil, &AppStoreError{
Code: "DEPLOYMENT_TYPE_ERROR",
Message: "Application is not configured for Kubernetes deployment",
}
}
if app.Deploy.Type == "" && app.DeploymentType != "kubernetes" && app.DeploymentType != "both" {
return nil, &AppStoreError{
Code: "DEPLOYMENT_TYPE_ERROR",
Message: "Application does not support Kubernetes deployment",
}
}
// Get Kubernetes client
k8sClient, err := km.buildK8sClient(cred)
if err != nil {
return nil, &AppStoreError{
Code: "KUBERNETES_CLIENT_ERROR",
Message: "Failed to create Kubernetes client",
Details: err.Error(),
}
}
// Build Kubernetes get options
getOptions, err := BuildK8sGetOptions(app, "", false)
if err != nil {
return nil, &AppStoreError{
Code: "KUBERNETES_OPTIONS_ERROR",
Message: "Failed to build Kubernetes get options",
Details: err.Error(),
}
}
// Get the application from Kubernetes
return application.GetApplication(km.ctx, k8sClient, getOptions)
}
// ListAppsFromKubernetes lists applications from a Kubernetes cluster
func (km *K8sManager) ListAppsFromKubernetes(app *App, cred *K8sCredential) (*applicationv1.ApplicationList, error) {
// Validate that the app supports Kubernetes deployment
// 优先检查实际部署类型,如果为空则检查支持的部署类型
if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" {
return nil, &AppStoreError{
Code: "DEPLOYMENT_TYPE_ERROR",
Message: "Application is not configured for Kubernetes deployment",
}
}
if app.Deploy.Type == "" && app.DeploymentType != "kubernetes" && app.DeploymentType != "both" {
return nil, &AppStoreError{
Code: "DEPLOYMENT_TYPE_ERROR",
Message: "Application does not support Kubernetes deployment",
}
}
// Get Kubernetes client
k8sClient, err := km.buildK8sClient(cred)
if err != nil {
return nil, &AppStoreError{
Code: "KUBERNETES_CLIENT_ERROR",
Message: "Failed to create Kubernetes client",
Details: err.Error(),
}
}
// Build Kubernetes list options
listOptions, err := BuildK8sListOptions("", metav1.ListOptions{})
if err != nil {
return nil, &AppStoreError{
Code: "KUBERNETES_OPTIONS_ERROR",
Message: "Failed to build Kubernetes list options",
Details: err.Error(),
}
}
// List applications from Kubernetes
return application.ListApplications(km.ctx, k8sClient, listOptions)
}
// UpdateAppInKubernetes updates an application in a Kubernetes cluster
func (km *K8sManager) UpdateAppInKubernetes(app *App, cred *K8sCredential) error {
// Validate that the app supports Kubernetes deployment
// 优先检查实际部署类型,如果为空则检查支持的部署类型
if app.Deploy.Type != "" && app.Deploy.Type != "kubernetes" {
return &AppStoreError{
Code: "DEPLOYMENT_TYPE_ERROR",
Message: "Application is not configured for Kubernetes deployment",
}
}
if app.Deploy.Type == "" && app.DeploymentType != "kubernetes" && app.DeploymentType != "both" {
return &AppStoreError{
Code: "DEPLOYMENT_TYPE_ERROR",
Message: "Application does not support Kubernetes deployment",
}
}
// Get Kubernetes client
k8sClient, err := km.buildK8sClient(cred)
if err != nil {
return &AppStoreError{
Code: "KUBERNETES_CLIENT_ERROR",
Message: "Failed to create Kubernetes client",
Details: err.Error(),
}
}
// 先获取现有的应用,以保留元数据(特别是 resourceVersion
existingApp, err := km.GetAppFromKubernetes(app, cred)
if err != nil {
// 如果应用不存在,尝试创建新应用
log.Warn("Application not found in Kubernetes, attempting to create new application: %v", err)
return &AppStoreError{
Code: "APP_NOT_FOUND",
Message: "应用在 Kubernetes 中不存在,请先安装应用",
Details: err.Error(),
}
}
// 确保获取到的是 Application 类型
appData, ok := existingApp.(*applicationv1.Application)
if !ok {
return &AppStoreError{
Code: "TYPE_ERROR",
Message: "无法获取现有应用数据",
Details: fmt.Sprintf("期望 *applicationv1.Application 类型,实际类型: %T", existingApp),
}
}
// Build Kubernetes update options传入现有应用以保留元数据
updateOptions, err := BuildK8sUpdateOptions(app, "", appData)
if err != nil {
return &AppStoreError{
Code: "KUBERNETES_OPTIONS_ERROR",
Message: "Failed to build Kubernetes update options",
Details: err.Error(),
}
}
// Update the application in Kubernetes
_, err = application.UpdateApplication(km.ctx, k8sClient, updateOptions)
if err != nil {
return &AppStoreError{
Code: "KUBERNETES_UPDATE_ERROR",
Message: "Failed to update application in Kubernetes",
Details: err.Error(),
}
}
return nil
}
// IsApplicationReady 封装底层就绪判断,避免上层直接依赖 k8s/application 的实现
func (km *K8sManager) IsApplicationReady(status *applicationv1.ApplicationStatus) bool {
return application.IsApplicationStatusReady(status)
}