* 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 build
import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"sync"
"time"
"gopkg.in/yaml.v3"
"gopkg.openfuyao.cn/bkeadm/pkg/global"
"gopkg.openfuyao.cn/bkeadm/pkg/infrastructure"
"gopkg.openfuyao.cn/bkeadm/pkg/root"
"gopkg.openfuyao.cn/bkeadm/utils"
"gopkg.openfuyao.cn/bkeadm/utils/log"
)
type Options struct {
root.Options
File string `json:"file"`
Target string `json:"target"`
Strategy string `json:"strategy"`
Arch string `json:"Arch"`
SkipTLSVerify bool `json:"skipTLSVerify"`
}
func (o *Options) Build() {
if !infrastructure.IsDocker() {
log.Error("This build instruction only supports running in docker environment.")
return
}
cfg, err := loadAndVerifyBuildConfig(o.File)
if err != nil {
log.Errorf("Failed to load and verify build config: %v", err)
return
}
if err := prepareBuildWorkspace(); err != nil {
log.Errorf("Failed to prepare build workspace: %v", err)
return
}
version, err := o.collectDependenciesAndImages(cfg)
if err != nil {
log.Errorf("Failed to collect dependencies and images: %v", err)
return
}
if err := o.createFinalPackage(cfg, version); err != nil {
log.Errorf("Failed to create final package: %v", err)
return
}
log.SteppedInfof("step.8", "Packaging complete %s", o.Target)
}
func loadAndVerifyBuildConfig(file string) (*BuildConfig, error) {
log.SteppedInfo("step.1", "Configuration file check")
cfg := &BuildConfig{}
yamlFile, err := os.ReadFile(file)
if err != nil {
log.Errorf("Failed to read the file, %v", err)
return nil, err
}
if err = yaml.Unmarshal(yamlFile, cfg); err != nil {
log.Errorf("Unable to serialize file, %v", err)
return nil, err
}
err = verifyConfigContent(cfg)
if err != nil {
log.Errorf("Configuration verification fails %v", err)
return nil, err
}
return cfg, nil
}
func prepareBuildWorkspace() error {
log.SteppedInfo("step.2", "Creates a workspace in the current directory")
if err := prepare(); err != nil {
log.Errorf("Failed to prepare workspace: %v", err)
return err
}
return nil
}
func (o *Options) collectDependenciesAndImages(cfg *BuildConfig) (string, error) {
var version string
var errNumber uint64
stopChan := make(chan struct{})
defer closeChanStruct(stopChan)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
var err error
version, err = collectRpmsAndBinary(cfg, stopChan, &errNumber)
if err != nil {
closeChanStruct(stopChan)
}
}()
wg.Add(1)
go func() {
defer wg.Done()
if err := collectRegistryImages(cfg, stopChan, &errNumber, o.SkipTLSVerify); err != nil {
closeChanStruct(stopChan)
}
}()
wg.Wait()
if errNumber > 0 {
log.Errorf("Build failures %d", errNumber)
return "", fmt.Errorf("build failures: %d", errNumber)
}
if len(version) == 0 {
log.Error("Failed to get bke version number, please check")
return "", fmt.Errorf("failed to get bke version")
}
return version, nil
}
func collectRpmsAndBinary(cfg *BuildConfig, stopChan chan struct{}, errNumber *uint64) (string, error) {
log.SteppedInfo("step.3", "Collect host dependency packages and package files")
if err := buildRpms(cfg, stopChan); err != nil {
log.Errorf("Collect host dependency packages and package files failed: %v", err)
*errNumber++
return "", err
}
log.SteppedInfo("step.4", "Collect the bke binary file")
version, err := buildBkeBinary()
if err != nil || len(version) == 0 {
log.Error("Collect the bke binary file failed, get bke version number failed")
if err != nil {
log.Errorf("Build bke binary failed: %v", err)
}
*errNumber++
return "", err
}
return version, nil
}
func collectRegistryImages(cfg *BuildConfig, stopChan chan struct{}, errNumber *uint64, skipTLSVerify bool) error {
log.SteppedInfo("step.5", "Collect the required image files")
if err := buildRegistry(cfg.Registry.ImageAddress, cfg.Registry.Architecture); err != nil {
log.Errorf("Collect image files from %s failed: %v", cfg.Registry.ImageAddress, err)
*errNumber++
return err
}
log.SteppedInfo("step.6", "Collect images from the source repository to the target repository")
if err := syncRepo(cfg, stopChan, skipTLSVerify); err != nil {
log.Errorf("Collect images from source repository to target repository failed: %v", err)
*errNumber++
return err
}
return nil
}
func (o *Options) createFinalPackage(cfg *BuildConfig, version string) error {
log.SteppedInfo("step.7", "Build the bke package, please wait for the larger package...")
if len(o.Target) == 0 {
fileInfo, err := os.Stat(o.File)
if err != nil {
log.Errorf("Stat build config file %s failed: %v", o.File, err)
return err
}
o.Target = path.Join(pwd, fmt.Sprintf("bke-%s-%s-%s-%s.tar.gz", version,
strings.TrimSuffix(fileInfo.Name(), ".yaml"),
strings.Join(cfg.Registry.Architecture, "-"), time.Now().Format("20060102150405")))
}
if err := compressedPackage(cfg, o.Target); err != nil {
log.Errorf("Compress package to %s failed: %v", o.Target, err)
return err
}
return nil
}
func compressedPackage(cfg *BuildConfig, target string) error {
err := writeManifestsFile(cfg, path.Join(bke, "manifests.yaml"))
if err != nil {
return err
}
return finalizeAndCompress(target)
}
func writeManifestsFile(cfg *BuildConfig, manifestPath string) error {
b, err := yaml.Marshal(cfg)
if err != nil {
log.Errorf("Description Failed to parse the configuration file %v", err)
return err
}
err = os.WriteFile(manifestPath, b, utils.DefaultFilePermission)
if err != nil {
log.Errorf("Failed to write the configuration file. Procedure %v", err)
return err
}
return nil
}
func finalizeAndCompress(target string) error {
if err := os.RemoveAll(tmp); err != nil {
log.Errorf("Unable to delete temporary file %v", err)
return err
}
if !strings.Contains(target, "/") {
target = filepath.Join(pwd, target)
}
if err := global.TaeGZWithoutChangeFile(packages, target); err != nil {
log.Errorf("Failed to compress the package to %s: %v", target, err)
return err
}
return nil
}