Merge branch 'development' into lint-update
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# wwapiclient is intended as and example grpc wwapid client.
|
||||
Run wwapid to start the server
|
||||
Run wwapid to start the server
|
||||
```./wwapic``` will get the version.
|
||||
This works with or without mTLS.
|
||||
@@ -33,9 +33,9 @@ curl http://localhost:9871/v1/node?nodeNames=testnode1 # this works! case sensit
|
||||
curl http://localhost:9871/v1/node?nodeNames=testnode%5B1-2%5D
|
||||
|
||||
# node add single discoverable node
|
||||
curl -d '{"nodeNames": ["testApiNode0"], "discoverable": true}' -H "Content-Type: application/json" -X POST http://localhost:9871/v1/node
|
||||
curl -d '{"nodeNames": ["testApiNode0"], "discoverable": true}' -H "Content-Type: application/json" -X POST http://localhost:9871/v1/node
|
||||
|
||||
curl -d '{"nodeNames": ["testApiNode1"], "discoverable": true}' -H "Content-Type: application/json" -X POST http://localhost:9871/v1/node
|
||||
curl -d '{"nodeNames": ["testApiNode1"], "discoverable": true}' -H "Content-Type: application/json" -X POST http://localhost:9871/v1/node
|
||||
|
||||
|
||||
# list the node we just added
|
||||
@@ -45,7 +45,7 @@ curl http://localhost:9871/v1/node?nodeNames=testApiNode0
|
||||
curl -d '{"nodeNames": ["testApiNode0"], "ipmiIpAddr": "10.0.8.220", "updateMask": "ipmiIpAddr,nodeNames"}' -H "Content-Type: application/json" -X PATCH http://localhost:9871/v1/node
|
||||
|
||||
# Node set with post:
|
||||
curl -d '{"nodeNames": ["testApiNode0"], "ipmiIpaddr": "6.7.8.9"}' -H "Content-Type: application/json" -X POST http://localhost:9871/v1/nodeset
|
||||
curl -d '{"nodeNames": ["testApiNode0"], "ipmiIpaddr": "6.7.8.9"}' -H "Content-Type: application/json" -X POST http://localhost:9871/v1/nodeset
|
||||
|
||||
# node status
|
||||
curl http://localhost:9871/v1/nodestatus
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
package delete
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/container"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/routes/wwapiv1"
|
||||
"github.com/hpcng/warewulf/internal/pkg/api/util"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
||||
|
||||
cdp := &wwapiv1.ContainerDeleteParameter{
|
||||
ContainerNames: args,
|
||||
}
|
||||
if !SetYes {
|
||||
yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to container %s", args))
|
||||
if !yes {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
return container.ContainerDelete(cdp)
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
var (
|
||||
baseCmd = &cobra.Command{
|
||||
DisableFlagsInUseLine: true,
|
||||
Use: "delete [OPTIONS] CONTAINER [...]",
|
||||
Short: "Delete an imported container",
|
||||
Long: "This command will delete CONTAINERs that have been imported into Warewulf.",
|
||||
RunE: CobraRunE,
|
||||
Use: "delete [OPTIONS] CONTAINER [...]",
|
||||
Short: "Delete an imported container",
|
||||
Long: "This command will delete CONTAINERs 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
|
||||
@@ -20,10 +20,11 @@ var (
|
||||
return list, cobra.ShellCompDirectiveNoFileComp
|
||||
},
|
||||
}
|
||||
SetYes bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
baseCmd.PersistentFlags().BoolVarP(&SetYes, "yes", "y", false, "Set 'yes' to all questions asked")
|
||||
}
|
||||
|
||||
// GetRootCommand returns the root cobra.Command for the application.
|
||||
|
||||
@@ -8,7 +8,7 @@ var (
|
||||
Use: "import [OPTIONS] SOURCE [NAME]",
|
||||
Short: "Import a container into Warewulf",
|
||||
Long: `This command will pull and import a container into Warewulf from SOURCE,
|
||||
optionally renaming it to NAME. The SOURCE must be in a supported URI format. Formats
|
||||
optionally renaming it to NAME. The SOURCE must be in a supported URI format. Formats
|
||||
are:
|
||||
* docker://registry.example.org/example:latest
|
||||
* docker-daemon://example:latest
|
||||
|
||||
@@ -40,6 +40,8 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
|
||||
os.Setenv("WW_CONTAINER_SHELL", containerName)
|
||||
|
||||
if err := c.Run(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -10,7 +10,7 @@ var (
|
||||
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.
|
||||
Long: `Shows the base directory for the chroot of the given container.
|
||||
More information about the conainer can be shown with the '-a' option.`,
|
||||
RunE: CobraRunE,
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
|
||||
@@ -25,6 +25,9 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(nodeList) == 0 {
|
||||
return
|
||||
}
|
||||
yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to delete %d nodes(s)", len(nodeList)))
|
||||
if !yes {
|
||||
return
|
||||
|
||||
@@ -22,6 +22,7 @@ var (
|
||||
Long: "Management of node settings. All node ranges can use brackets to identify\n" +
|
||||
"node ranges. For example: n00[00-4].cluster[0-1] will identify the first 5 nodes\n" +
|
||||
"in cluster0 and cluster1.",
|
||||
Aliases: []string{"nodes"},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package edit
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -47,34 +50,69 @@ func CobraRunE(cmd *cobra.Command, args []string) error {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
newFile := false
|
||||
if !util.IsFile(overlayFile) && filepath.Ext(overlayFile) == ".ww" {
|
||||
wwlog.Verbose("This is a new file, creating some default content")
|
||||
|
||||
newFile = true
|
||||
w, err := os.OpenFile(overlayFile, os.O_RDWR|os.O_CREATE, os.FileMode(PermMode))
|
||||
if err != nil {
|
||||
wwlog.Warn("Could not create file for writing: %s", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "# This is a Warewulf Template file.\n")
|
||||
fmt.Fprintf(w, "#\n")
|
||||
fmt.Fprintf(w, "# This file (suffix '.ww') will be automatically rewritten without the suffix\n")
|
||||
fmt.Fprintf(w, "# when the overlay is rendered for the individual nodes. Here are some examples\n")
|
||||
fmt.Fprintf(w, "# of macros and logic which can be used within this file:\n")
|
||||
fmt.Fprintf(w, "#\n")
|
||||
fmt.Fprintf(w, "# Node FQDN = {{.Id}}\n")
|
||||
fmt.Fprintf(w, "# Node Cluster = {{.ClusterName}}\n")
|
||||
fmt.Fprintf(w, "# Network Config = {{.NetDevs.eth0.Ipaddr}}, {{.NetDevs.eth0.Hwaddr}}, etc.\n")
|
||||
fmt.Fprintf(w, "#\n")
|
||||
fmt.Fprintf(w, "# Goto the documentation pages for more information: http://www.hpcng.org/...\n")
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmtStr := `# This is a Warewulf Template file.
|
||||
#
|
||||
# This file (suffix '.ww') will be automatically rewritten without the suffix
|
||||
# when the overlay is rendered for the individual nodes. Here are some examples
|
||||
# of macros and logic which can be used within this file:
|
||||
#
|
||||
# Node FQDN = {{.Id}}\n")
|
||||
# Node Cluster = {{.ClusterName}}\n")
|
||||
# Network Config = {{.NetDevs.eth0.Ipaddr}}, {{.NetDevs.eth0.Hwaddr}}, etc.
|
||||
#
|
||||
# Goto the documentation pages for more information: http://www.hpcng.org/...
|
||||
# Keep the following for better reference
|
||||
# This file is autogenerated by warewulf
|
||||
# Host: {{.BuildHost}}
|
||||
# Time: {{.BuildTime}}
|
||||
# Source: {{.BuildSource}}
|
||||
`
|
||||
fmt.Fprint(w, fmtStr)
|
||||
w.Close()
|
||||
}
|
||||
|
||||
err := util.ExecInteractive(editor, overlayFile)
|
||||
fileDesc1, err := os.OpenFile(overlayFile, os.O_RDWR, os.FileMode(PermMode))
|
||||
if err != nil {
|
||||
wwlog.Warn("Could not open file for editing: %s", err)
|
||||
}
|
||||
defer fileDesc1.Close()
|
||||
_, _ = fileDesc1.Seek(0, 0)
|
||||
hasher := sha256.New()
|
||||
if _, err := io.Copy(hasher, fileDesc1); err != nil {
|
||||
wwlog.Error("Problems getting checksum of file %s\n", err)
|
||||
}
|
||||
sum1 := hex.EncodeToString(hasher.Sum(nil))
|
||||
fileDesc1.Close()
|
||||
err = util.ExecInteractive(editor, overlayFile)
|
||||
if err != nil {
|
||||
wwlog.Error("Editor process existed with non-zero")
|
||||
os.Exit(1)
|
||||
}
|
||||
fileDesc2, err := os.OpenFile(overlayFile, os.O_RDWR, os.FileMode(PermMode))
|
||||
if err != nil {
|
||||
wwlog.Warn("Could not open file for editing: %s", err)
|
||||
}
|
||||
defer fileDesc2.Close()
|
||||
_, _ = fileDesc2.Seek(0, 0)
|
||||
hasher.Reset()
|
||||
if _, err := io.Copy(hasher, fileDesc2); err != nil {
|
||||
wwlog.Error("Problems getting checksum of file %s\n", err)
|
||||
}
|
||||
sum2 := hex.EncodeToString(hasher.Sum(nil))
|
||||
if sum1 == sum2 && newFile {
|
||||
wwlog.Verbose("New template %s wasn't modified, deleting it", overlayFile)
|
||||
err = os.Remove(overlayFile)
|
||||
if err != nil {
|
||||
wwlog.Warn("Could not delete %s", overlayFile)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ func GetCommand() *cobra.Command {
|
||||
}); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
baseCmd.PersistentFlags().BoolVarP(&SetYes, "yes", "y", false, "Set 'yes' to all questions asked")
|
||||
|
||||
return baseCmd
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ var (
|
||||
Use: "profile COMMAND [OPTIONS]",
|
||||
Short: "Node configuration profile management",
|
||||
Long: "Management of node profile settings",
|
||||
Aliases: []string{"nodes"},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -34,14 +34,14 @@ func CobraRunE(cmd *cobra.Command, args []string) (err error) {
|
||||
}
|
||||
|
||||
if !SetYes {
|
||||
var nodeCount uint
|
||||
var profileCount uint
|
||||
// The checks run twice in the prompt case.
|
||||
// Avoiding putting in a blocking prompt in an API.
|
||||
_, nodeCount, err = apiprofile.ProfileSetParameterCheck(&set, false)
|
||||
_, profileCount, err = apiprofile.ProfileSetParameterCheck(&set, false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d nodes(s)", nodeCount))
|
||||
yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d profile(s)", profileCount))
|
||||
if !yes {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -293,14 +293,19 @@ func ContainerList() (containerInfo []*wwapiv1.ContainerInfo, err error) {
|
||||
|
||||
wwlog.Debug("Finding kernel version for: %s", source)
|
||||
kernelVersion := container.KernelVersion(source)
|
||||
|
||||
creationTime, err := os.Stat(container.SourceDir(source))
|
||||
var creationTime uint64
|
||||
sourceStat, err := os.Stat(container.SourceDir(source))
|
||||
if err != nil {
|
||||
wwlog.Error("%s\n", err)
|
||||
} else {
|
||||
creationTime = uint64(sourceStat.ModTime().Unix())
|
||||
}
|
||||
modTime, err := os.Stat(container.ImageFile(source))
|
||||
var modTime uint64
|
||||
imageStat, err := os.Stat(container.ImageFile(source))
|
||||
if err != nil {
|
||||
wwlog.Error("%s\n", err)
|
||||
} else {
|
||||
modTime = uint64(imageStat.ModTime().Unix())
|
||||
}
|
||||
size, err := util.DirSize(container.SourceDir(source))
|
||||
if err != nil {
|
||||
@@ -309,20 +314,22 @@ func ContainerList() (containerInfo []*wwapiv1.ContainerInfo, err error) {
|
||||
imgSize, err := os.Stat(container.ImageFile(source))
|
||||
if err != nil {
|
||||
wwlog.Error("%s\n", err)
|
||||
} else {
|
||||
size += imgSize.Size()
|
||||
}
|
||||
size += imgSize.Size()
|
||||
imgSize, err = os.Stat(container.ImageFile(source) + ".gz")
|
||||
if err != nil {
|
||||
wwlog.Error("%s\n", err)
|
||||
} else {
|
||||
size += imgSize.Size()
|
||||
}
|
||||
size += imgSize.Size()
|
||||
|
||||
containerInfo = append(containerInfo, &wwapiv1.ContainerInfo{
|
||||
Name: source,
|
||||
NodeCount: uint32(nodemap[source]),
|
||||
KernelVersion: kernelVersion,
|
||||
CreateDate: uint64(creationTime.ModTime().Unix()),
|
||||
ModDate: uint64(modTime.ModTime().Unix()),
|
||||
CreateDate: creationTime,
|
||||
ModDate: modTime,
|
||||
Size: uint64(size),
|
||||
})
|
||||
|
||||
|
||||
@@ -116,7 +116,11 @@ func NodeList(nodeGet *wwapiv1.GetNodeList) (nodeList wwapiv1.NodeList, err erro
|
||||
for j := 0; j < netInfoType.NumField(); j++ {
|
||||
netConfField, ok := netConfType.FieldByName(netInfoType.Field(j).Name)
|
||||
if ok {
|
||||
fieldName = netName + ":" + netConfField.Tag.Get("lopt")
|
||||
if netConfField.Tag.Get("lopt") != "nettagadd" {
|
||||
fieldName = netName + ":" + netConfField.Tag.Get("lopt")
|
||||
} else {
|
||||
fieldName = netName + ":tag"
|
||||
}
|
||||
} else {
|
||||
fieldName = netName + ":" + netInfoType.Field(j).Name
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ message NodeListResponse {
|
||||
// Request a node list
|
||||
message GetNodeList {
|
||||
enum ListType {
|
||||
Simple = 0;
|
||||
Simple = 0;
|
||||
Ipmi = 1;
|
||||
Network = 2;
|
||||
Long = 3;
|
||||
|
||||
@@ -15,22 +15,24 @@ import (
|
||||
Creates '/etc/hosts' from the host template.
|
||||
*/
|
||||
func Hostfile() error {
|
||||
if !(util.IsFile(path.Join(overlay.OverlaySourceDir("host"), "/host/etc/hosts.ww"))) {
|
||||
hostTemplate := path.Join(overlay.OverlaySourceDir("host"), "/etc/hosts.ww")
|
||||
if !(util.IsFile(hostTemplate)) {
|
||||
wwlog.Error("'the overlay template '/etc/hosts.ww' does not exists in 'host' overlay")
|
||||
os.Exit(1)
|
||||
}
|
||||
var nodeInfo node.NodeInfo
|
||||
tstruct := overlay.InitStruct(nodeInfo)
|
||||
|
||||
nodeInfo := node.NewInfo()
|
||||
hostname, _ := os.Hostname()
|
||||
nodeInfo.Id.Set(hostname)
|
||||
tstruct := overlay.InitStruct(nodeInfo)
|
||||
buffer, backupFile, writeFile, err := overlay.RenderTemplateFile(
|
||||
path.Join(overlay.OverlaySourceDir("host"), "/host/etc/hosts.ww"),
|
||||
hostTemplate,
|
||||
tstruct)
|
||||
if err != nil {
|
||||
wwlog.Error("%s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
info, err := os.Stat(path.Join(overlay.OverlaySourceDir("host"), "/host/etc/hosts.ww"))
|
||||
info, err := os.Stat(hostTemplate)
|
||||
if err != nil {
|
||||
wwlog.Error("%s", err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/pkg/util"
|
||||
"github.com/hpcng/warewulf/internal/pkg/wwlog"
|
||||
)
|
||||
|
||||
@@ -15,11 +15,11 @@ var (
|
||||
`vmlinuz`,
|
||||
`vmlinux-*`,
|
||||
`vmlinuz-*`,
|
||||
`vmlinuz.gz` }
|
||||
`vmlinuz.gz`}
|
||||
|
||||
kernelDirs = []string{
|
||||
`/lib/modules/*/`,
|
||||
`/boot/` }
|
||||
`/boot/`}
|
||||
)
|
||||
|
||||
func KernelFind(container string) string {
|
||||
@@ -48,7 +48,11 @@ func KernelFind(container string) string {
|
||||
|
||||
for _, kernelPath := range kernelPaths {
|
||||
wwlog.Debug("Checking for kernel path: %s", kernelPath)
|
||||
if util.IsFile(kernelPath) {
|
||||
// Only succeeds if kernelPath exists and, if a
|
||||
// symlink, links to a path that also exists
|
||||
kernelPath, err = filepath.EvalSymlinks(kernelPath)
|
||||
if err == nil {
|
||||
wwlog.Debug("found kernel: %s", kernelPath)
|
||||
return kernelPath
|
||||
}
|
||||
}
|
||||
@@ -65,5 +69,10 @@ func KernelVersion(container string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
return path.Base(path.Dir(kernel))
|
||||
ret := path.Base(path.Dir(kernel))
|
||||
if ret == "boot" {
|
||||
ret = path.Base(kernel)
|
||||
}
|
||||
|
||||
return strings.TrimPrefix(ret, "vmlinuz-")
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
|
||||
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}
|
||||
{{end}}{{if .HasAvailableSubCommands}}
|
||||
Use "{{.CommandPath}} COMMAND --help" for more information about a COMMAND.
|
||||
{{end}}
|
||||
{{end}}
|
||||
`
|
||||
// End UsageTemplate
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ var ConfigFile string
|
||||
var DefaultConfig string
|
||||
|
||||
// used as fallback if DefaultConfig can't be read
|
||||
var FallBackConf = `
|
||||
var FallBackConf = `---
|
||||
defaultnode:
|
||||
runtime overlay:
|
||||
- generic
|
||||
@@ -27,6 +27,7 @@ defaultnode:
|
||||
args: quiet crashkernel=no vga=791 net.naming-scheme=v238
|
||||
init: /sbin/init
|
||||
root: initramfs
|
||||
ipxe template: default
|
||||
profiles:
|
||||
- default
|
||||
network devices:
|
||||
@@ -78,7 +79,7 @@ func (config *NodeYaml) FindAllNodes() ([]NodeInfo, error) {
|
||||
}
|
||||
*/
|
||||
var defConf map[string]*NodeConf
|
||||
wwlog.Verbose("Opening defaults failed %s\n", DefaultConfig)
|
||||
wwlog.Verbose("Opening defaults from file failed %s\n", DefaultConfig)
|
||||
defData, err := os.ReadFile(DefaultConfig)
|
||||
if err != nil {
|
||||
wwlog.Verbose("Couldn't read DefaultConfig :%s\n", err)
|
||||
|
||||
@@ -2,6 +2,7 @@ package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -200,13 +201,20 @@ func (ent *Entry) Get() string {
|
||||
Get the bool value of an entry.
|
||||
*/
|
||||
func (ent *Entry) GetB() bool {
|
||||
if len(ent.value) == 0 || ent.value[0] == "false" || ent.value[0] == "no" {
|
||||
if len(ent.altvalue) == 0 || ent.altvalue[0] == "false" || ent.altvalue[0] == "no" {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
if len(ent.value) > 0 {
|
||||
return !(strings.ToLower(ent.value[0]) == "false" ||
|
||||
strings.ToLower(ent.value[0]) == "no" ||
|
||||
ent.value[0] == "0")
|
||||
} else if len(ent.altvalue) > 0 {
|
||||
return !(strings.ToLower(ent.altvalue[0]) == "false" ||
|
||||
strings.ToLower(ent.altvalue[0]) == "no" ||
|
||||
ent.altvalue[0] == "0")
|
||||
} else {
|
||||
return !(len(ent.def) == 0 ||
|
||||
strings.ToLower(ent.def[0]) == "false" ||
|
||||
strings.ToLower(ent.def[0]) == "no" ||
|
||||
ent.def[0] == "0")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -334,6 +342,32 @@ Create an empty node NodeConf
|
||||
func NewConf() (nodeconf NodeConf) {
|
||||
nodeconf.Ipmi = new(IpmiConf)
|
||||
nodeconf.Kernel = new(KernelConf)
|
||||
nodeconf.NetDevs = map[string]*NetDevs{}
|
||||
nodeconf.NetDevs = make(map[string]*NetDevs)
|
||||
return nodeconf
|
||||
}
|
||||
|
||||
/*
|
||||
Create an empty node NodeInfo
|
||||
*/
|
||||
func NewInfo() (nodeInfo NodeInfo) {
|
||||
nodeInfo.Ipmi = new(IpmiEntry)
|
||||
nodeInfo.Kernel = new(KernelEntry)
|
||||
nodeInfo.NetDevs = make(map[string]*NetDevEntry)
|
||||
return nodeInfo
|
||||
}
|
||||
|
||||
/*
|
||||
Get a entry by its name
|
||||
*/
|
||||
func GetByName(node interface{}, name string) (string, error) {
|
||||
valEntry := reflect.ValueOf(node)
|
||||
entryField := valEntry.Elem().FieldByName(name)
|
||||
if entryField == (reflect.Value{}) {
|
||||
return "", fmt.Errorf("couldn't find field with name: %s", name)
|
||||
}
|
||||
if entryField.Type() != reflect.TypeOf(Entry{}) {
|
||||
return "", fmt.Errorf("field %s is not of type node.Entry", name)
|
||||
}
|
||||
myEntry := entryField.Interface().(Entry)
|
||||
return myEntry.Get(), nil
|
||||
}
|
||||
|
||||
37
internal/pkg/node/transformer_test.go
Normal file
37
internal/pkg/node/transformer_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_nodeYaml_SetFrom(t *testing.T) {
|
||||
c, _ := NewTestNode()
|
||||
singleNodeConf := c.Nodes["test_node"]
|
||||
singleNodeInfo := NewInfo()
|
||||
singleNodeInfo.SetFrom(singleNodeConf)
|
||||
tests := []struct {
|
||||
name string
|
||||
arg string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{"Right comment", "Comment", "Node Comment", false},
|
||||
{"FieldName", "comment", "NodeComment", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := GetByName(&singleNodeInfo, tt.arg)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetByName(%s,%s) error = %v, wantErr %v",
|
||||
reflect.TypeOf(singleNodeConf), tt.arg, err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if (got != tt.want) != tt.wantErr {
|
||||
t.Errorf("GetByName(%s,%s) got = %v, want = %v",
|
||||
reflect.TypeOf(singleNodeConf), tt.arg, got, tt.want)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ func (nodeConf *NodeConf) GetFrom(nodeInfo NodeInfo) {
|
||||
}
|
||||
|
||||
/*
|
||||
Abstract function which populates a NodeConf form the given NodeInfo
|
||||
Abstract function which populates a NodeConf from the given NodeInfo
|
||||
via getter functions.
|
||||
*/
|
||||
func (nodeConf *NodeConf) getterFrom(nodeInfo NodeInfo,
|
||||
@@ -72,7 +72,8 @@ func (nodeConf *NodeConf) getterFrom(nodeInfo NodeInfo,
|
||||
for key, val := range entryMap {
|
||||
confMap[key] = getter(val)
|
||||
}
|
||||
} else if nodeInfoVal.Field(i).Type().Kind() == reflect.Ptr {
|
||||
} else if nodeInfoVal.Field(i).Type().Kind() == reflect.Ptr && !nodeInfoVal.Field(i).IsNil() {
|
||||
// initialize the nested NodeConf structs, but only if these will be set
|
||||
if confField.Addr().Elem().IsZero() {
|
||||
switch confField.Addr().Elem().Type() {
|
||||
case reflect.TypeOf((*KernelConf)(nil)):
|
||||
@@ -164,6 +165,10 @@ func (nodeConf *NodeConf) getterFrom(nodeInfo NodeInfo,
|
||||
} else if netVal.Type() == reflect.TypeOf(map[string]*Entry{}) {
|
||||
entryMap := netVal.Interface().(map[string](*Entry))
|
||||
confMap := netConfVal.Elem().Field(j).Interface().(map[string]string)
|
||||
if confMap == nil {
|
||||
confMapPtr := netConfVal.Elem().Field(j).Addr().Interface().(*map[string]string)
|
||||
*confMapPtr = make(map[string]string)
|
||||
}
|
||||
if len(confMap) > len(entryMap) {
|
||||
for confMapKey := range confMap {
|
||||
foundKey := false
|
||||
@@ -337,6 +342,11 @@ func (node *NodeInfo) setterFrom(n *NodeConf, nameArg string,
|
||||
if node.Ipmi == nil {
|
||||
node.Ipmi = new(IpmiEntry)
|
||||
}
|
||||
// also n could be nil
|
||||
if n == nil {
|
||||
myn := NewConf()
|
||||
n = &myn
|
||||
}
|
||||
nodeInfoVal := reflect.ValueOf(node)
|
||||
nodeInfoType := reflect.TypeOf(node)
|
||||
nodeConfVal := reflect.ValueOf(n)
|
||||
@@ -505,6 +515,10 @@ Abstract function for setting a NetDevEntry from a NetDevs
|
||||
func (netDev *NetDevEntry) setterFrom(netYaml *NetDevs, nameArg string,
|
||||
setter func(*Entry, string, string),
|
||||
setterSlice func(*Entry, []string, string)) {
|
||||
// check if netYaml is empty
|
||||
if netYaml == nil {
|
||||
netYaml = new(NetDevs)
|
||||
}
|
||||
netValues := reflect.ValueOf(netDev)
|
||||
netInfoType := reflect.TypeOf(*netYaml)
|
||||
netInfoVal := reflect.ValueOf(*netYaml)
|
||||
|
||||
@@ -13,6 +13,7 @@ nodeprofiles:
|
||||
comment: This profile is automatically included for each node
|
||||
nodes:
|
||||
test_node:
|
||||
comment: Node Comment
|
||||
profiles:
|
||||
- default
|
||||
network devices:
|
||||
|
||||
@@ -130,8 +130,9 @@ func (p *puller) Pull(ctx context.Context, uri, dst string) (err error) {
|
||||
|
||||
// copy to cache location
|
||||
_, err = copy.Image(ctx, policyCtx, cacheRef, srcRef, ©.Options{
|
||||
ReportWriter: os.Stdout,
|
||||
SourceCtx: p.sysCtx,
|
||||
ReportWriter: os.Stdout,
|
||||
SourceCtx: p.sysCtx,
|
||||
RemoveSignatures: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -60,8 +60,6 @@ func InitStruct(nodeInfo node.NodeInfo) TemplateStruct {
|
||||
// init some convininence vars
|
||||
tstruct.Id = nodeInfo.Id.Get()
|
||||
tstruct.Hostname = nodeInfo.Id.Get()
|
||||
tstruct.Id = nodeInfo.Id.Get()
|
||||
tstruct.Hostname = nodeInfo.Id.Get()
|
||||
// Backwards compatibility for templates using "Keys"
|
||||
tstruct.AllNodes = allNodes
|
||||
tstruct.Nfs = *controller.Nfs
|
||||
@@ -83,6 +81,7 @@ func InitStruct(nodeInfo node.NodeInfo) TemplateStruct {
|
||||
dt := time.Now()
|
||||
tstruct.BuildTime = dt.Format("01-02-2006 15:04:05 MST")
|
||||
tstruct.BuildTimeUnix = strconv.FormatInt(dt.Unix(), 10)
|
||||
tstruct.NodeConf.Tags = map[string]string{}
|
||||
tstruct.NodeConf.GetFrom(nodeInfo)
|
||||
// FIXME: Set ipCIDR address at this point, will fail with
|
||||
// invalid ipv4 addr
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
"text/template"
|
||||
|
||||
"github.com/hpcng/warewulf/internal/pkg/node"
|
||||
@@ -48,13 +49,13 @@ func BuildAllOverlays(nodes []node.NodeInfo) error {
|
||||
wwlog.Info("Building system overlays for %s: [%s]", n.Id.Get(), strings.Join(sysOverlays, ", "))
|
||||
err := BuildOverlay(n, sysOverlays)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not build system overlays %v for nide %s", sysOverlays, n.Id.Get())
|
||||
return errors.Wrapf(err, "could not build system overlays %v for node %s", sysOverlays, n.Id.Get())
|
||||
}
|
||||
runOverlays := n.RuntimeOverlay.GetSlice()
|
||||
wwlog.Info("Building runtime overlays for %s: [%s]", n.Id.Get(), strings.Join(runOverlays, ", "))
|
||||
err = BuildOverlay(n, runOverlays)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not build runtime overlays %v for nide %s", runOverlays, n.Id.Get())
|
||||
return errors.Wrapf(err, "could not build runtime overlays %v for node %s", runOverlays, n.Id.Get())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -80,14 +81,11 @@ func BuildSpecificOverlays(nodes []node.NodeInfo, overlayNames []string) error {
|
||||
Build overlay for the host, so no argument needs to be given
|
||||
*/
|
||||
func BuildHostOverlay() error {
|
||||
var host node.NodeInfo
|
||||
host.Kernel = new(node.KernelEntry)
|
||||
host.Ipmi = new(node.IpmiEntry)
|
||||
var idEntry node.Entry
|
||||
host := node.NewInfo()
|
||||
hostname, _ := os.Hostname()
|
||||
host.Id.Set(hostname)
|
||||
|
||||
wwlog.Info("Building overlay for %s: host", hostname)
|
||||
idEntry.Set(hostname)
|
||||
host.Id = idEntry
|
||||
hostdir := OverlaySourceDir("host")
|
||||
stats, err := os.Stat(hostdir)
|
||||
if err != nil {
|
||||
@@ -198,6 +196,9 @@ func BuildOverlayIndir(nodeInfo node.NodeInfo, overlayNames []string, outputDir
|
||||
return errors.Errorf("overlay names contains illegal characters: %v", overlayNames)
|
||||
}
|
||||
|
||||
// Temporarily set umask to 0000, so directories in the overlay retain permissions
|
||||
defer syscall.Umask(syscall.Umask(0))
|
||||
|
||||
wwlog.Verbose("Processing node/overlay: %s/%s", nodeInfo.Id.Get(), strings.Join(overlayNames, "-"))
|
||||
for _, overlayName := range overlayNames {
|
||||
wwlog.Verbose("Building overlay %s for node %s in %s", overlayName, nodeInfo.Id.Get(), outputDir)
|
||||
|
||||
@@ -596,10 +596,14 @@ func FileGz(
|
||||
compressor, err := exec.LookPath("pigz")
|
||||
if err != nil {
|
||||
wwlog.Verbose("Could not locate PIGZ")
|
||||
compressor = "gzip"
|
||||
compressor, err = exec.LookPath("gzip")
|
||||
if err != nil {
|
||||
wwlog.Verbose("Could not locate GZIP")
|
||||
return errors.Wrapf(err, "No compressor program for image file: %s", file_gz)
|
||||
}
|
||||
}
|
||||
|
||||
wwlog.Verbose("Using gz compressor: %s", compressor)
|
||||
wwlog.Verbose("Using compressor program: %s", compressor)
|
||||
|
||||
proc := exec.Command(
|
||||
compressor,
|
||||
@@ -608,6 +612,52 @@ func FileGz(
|
||||
|
||||
out, err := proc.CombinedOutput()
|
||||
if len(out) > 0 {
|
||||
outStr := string(out[:])
|
||||
if err != nil && strings.HasSuffix(compressor, "gzip") && strings.Contains(outStr, "unrecognized option") {
|
||||
var gzippedFile *os.File
|
||||
var gzipStderr io.ReadCloser
|
||||
|
||||
/* Older version of gzip, try it another way: */
|
||||
wwlog.Verbose("%s does not recognize the --keep flag, trying redirected stdout", compressor)
|
||||
|
||||
/* Open the output file for writing: */
|
||||
gzippedFile, err = os.Create(file_gz)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Unable to open compressed image file for writing: %s", file_gz)
|
||||
}
|
||||
|
||||
/* We'll execute gzip with output to stdout and attach stdout to the compressed file we just
|
||||
created:
|
||||
*/
|
||||
proc = exec.Command(
|
||||
compressor,
|
||||
"--stdout",
|
||||
file )
|
||||
proc.Stdout = gzippedFile
|
||||
gzipStderr, err = proc.StderrPipe()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Unable to open stderr pipe for compression program: %s", compressor)
|
||||
}
|
||||
|
||||
/* Execute the command: */
|
||||
err = proc.Start()
|
||||
if err != nil {
|
||||
_ = proc.Wait()
|
||||
gzippedFile.Close()
|
||||
os.Remove(file_gz)
|
||||
err = errors.Wrapf(err, "Unable to successfully execute compression program: %s", compressor)
|
||||
} else {
|
||||
err = proc.Wait()
|
||||
gzippedFile.Close()
|
||||
if err != nil {
|
||||
os.Remove(file_gz)
|
||||
err = errors.Wrapf(err, "Unable to successfully create compressed image file: %s", file_gz)
|
||||
} else {
|
||||
wwlog.Verbose("Successfully compressed image file: %s", file_gz)
|
||||
}
|
||||
}
|
||||
out, _ = io.ReadAll(gzipStderr)
|
||||
}
|
||||
wwlog.Debug(string(out))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user