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:
@@ -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:
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
10
internal/app/wwctl/flags/flags.go
Normal file
10
internal/app/wwctl/flags/flags.go
Normal 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
|
||||
}
|
||||
20
internal/app/wwctl/image/build/build_test.go
Normal file
20
internal/app/wwctl/image/build/build_test.go
Normal 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.")
|
||||
}
|
||||
}
|
||||
16
internal/app/wwctl/image/build/main.go
Normal file
16
internal/app/wwctl/image/build/main.go
Normal 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)
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
50
internal/app/wwctl/image/copy/main.go
Normal file
50
internal/app/wwctl/image/copy/main.go
Normal 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
|
||||
}
|
||||
@@ -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"))
|
||||
})
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -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"))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
@@ -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)})...)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
84
internal/app/wwctl/image/list/main.go
Normal file
84
internal/app/wwctl/image/list/main.go
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
@@ -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"},
|
||||
}
|
||||
44
internal/app/wwctl/image/rename/main.go
Normal file
44
internal/app/wwctl/image/rename/main.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
45
internal/app/wwctl/image/root.go
Normal file
45
internal/app/wwctl/image/root.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
}
|
||||
|
||||
40
internal/app/wwctl/image/syncuser/main.go
Normal file
40
internal/app/wwctl/image/syncuser/main.go
Normal 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
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user