Add container rename support

Signed-off-by: jason yang <jasonyangshadow@gmail.com>
This commit is contained in:
jason yang
2023-12-21 23:32:10 -07:00
committed by Jonathon Anderson
parent 7d17bcc6fa
commit d262ec10ef
10 changed files with 822 additions and 385 deletions

View File

@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- OpenSUSE Leap build rebased to 15.5 (15.3 is EOL) - OpenSUSE Leap build rebased to 15.5 (15.3 is EOL)
- New build for Rocky Linux 9 - New build for Rocky Linux 9
- Add nightly release support. #969 - Add nightly release support. #969
- Add container rename command. #583
### Fixed ### Fixed
- Make Variables.mk consistent with spec file w.r.t. WWPROVISIONDIR, e.g. - Make Variables.mk consistent with spec file w.r.t. WWPROVISIONDIR, e.g.

View File

@@ -0,0 +1,45 @@
package rename
import (
"fmt"
api "github.com/hpcng/warewulf/internal/pkg/api/container"
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
"github.com/hpcng/warewulf/internal/pkg/container"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/spf13/cobra"
)
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
if len(args) > 2 {
wwlog.Warn("rename only requires 2 arguments but you provided %d arguments. Hence, they will be ignored.", 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) {
wwlog.Error("Container name contains illegal characters : %s", crp.TargetName)
return
}
err = api.ContainerRename(crp)
if err != nil {
err = fmt.Errorf("could not rename image: %s", err.Error())
return
}
wwlog.Info("Container %s successfully renamed as %s", crp.ContainerName, crp.TargetName)
return
}

View File

@@ -0,0 +1,63 @@
package rename
import (
"bytes"
"io"
"os"
"path"
"testing"
containerList "github.com/hpcng/warewulf/internal/app/wwctl/container/list"
"github.com/hpcng/warewulf/internal/pkg/testenv"
"github.com/hpcng/warewulf/internal/pkg/warewulfd"
"github.com/stretchr/testify/assert"
)
func Test_Rename(t *testing.T) {
env := testenv.New(t)
env.WriteFile(t, path.Join(testenv.WWChrootdir, "test-container/rootfs/file"), `test`)
defer env.RemoveAll(t)
warewulfd.SetNoDaemon()
// first we will verify that there is an existing container
t.Run("container list", func(t *testing.T) {
verifyContainerListOutput(t, "test-container")
})
// then rename it
t.Run("container rename", func(t *testing.T) {
baseCmd := GetCommand()
baseCmd.SetOut(nil)
baseCmd.SetErr(nil)
baseCmd.SetArgs([]string{"test-container", "test-container-rename"})
err := baseCmd.Execute()
assert.NoError(t, err)
})
// retrieve again
t.Run("Container list", func(t *testing.T) {
verifyContainerListOutput(t, "test-container-rename")
})
}
func verifyContainerListOutput(t *testing.T, content string) {
baseCmd := containerList.GetCommand()
baseCmd.SetOut(nil)
baseCmd.SetErr(nil)
stdoutR, stdoutW, _ := os.Pipe()
os.Stdout = stdoutW
err := baseCmd.Execute()
assert.NoError(t, err)
stdoutC := make(chan string)
go func() {
var buf bytes.Buffer
_, _ = io.Copy(&buf, stdoutR)
stdoutC <- buf.String()
}()
stdoutW.Close()
stdout := <-stdoutC
assert.NotEmpty(t, stdout, "output should not be empty")
assert.Contains(t, stdout, content)
}

View File

@@ -0,0 +1,35 @@
package rename
import (
"github.com/hpcng/warewulf/internal/pkg/container"
"github.com/spf13/cobra"
)
var baseCmd = &cobra.Command{
DisableFlagsInUseLine: true,
Use: "rename CONTAINER NEW_NAME",
Aliases: []string{"rn"},
Short: "Rename an existing container",
Long: "This command will rename an existing container.",
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()
return list, cobra.ShellCompDirectiveNoFileComp
},
}
var SetBuild bool
func init() {
// Nothing to do here
baseCmd.PersistentFlags().BoolVarP(&SetBuild, "build", "b", false, "Build container after rename")
}
// GetRootCommand returns the root cobra.Command for the application.
func GetCommand() *cobra.Command {
return baseCmd
}

View File

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

View File

@@ -404,6 +404,69 @@ func ContainerShow(csp *wwapiv1.ContainerShowParameter) (response *wwapiv1.Conta
return return
} }
func ContainerRename(crp *wwapiv1.ContainerRenameParameter) (err error) {
// rename the container source folder
sourceDir := container.SourceDir(crp.ContainerName)
destDir := container.SourceDir(crp.TargetName)
err = os.Rename(sourceDir, destDir)
if err != nil {
return err
}
if crp.Build {
err = container.Build(crp.TargetName, true)
if err != nil {
return err
}
}
// update the nodes profiles container name
nodeDB, err := node.New()
if err != nil {
return err
}
nodes, err := nodeDB.FindAllNodes()
if err != nil {
return err
}
for _, node := range nodes {
if node.ContainerName.Get() == crp.ContainerName {
node.ContainerName.Set(crp.TargetName)
if err := nodeDB.NodeUpdate(node); err != nil {
return err
}
}
}
profiles, err := nodeDB.FindAllProfiles()
if err != nil {
return err
}
for _, profile := range profiles {
if profile.ContainerName.Get() == crp.ContainerName {
profile.ContainerName.Set(crp.TargetName)
if err := nodeDB.ProfileUpdate(profile); err != nil {
return err
}
}
}
err = nodeDB.Persist()
if err != nil {
return err
}
err = warewulfd.DaemonStatus()
if err != nil {
// warewulfd is not running, skip
return nil
}
// else reload daemon to apply new changes
return warewulfd.DaemonReload()
}
// Private helpers // Private helpers
func setOCICredentials(sCtx *types.SystemContext) error { func setOCICredentials(sCtx *types.SystemContext) error {

View File

@@ -84,6 +84,13 @@ message ContainerSyncUserParameter {
string containerName = 1; string containerName = 1;
} }
// ContainerRenameParameter is the input for ContainerRename
message ContainerRenameParameter {
string containerName = 1;
string targetName = 2;
bool build = 3;
}
// Nodes // Nodes
// NodeNames is an array of node ids. // NodeNames is an array of node ids.
@@ -275,6 +282,14 @@ service WWApi {
}; };
} }
// ContainerRename renames the container
rpc ContainerRename(ContainerRenameParameter) returns (google.protobuf.Empty) {
option (google.api.http) = {
post: "/v1/containerrename"
body: "*"
};
}
// Nodes // Nodes
// NodeAdd adds one or more nodes for management by Warewulf and returns // NodeAdd adds one or more nodes for management by Warewulf and returns

File diff suppressed because it is too large Load Diff

View File

@@ -224,6 +224,40 @@ func local_request_WWApi_ContainerShow_0(ctx context.Context, marshaler runtime.
} }
func request_WWApi_ContainerRename_0(ctx context.Context, marshaler runtime.Marshaler, client WWApiClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ContainerRenameParameter
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ContainerRename(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WWApi_ContainerRename_0(ctx context.Context, marshaler runtime.Marshaler, server WWApiServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ContainerRenameParameter
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ContainerRename(ctx, &protoReq)
return msg, metadata, err
}
func request_WWApi_NodeAdd_0(ctx context.Context, marshaler runtime.Marshaler, client WWApiClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { func request_WWApi_NodeAdd_0(ctx context.Context, marshaler runtime.Marshaler, client WWApiClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq NodeAddParameter var protoReq NodeAddParameter
var metadata runtime.ServerMetadata var metadata runtime.ServerMetadata
@@ -574,6 +608,31 @@ func RegisterWWApiHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
}) })
mux.Handle("POST", pattern_WWApi_ContainerRename_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerRename", runtime.WithHTTPPathPattern("/v1/containerrename"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WWApi_ContainerRename_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_ContainerRename_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WWApi_NodeAdd_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("POST", pattern_WWApi_NodeAdd_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
@@ -897,6 +956,28 @@ func RegisterWWApiHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
}) })
mux.Handle("POST", pattern_WWApi_ContainerRename_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/wwapi.v1.WWApi/ContainerRename", runtime.WithHTTPPathPattern("/v1/containerrename"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WWApi_ContainerRename_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_WWApi_ContainerRename_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WWApi_NodeAdd_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("POST", pattern_WWApi_NodeAdd_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
@@ -1045,6 +1126,8 @@ var (
pattern_WWApi_ContainerShow_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "containershow"}, "")) pattern_WWApi_ContainerShow_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "containershow"}, ""))
pattern_WWApi_ContainerRename_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "containerrename"}, ""))
pattern_WWApi_NodeAdd_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "node"}, "")) pattern_WWApi_NodeAdd_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "node"}, ""))
pattern_WWApi_NodeDelete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "node"}, "")) pattern_WWApi_NodeDelete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "node"}, ""))
@@ -1071,6 +1154,8 @@ var (
forward_WWApi_ContainerShow_0 = runtime.ForwardResponseMessage forward_WWApi_ContainerShow_0 = runtime.ForwardResponseMessage
forward_WWApi_ContainerRename_0 = runtime.ForwardResponseMessage
forward_WWApi_NodeAdd_0 = runtime.ForwardResponseMessage forward_WWApi_NodeAdd_0 = runtime.ForwardResponseMessage
forward_WWApi_NodeDelete_0 = runtime.ForwardResponseMessage forward_WWApi_NodeDelete_0 = runtime.ForwardResponseMessage

View File

@@ -34,6 +34,8 @@ type WWApiClient interface {
ContainerList(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ContainerListResponse, error) ContainerList(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ContainerListResponse, error)
// ContainerShow lists ContainerShow for each container. // ContainerShow lists ContainerShow for each container.
ContainerShow(ctx context.Context, in *ContainerShowParameter, opts ...grpc.CallOption) (*ContainerShowResponse, error) ContainerShow(ctx context.Context, in *ContainerShowParameter, opts ...grpc.CallOption) (*ContainerShowResponse, error)
// ContainerRename renames the container
ContainerRename(ctx context.Context, in *ContainerRenameParameter, opts ...grpc.CallOption) (*emptypb.Empty, error)
// NodeAdd adds one or more nodes for management by Warewulf and returns // NodeAdd adds one or more nodes for management by Warewulf and returns
// the added nodes. Node fields may be shimmed in per profiles. // the added nodes. Node fields may be shimmed in per profiles.
NodeAdd(ctx context.Context, in *NodeAddParameter, opts ...grpc.CallOption) (*NodeListResponse, error) NodeAdd(ctx context.Context, in *NodeAddParameter, opts ...grpc.CallOption) (*NodeListResponse, error)
@@ -113,6 +115,15 @@ func (c *wWApiClient) ContainerShow(ctx context.Context, in *ContainerShowParame
return out, nil return out, nil
} }
func (c *wWApiClient) ContainerRename(ctx context.Context, in *ContainerRenameParameter, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, "/wwapi.v1.WWApi/ContainerRename", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *wWApiClient) NodeAdd(ctx context.Context, in *NodeAddParameter, opts ...grpc.CallOption) (*NodeListResponse, error) { func (c *wWApiClient) NodeAdd(ctx context.Context, in *NodeAddParameter, opts ...grpc.CallOption) (*NodeListResponse, error) {
out := new(NodeListResponse) out := new(NodeListResponse)
err := c.cc.Invoke(ctx, "/wwapi.v1.WWApi/NodeAdd", in, out, opts...) err := c.cc.Invoke(ctx, "/wwapi.v1.WWApi/NodeAdd", in, out, opts...)
@@ -182,6 +193,8 @@ type WWApiServer interface {
ContainerList(context.Context, *emptypb.Empty) (*ContainerListResponse, error) ContainerList(context.Context, *emptypb.Empty) (*ContainerListResponse, error)
// ContainerShow lists ContainerShow for each container. // ContainerShow lists ContainerShow for each container.
ContainerShow(context.Context, *ContainerShowParameter) (*ContainerShowResponse, error) ContainerShow(context.Context, *ContainerShowParameter) (*ContainerShowResponse, error)
// ContainerRename renames the container
ContainerRename(context.Context, *ContainerRenameParameter) (*emptypb.Empty, error)
// NodeAdd adds one or more nodes for management by Warewulf and returns // NodeAdd adds one or more nodes for management by Warewulf and returns
// the added nodes. Node fields may be shimmed in per profiles. // the added nodes. Node fields may be shimmed in per profiles.
NodeAdd(context.Context, *NodeAddParameter) (*NodeListResponse, error) NodeAdd(context.Context, *NodeAddParameter) (*NodeListResponse, error)
@@ -222,6 +235,9 @@ func (UnimplementedWWApiServer) ContainerList(context.Context, *emptypb.Empty) (
func (UnimplementedWWApiServer) ContainerShow(context.Context, *ContainerShowParameter) (*ContainerShowResponse, error) { func (UnimplementedWWApiServer) ContainerShow(context.Context, *ContainerShowParameter) (*ContainerShowResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ContainerShow not implemented") return nil, status.Errorf(codes.Unimplemented, "method ContainerShow not implemented")
} }
func (UnimplementedWWApiServer) ContainerRename(context.Context, *ContainerRenameParameter) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method ContainerRename not implemented")
}
func (UnimplementedWWApiServer) NodeAdd(context.Context, *NodeAddParameter) (*NodeListResponse, error) { func (UnimplementedWWApiServer) NodeAdd(context.Context, *NodeAddParameter) (*NodeListResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method NodeAdd not implemented") return nil, status.Errorf(codes.Unimplemented, "method NodeAdd not implemented")
} }
@@ -361,6 +377,24 @@ func _WWApi_ContainerShow_Handler(srv interface{}, ctx context.Context, dec func
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _WWApi_ContainerRename_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ContainerRenameParameter)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WWApiServer).ContainerRename(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/wwapi.v1.WWApi/ContainerRename",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WWApiServer).ContainerRename(ctx, req.(*ContainerRenameParameter))
}
return interceptor(ctx, in, info, handler)
}
func _WWApi_NodeAdd_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _WWApi_NodeAdd_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(NodeAddParameter) in := new(NodeAddParameter)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@@ -500,6 +534,10 @@ var WWApi_ServiceDesc = grpc.ServiceDesc{
MethodName: "ContainerShow", MethodName: "ContainerShow",
Handler: _WWApi_ContainerShow_Handler, Handler: _WWApi_ContainerShow_Handler,
}, },
{
MethodName: "ContainerRename",
Handler: _WWApi_ContainerRename_Handler,
},
{ {
MethodName: "NodeAdd", MethodName: "NodeAdd",
Handler: _WWApi_NodeAdd_Handler, Handler: _WWApi_NodeAdd_Handler,