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" ) 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" mysqlDir = filepath.Join(m.workdir, "dependency", "mysql") chartFile = filepath.Join(mysqlDir, "mysql-operator-0.6.3.tgz") clusterFile = filepath.Join(mysqlDir, "cluster.yaml") ) for _, fn := range opts { fn(o) } logger.Info("☑️ 开始构建 MySQL 资源...") if err = os.MkdirAll(mysqlDir, 0755); err != nil { return err } o.Password = base64.StdEncoding.EncodeToString([]byte(o.Password)) logger.Debug("正在下载 MySQL operator chart..., url = %s, dir = %s", chartURL, mysqlDir) 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.Info("✅ MySQL 资源构建成功!!!") return nil }