package main import ( "errors" "os" "slices" "strings" image2 "wdd.io/agent-common/image" "wdd.io/agent-common/utils" "wdd.io/agent-deploy/d_app" "wdd.io/agent-operator/image" ) const OfflineDeployHarborHost = "harbor.wdd.io" const PublicDeployHarborHost = "42.192.52.227" const DirectPushDeployHarborHost = "36.134.71.138" type ImageSyncEntity struct { ProjectName string // 优先级3 ProjectFromDemoNameNotNull bool ProjectVersion string // 优先级2 CmiiNameTagList []string // 优先级1 appName:tag的形式 FullNameImageList []string // 优先级1 DownloadImage bool // 下载镜像 DownloadFromOss bool // 下载镜像 CompressImageToGzip bool // 压缩镜像 UploadToDemoMinio bool // 上传镜像 ShouldDirectPushToHarbor bool // 直接推送到对方的主机 || 离线部署机 使用此部门 DirectHarborHost string // 目标Harbor仓库的Host,全名称带端口 不带http前缀 } type ImageSyncResult struct { ErrorPullImageList []string ErrorGzipImageList []string ErrorPushImageNameList []string RealImageNameList []string RealGzipFileNameList []string AllCmiiImageNameList []string } // PullFromEntityAndSyncConditionally 根据ImageSyncEntity拉取特定的镜像,然后上传到特定的目标机器(或者上传的minio中) func (sync ImageSyncEntity) PullFromEntityAndSyncConditionally() (imageSyncResult ImageSyncResult) { var realCmiiImageList []string var allCmiiImageNameList []string var errorPullImageList []string var allGzipFileNameList []string var errorGzipImageList []string var errorPushImageNameList []string var gzipFolderFullPath string if (sync.CmiiNameTagList == nil && sync.FullNameImageList == nil) || (len(sync.CmiiNameTagList) == 0 && len(sync.FullNameImageList) == 0) { // 没有指定特定的镜像,那么根据 ProjectVersion 或者从DEMO拉取镜像 // pull images // compress if sync.ProjectVersion != "" { // 获取特定版本的镜像 errorPullImageList, errorGzipImageList, allCmiiImageNameList, allGzipFileNameList = DownloadCompressUploadFromVersion(sync.ProjectVersion, sync.CompressImageToGzip, sync.UploadToDemoMinio) gzipFolderFullPath = image.OfflineImageGzipFolderPrefix + sync.ProjectVersion } else { // 获取DEMO的镜像 errorPullImageList, errorGzipImageList, allCmiiImageNameList, allGzipFileNameList = DownloadCompressUploadFromDemo(sync.ProjectName, sync.CompressImageToGzip, sync.UploadToDemoMinio) gzipFolderFullPath = image.OfflineImageGzipFolderPrefix + sync.ProjectName } } else { // 拉取特定的镜像 gzipFolderFullPath = image.OfflineImageGzipFolderPrefix + "tmp" // 组装镜像名称 allCmiiImageNameList = concatAndUniformCmiiImage(sync.FullNameImageList, sync.CmiiNameTagList) // DCU errorPullImageList, errorGzipImageList, realCmiiImageList, allGzipFileNameList = DownloadCompressUpload(true, allCmiiImageNameList, sync.CompressImageToGzip, gzipFolderFullPath, sync.UploadToDemoMinio) } // 直接传输到目标Harbor仓库 if sync.ShouldDirectPushToHarbor { if sync.DirectHarborHost == "" { log.ErrorF("DirectHarborHost is null ! can't push to target harbor !") } // push to errorPushImageNameList = image.TagFromListAndPushToCHarbor(realCmiiImageList, sync.DirectHarborHost) } // build result imageSyncResult.AllCmiiImageNameList = allCmiiImageNameList imageSyncResult.RealImageNameList = realCmiiImageList imageSyncResult.ErrorPullImageList = errorPullImageList imageSyncResult.RealGzipFileNameList = allGzipFileNameList imageSyncResult.ErrorGzipImageList = errorGzipImageList imageSyncResult.ErrorPushImageNameList = errorPushImageNameList return imageSyncResult } func concatAndUniformCmiiImage(fullImageList []string, cmiiImageList []string) []string { if cmiiImageList != nil || len(cmiiImageList) > 0 { // cmiiImageList has content if fullImageList == nil { fullImageList = []string{} } for _, cmiiImage := range cmiiImageList { fullImageList = append(fullImageList, image2.CmiiHarborPrefix+cmiiImage) } } return fullImageList } // DownloadCompressUpload DCU 镜像同步的前半部分,通常在35.71 LapPro执行,无需Bastion Mode func DownloadCompressUpload(downloadImage bool, fullNameList []string, shouldGzip bool, gzipFolderFullPath string, shouldOss bool) (errorPullImageList, errorGzipImageList, realCmiiImageName, allGzipFileFullNameList []string) { // write to file localGzipFileListTxt := gzipFolderFullPath + "all-gzip-image-file-name.txt" // Download log.Info("DOWNLOAD START !") if downloadImage { if fullNameList == nil || len(fullNameList) == 0 { log.InfoF("no image name list !") } else { errorPullImageList = image.PullFromFullNameList(fullNameList) } } // remove failed fullNameList = slices.DeleteFunc(fullNameList, func(imageName string) bool { return slices.Contains(errorPullImageList, imageName) }) // Compress if shouldGzip { // remove file _ = os.Remove(localGzipFileListTxt) gzipFileAlready := make(map[string]bool) if utils.FileOrFolderExists(gzipFolderFullPath) { dir, _ := os.ReadDir(gzipFolderFullPath) for _, entry := range dir { if entry.IsDir() { continue } gzipFileAlready[strings.TrimPrefix(entry.Name(), gzipFolderFullPath)] = true } } // mkdir folder err := os.MkdirAll(gzipFolderFullPath, os.ModeDir) if err != nil { if !errors.Is(err, os.ErrExist) { log.ErrorF("create folder error of %s", gzipFolderFullPath) panic(err) } } // 循环遍历压缩 log.Info("COMPRESS START") for _, imageFullName := range fullNameList { // gzip image file already exists gzipFileName := image2.ImageFullNameToGzipFileName(imageFullName) gzipImageFileFullPath := gzipFolderFullPath + gzipFileName _, ok := gzipFileAlready[gzipFileName] if len(gzipFileAlready) > 0 && ok { log.DebugF("gzip file %s already exists !", gzipFileName) } else { ok, gzipImageFileFullPath = image.SaveToGzipFile(imageFullName, gzipFolderFullPath) if !ok { errorGzipImageList = append(errorGzipImageList, imageFullName) continue } } // 压缩成功 allGzipFileFullNameList = append(allGzipFileFullNameList, gzipImageFileFullPath) } // remove failed fullNameList = slices.DeleteFunc(fullNameList, func(imageName string) bool { return slices.Contains(errorGzipImageList, imageName) }) for _, gzipFileFullName := range allGzipFileFullNameList { utils.AppendContentToFile( strings.TrimPrefix(gzipFileFullName, gzipFolderFullPath)+"\n", localGzipFileListTxt, ) } log.InfoF("all gzip file name list is %s", localGzipFileListTxt) } // Upload if shouldOss { //uploadGzipFileToDemoMinio() // get gzip file name list log.Info("UPLOAD OSS START !") // start to upload // extract demo oss location suffix from gzipFolderFullPath trimPrefix := strings.TrimPrefix(gzipFolderFullPath, image.OfflineImageGzipFolderPrefix) bucketNameWithPrefix := "cmlc-installation/" + trimPrefix log.InfoF("gzip file location in demo oss is %s", DefaultDemoEndpoint+"/"+bucketNameWithPrefix) // upload gzip file list txt to demo if !DefaultCmiiMinioOperator.UploadToDemo(bucketNameWithPrefix, gzipFolderFullPath, strings.TrimPrefix(localGzipFileListTxt, gzipFolderFullPath)) { log.ErrorF("upload of %s to demo oss error !", localGzipFileListTxt) } log.InfoF("upload all gzip file to demo oss !") for _, gzipFileFullName := range allGzipFileFullNameList { // SaveToGzipFile 返回的是全路径 归一化处理 gzip file name gzipFileFullName = strings.TrimPrefix(gzipFileFullName, gzipFolderFullPath) if !DefaultCmiiMinioOperator.UploadToDemo(bucketNameWithPrefix, gzipFolderFullPath, gzipFileFullName) { log.ErrorF("upload of %s to demo oss error !", gzipFileFullName) } } } return errorPullImageList, errorGzipImageList, fullNameList, allGzipFileFullNameList } // DownloadLoadTagUpload DLTU procedure ImageSync的另外一般流程,需要支持 堡垒机(纯离线)的模式 // 2. Gzip文件目录,RKE MIDDLE CMII三个文件目录 - 约定目录 // 约定目录 /root/wdd/image/rke/ /root/wdd/image/middle/ /root/wdd/image/cmii/ // 3. 读取本机的IP地址 - 参数传递 // 4. OSS地址 - ossUrlPrefix传空 则使用默认值 // 5. ossFileName - 如果结尾为txt,则为文件的形式,如果为tar.gz,则为gzip文件夹的形式 func DownloadLoadTagUpload(downloadFromOss bool, ossUrlPrefix, ossFileNameOrGzipFileListTxt, localGzipFolderOrGzipFile string, targetHarborFullName string) (targetImageFullNameList []string) { // 支持单文件的形式 if !utils.IsDirOrFile(localGzipFolderOrGzipFile) { // 单个压缩文件 肯定是离线的形式 if !strings.HasSuffix(localGzipFolderOrGzipFile, ".tar.gz") { log.ErrorF("local gzip file %s is not a .tar.gz file !", localGzipFolderOrGzipFile) return nil } // load image.LoadFromGzipFilePath(localGzipFolderOrGzipFile) } else { separator := os.PathSeparator if !strings.HasSuffix(localGzipFolderOrGzipFile, string(separator)) { localGzipFolderOrGzipFile += string(separator) } // download if downloadFromOss { if !parseAndDownloadFromOss(ossUrlPrefix, ossFileNameOrGzipFileListTxt, localGzipFolderOrGzipFile) { log.ErrorF("download from oss error !") return nil } } // load loadAllGzipImageFromLocalFolder(localGzipFolderOrGzipFile) } // tag // push allFileInFolder, err := utils.ListAllFileInFolder(localGzipFolderOrGzipFile) if err != nil { return nil } for _, gzipFileName := range allFileInFolder { // 过滤非.tar.gz结尾的文件 if !strings.HasSuffix(gzipFileName, ".tar.gz") { continue } log.DebugF("gzip file name is %s", gzipFileName) // gzip to image full name 拿到镜像的原始名称 imageFullName := image2.GzipFileNameToImageFullName(gzipFileName) if imageFullName == "" { log.ErrorF("gzip file %s to image full name error !", gzipFileName) continue } // tag 拿到目标名称 然后重新Tag targetImageFullName := image2.ImageNameToTargetImageFullName(imageFullName, targetHarborFullName) image.TagFromSourceToTarget(imageFullName, targetImageFullName) // uploadToHarbor 上传到目标Harbor if image.UploadToHarbor(targetImageFullName) { targetImageFullNameList = append(targetImageFullNameList, targetImageFullName) } else { log.ErrorF("upload to harbor error of %s", targetImageFullName) } } return targetImageFullNameList } func loadAllGzipImageFromLocalFolder(localGzipFolder string) { image.LoadFromFolderPath(localGzipFolder) } func parseAndDownloadFromOss(ossUrlPrefix, ossFileName, localGzipFolder string) bool { if ossUrlPrefix == "" { ossUrlPrefix = DefaultOssUrlPrefix } if !strings.HasSuffix(ossUrlPrefix, "/") { ossUrlPrefix += "/" } log.InfoF("prepare to download from %s%s", ossUrlPrefix, ossFileName) if !DefaultCmiiMinioOperator.DemoMinioOperator.DownloadFileFromOssFullUrl(ossUrlPrefix+ossFileName, localGzipFolder) { log.ErrorF("download %s from oss error !", ossUrlPrefix+ossFileName) return false } if strings.HasSuffix(ossFileName, ".txt") { // download from gzip file list txt // download all files in the txt file result := utils.ReadAllContentFromFile(localGzipFolder + ossFileName) for _, gzipFileName := range result { DefaultCmiiMinioOperator.DemoMinioOperator.DownloadFileFromOssFullUrl(ossUrlPrefix+gzipFileName, localGzipFolder) } } // 解析 return true } // DownloadCompressUploadFromDemo 获取DEMO环境的全部镜像 func DownloadCompressUploadFromDemo(projectName string, shouldGzip, shouldOss bool) (errorPullImageList, errorGzipImageList, realCmiiImageName, allGzipFileNameList []string) { // generate a project folder err := os.MkdirAll(image.OfflineImageGzipFolderPrefix+projectName, os.ModeDir) if err != nil { if !errors.Is(err, os.ErrExist) { log.ErrorF("[Download_Compress_Upload_From_Demo] - create folder of %s error %s", image.OfflineImageGzipFolderPrefix+projectName, err.Error()) return errorPullImageList, errorGzipImageList, realCmiiImageName, allGzipFileNameList } } // get demo image version map allCmiiImageNameListFromDemo := buildAllCmiiImageNameListFromDemo(projectName) // do work // DCU return DownloadCompressUpload(true, allCmiiImageNameListFromDemo, shouldGzip, image.OfflineImageGzipFolderPrefix+projectName, shouldOss) } func buildAllCmiiImageNameListFromDemo(projectName string) []string { var realCmiiImageName []string backendMap, frontendMap, srsMap := BackupAllCmiiDeploymentToMap(demo) // save map to file backendMapFile := image.OfflineImageGzipFolderPrefix + projectName + "-backend-app.json" frontendMapFile := image.OfflineImageGzipFolderPrefix + projectName + "-frontend-app.json" srsMapFile := image.OfflineImageGzipFolderPrefix + projectName + "-srs-app.json" _ = os.Remove(backendMapFile) _ = os.Remove(frontendMapFile) _ = os.Remove(srsMapFile) //utils.AppendContentToFile( // utils.BeautifulPrintToString(backendMap), // backendMapFile, //) //utils.AppendContentToFile( // utils.BeautifulPrintToString(frontendMap), // frontendMapFile, //) //utils.AppendContentToFile( // utils.BeautifulPrintToString(srsMapFile), // srsMapFile, //) realCmiiImageName = append(realCmiiImageName, image.CmiiImageMapToFullNameList(backendMap)...) realCmiiImageName = append(realCmiiImageName, image.CmiiImageMapToFullNameList(frontendMap)...) realCmiiImageName = append(realCmiiImageName, image.CmiiImageMapToFullNameList(srsMap)...) utils.BeautifulPrintListWithTitle(realCmiiImageName, "Cmii Project Image => "+projectName) return realCmiiImageName } // DownloadCompressUploadFromVersion 根据版本下载全部的CMII镜像 func DownloadCompressUploadFromVersion(cmiiVersion string, shouldGzip bool, shouldOss bool) (errorPullImageList, errorGzipImageList, realCmiiImageName, allGzipFileNameList []string) { // generate a project folder err := os.MkdirAll(image.OfflineImageGzipFolderPrefix+cmiiVersion, os.ModeDir) if err != nil { if !errors.Is(err, os.ErrExist) { log.ErrorF("[Download_Compress_Upload_From_Demo] - create folder of %s error %s", image.OfflineImageGzipFolderPrefix+cmiiVersion, err.Error()) return errorPullImageList, errorGzipImageList, realCmiiImageName, allGzipFileNameList } } // build all cmii image name list realCmiiImageName = buildAllCmiiImageNameListFromVersion(cmiiVersion) // do work // DCU procedure return DownloadCompressUpload(true, realCmiiImageName, shouldGzip, image.OfflineImageGzipFolderPrefix+cmiiVersion, shouldOss) } func buildAllCmiiImageNameListFromVersion(cmiiVersion string) []string { var realCmiiImageName []string backendMap := d_app.CmiiBackendAppMap frontendMap := d_app.CmiiFrontendAppMap for app := range backendMap { backendMap[app] = cmiiVersion } for app := range frontendMap { frontendMap[app] = cmiiVersion } realCmiiImageName = append(realCmiiImageName, image.CmiiImageMapToFullNameList(backendMap)...) realCmiiImageName = append(realCmiiImageName, image.CmiiImageMapToFullNameList(frontendMap)...) for key, value := range d_app.CmiiSrsAppMap { var app *CmiiDeploymentInterface if strings.Contains(value, "deployment") { app = DefaultCmiiOperator.DeploymentOneInterface(demo, key) if app != nil { realCmiiImageName = append(realCmiiImageName, app.Image) } } else if strings.Contains(value, "state") { app = DefaultCmiiOperator.StatefulSetOneInterface(demo, key) if app != nil { for _, imageName := range app.ContainerImageMap { realCmiiImageName = append(realCmiiImageName, imageName) } } } } utils.BeautifulPrintListWithTitle(realCmiiImageName, "Cmii Version Image => "+cmiiVersion) return realCmiiImageName } func DownloadCompressUploadDependency(shouldGzip bool, shouldOss bool, shouldDownload bool, isRKE bool) (errorPullImageList, errorGzipImageList, realCmiiImageName, allGzipFileNameList []string) { log.Info("DCU for middle and rke!") err := os.MkdirAll(image.OfflineImageGzipFolderPrefix, os.ModeDir) if err != nil { if !errors.Is(err, os.ErrExist) { log.ErrorF("[FetchDependencyRepos] - create folder of %s error %s", image.OfflineImageGzipFolderPrefix, err.Error()) } } var fulleImageNameList []string var gzipFolderPrefix string if isRKE { log.Info("DCU for rke!") fulleImageNameList = d_app.Rancher1204Amd64 gzipFolderPrefix = image.OfflineImageGzipFolderPrefix + "rke/" } else { log.Info("DCU for middle!") fulleImageNameList = d_app.MiddlewareAmd64 gzipFolderPrefix = image.OfflineImageGzipFolderPrefix + "middle/" } return DownloadCompressUpload(shouldDownload, fulleImageNameList, shouldGzip, gzipFolderPrefix, shouldOss) } func LoadSplitCmiiGzipImageToTargetHarbor(projectName, targetHarborHost string) (errorLoadImageNameList, errorPushImageNameList []string) { // list folder projectGzipFolder := image.OfflineImageGzipFolderPrefix + projectName errorLoadImageNameList = append(errorLoadImageNameList, image.LoadFromFolderPath(projectGzipFolder)...) // read from json errorPushImageNameList = append(errorPushImageNameList, image.TagFromListAndPushToCHarbor(d_app.Cmii520DemoImageList, targetHarborHost)...) // re-tag // push // todo clean host and harbor // check harbor exits return errorLoadImageNameList, errorPushImageNameList } func LoadSplitDepGzipImageToTargetHarbor(targetHarborHost string) (errorLoadImageNameList []string, errorPushImageNameList []string) { //middle := image.OfflineImageGzipFolderPrefix + "middle/" //rke := image.OfflineImageGzipFolderPrefix + "rke/" //errorLoadImageNameList = append(errorLoadImageNameList, ImageLoadFromFolderPath(middle)...) //errorLoadImageNameList = append(errorLoadImageNameList, ImageLoadFromFolderPath(rke)...) errorPushImageNameList = append(errorPushImageNameList, image.TagFromListAndPushToCHarbor(d_app.MiddlewareAmd64, targetHarborHost)...) errorPushImageNameList = append(errorPushImageNameList, image.TagFromListAndPushToCHarbor(d_app.Rancher1204Amd64, targetHarborHost)...) return errorLoadImageNameList, errorPushImageNameList }