diff --git a/internal/app/wwctl/image/build/main.go b/internal/app/wwctl/image/build/main.go index 4e5df393..a574f2b4 100644 --- a/internal/app/wwctl/image/build/main.go +++ b/internal/app/wwctl/image/build/main.go @@ -4,24 +4,35 @@ import ( "fmt" "github.com/spf13/cobra" - apiimage "github.com/warewulf/warewulf/internal/pkg/api/image" - "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" "github.com/warewulf/warewulf/internal/pkg/image" ) -func CobraRunE(cmd *cobra.Command, args []string) error { +func CobraRunE(cmd *cobra.Command, imageNames []string) error { + if BuildAll { + var err error + imageNames, err = image.ListSources() + if err != nil { + return fmt.Errorf("could not list images: %w", err) + } + } + + if len(imageNames) == 0 { + return fmt.Errorf("no images specified; use --all to build all images") + } + if SyncUser { - for _, name := range args { + for _, name := range imageNames { if err := image.Syncuser(name, true); err != nil { return fmt.Errorf("syncuser error: %w", err) } } } - cbp := &wwapiv1.ImageBuildParameter{ - ImageNames: args, - Force: BuildForce, - All: BuildAll, + for _, imageName := range imageNames { + if err := image.Build(imageName, BuildForce); err != nil { + return fmt.Errorf("error building image %s: %s", imageName, err) + } } - return apiimage.ImageBuild(cbp) + + return nil } diff --git a/internal/app/wwctl/image/delete/main.go b/internal/app/wwctl/image/delete/main.go index f7f266ac..156c0fce 100644 --- a/internal/app/wwctl/image/delete/main.go +++ b/internal/app/wwctl/image/delete/main.go @@ -3,24 +3,26 @@ package delete import ( "fmt" - "github.com/warewulf/warewulf/internal/pkg/api/image" - "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" + "github.com/warewulf/warewulf/internal/pkg/image" "github.com/warewulf/warewulf/internal/pkg/util" "github.com/spf13/cobra" ) -func CobraRunE(cmd *cobra.Command, args []string) (err error) { - cdp := &wwapiv1.ImageDeleteParameter{ - ImageNames: args, - } - +func CobraRunE(cmd *cobra.Command, imageNames []string) (err error) { if !SetYes { - yes := util.Confirm(fmt.Sprintf("Are you sure you want to delete image %s", args)) + yes := util.Confirm(fmt.Sprintf("Are you sure you want to delete image %s", imageNames)) if !yes { return } } - return image.ImageDelete(cdp) + + for _, imageName := range imageNames { + if err := image.Delete(imageName); err != nil { + return fmt.Errorf("error deleting image %s: %s", imageName, err) + } + } + + return nil } diff --git a/internal/app/wwctl/image/imprt/main.go b/internal/app/wwctl/image/imprt/main.go index 13b1bf1c..e3e5cdb6 100644 --- a/internal/app/wwctl/image/imprt/main.go +++ b/internal/app/wwctl/image/imprt/main.go @@ -1,32 +1,90 @@ package imprt import ( + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "github.com/containers/image/v5/types" "github.com/spf13/cobra" - apiimage "github.com/warewulf/warewulf/internal/pkg/api/image" - "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" + "github.com/warewulf/warewulf/internal/pkg/image" + "github.com/warewulf/warewulf/internal/pkg/util" + "github.com/warewulf/warewulf/internal/pkg/wwlog" ) -func CobraRunE(cmd *cobra.Command, args []string) (err error) { +func CobraRunE(cmd *cobra.Command, args []string) error { + source := args[0] // Shim in a name if none given. name := "" if len(args) == 2 { name = args[1] } - - cip := &wwapiv1.ImageImportParameter{ - Source: args[0], - Name: name, - Force: SetForce, - Update: SetUpdate, - Build: SetBuild, - SyncUser: SyncUser, - OciNoHttps: OciNoHttps, - OciUsername: OciUsername, - OciPassword: OciPassword, - Platform: Platform, + if name == "" { + name = path.Base(source) + wwlog.Info("Setting image name: %s", name) + } + if !image.ValidName(name) { + return fmt.Errorf("image name contains illegal characters: %s", name) } - _, err = apiimage.ImageImport(cip) - return + fullPath := image.SourceDir(name) + + // image already exists and should be removed first + if util.IsDir(fullPath) { + if SetUpdate { + wwlog.Info("Updating existing image") + } else if SetForce { + wwlog.Info("Overwriting existing image") + if err := os.RemoveAll(fullPath); err != nil { + return err + } + } else { + return fmt.Errorf("image name exists, specify --force, --update, or choose a different name: %s", name) + } + } + + if strings.HasPrefix(source, "docker://") || strings.HasPrefix(source, "docker-daemon://") || + strings.HasPrefix(source, "file://") || util.IsFile(source) { + var sCtx *types.SystemContext + sCtx, err := image.GetSystemContext(OciNoHttps, OciUsername, OciPassword, Platform) + if err != nil { + return err + } + + if util.IsFile(source) && !filepath.IsAbs(source) { + source, err = filepath.Abs(source) + if err != nil { + return fmt.Errorf("when resolving absolute path of %s, err: %v", source, err) + } + } + err = image.ImportDocker(source, name, sCtx) + if err != nil { + _ = image.DeleteSource(name) + return fmt.Errorf("could not import image: %s", err.Error()) + } + } else if util.IsDir(source) { + if err := image.ImportDirectory(source, name); err != nil { + _ = image.DeleteSource(name) + return fmt.Errorf("could not import image: %s", err.Error()) + } + } else { + return fmt.Errorf("invalid dir or uri: %s", source) + } + + if SyncUser { + if err := image.Syncuser(name, true); err != nil { + return fmt.Errorf("syncuser error: %w", err) + } + } + + if SetBuild { + wwlog.Info("Building image: %s", name) + if err := image.Build(name, true); err != nil { + return fmt.Errorf("could not build image %s: %s", name, err.Error()) + } + } + return nil } diff --git a/internal/app/wwctl/image/list/main.go b/internal/app/wwctl/image/list/main.go index 766defcf..6e96f133 100644 --- a/internal/app/wwctl/image/list/main.go +++ b/internal/app/wwctl/image/list/main.go @@ -1,54 +1,77 @@ package list import ( + "os" "strconv" "time" "github.com/spf13/cobra" "github.com/warewulf/warewulf/internal/app/wwctl/table" - apiimage "github.com/warewulf/warewulf/internal/pkg/api/image" "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/wwlog" ) -var imageList = apiimage.ImageList - func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err error) { return func(cmd *cobra.Command, args []string) (err error) { t := table.New(cmd.OutOrStdout()) showSize := vars.size || vars.chroot || vars.compressed if showSize || vars.full || vars.kernel { - imageInfo, err := imageList() + sources, err := image.ListSources() if err != nil { return err } + nodeDB, err := node.New() + if err != nil { + return err + } + nodes, err := nodeDB.FindAllNodes() + if err != nil { + return err + } + nodemap := make(map[string]int) + for _, n := range nodes { + nodemap[n.ImageName]++ + } + if vars.full { t.AddHeader("IMAGE NAME", "NODES", "KERNEL VERSION", "CREATION TIME", "MODIFICATION TIME", "SIZE") - for i := 0; i < len(imageInfo); i++ { - if len(args) > 0 && !util.InSlice(args, imageInfo[i].Name) { + for _, name := range sources { + if len(args) > 0 && !util.InSlice(args, name) { continue } - createTime := time.Unix(int64(imageInfo[i].CreateDate), 0) - modTime := time.Unix(int64(imageInfo[i].ModDate), 0) - sz := util.ByteToString(int64(imageInfo[i].ImgSize)) + kernelVersion := "" + if k := kernel.FindKernels(name).Default(); k != nil { + kernelVersion = k.Version() + } + createTime := time.Unix(0, 0) + if sourceStat, err := os.Stat(image.SourceDir(name)); err == nil { + createTime = sourceStat.ModTime() + } + modTime := time.Unix(0, 0) + if imageStat, err := os.Stat(image.ImageFile(name)); err == nil { + modTime = imageStat.ModTime() + } + sz := util.ByteToString(int64(image.ImageSize(name))) if vars.compressed { - sz = util.ByteToString(int64(imageInfo[i].ImgSizeComp)) + sz = util.ByteToString(int64(image.CompressedImageSize(name))) } if vars.chroot { - size, err := util.DirSize(image.SourceDir(imageInfo[i].Name)) + size, err := util.DirSize(image.SourceDir(name)) if err != nil { wwlog.Error("%s", err) - size = int64(0) + size = 0 } - sz = util.ByteToString(int64(size)) + sz = util.ByteToString(size) } t.AddLine( - imageInfo[i].Name, - strconv.FormatUint(uint64(imageInfo[i].NodeCount), 10), - imageInfo[i].KernelVersion, + name, + strconv.Itoa(nodemap[name]), + kernelVersion, createTime.Format(time.RFC822), modTime.Format(time.RFC822), sz, @@ -56,38 +79,41 @@ func CobraRunE(vars *variables) func(cmd *cobra.Command, args []string) (err err } } else if vars.kernel { t.AddHeader("IMAGE NAME", "NODES", "KERNEL VERSION") - for i := 0; i < len(imageInfo); i++ { - if len(args) > 0 && !util.InSlice(args, imageInfo[i].Name) { + for _, name := range sources { + if len(args) > 0 && !util.InSlice(args, name) { continue } + kernelVersion := "" + if k := kernel.FindKernels(name).Default(); k != nil { + kernelVersion = k.Version() + } t.AddLine( - imageInfo[i].Name, - strconv.FormatUint(uint64(imageInfo[i].NodeCount), 10), - imageInfo[i].KernelVersion, + name, + strconv.Itoa(nodemap[name]), + kernelVersion, ) } } else if showSize { t.AddHeader("IMAGE NAME", "NODES", "SIZE") - for i := 0; i < len(imageInfo); i++ { - if len(args) > 0 && !util.InSlice(args, imageInfo[i].Name) { + for _, name := range sources { + if len(args) > 0 && !util.InSlice(args, name) { continue } - sz := util.ByteToString(int64(imageInfo[i].ImgSize)) + sz := util.ByteToString(int64(image.ImageSize(name))) if vars.compressed { - sz = util.ByteToString(int64(imageInfo[i].ImgSizeComp)) + sz = util.ByteToString(int64(image.CompressedImageSize(name))) } if vars.chroot { - size, err := util.DirSize(image.SourceDir(imageInfo[i].Name)) + size, err := util.DirSize(image.SourceDir(name)) if err != nil { wwlog.Error("%s", err) - size = int64(0) + size = 0 } sz = util.ByteToString(size) } - t.AddLine( - imageInfo[i].Name, - strconv.FormatUint(uint64(imageInfo[i].NodeCount), 10), + name, + strconv.Itoa(nodemap[name]), sz, ) } diff --git a/internal/app/wwctl/image/list/main_test.go b/internal/app/wwctl/image/list/main_test.go index 91ea6275..42a4a681 100644 --- a/internal/app/wwctl/image/list/main_test.go +++ b/internal/app/wwctl/image/list/main_test.go @@ -4,63 +4,47 @@ import ( "bytes" "strings" "testing" - "time" "github.com/stretchr/testify/assert" - "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" "github.com/warewulf/warewulf/internal/pkg/testenv" - "github.com/warewulf/warewulf/internal/pkg/warewulfd" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) func Test_List(t *testing.T) { tests := []struct { - name string - args []string - stdout string - inDb string - mockFunc func() + name string + args []string + stdout string + inDb string }{ { name: "image list test", - args: []string{"-l"}, + args: []string{"-s"}, stdout: ` -IMAGE NAME NODES KERNEL VERSION CREATION TIME MODIFICATION TIME SIZE ----------- ----- -------------- ------------- ----------------- ---- -test 1 kernel 01 Jan 70 00:00 UTC 01 Jan 70 00:00 UTC 0 B +IMAGE NAME NODES SIZE +---------- ----- ---- +test 1 0 B `, inDb: ` nodeprofiles: default: {} nodes: n01: + image name: test profiles: - default `, - mockFunc: func() { - imageList = func() (imageInfo []*wwapiv1.ImageInfo, err error) { - imageInfo = append(imageInfo, &wwapiv1.ImageInfo{ - Name: "test", - NodeCount: 1, - KernelVersion: "kernel", - CreateDate: uint64(time.Unix(0, 0).Unix()), - ModDate: uint64(time.Unix(0, 0).Unix()), - }) - return - } - }, }, } - warewulfd.SetNoDaemon() for _, tt := range tests { env := testenv.New(t) defer env.RemoveAll() env.WriteFile("etc/warewulf/nodes.conf", tt.inDb) + env.MkdirAll("var/lib/warewulf/chroots/test/rootfs") t.Logf("Running test: %s\n", tt.name) t.Run(tt.name, func(t *testing.T) { - tt.mockFunc() buf := new(bytes.Buffer) baseCmd := GetCommand() baseCmd.SetArgs(tt.args) diff --git a/internal/app/wwctl/image/rename/main.go b/internal/app/wwctl/image/rename/main.go index e0198fc6..eff055b4 100644 --- a/internal/app/wwctl/image/rename/main.go +++ b/internal/app/wwctl/image/rename/main.go @@ -4,37 +4,34 @@ import ( "fmt" "github.com/spf13/cobra" - api "github.com/warewulf/warewulf/internal/pkg/api/image" - "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" "github.com/warewulf/warewulf/internal/pkg/image" + "github.com/warewulf/warewulf/internal/pkg/warewulfd" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) func CobraRunE(cmd *cobra.Command, args []string) (err error) { - crp := &wwapiv1.ImageRenameParameter{ - ImageName: args[0], - TargetName: args[1], - Build: SetBuild, + if !image.DoesSourceExist(args[0]) { + return fmt.Errorf("%s source dir does not exist", args[0]) } - if !image.DoesSourceExist(crp.ImageName) { - return fmt.Errorf("%s source dir does not exist", crp.ImageName) + if image.DoesSourceExist(args[1]) { + return fmt.Errorf("an other image with the name %s already exists", args[1]) } - if image.DoesSourceExist(crp.TargetName) { - return fmt.Errorf("an other image with the name %s already exists", crp.TargetName) + if !image.ValidName(args[1]) { + return fmt.Errorf("image name contains illegal characters: %s", args[1]) } - if !image.ValidName(crp.TargetName) { - return fmt.Errorf("image name contains illegal characters : %s", crp.TargetName) - } - - err = api.ImageRename(crp) + err = image.Rename(args[0], args[1], SetBuild) if err != nil { err = fmt.Errorf("could not rename image: %s", err.Error()) return } - wwlog.Info("Image %s successfully renamed as %s", crp.ImageName, crp.TargetName) - return + wwlog.Info("Image %s successfully renamed as %s", args[0], args[1]) + + if err = warewulfd.DaemonStatus(); err != nil { + return nil + } + return warewulfd.DaemonReload() } diff --git a/internal/app/wwctl/image/show/main.go b/internal/app/wwctl/image/show/main.go index 04bc4e4f..ee8cd24d 100644 --- a/internal/app/wwctl/image/show/main.go +++ b/internal/app/wwctl/image/show/main.go @@ -3,35 +3,61 @@ package show import ( "fmt" - "github.com/warewulf/warewulf/internal/pkg/api/image" - "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/spf13/cobra" ) -func CobraRunE(cmd *cobra.Command, args []string) (err error) { - csp := &wwapiv1.ImageShowParameter{ - ImageName: args[0], +func CobraRunE(cmd *cobra.Command, args []string) error { + + imageName := args[0] + + if !image.ValidName(imageName) { + return fmt.Errorf("%s is not a valid image name", imageName) } - var r *wwapiv1.ImageShowResponse - r, err = image.ImageShow(csp) + rootFsDir := image.RootFsDir(imageName) + if !util.IsDir(rootFsDir) { + return fmt.Errorf("%s is not a valid image", imageName) + } + kernel := kernel.FindKernels(imageName).Default() + kernelVersion := "" + if kernel != nil { + kernelVersion = kernel.Version() + } + + nodeDB, err := node.New() if err != nil { - return + return err + } + + nodes, err := nodeDB.FindAllNodes() + if err != nil { + return err + } + + var nodeList []string + for _, n := range nodes { + if n.ImageName == imageName { + nodeList = append(nodeList, n.Id()) + } } if !ShowAll { - fmt.Printf("%s\n", r.Rootfs) + fmt.Printf("%s\n", rootFsDir) } else { - kernelVersion := r.KernelVersion if kernelVersion == "" { kernelVersion = "not found" } - fmt.Printf("Name: %s\n", r.Name) + fmt.Printf("Name: %s\n", imageName) fmt.Printf("KernelVersion: %s\n", kernelVersion) - fmt.Printf("Rootfs: %s\n", r.Rootfs) - fmt.Printf("Nr nodes: %d\n", len(r.Nodes)) - fmt.Printf("Nodes: %s\n", r.Nodes) + fmt.Printf("Rootfs: %s\n", rootFsDir) + fmt.Printf("Nr nodes: %d\n", len(nodeList)) + fmt.Printf("Nodes: %v\n", nodeList) } - return + + return nil } diff --git a/internal/app/wwctl/image/syncuser/main.go b/internal/app/wwctl/image/syncuser/main.go index 7ac3c0d3..e76f1574 100644 --- a/internal/app/wwctl/image/syncuser/main.go +++ b/internal/app/wwctl/image/syncuser/main.go @@ -4,8 +4,6 @@ import ( "fmt" "github.com/spf13/cobra" - image_build "github.com/warewulf/warewulf/internal/pkg/api/image" - "github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1" "github.com/warewulf/warewulf/internal/pkg/image" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) @@ -25,13 +23,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error { wwlog.Info("Syncuser is completed. Rebuild image or add `--build` flag for automatic rebuild after syncuser.") } else if write && build { // if write = true and build = true, then it'll trigger the image build after sync - cbp := &wwapiv1.ImageBuildParameter{ - ImageNames: []string{imageName}, - Force: true, - All: false, - } - err := image_build.ImageBuild(cbp) - if err != nil { + if err := image.Build(imageName, true); err != nil { return fmt.Errorf("error during image build: %s", err) } } diff --git a/internal/pkg/api/image/image.go b/internal/pkg/api/image/image.go deleted file mode 100644 index 9f3c35b3..00000000 --- a/internal/pkg/api/image/image.go +++ /dev/null @@ -1,396 +0,0 @@ -package image - -import ( - "fmt" - "os" - "path" - "path/filepath" - "slices" - "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 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) - } - - // validate image names - for _, imageName := range cdp.ImageNames { - if !image.ValidSource(imageName) { - return fmt.Errorf("image name is not valid source: %s", imageName) - } - } - - // check if the deleted images are not used by nodes - for nodeName, node := range nodeDB.Nodes { - if slices.Contains(cdp.ImageNames, node.ImageName) { - return fmt.Errorf("image %s is in use by node %s, cannot delete", node.ImageName, nodeName) - } - } - - // check if the deleted images are not used by profiles - for profileName, profile := range nodeDB.NodeProfiles { - if slices.Contains(cdp.ImageNames, profile.ImageName) { - return fmt.Errorf("image %s is in use by profile %s, cannot delete", profile.ImageName, profileName) - } - } - - // delete images - for _, imageName := range cdp.ImageNames { - err := image.DeleteSource(imageName) - if err != nil { - return fmt.Errorf("could not remove source image %s: %w", imageName, err) - } - err = image.DeleteImage(imageName) - if err != nil { - return fmt.Errorf("could not remove image file %s: %w", imageName, err) - } - wwlog.Info("Image %q has been deleted", 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") - } - 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.Syncuser(cip.Name, true) - if err != nil { - err = fmt.Errorf("syncuser error: %w", 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 := image.ImageSize(source) - imgCSize := image.CompressedImageSize(source) - 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 - } - - for nodeId, node := range nodeDB.Nodes { - if node.ImageName == crp.ImageName { - wwlog.Debug("updating node %s image to %s", nodeId, crp.TargetName) - nodeDB.Nodes[nodeId].ImageName = crp.TargetName - } - } - - for profileId, profile := range nodeDB.NodeProfiles { - if profile.ImageName == crp.ImageName { - wwlog.Debug("updating profile %s image to %s", profileId, crp.TargetName) - nodeDB.NodeProfiles[profileId].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 -} diff --git a/internal/pkg/image/build.go b/internal/pkg/image/build.go index 116b6301..f9203037 100644 --- a/internal/pkg/image/build.go +++ b/internal/pkg/image/build.go @@ -4,8 +4,6 @@ import ( "fmt" "path" - "github.com/pkg/errors" - "github.com/warewulf/warewulf/internal/pkg/util" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) @@ -17,7 +15,7 @@ func Build(name string, buildForce bool) error { imagePath := ImageFile(name) if !ValidSource(name) { - return errors.Errorf("Image does not exist: %s", name) + return fmt.Errorf("Image does not exist: %s", name) } if !buildForce { diff --git a/internal/pkg/image/delete.go b/internal/pkg/image/delete.go new file mode 100644 index 00000000..2a7cb6bf --- /dev/null +++ b/internal/pkg/image/delete.go @@ -0,0 +1,78 @@ +package image + +import ( + "fmt" + "os" + + "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/pkg/util" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Delete(name string) error { + // validate image names + if !ValidSource(name) { + return fmt.Errorf("image name is not valid source: %s", name) + } + + nodeDB, err := node.New() + if err != nil { + return fmt.Errorf("could not open nodeDB: %s", err) + } + + // check if the deleted images are not used by nodes + for nodeName, node := range nodeDB.Nodes { + if node.ImageName == name { + return fmt.Errorf("image %s is in use by node %s, cannot delete", node.ImageName, nodeName) + } + } + + // check if the deleted images are not used by profiles + for profileName, profile := range nodeDB.NodeProfiles { + if profile.ImageName == name { + return fmt.Errorf("image %s is in use by profile %s, cannot delete", profile.ImageName, profileName) + } + } + + // delete images + if err := DeleteSource(name); err != nil { + return fmt.Errorf("could not remove image source %s: %w", name, err) + } + if err := DeleteImage(name); err != nil { + return fmt.Errorf("could not remove image file %s: %w", name, err) + } + wwlog.Info("Deleted image %q", name) + + return nil +} + +/* +Delete the chroot of an image +*/ +func DeleteSource(name string) error { + fullPath := SourceDir(name) + + wwlog.Verbose("Removing path: %s", fullPath) + return os.RemoveAll(fullPath) +} + +/* +Delete the image of an image +*/ +func DeleteImage(name string) error { + imageFile := ImageFile(name) + if util.IsFile(imageFile) { + wwlog.Verbose("removing %s for image %s", imageFile, name) + errImg := os.Remove(imageFile) + wwlog.Verbose("removing %s for image %s", imageFile+".gz", name) + errGz := os.Remove(imageFile + ".gz") + if errImg != nil { + return fmt.Errorf("Problems delete %s for image %s: %s", imageFile, name, errImg) + } + if errGz != nil { + return fmt.Errorf("Problems delete %s for image %s: %s", imageFile+".gz", name, errGz) + } + return nil + } + return fmt.Errorf("Image %s of image %s doesn't exist", imageFile, name) +} diff --git a/internal/pkg/image/imprt.go b/internal/pkg/image/imprt.go index 8e8f37bd..4b26432c 100644 --- a/internal/pkg/image/imprt.go +++ b/internal/pkg/image/imprt.go @@ -2,8 +2,10 @@ package image import ( "context" + "fmt" "os" "path" + "strconv" "github.com/containers/image/v5/types" "github.com/containers/storage/drivers/copy" @@ -74,3 +76,48 @@ func ImportDirectory(uri string, name string) error { return nil } + +// 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 +} diff --git a/internal/pkg/image/rename.go b/internal/pkg/image/rename.go new file mode 100644 index 00000000..8e7e003a --- /dev/null +++ b/internal/pkg/image/rename.go @@ -0,0 +1,60 @@ +package image + +import ( + "fmt" + "os" + + "github.com/warewulf/warewulf/internal/pkg/node" + "github.com/warewulf/warewulf/internal/pkg/wwlog" +) + +func Rename(name string, targetName string, build bool) error { + if !ValidSource(name) { + return fmt.Errorf("image source does not exist: %s", name) + } + if !ValidName(targetName) { + return fmt.Errorf("invalid image name: %s", targetName) + } + + // rename the image source folder + sourceDir := SourceDir(name) + destDir := SourceDir(targetName) + err := os.Rename(sourceDir, destDir) + if err != nil { + return err + } + + err = DeleteImage(name) + if err != nil { + wwlog.Warn("Could not remove image files for %s: %s", name, err) + } + + if build { + err = Build(targetName, true) + if err != nil { + return err + } + } + + // update the nodes profiles image name + nodeDB, err := node.New() + if err != nil { + return err + } + + for nodeId, node := range nodeDB.Nodes { + if node.ImageName == name { + wwlog.Debug("updating node %s image to %s", nodeId, targetName) + nodeDB.Nodes[nodeId].ImageName = targetName + } + } + + for profileId, profile := range nodeDB.NodeProfiles { + if profile.ImageName == name { + wwlog.Debug("updating profile %s image to %s", profileId, targetName) + nodeDB.NodeProfiles[profileId].ImageName = targetName + } + } + + return nodeDB.Persist() +} diff --git a/internal/pkg/image/util.go b/internal/pkg/image/util.go index f5cd6e5e..20ad5ad5 100644 --- a/internal/pkg/image/util.go +++ b/internal/pkg/image/util.go @@ -68,16 +68,6 @@ func ValidSource(name string) bool { return true } -/* -Delete the chroot of an image -*/ -func DeleteSource(name string) error { - fullPath := SourceDir(name) - - wwlog.Verbose("Removing path: %s", fullPath) - return os.RemoveAll(fullPath) -} - func Duplicate(name string, destination string) error { fullPathImageSource := RootFsDir(name) @@ -91,27 +81,6 @@ func Duplicate(name string, destination string) error { return nil } -/* -Delete the image of an image -*/ -func DeleteImage(name string) error { - imageFile := ImageFile(name) - if util.IsFile(imageFile) { - wwlog.Verbose("removing %s for image %s", imageFile, name) - errImg := os.Remove(imageFile) - wwlog.Verbose("removing %s for image %s", imageFile+".gz", name) - errGz := os.Remove(imageFile + ".gz") - if errImg != nil { - return errors.Errorf("Problems delete %s for image %s: %s\n", imageFile, name, errImg) - } - if errGz != nil { - return errors.Errorf("Problems delete %s for image %s: %s\n", imageFile+".gz", name, errGz) - } - return nil - } - return errors.Errorf("Image %s of image %s doesn't exist\n", imageFile, name) -} - func IsWriteAble(name string) bool { return !util.IsFile(filepath.Join(SourceDir(name), "readonly")) } diff --git a/internal/pkg/warewulfd/api/image.go b/internal/pkg/warewulfd/api/image.go index 46836196..dab54f54 100644 --- a/internal/pkg/warewulfd/api/image.go +++ b/internal/pkg/warewulfd/api/image.go @@ -7,11 +7,10 @@ import ( "github.com/swaggest/usecase" "github.com/swaggest/usecase/status" - image_api "github.com/warewulf/warewulf/internal/pkg/api/image" - "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/warewulfd" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) @@ -99,7 +98,7 @@ func importImage() usecase.Interactor { return status.Wrap(fmt.Errorf("name contains illegal characters: %s", input.Name), status.InvalidArgument) } - if sctx, err := image_api.GetSystemContext(input.NoHttps, input.User, input.Password, ""); err != nil { + if sctx, err := image.GetSystemContext(input.NoHttps, input.User, input.Password, ""); err != nil { return err } else { if err := image.ImportDocker(input.URI, input.Name, sctx); err != nil { @@ -139,11 +138,7 @@ func deleteImage() usecase.Interactor { } } - cdp := &wwapiv1.ImageDeleteParameter{ - ImageNames: []string{input.Name}, - } - - return image_api.ImageDelete(cdp) + return image.Delete(input.Name) }) u.SetTitle("Delete an image") u.SetDescription("Delete an existing OS image") @@ -163,16 +158,11 @@ func updateImage() usecase.Interactor { wwlog.Debug("api.updateImage(Name:%v, NewName:%v, Build:%v)", input.Name, input.NewName, input.Build) name := input.Name if input.NewName != "" { - crp := &wwapiv1.ImageRenameParameter{ - ImageName: input.Name, - TargetName: input.NewName, - Build: input.Build, - } - - if err := image_api.ImageRename(crp); err != nil { + if err := image.Rename(input.Name, input.NewName, input.Build); err != nil { return err } name = input.NewName + warewulfd.Reload() } *output = *NewImage(name) @@ -193,12 +183,8 @@ func buildImage() usecase.Interactor { u := usecase.NewInteractor(func(ctx context.Context, input buildImageInput, output *Image) error { wwlog.Debug("api.buildImage(Name:%v, Force:%v)", input.Name, input.Force) - cbp := &wwapiv1.ImageBuildParameter{ - ImageNames: []string{input.Name}, - Force: input.Force, - } - if err := image_api.ImageBuild(cbp); err != nil { + if err := image.Build(input.Name, input.Force); err != nil { return err }