* 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"
"strings"
"text/tabwriter"
"gopkg.in/yaml.v3"
reg "gopkg.openfuyao.cn/bkeadm/pkg/registry"
"gopkg.openfuyao.cn/bkeadm/pkg/root"
"gopkg.openfuyao.cn/bkeadm/utils/log"
)
type PreCheckOptions struct {
root.Options
File string `json:"file"`
OnlyImage bool `json:"onlyImage"`
}
func loadBuildConfig(filePath string) (*BuildConfig, error) {
cfg := &BuildConfig{}
yamlFile, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("failed to read the file, %v", err)
}
if err = yaml.Unmarshal(yamlFile, cfg); err != nil {
return nil, fmt.Errorf("unable to serialize file, %v", err)
}
return cfg, nil
}
func imagePrefix(sourceRepo string) string {
repos := strings.Split(sourceRepo, "/")
if len(repos) <= 1 {
return ""
}
prefixImage := strings.Join(repos[1:], "/")
if !strings.HasSuffix(prefixImage, "/") {
prefixImage += "/"
}
return prefixImage
}
func imageTagMap(subImage SubImage, prefixImage string, arch []string) map[string][]string {
images := map[string][]string{}
for _, image := range subImage.Images {
img := prefixImage + image.Name
images[img] = processTags(image.Tag, arch)
}
return images
}
func processTags(tags []string, arch []string) []string {
var result []string
for _, tag := range tags {
result = append(result, expandTag(tag, arch)...)
}
return result
}
func expandTag(tag string, arch []string) []string {
if !strings.Contains(tag, cut) {
return []string{tag}
}
tags := make([]string, 0, len(arch))
for _, ar := range arch {
tags = append(tags, strings.ReplaceAll(tag, cut, fmt.Sprintf("-%s-", ar)))
}
return tags
}
func checkRepoImages(cfg *BuildConfig) ([][]string, error) {
var rows [][]string
for _, cr := range cfg.Repos {
for _, subImage := range cr.SubImages {
repos := strings.Split(subImage.SourceRepo, "/")
prefixImage := imagePrefix(subImage.SourceRepo)
images := imageTagMap(subImage, prefixImage, cr.Architecture)
res, err := reg.ViewRepoImage(repos[0], images)
if err != nil {
return nil, fmt.Errorf("failed to check the image %s, %v", subImage.SourceRepo, err)
}
for _, v := range res {
rows = append(rows, v)
}
}
}
return rows, nil
}
func (bp *PreCheckOptions) PreCheck() {
cfg, err := loadBuildConfig(bp.File)
if err != nil {
log.Errorf("Load build config %s failed: %v", bp.File, err)
return
}
fmt.Println("Checking the configuration...")
if !bp.OnlyImage {
if err = verifyConfigContent(cfg); err != nil {
log.Errorf("Verify build config %s failed: %v", bp.File, err)
return
}
}
headers := []string{"IMAGE", "TAGS", "ARCHITECTURE", "CREATE_TIME", "SIZE"}
rows, err := checkRepoImages(cfg)
if err != nil {
log.Errorf("Check repo images for %s failed: %v", bp.File, err)
return
}
if err = exportTableToFile(bp.File, headers, rows); err != nil {
return
}
}
func exportTableToFile(filePath string, headers []string, rows [][]string) error {
fileName := strings.Split(filePath, "/")
fileName = strings.Split(fileName[len(fileName)-1], ".")
name := fileName[0]
const fileLen = 2
if len(fileName) > fileLen {
name = strings.Join(fileName[0:len(fileName)-1], ".")
}
const tabPadding = 2
w := &strings.Builder{}
tw := tabwriter.NewWriter(w, 0, 0, tabPadding, ' ', 0)
fmt.Fprintln(tw, strings.Join(headers, "\t"))
for _, row := range rows {
fmt.Fprintln(tw, strings.Join(row, "\t"))
}
err := tw.Flush()
if err != nil {
log.Errorf("Flush config check tablewriter failed: %v", err)
return err
}
const filePerm = 0644
outputFileName := name + "-config-check.txt"
err = os.WriteFile(outputFileName, []byte(w.String()), filePerm)
if err != nil {
log.Errorf("Export config check result to %s failed: %v", outputFileName, err)
return err
}
fmt.Println(fmt.Sprintf("Check the configuration is successful, check the file %s", outputFileName))
return nil
}