Rename "container" to "image"

- Updated `wwctl upgrade` to handle updates
- Maintained `.Container` and `.ContainerName` in tstruct
- Added `ContainerName()` methods to node and profile objects
- Added `--container`, `-C` aliases to wwctl commands (`<node|profile> <add|set>`)
- Added `wwctl container` alias
- Added support for `container_exit.sh` if `image_exit.sh` is not found

Signed-off-by: Jonathon Anderson <janderson@ciq.com>
This commit is contained in:
Jonathon Anderson
2025-01-18 17:34:48 -07:00
parent 96396ed47e
commit 45a690ca4e
142 changed files with 2212 additions and 2183 deletions

View File

@@ -14,12 +14,12 @@ wwctl node delete
wwctl node list
wwctl node set
wwctl node status
wwctl container build
wwctl container delete
wwctl container import
wwctl container list
wwctl container show
wwctl container copy
wwctl image build
wwctl image delete
wwctl image import
wwctl image list
wwctl image show
wwctl image copy
Some notes on the files:

View File

@@ -1,7 +1,7 @@
# Warewulf API Daemon
wwapid is a grpc service serving the Warewulf API. For v1, the intent is to serve the same interface as wwctl serves. For this preview PR, we are serving much of ```wwctl node``` and ```wwctl container```.
wwapid is a grpc service serving the Warewulf API. For v1, the intent is to serve the same interface as wwctl serves. For this preview PR, we are serving much of ```wwctl node``` and ```wwctl image```.
Initial security is by mTLS. For development we are generating our own keys. A good tutorial for this is here: https://www.handracs.info/blog/grpcmtlsgo/
The configuration file is wwapid.conf.
The configuration file is wwapid.conf.

View File

@@ -12,7 +12,7 @@ import (
"path"
"github.com/warewulf/warewulf/internal/pkg/api/apiconfig"
"github.com/warewulf/warewulf/internal/pkg/api/container"
"github.com/warewulf/warewulf/internal/pkg/api/image"
apinode "github.com/warewulf/warewulf/internal/pkg/api/node"
"github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1"
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
@@ -121,92 +121,92 @@ func insecureMode() {
// Api implementation.
// ContainerBuild builds one or more containers.
func (s *apiServer) ContainerBuild(ctx context.Context, request *wwapiv1.ContainerBuildParameter) (response *wwapiv1.ContainerListResponse, err error) {
// ImageBuild builds one or more images.
func (s *apiServer) ImageBuild(ctx context.Context, request *wwapiv1.ImageBuildParameter) (response *wwapiv1.ImageListResponse, err error) {
// Parameter checks.
if request == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request")
}
if request.ContainerNames == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request.ContainerNames")
if request.ImageNames == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request.ImageNames")
}
// Build the container.
err = container.ContainerBuild(request)
// Build the image.
err = image.ImageBuild(request)
if err != nil {
return
}
// Return the built containers. (A REST POST returns what is modified.)
var containers []*wwapiv1.ContainerInfo
containers, err = container.ContainerList()
// Return the built images. (A REST POST returns what is modified.)
var images []*wwapiv1.ImageInfo
images, err = image.ImageList()
if err != nil {
return
}
response = &wwapiv1.ContainerListResponse{}
for i := 0; i < len(containers); i++ {
for j := 0; j < len(request.ContainerNames); j++ {
if containers[i].Name == request.ContainerNames[j] {
response.Containers = append(response.Containers, containers[i])
response = &wwapiv1.ImageListResponse{}
for i := 0; i < len(images); i++ {
for j := 0; j < len(request.ImageNames); j++ {
if images[i].Name == request.ImageNames[j] {
response.Images = append(response.Images, images[i])
}
}
}
return
}
// ContainerCopy duplicates a container.
func (s *apiServer) ContainerCopy(ctx context.Context, request *wwapiv1.ContainerCopyParameter) (response *emptypb.Empty, err error) {
// ImageCopy duplicates an image.
func (s *apiServer) ImageCopy(ctx context.Context, request *wwapiv1.ImageCopyParameter) (response *emptypb.Empty, err error) {
// Parameter checks.
if request == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request")
}
err = container.ContainerCopy(request)
err = image.ImageCopy(request)
return
}
// ContainerDelete deletes one or more containers from Warewulf.
func (s *apiServer) ContainerDelete(ctx context.Context, request *wwapiv1.ContainerDeleteParameter) (response *emptypb.Empty, err error) {
// ImageDelete deletes one or more images from Warewulf.
func (s *apiServer) ImageDelete(ctx context.Context, request *wwapiv1.ImageDeleteParameter) (response *emptypb.Empty, err error) {
// Parameter checks.
if request == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request")
}
if request.ContainerNames == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request.ContainerNames")
if request.ImageNames == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request.ImageNames")
}
err = container.ContainerDelete(request)
err = image.ImageDelete(request)
return
}
func (s *apiServer) ContainerImport(ctx context.Context, request *wwapiv1.ContainerImportParameter) (response *wwapiv1.ContainerListResponse, err error) {
func (s *apiServer) ImageImport(ctx context.Context, request *wwapiv1.ImageImportParameter) (response *wwapiv1.ImageListResponse, err error) {
// Import the container.
var containerName string
containerName, err = container.ContainerImport(request)
// Import the image.
var imageName string
imageName, err = image.ImageImport(request)
if err != nil {
return
}
// Return the imported container to the client.
var containers []*wwapiv1.ContainerInfo
containers, err = container.ContainerList()
// Return the imported image to the client.
var images []*wwapiv1.ImageInfo
images, err = image.ImageList()
if err != nil {
return
}
// Container name may have been shimmed in during import,
// which is why ContainerImport returns it.
for i := 0; i < len(containers); i++ {
if containerName == containers[i].Name {
response = &wwapiv1.ContainerListResponse{
Containers: []*wwapiv1.ContainerInfo{containers[i]},
// Image name may have been shimmed in during import,
// which is why ImageImport returns it.
for i := 0; i < len(images); i++ {
if imageName == images[i].Name {
response = &wwapiv1.ImageListResponse{
Images: []*wwapiv1.ImageInfo{images[i]},
}
return
}
@@ -214,30 +214,30 @@ func (s *apiServer) ContainerImport(ctx context.Context, request *wwapiv1.Contai
return
}
// ContainerList returns details about containers.
func (s *apiServer) ContainerList(ctx context.Context, request *emptypb.Empty) (response *wwapiv1.ContainerListResponse, err error) {
// ImageList returns details about images.
func (s *apiServer) ImageList(ctx context.Context, request *emptypb.Empty) (response *wwapiv1.ImageListResponse, err error) {
var containers []*wwapiv1.ContainerInfo
containers, err = container.ContainerList()
var images []*wwapiv1.ImageInfo
images, err = image.ImageList()
if err != nil {
return
}
response = &wwapiv1.ContainerListResponse{
Containers: containers,
response = &wwapiv1.ImageListResponse{
Images: images,
}
return
}
// ContainerShow returns details about containers.
func (s *apiServer) ContainerShow(ctx context.Context, request *wwapiv1.ContainerShowParameter) (response *wwapiv1.ContainerShowResponse, err error) {
// ImageShow returns details about images.
func (s *apiServer) ImageShow(ctx context.Context, request *wwapiv1.ImageShowParameter) (response *wwapiv1.ImageShowResponse, err error) {
// Parameter checks.
if request == nil {
return response, status.Errorf(codes.InvalidArgument, "nil request")
}
return container.ContainerShow(request)
return image.ImageShow(request)
}
// NodeAdd adds one or more nodes for management by Warewulf and returns the added nodes.

View File

@@ -1,9 +1,9 @@
# Warewulf API Rest Daemon
wwapird is a client of wwapid serving a REST interface for the Warewulf API. For v1, the intent is to serve the same interface as wwctl serves. For this preview PR, we are serving much of ```wwctl node``` and ```wwctl container```.
wwapird is a client of wwapid serving a REST interface for the Warewulf API. For v1, the intent is to serve the same interface as wwctl serves. For this preview PR, we are serving much of ```wwctl node``` and ```wwctl image```.
Initial security is by mTLS. For development we are generating our own keys. A good tutorial for this is here: https://www.handracs.info/blog/grpcmtlsgo/
The configuration file is wwapird.conf.
Sample cURLs with and without mTLS are in curl.sh. This file is just a scratchpad for examples.
Sample cURLs with and without mTLS are in curl.sh. This file is just a scratchpad for examples.

View File

@@ -11,17 +11,17 @@ curl --cacert /usr/local/etc/warewulf/keys/cacert.pem \
--cert /usr/local/etc/warewulf/keys/client.pem \
https://localhost:9871/version
# container list all
curl http://localhost:9871/v1/container
# image list all
curl http://localhost:9871/v1/image
# container import
curl -d '{"source": "docker://ghcr.io/warewulf/warewulf-rockylinux:8", "name": "rocky-8", "update": true, "default": true}' -H "Content-Type: application/json" -X POST http://localhost:9871/v1/container
# image import
curl -d '{"source": "docker://ghcr.io/warewulf/warewulf-rockylinux:8", "name": "rocky-8", "update": true, "default": true}' -H "Content-Type: application/json" -X POST http://localhost:9871/v1/image
# container delete
curl -X DELETE http://localhost:9871/v1/container?containerNames=rocky-8
# image delete
curl -X DELETE http://localhost:9871/v1/image?imageNames=rocky-8
# container build
curl -d '{"containerNames": ["rocky-8"], "force": true}' -H "Content-Type: application/json" -X POST http://localhost:9871/v1/containerbuild
# image build
curl -d '{"imageNames": ["rocky-8"], "force": true}' -H "Content-Type: application/json" -X POST http://localhost:9871/v1/imagebuild
# node list all
curl http://localhost:9871/v1/node

View File

@@ -3,8 +3,8 @@ package completions
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/hostlist"
"github.com/warewulf/warewulf/internal/pkg/image"
"github.com/warewulf/warewulf/internal/pkg/kernel"
"github.com/warewulf/warewulf/internal/pkg/node"
)
@@ -19,8 +19,8 @@ func NodeKernelVersion(cmd *cobra.Command, args []string, toComplete string) ([]
for _, id := range nodes {
if node_, err := registry.GetNode(id); err != nil {
continue
} else if node_.ContainerName != "" {
kernels := kernel.FindKernels(node_.ContainerName)
} else if node_.ImageName != "" {
kernels := kernel.FindKernels(node_.ImageName)
for _, kernel_ := range kernels {
kernelVersions = append(kernelVersions, kernel_.Version(), kernel_.Path)
}
@@ -38,8 +38,8 @@ func ProfileKernelVersion(cmd *cobra.Command, args []string, toComplete string)
for _, id := range args {
if profile, err := registry.GetProfile(id); err != nil {
continue
} else if profile.ContainerName != "" {
kernels := kernel.FindKernels(profile.ContainerName)
} else if profile.ImageName != "" {
kernels := kernel.FindKernels(profile.ImageName)
for _, kernel_ := range kernels {
kernelVersions = append(kernelVersions, kernel_.Version(), kernel_.Path)
}
@@ -48,7 +48,7 @@ func ProfileKernelVersion(cmd *cobra.Command, args []string, toComplete string)
return kernelVersions, cobra.ShellCompDirectiveNoFileComp
}
func Containers(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
sources, _ := container.ListSources()
func Images(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
sources, _ := image.ListSources()
return sources, cobra.ShellCompDirectiveNoFileComp
}

View File

@@ -1,20 +0,0 @@
package build
import (
"testing"
)
// TestArgsContainerBuild is a regression test for 215.
func TestArgsContainerBuild(t *testing.T) {
command := GetCommand()
err := command.Args(command, []string{})
if err != nil {
t.Errorf("no arguments to container build should succeed.")
}
err = command.Args(command, []string{"container1", "container2"})
if err != nil {
t.Errorf("multiple arguments to container build should succeed.")
}
}

View File

@@ -1,16 +0,0 @@
package build
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/api/container"
"github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1"
)
func CobraRunE(cmd *cobra.Command, args []string) error {
cbp := &wwapiv1.ContainerBuildParameter{
ContainerNames: args,
Force: BuildForce,
All: BuildAll,
}
return container.ContainerBuild(cbp)
}

View File

@@ -1,50 +0,0 @@
package copy
import (
"fmt"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
if len(args) > 2 {
wwlog.Warn("copy only requires 2 arguments but you provided %d arguments. Hence, they will be ignored.", len(args))
}
cdp := &wwapiv1.ContainerCopyParameter{
ContainerSource: args[0],
ContainerDestination: args[1],
Build: Build,
}
if !container.DoesSourceExist(cdp.ContainerSource) {
return fmt.Errorf("container's source doesn't exists: %s", cdp.ContainerSource)
}
if !container.ValidName(cdp.ContainerDestination) {
return fmt.Errorf("container name contains illegal characters : %s", cdp.ContainerDestination)
}
if container.DoesSourceExist(cdp.ContainerDestination) {
return fmt.Errorf("an other container with name: %s already exists in sources", cdp.ContainerDestination)
}
err = container.Duplicate(cdp.ContainerSource, cdp.ContainerDestination)
if err != nil {
return fmt.Errorf("could not duplicate image: %s", err.Error())
}
if cdp.Build {
err = container.Build(cdp.ContainerDestination, true)
if err != nil {
return err
}
}
wwlog.Info("Container %s successfully duplicated as %s", cdp.ContainerSource, cdp.ContainerDestination)
return
}

View File

@@ -1,84 +0,0 @@
package list
import (
"strconv"
"time"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/table"
apicontainer "github.com/warewulf/warewulf/internal/pkg/api/container"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/util"
)
var containerList = apicontainer.ContainerList
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 {
containerInfo, err := containerList()
if err != nil {
return err
}
if vars.full {
t.AddHeader("CONTAINER NAME", "NODES", "KERNEL VERSION", "CREATION TIME", "MODIFICATION TIME", "SIZE")
for i := 0; i < len(containerInfo); i++ {
createTime := time.Unix(int64(containerInfo[i].CreateDate), 0)
modTime := time.Unix(int64(containerInfo[i].ModDate), 0)
sz := util.ByteToString(int64(containerInfo[i].ImgSize))
if vars.compressed {
sz = util.ByteToString(int64(containerInfo[i].ImgSizeComp))
}
if vars.chroot {
sz = util.ByteToString(int64(containerInfo[i].Size))
}
t.AddLine(
containerInfo[i].Name,
strconv.FormatUint(uint64(containerInfo[i].NodeCount), 10),
containerInfo[i].KernelVersion,
createTime.Format(time.RFC822),
modTime.Format(time.RFC822),
sz,
)
}
} else if vars.kernel {
t.AddHeader("CONTAINER NAME", "NODES", "KERNEL VERSION")
for i := 0; i < len(containerInfo); i++ {
t.AddLine(
containerInfo[i].Name,
strconv.FormatUint(uint64(containerInfo[i].NodeCount), 10),
containerInfo[i].KernelVersion,
)
}
} else if showSize {
t.AddHeader("CONTAINER NAME", "NODES", "SIZE")
for i := 0; i < len(containerInfo); i++ {
sz := util.ByteToString(int64(containerInfo[i].ImgSize))
if vars.compressed {
sz = util.ByteToString(int64(containerInfo[i].ImgSizeComp))
}
if vars.chroot {
sz = util.ByteToString(int64(containerInfo[i].Size))
}
t.AddLine(
containerInfo[i].Name,
strconv.FormatUint(uint64(containerInfo[i].NodeCount), 10),
sz,
)
}
}
} else {
t.AddHeader("CONTAINER NAME")
list, _ := container.ListSources()
for _, cont := range list {
t.AddLine(cont)
}
}
t.Print()
return
}
}

View File

@@ -1,44 +0,0 @@
package rename
import (
"fmt"
"github.com/spf13/cobra"
api "github.com/warewulf/warewulf/internal/pkg/api/container"
"github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
if len(args) != 2 {
return fmt.Errorf("rename requires 2 arguments: %d provided", len(args))
}
crp := &wwapiv1.ContainerRenameParameter{
ContainerName: args[0],
TargetName: args[1],
Build: SetBuild,
}
if !container.DoesSourceExist(crp.ContainerName) {
return fmt.Errorf("%s source dir does not exist", crp.ContainerName)
}
if container.DoesSourceExist(crp.TargetName) {
return fmt.Errorf("an other container with the name %s already exists", crp.TargetName)
}
if !container.ValidName(crp.TargetName) {
return fmt.Errorf("container name contains illegal characters : %s", crp.TargetName)
}
err = api.ContainerRename(crp)
if err != nil {
err = fmt.Errorf("could not rename image: %s", err.Error())
return
}
wwlog.Info("Container %s successfully renamed as %s", crp.ContainerName, crp.TargetName)
return
}

View File

@@ -1,45 +0,0 @@
package container
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/container/build"
"github.com/warewulf/warewulf/internal/app/wwctl/container/copy"
"github.com/warewulf/warewulf/internal/app/wwctl/container/delete"
"github.com/warewulf/warewulf/internal/app/wwctl/container/exec"
"github.com/warewulf/warewulf/internal/app/wwctl/container/imprt"
"github.com/warewulf/warewulf/internal/app/wwctl/container/kernels"
"github.com/warewulf/warewulf/internal/app/wwctl/container/list"
"github.com/warewulf/warewulf/internal/app/wwctl/container/rename"
"github.com/warewulf/warewulf/internal/app/wwctl/container/shell"
"github.com/warewulf/warewulf/internal/app/wwctl/container/show"
"github.com/warewulf/warewulf/internal/app/wwctl/container/syncuser"
)
var baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "container COMMAND [OPTIONS]",
Short: "Container / VNFS image management",
Long: "Starting with version 4, Warewulf uses containers to build the bootable VNFS\n" +
"node images. These commands will help you import, manage, and transform\n" +
"containers into bootable Warewulf VNFS images.",
Aliases: []string{"vnfs"},
}
func init() {
baseCmd.AddCommand(build.GetCommand())
baseCmd.AddCommand(list.GetCommand())
baseCmd.AddCommand(imprt.GetCommand())
baseCmd.AddCommand(exec.GetCommand())
baseCmd.AddCommand(shell.GetCommand())
baseCmd.AddCommand(delete.GetCommand())
baseCmd.AddCommand(show.GetCommand())
baseCmd.AddCommand(syncuser.GetCommand())
baseCmd.AddCommand(copy.GetCommand())
baseCmd.AddCommand(rename.GetCommand())
baseCmd.AddCommand(kernels.GetCommand())
}
// GetRootCommand returns the root cobra.Command for the application.
func GetCommand() *cobra.Command {
return baseCmd
}

View File

@@ -1,40 +0,0 @@
package syncuser
import (
"fmt"
"github.com/spf13/cobra"
container_build "github.com/warewulf/warewulf/internal/pkg/api/container"
"github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func CobraRunE(cmd *cobra.Command, args []string) error {
containerName := args[0]
if !container.ValidName(containerName) {
return fmt.Errorf("%s is not a valid container", containerName)
}
err := container.SyncUids(containerName, write)
if err != nil {
return fmt.Errorf("error in synchronize: %s", err)
}
if write && !build {
// when write = true and build = false, we will print a warnning, this is the default case
wwlog.Warn("Syncuser is completed, please remember to rebuild container or add `--build` flag for automatic rebuild after syncuser")
} else if write && build {
// if write = true and build = true, then it'll trigger the container build after sync
cbp := &wwapiv1.ContainerBuildParameter{
ContainerNames: []string{containerName},
Force: true,
All: false,
}
err := container_build.ContainerBuild(cbp)
if err != nil {
return fmt.Errorf("error during container build: %s", err)
}
}
return nil
}

View File

@@ -0,0 +1,10 @@
package flags
import (
"github.com/spf13/cobra"
)
func AddContainer(cmd *cobra.Command, dest *string) {
cmd.Flags().StringVarP(dest, "container", "C", "", "Set image name (backwards-compatibility)")
cmd.Flags().Lookup("container").Hidden = true
}

View File

@@ -0,0 +1,20 @@
package build
import (
"testing"
)
// TestArgsImageBuild is a regression test for 215.
func TestArgsImageBuild(t *testing.T) {
command := GetCommand()
err := command.Args(command, []string{})
if err != nil {
t.Errorf("no arguments to image build should succeed.")
}
err = command.Args(command, []string{"image1", "image2"})
if err != nil {
t.Errorf("multiple arguments to image build should succeed.")
}
}

View File

@@ -0,0 +1,16 @@
package build
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/api/image"
"github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1"
)
func CobraRunE(cmd *cobra.Command, args []string) error {
cbp := &wwapiv1.ImageBuildParameter{
ImageNames: args,
Force: BuildForce,
All: BuildAll,
}
return image.ImageBuild(cbp)
}

View File

@@ -2,22 +2,22 @@ package build
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/image"
)
var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "build [OPTIONS] CONTAINER [...]",
Short: "(Re)build a bootable VNFS image",
Long: "This command will build a bootable VNFS image from imported CONTAINER image(s).",
Use: "build [OPTIONS] IMAGE [...]",
Short: "(Re)build a bootable image",
Long: "This command will build a bootable image from an imported IMAGE(s).",
RunE: CobraRunE,
Args: cobra.ArbitraryArgs,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
list, _ := container.ListSources()
list, _ := image.ListSources()
return list, cobra.ShellCompDirectiveNoFileComp
},
}
@@ -26,7 +26,7 @@ var (
)
func init() {
baseCmd.PersistentFlags().BoolVarP(&BuildAll, "all", "a", false, "(re)Build all VNFS images for all nodes")
baseCmd.PersistentFlags().BoolVarP(&BuildAll, "all", "a", false, "(re)Build all images")
baseCmd.PersistentFlags().BoolVarP(&BuildForce, "force", "f", false, "Force rebuild, even if it isn't necessary")
}

View File

@@ -0,0 +1,50 @@
package copy
import (
"fmt"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1"
"github.com/warewulf/warewulf/internal/pkg/image"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
if len(args) > 2 {
wwlog.Warn("copy only requires 2 arguments but you provided %d arguments. Hence, they will be ignored.", len(args))
}
cdp := &wwapiv1.ImageCopyParameter{
ImageSource: args[0],
ImageDestination: args[1],
Build: Build,
}
if !image.DoesSourceExist(cdp.ImageSource) {
return fmt.Errorf("image's source doesn't exists: %s", cdp.ImageSource)
}
if !image.ValidName(cdp.ImageDestination) {
return fmt.Errorf("image name contains illegal characters : %s", cdp.ImageDestination)
}
if image.DoesSourceExist(cdp.ImageDestination) {
return fmt.Errorf("an other image with name: %s already exists in sources", cdp.ImageDestination)
}
err = image.Duplicate(cdp.ImageSource, cdp.ImageDestination)
if err != nil {
return fmt.Errorf("could not duplicate image: %s", err.Error())
}
if cdp.Build {
err = image.Build(cdp.ImageDestination, true)
if err != nil {
return err
}
}
wwlog.Info("Image %s successfully duplicated as %s", cdp.ImageSource, cdp.ImageDestination)
return
}

View File

@@ -11,23 +11,23 @@ import (
func Test_Copy(t *testing.T) {
env := testenv.New(t)
env.WriteFile(path.Join(testenv.WWChrootdir, "test-container/rootfs/bin/sh"), `test`)
env.WriteFile(path.Join(testenv.WWChrootdir, "test-image/rootfs/bin/sh"), `test`)
defer env.RemoveAll()
warewulfd.SetNoDaemon()
t.Run("container copy without build", func(t *testing.T) {
t.Run("image copy without build", func(t *testing.T) {
baseCmd := GetCommand()
baseCmd.SetArgs([]string{"test-container", "test-container-copy-without-build"})
baseCmd.SetArgs([]string{"test-image", "test-image-copy-without-build"})
err := baseCmd.Execute()
assert.NoError(t, err)
assert.NoFileExists(t, path.Join(env.BaseDir, testenv.WWProvisiondir, "container", "test-container-copy-without-build.img"))
assert.NoFileExists(t, path.Join(env.BaseDir, testenv.WWProvisiondir, "image", "test-image-copy-without-build.img"))
})
t.Run("container copy with build", func(t *testing.T) {
t.Run("image copy with build", func(t *testing.T) {
baseCmd := GetCommand()
baseCmd.SetArgs([]string{"-b", "test-container", "test-container-copy"})
baseCmd.SetArgs([]string{"-b", "test-image", "test-image-copy"})
err := baseCmd.Execute()
assert.NoError(t, err)
assert.FileExists(t, path.Join(env.BaseDir, testenv.WWProvisiondir, "container", "test-container-copy.img"))
assert.FileExists(t, path.Join(env.BaseDir, testenv.WWProvisiondir, "image", "test-image-copy.img"))
})
}

View File

@@ -2,23 +2,23 @@ package copy
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/image"
)
var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "copy CONTAINER NEW_NAME",
Use: "copy IMAGE NEW_NAME",
Aliases: []string{"cp"},
Short: "Copy an existing container",
Long: "This command will duplicate an imported container image.",
Short: "Copy an existing image",
Long: "This command will duplicate an imported image.",
RunE: CobraRunE,
Args: cobra.MinimumNArgs(2),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
list, _ := container.ListSources()
list, _ := image.ListSources()
return list, cobra.ShellCompDirectiveNoFileComp
},
}
@@ -26,7 +26,7 @@ var (
)
func init() {
baseCmd.PersistentFlags().BoolVarP(&Build, "build", "b", false, "Build container after copy")
baseCmd.PersistentFlags().BoolVarP(&Build, "build", "b", false, "Build image after copy")
}
// GetRootCommand returns the root cobra.Command for the application.

View File

@@ -3,7 +3,7 @@ package delete
import (
"fmt"
"github.com/warewulf/warewulf/internal/pkg/api/container"
"github.com/warewulf/warewulf/internal/pkg/api/image"
"github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1"
apiutil "github.com/warewulf/warewulf/internal/pkg/api/util"
@@ -11,16 +11,16 @@ import (
)
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
cdp := &wwapiv1.ContainerDeleteParameter{
ContainerNames: args,
cdp := &wwapiv1.ImageDeleteParameter{
ImageNames: args,
}
if !SetYes {
yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to delete container %s", args))
yes := apiutil.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to delete image %s", args))
if !yes {
return
}
}
return container.ContainerDelete(cdp)
return image.ImageDelete(cdp)
}

View File

@@ -2,22 +2,22 @@ package delete
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/image"
)
var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "delete [OPTIONS] CONTAINER [...]",
Use: "delete [OPTIONS] IMAGE [...]",
Aliases: []string{"rm", "remove", "del"},
Short: "Delete an imported container",
Long: "This command will delete CONTAINERs that have been imported into Warewulf.",
Short: "Delete an imported image",
Long: "This command will delete IMAGEs that have been imported into Warewulf.",
RunE: CobraRunE,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
list, _ := container.ListSources()
list, _ := image.ListSources()
return list, cobra.ShellCompDirectiveNoFileComp
},
}

View File

@@ -13,7 +13,7 @@ import (
"github.com/spf13/cobra"
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/image"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/overlay"
"github.com/warewulf/warewulf/internal/pkg/util"
@@ -27,20 +27,20 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
return fmt.Errorf("PID is not 1: %d", os.Getpid())
}
containerName := args[0]
imageName := args[0]
if !container.ValidSource(containerName) {
return fmt.Errorf("unknown Warewulf container: %s", containerName)
if !image.ValidSource(imageName) {
return fmt.Errorf("unknown Warewulf image: %s", imageName)
}
conf := warewulfconf.Get()
runDir := container.RunDir(containerName)
runDir := image.RunDir(imageName)
if _, err := os.Stat(runDir); os.IsNotExist(err) {
return fmt.Errorf("container run directory does not exist: %w", err)
return fmt.Errorf("image run directory does not exist: %w", err)
}
mountPts := conf.MountsContainer
mountPts = append(container.InitMountPnts(binds), mountPts...)
mountPts := conf.MountsImage
mountPts = append(image.InitMountPnts(binds), mountPts...)
// check for valid mount points
lowerObjects := checkMountPoints(containerName, mountPts)
lowerObjects := checkMountPoints(imageName, mountPts)
// need to create a overlay, where the lower layer contains
// the missing mount points
wwlog.Verbose("for ephermal mount use tempdir %s", runDir)
@@ -72,18 +72,18 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
defer desc.Close()
}
}
containerPath := container.RootFsDir(containerName)
imagePath := image.RootFsDir(imageName)
// running in a private PID space, so also make / private, so that nothing gets out from here
err = syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, "")
if err != nil {
return fmt.Errorf("failed to mount: %w", err)
}
ps1Str := fmt.Sprintf("[%s|%s] Warewulf> ", containerName, exitEval)
ps1Str := fmt.Sprintf("[%s|%s] Warewulf> ", imageName, exitEval)
if len(lowerObjects) != 0 && nodename == "" {
options := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s",
path.Join(runDir, "lower"), containerPath, path.Join(runDir, "work"))
path.Join(runDir, "lower"), imagePath, path.Join(runDir, "work"))
wwlog.Debug("overlay options: %s", options)
err = syscall.Mount("overlay", containerPath, "overlay", 0, options)
err = syscall.Mount("overlay", imagePath, "overlay", 0, options)
if err != nil {
wwlog.Warn("Couldn't create overlay for ephermal mount points: %s", err)
}
@@ -108,22 +108,22 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
return fmt.Errorf("could not build overlay: %s", err)
}
options := fmt.Sprintf("lowerdir=%s:%s:%s",
path.Join(runDir, "lower"), containerPath, path.Join(runDir, "nodeoverlay"))
path.Join(runDir, "lower"), imagePath, path.Join(runDir, "nodeoverlay"))
wwlog.Debug("overlay options: %s", options)
err = syscall.Mount("overlay", containerPath, "overlay", 0, options)
err = syscall.Mount("overlay", imagePath, "overlay", 0, options)
if err != nil {
return fmt.Errorf("Couldn't create overlay for node render overlay: %s", err)
}
ps1Str = fmt.Sprintf("[%s|ro|%s] Warewulf> ", containerName, nodename)
ps1Str = fmt.Sprintf("[%s|ro|%s] Warewulf> ", imageName, nodename)
}
if !container.IsWriteAble(containerName) && nodename == "" {
wwlog.Verbose("mounting %s ro", containerPath)
ps1Str = fmt.Sprintf("[%s|ro] Warewulf> ", containerName)
err = syscall.Mount(containerPath, containerPath, "", syscall.MS_BIND, "")
if !image.IsWriteAble(imageName) && nodename == "" {
wwlog.Verbose("mounting %s ro", imagePath)
ps1Str = fmt.Sprintf("[%s|ro] Warewulf> ", imageName)
err = syscall.Mount(imagePath, imagePath, "", syscall.MS_BIND, "")
if err != nil {
return fmt.Errorf("failed to prepare bind mount: %w", err)
}
err = syscall.Mount(containerPath, containerPath, "", syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_BIND, "")
err = syscall.Mount(imagePath, imagePath, "", syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_BIND, "")
if err != nil {
return fmt.Errorf("failed to remount ro: %w", err)
}
@@ -133,23 +133,23 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
if mntPnt.Copy() {
continue
}
wwlog.Debug("bind mounting: %s -> %s", mntPnt.Source, path.Join(containerPath, mntPnt.Dest))
err = syscall.Mount(mntPnt.Source, path.Join(containerPath, mntPnt.Dest), "", syscall.MS_BIND, "")
wwlog.Debug("bind mounting: %s -> %s", mntPnt.Source, path.Join(imagePath, mntPnt.Dest))
err = syscall.Mount(mntPnt.Source, path.Join(imagePath, mntPnt.Dest), "", syscall.MS_BIND, "")
if err != nil {
wwlog.Warn("Couldn't mount %s to %s: %s", mntPnt.Source, mntPnt.Dest, err)
} else if mntPnt.ReadOnly() {
err = syscall.Mount(mntPnt.Source, path.Join(containerPath, mntPnt.Dest), "", syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_BIND, "")
err = syscall.Mount(mntPnt.Source, path.Join(imagePath, mntPnt.Dest), "", syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_BIND, "")
if err != nil {
wwlog.Warn("failed to following mount readonly: %s", mntPnt.Source)
} else {
wwlog.Verbose("mounted readonly from host to container: %s:%s", mntPnt.Source, mntPnt.Dest)
wwlog.Verbose("mounted readonly from host to image: %s:%s", mntPnt.Source, mntPnt.Dest)
}
} else {
wwlog.Verbose("mounted from host to container: %s:%s", mntPnt.Source, mntPnt.Dest)
wwlog.Verbose("mounted from host to image: %s:%s", mntPnt.Source, mntPnt.Dest)
}
}
err = syscall.Chroot(containerPath)
err = syscall.Chroot(imagePath)
if err != nil {
return fmt.Errorf("failed to chroot: %w", err)
}
@@ -181,10 +181,10 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
}
/*
Check if the bind mount points exists in the given container. Returns
Check if the bind mount points exists in the given image. Returns
the invalid mount points. Directories always have '/' as suffix
*/
func checkMountPoints(containerName string, binds []*warewulfconf.MountEntry) (overlayObjects []string) {
func checkMountPoints(imageName string, binds []*warewulfconf.MountEntry) (overlayObjects []string) {
overlayObjects = []string{}
for _, b := range binds {
if b.Copy() {
@@ -192,17 +192,17 @@ func checkMountPoints(containerName string, binds []*warewulfconf.MountEntry) (o
}
_, err := os.Stat(b.Source)
if err != nil {
wwlog.Debug("Couldn't stat %s create no mount point in container", b.Source)
wwlog.Debug("Couldn't stat %s create no mount point in image", b.Source)
continue
}
wwlog.Debug("Checking in container for %s", path.Join(container.RootFsDir(containerName), b.Dest))
if _, err = os.Stat(path.Join(container.RootFsDir(containerName), b.Dest)); err != nil {
wwlog.Debug("Checking in image for %s", path.Join(image.RootFsDir(imageName), b.Dest))
if _, err = os.Stat(path.Join(image.RootFsDir(imageName), b.Dest)); err != nil {
if os.IsNotExist(err) {
if util.IsDir(b.Dest) && !strings.HasSuffix(b.Dest, "/") {
b.Dest += "/"
}
overlayObjects = append(overlayObjects, b.Dest)
wwlog.Debug("Container %s, needs following path: %s", containerName, b.Dest)
wwlog.Debug("Image %s, needs following path: %s", imageName, b.Dest)
}
}
}

View File

@@ -14,7 +14,7 @@ import (
warewulfconf "github.com/warewulf/warewulf/internal/pkg/config"
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/image"
"github.com/warewulf/warewulf/internal/pkg/util"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
@@ -33,15 +33,15 @@ func runChildCmd(cmd *cobra.Command, args []string) error {
var childCommandFunc = runChildCmd
// Fork a child process with a new PID space
func runContainedCmd(cmd *cobra.Command, containerName string, args []string) (err error) {
func runContainedCmd(cmd *cobra.Command, imageName string, args []string) (err error) {
wwlog.Debug("runContainedCmd:args: %v", args)
conf := warewulfconf.Get()
runDir := container.RunDir(containerName)
runDir := image.RunDir(imageName)
if err := os.Mkdir(runDir, 0750); err != nil {
if _, existerr := os.Stat(runDir); !os.IsNotExist(existerr) {
return fmt.Errorf("run directory already exists: another container command may already be running (otherwise, remove %s)", runDir)
return fmt.Errorf("run directory already exists: another image command may already be running (otherwise, remove %s)", runDir)
} else {
return fmt.Errorf("unable to create run directory: %w", err)
}
@@ -53,8 +53,8 @@ func runContainedCmd(cmd *cobra.Command, containerName string, args []string) (e
}()
logStr := fmt.Sprint(wwlog.GetLogLevel())
childArgs := []string{"--warewulfconf", conf.GetWarewulfConf(), "--loglevel", logStr, "container", "exec", "__child"}
childArgs = append(childArgs, containerName)
childArgs := []string{"--warewulfconf", conf.GetWarewulfConf(), "--loglevel", logStr, "image", "exec", "__child"}
childArgs = append(childArgs, imageName)
for _, b := range binds {
childArgs = append(childArgs, "--bind", b)
}
@@ -63,27 +63,27 @@ func runContainedCmd(cmd *cobra.Command, containerName string, args []string) (e
}
childArgs = append(childArgs, "--")
childArgs = append(childArgs, args...)
// copy the files into the container at this stage, es in __child the
// copy the files into the image at this stage, es in __child the
// command syscall.Exec which replaces the __child process with the
// exec command in the container. All the mounts, have to be done in
// exec command in the image. All the mounts, have to be done in
// __child so that the used mounts don't propagate outside on the host
// (see the CLONE attributes), but as for the copy option we need
// to see if a file was modified after it was copied into the container
// to see if a file was modified after it was copied into the image
// so do this here.
// At first read out conf, the parse commandline, as copy files has the
// same synatx as mount points
mountPts := append(container.InitMountPnts(binds), conf.MountsContainer...)
mountPts := append(image.InitMountPnts(binds), conf.MountsImage...)
filesToCpy := getCopyFiles(mountPts)
for _, cpyFile := range filesToCpy {
if err := (cpyFile).copyToContainer(containerName); err != nil {
if err := (cpyFile).copyToImage(imageName); err != nil {
wwlog.Warn("couldn't copy file: %s", err)
}
}
wwlog.Verbose("Running contained command: %s", childArgs)
retVal := childCommandFunc(cmd, childArgs)
for _, cpyFile := range filesToCpy {
if cpyFile.shouldRemoveFromContainer(containerName) {
if err := cpyFile.removeFromContainer(containerName); err != nil {
if cpyFile.shouldRemoveFromImage(imageName) {
if err := cpyFile.removeFromImage(imageName); err != nil {
wwlog.Warn("couldn't remove file: %s", err)
}
}
@@ -94,36 +94,40 @@ func runContainedCmd(cmd *cobra.Command, containerName string, args []string) (e
func CobraRunE(cmd *cobra.Command, args []string) error {
wwlog.Debug("CobraRunE:args: %v", args)
containerName := args[0]
wwlog.Debug("CobraRunE:containerName: %v", containerName)
if !container.ValidSource(containerName) {
return fmt.Errorf("unknown Warewulf container: %s", containerName)
imageName := args[0]
wwlog.Debug("CobraRunE:imageName: %v", imageName)
if !image.ValidSource(imageName) {
return fmt.Errorf("unknown Warewulf image: %s", imageName)
}
os.Setenv("WW_CONTAINER_SHELL", containerName)
os.Setenv("WW_CONTAINER_SHELL", imageName)
os.Setenv("WW_IMAGE_SHELL", imageName)
containerPath := container.RootFsDir(containerName)
imagePath := image.RootFsDir(imageName)
beforePasswdTime := getTime(path.Join(containerPath, "/etc/passwd"))
beforePasswdTime := getTime(path.Join(imagePath, "/etc/passwd"))
wwlog.Debug("passwdTime: %v", beforePasswdTime)
beforeGroupTime := getTime(path.Join(containerPath, "/etc/group"))
beforeGroupTime := getTime(path.Join(imagePath, "/etc/group"))
wwlog.Debug("groupTime: %v", beforeGroupTime)
err := runContainedCmd(cmd, containerName, args[1:])
err := runContainedCmd(cmd, imageName, args[1:])
if err != nil {
return fmt.Errorf("failed executing container command: %s", err)
return fmt.Errorf("command returned an error: %v: %s", args[1:], err)
}
if util.IsFile(path.Join(containerPath, "/etc/warewulf/container_exit.sh")) {
wwlog.Verbose("Found clean script: /etc/warewulf/container_exit.sh")
err = runContainedCmd(cmd, containerName, []string{"/bin/sh", "/etc/warewulf/container_exit.sh"})
if err != nil {
return fmt.Errorf("failed executing exit script: %s", err)
for _, exitScript := range []string{"/etc/warewulf/image_exit.sh", "/etc/warewulf/container_exit.sh"} {
if util.IsFile(path.Join(imagePath, exitScript)) {
wwlog.Verbose("Found exit script: %s", exitScript)
err = runContainedCmd(cmd, imageName, []string{"/bin/sh", exitScript})
if err != nil {
return fmt.Errorf("exit script returned an error: %v: %s", exitScript, err)
}
break
}
}
userdbChanged := false
if !beforePasswdTime.IsZero() {
afterPasswdTime := getTime(path.Join(containerPath, "/etc/passwd"))
afterPasswdTime := getTime(path.Join(imagePath, "/etc/passwd"))
wwlog.Debug("passwdTime: %v", afterPasswdTime)
if beforePasswdTime.Before(afterPasswdTime) {
if !SyncUser {
@@ -133,7 +137,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
}
}
if !beforeGroupTime.IsZero() {
afterGroupTime := getTime(path.Join(containerPath, "/etc/group"))
afterGroupTime := getTime(path.Join(imagePath, "/etc/group"))
wwlog.Debug("groupTime: %v", afterGroupTime)
if beforeGroupTime.Before(afterGroupTime) {
if !SyncUser {
@@ -143,17 +147,17 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
}
}
if userdbChanged && SyncUser {
err = container.SyncUids(containerName, false)
err = image.SyncUids(imageName, false)
if err != nil {
wwlog.Error("Error in user sync, fix error and run 'syncuser' manually: %s", err)
}
}
if Build {
wwlog.Info("Building container image: %s", containerName)
err = container.Build(containerName, false)
wwlog.Info("Building image: %s", imageName)
err = image.Build(imageName, false)
if err != nil {
return fmt.Errorf("could not build container image: %s: %s", containerName, err)
return fmt.Errorf("could not build image: %s: %s", imageName, err)
}
}
return nil
@@ -183,21 +187,21 @@ type copyFile struct {
modTime time.Time
}
func (this *copyFile) containerDest(containerName string) string {
return path.Join(container.RootFsDir(containerName), this.fileName)
func (this *copyFile) imageDest(imageName string) string {
return path.Join(image.RootFsDir(imageName), this.fileName)
}
func (this *copyFile) copyToContainer(containerName string) error {
containerDest := this.containerDest(containerName)
if _, err := os.Stat(path.Dir(containerDest)); err != nil {
func (this *copyFile) copyToImage(imageName string) error {
imageDest := this.imageDest(imageName)
if _, err := os.Stat(path.Dir(imageDest)); err != nil {
return err
} else if _, err := os.Stat(containerDest); err == nil {
} else if _, err := os.Stat(imageDest); err == nil {
return err
} else if _, err := os.Stat(this.src); err != nil {
return err
} else if err := util.CopyFile(this.src, containerDest); err != nil {
} else if err := util.CopyFile(this.src, imageDest); err != nil {
return err
} else if stat, err := os.Stat(containerDest); err != nil {
} else if stat, err := os.Stat(imageDest); err != nil {
return err
} else {
this.modTime = stat.ModTime()
@@ -205,12 +209,12 @@ func (this *copyFile) copyToContainer(containerName string) error {
}
}
func (this *copyFile) shouldRemoveFromContainer(containerName string) bool {
containerDest := this.containerDest(containerName)
func (this *copyFile) shouldRemoveFromImage(imageName string) bool {
imageDest := this.imageDest(imageName)
if this.modTime.IsZero() {
wwlog.Debug("file was not previously copied: %s", this.fileName)
return false
} else if destStat, err := os.Stat(containerDest); err != nil {
} else if destStat, err := os.Stat(imageDest); err != nil {
wwlog.Verbose("file is no longer present: %s (%s)", this.fileName, err)
return false
} else if destStat.ModTime() == this.modTime {
@@ -221,9 +225,9 @@ func (this *copyFile) shouldRemoveFromContainer(containerName string) bool {
}
}
func (this *copyFile) removeFromContainer(containerName string) error {
containerDest := this.containerDest(containerName)
return os.Remove(containerDest)
func (this *copyFile) removeFromImage(imageName string) error {
imageDest := this.imageDest(imageName)
return os.Remove(imageDest)
}
/*

View File

@@ -40,37 +40,37 @@ func Test_Exec(t *testing.T) {
{
name: "plain test",
args: []string{"test", "/bin/true"},
stdout: `--loglevel 20 container exec __child test -- /bin/true`,
stdout: `--loglevel 20 image exec __child test -- /bin/true`,
build: true,
},
{
name: "test with --bind",
args: []string{"test", "--bind", "/tmp", "/bin/true"},
stdout: `--loglevel 20 container exec __child test --bind /tmp -- /bin/true`,
stdout: `--loglevel 20 image exec __child test --bind /tmp -- /bin/true`,
build: true,
},
{
name: "test with --node",
args: []string{"test", "--node", "node1", "/bin/true"},
stdout: `--loglevel 20 container exec __child test --node node1 -- /bin/true`,
stdout: `--loglevel 20 image exec __child test --node node1 -- /bin/true`,
build: true,
},
{
name: "test with --build=false",
args: []string{"test", "--build=false", "/bin/true"},
stdout: `--loglevel 20 container exec __child test -- /bin/true`,
stdout: `--loglevel 20 image exec __child test -- /bin/true`,
build: false,
},
{
name: "test with --node and --bind",
args: []string{"test", "--bind", "/tmp", "--node", "node1", "/bin/true"},
stdout: `--loglevel 20 container exec __child test --bind /tmp --node node1 -- /bin/true`,
stdout: `--loglevel 20 image exec __child test --bind /tmp --node node1 -- /bin/true`,
build: true,
},
{
name: "test with complex command",
args: []string{"test", "/bin/bash", "echo 'hello'"},
stdout: `--loglevel 20 container exec __child test -- /bin/bash echo 'hello'`,
stdout: `--loglevel 20 image exec __child test -- /bin/bash echo 'hello'`,
build: true,
},
}
@@ -82,8 +82,8 @@ func Test_Exec(t *testing.T) {
nodeName = ""
Build = true
SyncUser = false
os.Remove(env.GetPath("/srv/warewulf/container/test.img"))
os.Remove(env.GetPath("/srv/warewulf/container/test.img.gz"))
os.Remove(env.GetPath("/srv/warewulf/image/test.img"))
os.Remove(env.GetPath("/srv/warewulf/image/test.img.gz"))
}()
cmd := GetCommand()
cmd.SetArgs(tt.args)
@@ -95,11 +95,11 @@ func Test_Exec(t *testing.T) {
assert.NotEmpty(t, out.String())
assert.Contains(t, out.String(), tt.stdout)
if tt.build {
assert.FileExists(t, env.GetPath("/srv/warewulf/container/test.img"))
assert.FileExists(t, env.GetPath("/srv/warewulf/container/test.img.gz"))
assert.FileExists(t, env.GetPath("/srv/warewulf/image/test.img"))
assert.FileExists(t, env.GetPath("/srv/warewulf/image/test.img.gz"))
} else {
assert.NoFileExists(t, env.GetPath("/srv/warewulf/container/test.img"))
assert.NoFileExists(t, env.GetPath("/srv/warewulf/container/test.img.gz"))
assert.NoFileExists(t, env.GetPath("/srv/warewulf/image/test.img"))
assert.NoFileExists(t, env.GetPath("/srv/warewulf/image/test.img.gz"))
}
})
}

View File

@@ -2,25 +2,25 @@ package exec
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/container/exec/child"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/app/wwctl/image/exec/child"
"github.com/warewulf/warewulf/internal/pkg/image"
)
var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "exec [OPTIONS] CONTAINER COMMAND",
Short: "Run a command inside of a Warewulf container",
Long: "Run a COMMAND inside of a warewulf CONTAINER.\n" +
Use: "exec [OPTIONS] IMAGE COMMAND",
Short: "Run a command inside of a Warewulf image",
Long: "Run a COMMAND inside of a warewulf IMAGE.\n" +
"This is commonly used with an interactive shell such as /bin/bash\n" +
"to run a virtual environment within the container.",
"to run a virtual environment within the image.",
RunE: CobraRunE,
Args: cobra.MinimumNArgs(2),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
list, _ := container.ListSources()
list, _ := image.ListSources()
return list, cobra.ShellCompDirectiveNoFileComp
},
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
@@ -34,12 +34,12 @@ var (
func init() {
baseCmd.AddCommand(child.GetCommand())
baseCmd.PersistentFlags().StringArrayVarP(&binds, "bind", "b", []string{}, `source[:destination[:{ro|copy}]]
Bind a local path which must exist into the container. If destination is not
Bind a local path which must exist into the image. If destination is not
set, uses the same path as source. "ro" binds read-only. "copy" temporarily
copies the file into the container.`)
baseCmd.PersistentFlags().BoolVar(&SyncUser, "syncuser", false, "Synchronize UIDs/GIDs from host to container")
baseCmd.PersistentFlags().BoolVar(&Build, "build", true, "(Re)build the container image automatically")
baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the container for the given node")
copies the file into the image.`)
baseCmd.PersistentFlags().BoolVar(&SyncUser, "syncuser", false, "Synchronize UIDs/GIDs from host to image")
baseCmd.PersistentFlags().BoolVar(&Build, "build", true, "(Re)build the image automatically")
baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the image for the given node")
}
// GetRootCommand returns the root cobra.Command for the application.

View File

@@ -2,7 +2,7 @@ package imprt
import (
"github.com/spf13/cobra"
apicontainer "github.com/warewulf/warewulf/internal/pkg/api/container"
apiimage "github.com/warewulf/warewulf/internal/pkg/api/image"
"github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1"
)
@@ -14,7 +14,7 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
name = args[1]
}
cip := &wwapiv1.ContainerImportParameter{
cip := &wwapiv1.ImageImportParameter{
Source: args[0],
Name: name,
Force: SetForce,
@@ -27,6 +27,6 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
Platform: Platform,
}
_, err = apicontainer.ContainerImport(cip)
_, err = apiimage.ImageImport(cip)
return
}

View File

@@ -9,9 +9,9 @@ var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "import [OPTIONS] SOURCE [NAME]",
Short: "Import a container into Warewulf",
Short: "Import an image into Warewulf",
Aliases: []string{"pull"},
Long: `This command will pull and import a container into Warewulf from SOURCE,
Long: `This command will pull and import an image into Warewulf from SOURCE,
optionally renaming it to NAME. The SOURCE must be in a supported URI format. Formats
are:
* docker://registry.example.org/example:latest
@@ -19,8 +19,8 @@ are:
* file://path/to/archive/tar/ball
* /path/to/archive/tar/ball
* /path/to/chroot/
Imported containers are used to create bootable VNFS images.`,
Example: "wwctl container import docker://ghcr.io/warewulf/warewulf-rockylinux:8 rockylinux-8",
Imported images are used to create bootable images.`,
Example: "wwctl image import docker://ghcr.io/warewulf/warewulf-rockylinux:8 rockylinux-8",
RunE: CobraRunE,
Args: cobra.MinimumNArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
@@ -40,10 +40,10 @@ Imported containers are used to create bootable VNFS images.`,
)
func init() {
baseCmd.PersistentFlags().BoolVarP(&SetForce, "force", "f", false, "Force overwrite of an existing container")
baseCmd.PersistentFlags().BoolVarP(&SetUpdate, "update", "u", false, "Update and overwrite an existing container")
baseCmd.PersistentFlags().BoolVarP(&SetBuild, "build", "b", false, "Build container after pulling")
baseCmd.PersistentFlags().BoolVar(&SyncUser, "syncuser", false, "Synchronize UIDs/GIDs from host to container")
baseCmd.PersistentFlags().BoolVarP(&SetForce, "force", "f", false, "Force overwrite of an existing image")
baseCmd.PersistentFlags().BoolVarP(&SetUpdate, "update", "u", false, "Update and overwrite an existing image")
baseCmd.PersistentFlags().BoolVarP(&SetBuild, "build", "b", false, "Build image after pulling")
baseCmd.PersistentFlags().BoolVar(&SyncUser, "syncuser", false, "Synchronize UIDs/GIDs from host to image")
baseCmd.PersistentFlags().BoolVar(&OciNoHttps, "nohttps", false, "Ignore wrong TLS certificates, superseedes env WAREWULF_OCI_NOHTTPS")
baseCmd.PersistentFlags().StringVar(&OciUsername, "username", "", "Set username for the access to the registry, superseedes env WAREWULF_OCI_USERNAME")
baseCmd.PersistentFlags().StringVar(&OciPassword, "password", "", "Set password for the access to the registry, superseedes env WAREWULF_OCI_PASSWORD")

View File

@@ -6,7 +6,7 @@ import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/table"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/image"
"github.com/warewulf/warewulf/internal/pkg/kernel"
"github.com/warewulf/warewulf/internal/pkg/node"
)
@@ -30,7 +30,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
var sources []string
if len(args) == 0 {
if sources_, err := container.ListSources(); err == nil {
if sources_, err := image.ListSources(); err == nil {
sources = sources_
} else {
return err
@@ -40,16 +40,16 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
}
t := table.New(cmd.OutOrStdout())
t.AddHeader("Container", "Kernel", "Version", "Default", "Nodes")
t.AddHeader("Image", "Kernel", "Version", "Default", "Nodes")
for _, source := range sources {
containerKernels := kernel.FindKernels(source)
defaultKernel := containerKernels.Default()
for _, kernel_ := range containerKernels {
imageKernels := kernel.FindKernels(source)
defaultKernel := imageKernels.Default()
for _, kernel_ := range imageKernels {
isDefault := defaultKernel != nil && defaultKernel == kernel_
defaultStr := strconv.FormatBool(isDefault)
nodeCount := kernelNodes[*kernel_]
if isDefault {
nodeCount = nodeCount + kernelNodes[kernel.Kernel{ContainerName: source, Path: ""}]
nodeCount = nodeCount + kernelNodes[kernel.Kernel{ImageName: source, Path: ""}]
}
t.AddLine(table.Prep([]string{source, kernel_.Path, kernel_.Version(), defaultStr, strconv.Itoa(nodeCount)})...)
}

View File

@@ -21,18 +21,18 @@ func Test_List(t *testing.T) {
files: map[string][]string{},
args: []string{},
stdout: `
Container Kernel Version Default Nodes
--------- ------ ------- ------- -----
Image Kernel Version Default Nodes
----- ------ ------- ------- -----
`,
},
"list": {
files: map[string][]string{
"container1": []string{
"image1": []string{
"/boot/vmlinuz-5.14.0-427.18.1.el9_4.x86_64",
"/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64",
"/boot/vmlinuz-4.14.0-427.18.1.el8_4.x86_64",
},
"container2": []string{
"image2": []string{
"/boot/vmlinuz-0-rescue-eb46964329b146e39518c625feab3ea0",
"/boot/vmlinuz-5.14.0-362.24.1.el9_3.aarch64",
"/boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64+debug",
@@ -42,26 +42,26 @@ Container Kernel Version Default Nodes
},
args: []string{},
stdout: `
Container Kernel Version Default Nodes
--------- ------ ------- ------- -----
container1 /boot/vmlinuz-4.14.0-427.18.1.el8_4.x86_64 4.14.0-427.18.1 false 0
container1 /boot/vmlinuz-5.14.0-427.18.1.el9_4.x86_64 5.14.0-427.18.1 false 0
container1 /boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64 5.14.0-427.24.1 true 0
container2 /boot/vmlinuz-0-rescue-eb46964329b146e39518c625feab3ea0 -- false 0
container2 /boot/vmlinuz-5.14.0-284.30.1.el9_2.aarch64 5.14.0-284.30.1 false 0
container2 /boot/vmlinuz-5.14.0-362.24.1.el9_3.aarch64 5.14.0-362.24.1 false 0
container2 /boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64 5.14.0-427.31.1 true 0
container2 /boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64+debug 5.14.0-427.31.1 false 0
Image Kernel Version Default Nodes
----- ------ ------- ------- -----
image1 /boot/vmlinuz-4.14.0-427.18.1.el8_4.x86_64 4.14.0-427.18.1 false 0
image1 /boot/vmlinuz-5.14.0-427.18.1.el9_4.x86_64 5.14.0-427.18.1 false 0
image1 /boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64 5.14.0-427.24.1 true 0
image2 /boot/vmlinuz-0-rescue-eb46964329b146e39518c625feab3ea0 -- false 0
image2 /boot/vmlinuz-5.14.0-284.30.1.el9_2.aarch64 5.14.0-284.30.1 false 0
image2 /boot/vmlinuz-5.14.0-362.24.1.el9_3.aarch64 5.14.0-362.24.1 false 0
image2 /boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64 5.14.0-427.31.1 true 0
image2 /boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64+debug 5.14.0-427.31.1 false 0
`,
},
"single container": {
"single image": {
files: map[string][]string{
"container1": []string{
"image1": []string{
"/boot/vmlinuz-5.14.0-427.18.1.el9_4.x86_64",
"/boot/vmlinuz-5.14.0-427.24.1.el9_4.x86_64",
"/boot/vmlinuz-4.14.0-427.18.1.el8_4.x86_64",
},
"container2": []string{
"image2": []string{
"/boot/vmlinuz-0-rescue-eb46964329b146e39518c625feab3ea0",
"/boot/vmlinuz-5.14.0-362.24.1.el9_3.aarch64",
"/boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64+debug",
@@ -69,15 +69,15 @@ container2 /boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64+debug 5.14.0-427.
"/boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64",
},
},
args: []string{"container2"},
args: []string{"image2"},
stdout: `
Container Kernel Version Default Nodes
--------- ------ ------- ------- -----
container2 /boot/vmlinuz-0-rescue-eb46964329b146e39518c625feab3ea0 -- false 0
container2 /boot/vmlinuz-5.14.0-284.30.1.el9_2.aarch64 5.14.0-284.30.1 false 0
container2 /boot/vmlinuz-5.14.0-362.24.1.el9_3.aarch64 5.14.0-362.24.1 false 0
container2 /boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64 5.14.0-427.31.1 true 0
container2 /boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64+debug 5.14.0-427.31.1 false 0
Image Kernel Version Default Nodes
----- ------ ------- ------- -----
image2 /boot/vmlinuz-0-rescue-eb46964329b146e39518c625feab3ea0 -- false 0
image2 /boot/vmlinuz-5.14.0-284.30.1.el9_2.aarch64 5.14.0-284.30.1 false 0
image2 /boot/vmlinuz-5.14.0-362.24.1.el9_3.aarch64 5.14.0-362.24.1 false 0
image2 /boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64 5.14.0-427.31.1 true 0
image2 /boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64+debug 5.14.0-427.31.1 false 0
`,
},
}
@@ -86,8 +86,8 @@ container2 /boot/vmlinuz-5.14.0-427.31.1.el9_4.aarch64+debug 5.14.0-427.
t.Run(name, func(t *testing.T) {
env := testenv.New(t)
defer env.RemoveAll()
for container, files := range tt.files {
rootfs := filepath.Join(filepath.Join("/var/lib/warewulf/chroots", container), "rootfs")
for image, files := range tt.files {
rootfs := filepath.Join(filepath.Join("/var/lib/warewulf/chroots", image), "rootfs")
for _, file := range files {
env.CreateFile(filepath.Join(rootfs, file))
}

View File

@@ -10,11 +10,11 @@ var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "kernels [OPTIONS]",
Short: "List available container kernels",
Long: "This command lists the kernels that are available in the imported containers.",
Short: "List available image kernels",
Long: "This command lists the kernels that are available in the imported images.",
RunE: CobraRunE,
Aliases: []string{"kernel"},
ValidArgsFunction: completions.Containers,
ValidArgsFunction: completions.Images,
}
)

View File

@@ -0,0 +1,84 @@
package list
import (
"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/util"
)
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()
if err != nil {
return err
}
if vars.full {
t.AddHeader("IMAGE NAME", "NODES", "KERNEL VERSION", "CREATION TIME", "MODIFICATION TIME", "SIZE")
for i := 0; i < len(imageInfo); i++ {
createTime := time.Unix(int64(imageInfo[i].CreateDate), 0)
modTime := time.Unix(int64(imageInfo[i].ModDate), 0)
sz := util.ByteToString(int64(imageInfo[i].ImgSize))
if vars.compressed {
sz = util.ByteToString(int64(imageInfo[i].ImgSizeComp))
}
if vars.chroot {
sz = util.ByteToString(int64(imageInfo[i].Size))
}
t.AddLine(
imageInfo[i].Name,
strconv.FormatUint(uint64(imageInfo[i].NodeCount), 10),
imageInfo[i].KernelVersion,
createTime.Format(time.RFC822),
modTime.Format(time.RFC822),
sz,
)
}
} else if vars.kernel {
t.AddHeader("IMAGE NAME", "NODES", "KERNEL VERSION")
for i := 0; i < len(imageInfo); i++ {
t.AddLine(
imageInfo[i].Name,
strconv.FormatUint(uint64(imageInfo[i].NodeCount), 10),
imageInfo[i].KernelVersion,
)
}
} else if showSize {
t.AddHeader("IMAGE NAME", "NODES", "SIZE")
for i := 0; i < len(imageInfo); i++ {
sz := util.ByteToString(int64(imageInfo[i].ImgSize))
if vars.compressed {
sz = util.ByteToString(int64(imageInfo[i].ImgSizeComp))
}
if vars.chroot {
sz = util.ByteToString(int64(imageInfo[i].Size))
}
t.AddLine(
imageInfo[i].Name,
strconv.FormatUint(uint64(imageInfo[i].NodeCount), 10),
sz,
)
}
}
} else {
t.AddHeader("IMAGE NAME")
list, _ := image.ListSources()
for _, cont := range list {
t.AddLine(cont)
}
}
t.Print()
return
}
}

View File

@@ -22,12 +22,12 @@ func Test_List(t *testing.T) {
mockFunc func()
}{
{
name: "container list test",
name: "image list test",
args: []string{"-l"},
stdout: `
CONTAINER 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 KERNEL VERSION CREATION TIME MODIFICATION TIME SIZE
---------- ----- -------------- ------------- ----------------- ----
test 1 kernel 01 Jan 70 00:00 UTC 01 Jan 70 00:00 UTC 0 B
`,
inDb: `
nodeprofiles:
@@ -38,8 +38,8 @@ nodes:
- default
`,
mockFunc: func() {
containerList = func() (containerInfo []*wwapiv1.ContainerInfo, err error) {
containerInfo = append(containerInfo, &wwapiv1.ContainerInfo{
imageList = func() (imageInfo []*wwapiv1.ImageInfo, err error) {
imageInfo = append(imageInfo, &wwapiv1.ImageInfo{
Name: "test",
NodeCount: 1,
KernelVersion: "kernel",

View File

@@ -18,8 +18,8 @@ func GetCommand() *cobra.Command {
baseCmd := &cobra.Command{
DisableFlagsInUseLine: true,
Use: "list [OPTIONS]",
Short: "List imported Warewulf containers",
Long: "This command will show you the containers that are imported into Warewulf.",
Short: "List imported Warewulf images",
Long: "This command will show you the images that are imported into Warewulf.",
RunE: CobraRunE(&vars),
Aliases: []string{"ls"},
}

View File

@@ -0,0 +1,44 @@
package rename
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/wwlog"
)
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
if len(args) != 2 {
return fmt.Errorf("rename requires 2 arguments: %d provided", len(args))
}
crp := &wwapiv1.ImageRenameParameter{
ImageName: args[0],
TargetName: args[1],
Build: SetBuild,
}
if !image.DoesSourceExist(crp.ImageName) {
return fmt.Errorf("%s source dir does not exist", crp.ImageName)
}
if image.DoesSourceExist(crp.TargetName) {
return fmt.Errorf("an other image with the name %s already exists", crp.TargetName)
}
if !image.ValidName(crp.TargetName) {
return fmt.Errorf("image name contains illegal characters : %s", crp.TargetName)
}
err = api.ImageRename(crp)
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
}

View File

@@ -7,7 +7,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
containerList "github.com/warewulf/warewulf/internal/app/wwctl/container/list"
imageList "github.com/warewulf/warewulf/internal/app/wwctl/image/list"
"github.com/warewulf/warewulf/internal/pkg/testenv"
"github.com/warewulf/warewulf/internal/pkg/warewulfd"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
@@ -15,33 +15,33 @@ import (
func Test_Rename(t *testing.T) {
env := testenv.New(t)
env.WriteFile(path.Join(testenv.WWChrootdir, "test-container/rootfs/file"), `test`)
env.WriteFile(path.Join(testenv.WWChrootdir, "test-image/rootfs/file"), `test`)
defer env.RemoveAll()
warewulfd.SetNoDaemon()
// first we will verify that there is an existing container
t.Run("container list", func(t *testing.T) {
verifyContainerListOutput(t, "test-container")
// first we will verify that there is an existing image
t.Run("image list", func(t *testing.T) {
verifyImageListOutput(t, "test-image")
})
// then rename it
t.Run("container rename", func(t *testing.T) {
t.Run("image rename", func(t *testing.T) {
baseCmd := GetCommand()
baseCmd.SetOut(os.Stdout)
baseCmd.SetErr(os.Stdout)
baseCmd.SetArgs([]string{"test-container", "test-container-rename"})
baseCmd.SetArgs([]string{"test-image", "test-image-rename"})
err := baseCmd.Execute()
assert.NoError(t, err)
})
// retrieve again
t.Run("Container list", func(t *testing.T) {
verifyContainerListOutput(t, "test-container-rename")
t.Run("Image list", func(t *testing.T) {
verifyImageListOutput(t, "test-image-rename")
})
}
func verifyContainerListOutput(t *testing.T, content string) {
baseCmd := containerList.GetCommand()
func verifyImageListOutput(t *testing.T, content string) {
baseCmd := imageList.GetCommand()
buf := new(bytes.Buffer)
baseCmd.SetOut(buf)
baseCmd.SetErr(buf)

View File

@@ -2,22 +2,22 @@ package rename
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/image"
)
var baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "rename CONTAINER NEW_NAME",
Use: "rename IMAGE NEW_NAME",
Aliases: []string{"mv"},
Short: "Rename an existing container",
Long: "This command will rename an existing container.",
Short: "Rename an existing image",
Long: "This command will rename an existing image.",
RunE: CobraRunE,
Args: cobra.MinimumNArgs(2),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
list, _ := container.ListSources()
list, _ := image.ListSources()
return list, cobra.ShellCompDirectiveNoFileComp
},
}
@@ -25,7 +25,7 @@ var baseCmd = &cobra.Command{
var SetBuild bool
func init() {
baseCmd.PersistentFlags().BoolVarP(&SetBuild, "build", "b", false, "Build container after rename")
baseCmd.PersistentFlags().BoolVarP(&SetBuild, "build", "b", false, "Build image after rename")
}
// GetRootCommand returns the root cobra.Command for the application.

View File

@@ -0,0 +1,45 @@
package image
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/image/build"
"github.com/warewulf/warewulf/internal/app/wwctl/image/copy"
"github.com/warewulf/warewulf/internal/app/wwctl/image/delete"
"github.com/warewulf/warewulf/internal/app/wwctl/image/exec"
"github.com/warewulf/warewulf/internal/app/wwctl/image/imprt"
"github.com/warewulf/warewulf/internal/app/wwctl/image/kernels"
"github.com/warewulf/warewulf/internal/app/wwctl/image/list"
"github.com/warewulf/warewulf/internal/app/wwctl/image/rename"
"github.com/warewulf/warewulf/internal/app/wwctl/image/shell"
"github.com/warewulf/warewulf/internal/app/wwctl/image/show"
"github.com/warewulf/warewulf/internal/app/wwctl/image/syncuser"
)
var baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "image COMMAND [OPTIONS]",
Short: "Operating system image management",
Long: "Starting with version 4, Warewulf uses images to build the bootable\n" +
"node images. These commands will help you import, manage, and transform\n" +
"images into bootable Warewulf images.",
Aliases: []string{"vnfs", "container"},
}
func init() {
baseCmd.AddCommand(build.GetCommand())
baseCmd.AddCommand(list.GetCommand())
baseCmd.AddCommand(imprt.GetCommand())
baseCmd.AddCommand(exec.GetCommand())
baseCmd.AddCommand(shell.GetCommand())
baseCmd.AddCommand(delete.GetCommand())
baseCmd.AddCommand(show.GetCommand())
baseCmd.AddCommand(syncuser.GetCommand())
baseCmd.AddCommand(copy.GetCommand())
baseCmd.AddCommand(rename.GetCommand())
baseCmd.AddCommand(kernels.GetCommand())
}
// GetRootCommand returns the root cobra.Command for the application.
func GetCommand() *cobra.Command {
return baseCmd
}

View File

@@ -9,22 +9,22 @@ import (
"path"
"github.com/spf13/cobra"
cntexec "github.com/warewulf/warewulf/internal/app/wwctl/container/exec"
"github.com/warewulf/warewulf/internal/pkg/container"
cntexec "github.com/warewulf/warewulf/internal/app/wwctl/image/exec"
"github.com/warewulf/warewulf/internal/pkg/image"
"github.com/warewulf/warewulf/internal/pkg/wwlog"
)
func CobraRunE(cmd *cobra.Command, args []string) error {
containerName := args[0]
imageName := args[0]
var allargs []string
if !container.ValidSource(containerName) {
return fmt.Errorf("unknown Warewulf container: %s", containerName)
if !image.ValidSource(imageName) {
return fmt.Errorf("unknown Warewulf image: %s", imageName)
}
shellName := os.Getenv("SHELL")
if !container.ValidSource(containerName) {
return fmt.Errorf("unknown Warewulf container: %s", containerName)
if !image.ValidSource(imageName) {
return fmt.Errorf("unknown Warewulf image: %s", imageName)
}
var shells []string
if shellName == "" {
@@ -33,7 +33,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
shells = append(shells, shellName, "/bin/bash")
}
for _, s := range shells {
if _, err := os.Stat(path.Join(container.RootFsDir(containerName), s)); err == nil {
if _, err := os.Stat(path.Join(image.RootFsDir(imageName), s)); err == nil {
shellName = s
break
}
@@ -46,7 +46,7 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
cntexec.SyncUser = syncUser
cntexec.Build = build
if cntexec.Build {
wwlog.Info("Container image build will be skipped if the shell ends with a non-zero exit code.")
wwlog.Info("Image build will be skipped if the shell ends with a non-zero exit code.")
}
return cntexec.CobraRunE(cmd, allargs)
}

View File

@@ -2,15 +2,15 @@ package shell
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/image"
)
var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "shell [OPTIONS] CONTAINER",
Short: "Run a shell inside of a Warewulf container",
Long: "Run a interactive shell inside of a warewulf CONTAINER.\n",
Use: "shell [OPTIONS] IMAGE",
Short: "Run a shell inside of a Warewulf image",
Long: "Run a interactive shell inside of a warewulf IMAGE.\n",
Aliases: []string{"chroot"},
RunE: CobraRunE,
Args: cobra.MinimumNArgs(1),
@@ -18,7 +18,7 @@ var (
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
list, _ := container.ListSources()
list, _ := image.ListSources()
return list, cobra.ShellCompDirectiveNoFileComp
},
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
@@ -31,12 +31,12 @@ var (
func init() {
baseCmd.PersistentFlags().StringArrayVarP(&binds, "bind", "b", []string{}, `source[:destination[:{ro|copy}]]
Bind a local path which must exist into the container. If destination is not
Bind a local path which must exist into the image. If destination is not
set, uses the same path as source. "ro" binds read-only. "copy" temporarily
copies the file into the container.`)
baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the container for the given node")
baseCmd.PersistentFlags().BoolVar(&syncUser, "syncuser", false, "Synchronize UIDs/GIDs from host to container")
baseCmd.PersistentFlags().BoolVar(&build, "build", true, "(Re)build the container image automatically")
copies the file into the image.`)
baseCmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "Create a read only view of the image for the given node")
baseCmd.PersistentFlags().BoolVar(&syncUser, "syncuser", false, "Synchronize UIDs/GIDs from host to image")
baseCmd.PersistentFlags().BoolVar(&build, "build", true, "(Re)build the image automatically")
}
// GetRootCommand returns the root cobra.Command for the application.

View File

@@ -3,7 +3,7 @@ package show
import (
"fmt"
"github.com/warewulf/warewulf/internal/pkg/api/container"
"github.com/warewulf/warewulf/internal/pkg/api/image"
"github.com/warewulf/warewulf/internal/pkg/api/routes/wwapiv1"
"github.com/spf13/cobra"
@@ -11,12 +11,12 @@ import (
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
csp := &wwapiv1.ContainerShowParameter{
ContainerName: args[0],
csp := &wwapiv1.ImageShowParameter{
ImageName: args[0],
}
var r *wwapiv1.ContainerShowResponse
r, err = container.ContainerShow(csp)
var r *wwapiv1.ImageShowResponse
r, err = image.ImageShow(csp)
if err != nil {
return
}

View File

@@ -2,22 +2,22 @@ package show
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/image"
)
var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "show [OPTIONS] CONTAINER",
Short: "Show root fs dir for container",
Long: `Shows the base directory for the chroot of the given container.
More information about the container can be shown with the '-a' option.`,
Use: "show [OPTIONS] IMAGE",
Short: "Show root fs dir for image",
Long: `Shows the base directory for the chroot of the given image.
More information about the image can be shown with the '-a' option.`,
RunE: CobraRunE,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
list, _ := container.ListSources()
list, _ := image.ListSources()
return list, cobra.ShellCompDirectiveNoFileComp
},
@@ -27,7 +27,7 @@ More information about the container can be shown with the '-a' option.`,
)
func init() {
baseCmd.PersistentFlags().BoolVarP(&ShowAll, "all", "a", false, "Show all information about a container")
baseCmd.PersistentFlags().BoolVarP(&ShowAll, "all", "a", false, "Show all information about an image")
}

View File

@@ -0,0 +1,40 @@
package syncuser
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"
)
func CobraRunE(cmd *cobra.Command, args []string) error {
imageName := args[0]
if !image.ValidName(imageName) {
return fmt.Errorf("%s is not a valid image", imageName)
}
err := image.SyncUids(imageName, write)
if err != nil {
return fmt.Errorf("error in synchronize: %s", err)
}
if write && !build {
// when write = true and build = false, we will print a warnning, this is the default case
wwlog.Warn("Syncuser is completed, please remember to 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 {
return fmt.Errorf("error during image build: %s", err)
}
}
return nil
}

View File

@@ -2,23 +2,23 @@ package syncuser
import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/pkg/image"
)
var (
baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "syncuser [OPTIONS] CONTAINER",
Short: "Synchronizes user in container",
Long: `Synchronize the uids and gids from the host to the container.
Users/groups which are only present in the container will be preserved if no
Use: "syncuser [OPTIONS] IMAGE",
Short: "Synchronizes user in image",
Long: `Synchronize the uids and gids from the host to the image.
Users/groups which are only present in the image will be preserved if no
uid/gid collision is detected. File ownerships are also changed.`,
RunE: CobraRunE,
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
list, _ := container.ListSources()
list, _ := image.ListSources()
return list, cobra.ShellCompDirectiveNoFileComp
},
@@ -29,8 +29,8 @@ uid/gid collision is detected. File ownerships are also changed.`,
)
func init() {
baseCmd.PersistentFlags().BoolVar(&write, "write", false, "Synchronize uis/gids and write files in container")
baseCmd.PersistentFlags().BoolVar(&build, "build", false, "Build container after syncuser is completed")
baseCmd.PersistentFlags().BoolVar(&write, "write", false, "Synchronize uis/gids and write files in image")
baseCmd.PersistentFlags().BoolVar(&build, "build", false, "Build image after syncuser is completed")
}
// GetRootCommand returns the root cobra.Command for the application.

View File

@@ -5,7 +5,8 @@ import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/app/wwctl/flags"
"github.com/warewulf/warewulf/internal/pkg/image"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/overlay"
)
@@ -31,9 +32,10 @@ func GetCommand() *cobra.Command {
}
vars.nodeConf.CreateFlags(baseCmd)
vars.nodeAdd.CreateAddFlags(baseCmd)
flags.AddContainer(baseCmd, &(vars.nodeConf.Profile.ImageName))
// register the command line completions
if err := baseCmd.RegisterFlagCompletionFunc("container", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := container.ListSources()
if err := baseCmd.RegisterFlagCompletionFunc("image", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := image.ListSources()
return list, cobra.ShellCompDirectiveNoFileComp
}); err != nil {
log.Println(err)

View File

@@ -271,9 +271,9 @@ nodes:
args: []string{"-l"},
wantErr: false,
stdout: `
NODE NAME KERNEL VERSION CONTAINER OVERLAYS (S/R)
--------- -------------- --------- --------------
n01 -- -- /rop1,rop2
NODE NAME KERNEL VERSION IMAGE OVERLAYS (S/R)
--------- -------------- ----- --------------
n01 -- -- /rop1,rop2
`,
inDb: `nodeprofiles:
p1:
@@ -291,9 +291,9 @@ nodes:
args: []string{"-l"},
wantErr: false,
stdout: `
NODE NAME KERNEL VERSION CONTAINER OVERLAYS (S/R)
--------- -------------- --------- --------------
n01 -- -- sop1/rop1,rop2,nop1,~rop1
NODE NAME KERNEL VERSION IMAGE OVERLAYS (S/R)
--------- -------------- ----- --------------
n01 -- -- sop1/rop1,rop2,nop1,~rop1
`,
inDb: `nodeprofiles:
p1:
@@ -453,7 +453,7 @@ nodes:
],
"Comment": "",
"ClusterName": "",
"ContainerName": "",
"ImageName": "",
"Ipxe": "",
"RuntimeOverlay": null,
"SystemOverlay": null,
@@ -492,7 +492,7 @@ nodes:
],
"Comment": "",
"ClusterName": "",
"ContainerName": "",
"ImageName": "",
"Ipxe": "",
"RuntimeOverlay": null,
"SystemOverlay": null,
@@ -515,7 +515,7 @@ nodes:
],
"Comment": "",
"ClusterName": "",
"ContainerName": "",
"ImageName": "",
"Ipxe": "",
"RuntimeOverlay": null,
"SystemOverlay": null,

View File

@@ -5,7 +5,8 @@ import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/app/wwctl/flags"
"github.com/warewulf/warewulf/internal/pkg/image"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/overlay"
)
@@ -44,12 +45,13 @@ func GetCommand() *cobra.Command {
vars.nodeConf.CreateFlags(baseCmd)
vars.nodeAdd.CreateAddFlags(baseCmd)
vars.nodeDel.CreateDelFlags(baseCmd)
flags.AddContainer(baseCmd, &(vars.nodeConf.Profile.ImageName))
baseCmd.PersistentFlags().BoolVarP(&vars.setNodeAll, "all", "a", false, "Set all nodes")
baseCmd.PersistentFlags().BoolVarP(&vars.setYes, "yes", "y", false, "Set 'yes' to all questions asked")
baseCmd.PersistentFlags().BoolVarP(&vars.setForce, "force", "f", false, "Force configuration (even on error)")
// register the command line completions
if err := baseCmd.RegisterFlagCompletionFunc("container", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := container.ListSources()
if err := baseCmd.RegisterFlagCompletionFunc("image", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := image.ListSources()
return list, cobra.ShellCompDirectiveNoFileComp
}); err != nil {
log.Println(err)

View File

@@ -17,7 +17,7 @@ func Benchmark_Overlay_Build(b *testing.B) {
`nodeprofiles:
default:
comment: This profile is automatically included for each node
container name: rockylinux-9
image name: rockylinux-9
ipxe template: default
runtime overlay:
- hosts

View File

@@ -5,7 +5,8 @@ import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/app/wwctl/flags"
"github.com/warewulf/warewulf/internal/pkg/image"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/overlay"
)
@@ -30,9 +31,10 @@ func GetCommand() *cobra.Command {
}
vars.profileConf.CreateFlags(baseCmd)
vars.profileAdd.CreateAddFlags(baseCmd)
flags.AddContainer(baseCmd, &(vars.profileConf.ImageName))
// register the command line completions
if err := baseCmd.RegisterFlagCompletionFunc("container", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := container.ListSources()
if err := baseCmd.RegisterFlagCompletionFunc("image", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := image.ListSources()
return list, cobra.ShellCompDirectiveNoFileComp
}); err != nil {
log.Println(err)

View File

@@ -157,7 +157,7 @@ nodes:
"Profiles": null,
"Comment": "",
"ClusterName": "",
"ContainerName": "",
"ImageName": "",
"Ipxe": "",
"RuntimeOverlay": null,
"SystemOverlay": null,
@@ -209,7 +209,7 @@ nodes:
"Profiles": null,
"Comment": "",
"ClusterName": "",
"ContainerName": "",
"ImageName": "",
"Ipxe": "",
"RuntimeOverlay": null,
"SystemOverlay": null,
@@ -228,7 +228,7 @@ nodes:
"Profiles": null,
"Comment": "",
"ClusterName": "",
"ContainerName": "",
"ImageName": "",
"Ipxe": "",
"RuntimeOverlay": null,
"SystemOverlay": null,

View File

@@ -5,7 +5,8 @@ import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/completions"
"github.com/warewulf/warewulf/internal/pkg/container"
"github.com/warewulf/warewulf/internal/app/wwctl/flags"
"github.com/warewulf/warewulf/internal/pkg/image"
"github.com/warewulf/warewulf/internal/pkg/node"
"github.com/warewulf/warewulf/internal/pkg/overlay"
)
@@ -51,10 +52,11 @@ func GetCommand() *cobra.Command {
vars.profileConf.CreateFlags(baseCmd)
vars.profileDel.CreateDelFlags(baseCmd)
vars.profileAdd.CreateAddFlags(baseCmd)
flags.AddContainer(baseCmd, &(vars.profileConf.ImageName))
baseCmd.PersistentFlags().BoolVarP(&vars.setYes, "yes", "y", false, "Set 'yes' to all questions asked")
// register the command line completions
if err := baseCmd.RegisterFlagCompletionFunc("container", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := container.ListSources()
if err := baseCmd.RegisterFlagCompletionFunc("image", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, _ := image.ListSources()
return list, cobra.ShellCompDirectiveNoFileComp
}); err != nil {
log.Println(err)

View File

@@ -6,8 +6,8 @@ import (
"github.com/spf13/cobra"
"github.com/warewulf/warewulf/internal/app/wwctl/clean"
"github.com/warewulf/warewulf/internal/app/wwctl/configure"
"github.com/warewulf/warewulf/internal/app/wwctl/container"
"github.com/warewulf/warewulf/internal/app/wwctl/genconf"
"github.com/warewulf/warewulf/internal/app/wwctl/image"
"github.com/warewulf/warewulf/internal/app/wwctl/node"
"github.com/warewulf/warewulf/internal/app/wwctl/overlay"
"github.com/warewulf/warewulf/internal/app/wwctl/power"
@@ -49,7 +49,7 @@ func init() {
rootCmd.SetUsageTemplate(help.UsageTemplate)
rootCmd.SetHelpTemplate(help.HelpTemplate)
rootCmd.AddCommand(overlay.GetCommand())
rootCmd.AddCommand(container.GetCommand())
rootCmd.AddCommand(image.GetCommand())
rootCmd.AddCommand(node.GetCommand())
rootCmd.AddCommand(power.GetCommand())
rootCmd.AddCommand(profile.GetCommand())