* Copyright (c) 2025 Bocloud Technologies Co., Ltd.
* installer is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain n copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
******************************************************************/
package cluster
import (
"errors"
"os"
"strings"
configv1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/bkecommon/v1beta1"
bkecommon "gopkg.openfuyao.cn/cluster-api-provider-bke/common"
configinit "gopkg.openfuyao.cn/cluster-api-provider-bke/common/cluster/initialize"
"gopkg.openfuyao.cn/cluster-api-provider-bke/common/cluster/validation"
"gopkg.openfuyao.cn/cluster-api-provider-bke/common/security"
yaml2 "sigs.k8s.io/yaml"
)
type ClusterResources struct {
BKECluster *configv1beta1.BKECluster
BKENodes []configv1beta1.BKENode
}
func NewClusterResourcesFromFiles(clusterFile, nodesFile string) (*ClusterResources, error) {
resources := &ClusterResources{}
bkeCluster, err := loadBKEClusterFromFile(clusterFile)
if err != nil {
return nil, err
}
resources.BKECluster = bkeCluster
bkeNodes, err := loadBKENodesFromFile(nodesFile)
if err != nil {
return nil, err
}
resources.BKENodes = bkeNodes
if err := processClusterResources(resources); err != nil {
return nil, err
}
return resources, nil
}
func loadBKEClusterFromFile(file string) (*configv1beta1.BKECluster, error) {
conf := &configv1beta1.BKECluster{}
yamlFile, err := os.ReadFile(file)
if err != nil {
return nil, err
}
if err := yaml2.Unmarshal(yamlFile, conf); err != nil {
return nil, err
}
if conf.Spec.ClusterConfig == nil {
return nil, errors.New("the cluster configuration cannot be empty")
}
return conf, nil
}
func loadBKENodesFromFile(file string) ([]configv1beta1.BKENode, error) {
yamlFile, err := os.ReadFile(file)
if err != nil {
return nil, err
}
var nodes []configv1beta1.BKENode
nodeList := &configv1beta1.BKENodeList{}
if err := yaml2.Unmarshal(yamlFile, nodeList); err == nil && len(nodeList.Items) > 0 {
return nodeList.Items, nil
}
if strings.Contains(string(yamlFile), "\n---") {
docs := splitYAMLDocuments(yamlFile)
for _, doc := range docs {
if len(doc) == 0 {
continue
}
n := configv1beta1.BKENode{}
if err := yaml2.Unmarshal(doc, &n); err == nil && n.Spec.IP != "" {
nodes = append(nodes, n)
}
}
if len(nodes) > 0 {
return nodes, nil
}
}
node := &configv1beta1.BKENode{}
if err := yaml2.Unmarshal(yamlFile, node); err == nil && node.Spec.IP != "" {
nodes = append(nodes, *node)
return nodes, nil
}
if len(nodes) == 0 {
return nil, errors.New("no valid BKENode resources found in the file")
}
return nodes, nil
}
func splitYAMLDocuments(data []byte) [][]byte {
var docs [][]byte
var currentDoc []byte
lines := splitLines(data)
for _, line := range lines {
if string(line) == "---" {
if len(currentDoc) > 0 {
docs = append(docs, currentDoc)
currentDoc = nil
}
} else {
currentDoc = append(currentDoc, line...)
currentDoc = append(currentDoc, '\n')
}
}
if len(currentDoc) > 0 {
docs = append(docs, currentDoc)
}
return docs
}
func splitLines(data []byte) [][]byte {
var lines [][]byte
var line []byte
for _, b := range data {
if b == '\n' {
lines = append(lines, line)
line = nil
} else if b != '\r' {
line = append(line, b)
}
}
if len(line) > 0 {
lines = append(lines, line)
}
return lines
}
func processClusterResources(resources *ClusterResources) error {
bc, err := configinit.NewBkeConfigFromClusterConfig(resources.BKECluster.Spec.ClusterConfig)
if err != nil {
return err
}
if err := bc.Validate(); err != nil {
return err
}
if len(resources.BKENodes) == 0 {
return errors.New("at least one BKENode resource is required")
}
if err := validation.ValidateBKENodes(resources.BKENodes); err != nil {
return err
}
for i := range resources.BKENodes {
node := &resources.BKENodes[i]
_, err := security.AesDecrypt(node.Spec.Password)
if err != nil {
p, err := security.AesEncrypt(node.Spec.Password)
if err != nil {
return err
}
node.Spec.Password = p
}
if node.Labels == nil {
node.Labels = make(map[string]string)
}
node.Labels["cluster.x-k8s.io/cluster-name"] = resources.BKECluster.Name
if node.Namespace == "" {
node.Namespace = resources.BKECluster.Namespace
}
}
resources.BKECluster.Spec.ClusterConfig, _ = configinit.ConvertBkEConfig(bc)
if resources.BKECluster.Spec.ClusterConfig.CustomExtra == nil {
resources.BKECluster.Spec.ClusterConfig.CustomExtra = map[string]string{
bkecommon.BKEAgentListenerAnnotationKey: bkecommon.BKEAgentListenerCurrent,
}
} else {
if len(resources.BKECluster.Spec.ClusterConfig.CustomExtra[bkecommon.BKEAgentListenerAnnotationKey]) == 0 {
resources.BKECluster.Spec.ClusterConfig.CustomExtra[bkecommon.BKEAgentListenerAnnotationKey] = bkecommon.BKEAgentListenerCurrent
}
}
return nil
}
func NewBKEClusterFromFile(file string) (*configv1beta1.BKECluster, error) {
return loadBKEClusterFromFile(file)
}