Initial cut of wwapi.

We need an API for Warewulf in order to automate around it. The initial version of the API will look a lot like wwctl. wwctl will call the API so that we do not need to maintain separate code paths. In this preview wwctl calls the API via direct function call, but we can change that to a gprc or REST call to a Warewulf server in the future.

This commit contains:
wwapid WareWulf API Daemon. A grpc server with mTLS auth.
wwapic WareWulf API Client. A grpc client with mTLS auth. It's just a sample that reads the version from the server.
wwapird WareWulf API Rest Daemon. A http REST reverse proxy to wwapid.
There are also sample insecure and secure curls to test with.

Implemented Functionality:
wwctl node add
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

Some notes on the files:

    Logic that was in wwctl has moved to warewulf/internal/pkg/api. wwctl just calls the API. wwctl functionality is unchanged.
    Everything under the /google directory is copied google proto code to stand up wwapird. It's a bit strange to copy in the code, but that is currently how it's done.
    Everything in warewulf/internal/pkg/api/routes/wwapiv1 is generated code.

There are some loose ends here, such as no service installers. I just ran off the command line for development.
The Makefile could use improvement. I'm building by make clean setup proto all build wwapid wwapic wwapird ; echo $?
I copied the configs from warewulf/etc to /usr/local/etc/warewulf manually.
This commit is contained in:
MatthewHink
2022-05-13 15:12:32 -04:00
parent d976e7fc31
commit d8cd6049ac
57 changed files with 8842 additions and 1157 deletions

View File

@@ -2,413 +2,71 @@ package set
import (
"fmt"
"os"
"strings"
"github.com/hpcng/warewulf/internal/pkg/node"
"github.com/hpcng/warewulf/internal/pkg/util"
"github.com/hpcng/warewulf/internal/pkg/warewulfd"
"github.com/hpcng/warewulf/internal/pkg/wwlog"
"github.com/manifoldco/promptui"
"github.com/pkg/errors"
"github.com/hpcng/warewulf/internal/pkg/api/node"
"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) error {
var err error
var count uint
nodeDB, err := node.New()
if err != nil {
wwlog.Printf(wwlog.ERROR, "Could not open node configuration: %s\n", err)
os.Exit(1)
func CobraRunE(cmd *cobra.Command, args []string) (err error) {
set := wwapiv1.NodeSetParameter{
Comment: SetComment,
Container: SetContainer,
KernelOverride: SetKernelOverride,
KernelArgs: SetKernelArgs,
Netname: SetNetName,
Netdev: SetNetDev,
Ipaddr: SetIpaddr,
Netmask: SetNetmask,
Gateway: SetGateway,
Hwaddr: SetHwaddr,
Type: SetType,
Onboot: SetNetOnBoot,
NetDefault: SetNetPrimary,
NetdevDelete: SetNetDevDel,
Cluster: SetClusterName,
Ipxe: SetIpxe,
InitOverlay: SetInitOverlay,
RuntimeOverlay: SetRuntimeOverlay,
SystemOverlay: SetSystemOverlay,
IpmiIpaddr: SetIpmiIpaddr,
IpmiNetmask: SetIpmiNetmask,
IpmiPort: SetIpmiPort,
IpmiGateway: SetIpmiGateway,
IpmiUsername: SetIpmiUsername,
IpmiPassword: SetIpmiPassword,
IpmiInterface: SetIpmiInterface,
IpmiWrite: SetIpmiWrite,
AllNodes: SetNodeAll,
Profile: SetProfile,
ProfileAdd: SetAddProfile,
ProfileDelete: SetDelProfile,
Force: SetForce,
Init: SetInit,
Discoverable: SetDiscoverable,
Undiscoverable: SetUndiscoverable,
Root: SetRoot,
Tags: SetTags,
TagsDelete: SetDelTags,
AssetKey: SetAssetKey,
NodeNames: args,
NetTags: SetNetTags,
NetDeleteTags: SetNetDelTags,
}
nodes, err := nodeDB.FindAllNodes()
if err != nil {
wwlog.Printf(wwlog.ERROR, "Could not get node list: %s\n", err)
os.Exit(1)
}
if SetNodeAll || (len(args) == 0 && len(nodes) > 0) {
fmt.Printf("\n*** WARNING: This command will modify all nodes! ***\n\n")
} else {
nodes = node.FilterByName(nodes, args)
}
if len(nodes) == 0 {
fmt.Printf("No nodes found\n")
os.Exit(1)
}
for _, n := range nodes {
wwlog.Printf(wwlog.VERBOSE, "Evaluating node: %s\n", n.Id.Get())
if SetComment != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting comment to: %s\n", n.Id.Get(), SetComment)
n.Comment.Set(SetComment)
}
if SetContainer != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting container name to: %s\n", n.Id.Get(), SetContainer)
n.ContainerName.Set(SetContainer)
}
if SetInit != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting init command to: %s\n", n.Id.Get(), SetInit)
n.Init.Set(SetInit)
}
if SetRoot != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting root to: %s\n", n.Id.Get(), SetRoot)
n.Root.Set(SetRoot)
}
if SetAssetKey != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting asset key to: %s\n", n.Id.Get(), SetAssetKey)
n.AssetKey.Set(SetAssetKey)
}
if SetKernelOverride != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting kernel override to: %s\n", n.Id.Get(), SetKernelOverride)
n.Kernel.Override.Set(SetKernelOverride)
}
if SetKernelArgs != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting kernel args to: %s\n", n.Id.Get(), SetKernelArgs)
n.Kernel.Args.Set(SetKernelArgs)
}
if SetClusterName != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting cluster name to: %s\n", n.Id.Get(), SetClusterName)
n.ClusterName.Set(SetClusterName)
}
if SetIpxe != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting iPXE template to: %s\n", n.Id.Get(), SetIpxe)
n.Ipxe.Set(SetIpxe)
}
if len(SetRuntimeOverlay) != 0 {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting runtime overlay to: %s\n", n.Id.Get(), SetRuntimeOverlay)
n.RuntimeOverlay.SetSlice(SetRuntimeOverlay)
}
if len(SetSystemOverlay) != 0 {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting system overlay to: %s\n", n.Id.Get(), SetSystemOverlay)
n.SystemOverlay.SetSlice(SetSystemOverlay)
}
if SetIpmiIpaddr != "" {
NewIpaddr := util.IncrementIPv4(SetIpmiIpaddr, count)
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting IPMI IP address to: %s\n", n.Id.Get(), NewIpaddr)
n.Ipmi.Ipaddr.Set(NewIpaddr)
}
if SetIpmiNetmask != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting IPMI netmask to: %s\n", n.Id.Get(), SetIpmiNetmask)
n.Ipmi.Netmask.Set(SetIpmiNetmask)
}
if SetIpmiPort != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting IPMI port to: %s\n", n.Id.Get(), SetIpmiPort)
n.Ipmi.Port.Set(SetIpmiPort)
}
if SetIpmiGateway != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting IPMI gateway to: %s\n", n.Id.Get(), SetIpmiGateway)
n.Ipmi.Gateway.Set(SetIpmiGateway)
}
if SetIpmiUsername != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting IPMI IP username to: %s\n", n.Id.Get(), SetIpmiUsername)
n.Ipmi.UserName.Set(SetIpmiUsername)
}
if SetIpmiPassword != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting IPMI IP password to: %s\n", n.Id.Get(), SetIpmiPassword)
n.Ipmi.Password.Set(SetIpmiPassword)
}
if SetIpmiInterface != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting IPMI IP interface to: %s\n", n.Id.Get(), SetIpmiInterface)
n.Ipmi.Interface.Set(SetIpmiInterface)
}
if SetIpmiWrite == "yes" || SetNetOnBoot == "y" || SetNetOnBoot == "1" || SetNetOnBoot == "true" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting Ipmiwrite to %s\n", n.Id.Get(), SetIpmiWrite)
n.Ipmi.Write.SetB(true)
} else {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting Ipmiwrite to %s\n", n.Id.Get(), SetIpmiWrite)
n.Ipmi.Write.SetB(false)
}
if SetDiscoverable {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting node to discoverable\n", n.Id.Get())
n.Discoverable.SetB(true)
}
if SetUndiscoverable {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting node to undiscoverable\n", n.Id.Get())
n.Discoverable.SetB(false)
}
if SetProfile != "" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting profiles to: %s\n", n.Id.Get(), SetProfile)
n.Profiles = []string{SetProfile}
}
if len(SetAddProfile) > 0 {
for _, p := range SetAddProfile {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, adding profile '%s'\n", n.Id.Get(), p)
n.Profiles = util.SliceAddUniqueElement(n.Profiles, p)
}
}
if len(SetDelProfile) > 0 {
for _, p := range SetDelProfile {
wwlog.Printf(wwlog.VERBOSE, "Node: %s, deleting profile '%s'\n", n.Id.Get(), p)
n.Profiles = util.SliceRemoveElement(n.Profiles, p)
}
}
if SetNetName != "" {
if _, ok := n.NetDevs[SetNetName]; !ok {
var nd node.NetDevEntry
nd.Tags = make(map[string]*node.Entry)
n.NetDevs[SetNetName] = &nd
if SetNetDev == "" {
n.NetDevs[SetNetName].Device.Set(SetNetName)
}
}
var def bool = true
SetNetOnBoot = "yes"
for _, n := range n.NetDevs {
if n.Primary.GetB() {
def = false
}
}
if def {
SetNetPrimary = "yes"
}
}
if SetNetDev != "" {
if SetNetName == "" {
wwlog.Printf(wwlog.ERROR, "You must include the '--netname' option\n")
os.Exit(1)
}
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Setting net Device to: %s\n", n.Id.Get(), SetNetName, SetNetDev)
n.NetDevs[SetNetName].Device.Set(SetNetDev)
}
if SetIpaddr != "" {
if SetNetName == "" {
wwlog.Printf(wwlog.ERROR, "You must include the '--netname' option\n")
os.Exit(1)
}
NewIpaddr := util.IncrementIPv4(SetIpaddr, count)
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Setting Ipaddr to: %s\n", n.Id.Get(), SetNetName, NewIpaddr)
n.NetDevs[SetNetName].Ipaddr.Set(NewIpaddr)
}
if SetNetmask != "" {
if SetNetName == "" {
wwlog.Printf(wwlog.ERROR, "You must include the '--netname' option\n")
os.Exit(1)
}
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Setting netmask to: %s\n", n.Id.Get(), SetNetName, SetNetmask)
n.NetDevs[SetNetName].Netmask.Set(SetNetmask)
}
if SetGateway != "" {
if SetNetName == "" {
wwlog.Printf(wwlog.ERROR, "You must include the '--netname' option\n")
os.Exit(1)
}
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Setting gateway to: %s\n", n.Id.Get(), SetNetName, SetGateway)
n.NetDevs[SetNetName].Gateway.Set(SetGateway)
}
if SetHwaddr != "" {
if SetNetName == "" {
wwlog.Printf(wwlog.ERROR, "You must include the '--netname' option\n")
os.Exit(1)
}
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Setting HW address to: %s\n", n.Id.Get(), SetNetName, SetHwaddr)
n.NetDevs[SetNetName].Hwaddr.Set(SetHwaddr)
}
if SetType != "" {
if SetNetName == "" {
wwlog.Printf(wwlog.ERROR, "You must include the '--netname' option\n")
os.Exit(1)
}
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Setting Type %s\n", n.Id.Get(), SetNetName, SetType)
n.NetDevs[SetNetName].Type.Set(SetType)
}
if SetNetOnBoot != "" {
if SetNetName == "" {
wwlog.Printf(wwlog.ERROR, "You must include the '--netname' option\n")
os.Exit(1)
}
if SetNetOnBoot == "yes" || SetNetOnBoot == "y" || SetNetOnBoot == "1" || SetNetOnBoot == "true" {
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Setting ONBOOT\n", n.Id.Get(), SetNetName)
n.NetDevs[SetNetName].OnBoot.SetB(true)
} else {
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Unsetting ONBOOT\n", n.Id.Get(), SetNetName)
n.NetDevs[SetNetName].OnBoot.SetB(false)
}
}
if SetNetPrimary != "" {
if SetNetName == "" {
wwlog.Printf(wwlog.ERROR, "You must include the '--netname' option\n")
os.Exit(1)
}
if SetNetPrimary == "yes" || SetNetPrimary == "y" || SetNetPrimary == "1" || SetNetPrimary == "true" {
// Set all other devices to non-default
for _, n := range n.NetDevs {
n.Primary.SetB(false)
}
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Setting PRIMARY\n", n.Id.Get(), SetNetName)
n.NetDevs[SetNetName].Primary.SetB(true)
} else {
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Unsetting PRIMARY\n", n.Id.Get(), SetNetName)
n.NetDevs[SetNetName].Primary.SetB(false)
}
}
if SetNetDevDel {
if SetNetName == "" {
wwlog.Printf(wwlog.ERROR, "You must include the '--netname' option\n")
os.Exit(1)
}
if _, ok := n.NetDevs[SetNetName]; !ok {
wwlog.Printf(wwlog.ERROR, "Network device name doesn't exist: %s\n", SetNetName)
os.Exit(1)
}
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Deleting network device: %s\n", n.Id.Get(), SetNetName)
delete(n.NetDevs, SetNetName)
}
if len(SetTags) > 0 {
for _, t := range SetTags {
keyval := strings.SplitN(t, "=", 2)
key := keyval[0]
val := keyval[1]
if _, ok := n.Tags[key]; !ok {
var nd node.Entry
n.Tags[key] = &nd
}
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Setting Tag '%s'='%s'\n", n.Id.Get(), key, val)
n.Tags[key].Set(val)
}
}
if len(SetDelTags) > 0 {
for _, t := range SetDelTags {
keyval := strings.SplitN(t, "=", 1)
key := keyval[0]
if _, ok := n.Tags[key]; !ok {
wwlog.Printf(wwlog.WARN, "Key does not exist: %s\n", key)
os.Exit(1)
}
wwlog.Printf(wwlog.VERBOSE, "Node: %s, Deleting tag: %s\n", n.Id.Get(), key)
delete(n.Tags, key)
}
}
if len(SetNetTags) > 0 {
for _, t := range SetNetTags {
keyval := strings.SplitN(t, "=", 2)
key := keyval[0]
val := keyval[1]
if _, ok := n.NetDevs[SetNetName].Tags[key]; !ok {
var nd node.Entry
n.NetDevs[SetNetName].Tags[key] = &nd
}
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Setting NETTAG '%s'='%s'\n", n.Id.Get(), SetNetName, key, val)
n.NetDevs[SetNetName].Tags[key].Set(val)
}
}
if len(SetNetDelTags) > 0 {
for _, t := range SetNetDelTags {
keyval := strings.SplitN(t, "=", 1)
key := keyval[0]
if _, ok := n.NetDevs[SetNetName].Tags[key]; !ok {
wwlog.Printf(wwlog.WARN, "Node: %s:%s Key %s does not exist\n", n.Id.Get(), SetNetName, key)
os.Exit(1)
}
wwlog.Printf(wwlog.VERBOSE, "Node: %s:%s, Deleting Tag %s\n", n.Id.Get(), SetNetName, key)
delete(n.NetDevs[SetNetName].Tags, key)
}
}
err := nodeDB.NodeUpdate(n)
if !SetYes {
var nodeCount uint
// The checks run twice in the prompt case.
// Avoiding putting in a blocking prompt in an API.
_, nodeCount, err = node.NodeSetParameterCheck(&set, false)
if err != nil {
wwlog.Printf(wwlog.ERROR, "%s\n", err)
os.Exit(1)
return
}
count++
}
if SetYes {
err := nodeDB.Persist()
if err != nil {
return errors.Wrap(err, "failed to persist nodedb")
}
err = warewulfd.DaemonReload()
if err != nil {
return errors.Wrap(err, "failed to reload warewulf daemon")
}
} else {
q := fmt.Sprintf("Are you sure you want to modify %d nodes(s)", len(nodes))
prompt := promptui.Prompt{
Label: q,
IsConfirm: true,
}
result, _ := prompt.Run()
if result == "y" || result == "yes" {
err := nodeDB.Persist()
if err != nil {
return errors.Wrap(err, "failed to persist nodedb")
}
err = warewulfd.DaemonReload()
if err != nil {
return errors.Wrap(err, "failed to reload warewulf daemon")
}
yes := util.ConfirmationPrompt(fmt.Sprintf("Are you sure you want to modify %d nodes(s)", nodeCount))
if !yes {
return
}
}
return nil
return node.NodeSet(&set)
}