263 lines
7.3 KiB
Go
263 lines
7.3 KiB
Go
|
package utils
|
|||
|
|
|||
|
import (
|
|||
|
"bytes"
|
|||
|
"fmt"
|
|||
|
"text/template"
|
|||
|
|
|||
|
applicationv1 "code.gitea.io/gitea/modules/k8s/api/application/v1"
|
|||
|
apps_v1 "k8s.io/api/apps/v1"
|
|||
|
core_v1 "k8s.io/api/core/v1"
|
|||
|
"k8s.io/apimachinery/pkg/runtime"
|
|||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
|||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
|||
|
"k8s.io/client-go/kubernetes/scheme"
|
|||
|
)
|
|||
|
|
|||
|
const (
|
|||
|
TemplatePath = "modules/k8s/controller/application/templates/"
|
|||
|
)
|
|||
|
|
|||
|
// parseTemplate 解析 Go Template 模板文件
|
|||
|
func parseTemplate(templateName string, app *applicationv1.Application) ([]byte, error) {
|
|||
|
tmpl, err := template.
|
|||
|
New(templateName + ".yaml").
|
|||
|
Funcs(template.FuncMap{"default": DefaultFunc}).
|
|||
|
ParseFiles(TemplatePath + templateName + ".yaml")
|
|||
|
if err != nil {
|
|||
|
return nil, fmt.Errorf("failed to parse template %s: %w", templateName, err)
|
|||
|
}
|
|||
|
|
|||
|
b := new(bytes.Buffer)
|
|||
|
err = tmpl.Execute(b, app)
|
|||
|
if err != nil {
|
|||
|
return nil, fmt.Errorf("failed to execute template %s: %w", templateName, err)
|
|||
|
}
|
|||
|
|
|||
|
return b.Bytes(), nil
|
|||
|
}
|
|||
|
|
|||
|
// NewDeployment 使用模板创建 Deployment
|
|||
|
func NewDeployment(app *applicationv1.Application) (*apps_v1.Deployment, error) {
|
|||
|
yamlBytes, err := parseTemplate("deployment", app)
|
|||
|
if err != nil {
|
|||
|
return nil, fmt.Errorf("failed to parse deployment template: %w", err)
|
|||
|
}
|
|||
|
|
|||
|
deployment := &apps_v1.Deployment{}
|
|||
|
decoder := serializer.NewCodecFactory(scheme.Scheme).UniversalDecoder()
|
|||
|
err = runtime.DecodeInto(decoder, yamlBytes, deployment)
|
|||
|
if err != nil {
|
|||
|
return nil, fmt.Errorf("failed to decode deployment YAML: %w", err)
|
|||
|
}
|
|||
|
|
|||
|
// 设置 ObjectMeta
|
|||
|
deployment.Name = app.Name
|
|||
|
deployment.Namespace = app.Namespace
|
|||
|
|
|||
|
return deployment, nil
|
|||
|
}
|
|||
|
|
|||
|
// NewService 使用模板创建 Service
|
|||
|
func NewService(app *applicationv1.Application) (*core_v1.Service, error) {
|
|||
|
// 检查是否需要创建 Service
|
|||
|
if !shouldCreateService(app) {
|
|||
|
return nil, nil
|
|||
|
}
|
|||
|
|
|||
|
yamlBytes, err := parseTemplate("service", app)
|
|||
|
if err != nil {
|
|||
|
return nil, fmt.Errorf("failed to parse service template: %w", err)
|
|||
|
}
|
|||
|
|
|||
|
service := &core_v1.Service{}
|
|||
|
decoder := serializer.NewCodecFactory(scheme.Scheme).UniversalDecoder()
|
|||
|
err = runtime.DecodeInto(decoder, yamlBytes, service)
|
|||
|
if err != nil {
|
|||
|
return nil, fmt.Errorf("failed to decode service YAML: %w", err)
|
|||
|
}
|
|||
|
|
|||
|
// 设置 ObjectMeta
|
|||
|
service.Name = app.Name + "-svc"
|
|||
|
service.Namespace = app.Namespace
|
|||
|
|
|||
|
// 后处理:根据新的 Service 配置更新
|
|||
|
updateServiceWithConfig(service, app)
|
|||
|
|
|||
|
return service, nil
|
|||
|
}
|
|||
|
|
|||
|
// NewStatefulSet 使用模板创建 StatefulSet
|
|||
|
func NewStatefulSet(app *applicationv1.Application) (*apps_v1.StatefulSet, error) {
|
|||
|
yamlBytes, err := parseTemplate("statefulset", app)
|
|||
|
if err != nil {
|
|||
|
return nil, fmt.Errorf("failed to parse statefulset template: %w", err)
|
|||
|
}
|
|||
|
|
|||
|
statefulSet := &apps_v1.StatefulSet{}
|
|||
|
decoder := serializer.NewCodecFactory(scheme.Scheme).UniversalDecoder()
|
|||
|
err = runtime.DecodeInto(decoder, yamlBytes, statefulSet)
|
|||
|
if err != nil {
|
|||
|
return nil, fmt.Errorf("failed to decode statefulset YAML: %w", err)
|
|||
|
}
|
|||
|
|
|||
|
// 设置 ObjectMeta
|
|||
|
statefulSet.Name = app.Name
|
|||
|
statefulSet.Namespace = app.Namespace
|
|||
|
|
|||
|
return statefulSet, nil
|
|||
|
}
|
|||
|
|
|||
|
// updateServiceWithConfig 根据新的 Service 配置更新 Service
|
|||
|
func updateServiceWithConfig(service *core_v1.Service, app *applicationv1.Application) {
|
|||
|
if app.Spec.Service == nil {
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
config := app.Spec.Service
|
|||
|
|
|||
|
// 更新服务类型
|
|||
|
if config.Type != "" {
|
|||
|
service.Spec.Type = core_v1.ServiceType(config.Type)
|
|||
|
}
|
|||
|
|
|||
|
// 添加自定义注解
|
|||
|
if config.Annotations != nil {
|
|||
|
if service.Annotations == nil {
|
|||
|
service.Annotations = make(map[string]string)
|
|||
|
}
|
|||
|
for k, v := range config.Annotations {
|
|||
|
service.Annotations[k] = v
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 添加自定义标签
|
|||
|
if config.Labels != nil {
|
|||
|
if service.Labels == nil {
|
|||
|
service.Labels = make(map[string]string)
|
|||
|
}
|
|||
|
for k, v := range config.Labels {
|
|||
|
service.Labels[k] = v
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 设置特定配置
|
|||
|
switch config.Type {
|
|||
|
case "LoadBalancer":
|
|||
|
if config.LoadBalancerIP != "" {
|
|||
|
service.Spec.LoadBalancerIP = config.LoadBalancerIP
|
|||
|
}
|
|||
|
if len(config.LoadBalancerSourceRanges) > 0 {
|
|||
|
service.Spec.LoadBalancerSourceRanges = config.LoadBalancerSourceRanges
|
|||
|
}
|
|||
|
case "ExternalName":
|
|||
|
if config.ExternalName != "" {
|
|||
|
service.Spec.ExternalName = config.ExternalName
|
|||
|
}
|
|||
|
// ExternalName 类型不需要 selector 和 ports
|
|||
|
service.Spec.Selector = nil
|
|||
|
service.Spec.Ports = nil
|
|||
|
}
|
|||
|
|
|||
|
// 设置会话亲和性
|
|||
|
if config.SessionAffinity != "" {
|
|||
|
service.Spec.SessionAffinity = core_v1.ServiceAffinity(config.SessionAffinity)
|
|||
|
}
|
|||
|
|
|||
|
// 更新端口配置(如果有自定义端口配置)
|
|||
|
if len(config.Ports) > 0 {
|
|||
|
service.Spec.Ports = getServicePorts(app, config)
|
|||
|
} else if config.NodePorts != nil {
|
|||
|
// 如果只配置了 NodePorts,更新现有端口的 NodePort
|
|||
|
updateServiceNodePorts(service, config)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// updateServiceNodePorts 更新服务的 NodePort 配置
|
|||
|
func updateServiceNodePorts(service *core_v1.Service, config *applicationv1.ServiceConfig) {
|
|||
|
for i, port := range service.Spec.Ports {
|
|||
|
if nodePort, exists := config.NodePorts[port.Name]; exists {
|
|||
|
service.Spec.Ports[i].NodePort = nodePort
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// getServicePorts 获取 Service 端口配置
|
|||
|
func getServicePorts(app *applicationv1.Application, config *applicationv1.ServiceConfig) []core_v1.ServicePort {
|
|||
|
var servicePorts []core_v1.ServicePort
|
|||
|
|
|||
|
// 如果配置了自定义端口,使用自定义端口
|
|||
|
if len(config.Ports) > 0 {
|
|||
|
for _, port := range config.Ports {
|
|||
|
servicePort := core_v1.ServicePort{
|
|||
|
Name: port.Name,
|
|||
|
Port: port.Port,
|
|||
|
Protocol: core_v1.Protocol(getPortProtocol(port.Protocol)),
|
|||
|
}
|
|||
|
|
|||
|
// 设置目标端口
|
|||
|
if port.TargetPort != "" {
|
|||
|
servicePort.TargetPort = intstr.FromString(port.TargetPort)
|
|||
|
} else {
|
|||
|
servicePort.TargetPort = intstr.FromInt(int(port.Port))
|
|||
|
}
|
|||
|
|
|||
|
// 设置 NodePort(仅适用于 NodePort 和 LoadBalancer 类型)
|
|||
|
if (config.Type == "NodePort" || config.Type == "LoadBalancer") && port.NodePort > 0 {
|
|||
|
servicePort.NodePort = port.NodePort
|
|||
|
}
|
|||
|
|
|||
|
servicePorts = append(servicePorts, servicePort)
|
|||
|
}
|
|||
|
} else {
|
|||
|
// 使用模板中的端口配置
|
|||
|
for _, port := range app.Spec.Template.Ports {
|
|||
|
servicePort := core_v1.ServicePort{
|
|||
|
Name: port.Name,
|
|||
|
Port: port.Port,
|
|||
|
TargetPort: intstr.FromInt(int(port.Port)),
|
|||
|
Protocol: core_v1.Protocol(getPortProtocol(port.Protocol)),
|
|||
|
}
|
|||
|
|
|||
|
// 如果是 NodePort 类型,检查是否有指定的 NodePort
|
|||
|
if (config.Type == "NodePort" || config.Type == "LoadBalancer") &&
|
|||
|
config.NodePorts != nil {
|
|||
|
if nodePort, exists := config.NodePorts[port.Name]; exists {
|
|||
|
servicePort.NodePort = nodePort
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
servicePorts = append(servicePorts, servicePort)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return servicePorts
|
|||
|
}
|
|||
|
|
|||
|
// shouldCreateService 判断是否需要创建 Service
|
|||
|
func shouldCreateService(app *applicationv1.Application) bool {
|
|||
|
// 优先使用新的 Service 配置
|
|||
|
if app.Spec.Service != nil {
|
|||
|
return app.Spec.Service.Enabled
|
|||
|
}
|
|||
|
|
|||
|
// 向后兼容:使用旧的 expose 配置
|
|||
|
return app.Spec.Expose && len(app.Spec.Template.Ports) > 0
|
|||
|
}
|
|||
|
|
|||
|
// getPortProtocol 获取端口协议,设置默认值
|
|||
|
func getPortProtocol(protocol string) string {
|
|||
|
if protocol == "" {
|
|||
|
return "TCP"
|
|||
|
}
|
|||
|
return protocol
|
|||
|
}
|
|||
|
|
|||
|
// DefaultFunc 函数用于实现默认值
|
|||
|
func DefaultFunc(value interface{}, defaultValue interface{}) interface{} {
|
|||
|
if value == nil || value == "" {
|
|||
|
return defaultValue
|
|||
|
}
|
|||
|
return value
|
|||
|
}
|