Files
forge/internal/controller/maker/mysql.go
2025-12-31 18:58:52 +08:00

189 lines
5.0 KiB
Go

package maker
import (
"context"
"encoding/base64"
"fmt"
"os"
"path/filepath"
"gitea.loveuer.com/yizhisec/pkg3/logger"
"yizhisec.com/hsv2/forge/internal/opt"
"yizhisec.com/hsv2/forge/pkg/downloader"
"yizhisec.com/hsv2/forge/pkg/model"
)
type MysqlOpt func(*mysqlOpt)
type mysqlOpt struct {
Replica int
Storage string
Password string
}
func WithMySQLReplica(replica int) MysqlOpt {
return func(o *mysqlOpt) {
o.Replica = replica
}
}
func WithMySQLStorage(storage string) MysqlOpt {
return func(o *mysqlOpt) {
// validate Kubernetes storage size string (e.g., "50Gi", "100Mi")
if opt.StorageSizeReg.MatchString(storage) {
o.Storage = storage
}
}
}
func WithMySQLPassword(password string) MysqlOpt {
return func(o *mysqlOpt) {
if password != "" {
o.Password = password
}
}
}
func (m *maker) MySQL(ctx context.Context, opts ...MysqlOpt) error {
const (
_yaml = `
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
namespace: db-mysql
type: Opaque
data:
ROOT_PASSWORD: %s
---
apiVersion: mysql.presslabs.org/v1alpha1
kind: MysqlCluster
metadata:
name: mysql-cluster
namespace: db-mysql
spec:
mysqlVersion: "8.0"
replicas: %d
image: docker.io/library/percona:8.0.28-20
secretName: mysql-secret
volumeSpec:
persistentVolumeClaim:
accessModes: ["ReadWriteOnce"]
storageClassName: longhorn
resources:
requests:
storage: %s
podSpec:
resources:
requests:
cpu: "250m"
memory: "250Mi"
limits:
cpu: "2"
memory: "4Gi"
# affinity:
# podAntiAffinity:
# requiredDuringSchedulingIgnoredDuringExecution: # 强制规则
# - labelSelector:
# matchExpressions:
# - key: app.kubernetes.io/name
# operator: In
# values: ["mysql"]
# - key: app.kubernetes.io/instance
# operator: In
# values: ["mysql-cluster"]
# topologyKey: "kubernetes.io/hostname" # 确保不同节点
---
apiVersion: mysql.presslabs.org/v1alpha1
kind: MysqlDatabase
metadata:
name: my-database-mie
namespace: db-mysql
spec:
database: mie
clusterRef:
name: mysql-cluster
namespace: db-mysql
`
)
var (
o = &mysqlOpt{
Replica: 2,
Storage: "50Gi",
Password: "L0hMysql.",
}
err error
chartURL = "https://artifactory.yizhisec.com:443/artifactory/filestore/hsv3/charts/mysql-operator-0.6.3.tgz"
location = filepath.Join(m.workdir, "dependency", "mysql")
chartFile = filepath.Join(location, "mysql-operator-0.6.3.tgz")
clusterFile = filepath.Join(location, "cluster.yaml")
)
for _, fn := range opts {
fn(o)
}
logger.Info("☑️ 开始构建 MySQL 资源...")
if err = os.MkdirAll(location, 0755); err != nil {
return err
}
o.Password = base64.StdEncoding.EncodeToString([]byte(o.Password))
logger.Debug("正在下载 MySQL operator chart..., url = %s, dir = %s", chartURL, location)
if err = downloader.Download(
ctx,
chartURL,
chartFile,
downloader.WithInsecureSkipVerify(),
); err != nil {
logger.Info("❌ 下载 MySQL operator chart 失败")
return err
}
logger.Debug("✅ 成功下载 MySQL operator chart")
bs := []byte(fmt.Sprintf(_yaml, o.Password, o.Replica, o.Storage))
// Generate secret.yaml
if err = os.WriteFile(clusterFile, bs, 0644); err != nil {
logger.Debug("创建 database.yaml 失败: %v", err)
logger.Info("❌ 创建 database.yaml 失败")
return err
}
logger.Debug("✅ 成功创建 database.yaml")
logger.Debug("☑️ MakeMysql: 开始获取所需镜像...")
var images = []*model.Image{
{Name: "docker.io/bitpoke/mysql-operator:v0.6.3", Fallback: "docker-mirror.yizhisec.com/bitpoke/mysql-operator:v0.6.3", Save: "bitpoke.mysql-operator.v0.6.3.tar"},
{Name: "docker.io/bitpoke/mysql-operator-orchestrator:v0.6.3", Fallback: "docker-mirror.yizhisec.com/bitpoke/mysql-operator-orchestrator:v0.6.3", Save: "bitpoke.mysql-operator-orchestrator.v0.6.3.tar"},
{Name: "docker.io/bitpoke/mysql-operator-sidecar-8.0:v0.6.3", Fallback: "docker-mirror.yizhisec.com/bitpoke/mysql-operator-sidecar-8.0:v0.6.3", Save: "bitpoke.mysql-operator-sidecar-8.0.v0.6.3.tar"},
{Name: "docker.io/library/percona:8.0.28-20", Fallback: "docker-mirror.yizhisec.com/library/percona:8.0.28-20", Save: "percona.8.0.28-20.tar"},
{Name: "docker.io/prom/mysqld-exporter:v0.13.0", Fallback: "docker-mirror.yizhisec.com/prom/mysqld-exporter:v0.13.0", Save: "prom.mysqld-exporter.v0.13.0.tar"},
}
for _, image := range images {
opts := []ImageOpt{
WithImageFallback(image.Fallback),
WithImageSave(filepath.Join(location, image.Save)),
WithImageForcePull(image.Force),
}
if err := m.Image(ctx, image.Name, opts...); err != nil {
logger.Error("❌ MakeMysql: 获取镜像失败: %s, 可以手动获取后重试", image.Name)
logger.Debug("❌ MakeMysql: 获取镜像失败: %s, %v", image.Name, err)
return err
}
}
logger.Debug("✅ MakeMysql: 获取镜像成功!!!")
logger.Info("✅ MySQL 资源构建成功!!!")
return nil
}