package image import ( "fmt" "os" "path" "path/filepath" "strconv" "strings" "github.com/containers/image/v5/types" "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" "github.com/warewulf/warewulf/internal/pkg/image" "github.com/warewulf/warewulf/internal/pkg/kernel" "github.com/warewulf/warewulf/internal/pkg/node" "github.com/warewulf/warewulf/internal/pkg/util" "github.com/warewulf/warewulf/internal/pkg/warewulfd" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) func ImageCopy(cbp *wwapiv1.ImageCopyParameter) (err error) { if cbp == nil { return fmt.Errorf("imageCopyParameter is nil") } if !image.DoesSourceExist(cbp.ImageSource) { return fmt.Errorf("image %s does not exist", cbp.ImageSource) } if !image.ValidName(cbp.ImageDestination) { return fmt.Errorf("image name contains illegal characters : %s", cbp.ImageDestination) } if image.DoesSourceExist(cbp.ImageDestination) { return fmt.Errorf("an other image with the name %s already exists", cbp.ImageDestination) } err = image.Duplicate(cbp.ImageSource, cbp.ImageDestination) if err != nil { return fmt.Errorf("could not duplicate image: %s", err.Error()) } if cbp.Build { err = image.Build(cbp.ImageDestination, true) if err != nil { return err } } return fmt.Errorf("image %s has been succesfully duplicated as %s", cbp.ImageSource, cbp.ImageDestination) } func ImageBuild(cbp *wwapiv1.ImageBuildParameter) (err error) { if cbp == nil { return fmt.Errorf("input parameter is nil") } var images []string if cbp.All { images, err = image.ListSources() } else { images = cbp.ImageNames } if len(images) == 0 { return } for _, c := range images { if !image.ValidSource(c) { return fmt.Errorf("image name does not exist: %s", c) } err = image.Build(c, cbp.Force) if err != nil { return fmt.Errorf("could not build image %s: %s", c, err) } } return } func ImageDelete(cdp *wwapiv1.ImageDeleteParameter) (err error) { if cdp == nil { return fmt.Errorf("input parameter is nil") } nodeDB, err := node.New() if err != nil { return fmt.Errorf("could not open nodeDB: %s", err) } nodes, err := nodeDB.FindAllNodes() if err != nil { return } ARG_LOOP: for i := 0; i < len(cdp.ImageNames); i++ { //_, arg := range args { imageName := cdp.ImageNames[i] for _, n := range nodes { if n.ImageName == imageName { wwlog.Error("image is configured for nodes, skipping: %s", imageName) continue ARG_LOOP } } if !image.ValidSource(imageName) { wwlog.Error("image name is not a valid source: %s", imageName) continue } err := image.DeleteSource(imageName) if err != nil { wwlog.Error("could not remove source: %s", imageName) } err = image.DeleteImage(imageName) if err != nil { wwlog.Error("could not remove image files %s", imageName) } fmt.Printf("Image has been deleted: %s\n", imageName) } return } func ImageImport(cip *wwapiv1.ImageImportParameter) (imageName string, err error) { if cip == nil { err = fmt.Errorf("input parameter is nil") return } if cip.Name == "" { name := path.Base(cip.Source) wwlog.Info("Setting image name: %s", name) cip.Name = name } if !image.ValidName(cip.Name) { err = fmt.Errorf("image name contains illegal characters: %s", cip.Name) return } imageName = cip.Name fullPath := image.SourceDir(cip.Name) // image already exists and should be removed first if util.IsDir(fullPath) && cip.Force { wwlog.Info("Overwriting existing image") err = os.RemoveAll(fullPath) if err != nil { return } } if util.IsDir(fullPath) { if !cip.Update { err = fmt.Errorf("image name exists, specify --force, --update, or choose a different name: %s", cip.Name) return } wwlog.Info("Updating existing image") } else if strings.HasPrefix(cip.Source, "docker://") || strings.HasPrefix(cip.Source, "docker-daemon://") || strings.HasPrefix(cip.Source, "file://") || util.IsFile(cip.Source) { var sCtx *types.SystemContext sCtx, err = getSystemContext(cip.OciNoHttps, cip.OciUsername, cip.OciPassword, cip.Platform) if err != nil { return } if util.IsFile(cip.Source) && !filepath.IsAbs(cip.Source) { cip.Source, err = filepath.Abs(cip.Source) if err != nil { err = fmt.Errorf("when resolving absolute path of %s, err: %v", cip.Source, err) return } } err = image.ImportDocker(cip.Source, cip.Name, sCtx) if err != nil { err = fmt.Errorf("could not import image: %s", err.Error()) _ = image.DeleteSource(cip.Name) return } } else if util.IsDir(cip.Source) { err = image.ImportDirectory(cip.Source, cip.Name) if err != nil { err = fmt.Errorf("could not import image: %s", err.Error()) _ = image.DeleteSource(cip.Name) return } } else { err = fmt.Errorf("invalid dir or uri: %s", cip.Source) return } if cip.SyncUser { err = image.SyncUids(cip.Name, true) if err != nil { err = fmt.Errorf("error in user sync, fix error and run 'syncuser' manually: %s", err) return } } if cip.Build { wwlog.Info("Building image: %s", cip.Name) err = image.Build(cip.Name, true) if err != nil { err = fmt.Errorf("could not build image %s: %s", cip.Name, err.Error()) return } } return } func ImageList() (imageInfo []*wwapiv1.ImageInfo, err error) { var sources []string sources, err = image.ListSources() if err != nil { wwlog.Error("%s", err) return } nodeDB, err := node.New() if err != nil { wwlog.Error("%s", err) return } nodes, err := nodeDB.FindAllNodes() if err != nil { wwlog.Error("%s", err) return } nodemap := make(map[string]int) for _, n := range nodes { nodemap[n.ImageName]++ } for _, source := range sources { if nodemap[source] == 0 { nodemap[source] = 0 } wwlog.Debug("Finding kernel version for: %s", source) kernel := kernel.FindKernels(source).Default() kernelVersion := "" if kernel != nil { kernelVersion = kernel.Version() } var creationTime uint64 sourceStat, err := os.Stat(image.SourceDir(source)) wwlog.Debug("Checking creation time for: %s,%v", image.SourceDir(source), sourceStat.ModTime()) if err != nil { wwlog.Error("%s", err) } else { creationTime = uint64(sourceStat.ModTime().Unix()) } var modTime uint64 imageStat, err := os.Stat(image.ImageFile(source)) if err == nil { modTime = uint64(imageStat.ModTime().Unix()) } imgSize := 0 if imgF, err := os.Stat(image.ImageFile(source)); err == nil { imgSize = int(imgF.Size()) } imgCSize := 0 if imgFC, err := os.Stat(image.ImageFile(source) + ".gz"); err == nil { imgCSize = int(imgFC.Size()) } imageInfo = append(imageInfo, &wwapiv1.ImageInfo{ Name: source, NodeCount: uint32(nodemap[source]), KernelVersion: kernelVersion, CreateDate: creationTime, ModDate: modTime, ImgSize: uint64(imgSize), ImgSizeComp: uint64(imgCSize), }) } return } func ImageShow(csp *wwapiv1.ImageShowParameter) (response *wwapiv1.ImageShowResponse, err error) { imageName := csp.ImageName if !image.ValidName(imageName) { err = fmt.Errorf("%s is not a valid image name", imageName) return } rootFsDir := image.RootFsDir(imageName) if !util.IsDir(rootFsDir) { err = fmt.Errorf("%s is not a valid image", imageName) return } kernel := kernel.FindKernels(imageName).Default() kernelVersion := "" if kernel != nil { kernelVersion = kernel.Version() } nodeDB, err := node.New() if err != nil { return } nodes, err := nodeDB.FindAllNodes() if err != nil { return } var nodeList []string for _, n := range nodes { if n.ImageName == imageName { nodeList = append(nodeList, n.Id()) } } response = &wwapiv1.ImageShowResponse{ Name: imageName, Rootfs: rootFsDir, Nodes: nodeList, KernelVersion: kernelVersion, } return } func ImageRename(crp *wwapiv1.ImageRenameParameter) (err error) { // rename the image source folder sourceDir := image.SourceDir(crp.ImageName) destDir := image.SourceDir(crp.TargetName) err = os.Rename(sourceDir, destDir) if err != nil { return err } err = image.DeleteImage(crp.ImageName) if err != nil { wwlog.Warn("Could not remove image files for %s: %s", crp.ImageName, err) } if crp.Build { err = image.Build(crp.TargetName, true) if err != nil { return err } } // update the nodes profiles image name nodeDB, err := node.New() if err != nil { return err } nodes, err := nodeDB.FindAllNodes() if err != nil { return err } for _, node := range nodes { if node.ImageName == crp.ImageName { node.ImageName = crp.TargetName } } profiles, err := nodeDB.FindAllProfiles() if err != nil { return err } for _, profile := range profiles { if profile.ImageName == crp.ImageName { profile.ImageName = crp.TargetName } } err = nodeDB.Persist() if err != nil { return err } err = warewulfd.DaemonStatus() if err != nil { // warewulfd is not running, skip return nil } // else reload daemon to apply new changes return warewulfd.DaemonReload() } // create the system context and reading out environment variables func getSystemContext(noHttps bool, username string, password string, platform string) (sCtx *types.SystemContext, err error) { sCtx = &types.SystemContext{} // only check env if noHttps wasn't set if !noHttps { val, ok := os.LookupEnv("WAREWULF_OCI_NOHTTPS") if ok { noHttps, err = strconv.ParseBool(val) if err != nil { return nil, fmt.Errorf("while parsing insecure http option: %v", err) } } // only set this if we want to disable, otherwise leave as undefined if noHttps { sCtx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(true) } sCtx.OCIInsecureSkipTLSVerify = noHttps } if username == "" { username, _ = os.LookupEnv("WAREWULF_OCI_USERNAME") } if password == "" { password, _ = os.LookupEnv("WAREWULF_OCI_PASSWORD") } if username != "" || password != "" { if username != "" && password != "" { sCtx.DockerAuthConfig = &types.DockerAuthConfig{ Username: username, Password: password, } } else { return nil, fmt.Errorf("oci username and password env vars must be specified together") } } if platform == "" { platform, _ = os.LookupEnv("WAREWULF_OCI_PLATFORM") } if platform != "" { sCtx.ArchitectureChoice = platform } return sCtx, nil }